1
0
Fork 0

Rewrite of sticky effects system, to allow any other effect to be sticky

This commit is contained in:
Michaël Lemaire 2017-01-24 01:14:04 +01:00
parent 0a822e705e
commit 875b71828d
13 changed files with 140 additions and 66 deletions

1
TODO
View file

@ -1,6 +1,5 @@
* Use succession's tools and serializer
* 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 equipment info (or summary) in ship tooltip
* Handle effects overflowing ship tooltip when too numerous

View file

@ -35,11 +35,7 @@ module SpaceTac.Game {
// Generate an effect with a given power
generateFixed(power: number): BaseEffect {
var effect = Tools.copyObject(this.effect);
this.modifiers.forEach((modifier: EffectTemplateModifier) => {
effect[modifier.name] = modifier.range.getProportional(power);
});
return effect;
return this.effect.getModifiedCopy(this.modifiers, power);
}
}
}

View file

@ -44,7 +44,7 @@ module SpaceTac.Game.Specs {
expect(equipment.getActionDescription()).toEqual("- 50 damage on all ships in 20km of impact");
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");
});
});

View file

@ -149,9 +149,9 @@ module SpaceTac.Game {
}
// Convenience function to add a sticking effect on target
addSticky(effect: StickyEffect, min_value: number, max_value: number = null,
min_duration: number = 1, max_duration: number = null): void {
var template = new EffectTemplate(effect);
addSticky(effect: BaseEffect, min_value: number, max_value: number = null,
min_duration: number = 1, max_duration: number = null, on_stick = false, on_turn_start = false): void {
var template = new EffectTemplate(new StickyEffect(effect, 0, on_stick, on_turn_start));
template.addModifier("value", new IntegerRange(min_value, max_value));
template.addModifier("duration", new IntegerRange(min_duration, max_duration));
this.target_effects.push(template);

View file

@ -123,22 +123,20 @@ module SpaceTac.Game.Specs {
var ship = new Ship();
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([effect]);
expect(ship.sticky_effects).toEqual([new StickyEffect(new BaseEffect("test"), 2, false, true)]);
expect(battle.log.events).toEqual([
new EffectAddedEvent(ship, effect)
new EffectAddedEvent(ship, new StickyEffect(new BaseEffect("test"), 2, false, true))
]);
ship.startTurn();
battle.log.clear();
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([
new EffectDurationChangedEvent(ship, new StickyEffect("test", 1), 2)
new EffectDurationChangedEvent(ship, new StickyEffect(new BaseEffect("test"), 1, false, true), 2)
]);
ship.startTurn();
@ -147,7 +145,8 @@ module SpaceTac.Game.Specs {
expect(ship.sticky_effects).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();

View file

@ -223,6 +223,7 @@ module SpaceTac.Game {
this.initializeActionPoints();
}
// Method called at the start of this ship turn
startTurn(): void {
if (this.playing) {
@ -235,9 +236,8 @@ module SpaceTac.Game {
this.updateAttributes();
// Apply sticky effects
this.sticky_effects.forEach((effect: StickyEffect) => {
effect.singleApply(this, false);
});
this.sticky_effects.forEach(effect => effect.startTurn(this));
this.cleanStickyEffects();
}
// Method called at the end of this ship turn
@ -251,32 +251,32 @@ module SpaceTac.Game {
// Recover action points for next turn
this.recoverActionPoints();
// Decrement sticky effects duration
let removed_effects: EffectRemovedEvent[] = [];
this.sticky_effects = this.sticky_effects.filter((effect: StickyEffect): boolean => {
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));
// Apply sticky effects
this.sticky_effects.forEach(effect => effect.endTurn(this));
this.cleanStickyEffects();
}
// Add a sticky effect
// A copy of the effect will be used
addStickyEffect(effect: StickyEffect, log: boolean = true): void {
this.sticky_effects.push(Tools.copyObject(effect));
/**
* Register a sticky 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) {
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
// This does not check or consume action points
moveTo(x: number, y: number, log: boolean = true): void {

View file

@ -14,7 +14,7 @@ module SpaceTac.Game.Specs {
}
describe("Tools", () => {
it("copies full javascript objects", () => {
it("copies full javascript objects", function () {
var ini = new TestObj();
var cop = Tools.copyObject(ini);
@ -25,10 +25,14 @@ module SpaceTac.Game.Specs {
expect(cop.get()).toEqual("test");
});
it("merges objects", () => {
it("merges objects", function () {
expect(Tools.merge({}, {})).toEqual({});
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 });
});
it("partitions arrays by a predicate", function () {
expect(Tools.binpartition([1, 2, 3, 4], i => i % 2 == 0)).toEqual([[2, 4], [1, 3]]);
});
});
}

View file

@ -3,7 +3,7 @@ module SpaceTac.Game {
export class Tools {
// 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);
for (var key in object) {
@ -25,5 +25,13 @@ module SpaceTac.Game {
}
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];
}
}
}

View file

@ -1,27 +1,28 @@
/// <reference path="StickyEffect.ts"/>
/// <reference path="BaseEffect.ts"/>
module SpaceTac.Game {
// Hard limitation on attribute value
// 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
attrcode: AttributeCode;
// Limit of the attribute value
value: number;
constructor(attrcode: AttributeCode, duration: number = 0, value: number = 0) {
super("attrlimit", duration);
constructor(attrcode: AttributeCode, value: number = 0) {
super("attrlimit");
this.attrcode = attrcode;
this.value = value;
}
singleApply(ship: Ship, on_stick: boolean): void {
applyOnShip(ship: Ship): boolean {
var current = ship.attributes.getValue(this.attrcode);
if (current > this.value) {
ship.setAttribute(ship.attributes.getRawAttr(this.attrcode), this.value);
}
return true;
}
getFullCode(): string {

View file

@ -14,6 +14,17 @@ module SpaceTac.Game {
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
// Return true if the effect could be applied
applyOnShip(ship: Ship): boolean {
@ -25,6 +36,11 @@ module SpaceTac.Game {
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
getDescription(): string {
return "unknown effect";

View file

@ -1,33 +1,85 @@
/// <reference path="BaseEffect.ts"/>
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 {
// Wrapped effect
base: BaseEffect;
// Duration, in number of turns
duration: number;
// Base constructor
constructor(code: string, duration: number = 0) {
super(code);
// Apply the effect on stick (doesn't count against duration)
on_stick: boolean;
// 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.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 {
ship.addStickyEffect(this);
this.singleApply(ship, true);
ship.addStickyEffect(new StickyEffect(this.base, this.duration, this.on_stick, this.on_turn_end));
if (this.on_stick) {
this.base.applyOnShip(ship);
}
return true;
}
// Method to implement to apply the effect ponctually
// on_stick is true when this is called by applyOnShip, and false when called at turn start
singleApply(ship: Ship, on_stick: boolean): void {
// Abstract
private applyOnce(ship: Ship) {
if (this.duration > 0) {
this.base.applyOnShip(ship);
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 {
return this.code;
return this.base.getFullCode();
}
getDescription(): string {
return this.base.getDescription();
}
}
}

View file

@ -17,19 +17,18 @@ module SpaceTac.Game.Specs {
expect(target.ap_current.current).toBe(4);
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
target.ap_current.set(6);
target.recoverActionPoints();
target.startTurn();
expect(target.ap_current.current).toBe(4);
expect(target.sticky_effects).toEqual([
new AttributeLimitEffect(AttributeCode.AP, 1, 4)
]);
expect(target.sticky_effects).toEqual([]);
// Attribute vanishes before the end of turn, so AP recovery happens
// Effect vanished, so AP recovery happens
target.endTurn();
expect(target.ap_current.current).toBe(6);

View file

@ -10,7 +10,7 @@ module SpaceTac.Game.Equipments {
this.ap_usage = new IntegerRange(4, 5);
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);
}
}
}