Rewrite of sticky effects system, to allow any other effect to be sticky
This commit is contained in:
parent
0a822e705e
commit
875b71828d
1
TODO
1
TODO
|
@ -1,6 +1,5 @@
|
||||||
* Use succession's tools and serializer
|
* Use succession's tools and serializer
|
||||||
* Effect should be random in a range (eg. "damage target 50-75")
|
* Effect should be random in a range (eg. "damage target 50-75")
|
||||||
* Rewrite sticky effects to be more flexible (should be able to stick any effect)
|
|
||||||
* Add auto-move to attack
|
* Add auto-move to attack
|
||||||
* Add equipment info (or summary) in ship tooltip
|
* Add equipment info (or summary) in ship tooltip
|
||||||
* Handle effects overflowing ship tooltip when too numerous
|
* Handle effects overflowing ship tooltip when too numerous
|
||||||
|
|
|
@ -35,11 +35,7 @@ module SpaceTac.Game {
|
||||||
|
|
||||||
// Generate an effect with a given power
|
// Generate an effect with a given power
|
||||||
generateFixed(power: number): BaseEffect {
|
generateFixed(power: number): BaseEffect {
|
||||||
var effect = Tools.copyObject(this.effect);
|
return this.effect.getModifiedCopy(this.modifiers, power);
|
||||||
this.modifiers.forEach((modifier: EffectTemplateModifier) => {
|
|
||||||
effect[modifier.name] = modifier.range.getProportional(power);
|
|
||||||
});
|
|
||||||
return effect;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ module SpaceTac.Game.Specs {
|
||||||
expect(equipment.getActionDescription()).toEqual("- 50 damage on all ships in 20km of impact");
|
expect(equipment.getActionDescription()).toEqual("- 50 damage on all ships in 20km of impact");
|
||||||
|
|
||||||
equipment.blast = 0;
|
equipment.blast = 0;
|
||||||
equipment.target_effects.push(new AttributeLimitEffect(AttributeCode.Shield, 3, 200));
|
equipment.target_effects.push(new StickyEffect(new AttributeLimitEffect(AttributeCode.Shield, 200), 3));
|
||||||
expect(equipment.getActionDescription()).toEqual("- 50 damage on target\n- limit shield to 200 for 3 turns on target");
|
expect(equipment.getActionDescription()).toEqual("- 50 damage on target\n- limit shield to 200 for 3 turns on target");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -149,9 +149,9 @@ module SpaceTac.Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience function to add a sticking effect on target
|
// Convenience function to add a sticking effect on target
|
||||||
addSticky(effect: StickyEffect, min_value: number, max_value: number = null,
|
addSticky(effect: BaseEffect, min_value: number, max_value: number = null,
|
||||||
min_duration: number = 1, max_duration: number = null): void {
|
min_duration: number = 1, max_duration: number = null, on_stick = false, on_turn_start = false): void {
|
||||||
var template = new EffectTemplate(effect);
|
var template = new EffectTemplate(new StickyEffect(effect, 0, on_stick, on_turn_start));
|
||||||
template.addModifier("value", new IntegerRange(min_value, max_value));
|
template.addModifier("value", new IntegerRange(min_value, max_value));
|
||||||
template.addModifier("duration", new IntegerRange(min_duration, max_duration));
|
template.addModifier("duration", new IntegerRange(min_duration, max_duration));
|
||||||
this.target_effects.push(template);
|
this.target_effects.push(template);
|
||||||
|
|
|
@ -123,22 +123,20 @@ module SpaceTac.Game.Specs {
|
||||||
var ship = new Ship();
|
var ship = new Ship();
|
||||||
var battle = new Battle(ship.fleet);
|
var battle = new Battle(ship.fleet);
|
||||||
|
|
||||||
var effect = new StickyEffect("test", 2);
|
ship.addStickyEffect(new StickyEffect(new BaseEffect("test"), 2, false, true));
|
||||||
|
|
||||||
ship.addStickyEffect(effect);
|
expect(ship.sticky_effects).toEqual([new StickyEffect(new BaseEffect("test"), 2, false, true)]);
|
||||||
|
|
||||||
expect(ship.sticky_effects).toEqual([effect]);
|
|
||||||
expect(battle.log.events).toEqual([
|
expect(battle.log.events).toEqual([
|
||||||
new EffectAddedEvent(ship, effect)
|
new EffectAddedEvent(ship, new StickyEffect(new BaseEffect("test"), 2, false, true))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ship.startTurn();
|
ship.startTurn();
|
||||||
battle.log.clear();
|
battle.log.clear();
|
||||||
ship.endTurn();
|
ship.endTurn();
|
||||||
|
|
||||||
expect(ship.sticky_effects).toEqual([new StickyEffect("test", 1)]);
|
expect(ship.sticky_effects).toEqual([new StickyEffect(new BaseEffect("test"), 1, false, true)]);
|
||||||
expect(battle.log.events).toEqual([
|
expect(battle.log.events).toEqual([
|
||||||
new EffectDurationChangedEvent(ship, new StickyEffect("test", 1), 2)
|
new EffectDurationChangedEvent(ship, new StickyEffect(new BaseEffect("test"), 1, false, true), 2)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ship.startTurn();
|
ship.startTurn();
|
||||||
|
@ -147,7 +145,8 @@ module SpaceTac.Game.Specs {
|
||||||
|
|
||||||
expect(ship.sticky_effects).toEqual([]);
|
expect(ship.sticky_effects).toEqual([]);
|
||||||
expect(battle.log.events).toEqual([
|
expect(battle.log.events).toEqual([
|
||||||
new EffectRemovedEvent(ship, new StickyEffect("test", 1))
|
new EffectDurationChangedEvent(ship, new StickyEffect(new BaseEffect("test"), 0, false, true), 1),
|
||||||
|
new EffectRemovedEvent(ship, new StickyEffect(new BaseEffect("test"), 0, false, true))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ship.startTurn();
|
ship.startTurn();
|
||||||
|
|
|
@ -223,6 +223,7 @@ module SpaceTac.Game {
|
||||||
this.initializeActionPoints();
|
this.initializeActionPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Method called at the start of this ship turn
|
// Method called at the start of this ship turn
|
||||||
startTurn(): void {
|
startTurn(): void {
|
||||||
if (this.playing) {
|
if (this.playing) {
|
||||||
|
@ -235,9 +236,8 @@ module SpaceTac.Game {
|
||||||
this.updateAttributes();
|
this.updateAttributes();
|
||||||
|
|
||||||
// Apply sticky effects
|
// Apply sticky effects
|
||||||
this.sticky_effects.forEach((effect: StickyEffect) => {
|
this.sticky_effects.forEach(effect => effect.startTurn(this));
|
||||||
effect.singleApply(this, false);
|
this.cleanStickyEffects();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method called at the end of this ship turn
|
// Method called at the end of this ship turn
|
||||||
|
@ -251,32 +251,32 @@ module SpaceTac.Game {
|
||||||
// Recover action points for next turn
|
// Recover action points for next turn
|
||||||
this.recoverActionPoints();
|
this.recoverActionPoints();
|
||||||
|
|
||||||
// Decrement sticky effects duration
|
// Apply sticky effects
|
||||||
let removed_effects: EffectRemovedEvent[] = [];
|
this.sticky_effects.forEach(effect => effect.endTurn(this));
|
||||||
this.sticky_effects = this.sticky_effects.filter((effect: StickyEffect): boolean => {
|
this.cleanStickyEffects();
|
||||||
if (effect.duration <= 1) {
|
|
||||||
removed_effects.push(new EffectRemovedEvent(this, effect));
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.sticky_effects.forEach(effect => {
|
|
||||||
effect.duration -= 1;
|
|
||||||
this.addBattleEvent(new EffectDurationChangedEvent(this, effect, effect.duration + 1));
|
|
||||||
});
|
|
||||||
removed_effects.forEach(effect => this.addBattleEvent(effect));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a sticky effect
|
/**
|
||||||
// A copy of the effect will be used
|
* Register a sticky effect
|
||||||
addStickyEffect(effect: StickyEffect, log: boolean = true): void {
|
*
|
||||||
this.sticky_effects.push(Tools.copyObject(effect));
|
* Pay attention to pass a copy, not the original equipment effect, because it will be modified
|
||||||
|
*/
|
||||||
|
addStickyEffect(effect: StickyEffect, log = true): void {
|
||||||
|
this.sticky_effects.push(effect);
|
||||||
if (log) {
|
if (log) {
|
||||||
this.addBattleEvent(new EffectAddedEvent(this, effect));
|
this.addBattleEvent(new EffectAddedEvent(this, effect));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean sticky effects that are no longer active
|
||||||
|
*/
|
||||||
|
cleanStickyEffects() {
|
||||||
|
let [active, ended] = Tools.binpartition(this.sticky_effects, effect => effect.duration > 0);
|
||||||
|
this.sticky_effects = active;
|
||||||
|
ended.forEach(effect => this.addBattleEvent(new EffectRemovedEvent(this, effect)));
|
||||||
|
}
|
||||||
|
|
||||||
// Move toward a location
|
// Move toward a location
|
||||||
// This does not check or consume action points
|
// This does not check or consume action points
|
||||||
moveTo(x: number, y: number, log: boolean = true): void {
|
moveTo(x: number, y: number, log: boolean = true): void {
|
||||||
|
|
|
@ -14,7 +14,7 @@ module SpaceTac.Game.Specs {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Tools", () => {
|
describe("Tools", () => {
|
||||||
it("copies full javascript objects", () => {
|
it("copies full javascript objects", function () {
|
||||||
var ini = new TestObj();
|
var ini = new TestObj();
|
||||||
|
|
||||||
var cop = Tools.copyObject(ini);
|
var cop = Tools.copyObject(ini);
|
||||||
|
@ -25,10 +25,14 @@ module SpaceTac.Game.Specs {
|
||||||
expect(cop.get()).toEqual("test");
|
expect(cop.get()).toEqual("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("merges objects", () => {
|
it("merges objects", function () {
|
||||||
expect(Tools.merge({}, {})).toEqual({});
|
expect(Tools.merge({}, {})).toEqual({});
|
||||||
expect(Tools.merge({ "a": 1 }, { "b": 2 })).toEqual({ "a": 1, "b": 2 });
|
expect(Tools.merge({ "a": 1 }, { "b": 2 })).toEqual({ "a": 1, "b": 2 });
|
||||||
expect(Tools.merge({ "a": 1 }, { "a": 3, "b": 2 })).toEqual({ "a": 3, "b": 2 });
|
expect(Tools.merge({ "a": 1 }, { "a": 3, "b": 2 })).toEqual({ "a": 3, "b": 2 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("partitions arrays by a predicate", function () {
|
||||||
|
expect(Tools.binpartition([1, 2, 3, 4], i => i % 2 == 0)).toEqual([[2, 4], [1, 3]]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ module SpaceTac.Game {
|
||||||
export class Tools {
|
export class Tools {
|
||||||
|
|
||||||
// Copy an object (only a shallow copy of immediate properties)
|
// Copy an object (only a shallow copy of immediate properties)
|
||||||
static copyObject<T> (object: T): T {
|
static copyObject<T>(object: T): T {
|
||||||
var objectCopy = <T>Object.create(object.constructor.prototype);
|
var objectCopy = <T>Object.create(object.constructor.prototype);
|
||||||
|
|
||||||
for (var key in object) {
|
for (var key in object) {
|
||||||
|
@ -25,5 +25,13 @@ module SpaceTac.Game {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Partition a list by a predicate, returning the items that pass the predicate, then the ones that don't pass it
|
||||||
|
static binpartition<T>(array: T[], predicate: (T) => boolean): [T[], T[]] {
|
||||||
|
let pass = [];
|
||||||
|
let fail = [];
|
||||||
|
array.forEach(item => (predicate(item) ? pass : fail).push(item));
|
||||||
|
return [pass, fail];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
/// <reference path="StickyEffect.ts"/>
|
/// <reference path="BaseEffect.ts"/>
|
||||||
|
|
||||||
module SpaceTac.Game {
|
module SpaceTac.Game {
|
||||||
// Hard limitation on attribute value
|
// Hard limitation on attribute value
|
||||||
// For example, this could be used to slow a target by limiting its action points
|
// For example, this could be used to slow a target by limiting its action points
|
||||||
export class AttributeLimitEffect extends StickyEffect {
|
export class AttributeLimitEffect extends BaseEffect {
|
||||||
// Affected attribute
|
// Affected attribute
|
||||||
attrcode: AttributeCode;
|
attrcode: AttributeCode;
|
||||||
|
|
||||||
// Limit of the attribute value
|
// Limit of the attribute value
|
||||||
value: number;
|
value: number;
|
||||||
|
|
||||||
constructor(attrcode: AttributeCode, duration: number = 0, value: number = 0) {
|
constructor(attrcode: AttributeCode, value: number = 0) {
|
||||||
super("attrlimit", duration);
|
super("attrlimit");
|
||||||
|
|
||||||
this.attrcode = attrcode;
|
this.attrcode = attrcode;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
singleApply(ship: Ship, on_stick: boolean): void {
|
applyOnShip(ship: Ship): boolean {
|
||||||
var current = ship.attributes.getValue(this.attrcode);
|
var current = ship.attributes.getValue(this.attrcode);
|
||||||
if (current > this.value) {
|
if (current > this.value) {
|
||||||
ship.setAttribute(ship.attributes.getRawAttr(this.attrcode), this.value);
|
ship.setAttribute(ship.attributes.getRawAttr(this.attrcode), this.value);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFullCode(): string {
|
getFullCode(): string {
|
||||||
|
|
|
@ -14,6 +14,17 @@ module SpaceTac.Game {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a copy, modified by template modifiers
|
||||||
|
*/
|
||||||
|
getModifiedCopy(modifiers: EffectTemplateModifier[], power: number): BaseEffect {
|
||||||
|
let result = Tools.copyObject(this);
|
||||||
|
modifiers.forEach(modifier => {
|
||||||
|
result[modifier.name] = modifier.range.getProportional(power);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply ponctually the effect on a given ship
|
// Apply ponctually the effect on a given ship
|
||||||
// Return true if the effect could be applied
|
// Return true if the effect could be applied
|
||||||
applyOnShip(ship: Ship): boolean {
|
applyOnShip(ship: Ship): boolean {
|
||||||
|
@ -25,6 +36,11 @@ module SpaceTac.Game {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a full code, that can be used to identify this effect (for example: "attrlimit-aprecovery")
|
||||||
|
getFullCode(): string {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
// Return a human readable description
|
// Return a human readable description
|
||||||
getDescription(): string {
|
getDescription(): string {
|
||||||
return "unknown effect";
|
return "unknown effect";
|
||||||
|
|
|
@ -1,33 +1,85 @@
|
||||||
/// <reference path="BaseEffect.ts"/>
|
/// <reference path="BaseEffect.ts"/>
|
||||||
|
|
||||||
module SpaceTac.Game {
|
module SpaceTac.Game {
|
||||||
// Base class for actions that will stick to a target for a number of rounds
|
/**
|
||||||
|
* Wrapper around another effect, to make it stick to a ship.
|
||||||
|
*
|
||||||
|
* The "effect" is to stick the wrapped effect to the ship, that will be applied in time.
|
||||||
|
*/
|
||||||
export class StickyEffect extends BaseEffect {
|
export class StickyEffect extends BaseEffect {
|
||||||
|
// Wrapped effect
|
||||||
|
base: BaseEffect;
|
||||||
|
|
||||||
// Duration, in number of turns
|
// Duration, in number of turns
|
||||||
duration: number;
|
duration: number;
|
||||||
|
|
||||||
// Base constructor
|
// Apply the effect on stick (doesn't count against duration)
|
||||||
constructor(code: string, duration: number = 0) {
|
on_stick: boolean;
|
||||||
super(code);
|
|
||||||
|
|
||||||
|
// Apply the effect on turn start instead of end
|
||||||
|
on_turn_end: boolean;
|
||||||
|
|
||||||
|
// Base constructor
|
||||||
|
constructor(base: BaseEffect, duration = 0, on_stick = false, on_turn_end = false) {
|
||||||
|
super(base.code);
|
||||||
|
|
||||||
|
this.base = base;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
|
this.on_stick = on_stick;
|
||||||
|
this.on_turn_end = on_turn_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
getModifiedCopy(modifiers: EffectTemplateModifier[], power: number): BaseEffect {
|
||||||
|
let [current, base] = Tools.binpartition(modifiers, modifier => modifier.name == "duration");
|
||||||
|
let result = <StickyEffect>super.getModifiedCopy(current, power);
|
||||||
|
result.base = result.base.getModifiedCopy(base, power);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyOnShip(ship: Ship): boolean {
|
applyOnShip(ship: Ship): boolean {
|
||||||
ship.addStickyEffect(this);
|
ship.addStickyEffect(new StickyEffect(this.base, this.duration, this.on_stick, this.on_turn_end));
|
||||||
this.singleApply(ship, true);
|
if (this.on_stick) {
|
||||||
|
this.base.applyOnShip(ship);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to implement to apply the effect ponctually
|
private applyOnce(ship: Ship) {
|
||||||
// on_stick is true when this is called by applyOnShip, and false when called at turn start
|
if (this.duration > 0) {
|
||||||
singleApply(ship: Ship, on_stick: boolean): void {
|
this.base.applyOnShip(ship);
|
||||||
// Abstract
|
this.duration--;
|
||||||
|
ship.addBattleEvent(new EffectDurationChangedEvent(ship, this, this.duration + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the effect at the beginning of the turn, for the ship this effect is sticked to.
|
||||||
|
*/
|
||||||
|
startTurn(ship: Ship) {
|
||||||
|
if (!this.on_turn_end) {
|
||||||
|
this.applyOnce(ship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the effect at the end of the turn, for the ship this effect is sticked to.
|
||||||
|
*/
|
||||||
|
endTurn(ship: Ship) {
|
||||||
|
if (this.on_turn_end) {
|
||||||
|
this.applyOnce(ship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isBeneficial(): boolean {
|
||||||
|
return this.base.isBeneficial();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a full code, that can be used to identify this effect (for example: "attrlimit-aprecovery")
|
|
||||||
getFullCode(): string {
|
getFullCode(): string {
|
||||||
return this.code;
|
return this.base.getFullCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDescription(): string {
|
||||||
|
return this.base.getDescription();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,18 @@ module SpaceTac.Game.Specs {
|
||||||
|
|
||||||
expect(target.ap_current.current).toBe(4);
|
expect(target.ap_current.current).toBe(4);
|
||||||
expect(target.sticky_effects).toEqual([
|
expect(target.sticky_effects).toEqual([
|
||||||
new AttributeLimitEffect(AttributeCode.AP, 1, 4)
|
new StickyEffect(new AttributeLimitEffect(AttributeCode.AP, 4), 1, true, false)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Attribute is limited for one turn, and prevents AP recovery
|
// Attribute is limited for one turn, and prevents AP recovery
|
||||||
target.ap_current.set(6);
|
target.ap_current.set(6);
|
||||||
|
target.recoverActionPoints();
|
||||||
target.startTurn();
|
target.startTurn();
|
||||||
|
|
||||||
expect(target.ap_current.current).toBe(4);
|
expect(target.ap_current.current).toBe(4);
|
||||||
expect(target.sticky_effects).toEqual([
|
expect(target.sticky_effects).toEqual([]);
|
||||||
new AttributeLimitEffect(AttributeCode.AP, 1, 4)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Attribute vanishes before the end of turn, so AP recovery happens
|
// Effect vanished, so AP recovery happens
|
||||||
target.endTurn();
|
target.endTurn();
|
||||||
|
|
||||||
expect(target.ap_current.current).toBe(6);
|
expect(target.ap_current.current).toBe(6);
|
||||||
|
|
|
@ -10,7 +10,7 @@ module SpaceTac.Game.Equipments {
|
||||||
this.ap_usage = new IntegerRange(4, 5);
|
this.ap_usage = new IntegerRange(4, 5);
|
||||||
this.min_level = new IntegerRange(1, 3);
|
this.min_level = new IntegerRange(1, 3);
|
||||||
|
|
||||||
this.addSticky(new AttributeLimitEffect(AttributeCode.AP), 4, 3, 1, 2);
|
this.addSticky(new AttributeLimitEffect(AttributeCode.AP), 4, 3, 1, 2, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue