diff --git a/src/index.html b/src/index.html
index 06557f8..14dd02e 100644
--- a/src/index.html
+++ b/src/index.html
@@ -36,7 +36,7 @@
var queryString = new jasmine.QueryString({
getWindowLocation: function() { return window.location; }
});
- if (queryString.getParam('onlytests')) {
+ if (queryString.getParam('onlytests') || queryString.getParam('spec')) {
document.getElementById("-space-tac").hidden = true;
} else {
new SpaceTac.GameRouter();
diff --git a/src/scripts/game/actions/BaseAction.ts b/src/scripts/game/actions/BaseAction.ts
index ad8305e..dbd2352 100644
--- a/src/scripts/game/actions/BaseAction.ts
+++ b/src/scripts/game/actions/BaseAction.ts
@@ -21,8 +21,18 @@ module SpaceTac.Game {
// Check basic conditions to know if the ship can use this action at all
// Method to reimplement to set conditions
- canBeUsed(battle: Battle, ship: Ship): boolean {
- return true;
+ canBeUsed(battle: Battle, ship: Ship, remaining_ap: number = null): boolean {
+ if (battle && battle.playing_ship !== ship) {
+ // Ship is not playing
+ return false;
+ }
+
+ // Check AP usage
+ if (remaining_ap === null) {
+ remaining_ap = ship.ap_current.current;
+ }
+ var ap_usage = this.equipment ? this.equipment.ap_usage : 0;
+ return remaining_ap >= ap_usage;
}
// Method to check if a target is applicable for this action
diff --git a/src/scripts/game/actions/EndTurnAction.ts b/src/scripts/game/actions/EndTurnAction.ts
index 7eb8352..8407b7d 100644
--- a/src/scripts/game/actions/EndTurnAction.ts
+++ b/src/scripts/game/actions/EndTurnAction.ts
@@ -7,10 +7,6 @@ module SpaceTac.Game {
super("endturn", false);
}
- canBeUsed(battle: Battle, ship: Ship): boolean {
- return battle.playing_ship === ship;
- }
-
protected customApply(battle: Battle, ship: Ship, target: Target): boolean {
battle.advanceToNextShip();
return true;
diff --git a/src/scripts/game/actions/FireWeaponAction.ts b/src/scripts/game/actions/FireWeaponAction.ts
index d73b239..96048e4 100644
--- a/src/scripts/game/actions/FireWeaponAction.ts
+++ b/src/scripts/game/actions/FireWeaponAction.ts
@@ -14,10 +14,6 @@ module SpaceTac.Game {
this.can_target_space = can_target_space;
}
- canBeUsed(battle: Battle, ship: Ship): boolean {
- return ship.ap_current.current >= this.equipment.ap_usage;
- }
-
checkLocationTarget(battle: Battle, ship: Ship, target: Target): Target {
if (this.can_target_space) {
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.equipment.distance);
diff --git a/src/scripts/game/actions/MoveAction.ts b/src/scripts/game/actions/MoveAction.ts
index 55fe1e0..8a6c294 100644
--- a/src/scripts/game/actions/MoveAction.ts
+++ b/src/scripts/game/actions/MoveAction.ts
@@ -7,8 +7,16 @@ module SpaceTac.Game {
super("move", true, equipment);
}
- canBeUsed(battle: Battle, ship: Ship): boolean {
- return ship.ap_current.current > 0;
+ canBeUsed(battle: Battle, ship: Ship, remaining_ap: number = null): boolean {
+ if (battle && battle.playing_ship !== ship) {
+ return false;
+ }
+
+ // Check AP usage
+ if (remaining_ap === null) {
+ remaining_ap = ship.ap_current.current;
+ }
+ return remaining_ap > 0.0001;
}
checkLocationTarget(battle: Battle, ship: Ship, target: Target): Target {
diff --git a/src/scripts/game/ai/specs/BullyAI.spec.ts b/src/scripts/game/ai/specs/BullyAI.spec.ts
index eb105a4..ce5a4ed 100644
--- a/src/scripts/game/ai/specs/BullyAI.spec.ts
+++ b/src/scripts/game/ai/specs/BullyAI.spec.ts
@@ -199,6 +199,7 @@ module SpaceTac.Game.AI.Specs {
var move = ai.checkBullyMove(ship2, weapon);
expect(move).not.toBeNull();
+ battle.playing_ship = ai.ship;
battle.log.clear();
ai.applyMove(move);
diff --git a/src/scripts/game/specs/BaseAction.spec.ts b/src/scripts/game/specs/BaseAction.spec.ts
new file mode 100644
index 0000000..eb4f509
--- /dev/null
+++ b/src/scripts/game/specs/BaseAction.spec.ts
@@ -0,0 +1,33 @@
+///
+
+module SpaceTac.Game {
+ "use strict";
+
+ describe("BaseAction", function () {
+ it("check if equipment can be used with remaining AP", function () {
+ var equipment = new Equipment(SlotType.Armor);
+ equipment.ap_usage = 3;
+ var action = new BaseAction("test", false, equipment);
+ var ship = new Ship();
+ ship.addSlot(SlotType.Armor).attach(equipment);
+ ship.ap_current.setMaximal(10);
+
+ expect(action.canBeUsed(null, ship)).toBe(false);
+
+ ship.ap_current.set(5);
+
+ expect(action.canBeUsed(null, ship)).toBe(true);
+ expect(action.canBeUsed(null, ship, 4)).toBe(true);
+ expect(action.canBeUsed(null, ship, 3)).toBe(true);
+ expect(action.canBeUsed(null, ship, 2)).toBe(false);
+
+ ship.ap_current.set(3);
+
+ expect(action.canBeUsed(null, ship)).toBe(true);
+
+ ship.ap_current.set(2);
+
+ expect(action.canBeUsed(null, ship)).toBe(false);
+ });
+ });
+}
diff --git a/src/scripts/game/specs/MoveAction.spec.ts b/src/scripts/game/specs/MoveAction.spec.ts
index a1158c5..e0b6f55 100644
--- a/src/scripts/game/specs/MoveAction.spec.ts
+++ b/src/scripts/game/specs/MoveAction.spec.ts
@@ -49,6 +49,7 @@ module SpaceTac.Game {
engine.distance = 1;
engine.ap_usage = 1;
var action = new MoveAction(engine);
+ battle.playing_ship = ship;
var result = action.apply(battle, ship, Target.newFromLocation(10, 10));
expect(result).toBe(true);
diff --git a/src/scripts/view/Preload.ts b/src/scripts/view/Preload.ts
index a9afc3b..5591ce0 100644
--- a/src/scripts/view/Preload.ts
+++ b/src/scripts/view/Preload.ts
@@ -22,6 +22,7 @@ module SpaceTac.View {
this.loadImage("battle/actionbar.png");
this.loadImage("battle/action-inactive.png");
this.loadImage("battle/action-active.png");
+ this.loadImage("battle/action-fading.png");
this.loadImage("battle/actionpointsempty.png");
this.loadImage("battle/actionpointsfull.png");
this.loadImage("battle/arena/shipspritehover.png");
diff --git a/src/scripts/view/battle/ActionBar.ts b/src/scripts/view/battle/ActionBar.ts
index 5773cab..4ad2728 100644
--- a/src/scripts/view/battle/ActionBar.ts
+++ b/src/scripts/view/battle/ActionBar.ts
@@ -60,6 +60,19 @@ module SpaceTac.View {
}
}
+ // Update fading flags
+ // ap_usage is the consumption of currently selected action
+ updateFadings(ap_usage: number): void {
+ var remaining_ap = this.ship.ap_current.current - ap_usage;
+ if (remaining_ap < 0) {
+ remaining_ap = 0;
+ }
+
+ this.actions.forEach((icon: ActionIcon) => {
+ icon.updateFadingStatus(remaining_ap);
+ });
+ }
+
// Set action icons from selected ship
setShip(ship: Game.Ship): void {
var action_bar = this;
diff --git a/src/scripts/view/battle/ActionIcon.ts b/src/scripts/view/battle/ActionIcon.ts
index e49a01e..49f7d33 100644
--- a/src/scripts/view/battle/ActionIcon.ts
+++ b/src/scripts/view/battle/ActionIcon.ts
@@ -15,6 +15,12 @@ module SpaceTac.View {
// Related game action
action: Game.BaseAction;
+ // True if the action can be used
+ active: boolean;
+
+ // True if an action is currently selected, and this one won't be available after its use
+ fading: boolean;
+
// Current targetting
private targetting: Targetting;
@@ -24,6 +30,9 @@ module SpaceTac.View {
// Layer applied when the action is active
private layer_active: Phaser.Image;
+ // Layer applied when the action will become unavailable if another action is played
+ private layer_fading: Phaser.Image;
+
// Create an icon for a single ship action
constructor(bar: ActionBar, x: number, y: number, ship: Game.Ship, action: Game.BaseAction) {
this.bar = bar;
@@ -35,13 +44,21 @@ module SpaceTac.View {
bar.addChild(this);
// Active layer
+ this.active = false;
this.layer_active = new Phaser.Image(this.game, 0, 0, "battle-action-active", 0);
+ this.layer_active.visible = false;
this.addChild(this.layer_active);
// Icon layer
this.layer_icon = new Phaser.Image(this.game, 15, 18, "battle-actions-" + action.code, 0);
this.addChild(this.layer_icon);
+ // Fading layer
+ this.fading = false;
+ this.layer_fading = new Phaser.Image(this.game, 0, 0, "battle-action-fading", 0);
+ this.layer_fading.visible = false;
+ this.addChild(this.layer_fading);
+
// Click process
this.onInputUp.add(() => {
this.processClick();
@@ -62,8 +79,14 @@ module SpaceTac.View {
// End any previously selected action
this.bar.actionEnded();
+ // Update fading statuses
+ this.bar.updateFadings(this.action.equipment.ap_usage);
+
// Set the lighting color to highlight
- this.layer_active.tint = 0xFFD060;
+ if (this.game.renderType !== Phaser.HEADLESS) {
+ // Tint doesn't work in headless renderer
+ this.layer_active.tint = 0xFFD060;
+ }
if (this.action.needs_target) {
// Switch to targetting mode (will apply action when a target is selected)
@@ -104,10 +127,16 @@ module SpaceTac.View {
// Update the active status, from the action canBeUsed result
updateActiveStatus(): void {
- var active = this.action.canBeUsed(this.battleview.battle, this.ship);
- Animation.setVisibility(this.game, this.layer_active, active, 500);
- this.game.tweens.create(this.layer_icon).to({alpha: active ? 1 : 0.3}, 500).start();
- this.input.useHandCursor = active;
+ this.active = this.action.canBeUsed(this.battleview.battle, this.ship);
+ Animation.setVisibility(this.game, this.layer_active, this.active, 500);
+ this.game.tweens.create(this.layer_icon).to({alpha: this.active ? 1 : 0.3}, 500).start();
+ this.input.useHandCursor = this.active;
+ }
+
+ // Update the fading status, given an hypothetical remaining AP
+ updateFadingStatus(remaining_ap: number): void {
+ this.fading = !this.action.canBeUsed(this.battleview.battle, this.ship, remaining_ap);
+ Animation.setVisibility(this.game, this.layer_fading, this.fading, 200);
}
}
}
diff --git a/src/scripts/view/specs/ActionBar.spec.ts b/src/scripts/view/specs/ActionBar.spec.ts
new file mode 100644
index 0000000..e5a4634
--- /dev/null
+++ b/src/scripts/view/specs/ActionBar.spec.ts
@@ -0,0 +1,87 @@
+///
+///
+
+module SpaceTac.View.Specs {
+ "use strict";
+
+ describe("ActionBar", () => {
+ inbattleview_it("lists available actions for selected ship", (battleview: BattleView) => {
+ var bar = battleview.action_bar;
+
+ // Ship not owned by current battleview player
+ var ship = new Game.Ship();
+ bar.setShip(ship);
+ expect(bar.actions.length).toBe(0);
+
+ // Ship with no equipment (only endturn action)
+ battleview.player = ship.getPlayer();
+ bar.setShip(ship);
+ expect(bar.actions.length).toBe(1);
+ expect(bar.actions[0].action.code).toEqual("endturn");
+
+ // Add an engine, with move action
+ ship.addSlot(Game.SlotType.Engine).attach((new Game.Equipments.ConventionalEngine()).generate());
+ bar.setShip(ship);
+ expect(bar.actions.length).toBe(2);
+ expect(bar.actions[0].action.code).toEqual("move");
+
+ // Add a weapon, with fire action
+ ship.addSlot(Game.SlotType.Weapon).attach((new Game.Equipments.GatlingGun()).generate());
+ bar.setShip(ship);
+ expect(bar.actions.length).toBe(3);
+ expect(bar.actions[1].action.code).toEqual("fire-gatlinggun");
+ });
+
+ inbattleview_it("mark actions that would become unavailable after use", (battleview: BattleView) => {
+ var bar = battleview.action_bar;
+ var ship = new Game.Ship();
+ var engine = (new Game.Equipments.ConventionalEngine()).generate();
+ engine.ap_usage = 8;
+ engine.distance = 4;
+ ship.addSlot(Game.SlotType.Engine).attach(engine);
+ var weapon1 = (new Game.Equipments.GatlingGun()).generate();
+ weapon1.ap_usage = 3;
+ ship.addSlot(Game.SlotType.Weapon).attach(weapon1);
+ var weapon2 = (new Game.Equipments.GatlingGun()).generate();
+ weapon2.ap_usage = 5;
+ ship.addSlot(Game.SlotType.Weapon).attach(weapon2);
+ battleview.battle.playing_ship = ship;
+ battleview.player = ship.getPlayer();
+
+ ship.ap_current.setMaximal(10);
+ ship.ap_current.set(9);
+ bar.setShip(ship);
+
+ expect(bar.actions.length).toBe(4);
+
+ var checkFading = (fading: number[], available: number[]) => {
+ fading.forEach((index: number) => {
+ var icon = bar.actions[index];
+ expect(icon.fading).toBe(true);
+ });
+ available.forEach((index: number) => {
+ var icon = bar.actions[index];
+ expect(icon.fading).toBe(false);
+ });
+ };
+
+ // Weapon 1 leaves all choices open
+ bar.actions[1].processClick();
+ checkFading([], [0, 1, 2, 3]);
+
+ // Weapon 2 can't be fired twice
+ bar.actions[2].processClick();
+ checkFading([2], [0, 1, 3]);
+
+ // Not enough AP for both weapons
+ ship.ap_current.set(7);
+ bar.actions[2].processClick();
+ checkFading([1, 2], [0, 3]);
+
+ // Not enough AP to move
+ ship.ap_current.set(3);
+ bar.actions[1].processClick();
+ checkFading([0, 1, 2], [3]);
+ });
+ });
+}
diff --git a/src/scripts/view/specs/BattleView.spec.ts b/src/scripts/view/specs/BattleView.spec.ts
index 50bfd6d..60bc682 100644
--- a/src/scripts/view/specs/BattleView.spec.ts
+++ b/src/scripts/view/specs/BattleView.spec.ts
@@ -5,15 +5,6 @@
module SpaceTac.View.Specs {
"use strict";
- export function inbattleview_it(desc: string, func: (battleview: BattleView) => void) {
- var battleview = new BattleView();
- var battle = Game.Battle.newQuickRandom();
- var player = battle.playing_ship.getPlayer();
- ingame_it(desc, (game: Phaser.Game, state: Phaser.State) => {
- func(battleview);
- }, battleview, player, battle);
- }
-
describe("BattleView", () => {
inbattleview_it("forwards events in targetting mode", (battleview: BattleView) => {
expect(battleview.targetting).toBeNull();
diff --git a/src/scripts/view/specs/TestGame.ts b/src/scripts/view/specs/TestGame.ts
index 9caa373..9a375a4 100644
--- a/src/scripts/view/specs/TestGame.ts
+++ b/src/scripts/view/specs/TestGame.ts
@@ -7,6 +7,9 @@ module SpaceTac.View.Specs {
export function ingame_it(desc: string, func: (game: Phaser.Game, state: Phaser.State) => void,
state: Phaser.State = null, ...stateargs: any[]) {
it(desc, (done: () => void) => {
+ spyOn(console, "log").and.stub();
+ spyOn(console, "warn").and.stub();
+
var game = new Phaser.Game(500, 500, Phaser.HEADLESS);
if (!state) {
@@ -31,4 +34,14 @@ module SpaceTac.View.Specs {
game.state.start.apply(game.state, args);
});
}
+
+ // Test game wrapper, with a battleview initialized on a random battle
+ export function inbattleview_it(desc: string, func: (battleview: BattleView) => void) {
+ var battleview = new BattleView();
+ var battle = Game.Battle.newQuickRandom();
+ var player = battle.playing_ship.getPlayer();
+ ingame_it(desc, (game: Phaser.Game, state: Phaser.State) => {
+ func(battleview);
+ }, battleview, player, battle);
+ }
}