1
0
Fork 0

Removed dead ships from play order

This commit is contained in:
Michaël Lemaire 2017-10-26 00:45:53 +02:00
parent 939313889c
commit 4b2d46754f
38 changed files with 521 additions and 282 deletions

View file

@ -41,7 +41,6 @@ Battle
* Fix arena's ship hovering happening even when the character sheet is open on top * Fix arena's ship hovering happening even when the character sheet is open on top
* Add a voluntary retreat option * Add a voluntary retreat option
* Add scroll buttons when there are too many actions * 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 * Add quick animation of playing ship indicator, on ship change
* Toggle bar/text display in power section of action bar * Toggle bar/text display in power section of action bar
* Display effects description instead of attribute changes * 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 * 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) * Fix delay of shield/hull impact effects (should depend on weapon animation, and ship location)
* Indicate visually the power gain of "end turn" * Indicate visually the power gain of "end turn"
* Add a turn count marker in the ship list
Ships models and equipments Ships models and equipments
--------------------------- ---------------------------

@ -1 +1 @@
Subproject commit a530998523e8f8c7a37323d5dc047241f71f6a36 Subproject commit 586788a4685a5b9a5cc8f4882f48f18c91997dc3

View file

@ -63,54 +63,44 @@ module TK.SpaceTac {
var fleet1 = new Fleet(); var fleet1 = new Fleet();
var fleet2 = new Fleet(); var fleet2 = new Fleet();
var ship1 = new Ship(fleet1, "F1S1"); var ship1 = new Ship(fleet1, "ship1");
var ship2 = new Ship(fleet1, "F1S2"); var ship2 = new Ship(fleet1, "ship2");
var ship3 = new Ship(fleet2, "F2S1"); var ship3 = new Ship(fleet2, "ship3");
var battle = new Battle(fleet1, fleet2); var battle = new Battle(fleet1, fleet2);
// Check empty play_order case // Check empty play_order case
expect(battle.playing_ship).toBeNull(); expect(battle.playing_ship).toBeNull();
expect(battle.playing_ship_index).toBeNull();
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBeNull(); expect(battle.playing_ship).toBeNull();
expect(battle.playing_ship_index).toBeNull();
// Force play order // Force play order
iforeach(battle.iships(), ship => ship.setAttribute("maneuvrability", 1)); iforeach(battle.iships(), ship => ship.setAttribute("maneuvrability", 1));
var gen = new SkewedRandomGenerator([0.1, 0.2, 0.0]); var gen = new SkewedRandomGenerator([0.1, 0.2, 0.0]);
battle.throwInitiative(gen); battle.throwInitiative(gen);
expect(battle.playing_ship).toBeNull(); expect(battle.playing_ship).toBeNull();
expect(battle.playing_ship_index).toBeNull();
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBe(ship2); expect(battle.playing_ship).toBe(ship2);
expect(battle.playing_ship_index).toBe(0);
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBe(ship1); expect(battle.playing_ship).toBe(ship1);
expect(battle.playing_ship_index).toBe(1);
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBe(ship3); expect(battle.playing_ship).toBe(ship3);
expect(battle.playing_ship_index).toBe(2);
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBe(ship2); 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(); ship1.setDead();
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBe(ship3);
expect(battle.playing_ship).toBe(ship1); // Playing ship dies
expect(battle.playing_ship_index).toBe(1); ship3.setDead();
battle.advanceToNextShip();
expect(battle.playing_ship).toBe(ship2);
}); });
it("calls startTurn on ships", function () { it("calls startTurn on ships", function () {
@ -290,7 +280,7 @@ module TK.SpaceTac {
expect(battle.canPlay(player)).toBe(false); expect(battle.canPlay(player)).toBe(false);
let ship = new Ship(); let ship = new Ship();
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
expect(battle.canPlay(player)).toBe(false); expect(battle.canPlay(player)).toBe(false);
@ -306,28 +296,28 @@ module TK.SpaceTac {
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBe(battle.play_order[0]); expect(battle.playing_ship).toBe(battle.play_order[0]);
expect(battle.getTurnsBefore(battle.play_order[0])).toBe(0); expect(battle.getPlayOrder(battle.play_order[0])).toBe(0);
expect(battle.getTurnsBefore(battle.play_order[1])).toBe(1); expect(battle.getPlayOrder(battle.play_order[1])).toBe(1);
expect(battle.getTurnsBefore(battle.play_order[2])).toBe(2); expect(battle.getPlayOrder(battle.play_order[2])).toBe(2);
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.playing_ship).toBe(battle.play_order[1]); expect(battle.playing_ship).toBe(battle.play_order[1]);
expect(battle.getTurnsBefore(battle.play_order[0])).toBe(2); expect(battle.getPlayOrder(battle.play_order[0])).toBe(2);
expect(battle.getTurnsBefore(battle.play_order[1])).toBe(0); expect(battle.getPlayOrder(battle.play_order[1])).toBe(0);
expect(battle.getTurnsBefore(battle.play_order[2])).toBe(1); expect(battle.getPlayOrder(battle.play_order[2])).toBe(1);
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.getTurnsBefore(battle.play_order[0])).toBe(1); expect(battle.getPlayOrder(battle.play_order[0])).toBe(1);
expect(battle.getTurnsBefore(battle.play_order[1])).toBe(2); expect(battle.getPlayOrder(battle.play_order[1])).toBe(2);
expect(battle.getTurnsBefore(battle.play_order[2])).toBe(0); expect(battle.getPlayOrder(battle.play_order[2])).toBe(0);
battle.advanceToNextShip(); battle.advanceToNextShip();
expect(battle.getTurnsBefore(battle.play_order[0])).toBe(0); expect(battle.getPlayOrder(battle.play_order[0])).toBe(0);
expect(battle.getTurnsBefore(battle.play_order[1])).toBe(1); expect(battle.getPlayOrder(battle.play_order[1])).toBe(1);
expect(battle.getTurnsBefore(battle.play_order[2])).toBe(2); expect(battle.getPlayOrder(battle.play_order[2])).toBe(2);
}); });
it("lists area effects", function () { it("lists area effects", function () {

View file

@ -19,15 +19,15 @@ module TK.SpaceTac {
// List of fleets engaged in battle // List of fleets engaged in battle
fleets: Fleet[] fleets: Fleet[]
// List of ships, sorted by their initiative throw // Container of all engaged ships
ships: RObjectContainer<Ship>
// List of playing ships, sorted by their initiative throw
play_order: Ship[] play_order: Ship[]
play_index = -1
// Current turn // Current battle "cycle" (one cycle is one turn done for all ships in the play order)
turn: number cycle: number
// Current ship whose turn it is to play
playing_ship_index: number | null
playing_ship: Ship | null
// List of deployed drones // List of deployed drones
drones: Drone[] = [] drones: Drone[] = []
@ -47,9 +47,8 @@ module TK.SpaceTac {
// Create a battle between two fleets // Create a battle between two fleets
constructor(fleet1 = new Fleet(), fleet2 = new Fleet(), width = 1808, height = 948) { constructor(fleet1 = new Fleet(), fleet2 = new Fleet(), width = 1808, height = 948) {
this.fleets = [fleet1, fleet2]; this.fleets = [fleet1, fleet2];
this.ships = new RObjectContainer(fleet1.ships.concat(fleet2.ships));
this.play_order = []; this.play_order = [];
this.playing_ship_index = null;
this.playing_ship = null;
this.ended = false; this.ended = false;
this.width = width; this.width = width;
this.height = height; 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 { get playing_ship(): Ship | null {
return this.play_order.length; 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 { getShip(id: RObjectId): Ship | null {
let pos = this.play_order.indexOf(ship) - (this.playing_ship_index || 0); return this.ships.get(id);
if (pos < 0) {
pos += this.play_order.length;
}
return pos;
} }
/** /**
@ -150,6 +145,42 @@ module TK.SpaceTac {
return (ship2.play_priority - ship1.play_priority); return (ship2.play_priority - ship1.play_priority);
}); });
this.play_order = play_order; 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 // Defines the initial ship positions of all engaged fleets
@ -195,7 +226,7 @@ module TK.SpaceTac {
} }
// Apply to all ships // 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]); 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 // If at the end of the play order, next turn will start automatically
// Member 'play_order' must be defined before calling this function // Member 'play_order' must be defined before calling this function
advanceToNextShip(log: boolean = true): void { 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) { if (previous_ship && previous_ship.playing) {
this.playing_ship.endTurn(); previous_ship.endTurn();
} }
if (this.checkEndBattle(log)) { if (this.checkEndBattle(log)) {
@ -240,21 +271,14 @@ module TK.SpaceTac {
this.drones.forEach(drone => drone.activate()); this.drones.forEach(drone => drone.activate());
if (this.play_order.length === 0) { this.play_index += 1;
this.playing_ship_index = null; if (this.play_index >= this.play_order.length) {
this.playing_ship = null; this.play_index = 0;
} 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];
} }
if (this.playing_ship) { if (this.playing_ship) {
if (this.playing_ship_index == 0) { if (this.play_index == 0) {
this.turn += 1; this.cycle += 1;
} }
this.playing_ship.startTurn(); this.playing_ship.startTurn();
} }
@ -288,7 +312,7 @@ module TK.SpaceTac {
// This will not add any event to the battle log // This will not add any event to the battle log
start(): void { start(): void {
this.ended = false; this.ended = false;
this.turn = 0; this.cycle = 0;
this.placeShips(); this.placeShips();
this.stats.onBattleStart(this.fleets[0], this.fleets[1]); this.stats.onBattleStart(this.fleets[0], this.fleets[1]);
this.throwInitiative(); this.throwInitiative();
@ -324,7 +348,7 @@ module TK.SpaceTac {
// Indicate emergency stasis // Indicate emergency stasis
this.play_order.forEach(ship => { this.play_order.forEach(ship => {
if (!ship.alive) { if (!ship.alive) {
let event = new DeathEvent(ship); let event = new DeathEvent(this, ship);
event.initial = true; event.initial = true;
result.push(event); result.push(event);
} }
@ -347,6 +371,18 @@ module TK.SpaceTac {
return result; 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 * Defines the initial ship positions for one fleet
* *

View file

@ -22,12 +22,13 @@ module TK.SpaceTac.Specs {
it("adds an equipment", function () { it("adds an equipment", function () {
let battle = new Battle(); let battle = new Battle();
battle.playing_ship = new Ship(); let ship = new Ship();
battle.playing_ship.upgradeSkill("skill_materials"); TestTools.setShipPlaying(battle, ship);
ship.upgradeSkill("skill_materials");
expect(battle.playing_ship.listEquipment()).toEqual([]); expect(ship.listEquipment()).toEqual([]);
battle.cheats.equip("Iron Hull"); battle.cheats.equip("Iron Hull");
expect(battle.playing_ship.listEquipment()).toEqual([<any>jasmine.objectContaining({name: "Iron Hull", level: 1})]); expect(ship.listEquipment()).toEqual([<any>jasmine.objectContaining({ name: "Iron Hull", level: 1 })]);
}) })
}) })
} }

View file

@ -83,7 +83,8 @@ module TK.SpaceTac {
}); });
it("checks if a fleet is alive", function () { 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); expect(fleet.isAlive()).toBe(false);
let ship1 = fleet.addShip(); let ship1 = fleet.addShip();

View file

@ -61,6 +61,9 @@ module TK.SpaceTac {
} }
add(this.ships, ship); add(this.ships, ship);
ship.fleet = this; ship.fleet = this;
if (this.battle) {
this.battle.ships.add(ship);
}
return ship; return ship;
} }

View file

@ -236,7 +236,7 @@ module TK.SpaceTac.Specs {
expect(action.activated).toBe(false); expect(action.activated).toBe(false);
let battle = new Battle(ship.fleet); let battle = new Battle(ship.fleet);
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
ship.startTurn(); ship.startTurn();
expect(action.activated).toBe(false); expect(action.activated).toBe(false);
@ -273,7 +273,7 @@ module TK.SpaceTac.Specs {
let shield = ship1.addSlot(SlotType.Shield).attach(new Equipment(SlotType.Shield)); let shield = ship1.addSlot(SlotType.Shield).attach(new Equipment(SlotType.Shield));
shield.action = new ToggleAction(shield, 0, 15, [new AttributeEffect("shield_capacity", 5)]); shield.action = new ToggleAction(shield, 0, 15, [new AttributeEffect("shield_capacity", 5)]);
battle.playing_ship = ship1; TestTools.setShipPlaying(battle, ship1);
shield.action.apply(ship1); shield.action.apply(ship1);
expect(ship1.getAttribute("shield_capacity")).toBe(5); expect(ship1.getAttribute("shield_capacity")).toBe(5);
@ -325,15 +325,15 @@ module TK.SpaceTac.Specs {
ship.addDamage(5, 0); ship.addDamage(5, 0);
expect(ship.alive).toBe(false); 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[0].code).toEqual("value");
expect(battle.log.events[1].code).toEqual("damage"); expect(battle.log.events[1].code).toEqual("damage");
expect(battle.log.events[2].code).toEqual("activeeffects"); expect(battle.log.events[2].code).toEqual("death");
expect(battle.log.events[3].code).toEqual("death");
}); });
it("checks if a ship is able to play", function () { 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()).toBe(false);
expect(ship.isAbleToPlay(false)).toBe(true); expect(ship.isAbleToPlay(false)).toBe(true);

View file

@ -1,8 +1,10 @@
/// <reference path="../common/RObject.ts" />
module TK.SpaceTac { module TK.SpaceTac {
/** /**
* A single ship in a fleet * A single ship in a fleet
*/ */
export class Ship { export class Ship extends RObject {
// Fleet this ship is a member of // Fleet this ship is a member of
fleet: Fleet fleet: Fleet
@ -56,6 +58,8 @@ module TK.SpaceTac {
// Create a new ship inside a fleet // Create a new ship inside a fleet
constructor(fleet: Fleet | null = null, name = "unnamed", model = new ShipModel("default", "Default", 1, 0, false, 0)) { constructor(fleet: Fleet | null = null, name = "unnamed", model = new ShipModel("default", "Default", 1, 0, false, 0)) {
super();
this.fleet = fleet || new Fleet(); this.fleet = fleet || new Fleet();
this.name = name; this.name = name;
this.alive = true; 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 * Set the death status on this ship
*/ */
setDead(log: boolean = true): void { setDead(): void {
this.alive = false; let battle = this.getBattle();
this.values.hull.set(0); if (battle) {
this.values.shield.set(0); let events = this.getDeathEvents(battle);
this.values.power.set(0); battle.applyEvents(events);
} else {
this.sticky_effects = []; console.error("Cannot set ship dead outside of battle", this);
this.setActiveEffectsChanged();
if (log) {
this.addBattleEvent(new DeathEvent(this));
} }
} }
@ -500,7 +533,7 @@ module TK.SpaceTac {
if (this.values.hull.get() === 0) { if (this.values.hull.get() === 0) {
// Ship is dead // Ship is dead
this.setDead(log); this.setDead();
} }
} }

View file

@ -53,6 +53,13 @@ module TK.SpaceTac {
return equipment; 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 // Set a ship action points, adding/updating an equipment if needed
static setShipAP(ship: Ship, points: number, recovery: number = 0): void { static setShipAP(ship: Ship, points: number, recovery: number = 0): void {
var equipment = this.getOrGenEquipment(ship, SlotType.Power, new Equipments.NuclearReactor()); var equipment = this.getOrGenEquipment(ship, SlotType.Power, new Equipments.NuclearReactor());

View file

@ -26,7 +26,7 @@ module TK.SpaceTac {
let battle = new Battle(); let battle = new Battle();
let ship = battle.fleets[0].addShip(); let ship = battle.fleets[0].addShip();
ship.setArenaPosition(0, 0); ship.setArenaPosition(0, 0);
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
TestTools.setShipAP(ship, 3); TestTools.setShipAP(ship, 3);
let equipment = new Equipment(SlotType.Weapon, "testdrone"); let equipment = new Equipment(SlotType.Weapon, "testdrone");
let action = new DeployDroneAction(equipment, 2, 8, 2, 4, [new DamageEffect(50)]); let action = new DeployDroneAction(equipment, 2, 8, 2, 4, [new DamageEffect(50)]);

View file

@ -20,11 +20,11 @@ module TK.SpaceTac.Specs {
let battle = Battle.newQuickRandom(); let battle = Battle.newQuickRandom();
let action = new EndTurnAction(); 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])); let result = action.apply(battle.play_order[0], Target.newFromShip(battle.play_order[0]));
expect(result).toBe(true); expect(result).toBe(true);
expect(battle.playing_ship_index).toBe(1); expect(battle.play_index).toBe(1);
}); });
}); });
} }

View file

@ -3,7 +3,7 @@ module TK.SpaceTac {
it("checks movement against remaining AP", function () { it("checks movement against remaining AP", function () {
var ship = new Ship(); var ship = new Ship();
var battle = new Battle(ship.fleet); var battle = new Battle(ship.fleet);
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
ship.values.power.setMaximal(20); ship.values.power.setMaximal(20);
ship.values.power.set(6); ship.values.power.set(6);
ship.arena_x = 0; ship.arena_x = 0;
@ -45,7 +45,7 @@ module TK.SpaceTac {
ship.arena_y = 0; ship.arena_y = 0;
var engine = new Equipment(); var engine = new Equipment();
var action = new MoveAction(engine, 1); var action = new MoveAction(engine, 1);
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
spyOn(console, "warn").and.stub(); spyOn(console, "warn").and.stub();

View file

@ -29,7 +29,7 @@ module TK.SpaceTac {
let battle = new Battle(fleet); let battle = new Battle(fleet);
battle.play_order = [ship, ship1, ship2, ship3]; battle.play_order = [ship, ship1, ship2, ship3];
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
fleet.setBattle(battle); fleet.setBattle(battle);
action.apply(ship, Target.newFromLocation(50, 50)); action.apply(ship, Target.newFromLocation(50, 50));

View file

@ -78,7 +78,7 @@ module TK.SpaceTac {
}); });
// Run battle // Run battle
while (!battle.ended && battle.turn < 100) { while (!battle.ended && battle.cycle < 100) {
if (this.stopped) { if (this.stopped) {
return; return;
} }

View file

@ -24,7 +24,7 @@ module TK.SpaceTac.Specs {
it("applies the highest evaluated maneuver", function () { it("applies the highest evaluated maneuver", function () {
let battle = new Battle(); let battle = new Battle();
let ship = battle.fleets[0].addShip(); let ship = battle.fleets[0].addShip();
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
ship.playing = true; ship.playing = true;
let ai = new TacticalAI(ship, Timer.synchronous); let ai = new TacticalAI(ship, Timer.synchronous);

View file

@ -8,7 +8,7 @@ module TK.SpaceTac.Specs {
let ship1b = battle.fleets[1].addShip(new Ship(null, "1B")); let ship1b = battle.fleets[1].addShip(new Ship(null, "1B"));
TestTools.setShipAP(ship0a, 10); TestTools.setShipAP(ship0a, 10);
battle.playing_ship = ship0a; TestTools.setShipPlaying(battle, ship0a);
let result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle)); let result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle));
expect(result.length).toBe(0); expect(result.length).toBe(0);
@ -30,7 +30,7 @@ module TK.SpaceTac.Specs {
let ship = battle.fleets[0].addShip(); let ship = battle.fleets[0].addShip();
TestTools.setShipAP(ship, 10); TestTools.setShipAP(ship, 10);
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
let result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1)); let result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1));
expect(result.length).toBe(0); expect(result.length).toBe(0);
@ -52,7 +52,7 @@ module TK.SpaceTac.Specs {
let weapon = TestTools.addWeapon(ship, 50, 1, 1000, 105); let weapon = TestTools.addWeapon(ship, 50, 1, 1000, 105);
TestTools.setShipAP(ship, 10); TestTools.setShipAP(ship, 10);
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
let result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle)); let result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle));
expect(result.length).toBe(0); expect(result.length).toBe(0);

View file

@ -1,7 +1,8 @@
module TK.SpaceTac.Specs { module TK.SpaceTac.Specs {
describe("DamageEffect", function () { describe("DamageEffect", function () {
it("applies damage and wear", 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); TestTools.setShipHP(ship, 150, 400);
let hull = ship.listEquipment(SlotType.Hull)[0]; let hull = ship.listEquipment(SlotType.Hull)[0];

View file

@ -40,8 +40,7 @@ module TK.SpaceTac.Equipments {
ship1.setArenaPosition(0, 0); ship1.setArenaPosition(0, 0);
ship2.setArenaPosition(100, 0); ship2.setArenaPosition(100, 0);
ship3.setArenaPosition(800, 0); ship3.setArenaPosition(800, 0);
battle.playing_ship = ship1; TestTools.setShipPlaying(battle, ship1);
ship1.playing = true;
expect(ship1.getAvailableActions()).toEqual([action, new EndTurnAction()]); expect(ship1.getAvailableActions()).toEqual([action, new EndTurnAction()]);
TestTools.setShipHP(ship1, 100, 0); TestTools.setShipHP(ship1, 100, 0);

View file

@ -38,8 +38,7 @@ module TK.SpaceTac.Equipments {
let battle = new Battle(); let battle = new Battle();
let ship = battle.fleets[0].addShip(); let ship = battle.fleets[0].addShip();
battle.playing_ship = ship; TestTools.setShipPlaying(battle, ship);
battle.play_order = [ship];
TestTools.setShipAP(ship, 10); TestTools.setShipAP(ship, 10);
let result = nn(equipment.action).apply(ship, new Target(5, 5, null)); let result = nn(equipment.action).apply(ship, new Target(5, 5, null));
expect(result).toBe(true); expect(result).toBe(true);

View file

@ -28,7 +28,16 @@ module TK.SpaceTac {
* *
* By default it does nothing * 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);
} }
/** /**

View file

@ -0,0 +1,39 @@
/// <reference path="../../common/Testing.ts" />
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");
});
});
}

View file

@ -1,10 +1,48 @@
/// <reference path="BaseBattleEvent.ts"/> /// <reference path="BaseBattleEvent.ts"/>
module TK.SpaceTac { 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 { 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); 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);
}
} }
} }
} }

View file

@ -1,9 +1,27 @@
/// <reference path="../../common/Testing.ts" />
module TK.SpaceTac.Specs { module TK.SpaceTac.Specs {
describe("ValueChangeEvent", function () { testing("ValueChangeEvent", test => {
it("get reverse event", function () { test.case("get reverse event", check => {
let ship = new Ship(); let ship = new Ship();
let event = new ValueChangeEvent(ship, new ShipValue("hull", 15, 22), 10); 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);
}); });
}); });
} }

View file

@ -5,15 +5,19 @@ module TK.SpaceTac {
* Event logged when a ship value or attribute changed * Event logged when a ship value or attribute changed
*/ */
export class ValueChangeEvent extends BaseLogShipEvent { export class ValueChangeEvent extends BaseLogShipEvent {
// Ship ID
ship_id: RObjectId
// Saved version of the current value // Saved version of the current value
value: ShipValue; value: ShipValue
// Value variation // Value variation
diff: number; diff: number
constructor(ship: Ship, value: ShipValue, diff: number) { constructor(ship: Ship, value: ShipValue, diff: number) {
super("value", ship); super("value", ship);
this.ship_id = ship.id;
this.value = copy(value); this.value = copy(value);
this.diff = diff; this.diff = diff;
} }
@ -23,5 +27,14 @@ module TK.SpaceTac {
value.set(value.get() - this.diff); value.set(value.get() - this.diff);
return new ValueChangeEvent(this.ship, value, -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(<any>this.value.name, this.value.get(), false, false);
} else {
console.warn("Ship not found", this);
}
}
} }
} }

View file

@ -1,6 +1,6 @@
module TK.SpaceTac.Multi.Specs { module TK.SpaceTac.Multi.Specs {
describe("Connection", function () { testing("Connection", test => {
async_it("finds an unused token", async function () { test.acase("finds an unused token", async check => {
let storage = new FakeRemoteStorage(); let storage = new FakeRemoteStorage();
let connection = new Connection("test", storage); let connection = new Connection("test", storage);
@ -15,7 +15,7 @@ module TK.SpaceTac.Multi.Specs {
expect(other).toEqual("123456"); 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 session = new GameSession();
let serializer = new Serializer(TK.SpaceTac); let serializer = new Serializer(TK.SpaceTac);
let storage = new FakeRemoteStorage(); let storage = new FakeRemoteStorage();
@ -42,7 +42,7 @@ module TK.SpaceTac.Multi.Specs {
expect(result).toBeNull(); 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 storage = new FakeRemoteStorage();
let connection = new Connection("test", storage); let connection = new Connection("test", storage);
@ -57,7 +57,7 @@ module TK.SpaceTac.Multi.Specs {
expect(result).toEqual({ abc: "ABC", cba: "CBA" }); 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 session = new GameSession();
let storage = new FakeRemoteStorage(); let storage = new FakeRemoteStorage();
let connection = new Connection("test", storage); let connection = new Connection("test", storage);

View file

@ -1,5 +1,5 @@
module TK.SpaceTac.Multi.Specs { module TK.SpaceTac.Multi.Specs {
describe("Exchange", function () { testing("Exchange", test => {
function newExchange(token: string, storage = new FakeRemoteStorage()): [FakeRemoteStorage, Exchange, Exchange] { function newExchange(token: string, storage = new FakeRemoteStorage()): [FakeRemoteStorage, Exchange, Exchange] {
let connection = new Connection("test", storage); let connection = new Connection("test", storage);
@ -17,7 +17,7 @@ module TK.SpaceTac.Multi.Specs {
spyOn(console, "log").and.stub(); 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"); let [storage, peer1, peer2] = newExchange("abc");
spyOn(peer1, "getNextId").and.returnValues("1A", "1B", "1C"); spyOn(peer1, "getNextId").and.returnValues("1A", "1B", "1C");
spyOn(peer2, "getNextId").and.returnValues("2A", "2B", "2C"); spyOn(peer2, "getNextId").and.returnValues("2A", "2B", "2C");

View file

@ -1,6 +1,6 @@
module TK.SpaceTac.Multi.Specs { module TK.SpaceTac.Multi.Specs {
describe("FakeRemoteStorage", function () { testing("FakeRemoteStorage", test => {
async_it("can fetch a single record", async function () { test.acase("can fetch a single record", async function () {
let storage = new FakeRemoteStorage(); let storage = new FakeRemoteStorage();
let result = await storage.find("test", { key: 5 }); let result = await storage.find("test", { key: 5 });
@ -21,7 +21,7 @@ module TK.SpaceTac.Multi.Specs {
expect(result).toBeNull(); expect(result).toBeNull();
}); });
async_it("inserts or updates objects", async function () { test.acase("inserts or updates objects", async function () {
let storage = new FakeRemoteStorage(); let storage = new FakeRemoteStorage();
let result = await storage.search("test", { key: 5 }); let result = await storage.search("test", { key: 5 });

View file

@ -241,17 +241,21 @@ module TK.SpaceTac.UI {
*/ */
getImageInfo(name: string): { key: string, frame: number } { getImageInfo(name: string): { key: string, frame: number } {
// TODO Cache // TODO Cache
let i = 1; if (this.game.cache.checkImageKey(name)) {
while (this.game.cache.checkImageKey(`atlas-${i}`)) { return { key: name, frame: 0 };
let data = this.game.cache.getFrameData(`atlas-${i}`); } else {
let frames = data.getFrames(); let i = 1;
let frame = first(frames, frame => AssetLoading.getKey(frame.name) == `graphics-exported-${name}`); while (this.game.cache.checkImageKey(`atlas-${i}`)) {
if (frame) { let data = this.game.cache.getFrameData(`atlas-${i}`);
return { key: `atlas-${i}`, frame: frame.index }; 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 };
} }
/** /**

View file

@ -121,7 +121,7 @@ module TK.SpaceTac.UI {
this.updateEffectsRadius(); this.updateEffectsRadius();
// Set location // 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.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); this.moveTo(ship.arena_x, ship.arena_y, ship.arena_angle);
} else { } else {
@ -145,7 +145,7 @@ module TK.SpaceTac.UI {
if (event.new_ship === this.ship) { if (event.new_ship === this.ship) {
this.play_order.text = "-"; this.play_order.text = "-";
} else { } 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; return 0;

View file

@ -1,8 +1,19 @@
/// <reference path="../BaseView.ts"/> /// <reference path="../BaseView.ts"/>
module TK.SpaceTac.UI { 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 // Displayed battle
battle: Battle battle: Battle
@ -102,8 +113,8 @@ module TK.SpaceTac.UI {
// Add UI elements // Add UI elements
this.action_bar = new ActionBar(this); this.action_bar = new ActionBar(this);
this.action_bar.position.set(0, this.getHeight() - 132); this.action_bar.position.set(0, this.getHeight() - 132);
this.ship_list = new ShipList(this); this.ship_list = new ShipList(this, this.battle, this.player, this.toggle_tactical_mode, this,
this.ship_list.position.set(this.getWidth() - 112, 0); this.layer_borders, this.getWidth() - 112, 0);
this.ship_tooltip = new ShipTooltip(this); this.ship_tooltip = new ShipTooltip(this);
this.character_sheet = new CharacterSheet(this, -this.getWidth()); this.character_sheet = new CharacterSheet(this, -this.getWidth());
this.layer_sheets.add(this.character_sheet); this.layer_sheets.add(this.character_sheet);
@ -179,7 +190,7 @@ module TK.SpaceTac.UI {
numberPressed(num: number): void { numberPressed(num: number): void {
if (this.interacting) { if (this.interacting) {
if (this.targetting.active) { 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) { if (ship) {
this.targetting.setTarget(Target.newFromShip(ship)); this.targetting.setTarget(Target.newFromShip(ship));
} }

View file

@ -12,7 +12,7 @@ module TK.SpaceTac.UI.Specs {
} }
apply(battle: Battle) { apply(battle: Battle) {
battle.turn += this.diff; battle.cycle += this.diff;
} }
getReverse(): BaseBattleEvent { getReverse(): BaseBattleEvent {
@ -31,42 +31,42 @@ module TK.SpaceTac.UI.Specs {
event.apply(battle); event.apply(battle);
return 0; return 0;
}); });
expect(battle.turn).toBe(1); expect(battle.cycle).toBe(1);
expect(processor.atStart()).toBe(true); expect(processor.atStart()).toBe(true);
expect(processor.atEnd()).toBe(true); expect(processor.atEnd()).toBe(true);
processor.stepForward(); processor.stepForward();
expect(battle.turn).toBe(1); expect(battle.cycle).toBe(1);
expect(processor.atStart()).toBe(true); expect(processor.atStart()).toBe(true);
expect(processor.atEnd()).toBe(true); expect(processor.atEnd()).toBe(true);
battle.log.add(new FakeEvent()); battle.log.add(new FakeEvent());
expect(battle.turn).toBe(1); expect(battle.cycle).toBe(1);
expect(processor.atStart()).toBe(true); expect(processor.atStart()).toBe(true);
expect(processor.atEnd()).toBe(false); expect(processor.atEnd()).toBe(false);
processor.stepForward(); processor.stepForward();
expect(battle.turn).toBe(2); expect(battle.cycle).toBe(2);
expect(processor.atStart()).toBe(false); expect(processor.atStart()).toBe(false);
expect(processor.atEnd()).toBe(true); expect(processor.atEnd()).toBe(true);
processor.stepForward(); processor.stepForward();
expect(battle.turn).toBe(2); expect(battle.cycle).toBe(2);
expect(processor.atStart()).toBe(false); expect(processor.atStart()).toBe(false);
expect(processor.atEnd()).toBe(true); expect(processor.atEnd()).toBe(true);
processor.stepBackward(); processor.stepBackward();
expect(battle.turn).toBe(1); expect(battle.cycle).toBe(1);
expect(processor.atStart()).toBe(true); expect(processor.atStart()).toBe(true);
expect(processor.atEnd()).toBe(false); expect(processor.atEnd()).toBe(false);
processor.stepBackward(); processor.stepBackward();
expect(battle.turn).toBe(1); expect(battle.cycle).toBe(1);
expect(processor.atStart()).toBe(true); expect(processor.atStart()).toBe(true);
expect(processor.atEnd()).toBe(false); expect(processor.atEnd()).toBe(false);
processor.stepForward(); processor.stepForward();
expect(battle.turn).toBe(2); expect(battle.cycle).toBe(2);
expect(processor.atStart()).toBe(false); expect(processor.atStart()).toBe(false);
expect(processor.atEnd()).toBe(true); expect(processor.atEnd()).toBe(true);
}) })

View file

@ -288,7 +288,7 @@ module TK.SpaceTac.UI {
private processShipChangeEvent(event: ShipChangeEvent): number { private processShipChangeEvent(event: ShipChangeEvent): number {
this.current_ship = event.new_ship; this.current_ship = event.new_ship;
this.view.arena.setShipPlaying(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) { if (event.ship !== event.new_ship) {
this.view.audio.playOnce("battle-ship-change"); this.view.audio.playOnce("battle-ship-change");
} }
@ -306,13 +306,19 @@ module TK.SpaceTac.UI {
// A ship died // A ship died
private processDeathEvent(event: DeathEvent): number { private processDeathEvent(event: DeathEvent): number {
if (this.view.ship_hovered === event.ship) { let ship = this.battle.getShip(event.ship_id);
this.view.setShipHovered(null);
}
this.view.arena.markAsDead(event.ship);
this.view.ship_list.markAsDead(event.ship);
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 // Weapon used

View file

@ -1,26 +1,65 @@
/// <reference path="../TestGame.ts"/> /// <reference path="../TestGame.ts"/>
module TK.SpaceTac.UI.Specs { module TK.SpaceTac.UI.Specs {
describe("ShipList", function () { testing("ShipList", test => {
let testgame = setupBattleview(); let testgame = setupEmptyView();
it("handles play position of ships", function () { function createList(): ShipList {
let battleview = testgame.view; let view = testgame.view;
var list = battleview.ship_list; 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); test.case("handles play position of ships", check => {
expect(list.children.length).toBe(11); 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); battle.fleets[0].addShip();
expect(list.findPlayPosition(battleview.battle.play_order[1])).toBe(1); list.setShipsFromBattle(battle, false);
expect(list.findPlayPosition(battleview.battle.play_order[2])).toBe(2); 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(); battle.throwInitiative();
battleview.battle.advanceToNextShip(); 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); battle.fleets[1].addShip();
expect(list.findPlayPosition(battleview.battle.play_order[1])).toBe(0); battle.throwInitiative();
expect(list.findPlayPosition(battleview.battle.play_order[2])).toBe(1); 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));
}); });
}); });
} }

View file

@ -1,126 +1,117 @@
module TK.SpaceTac.UI { module TK.SpaceTac.UI {
// Bar with all playing ships, by play order /**
export class ShipList extends Phaser.Image { * Side bar with all playing ships, sorted by play order
// Link to the parent battleview */
battleview: BattleView; 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 // List of ship items
ships_container: Phaser.Group; items: ShipListItem[]
ships: ShipListItem[];
// Playing ship
playing: ShipListItem | null;
// Hovered ship // Hovered ship
hovered: ShipListItem | null; hovered: ShipListItem | null
// Info button // Info button
info_button: Phaser.Button; info_button: Phaser.Button
// Create an empty action bar constructor(view: BaseView, battle: Battle, player: Player, tactical_mode: Toggle, ship_buttons: IShipButton, parent?: UIContainer, x = 0, y = 0) {
constructor(battleview: BattleView) { let builder = new UIBuilder(view, parent);
super(battleview.game, 0, 0, "battle-shiplist-background"); this.container = builder.image("battle-shiplist-background", x, y);
this.battleview = battleview; this.view = view;
this.ships = []; // TODO Should use an UI game state, not the actual game state
this.playing = null; this.battle = battle;
this.player = player;
this.ship_buttons = ship_buttons;
this.items = [];
this.hovered = null; this.hovered = null;
this.info_button = new Phaser.Button(this.game, 0, 0, "battle-shiplist-info-button"); this.info_button = new Phaser.Button(view.game, 0, 0, "battle-shiplist-info-button");
this.battleview.inputs.setHoverClick(this.info_button, this.view.inputs.setHoverClick(this.info_button,
() => this.battleview.toggle_tactical_mode.manipulate("button")(true), () => tactical_mode.manipulate("shiplist")(true),
() => this.battleview.toggle_tactical_mode.manipulate("button")(false), () => tactical_mode.manipulate("shiplist")(false),
() => null); () => null);
this.addChild(this.info_button); this.container.addChild(this.info_button);
battleview.layer_borders.add(this); this.setShipsFromBattle(battle);
if (battleview.battle) {
this.setShipsFromBattle(battleview.battle);
}
} }
// Clear the action icons /**
* Clear all ship cards
*/
clearAll(): void { clearAll(): void {
this.ships.forEach((ship: ShipListItem) => { this.items.forEach(ship => ship.destroy());
ship.destroy(); this.items = [];
});
this.ships = [];
} }
// 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(); this.clearAll();
battle.play_order.forEach((ship: Ship) => { iforeach(battle.iships(true), ship => this.addShip(ship));
this.addShip(ship); this.refresh(animate);
}, this);
this.updateItemsLocation();
} }
// Add a ship icon /**
* Add a ship card
*/
addShip(ship: Ship): ShipListItem { addShip(ship: Ship): ShipListItem {
var owned = ship.getPlayer() === this.battleview.player; var owned = ship.getPlayer() === this.player;
var result = new ShipListItem(this, 200, this.height / 2, ship, owned); var result = new ShipListItem(this, 200, this.container.height / 2, ship, owned, this.ship_buttons);
this.ships.push(result); this.items.push(result);
this.addChild(result); this.container.addChild(result);
return 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 { findItem(ship: Ship): ShipListItem | null {
var found: ShipListItem | null = null; return first(this.items, item => item.ship == ship);
this.ships.forEach((item: ShipListItem) => {
if (item.ship === ship) {
found = item;
}
});
return found;
} }
// Find the play position in play_order for a given ship (0 is currently playing) /**
findPlayPosition(ship: Ship): number { * Update the locations of all items
var battle = this.battleview.battle; */
var idx = battle.play_order.indexOf(ship); refresh(animate = true): void {
var diff = idx - (battle.playing_ship_index || 0); this.items.forEach(item => {
if (diff < 0) { if (item.ship.alive) {
diff += battle.play_order.length; let position = this.battle.getPlayOrder(item.ship);
} if (position < 0) {
return diff; item.visible = false;
} } else {
if (position == 0) {
// Update the locations of all items item.moveTo(-18, 962, animate ? 1000 : 0);
updateItemsLocation(animate: boolean = true): void { } else {
this.ships.forEach((item: ShipListItem) => { item.moveTo(2, 942 - position * 99, animate ? 1000 : 0);
var position = this.findPlayPosition(item.ship); }
if (position === 0) { item.visible = true;
item.moveTo(-18, 962, animate); this.container.setChildIndex(item, position);
}
} else { } 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 { * Set the currently hovered ship
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
setHovered(ship: Ship | null): void { setHovered(ship: Ship | null): void {
if (this.hovered) { if (this.hovered) {
this.hovered.setHovered(false); this.hovered.setHovered(false);

View file

@ -2,11 +2,14 @@ module TK.SpaceTac.UI {
// One item in a ship list (used in BattleView) // One item in a ship list (used in BattleView)
export class ShipListItem extends Phaser.Button { export class ShipListItem extends Phaser.Button {
// Reference to the view // Reference to the view
view: BattleView view: BaseView
// Reference to the ship game object // Reference to the ship game object
ship: Ship ship: Ship
// Callbacks to act as buttons
ship_buttons: IShipButton
// Player indicator // Player indicator
player_indicator: Phaser.Image player_indicator: Phaser.Image
@ -20,9 +23,9 @@ module TK.SpaceTac.UI {
hover_indicator: Phaser.Image hover_indicator: Phaser.Image
// Create a ship button for the battle ship list // Create a ship button for the battle ship list
constructor(list: ShipList, x: number, y: number, ship: Ship, owned: boolean) { constructor(list: ShipList, x: number, y: number, ship: Ship, owned: boolean, ship_buttons: IShipButton) {
super(list.battleview.game, x, y, "battle-shiplist-item-background"); super(list.view.game, x, y, "battle-shiplist-item-background");
this.view = list.battleview; this.view = list.view;
this.ship = ship; this.ship = ship;
@ -44,9 +47,9 @@ module TK.SpaceTac.UI {
this.addChild(this.hover_indicator); this.addChild(this.hover_indicator);
this.view.inputs.setHoverClick(this, this.view.inputs.setHoverClick(this,
() => list.battleview.cursorOnShip(ship), () => ship_buttons.cursorOnShip(ship),
() => list.battleview.cursorOffShip(ship), () => ship_buttons.cursorOffShip(ship),
() => list.battleview.cursorClicked() () => ship_buttons.cursorClicked()
); );
} }
@ -56,11 +59,9 @@ module TK.SpaceTac.UI {
} }
// Move to a given location on screen // Move to a given location on screen
moveTo(x: number, y: number, animate: boolean) { moveTo(x: number, y: number, duration: number) {
if (animate) { if (duration && (this.x != x || this.y != y)) {
var tween = this.game.tweens.create(this); this.view.animations.addAnimation(this, {x: x, y: y}, duration, Phaser.Easing.Linear.None);
tween.to({ x: x, y: y });
tween.start();
} else { } else {
this.x = x; this.x = x;
this.y = y; this.y = y;

View file

@ -31,7 +31,7 @@ module TK.SpaceTac.UI {
filler.text(ship.getFullName(), 140, 0, { color: enemy ? "#cc0d00" : "#ffffff", size: 22, bold: true }); filler.text(ship.getFullName(), 140, 0, { color: enemy ? "#cc0d00" : "#ffffff", size: 22, bold: true });
if (ship.alive) { 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 }); 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 }); let hsp_builder = filler.styled({ color: "#eb4e4a", size: 20, center: true, vcenter: true, bold: true });

View file

@ -1,10 +1,10 @@
/// <reference path="../TestGame.ts"/> /// <reference path="../TestGame.ts"/>
module TK.SpaceTac.UI.Specs { module TK.SpaceTac.UI.Specs {
describe("FleetCreationView", function () { testing("FleetCreationView", test => {
let testgame = setupSingleView(() => [new FleetCreationView, []]); 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 shop = testgame.view.infinite_shop;
let itemcount = shop.getStock().length; let itemcount = shop.getStock().length;
expect(unique(shop.getStock().map(equ => equ.code)).length).toEqual(itemcount); 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); 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.isFleetCreated()).toBe(false, "no fleet created");
expect(testgame.ui.session.player.fleet.ships.length).toBe(0, "empty session fleet"); expect(testgame.ui.session.player.fleet.ships.length).toBe(0, "empty session fleet");
expect(testgame.view.dialogs_layer.children.length).toBe(0, "no dialogs"); expect(testgame.view.dialogs_layer.children.length).toBe(0, "no dialogs");