diff --git a/TODO.md b/TODO.md index ab60a4e..deefc23 100644 --- a/TODO.md +++ b/TODO.md @@ -41,7 +41,6 @@ Battle * Fix arena's ship hovering happening even when the character sheet is open on top * Add a voluntary retreat option * Add scroll buttons when there are too many actions -* Remove dead ships from ship list and play order * Add quick animation of playing ship indicator, on ship change * Toggle bar/text display in power section of action bar * Display effects description instead of attribute changes @@ -64,6 +63,7 @@ Battle * Add shorcut to perform only the "move" part of a move+fire simulation * Fix delay of shield/hull impact effects (should depend on weapon animation, and ship location) * Indicate visually the power gain of "end turn" +* Add a turn count marker in the ship list Ships models and equipments --------------------------- diff --git a/src/common b/src/common index a530998..586788a 160000 --- a/src/common +++ b/src/common @@ -1 +1 @@ -Subproject commit a530998523e8f8c7a37323d5dc047241f71f6a36 +Subproject commit 586788a4685a5b9a5cc8f4882f48f18c91997dc3 diff --git a/src/core/Battle.spec.ts b/src/core/Battle.spec.ts index 7aacc11..32c4a16 100644 --- a/src/core/Battle.spec.ts +++ b/src/core/Battle.spec.ts @@ -63,54 +63,44 @@ module TK.SpaceTac { var fleet1 = new Fleet(); var fleet2 = new Fleet(); - var ship1 = new Ship(fleet1, "F1S1"); - var ship2 = new Ship(fleet1, "F1S2"); - var ship3 = new Ship(fleet2, "F2S1"); + var ship1 = new Ship(fleet1, "ship1"); + var ship2 = new Ship(fleet1, "ship2"); + var ship3 = new Ship(fleet2, "ship3"); var battle = new Battle(fleet1, fleet2); // Check empty play_order case expect(battle.playing_ship).toBeNull(); - expect(battle.playing_ship_index).toBeNull(); battle.advanceToNextShip(); expect(battle.playing_ship).toBeNull(); - expect(battle.playing_ship_index).toBeNull(); // Force play order iforeach(battle.iships(), ship => ship.setAttribute("maneuvrability", 1)); var gen = new SkewedRandomGenerator([0.1, 0.2, 0.0]); battle.throwInitiative(gen); - expect(battle.playing_ship).toBeNull(); - expect(battle.playing_ship_index).toBeNull(); battle.advanceToNextShip(); - expect(battle.playing_ship).toBe(ship2); - expect(battle.playing_ship_index).toBe(0); battle.advanceToNextShip(); - expect(battle.playing_ship).toBe(ship1); - expect(battle.playing_ship_index).toBe(1); battle.advanceToNextShip(); - expect(battle.playing_ship).toBe(ship3); - expect(battle.playing_ship_index).toBe(2); battle.advanceToNextShip(); - expect(battle.playing_ship).toBe(ship2); - expect(battle.playing_ship_index).toBe(0); - // A dead ship is not skipped + // A dead ship is skipped ship1.setDead(); - battle.advanceToNextShip(); + expect(battle.playing_ship).toBe(ship3); - expect(battle.playing_ship).toBe(ship1); - expect(battle.playing_ship_index).toBe(1); + // Playing ship dies + ship3.setDead(); + battle.advanceToNextShip(); + expect(battle.playing_ship).toBe(ship2); }); it("calls startTurn on ships", function () { @@ -290,7 +280,7 @@ module TK.SpaceTac { expect(battle.canPlay(player)).toBe(false); let ship = new Ship(); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); expect(battle.canPlay(player)).toBe(false); @@ -306,28 +296,28 @@ module TK.SpaceTac { battle.advanceToNextShip(); expect(battle.playing_ship).toBe(battle.play_order[0]); - expect(battle.getTurnsBefore(battle.play_order[0])).toBe(0); - expect(battle.getTurnsBefore(battle.play_order[1])).toBe(1); - expect(battle.getTurnsBefore(battle.play_order[2])).toBe(2); + expect(battle.getPlayOrder(battle.play_order[0])).toBe(0); + expect(battle.getPlayOrder(battle.play_order[1])).toBe(1); + expect(battle.getPlayOrder(battle.play_order[2])).toBe(2); battle.advanceToNextShip(); expect(battle.playing_ship).toBe(battle.play_order[1]); - expect(battle.getTurnsBefore(battle.play_order[0])).toBe(2); - expect(battle.getTurnsBefore(battle.play_order[1])).toBe(0); - expect(battle.getTurnsBefore(battle.play_order[2])).toBe(1); + expect(battle.getPlayOrder(battle.play_order[0])).toBe(2); + expect(battle.getPlayOrder(battle.play_order[1])).toBe(0); + expect(battle.getPlayOrder(battle.play_order[2])).toBe(1); battle.advanceToNextShip(); - expect(battle.getTurnsBefore(battle.play_order[0])).toBe(1); - expect(battle.getTurnsBefore(battle.play_order[1])).toBe(2); - expect(battle.getTurnsBefore(battle.play_order[2])).toBe(0); + expect(battle.getPlayOrder(battle.play_order[0])).toBe(1); + expect(battle.getPlayOrder(battle.play_order[1])).toBe(2); + expect(battle.getPlayOrder(battle.play_order[2])).toBe(0); battle.advanceToNextShip(); - expect(battle.getTurnsBefore(battle.play_order[0])).toBe(0); - expect(battle.getTurnsBefore(battle.play_order[1])).toBe(1); - expect(battle.getTurnsBefore(battle.play_order[2])).toBe(2); + expect(battle.getPlayOrder(battle.play_order[0])).toBe(0); + expect(battle.getPlayOrder(battle.play_order[1])).toBe(1); + expect(battle.getPlayOrder(battle.play_order[2])).toBe(2); }); it("lists area effects", function () { diff --git a/src/core/Battle.ts b/src/core/Battle.ts index b3d126c..66872c7 100644 --- a/src/core/Battle.ts +++ b/src/core/Battle.ts @@ -19,15 +19,15 @@ module TK.SpaceTac { // List of fleets engaged in battle fleets: Fleet[] - // List of ships, sorted by their initiative throw + // Container of all engaged ships + ships: RObjectContainer + + // List of playing ships, sorted by their initiative throw play_order: Ship[] + play_index = -1 - // Current turn - turn: number - - // Current ship whose turn it is to play - playing_ship_index: number | null - playing_ship: Ship | null + // Current battle "cycle" (one cycle is one turn done for all ships in the play order) + cycle: number // List of deployed drones drones: Drone[] = [] @@ -47,9 +47,8 @@ module TK.SpaceTac { // Create a battle between two fleets constructor(fleet1 = new Fleet(), fleet2 = new Fleet(), width = 1808, height = 948) { this.fleets = [fleet1, fleet2]; + this.ships = new RObjectContainer(fleet1.ships.concat(fleet2.ships)); this.play_order = []; - this.playing_ship_index = null; - this.playing_ship = null; this.ended = false; this.width = width; this.height = height; @@ -82,21 +81,17 @@ module TK.SpaceTac { } /** - * Get the number of turns in a game cycle. + * Get the currently playing ship */ - getCycleLength(): number { - return this.play_order.length; + get playing_ship(): Ship | null { + return this.play_order[this.play_index] || null; } /** - * Get the number of turns before a specific ship plays (currently playing ship will return 0). + * Get a ship by its ID. */ - getTurnsBefore(ship: Ship): number { - let pos = this.play_order.indexOf(ship) - (this.playing_ship_index || 0); - if (pos < 0) { - pos += this.play_order.length; - } - return pos; + getShip(id: RObjectId): Ship | null { + return this.ships.get(id); } /** @@ -150,6 +145,42 @@ module TK.SpaceTac { return (ship2.play_priority - ship1.play_priority); }); this.play_order = play_order; + this.play_index = -1; + } + + /** + * Get the number of turns before a specific ship plays (currently playing ship will return 0). + * + * Returns -1 if the ship is not in the play list. + */ + getPlayOrder(ship: Ship): number { + let index = this.play_order.indexOf(ship); + if (index < 0) { + return -1; + } else { + let result = index - this.play_index; + return (result < 0) ? result + this.play_order.length : result; + } + } + + /** + * Add a ship in the play order list + */ + removeFromPlayOrder(idx: number): void { + this.play_order.splice(idx, 1); + if (idx <= this.play_index) { + this.play_index -= 1; + } + } + + /** + * Remove a ship from the play order list + */ + insertInPlayOrder(idx: number, ship: Ship): void { + this.play_order.splice(idx, 0, ship); + if (idx <= this.play_index) { + this.play_index += 1; + } } // Defines the initial ship positions of all engaged fleets @@ -195,7 +226,7 @@ module TK.SpaceTac { } // Apply to all ships - iforeach(this.iships(), ship => ship.endBattle(this.turn)); + iforeach(this.iships(), ship => ship.endBattle(this.cycle)); this.stats.onBattleEnd(this.fleets[0], this.fleets[1]); } @@ -228,10 +259,10 @@ module TK.SpaceTac { // If at the end of the play order, next turn will start automatically // Member 'play_order' must be defined before calling this function advanceToNextShip(log: boolean = true): void { - var previous_ship = this.playing_ship; + let previous_ship = this.playing_ship; - if (this.playing_ship && this.playing_ship.playing) { - this.playing_ship.endTurn(); + if (previous_ship && previous_ship.playing) { + previous_ship.endTurn(); } if (this.checkEndBattle(log)) { @@ -240,21 +271,14 @@ module TK.SpaceTac { this.drones.forEach(drone => drone.activate()); - if (this.play_order.length === 0) { - this.playing_ship_index = null; - this.playing_ship = null; - } else { - if (this.playing_ship_index == null) { - this.playing_ship_index = 0; - } else { - this.playing_ship_index = (this.playing_ship_index + 1) % this.play_order.length; - } - this.playing_ship = this.play_order[this.playing_ship_index]; + this.play_index += 1; + if (this.play_index >= this.play_order.length) { + this.play_index = 0; } if (this.playing_ship) { - if (this.playing_ship_index == 0) { - this.turn += 1; + if (this.play_index == 0) { + this.cycle += 1; } this.playing_ship.startTurn(); } @@ -288,7 +312,7 @@ module TK.SpaceTac { // This will not add any event to the battle log start(): void { this.ended = false; - this.turn = 0; + this.cycle = 0; this.placeShips(); this.stats.onBattleStart(this.fleets[0], this.fleets[1]); this.throwInitiative(); @@ -324,7 +348,7 @@ module TK.SpaceTac { // Indicate emergency stasis this.play_order.forEach(ship => { if (!ship.alive) { - let event = new DeathEvent(ship); + let event = new DeathEvent(this, ship); event.initial = true; result.push(event); } @@ -347,6 +371,18 @@ module TK.SpaceTac { return result; } + /** + * Apply a list of events on this game state (and optionally store the events in the battle log) + */ + applyEvents(events: BaseBattleEvent[], log = true): void { + events.forEach(event => { + event.apply(this); + if (log) { + this.log.add(event); + } + }); + } + /** * Defines the initial ship positions for one fleet * diff --git a/src/core/BattleCheats.spec.ts b/src/core/BattleCheats.spec.ts index 1ee201f..c9b5581 100644 --- a/src/core/BattleCheats.spec.ts +++ b/src/core/BattleCheats.spec.ts @@ -22,12 +22,13 @@ module TK.SpaceTac.Specs { it("adds an equipment", function () { let battle = new Battle(); - battle.playing_ship = new Ship(); - battle.playing_ship.upgradeSkill("skill_materials"); + let ship = new Ship(); + TestTools.setShipPlaying(battle, ship); + ship.upgradeSkill("skill_materials"); - expect(battle.playing_ship.listEquipment()).toEqual([]); + expect(ship.listEquipment()).toEqual([]); battle.cheats.equip("Iron Hull"); - expect(battle.playing_ship.listEquipment()).toEqual([jasmine.objectContaining({name: "Iron Hull", level: 1})]); + expect(ship.listEquipment()).toEqual([jasmine.objectContaining({ name: "Iron Hull", level: 1 })]); }) }) } diff --git a/src/core/Fleet.spec.ts b/src/core/Fleet.spec.ts index 49445b4..81cc94a 100644 --- a/src/core/Fleet.spec.ts +++ b/src/core/Fleet.spec.ts @@ -83,7 +83,8 @@ module TK.SpaceTac { }); it("checks if a fleet is alive", function () { - let fleet = new Fleet(); + let battle = new Battle(); + let fleet = battle.fleets[0]; expect(fleet.isAlive()).toBe(false); let ship1 = fleet.addShip(); diff --git a/src/core/Fleet.ts b/src/core/Fleet.ts index 2d5cefd..087e5f1 100644 --- a/src/core/Fleet.ts +++ b/src/core/Fleet.ts @@ -61,6 +61,9 @@ module TK.SpaceTac { } add(this.ships, ship); ship.fleet = this; + if (this.battle) { + this.battle.ships.add(ship); + } return ship; } diff --git a/src/core/Ship.spec.ts b/src/core/Ship.spec.ts index 9b0e839..c947e19 100644 --- a/src/core/Ship.spec.ts +++ b/src/core/Ship.spec.ts @@ -236,7 +236,7 @@ module TK.SpaceTac.Specs { expect(action.activated).toBe(false); let battle = new Battle(ship.fleet); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); ship.startTurn(); expect(action.activated).toBe(false); @@ -273,7 +273,7 @@ module TK.SpaceTac.Specs { 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; + TestTools.setShipPlaying(battle, ship1); shield.action.apply(ship1); expect(ship1.getAttribute("shield_capacity")).toBe(5); @@ -325,15 +325,15 @@ module TK.SpaceTac.Specs { ship.addDamage(5, 0); expect(ship.alive).toBe(false); - expect(battle.log.events.length).toBe(4); + expect(battle.log.events.length).toBe(3); expect(battle.log.events[0].code).toEqual("value"); expect(battle.log.events[1].code).toEqual("damage"); - expect(battle.log.events[2].code).toEqual("activeeffects"); - expect(battle.log.events[3].code).toEqual("death"); + expect(battle.log.events[2].code).toEqual("death"); }); it("checks if a ship is able to play", function () { - var ship = new Ship(); + let battle = new Battle(); + let ship = battle.fleets[0].addShip(); expect(ship.isAbleToPlay()).toBe(false); expect(ship.isAbleToPlay(false)).toBe(true); diff --git a/src/core/Ship.ts b/src/core/Ship.ts index 29eeb1f..3301963 100644 --- a/src/core/Ship.ts +++ b/src/core/Ship.ts @@ -1,8 +1,10 @@ +/// + module TK.SpaceTac { /** * A single ship in a fleet */ - export class Ship { + export class Ship extends RObject { // Fleet this ship is a member of fleet: Fleet @@ -56,6 +58,8 @@ module TK.SpaceTac { // Create a new ship inside a fleet constructor(fleet: Fleet | null = null, name = "unnamed", model = new ShipModel("default", "Default", 1, 0, false, 0)) { + super(); + this.fleet = fleet || new Fleet(); this.name = name; this.alive = true; @@ -463,20 +467,49 @@ module TK.SpaceTac { } } + /** + * Get the events needed to apply changes to ship values or attributes + */ + getValueEvents(name: keyof ShipValues, value: number): BaseBattleEvent[] { + let result: BaseBattleEvent[] = []; + + let current = this.values[name]; + if (current.get() != value) { + let newval = copy(current); + newval.set(value); + result.push(new ValueChangeEvent(this, newval, value - current.get())); + } + + return result; + } + + /** + * Produce events to set the ship in emergency stasis + */ + getDeathEvents(battle: Battle): BaseBattleEvent[] { + let result: BaseBattleEvent[] = []; + + keys(SHIP_VALUES).forEach(value => { + result = result.concat(this.getValueEvents(value, 0)); + }); + + // TODO Remove sticky effects + + result.push(new DeathEvent(battle, this)); + + return result; + } + /** * Set the death status on this ship */ - setDead(log: boolean = true): void { - this.alive = false; - this.values.hull.set(0); - this.values.shield.set(0); - this.values.power.set(0); - - this.sticky_effects = []; - this.setActiveEffectsChanged(); - - if (log) { - this.addBattleEvent(new DeathEvent(this)); + setDead(): void { + let battle = this.getBattle(); + if (battle) { + let events = this.getDeathEvents(battle); + battle.applyEvents(events); + } else { + console.error("Cannot set ship dead outside of battle", this); } } @@ -500,7 +533,7 @@ module TK.SpaceTac { if (this.values.hull.get() === 0) { // Ship is dead - this.setDead(log); + this.setDead(); } } diff --git a/src/core/TestTools.ts b/src/core/TestTools.ts index c182c5e..5289624 100644 --- a/src/core/TestTools.ts +++ b/src/core/TestTools.ts @@ -53,6 +53,13 @@ module TK.SpaceTac { return equipment; } + // Set the current playing ship + static setShipPlaying(battle: Battle, ship: Ship): void { + add(battle.play_order, ship); + battle.play_index = battle.play_order.indexOf(ship); + ship.playing = true; + } + // Set a ship action points, adding/updating an equipment if needed static setShipAP(ship: Ship, points: number, recovery: number = 0): void { var equipment = this.getOrGenEquipment(ship, SlotType.Power, new Equipments.NuclearReactor()); diff --git a/src/core/actions/DeployDroneAction.spec.ts b/src/core/actions/DeployDroneAction.spec.ts index 61cc2bd..dc526f3 100644 --- a/src/core/actions/DeployDroneAction.spec.ts +++ b/src/core/actions/DeployDroneAction.spec.ts @@ -26,7 +26,7 @@ module TK.SpaceTac { let battle = new Battle(); let ship = battle.fleets[0].addShip(); ship.setArenaPosition(0, 0); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); TestTools.setShipAP(ship, 3); let equipment = new Equipment(SlotType.Weapon, "testdrone"); let action = new DeployDroneAction(equipment, 2, 8, 2, 4, [new DamageEffect(50)]); diff --git a/src/core/actions/EndTurnAction.spec.ts b/src/core/actions/EndTurnAction.spec.ts index 367fcda..cb761db 100644 --- a/src/core/actions/EndTurnAction.spec.ts +++ b/src/core/actions/EndTurnAction.spec.ts @@ -20,11 +20,11 @@ module TK.SpaceTac.Specs { let battle = Battle.newQuickRandom(); let action = new EndTurnAction(); - expect(battle.playing_ship_index).toBe(0); + expect(battle.play_index).toBe(0); let result = action.apply(battle.play_order[0], Target.newFromShip(battle.play_order[0])); expect(result).toBe(true); - expect(battle.playing_ship_index).toBe(1); + expect(battle.play_index).toBe(1); }); }); } diff --git a/src/core/actions/MoveAction.spec.ts b/src/core/actions/MoveAction.spec.ts index 8c56c62..f75189a 100644 --- a/src/core/actions/MoveAction.spec.ts +++ b/src/core/actions/MoveAction.spec.ts @@ -3,7 +3,7 @@ module TK.SpaceTac { it("checks movement against remaining AP", function () { var ship = new Ship(); var battle = new Battle(ship.fleet); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); ship.values.power.setMaximal(20); ship.values.power.set(6); ship.arena_x = 0; @@ -45,7 +45,7 @@ module TK.SpaceTac { ship.arena_y = 0; var engine = new Equipment(); var action = new MoveAction(engine, 1); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); spyOn(console, "warn").and.stub(); diff --git a/src/core/actions/TriggerAction.spec.ts b/src/core/actions/TriggerAction.spec.ts index 730b5da..9e0925a 100644 --- a/src/core/actions/TriggerAction.spec.ts +++ b/src/core/actions/TriggerAction.spec.ts @@ -29,7 +29,7 @@ module TK.SpaceTac { let battle = new Battle(fleet); battle.play_order = [ship, ship1, ship2, ship3]; - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); fleet.setBattle(battle); action.apply(ship, Target.newFromLocation(50, 50)); diff --git a/src/core/ai/AIDuel.ts b/src/core/ai/AIDuel.ts index 4b1fe0e..c6993d6 100644 --- a/src/core/ai/AIDuel.ts +++ b/src/core/ai/AIDuel.ts @@ -78,7 +78,7 @@ module TK.SpaceTac { }); // Run battle - while (!battle.ended && battle.turn < 100) { + while (!battle.ended && battle.cycle < 100) { if (this.stopped) { return; } diff --git a/src/core/ai/TacticalAI.spec.ts b/src/core/ai/TacticalAI.spec.ts index a93c5be..e943368 100644 --- a/src/core/ai/TacticalAI.spec.ts +++ b/src/core/ai/TacticalAI.spec.ts @@ -24,7 +24,7 @@ module TK.SpaceTac.Specs { it("applies the highest evaluated maneuver", function () { let battle = new Battle(); let ship = battle.fleets[0].addShip(); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); ship.playing = true; let ai = new TacticalAI(ship, Timer.synchronous); diff --git a/src/core/ai/TacticalAIHelpers.spec.ts b/src/core/ai/TacticalAIHelpers.spec.ts index c7f3031..7a8cc54 100644 --- a/src/core/ai/TacticalAIHelpers.spec.ts +++ b/src/core/ai/TacticalAIHelpers.spec.ts @@ -8,7 +8,7 @@ module TK.SpaceTac.Specs { let ship1b = battle.fleets[1].addShip(new Ship(null, "1B")); TestTools.setShipAP(ship0a, 10); - battle.playing_ship = ship0a; + TestTools.setShipPlaying(battle, ship0a); let result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle)); expect(result.length).toBe(0); @@ -30,7 +30,7 @@ module TK.SpaceTac.Specs { let ship = battle.fleets[0].addShip(); TestTools.setShipAP(ship, 10); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); let result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1)); expect(result.length).toBe(0); @@ -52,7 +52,7 @@ module TK.SpaceTac.Specs { let weapon = TestTools.addWeapon(ship, 50, 1, 1000, 105); TestTools.setShipAP(ship, 10); - battle.playing_ship = ship; + TestTools.setShipPlaying(battle, ship); let result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle)); expect(result.length).toBe(0); diff --git a/src/core/effects/DamageEffect.spec.ts b/src/core/effects/DamageEffect.spec.ts index c4888cc..384b48d 100644 --- a/src/core/effects/DamageEffect.spec.ts +++ b/src/core/effects/DamageEffect.spec.ts @@ -1,7 +1,8 @@ module TK.SpaceTac.Specs { describe("DamageEffect", function () { it("applies damage and wear", function () { - var ship = new Ship(); + let battle = new Battle(); + let ship = battle.fleets[0].addShip(); TestTools.setShipHP(ship, 150, 400); let hull = ship.listEquipment(SlotType.Hull)[0]; diff --git a/src/core/equipments/DamageProtector.spec.ts b/src/core/equipments/DamageProtector.spec.ts index 90eea3c..804b760 100644 --- a/src/core/equipments/DamageProtector.spec.ts +++ b/src/core/equipments/DamageProtector.spec.ts @@ -40,8 +40,7 @@ module TK.SpaceTac.Equipments { ship1.setArenaPosition(0, 0); ship2.setArenaPosition(100, 0); ship3.setArenaPosition(800, 0); - battle.playing_ship = ship1; - ship1.playing = true; + TestTools.setShipPlaying(battle, ship1); expect(ship1.getAvailableActions()).toEqual([action, new EndTurnAction()]); TestTools.setShipHP(ship1, 100, 0); diff --git a/src/core/equipments/RepairDrone.spec.ts b/src/core/equipments/RepairDrone.spec.ts index c59e209..4213f0e 100644 --- a/src/core/equipments/RepairDrone.spec.ts +++ b/src/core/equipments/RepairDrone.spec.ts @@ -38,8 +38,7 @@ module TK.SpaceTac.Equipments { let battle = new Battle(); let ship = battle.fleets[0].addShip(); - battle.playing_ship = ship; - battle.play_order = [ship]; + TestTools.setShipPlaying(battle, ship); TestTools.setShipAP(ship, 10); let result = nn(equipment.action).apply(ship, new Target(5, 5, null)); expect(result).toBe(true); diff --git a/src/core/events/BaseBattleEvent.ts b/src/core/events/BaseBattleEvent.ts index cb4ba5f..ed2a4cc 100644 --- a/src/core/events/BaseBattleEvent.ts +++ b/src/core/events/BaseBattleEvent.ts @@ -28,7 +28,16 @@ module TK.SpaceTac { * * By default it does nothing */ - apply(battle: Battle) { + apply(battle: Battle): void { + } + + /** + * Reverts the event from a battle state + * + * By default it applies the reverse event + */ + revert(battle: Battle): void { + this.getReverse().apply(battle); } /** diff --git a/src/core/events/DeathEvent.spec.ts b/src/core/events/DeathEvent.spec.ts new file mode 100644 index 0000000..f64d086 --- /dev/null +++ b/src/core/events/DeathEvent.spec.ts @@ -0,0 +1,39 @@ +/// + +module TK.SpaceTac.Specs { + testing("DeathEvent", test => { + test.case("applies and reverts", check => { + let battle = new Battle(); + let ship1 = battle.fleets[0].addShip(); + let ship2 = battle.fleets[0].addShip(); + let ship3 = battle.fleets[1].addShip(); + battle.play_order = [ship3, ship2, ship1]; + + check.equals(ship2.alive, true, "alive"); + check.equals(imaterialize(battle.iships(false)), [ship1, ship2, ship3], "in all ships"); + check.equals(imaterialize(battle.iships(true)), [ship1, ship2, ship3], "in alive ships"); + check.equals(battle.fleets[0].ships, [ship1, ship2], "fleet1"); + check.equals(battle.fleets[1].ships, [ship3], "fleet2"); + check.equals(battle.play_order, [ship3, ship2, ship1], "in play order"); + + let event = new DeathEvent(battle, ship2); + event.apply(battle); + + check.equals(ship2.alive, false, "dead"); + check.equals(imaterialize(battle.iships(false)), [ship1, ship2, ship3], "in all ships"); + check.equals(imaterialize(battle.iships(true)), [ship1, ship3], "not in alive ships anymore"); + check.equals(battle.fleets[0].ships, [ship1, ship2], "fleet1"); + check.equals(battle.fleets[1].ships, [ship3], "fleet2"); + check.equals(battle.play_order, [ship3, ship1], "removed from play order"); + + event.revert(battle); + + check.equals(ship2.alive, true, "alive again"); + check.equals(imaterialize(battle.iships(false)), [ship1, ship2, ship3], "in all ships"); + check.equals(imaterialize(battle.iships(true)), [ship1, ship2, ship3], "back in alive ships"); + check.equals(battle.fleets[0].ships, [ship1, ship2], "fleet1"); + check.equals(battle.fleets[1].ships, [ship3], "fleet2"); + check.equals(battle.play_order, [ship3, ship2, ship1], "back in play order"); + }); + }); +} \ No newline at end of file diff --git a/src/core/events/DeathEvent.ts b/src/core/events/DeathEvent.ts index 5ab2c7f..dfee07e 100644 --- a/src/core/events/DeathEvent.ts +++ b/src/core/events/DeathEvent.ts @@ -1,10 +1,48 @@ /// module TK.SpaceTac { - // Event logged when a ship is dead + /** + * A ship dies (or rather is put in emergency stasis mode) + * + * This typically happens when the ship's hull reaches 0. + * A dead ship cannot be interacted with, and will be removed from play order. + */ export class DeathEvent extends BaseLogShipEvent { - constructor(ship: Ship) { + // Unique ID of the ship in the battle + ship_id: RObjectId + + // Index in the play order at which the ship was + play_index: number + + constructor(battle: Battle, ship: Ship) { super("death", ship); + + this.ship_id = ship.id; + this.play_index = battle.play_order.indexOf(ship); + } + + apply(battle: Battle) { + let ship = battle.getShip(this.ship_id); + if (ship) { + ship.alive = false; + if (this.play_index >= 0) { + battle.removeFromPlayOrder(this.play_index); + } + } else { + console.warn("Ship not found", this); + } + } + + revert(battle: Battle) { + let ship = battle.getShip(this.ship_id); + if (ship) { + ship.alive = true; + if (this.play_index >= 0) { + battle.insertInPlayOrder(this.play_index, ship); + } + } else { + console.warn("Ship not found", this); + } } } } diff --git a/src/core/events/ValueChangeEvent.spec.ts b/src/core/events/ValueChangeEvent.spec.ts index c25e2aa..c581aff 100644 --- a/src/core/events/ValueChangeEvent.spec.ts +++ b/src/core/events/ValueChangeEvent.spec.ts @@ -1,9 +1,27 @@ +/// + module TK.SpaceTac.Specs { - describe("ValueChangeEvent", function () { - it("get reverse event", function () { + testing("ValueChangeEvent", test => { + test.case("get reverse event", check => { let ship = new Ship(); let event = new ValueChangeEvent(ship, new ShipValue("hull", 15, 22), 10); - expect(event.getReverse()).toEqual(new ValueChangeEvent(ship, new ShipValue("hull", 5, 22), -10)); + check.equals(event.getReverse(), new ValueChangeEvent(ship, new ShipValue("hull", 5, 22), -10)); + }); + + test.case("applies and reverts", check => { + let battle = new Battle(); + let ship = battle.fleets[0].addShip(); + check.equals(ship.getValue("hull"), 0); + + let events = ship.getValueEvents("hull", 15); + check.equals(ship.getValue("hull"), 0); + check.equals(events.length, 1); + + events[0].apply(battle); + check.equals(ship.getValue("hull"), 15); + + events[0].revert(battle); + check.equals(ship.getValue("hull"), 0); }); }); } \ No newline at end of file diff --git a/src/core/events/ValueChangeEvent.ts b/src/core/events/ValueChangeEvent.ts index 8b5baae..9e3732b 100644 --- a/src/core/events/ValueChangeEvent.ts +++ b/src/core/events/ValueChangeEvent.ts @@ -5,15 +5,19 @@ module TK.SpaceTac { * Event logged when a ship value or attribute changed */ export class ValueChangeEvent extends BaseLogShipEvent { + // Ship ID + ship_id: RObjectId + // Saved version of the current value - value: ShipValue; + value: ShipValue // Value variation - diff: number; + diff: number constructor(ship: Ship, value: ShipValue, diff: number) { super("value", ship); + this.ship_id = ship.id; this.value = copy(value); this.diff = diff; } @@ -23,5 +27,14 @@ module TK.SpaceTac { value.set(value.get() - this.diff); return new ValueChangeEvent(this.ship, value, -this.diff); } + + apply(battle: Battle): void { + let ship = battle.getShip(this.ship_id); + if (ship) { + ship.setValue(this.value.name, this.value.get(), false, false); + } else { + console.warn("Ship not found", this); + } + } } } diff --git a/src/multi/Connection.spec.ts b/src/multi/Connection.spec.ts index 9a2425a..fd6cbef 100644 --- a/src/multi/Connection.spec.ts +++ b/src/multi/Connection.spec.ts @@ -1,6 +1,6 @@ module TK.SpaceTac.Multi.Specs { - describe("Connection", function () { - async_it("finds an unused token", async function () { + testing("Connection", test => { + test.acase("finds an unused token", async check => { let storage = new FakeRemoteStorage(); let connection = new Connection("test", storage); @@ -15,7 +15,7 @@ module TK.SpaceTac.Multi.Specs { expect(other).toEqual("123456"); }); - async_it("loads a session by its id", async function () { + test.acase("loads a session by its id", async check => { let session = new GameSession(); let serializer = new Serializer(TK.SpaceTac); let storage = new FakeRemoteStorage(); @@ -42,7 +42,7 @@ module TK.SpaceTac.Multi.Specs { expect(result).toBeNull(); }); - async_it("lists saves from a device", async function () { + test.acase("lists saves from a device", async check => { let storage = new FakeRemoteStorage(); let connection = new Connection("test", storage); @@ -57,7 +57,7 @@ module TK.SpaceTac.Multi.Specs { expect(result).toEqual({ abc: "ABC", cba: "CBA" }); }); - async_it("publishes saves and retrieves them by token", async function () { + test.acase("publishes saves and retrieves them by token", async check => { let session = new GameSession(); let storage = new FakeRemoteStorage(); let connection = new Connection("test", storage); diff --git a/src/multi/Exchange.spec.ts b/src/multi/Exchange.spec.ts index 027fdaa..c5cd8ce 100644 --- a/src/multi/Exchange.spec.ts +++ b/src/multi/Exchange.spec.ts @@ -1,5 +1,5 @@ module TK.SpaceTac.Multi.Specs { - describe("Exchange", function () { + testing("Exchange", test => { function newExchange(token: string, storage = new FakeRemoteStorage()): [FakeRemoteStorage, Exchange, Exchange] { let connection = new Connection("test", storage); @@ -17,7 +17,7 @@ module TK.SpaceTac.Multi.Specs { spyOn(console, "log").and.stub(); }); - async_it("says hello on start", async function () { + test.acase("says hello on start", async function () { let [storage, peer1, peer2] = newExchange("abc"); spyOn(peer1, "getNextId").and.returnValues("1A", "1B", "1C"); spyOn(peer2, "getNextId").and.returnValues("2A", "2B", "2C"); diff --git a/src/multi/RemoteStorage.spec.ts b/src/multi/RemoteStorage.spec.ts index ff45936..6308187 100644 --- a/src/multi/RemoteStorage.spec.ts +++ b/src/multi/RemoteStorage.spec.ts @@ -1,6 +1,6 @@ module TK.SpaceTac.Multi.Specs { - describe("FakeRemoteStorage", function () { - async_it("can fetch a single record", async function () { + testing("FakeRemoteStorage", test => { + test.acase("can fetch a single record", async function () { let storage = new FakeRemoteStorage(); let result = await storage.find("test", { key: 5 }); @@ -21,7 +21,7 @@ module TK.SpaceTac.Multi.Specs { expect(result).toBeNull(); }); - async_it("inserts or updates objects", async function () { + test.acase("inserts or updates objects", async function () { let storage = new FakeRemoteStorage(); let result = await storage.search("test", { key: 5 }); diff --git a/src/ui/BaseView.ts b/src/ui/BaseView.ts index e7e63a5..82a2649 100644 --- a/src/ui/BaseView.ts +++ b/src/ui/BaseView.ts @@ -241,17 +241,21 @@ module TK.SpaceTac.UI { */ getImageInfo(name: string): { key: string, frame: number } { // TODO Cache - let i = 1; - while (this.game.cache.checkImageKey(`atlas-${i}`)) { - let data = this.game.cache.getFrameData(`atlas-${i}`); - let frames = data.getFrames(); - let frame = first(frames, frame => AssetLoading.getKey(frame.name) == `graphics-exported-${name}`); - if (frame) { - return { key: `atlas-${i}`, frame: frame.index }; + if (this.game.cache.checkImageKey(name)) { + return { key: name, frame: 0 }; + } else { + let i = 1; + while (this.game.cache.checkImageKey(`atlas-${i}`)) { + let data = this.game.cache.getFrameData(`atlas-${i}`); + let frames = data.getFrames(); + let frame = first(frames, frame => AssetLoading.getKey(frame.name) == `graphics-exported-${name}`); + if (frame) { + return { key: `atlas-${i}`, frame: frame.index }; + } + i++; } - i++; + return { key: `-missing-${name}`, frame: 0 }; } - return { key: `-missing-${name}`, frame: 0 }; } /** diff --git a/src/ui/battle/ArenaShip.ts b/src/ui/battle/ArenaShip.ts index 5a264ee..6e3d422 100644 --- a/src/ui/battle/ArenaShip.ts +++ b/src/ui/battle/ArenaShip.ts @@ -121,7 +121,7 @@ module TK.SpaceTac.UI { this.updateEffectsRadius(); // Set location - if (this.battleview.battle.turn == 1 && ship.alive && ship.fleet.player === this.battleview.player) { + if (this.battleview.battle.cycle == 1 && this.battleview.battle.play_index == 0 && ship.alive && ship.fleet.player === this.battleview.player) { this.position.set(ship.arena_x - 500 * Math.cos(ship.arena_angle), ship.arena_y - 500 * Math.sin(ship.arena_angle)); this.moveTo(ship.arena_x, ship.arena_y, ship.arena_angle); } else { @@ -145,7 +145,7 @@ module TK.SpaceTac.UI { if (event.new_ship === this.ship) { this.play_order.text = "-"; } else { - this.play_order.text = this.battleview.battle.getTurnsBefore(this.ship).toString(); + this.play_order.text = this.battleview.battle.getPlayOrder(this.ship).toString(); } } return 0; diff --git a/src/ui/battle/BattleView.ts b/src/ui/battle/BattleView.ts index b8d4d80..dea043d 100644 --- a/src/ui/battle/BattleView.ts +++ b/src/ui/battle/BattleView.ts @@ -1,8 +1,19 @@ /// module TK.SpaceTac.UI { - // Interactive view of a Battle - export class BattleView extends BaseView { + /** + * Interface for interacting with a ship (hover and click) + */ + export interface IShipButton { + cursorOnShip: (ship: Ship) => void; + cursorOffShip: (ship: Ship) => void; + cursorClicked: () => void; + } + + /** + * Interactive view of a Battle + */ + export class BattleView extends BaseView implements IShipButton { // Displayed battle battle: Battle @@ -102,8 +113,8 @@ module TK.SpaceTac.UI { // Add UI elements this.action_bar = new ActionBar(this); this.action_bar.position.set(0, this.getHeight() - 132); - this.ship_list = new ShipList(this); - this.ship_list.position.set(this.getWidth() - 112, 0); + this.ship_list = new ShipList(this, this.battle, this.player, this.toggle_tactical_mode, this, + this.layer_borders, this.getWidth() - 112, 0); this.ship_tooltip = new ShipTooltip(this); this.character_sheet = new CharacterSheet(this, -this.getWidth()); this.layer_sheets.add(this.character_sheet); @@ -179,7 +190,7 @@ module TK.SpaceTac.UI { numberPressed(num: number): void { if (this.interacting) { if (this.targetting.active) { - let ship = ifirst(this.battle.iships(true), ship => this.battle.getTurnsBefore(ship) == num % 10); + let ship = ifirst(this.battle.iships(true), ship => this.battle.getPlayOrder(ship) == num % 10); if (ship) { this.targetting.setTarget(Target.newFromShip(ship)); } diff --git a/src/ui/battle/LogProcessor.spec.ts b/src/ui/battle/LogProcessor.spec.ts index a41edd8..9e9ace2 100644 --- a/src/ui/battle/LogProcessor.spec.ts +++ b/src/ui/battle/LogProcessor.spec.ts @@ -12,7 +12,7 @@ module TK.SpaceTac.UI.Specs { } apply(battle: Battle) { - battle.turn += this.diff; + battle.cycle += this.diff; } getReverse(): BaseBattleEvent { @@ -31,42 +31,42 @@ module TK.SpaceTac.UI.Specs { event.apply(battle); return 0; }); - expect(battle.turn).toBe(1); + expect(battle.cycle).toBe(1); expect(processor.atStart()).toBe(true); expect(processor.atEnd()).toBe(true); processor.stepForward(); - expect(battle.turn).toBe(1); + expect(battle.cycle).toBe(1); expect(processor.atStart()).toBe(true); expect(processor.atEnd()).toBe(true); battle.log.add(new FakeEvent()); - expect(battle.turn).toBe(1); + expect(battle.cycle).toBe(1); expect(processor.atStart()).toBe(true); expect(processor.atEnd()).toBe(false); processor.stepForward(); - expect(battle.turn).toBe(2); + expect(battle.cycle).toBe(2); expect(processor.atStart()).toBe(false); expect(processor.atEnd()).toBe(true); processor.stepForward(); - expect(battle.turn).toBe(2); + expect(battle.cycle).toBe(2); expect(processor.atStart()).toBe(false); expect(processor.atEnd()).toBe(true); processor.stepBackward(); - expect(battle.turn).toBe(1); + expect(battle.cycle).toBe(1); expect(processor.atStart()).toBe(true); expect(processor.atEnd()).toBe(false); processor.stepBackward(); - expect(battle.turn).toBe(1); + expect(battle.cycle).toBe(1); expect(processor.atStart()).toBe(true); expect(processor.atEnd()).toBe(false); processor.stepForward(); - expect(battle.turn).toBe(2); + expect(battle.cycle).toBe(2); expect(processor.atStart()).toBe(false); expect(processor.atEnd()).toBe(true); }) diff --git a/src/ui/battle/LogProcessor.ts b/src/ui/battle/LogProcessor.ts index a162ba5..435b145 100644 --- a/src/ui/battle/LogProcessor.ts +++ b/src/ui/battle/LogProcessor.ts @@ -288,7 +288,7 @@ module TK.SpaceTac.UI { private processShipChangeEvent(event: ShipChangeEvent): number { this.current_ship = event.new_ship; this.view.arena.setShipPlaying(event.new_ship); - this.view.ship_list.setPlaying(event.new_ship); + this.view.ship_list.refresh(!event.initial); if (event.ship !== event.new_ship) { this.view.audio.playOnce("battle-ship-change"); } @@ -306,13 +306,19 @@ module TK.SpaceTac.UI { // A ship died private processDeathEvent(event: DeathEvent): number { - if (this.view.ship_hovered === event.ship) { - this.view.setShipHovered(null); - } - this.view.arena.markAsDead(event.ship); - this.view.ship_list.markAsDead(event.ship); + let ship = this.battle.getShip(event.ship_id); - return event.initial ? 0 : 1000; + if (ship) { + if (this.view.ship_hovered === ship) { + this.view.setShipHovered(null); + } + this.view.arena.markAsDead(ship); + this.view.ship_list.refresh(!event.initial); + + return event.initial ? 0 : 3000; + } else { + return 0; + } } // Weapon used diff --git a/src/ui/battle/ShipList.spec.ts b/src/ui/battle/ShipList.spec.ts index 6c92415..909d961 100644 --- a/src/ui/battle/ShipList.spec.ts +++ b/src/ui/battle/ShipList.spec.ts @@ -1,26 +1,65 @@ /// module TK.SpaceTac.UI.Specs { - describe("ShipList", function () { - let testgame = setupBattleview(); + testing("ShipList", test => { + let testgame = setupEmptyView(); - it("handles play position of ships", function () { - let battleview = testgame.view; - var list = battleview.ship_list; + function createList(): ShipList { + let view = testgame.view; + let battle = new Battle(); + let tactical_mode = new Toggle(); + let ship_buttons = jasmine.createSpyObj("ship_buttons", ["cursorOnShip", "cursorOffShip", "cursorClicked"]); + let list = new ShipList(view, battle, battle.fleets[0].player, tactical_mode, ship_buttons); + return list; + } - expect(battleview.battle.play_order.length).toBe(10); - expect(list.children.length).toBe(11); + test.case("handles play position of ships", check => { + let list = createList(); + let battle = list.battle; + check.equals(list.items.length, 0, "no item at first"); - expect(list.findPlayPosition(battleview.battle.play_order[0])).toBe(0); - expect(list.findPlayPosition(battleview.battle.play_order[1])).toBe(1); - expect(list.findPlayPosition(battleview.battle.play_order[2])).toBe(2); + battle.fleets[0].addShip(); + list.setShipsFromBattle(battle, false); + check.equals(list.items.length, 1, "one ship added but not in play order"); + check.equals(list.items[0].visible, false, "one ship added but not in play order"); - spyOn(battleview.battle, "playAI").and.stub(); - battleview.battle.advanceToNextShip(); + battle.throwInitiative(); + list.refresh(false); + check.equals(list.items[0].visible, true, "ship now in play order"); - expect(list.findPlayPosition(battleview.battle.play_order[0])).toBe(9); - expect(list.findPlayPosition(battleview.battle.play_order[1])).toBe(0); - expect(list.findPlayPosition(battleview.battle.play_order[2])).toBe(1); + battle.fleets[1].addShip(); + battle.throwInitiative(); + list.setShipsFromBattle(battle, false); + check.equals(list.items.length, 2, "ship added in the other fleet"); + check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(2, 843)); + check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 744)); + + battle.advanceToNextShip(); + list.refresh(false); + check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(-18, 962)); + check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 843)); + + battle.advanceToNextShip(); + list.refresh(false); + check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(2, 843)); + check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(-18, 962)); + + battle.fleets[1].addShip(); + battle.throwInitiative(); + battle.advanceToNextShip(); + list.setShipsFromBattle(battle, false); + check.equals(list.items.length, 3, "three ships"); + check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(-18, 962)); + check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 843)); + check.equals(nn(list.findItem(battle.play_order[2])).position, new Phaser.Point(2, 744)); + + let dead = battle.play_order[1]; + dead.setDead(); + list.refresh(false); + check.equals(list.items.length, 3, "dead ship"); + check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(-18, 962)); + check.equals(nn(list.findItem(dead)).position, new Phaser.Point(200, 843)); + check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 843)); }); }); } diff --git a/src/ui/battle/ShipList.ts b/src/ui/battle/ShipList.ts index 7dfce37..0c64848 100644 --- a/src/ui/battle/ShipList.ts +++ b/src/ui/battle/ShipList.ts @@ -1,126 +1,117 @@ module TK.SpaceTac.UI { - // Bar with all playing ships, by play order - export class ShipList extends Phaser.Image { - // Link to the parent battleview - battleview: BattleView; + /** + * Side bar with all playing ships, sorted by play order + */ + export class ShipList { + // Link to the parent view + view: BaseView + + // Current battle + battle: Battle + + // Current player + player: Player + + // Interface for acting as ship button + ship_buttons: IShipButton + + // Container + container: Phaser.Image // List of ship items - ships_container: Phaser.Group; - ships: ShipListItem[]; - - // Playing ship - playing: ShipListItem | null; + items: ShipListItem[] // Hovered ship - hovered: ShipListItem | null; + hovered: ShipListItem | null // Info button - info_button: Phaser.Button; + info_button: Phaser.Button - // Create an empty action bar - constructor(battleview: BattleView) { - super(battleview.game, 0, 0, "battle-shiplist-background"); + constructor(view: BaseView, battle: Battle, player: Player, tactical_mode: Toggle, ship_buttons: IShipButton, parent?: UIContainer, x = 0, y = 0) { + let builder = new UIBuilder(view, parent); + this.container = builder.image("battle-shiplist-background", x, y); - this.battleview = battleview; - this.ships = []; - this.playing = null; + this.view = view; + // TODO Should use an UI game state, not the actual game state + this.battle = battle; + this.player = player; + this.ship_buttons = ship_buttons; + + this.items = []; this.hovered = null; - this.info_button = new Phaser.Button(this.game, 0, 0, "battle-shiplist-info-button"); - this.battleview.inputs.setHoverClick(this.info_button, - () => this.battleview.toggle_tactical_mode.manipulate("button")(true), - () => this.battleview.toggle_tactical_mode.manipulate("button")(false), + this.info_button = new Phaser.Button(view.game, 0, 0, "battle-shiplist-info-button"); + this.view.inputs.setHoverClick(this.info_button, + () => tactical_mode.manipulate("shiplist")(true), + () => tactical_mode.manipulate("shiplist")(false), () => null); - this.addChild(this.info_button); + this.container.addChild(this.info_button); - battleview.layer_borders.add(this); - - if (battleview.battle) { - this.setShipsFromBattle(battleview.battle); - } + this.setShipsFromBattle(battle); } - // Clear the action icons + /** + * Clear all ship cards + */ clearAll(): void { - this.ships.forEach((ship: ShipListItem) => { - ship.destroy(); - }); - this.ships = []; + this.items.forEach(ship => ship.destroy()); + this.items = []; } - // Set the ship list from a battle - setShipsFromBattle(battle: Battle): void { + /** + * Rebuild the ship list from an ongoing battle + */ + setShipsFromBattle(battle: Battle, animate = true): void { this.clearAll(); - battle.play_order.forEach((ship: Ship) => { - this.addShip(ship); - }, this); - this.updateItemsLocation(); + iforeach(battle.iships(true), ship => this.addShip(ship)); + this.refresh(animate); } - // Add a ship icon + /** + * Add a ship card + */ addShip(ship: Ship): ShipListItem { - var owned = ship.getPlayer() === this.battleview.player; - var result = new ShipListItem(this, 200, this.height / 2, ship, owned); - this.ships.push(result); - this.addChild(result); + var owned = ship.getPlayer() === this.player; + var result = new ShipListItem(this, 200, this.container.height / 2, ship, owned, this.ship_buttons); + this.items.push(result); + this.container.addChild(result); return result; } - // Find an item for a ship - // Returns null if not found + /** + * Find the item (card) that displays a given ship + */ findItem(ship: Ship): ShipListItem | null { - var found: ShipListItem | null = null; - this.ships.forEach((item: ShipListItem) => { - if (item.ship === ship) { - found = item; - } - }); - return found; + return first(this.items, item => item.ship == ship); } - // Find the play position in play_order for a given ship (0 is currently playing) - findPlayPosition(ship: Ship): number { - var battle = this.battleview.battle; - var idx = battle.play_order.indexOf(ship); - var diff = idx - (battle.playing_ship_index || 0); - if (diff < 0) { - diff += battle.play_order.length; - } - return diff; - } - - // Update the locations of all items - updateItemsLocation(animate: boolean = true): void { - this.ships.forEach((item: ShipListItem) => { - var position = this.findPlayPosition(item.ship); - if (position === 0) { - item.moveTo(-18, 962, animate); + /** + * Update the locations of all items + */ + refresh(animate = true): void { + this.items.forEach(item => { + if (item.ship.alive) { + let position = this.battle.getPlayOrder(item.ship); + if (position < 0) { + item.visible = false; + } else { + if (position == 0) { + item.moveTo(-18, 962, animate ? 1000 : 0); + } else { + item.moveTo(2, 942 - position * 99, animate ? 1000 : 0); + } + item.visible = true; + this.container.setChildIndex(item, position); + } } else { - item.moveTo(2, 942 - position * 99, animate); + item.moveTo(200, item.y, animate ? 1000 : 0); } - this.setChildIndex(item, position); }); } - // Remove a ship from the list - markAsDead(ship: Ship): void { - var item = this.findItem(ship); - if (item) { - item.alpha = 0.5; - } - } - - // Set the currently playing ship - setPlaying(ship: Ship | null): void { - if (ship) { - this.playing = this.findItem(ship); - } else { - this.playing = null; - } - this.updateItemsLocation(); - } - - // Set the currently hovered ship + /** + * Set the currently hovered ship + */ setHovered(ship: Ship | null): void { if (this.hovered) { this.hovered.setHovered(false); diff --git a/src/ui/battle/ShipListItem.ts b/src/ui/battle/ShipListItem.ts index 86306c3..51383dc 100644 --- a/src/ui/battle/ShipListItem.ts +++ b/src/ui/battle/ShipListItem.ts @@ -2,11 +2,14 @@ module TK.SpaceTac.UI { // One item in a ship list (used in BattleView) export class ShipListItem extends Phaser.Button { // Reference to the view - view: BattleView + view: BaseView // Reference to the ship game object ship: Ship + // Callbacks to act as buttons + ship_buttons: IShipButton + // Player indicator player_indicator: Phaser.Image @@ -20,9 +23,9 @@ module TK.SpaceTac.UI { hover_indicator: Phaser.Image // Create a ship button for the battle ship list - constructor(list: ShipList, x: number, y: number, ship: Ship, owned: boolean) { - super(list.battleview.game, x, y, "battle-shiplist-item-background"); - this.view = list.battleview; + constructor(list: ShipList, x: number, y: number, ship: Ship, owned: boolean, ship_buttons: IShipButton) { + super(list.view.game, x, y, "battle-shiplist-item-background"); + this.view = list.view; this.ship = ship; @@ -44,9 +47,9 @@ module TK.SpaceTac.UI { this.addChild(this.hover_indicator); this.view.inputs.setHoverClick(this, - () => list.battleview.cursorOnShip(ship), - () => list.battleview.cursorOffShip(ship), - () => list.battleview.cursorClicked() + () => ship_buttons.cursorOnShip(ship), + () => ship_buttons.cursorOffShip(ship), + () => ship_buttons.cursorClicked() ); } @@ -56,11 +59,9 @@ module TK.SpaceTac.UI { } // Move to a given location on screen - moveTo(x: number, y: number, animate: boolean) { - if (animate) { - var tween = this.game.tweens.create(this); - tween.to({ x: x, y: y }); - tween.start(); + moveTo(x: number, y: number, duration: number) { + if (duration && (this.x != x || this.y != y)) { + this.view.animations.addAnimation(this, {x: x, y: y}, duration, Phaser.Easing.Linear.None); } else { this.x = x; this.y = y; diff --git a/src/ui/battle/ShipTooltip.ts b/src/ui/battle/ShipTooltip.ts index b321c1b..504c82c 100644 --- a/src/ui/battle/ShipTooltip.ts +++ b/src/ui/battle/ShipTooltip.ts @@ -31,7 +31,7 @@ module TK.SpaceTac.UI { filler.text(ship.getFullName(), 140, 0, { color: enemy ? "#cc0d00" : "#ffffff", size: 22, bold: true }); if (ship.alive) { - let turns = this.battleview.battle.getTurnsBefore(ship); + let turns = this.battleview.battle.getPlayOrder(ship); filler.text((turns == 0) ? "Playing" : ((turns == 1) ? "Plays next" : `Plays in ${turns} turns`), 140, 36, { color: "#cccccc", size: 18 }); let hsp_builder = filler.styled({ color: "#eb4e4a", size: 20, center: true, vcenter: true, bold: true }); diff --git a/src/ui/character/FleetCreationView.spec.ts b/src/ui/character/FleetCreationView.spec.ts index c251f5f..295eb3f 100644 --- a/src/ui/character/FleetCreationView.spec.ts +++ b/src/ui/character/FleetCreationView.spec.ts @@ -1,10 +1,10 @@ /// module TK.SpaceTac.UI.Specs { - describe("FleetCreationView", function () { + testing("FleetCreationView", test => { let testgame = setupSingleView(() => [new FleetCreationView, []]); - it("has a basic equipment shop with infinite stock", function () { + test.case("has a basic equipment shop with infinite stock", function () { let shop = testgame.view.infinite_shop; let itemcount = shop.getStock().length; expect(unique(shop.getStock().map(equ => equ.code)).length).toEqual(itemcount); @@ -21,7 +21,7 @@ module TK.SpaceTac.UI.Specs { expect(shop.getStock().length).toBe(itemcount); }) - async_it("validates the fleet creation", async function () { + test.acase("validates the fleet creation", async function () { expect(testgame.ui.session.isFleetCreated()).toBe(false, "no fleet created"); expect(testgame.ui.session.player.fleet.ships.length).toBe(0, "empty session fleet"); expect(testgame.view.dialogs_layer.children.length).toBe(0, "no dialogs");