1
0
Fork 0

Handle toggle actions effects correctly

This commit is contained in:
Michaël Lemaire 2017-06-14 00:01:39 +02:00
parent 2d0b9c7ab6
commit 8c6892f5c0
15 changed files with 257 additions and 101 deletions

1
TODO
View file

@ -20,6 +20,7 @@
* Menu: end appear animation when a button is clicked
* Menu: allow to delete cloud saves
* Arena: display effects description instead of attribute changes
* Arena: display radius for area effects (both on action hover, and while action is activated)
* Arena: add auto-move to attack
* Arena: fix effects originating from real ship location instead of current sprite (when AI fires then moves)
* Arena: add engine trail

@ -1 +1 @@
Subproject commit df24442f844494bf0f799fcf5bdcf5bcad36501e
Subproject commit 1905bfa0d1bb53d21c2ee6537fe2551c13d8dbbe

View file

@ -174,7 +174,7 @@ module TS.SpaceTac.Specs {
expect(ship.sticky_effects).toEqual([new StickyEffect(new BaseEffect("test"), 2, false, true)]);
expect(battle.log.events).toEqual([
new EffectAddedEvent(ship, new StickyEffect(new BaseEffect("test"), 2, false, true))
new ActiveEffectsEvent(ship, [], [new StickyEffect(new BaseEffect("test"), 2, false, true)])
]);
ship.startTurn();
@ -183,7 +183,7 @@ module TS.SpaceTac.Specs {
expect(ship.sticky_effects).toEqual([new StickyEffect(new BaseEffect("test"), 1, false, true)]);
expect(battle.log.events).toEqual([
new EffectDurationChangedEvent(ship, new StickyEffect(new BaseEffect("test"), 1, false, true), 2)
new ActiveEffectsEvent(ship, [], [new StickyEffect(new BaseEffect("test"), 1, false, true)])
]);
ship.startTurn();
@ -192,8 +192,8 @@ module TS.SpaceTac.Specs {
expect(ship.sticky_effects).toEqual([]);
expect(battle.log.events).toEqual([
new EffectDurationChangedEvent(ship, new StickyEffect(new BaseEffect("test"), 0, false, true), 1),
new EffectRemovedEvent(ship, new StickyEffect(new BaseEffect("test"), 0, false, true))
new ActiveEffectsEvent(ship, [], [new StickyEffect(new BaseEffect("test"), 0, false, true)]),
new ActiveEffectsEvent(ship, [], [])
]);
ship.startTurn();
@ -204,6 +204,80 @@ module TS.SpaceTac.Specs {
expect(battle.log.events).toEqual([]);
});
it("resets toggle actions at the start of turn", function () {
let ship = new Ship();
let equ = ship.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
let action = equ.action = new ToggleAction(equ, 0, 10, [new AttributeEffect("power_capacity", 1)]);
expect(action.activated).toBe(false);
let battle = new Battle(ship.fleet);
battle.playing_ship = ship;
ship.startTurn();
expect(action.activated).toBe(false);
let result = action.apply(ship, null);
expect(result).toBe(true, "Could not be applied");
expect(action.activated).toBe(true);
ship.endTurn();
expect(action.activated).toBe(true);
ship.startTurn();
expect(action.activated).toBe(false);
expect(battle.log.events).toEqual([
new ToggleEvent(ship, action, true),
new ActiveEffectsEvent(ship, [], [], [new AttributeEffect("power_capacity", 1)]),
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 1), 1),
new ToggleEvent(ship, action, false),
new ActiveEffectsEvent(ship, [], [], []),
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 0), -1),
]);
});
it("updates area effects when the ship moves", function () {
let battle = new Battle();
let ship1 = battle.fleets[0].addShip();
let ship2 = battle.fleets[0].addShip();
ship2.setArenaPosition(10, 0);
let ship3 = battle.fleets[0].addShip();
ship3.setArenaPosition(20, 0);
let shield = ship1.addSlot(SlotType.Shield).attach(new Equipment(SlotType.Shield));
shield.action = new ToggleAction(shield, 0, 15, [new AttributeEffect("shield_capacity", 5)]);
battle.playing_ship = ship1;
shield.action.apply(ship1, null);
expect(ship1.getAttribute("shield_capacity")).toBe(5);
expect(ship2.getAttribute("shield_capacity")).toBe(5);
expect(ship3.getAttribute("shield_capacity")).toBe(0);
ship1.moveTo(15, 0);
expect(ship1.getAttribute("shield_capacity")).toBe(5);
expect(ship2.getAttribute("shield_capacity")).toBe(5);
expect(ship3.getAttribute("shield_capacity")).toBe(5);
ship1.moveTo(30, 0);
expect(ship1.getAttribute("shield_capacity")).toBe(5);
expect(ship2.getAttribute("shield_capacity")).toBe(0);
expect(ship3.getAttribute("shield_capacity")).toBe(5);
ship1.moveTo(50, 0);
expect(ship1.getAttribute("shield_capacity")).toBe(5);
expect(ship2.getAttribute("shield_capacity")).toBe(0);
expect(ship3.getAttribute("shield_capacity")).toBe(0);
ship2.moveTo(40, 0);
expect(ship1.getAttribute("shield_capacity")).toBe(5);
expect(ship2.getAttribute("shield_capacity")).toBe(5);
expect(ship3.getAttribute("shield_capacity")).toBe(0);
});
it("sets and logs death state", function () {
var fleet = new Fleet();
var battle = new Battle(fleet);

View file

@ -111,9 +111,7 @@ module TS.SpaceTac {
this.arena_y = 0;
this.arena_angle = 0;
if (fleet) {
fleet.addShip(this);
}
this.fleet.addShip(this);
this.model = model;
this.setCargoSpace(model.cargo);
@ -360,6 +358,13 @@ module TS.SpaceTac {
// Apply sticky effects
this.sticky_effects.forEach(effect => effect.startTurn(this));
this.cleanStickyEffects();
// Reset toggle actions state
this.listEquipment().forEach(equipment => {
if (equipment.action instanceof ToggleAction && equipment.action.activated) {
equipment.action.apply(this, null);
}
});
}
}
@ -394,7 +399,7 @@ module TS.SpaceTac {
if (this.alive) {
this.sticky_effects.push(effect);
if (log) {
this.addBattleEvent(new EffectAddedEvent(this, effect));
this.setActiveEffectsChanged();
}
}
}
@ -405,7 +410,9 @@ module TS.SpaceTac {
cleanStickyEffects() {
let [active, ended] = binpartition(this.sticky_effects, effect => effect.duration > 0);
this.sticky_effects = active;
ended.forEach(effect => this.addBattleEvent(new EffectRemovedEvent(this, effect)));
if (ended.length) {
this.setActiveEffectsChanged();
}
}
/**
@ -447,6 +454,10 @@ module TS.SpaceTac {
if (dx != 0 || dy != 0) {
let start = copy(this.location);
let area_effects = imaterialize(this.iToggleActions(true));
let old_impacted_ships = area_effects.map(action => action.getAffectedShips(this));
let old_area_effects = this.getActiveEffects().area;
let angle = Math.atan2(dy, dx);
this.setArenaFacingAngle(angle);
this.setArenaPosition(x, y);
@ -454,6 +465,14 @@ module TS.SpaceTac {
if (log) {
this.addBattleEvent(new MoveEvent(this, start, copy(this.location)));
}
let new_impacted_ships = area_effects.map(action => action.getAffectedShips(this));
let diff_impacted_ships = flatten(zip(old_impacted_ships, new_impacted_ships).map(([a, b]) => disjunctunion(a, b)));
let new_area_effects = this.getActiveEffects().area;
if (disjunctunion(old_area_effects, new_area_effects).length > 0) {
diff_impacted_ships.push(this);
}
unique(diff_impacted_ships).forEach(ship => ship.setActiveEffectsChanged());
}
}
@ -679,13 +698,33 @@ module TS.SpaceTac {
}
/**
* Iterator over all effects active for this ship.
* Get the list of all effects applied on this ship
*
* This includes:
* - Permanent equipment effects
* - Sticky effects
* - Area effects at current location
*/
getActiveEffects(): ActiveEffectsEvent {
let result = new ActiveEffectsEvent(this);
result.equipment = flatten(this.slots.map(slot => slot.attached ? slot.attached.effects : []));
result.sticky = this.sticky_effects;
let battle = this.getBattle();
result.area = battle ? imaterialize(battle.iAreaEffects(this.arena_x, this.arena_y)) : [];
return result;
}
/**
* Indicate a change in active effects to the log
*/
setActiveEffectsChanged(): void {
this.addBattleEvent(this.getActiveEffects());
this.updateAttributes();
}
/**
* Iterator over all effects active for this ship.
*/
ieffects(): Iterator<BaseEffect> {
let battle = this.getBattle();
let area_effects = battle ? battle.iAreaEffects(this.arena_x, this.arena_y) : IEMPTY;
@ -696,13 +735,22 @@ module TS.SpaceTac {
);
}
/**
* Iterator over toggle actions
*/
iToggleActions(only_active = false): Iterator<ToggleAction> {
return <Iterator<ToggleAction>>ifilter(iarray(this.getAvailableActions()), action => {
return (action instanceof ToggleAction && (action.activated || !only_active));
});
}
/**
* Iterator over area effects from this ship impacting a location
*/
iAreaEffects(x: number, y: number): Iterator<BaseEffect> {
let distance = Target.newFromShip(this).getDistanceTo({ x: x, y: y });
return ichainit(imap(iarray(this.getAvailableActions()), action => {
if (action instanceof ToggleAction && action.activated && distance <= action.radius) {
return ichainit(imap(this.iToggleActions(true), action => {
if (distance <= action.radius) {
return iarray(action.effects);
} else {
return IEMPTY;

View file

@ -40,15 +40,22 @@ module TS.SpaceTac {
return this.radius;
}
/**
* Get the list of ships in range to be affected
*/
getAffectedShips(ship: Ship): Ship[] {
let target = Target.newFromShip(ship);
let radius = this.getBlastRadius(ship);
let battle = ship.getBattle();
return (radius && battle) ? battle.collectShipsInCircle(target, radius, true) : ((target.ship && target.ship.alive) ? [target.ship] : []);
}
/**
* Collect the effects applied by this action
*/
getEffects(ship: Ship): [Ship, BaseEffect][] {
let target = Target.newFromShip(ship);
let result: [Ship, BaseEffect][] = [];
let radius = this.getBlastRadius(ship);
let battle = ship.getBattle();
let ships = (radius && battle) ? battle.collectShipsInCircle(target, radius, true) : ((target.ship && target.ship.alive) ? [target.ship] : []);
let ships = this.getAffectedShips(ship);
ships.forEach(ship => {
this.effects.forEach(effect => result.push([ship, effect]));
});
@ -58,7 +65,7 @@ module TS.SpaceTac {
protected customApply(ship: Ship, target: Target) {
this.activated = !this.activated;
ship.addBattleEvent(new ToggleEvent(ship, this, this.activated));
// TODO Refresh area effects
this.getAffectedShips(ship).forEach(iship => iship.setActiveEffectsChanged());
}
getEffectsDescription(): string {

View file

@ -17,5 +17,9 @@ module TS.SpaceTac {
getDescription(): string {
return `damage ${this.factor}%`;
}
isBeneficial(): boolean {
return this.factor <= 0;
}
}
}

View file

@ -41,7 +41,7 @@ module TS.SpaceTac {
if (this.duration > 0) {
this.base.applyOnShip(ship, ship); // FIXME Does not remember the source
this.duration--;
ship.addBattleEvent(new EffectDurationChangedEvent(ship, this, this.duration + 1));
ship.setActiveEffectsChanged();
}
}

View file

@ -0,0 +1,25 @@
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
/**
* Event logged when a list of active effects changes
*/
export class ActiveEffectsEvent extends BaseLogShipEvent {
// Effects active because of equipment
equipment: BaseEffect[]
// Sticky effects active
sticky: StickyEffect[]
// Area effects
area: BaseEffect[]
constructor(ship: Ship, equipment: BaseEffect[] = [], sticky: StickyEffect[] = [], area: BaseEffect[] = []) {
super("activeeffects", ship);
this.equipment = equipment.map(copy);
this.sticky = sticky.map(copy);
this.area = area.map(copy);
}
}
}

View file

@ -1,15 +0,0 @@
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a sticky effect is added to a ship
export class EffectAddedEvent extends BaseLogShipEvent {
// Pointer to the effect
effect: StickyEffect;
constructor(ship: Ship, effect: StickyEffect) {
super("effectadd", ship);
this.effect = effect;
}
}
}

View file

@ -1,19 +0,0 @@
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a sticky effect is added to a ship
export class EffectDurationChangedEvent extends BaseLogShipEvent {
// Pointer to the effect
effect: StickyEffect;
// Previous duration
previous: number;
constructor(ship: Ship, effect: StickyEffect, previous: number) {
super("effectduration", ship);
this.effect = effect;
this.previous = previous;
}
}
}

View file

@ -1,15 +0,0 @@
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a sticky effect is removed from a ship
export class EffectRemovedEvent extends BaseLogShipEvent {
// Pointer to the effect
effect: StickyEffect;
constructor(ship: Ship, effect: StickyEffect) {
super("effectdel", ship);
this.effect = effect;
}
}
}

View file

@ -36,8 +36,9 @@ module TS.SpaceTac.UI {
frame: Phaser.Image
// Effects display
sticky_effects: Phaser.Group
effects: Phaser.Group
active_effects: ActiveEffectsEvent
active_effects_display: Phaser.Group
effects_messages: Phaser.Group
// Create a ship sprite usable in the Arena
constructor(parent: Arena, ship: Ship) {
@ -93,12 +94,13 @@ module TS.SpaceTac.UI {
this.add(this.play_order);
// Effects display
this.sticky_effects = new Phaser.Group(this.game);
this.add(this.sticky_effects);
this.effects = new Phaser.Group(this.game);
this.add(this.effects);
this.active_effects = new ActiveEffectsEvent(ship);
this.active_effects_display = new Phaser.Group(this.game);
this.add(this.active_effects_display);
this.effects_messages = new Phaser.Group(this.game);
this.add(this.effects_messages);
this.updateStickyEffects();
this.updateActiveEffects();
this.updatePowerIndicator(ship.getValue("power"));
// Handle input on ship sprite
@ -139,8 +141,9 @@ module TS.SpaceTac.UI {
* Process a log event for this ship
*/
private processShipLogEvent(event: BaseLogShipEvent): number {
if (event instanceof EffectAddedEvent || event instanceof EffectRemovedEvent || event instanceof EffectDurationChangedEvent) {
this.updateStickyEffects();
if (event instanceof ActiveEffectsEvent) {
this.active_effects = event;
this.updateActiveEffects();
return 0;
} else if (event instanceof ValueChangeEvent) {
if (event.value.name == "hull") {
@ -242,19 +245,19 @@ module TS.SpaceTac.UI {
* Briefly show an effect on this ship
*/
displayEffect(message: string, beneficial: boolean) {
let text = new Phaser.Text(this.game, 0, 20 * this.effects.children.length, message, { font: "14pt Arial", fill: beneficial ? "#afe9c6" : "#e9afaf" });
this.effects.addChild(text);
let text = new Phaser.Text(this.game, 0, 20 * this.effects_messages.children.length, message, { font: "14pt Arial", fill: beneficial ? "#afe9c6" : "#e9afaf" });
this.effects_messages.addChild(text);
let arena = this.battleview.arena.getBoundaries();
this.effects.position.set(
(this.ship.arena_x < 100) ? -35 : ((this.ship.arena_x > arena.width - 100) ? (35 - this.effects.width) : (-this.effects.width * 0.5)),
(this.ship.arena_y < arena.height * 0.9) ? 45 : (-45 - this.effects.height)
this.effects_messages.position.set(
(this.ship.arena_x < 100) ? -35 : ((this.ship.arena_x > arena.width - 100) ? (35 - this.effects_messages.width) : (-this.effects_messages.width * 0.5)),
(this.ship.arena_y < arena.height * 0.9) ? 45 : (-45 - this.effects_messages.height)
);
this.game.tweens.removeFrom(this.effects);
this.effects.alpha = 1;
let tween = this.game.tweens.create(this.effects).to({ alpha: 0 }, 500).delay(1000).start();
tween.onComplete.addOnce(() => this.effects.removeAll(true));
this.game.tweens.removeFrom(this.effects_messages);
this.effects_messages.alpha = 1;
let tween = this.game.tweens.create(this.effects_messages).to({ alpha: 0 }, 500).delay(1000).start();
tween.onComplete.addOnce(() => this.effects_messages.removeAll(true));
}
/**
@ -267,17 +270,20 @@ module TS.SpaceTac.UI {
}
/**
* Update the stick effects
* Update the list of effects active on the ship
*/
updateStickyEffects() {
this.sticky_effects.removeAll();
updateActiveEffects() {
this.active_effects_display.removeAll();
let count = this.ship.sticky_effects.length
let effects = this.active_effects.sticky.map(sticky => sticky.base).concat(this.active_effects.area);
let count = effects.length;
if (count) {
let positions = UITools.evenlySpace(70, 10, count);
this.ship.sticky_effects.forEach((effect, index) => {
effects.forEach((effect, index) => {
let dot = new Phaser.Image(this.game, positions[index] - 40, -47, `battle-arena-ship-effect-${effect.isBeneficial() ? "good" : "bad"}`);
this.sticky_effects.add(dot);
this.active_effects_display.add(dot);
});
}
}

View file

@ -0,0 +1,37 @@
module TS.SpaceTac.UI.Specs {
describe("ShipTooltip", function () {
let testgame = setupBattleview();
it("fills ship details", function () {
let tooltip = new ShipTooltip(testgame.battleview);
let ship = testgame.battleview.battle.play_order[2];
ship.name = "Fury";
ship.model = new ShipModel("fake", "Fury");
ship.listEquipment(SlotType.Weapon).forEach(equ => equ.detach());
TestTools.setShipHP(ship, 58, 140);
TestTools.setShipAP(ship, 12);
TestTools.addWeapon(ship, 50);
let sprite = nn(testgame.battleview.arena.findShipSprite(ship));
sprite.active_effects = new ActiveEffectsEvent(ship,
[new AttributeEffect("hull_capacity", 50)],
[new StickyEffect(new DamageModifierEffect(-15), 3)],
[new AttributeLimitEffect("precision", 10)])
tooltip.setShip(ship);
let content = (<any>tooltip).container.content;
expect(content.children[0].data.key).toBe("ship-fake-portrait");
expect(content.children[1].text).toBe("Fury");
expect(content.children[2].text).toBe("Plays in 2 turns");
expect(content.children[3].text).toBe("Hull\n58");
expect(content.children[4].text).toBe("Shield\n140");
expect(content.children[5].text).toBe("Power\n12");
expect(content.children[6].text).toBe("Active effects");
expect(content.children[7].text).toBe("• limit precision to 10");
expect(content.children[8].text).toBe("• damage -15% for 3 turns");
expect(content.children[9].text).toBe("Weapons");
expect(content.children[10].text).toBe("• equipment Mk1");
});
});
}

View file

@ -20,6 +20,7 @@ module TS.SpaceTac.UI {
this.hide();
let filler = this.getFiller();
let sprite = this.battleview.arena.findShipSprite(ship);
filler.configure(10, 6, this.battleview.arena.getBoundaries());
@ -38,14 +39,16 @@ module TS.SpaceTac.UI {
let iy = 148;
let effects = ship.sticky_effects;
if (effects.length > 0) {
filler.addText(0, iy, "Active effects", "#ffffff", 18, false, true);
iy += 30;
effects.forEach(effect => {
filler.addText(0, iy, `${effect.getDescription()}`, effect.isBeneficial() ? "#afe9c6" : "#e9afaf", 16);
iy += 26;
});
if (sprite) {
let effects = sprite.active_effects.area.concat(sprite.active_effects.sticky);
if (effects.length > 0) {
filler.addText(0, iy, "Active effects", "#ffffff", 18, false, true);
iy += 30;
effects.forEach(effect => {
filler.addText(0, iy, `${effect.getDescription()}`, effect.isBeneficial() ? "#afe9c6" : "#e9afaf", 16);
iy += 26;
});
}
}
let weapons = ship.listEquipment(SlotType.Weapon);
@ -61,7 +64,6 @@ module TS.SpaceTac.UI {
filler.addText(140, 36, "Emergency Stasis Protocol\nship disabled", "#a899db", 20, true, true);
}
let sprite = this.battleview.arena.findShipSprite(ship);
if (sprite) {
let bounds = sprite.getBounds();
bounds.x = sprite.worldPosition.x + sprite.width * sprite.worldScale.x * 0.5; // TODO Should not be necessary

View file

@ -102,6 +102,7 @@ module TS.SpaceTac.UI {
*/
addImage(x: number, y: number, key: string, scale = 1): void {
let image = new Phaser.Image(this.container.game, x, y, key);
image.data.key = key;
image.scale.set(scale);
this.container.content.add(image);
}