diff --git a/README.md b/README.md index c8e78f1..87edf2f 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,17 @@ After making changes to sources, you need to recompile: In combat, a ship's vitals are represented by the HSP system (Hull-Shield-Power): -* **Hull** - Amout of damage that a ship can receive before having to shut down all its systems +* **Hull** - Amount of damage that a ship can sustain before having to engage emergency stasis * **Shield** - Amount of damage that the shield equipments may absorb to protect the Hull * **Power** - Available action points (some actions require more power than others) These values will be changed by various effects (usage of equipments, sustained damage...). +Once the Hull of a ship is fully damaged (Hull=0), the ship engages its ESP, or Emergency +Statis Protocol. This protocol activates a statis field that protects the ship for the +remaining of the battle, preventing any further damage, but rendering it fully inoperent. +For battle purpose, the ship is to be considered "dead". + ### Attributes Attributes represent a ship's ability to use its HSP system: @@ -67,11 +72,11 @@ Each equipment has minimal skill requirements to be used. For example, a weapon and "energy >= 3" to be equipped. A ship that does not meet these requirements will not be able to use the equipment. -As for attributes, skill values are controlled by equipments, effects and level up. +Like for attributes, skill values are controlled by equipments, effects and level up. -If an equipped item has a requirement of "time >= 2", that the ship has time skill of exactly 2, and that a -temporary effect of "time -1" is active, the requirement is no longer fulfilled and the equipped item -is then temporarily disabled (no more effects and cannot be used), until the "time -1" effect is lifted. +If an equipped item has a requirement of "time skill >= 2", that the ship has "time skill" of exactly 2, and +that a temporary effect of "time skill -1" is active, the requirement is no longer fulfilled and the equipped +item is then temporarily disabled (no more effects and cannot be used), until the "time skill -1" effect is lifted. ## Drones diff --git a/TODO b/TODO index 978706d..06f9b26 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,11 @@ * Drones: add tooltip * Drones: add hull points and take area damage * Drones: change the sprite angle for deploy animation -* Better display of effects over ship sprite (repairs, sticky effects...) * Add a battle log display * Organize arena objects and information in layers +* Prevent arena effects information (eg. "shield -36") to overflow out of the arena * Allow to cancel last moves +* Identify ships in emergency stasis more clearly * Add more visual effects to weapons, hits and explosions * Effect should be random in a range (eg. "damage target 50-75") * Add an overload/cooling system diff --git a/out/assets/images/battle/attributes/powercapacity.png b/out/assets/images/battle/attributes/powercapacity.png new file mode 100644 index 0000000..7247fe7 Binary files /dev/null and b/out/assets/images/battle/attributes/powercapacity.png differ diff --git a/src/core/Ship.spec.ts b/src/core/Ship.spec.ts index 1b88d80..c7155a3 100644 --- a/src/core/Ship.spec.ts +++ b/src/core/Ship.spec.ts @@ -107,8 +107,8 @@ module TS.SpaceTac.Specs { expect(ship.values.hull.get()).toEqual(40); expect(ship.values.shield.get()).toEqual(80); expect(battle.log.events.length).toBe(3); - expect(battle.log.events[0]).toEqual(new ValueChangeEvent(ship, ship.values.shield)); - expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship, ship.values.hull)); + expect(battle.log.events[0]).toEqual(new ValueChangeEvent(ship, ship.values.shield, -20)); + expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship, ship.values.hull, -10)); expect(battle.log.events[2]).toEqual(new DamageEvent(ship, 10, 20)); battle.log.clear(); diff --git a/src/core/Ship.ts b/src/core/Ship.ts index 0ac82e3..3294647 100644 --- a/src/core/Ship.ts +++ b/src/core/Ship.ts @@ -195,20 +195,20 @@ module TS.SpaceTac { * Returns true if the value changed. */ setValue(name: keyof ShipValues, value: number, offset = false, log = true): boolean { - let changed: boolean; + let diff = 0; let val = this.values[name]; if (offset) { - changed = val.add(value); + diff = val.add(value); } else { - changed = val.set(value); + diff = val.set(value); } - if (changed && log) { - this.addBattleEvent(new ValueChangeEvent(this, val)); + if (log && diff != 0) { + this.addBattleEvent(new ValueChangeEvent(this, val, diff)); } - return changed; + return diff != 0; } /** @@ -226,10 +226,8 @@ module TS.SpaceTac { * Returns true if the value changed. */ setAttribute(name: keyof ShipAttributes, value: number, log = true): boolean { - let changed: boolean; let attr = this.attributes[name]; - - changed = attr.set(value); + let diff = attr.set(value); // TODO more generic if (name == "power_capacity") { @@ -240,11 +238,11 @@ module TS.SpaceTac { this.values.hull.setMaximal(attr.get()); } - if (changed && log) { - this.addBattleEvent(new ValueChangeEvent(this, attr)); + if (log && diff != 0) { + this.addBattleEvent(new ValueChangeEvent(this, attr, diff)); } - return changed; + return diff != 0; } // Initialize the action points counter diff --git a/src/core/ShipValue.spec.ts b/src/core/ShipValue.spec.ts index a8e7872..8e7aa7c 100644 --- a/src/core/ShipValue.spec.ts +++ b/src/core/ShipValue.spec.ts @@ -39,34 +39,34 @@ module TS.SpaceTac { expect(attr.get()).toBe(50); }); - it("tells if value changed", function () { - var result: boolean; + it("tells the value variation", function () { + var result: number; var attr = new ShipValue("test", 50, 100); expect(attr.get()).toBe(50); result = attr.set(51); - expect(result).toBe(true); + expect(result).toBe(1); result = attr.set(51); - expect(result).toBe(false); + expect(result).toBe(0); result = attr.add(1); - expect(result).toBe(true); + expect(result).toBe(1); result = attr.add(0); - expect(result).toBe(false); + expect(result).toBe(0); result = attr.add(1000); - expect(result).toBe(true); + expect(result).toBe(48); result = attr.add(2000); - expect(result).toBe(false); + expect(result).toBe(0); result = attr.set(-500); - expect(result).toBe(true); + expect(result).toBe(-100); result = attr.add(-600); - expect(result).toBe(false); + expect(result).toBe(0); }); }); } diff --git a/src/core/ShipValue.ts b/src/core/ShipValue.ts index bf46f33..612a433 100644 --- a/src/core/ShipValue.ts +++ b/src/core/ShipValue.ts @@ -36,13 +36,13 @@ module TS.SpaceTac { /** * Set an absolute value * - * Returns true if the value changed + * Returns the variation in value */ - set(value: number): boolean { + set(value: number): number { var old_value = this.current; this.current = value; this.fix(); - return this.current !== old_value; + return this.current - old_value; } /** @@ -50,11 +50,11 @@ module TS.SpaceTac { * * Returns true if the value changed */ - add(value: number): boolean { + add(value: number): number { var old_value = this.current; this.current += value; this.fix(); - return this.current !== old_value; + return this.current - old_value; } /** diff --git a/src/core/ai/BullyAI.spec.ts b/src/core/ai/BullyAI.spec.ts index aa29179..1e52aff 100644 --- a/src/core/ai/BullyAI.spec.ts +++ b/src/core/ai/BullyAI.spec.ts @@ -235,17 +235,13 @@ module TS.SpaceTac.Specs { expect(battle.log.events.length).toBe(7); expect(battle.log.events[0]).toEqual(new MoveEvent(ship1, 2, 0)); - expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship1, - new ShipValue("power", 2, 10))); + expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship1, new ShipValue("power", 2, 10), -4)); expect(battle.log.events[2]).toEqual(new FireEvent(ship1, weapon, Target.newFromShip(ship2))); - expect(battle.log.events[3]).toEqual(new ValueChangeEvent(ship2, - new ShipValue("shield", 0))); - expect(battle.log.events[4]).toEqual(new ValueChangeEvent(ship2, - new ShipValue("hull", 5))); + expect(battle.log.events[3]).toEqual(new ValueChangeEvent(ship2, new ShipValue("shield", 0), -10)); + expect(battle.log.events[4]).toEqual(new ValueChangeEvent(ship2, new ShipValue("hull", 5), -10)); expect(battle.log.events[5]).toEqual(new DamageEvent(ship2, 10, 10)); - expect(battle.log.events[6]).toEqual(new ValueChangeEvent(ship1, - new ShipValue("power", 1, 10))); + expect(battle.log.events[6]).toEqual(new ValueChangeEvent(ship1, new ShipValue("power", 1, 10), -1)); }); }); } diff --git a/src/core/events/ValueChangeEvent.ts b/src/core/events/ValueChangeEvent.ts index d70fab4..b186d15 100644 --- a/src/core/events/ValueChangeEvent.ts +++ b/src/core/events/ValueChangeEvent.ts @@ -3,13 +3,17 @@ module TS.SpaceTac { // Event logged when a ship value or attribute changed export class ValueChangeEvent extends BaseLogEvent { - // Saved version of the value + // Saved version of the current value value: ShipValue; - constructor(ship: Ship, value: ShipValue) { + // Value variation + diff: number; + + constructor(ship: Ship, value: ShipValue, diff: number) { super("value", ship); this.value = copy(value); + this.diff = diff; } } } diff --git a/src/ui/Preload.ts b/src/ui/Preload.ts index 8ccb25b..84b66f5 100644 --- a/src/ui/Preload.ts +++ b/src/ui/Preload.ts @@ -56,6 +56,7 @@ module TS.SpaceTac.UI { this.loadImage("battle/actions/deploy-repairdrone.png"); this.loadImage("battle/weapon/bullet.png"); this.loadImage("battle/attributes/power.png"); + this.loadImage("battle/attributes/powercapacity.png"); this.loadImage("battle/attributes/effect-increase.png"); this.loadImage("battle/attributes/effect-decrease.png"); this.loadImage("battle/attributes/effect-limit.png"); diff --git a/src/ui/battle/Arena.ts b/src/ui/battle/Arena.ts index 143f3ff..5b3f92c 100644 --- a/src/ui/battle/Arena.ts +++ b/src/ui/battle/Arena.ts @@ -2,6 +2,9 @@ module TS.SpaceTac.UI { // Graphical representation of a battle // This is the area in the BattleView that will display ships with their real positions export class Arena extends Phaser.Group { + // Link to battleview + battleview: BattleView; + // Arena background background: Phaser.Button; @@ -11,9 +14,6 @@ module TS.SpaceTac.UI { // Input callback to receive mouse move events private input_callback: any; - // Link to battleview - private battleview: BattleView; - // List of ship sprites private ship_sprites: ArenaShip[] = []; @@ -75,8 +75,8 @@ module TS.SpaceTac.UI { // Initialize state (create sprites) init(): void { // Add ship sprites - this.battleview.battle.play_order.forEach((ship: Ship) => { - var sprite = new ArenaShip(this.battleview, ship); + this.battleview.battle.play_order.forEach(ship => { + var sprite = new ArenaShip(this, ship); this.addChild(sprite); this.ship_sprites.push(sprite); }); @@ -92,6 +92,7 @@ module TS.SpaceTac.UI { var sprite = this.findShipSprite(ship); if (sprite) { sprite.alpha = 0.5; + sprite.displayEffect("Emergency Stasis", false); } } diff --git a/src/ui/battle/ArenaShip.spec.ts b/src/ui/battle/ArenaShip.spec.ts new file mode 100644 index 0000000..f964a4a --- /dev/null +++ b/src/ui/battle/ArenaShip.spec.ts @@ -0,0 +1,24 @@ +/// + +module TS.SpaceTac.UI.Specs { + describe("ArenaShip", () => { + inbattleview_it("adds effects display", (battleview: BattleView) => { + let ship = battleview.battle.playing_ship; + let sprite = battleview.arena.findShipSprite(ship); + + expect(sprite.effects.children.length).toBe(0); + + sprite.displayValueChanged(new ValueChangeEvent(ship, ship.attributes.power_recovery, -4)); + + expect(sprite.effects.children.length).toBe(1); + let t1 = sprite.effects.getChildAt(0); + expect(t1.text).toBe("power recovery -4"); + + sprite.displayValueChanged(new ValueChangeEvent(ship, ship.values.shield, 12)); + + expect(sprite.effects.children.length).toBe(2); + let t2 = sprite.effects.getChildAt(1); + expect(t2.text).toBe("shield +12"); + }); + }); +} diff --git a/src/ui/battle/ArenaShip.ts b/src/ui/battle/ArenaShip.ts index e9c628e..fe5f084 100644 --- a/src/ui/battle/ArenaShip.ts +++ b/src/ui/battle/ArenaShip.ts @@ -16,30 +16,38 @@ module TS.SpaceTac.UI { // Frame to indicate the owner of the ship, and if it is playing frame: Phaser.Image; + // Effects display + effects: Phaser.Group; + // Create a ship sprite usable in the Arena - constructor(battleview: BattleView, ship: Ship) { - super(battleview.game); + constructor(parent: Arena, ship: Ship) { + super(parent.game); + let battleview = parent.battleview; this.ship = ship; this.enemy = this.ship.getPlayer() != battleview.player; // Add ship sprite - this.sprite = new Phaser.Button(battleview.game, 0, 0, "ship-" + ship.model + "-sprite"); + this.sprite = new Phaser.Button(this.game, 0, 0, "ship-" + ship.model + "-sprite"); this.sprite.rotation = ship.arena_angle; this.sprite.anchor.set(0.5, 0.5); this.addChild(this.sprite); // Add playing effect - this.frame = new Phaser.Image(battleview.game, 0, 0, `battle-arena-ship-normal-${this.enemy ? "enemy" : "own"}`, 0); + this.frame = new Phaser.Image(this.game, 0, 0, `battle-arena-ship-normal-${this.enemy ? "enemy" : "own"}`, 0); this.frame.anchor.set(0.5, 0.5); this.addChild(this.frame); // Add hover effect - this.hover = new Phaser.Image(battleview.game, 0, 0, "battle-arena-ship-hover", 0); + this.hover = new Phaser.Image(this.game, 0, 0, "battle-arena-ship-hover", 0); this.hover.anchor.set(0.5, 0.5); this.hover.visible = false; this.addChild(this.hover); + // Effects display + this.effects = new Phaser.Group(this.game); + this.addChild(this.effects); + // Handle input on ship sprite Tools.setHoverClick(this.sprite, () => battleview.cursorOnShip(ship), () => battleview.cursorOffShip(ship), () => battleview.cursorClicked()); @@ -75,33 +83,28 @@ module TS.SpaceTac.UI { } } - // Briefly display the damage done to the ship - displayDamage(hull: number, shield: number) { - if (hull > 0) { - var hull_text = new Phaser.Text(this.game, -20, -20, Math.round(hull).toString(), - { font: "bold 16pt Arial", align: "center", fill: "#eb4e4a" }); - hull_text.anchor.set(0.5, 0.5); - this.addChild(hull_text); - this.animateDamageText(hull_text); - } - if (shield > 0) { - var shield_text = new Phaser.Text(this.game, 20, -20, Math.round(shield).toString(), - { font: "bold 16pt Arial", align: "center", fill: "#2ad8dc" }); - shield_text.anchor.set(0.5, 0.5); - this.addChild(shield_text); - this.animateDamageText(shield_text); - } + /** + * 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); + + this.effects.position.set(-this.effects.width / 2, this.sprite.height * 0.7); + + 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)); } - private animateDamageText(text: Phaser.Text) { - text.alpha = 0; - var tween = this.game.tweens.create(text); - tween.to({ alpha: 1 }, 100, Phaser.Easing.Circular.In, false, 400); - tween.to({ y: -50, alpha: 0 }, 1000, Phaser.Easing.Circular.In, false, 1000); - tween.onComplete.addOnce(() => { - text.destroy(); - }); - tween.start(); + /** + * Display interesting changes in ship values + */ + displayValueChanged(event: ValueChangeEvent) { + let diff = event.diff; + let name = event.value.name; + this.displayEffect(`${name} ${diff < 0 ? "-" : "+"}${Math.abs(diff)}`, diff >= 0); } } } diff --git a/src/ui/battle/LogProcessor.ts b/src/ui/battle/LogProcessor.ts index 1d90259..0789fcd 100644 --- a/src/ui/battle/LogProcessor.ts +++ b/src/ui/battle/LogProcessor.ts @@ -72,10 +72,6 @@ module TS.SpaceTac.UI { // Damage to ship private processDamageEvent(event: DamageEvent): void { - var sprite = this.view.arena.findShipSprite(event.ship); - if (sprite) { - sprite.displayDamage(event.hull, event.shield); - } var item = this.view.ship_list.findItem(event.ship); if (item) { item.setDamageHit(); @@ -92,10 +88,14 @@ module TS.SpaceTac.UI { // Ship value changed private processValueChangedEvent(event: ValueChangeEvent): void { + var sprite = this.view.arena.findShipSprite(event.ship); + sprite.displayValueChanged(event); + var item = this.view.ship_list.findItem(event.ship); if (item) { item.updateAttributes(); } + // TODO Update tooltip } diff --git a/src/ui/battle/ShipTooltip.ts b/src/ui/battle/ShipTooltip.ts index 6413822..668c9c1 100644 --- a/src/ui/battle/ShipTooltip.ts +++ b/src/ui/battle/ShipTooltip.ts @@ -136,7 +136,7 @@ module TS.SpaceTac.UI { } let text = `${effect.getDescription()} (${effect.duration} turns)`; - let color = effect.isBeneficial() ? "afe9c6" : "#e9afaf"; + let color = effect.isBeneficial() ? "#afe9c6" : "#e9afaf"; let effect_text = new Phaser.Text(this.game, 60, effect_group.height / 2, text, { font: "16pt Arial", fill: color }); effect_text.anchor.set(0, 0.5); effect_group.addChild(effect_text);