diff --git a/TODO.md b/TODO.md index 0ef683e..71acad7 100644 --- a/TODO.md +++ b/TODO.md @@ -60,7 +60,6 @@ Battle * Apply to action's default targets * Add engine trail effect, and sound * Find incentives to move from starting position (permanent drones or anomalies?) -* Mark targetting in error when target is refused by the action (there is already an arrow for this) * Allow to undo last moves * Add a battle log display * Allow to move targetting indicator with arrow keys diff --git a/src/core/MoveFireSimulator.spec.ts b/src/core/MoveFireSimulator.spec.ts index 948b340..cd0fa75 100644 --- a/src/core/MoveFireSimulator.spec.ts +++ b/src/core/MoveFireSimulator.spec.ts @@ -31,11 +31,11 @@ module TK.SpaceTac.Specs { let [ship, simulator, action] = simpleWeaponCase(); let result = simulator.simulateAction(action, new Target(ship.arena_x + 5, ship.arena_y)); - check.same(result.success, true, 'success'); - check.same(result.need_move, false, 'need_move'); - check.same(result.need_fire, true, 'need_fire'); - check.same(result.can_fire, true, 'can_fire'); - check.same(result.total_fire_ap, 3, 'total_fire_ap'); + check.equals(result.status, MoveFireStatus.OK, 'status'); + check.equals(result.need_move, false, 'need_move'); + check.equals(result.need_fire, true, 'need_fire'); + check.equals(result.can_fire, true, 'can_fire'); + check.equals(result.total_fire_ap, 3, 'total_fire_ap'); check.equals(result.parts, [ { action: action, target: new Target(ship.arena_x + 5, ship.arena_y), ap: 3, possible: true } @@ -45,7 +45,7 @@ module TK.SpaceTac.Specs { test.case("can't fire when in range, but not enough AP", check => { let [ship, simulator, action] = simpleWeaponCase(10, 2, 3); let result = simulator.simulateAction(action, new Target(ship.arena_x + 5, ship.arena_y)); - check.equals(result.success, true, 'success'); + check.equals(result.status, MoveFireStatus.NOT_ENOUGH_POWER, 'status'); check.equals(result.need_move, false, 'need_move'); check.equals(result.need_fire, true, 'need_fire'); check.equals(result.can_fire, false, 'can_fire'); @@ -59,13 +59,13 @@ module TK.SpaceTac.Specs { test.case("can't fire at invalid target", check => { let [ship, simulator, action] = simpleWeaponCase(10, 3, 3); let result = simulator.simulateAction(action, new Target(ship.arena_x + 5, ship.arena_y)); - check.equals(result.complete, true, 'complete'); + check.equals(result.status, MoveFireStatus.OK, 'status'); check.equals(result.need_fire, true, 'need_fire'); check.equals(result.can_fire, true, 'can_fire'); check.in("with invalid target", check => { check.patch(action, "checkTarget", () => false); result = simulator.simulateAction(action, new Target(ship.arena_x + 5, ship.arena_y)); - check.equals(result.complete, false, 'complete'); + check.equals(result.status, MoveFireStatus.INVALID_TARGET, 'status'); check.equals(result.need_fire, true, 'need_fire'); check.equals(result.can_fire, false, 'can_fire'); }); @@ -74,7 +74,7 @@ module TK.SpaceTac.Specs { test.case("moves straight to get within range", check => { let [ship, simulator, action] = simpleWeaponCase(); let result = simulator.simulateAction(action, new Target(ship.arena_x + 15, ship.arena_y)); - check.equals(result.success, true, 'success'); + check.equals(result.status, MoveFireStatus.OK, 'status'); check.equals(result.need_move, true, 'need_move'); check.equals(result.can_move, true, 'can_move'); check.equals(result.can_end_move, true, 'can_end_move'); @@ -151,7 +151,7 @@ module TK.SpaceTac.Specs { test.case("moves to get in range, even if not enough AP to fire", check => { let [ship, simulator, action] = simpleWeaponCase(8, 3, 2, 5); let result = simulator.simulateAction(action, new Target(ship.arena_x + 18, ship.arena_y)); - check.same(result.success, true, 'success'); + check.same(result.status, MoveFireStatus.NOT_ENOUGH_POWER, 'status'); check.same(result.need_move, true, 'need_move'); check.same(result.can_end_move, true, 'can_end_move'); check.equals(result.move_location, new Target(ship.arena_x + 10, ship.arena_y)); @@ -171,7 +171,7 @@ module TK.SpaceTac.Specs { let [ship, simulator, action] = simpleWeaponCase(); let move_action = ship.actions.listAll().filter(action => action instanceof MoveAction)[0]; let result = simulator.simulateAction(move_action, new Target(ship.arena_x, ship.arena_y)); - check.equals(result.success, false); + check.equals(result.status, MoveFireStatus.NO_ACTION, 'status'); check.equals(result.need_move, false); check.equals(result.need_fire, false); check.equals(result.parts, []); @@ -180,10 +180,10 @@ module TK.SpaceTac.Specs { test.case("does not move if already in range, even if in the safety margin", check => { let [ship, simulator, action] = simpleWeaponCase(100); let result = simulator.simulateAction(action, new Target(ship.arena_x + 97, ship.arena_y), 5); - check.equals(result.success, true); + check.equals(result.status, MoveFireStatus.OK, 'status'); check.equals(result.need_move, false); result = simulator.simulateAction(action, new Target(ship.arena_x + 101, ship.arena_y), 5); - check.equals(result.success, true); + check.equals(result.status, MoveFireStatus.OK, 'status'); check.equals(result.need_move, true); check.equals(result.move_location, new Target(ship.arena_x + 6, ship.arena_y)); }); diff --git a/src/core/MoveFireSimulator.ts b/src/core/MoveFireSimulator.ts index df92088..470c673 100644 --- a/src/core/MoveFireSimulator.ts +++ b/src/core/MoveFireSimulator.ts @@ -7,6 +7,17 @@ module TK.SpaceTac { NO_VECTOR_FOUND, } + /** + * Status for simulation + */ + export enum MoveFireStatus { + OK, + NO_ACTION, + NOT_ENOUGH_POWER, + INVALID_TARGET, + IMPOSSIBLE_APPROACH, + } + /** * A single action in the sequence result from the simulator */ @@ -21,12 +32,10 @@ module TK.SpaceTac { * A simulation result */ export class MoveFireResult { - // Simulation success, false only if no route can be found - success = false + // Simulation status + status = MoveFireStatus.OK // Ideal successive parts to make the full move+fire parts: MoveFirePart[] = [] - // Simulation complete (both move and fire are possible) - complete = false need_move = false can_move = false @@ -135,7 +144,7 @@ module TK.SpaceTac { } else if (approach != ApproachSimulationError.NO_MOVE_NEEDED) { result.need_move = true; result.can_move = false; - result.success = false; + result.status = MoveFireStatus.IMPOSSIBLE_APPROACH; return result; } } @@ -145,6 +154,9 @@ module TK.SpaceTac { result.need_move = false; } else { result.can_move = bool(move_action.checkTarget(this.ship, move_target)); + if (!result.can_move) { + result.status = MoveFireStatus.INVALID_TARGET; + } } } @@ -157,22 +169,33 @@ module TK.SpaceTac { // TODO Split in "this turn" part and "next turn" part if needed result.parts.push({ action: move_action, target: move_target, ap: result.total_move_ap, possible: result.can_move }); + if (!result.can_end_move) { + result.status = MoveFireStatus.NOT_ENOUGH_POWER; + } + ap -= result.total_move_ap; } // Check action AP - if (action instanceof MoveAction) { - result.success = result.need_move && result.can_move; - } else { + if (!(action instanceof MoveAction)) { result.need_fire = true; result.total_fire_ap = action.getPowerUsage(this.ship, target); - result.can_fire = (action.checkTarget(this.ship, target, result.move_location)) && (result.total_fire_ap <= ap); + if (action.checkTarget(this.ship, target, result.move_location)) { + if (result.total_fire_ap <= ap) { + result.can_fire = true; + } else { + result.status = MoveFireStatus.NOT_ENOUGH_POWER; + } + } else { + result.status = MoveFireStatus.INVALID_TARGET; + } result.fire_location = target; result.parts.push({ action: action, target: target, ap: result.total_fire_ap, possible: (!result.need_move || result.can_end_move) && result.can_fire }); - result.success = true; } - result.complete = (!result.need_move || result.can_end_move) && (!result.need_fire || result.can_fire); + if (!result.need_move && !result.need_fire) { + result.status = MoveFireStatus.NO_ACTION; + } return result; } diff --git a/src/core/actions/BaseAction.ts b/src/core/actions/BaseAction.ts index 7c9b723..d562124 100644 --- a/src/core/actions/BaseAction.ts +++ b/src/core/actions/BaseAction.ts @@ -237,14 +237,10 @@ module TK.SpaceTac { * Will call checkLocationTarget or checkShipTarget by default */ checkTarget(ship: Ship, target: Target, from: IArenaLocation = ship.location): boolean { - if (this.checkCannotBeApplied(ship)) { - return false; + if (target.isShip()) { + return this.checkShipTarget(ship, target, from); } else { - if (target.isShip()) { - return this.checkShipTarget(ship, target, from); - } else { - return this.checkLocationTarget(ship, target, from); - } + return this.checkLocationTarget(ship, target, from); } } diff --git a/src/core/ai/Maneuver.ts b/src/core/ai/Maneuver.ts index 634f61a..b691fd6 100644 --- a/src/core/ai/Maneuver.ts +++ b/src/core/ai/Maneuver.ts @@ -91,7 +91,7 @@ module TK.SpaceTac { if (!this.ship.is(battle.playing_ship)) { console.error("Maneuver was not produced for the playing ship", this, battle); return false; - } else if (!this.simulation.success) { + } else if (this.simulation.status != MoveFireStatus.OK) { return false; } else { let parts = this.simulation.parts; diff --git a/src/core/ai/TacticalAI.spec.ts b/src/core/ai/TacticalAI.spec.ts index 817936f..93442e1 100644 --- a/src/core/ai/TacticalAI.spec.ts +++ b/src/core/ai/TacticalAI.spec.ts @@ -7,7 +7,8 @@ module TK.SpaceTac.Specs { constructor(score: number) { let battle = new Battle(); let ship = battle.fleets[0].addShip(); - super(ship, new BaseAction("nothing"), new Target(0, 0)); + let action = ship.actions.addCustom(new TriggerAction("nothing", { power: 0 })); + super(ship, action, Target.newFromShip(ship)); this.score = score; } } diff --git a/src/ui/battle/ActionBar.ts b/src/ui/battle/ActionBar.ts index 0ff6e09..a08c563 100644 --- a/src/ui/battle/ActionBar.ts +++ b/src/ui/battle/ActionBar.ts @@ -212,7 +212,7 @@ module TK.SpaceTac.UI { * Temporarily set power status for a given move-fire simulation */ updateFromSimulation(action: BaseAction, simulation: MoveFireResult) { - if (simulation.complete) { + if (simulation.status == MoveFireStatus.OK) { this.updateSelectedActionPower(simulation.total_move_ap, simulation.total_fire_ap, action); } else { this.updateSelectedActionPower(0, 0, action); diff --git a/src/ui/battle/Targetting.spec.ts b/src/ui/battle/Targetting.spec.ts index 4ffbaec..33087a1 100644 --- a/src/ui/battle/Targetting.spec.ts +++ b/src/ui/battle/Targetting.spec.ts @@ -89,8 +89,7 @@ module TK.SpaceTac.UI.Specs { check.patch(targetting, "simulate", () => { let result = new MoveFireResult(); - result.success = true; - result.complete = true; + result.status = MoveFireStatus.OK; result.need_move = true; result.move_location = Target.newFromLocation(80, 20); result.can_move = true; diff --git a/src/ui/battle/Targetting.ts b/src/ui/battle/Targetting.ts index f5d6b98..d176e0d 100644 --- a/src/ui/battle/Targetting.ts +++ b/src/ui/battle/Targetting.ts @@ -236,10 +236,17 @@ module TK.SpaceTac.UI { let from = (simulation.need_fire && this.mode != ActionTargettingMode.SURROUNDINGS) ? simulation.move_location : this.ship.location; let angle = Math.atan2(this.target.y - from.y, this.target.x - from.x); - if (simulation.success) { + if (simulation.status == MoveFireStatus.IMPOSSIBLE_APPROACH || simulation.status == MoveFireStatus.INVALID_TARGET || simulation.status == MoveFireStatus.NO_ACTION) { + this.drawVector(0x888888, this.ship.arena_x, this.ship.arena_y, this.target.x, this.target.y); + this.fire_arrow.setPosition(this.target.x, this.target.y); + this.fire_arrow.setRotation(angle); + this.view.changeImage(this.fire_arrow, (simulation.status == MoveFireStatus.INVALID_TARGET) ? "battle-hud-simulator-target" : "battle-hud-simulator-failed"); + this.fire_arrow.visible = true; + this.impact_area.visible = false; + } else { let previous: MoveFirePart | null = null; simulation.parts.forEach(part => { - this.drawPart(part, simulation.complete, previous); + this.drawPart(part, simulation.status == MoveFireStatus.OK, previous); previous = part; }); @@ -266,7 +273,7 @@ module TK.SpaceTac.UI { if (this.mode != ActionTargettingMode.SURROUNDINGS) { this.fire_arrow.setPosition(this.target.x, this.target.y); this.fire_arrow.setRotation(angle); - this.view.changeImage(this.fire_arrow, simulation.complete ? "battle-hud-simulator-ok" : "battle-hud-simulator-power"); + this.view.changeImage(this.fire_arrow, (simulation.status == MoveFireStatus.OK) ? "battle-hud-simulator-ok" : "battle-hud-simulator-power"); this.fire_arrow.visible = true; } } else { @@ -274,13 +281,6 @@ module TK.SpaceTac.UI { this.impact_indicators.visible = false; this.fire_arrow.visible = false; } - } else { - this.drawVector(0x888888, this.ship.arena_x, this.ship.arena_y, this.target.x, this.target.y); - this.fire_arrow.setPosition(this.target.x, this.target.y); - this.fire_arrow.setRotation(angle); - this.view.changeImage(this.fire_arrow, "battle-hud-simulator-failed"); - this.fire_arrow.visible = true; - this.impact_area.visible = false; } this.updateDiffsDisplay(); @@ -292,30 +292,6 @@ module TK.SpaceTac.UI { // Toggle tactical mode this.tactical_mode(bool(this.action)); - - // Toggle range hint - if (this.ship && this.action) { - if (this.simulation.need_move) { - let move_action: MoveAction | null = null; - if (this.simulation.success) { - let last_move = first(acopy(this.simulation.parts).reverse(), part => part.action instanceof MoveAction); - if (last_move) { - move_action = last_move.action; - } - } else { - let engine = new MoveFireSimulator(this.ship, new ArenaGrid()).findEngine(); - if (engine) { - move_action = engine; - } - } - if (move_action) { - let power = this.ship.getValue("power"); - if (this.action !== move_action) { - power = Math.max(power - this.action.getPowerUsage(this.ship, this.target), 0); - } - } - } - } } /** @@ -406,8 +382,7 @@ module TK.SpaceTac.UI { if (this.active) { this.simulate(); - if (this.ship && this.simulation.complete) { - let ship = this.ship; + if (this.simulation.status == MoveFireStatus.OK) { this.simulation.parts.forEach(part => { if (part.possible) { applier(part.action, part.target);