diff --git a/TODO.md b/TODO.md index 4b16a77..0ef683e 100644 --- a/TODO.md +++ b/TODO.md @@ -42,6 +42,7 @@ Battle * Fix tactical information being hidden when changing selected action * Right click while targetting switch to edit mode (move target and fire target are then draggable) - this will be the default on mobile +* Move animation should face the target when firing after * Improve arena ships layering (sometimes information is displayed behind other sprites) * In the ship tooltip, show power cost, toggled and overheat states * Display shield (and its (dis)appearance) diff --git a/src/core/ArenaGrid.ts b/src/core/ArenaGrid.ts index 31255c3..98606e8 100644 --- a/src/core/ArenaGrid.ts +++ b/src/core/ArenaGrid.ts @@ -64,14 +64,15 @@ module TK.SpaceTac { } measure(loc1: IArenaLocation, loc2: IArenaLocation): number { + // FIXME let d = super.measure(loc1, loc2) / this.unit; let r = Math.round(d); if (r >= d) { - return Math.floor(d); - } else if (r - d < 1e-8) { + return Math.ceil(d); + } else if (d - r < 1e-8) { return r; } else { - return Math.ceil(d); + return Math.floor(d); } } } diff --git a/src/core/MoveFireSimulator.spec.ts b/src/core/MoveFireSimulator.spec.ts index d5d2d51..948b340 100644 --- a/src/core/MoveFireSimulator.spec.ts +++ b/src/core/MoveFireSimulator.spec.ts @@ -5,8 +5,9 @@ module TK.SpaceTac.Specs { let ship = new Ship(); TestTools.setShipModel(ship, 100, 0, ship_ap); TestTools.addEngine(ship, engine_distance); - let action = new TriggerAction("weapon", { power: weapon_ap, range: distance }); + let action = new TriggerAction("weapon", { power: weapon_ap, range: distance, blast: 0.1 }); let simulator = new MoveFireSimulator(ship, new PixelGrid()); + ship.actions.addCustom(action); return [ship, simulator, action]; } @@ -28,7 +29,7 @@ module TK.SpaceTac.Specs { test.case("fires directly when in range", check => { let [ship, simulator, action] = simpleWeaponCase(); - let result = simulator.simulateAction(action, new Target(ship.arena_x + 5, ship.arena_y, null)); + 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'); @@ -37,13 +38,13 @@ module TK.SpaceTac.Specs { check.same(result.total_fire_ap, 3, 'total_fire_ap'); check.equals(result.parts, [ - { action: action, target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 3, possible: true } + { action: action, target: new Target(ship.arena_x + 5, ship.arena_y), ap: 3, possible: true } ]); }); 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, null)); + let result = simulator.simulateAction(action, new Target(ship.arena_x + 5, ship.arena_y)); check.equals(result.success, true, 'success'); check.equals(result.need_move, false, 'need_move'); check.equals(result.need_fire, true, 'need_fire'); @@ -51,18 +52,33 @@ module TK.SpaceTac.Specs { 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, null), ap: 3, possible: false } + { action: action, target: new Target(ship.arena_x + 5, ship.arena_y), ap: 3, possible: false } ]); }); + 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.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.need_fire, true, 'need_fire'); + check.equals(result.can_fire, false, 'can_fire'); + }); + }); + 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, null)); + let result = simulator.simulateAction(action, new Target(ship.arena_x + 15, ship.arena_y)); check.equals(result.success, true, 'success'); 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'); - check.equals(result.move_location, new Target(ship.arena_x + 5, ship.arena_y, null)); + check.equals(result.move_location, new Target(ship.arena_x + 5, ship.arena_y)); check.equals(result.total_move_ap, 1); check.equals(result.need_fire, true, 'need_fire'); check.equals(result.can_fire, true, 'can_fire'); @@ -70,8 +86,8 @@ module TK.SpaceTac.Specs { let move_action = ship.actions.listAll().filter(action => action instanceof MoveAction)[0]; check.equals(result.parts, [ - { action: move_action, target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 1, possible: true }, - { action: action, target: new Target(ship.arena_x + 15, ship.arena_y, null), ap: 3, possible: true } + { action: move_action, target: new Target(ship.arena_x + 5, ship.arena_y), ap: 1, possible: true }, + { action: action, target: new Target(ship.arena_x + 15, ship.arena_y), ap: 3, possible: true } ]); }); @@ -134,11 +150,11 @@ 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, null)); + let result = simulator.simulateAction(action, new Target(ship.arena_x + 18, ship.arena_y)); check.same(result.success, true, 'success'); 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, null)); + check.equals(result.move_location, new Target(ship.arena_x + 10, ship.arena_y)); check.equals(result.total_move_ap, 2); check.same(result.need_fire, true, 'need_fire'); check.same(result.can_fire, false, 'can_fire'); @@ -146,15 +162,15 @@ module TK.SpaceTac.Specs { let move_action = ship.actions.listAll().filter(action => action instanceof MoveAction)[0]; check.equals(result.parts, [ - { action: move_action, target: new Target(ship.arena_x + 10, ship.arena_y, null), ap: 2, possible: true }, - { action: action, target: new Target(ship.arena_x + 18, ship.arena_y, null), ap: 2, possible: false } + { action: move_action, target: new Target(ship.arena_x + 10, ship.arena_y), ap: 2, possible: true }, + { action: action, target: new Target(ship.arena_x + 18, ship.arena_y), ap: 2, possible: false } ]); }); test.case("does nothing if trying to move in the same spot", check => { 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, null)); + let result = simulator.simulateAction(move_action, new Target(ship.arena_x, ship.arena_y)); check.equals(result.success, false); check.equals(result.need_move, false); check.equals(result.need_fire, false); @@ -163,10 +179,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, null), 5); + let result = simulator.simulateAction(action, new Target(ship.arena_x + 97, ship.arena_y), 5); check.equals(result.success, true); check.equals(result.need_move, false); - result = simulator.simulateAction(action, new Target(ship.arena_x + 101, ship.arena_y, null), 5); + result = simulator.simulateAction(action, new Target(ship.arena_x + 101, ship.arena_y), 5); check.equals(result.success, true); 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 f024828..df92088 100644 --- a/src/core/MoveFireSimulator.ts +++ b/src/core/MoveFireSimulator.ts @@ -96,7 +96,7 @@ module TK.SpaceTac { let candidates: [number, Target][] = []; iforeach(this.scanCircle(target.x, target.y, radius), candidate => { - if (action.checkLocationTarget(this.ship, candidate)) { + if (action.checkTarget(this.ship, candidate)) { candidates.push([candidate.getDistanceTo(this.ship.location), candidate]); } }); @@ -166,7 +166,7 @@ module TK.SpaceTac { } else { result.need_fire = true; result.total_fire_ap = action.getPowerUsage(this.ship, target); - result.can_fire = result.total_fire_ap <= ap; + result.can_fire = (action.checkTarget(this.ship, target, result.move_location)) && (result.total_fire_ap <= ap); 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; diff --git a/src/core/Target.spec.ts b/src/core/Target.spec.ts index 14226b4..da9f997 100644 --- a/src/core/Target.spec.ts +++ b/src/core/Target.spec.ts @@ -28,5 +28,13 @@ module TK.SpaceTac.Specs { var t2 = Target.newFromLocation(4, 5); check.nears(t1.getAngleTo(t2), Math.PI / 4); }); + + test.case("snaps to a grid", check => { + let grid = new HexagonalArenaGrid(5, 1); + check.equals(Target.newFromLocation(1, 1).snap(grid), Target.newFromLocation(0, 0), "1,1"); + check.equals(Target.newFromLocation(6, 4).snap(grid), Target.newFromLocation(7.5, 5), "6,4"); + let ship = new Ship(); + check.equals(new Target(6, 4, ship).snap(grid), new Target(7.5, 5, ship), "6,4,ship"); + }); }); } diff --git a/src/core/Target.ts b/src/core/Target.ts index c71e4af..6a0063e 100644 --- a/src/core/Target.ts +++ b/src/core/Target.ts @@ -10,10 +10,10 @@ module TK.SpaceTac { ship_id: RObjectId | null // Standard constructor - constructor(x: number, y: number, ship: Ship | null = null) { + constructor(x: number, y: number, ship: Ship | RObjectId | null = null) { this.x = x; this.y = y; - this.ship_id = ship ? ship.id : null; + this.ship_id = (ship === null) ? null : rid(ship); } jasmineToString() { @@ -38,12 +38,8 @@ module TK.SpaceTac { * Snap to battle grid */ snap(grid: ArenaGrid): Target { - if (this.ship_id) { - return this; - } else { - let location = grid.snap(this); - return Target.newFromLocation(location.x, location.y); - } + let location = grid.snap(this); + return new Target(location.x, location.y, this.ship_id); } // Get distance to another target diff --git a/src/core/actions/BaseAction.ts b/src/core/actions/BaseAction.ts index 0d1aafd..7c9b723 100644 --- a/src/core/actions/BaseAction.ts +++ b/src/core/actions/BaseAction.ts @@ -236,14 +236,14 @@ module TK.SpaceTac { * * Will call checkLocationTarget or checkShipTarget by default */ - checkTarget(ship: Ship, target: Target): boolean { + checkTarget(ship: Ship, target: Target, from: IArenaLocation = ship.location): boolean { if (this.checkCannotBeApplied(ship)) { return false; } else { if (target.isShip()) { - return this.checkShipTarget(ship, target); + return this.checkShipTarget(ship, target, from); } else { - return this.checkLocationTarget(ship, target); + return this.checkLocationTarget(ship, target, from); } } } @@ -253,7 +253,7 @@ module TK.SpaceTac { * * Should not check for power or action availability, only whether the target is valid */ - protected checkLocationTarget(ship: Ship, target: Target): boolean { + protected checkLocationTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { return false; } @@ -262,7 +262,7 @@ module TK.SpaceTac { * * Should not check for power or action availability, only whether the target is valid */ - protected checkShipTarget(ship: Ship, target: Target): boolean { + protected checkShipTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { return false; } diff --git a/src/core/actions/DeployDroneAction.ts b/src/core/actions/DeployDroneAction.ts index 0d48889..8ada193 100644 --- a/src/core/actions/DeployDroneAction.ts +++ b/src/core/actions/DeployDroneAction.ts @@ -75,7 +75,7 @@ module TK.SpaceTac { return result; } - checkShipTarget(ship: Ship, target: Target): boolean { + checkShipTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { if (ship.actions.isToggled(this)) { return target.isShip(ship); } else { @@ -83,11 +83,11 @@ module TK.SpaceTac { } } - checkLocationTarget(ship: Ship, target: Target): boolean { + checkLocationTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { if (ship.actions.isToggled(this)) { return false; } else { - return ship.grid.inRange(ship.location, target, this.deploy_distance); + return ship.grid.inRange(from, target, this.deploy_distance); } } diff --git a/src/core/actions/MoveAction.spec.ts b/src/core/actions/MoveAction.spec.ts index 08fe110..a8fd1b2 100644 --- a/src/core/actions/MoveAction.spec.ts +++ b/src/core/actions/MoveAction.spec.ts @@ -68,19 +68,19 @@ module TK.SpaceTac.Specs { var action = new MoveAction("Engine", { distance_per_power: 1000 }); - var result = action.checkLocationTarget(ship, Target.newFromLocation(700, 500)); + var result = action.checkTarget(ship, Target.newFromLocation(700, 500)); check.equals(result, true); - result = action.checkLocationTarget(ship, Target.newFromLocation(800, 500)); + result = action.checkTarget(ship, Target.newFromLocation(800, 500)); check.equals(result, true); - result = action.checkLocationTarget(ship, Target.newFromLocation(900, 500)); + result = action.checkTarget(ship, Target.newFromLocation(900, 500)); check.equals(result, false); - result = action.checkLocationTarget(ship, Target.newFromLocation(1000, 500)); + result = action.checkTarget(ship, Target.newFromLocation(1000, 500)); check.equals(result, false); - result = action.checkLocationTarget(ship, Target.newFromLocation(1200, 500)); + result = action.checkTarget(ship, Target.newFromLocation(1200, 500)); check.equals(result, true); }); diff --git a/src/core/actions/MoveAction.ts b/src/core/actions/MoveAction.ts index 8f9fedf..f5fdfd2 100644 --- a/src/core/actions/MoveAction.ts +++ b/src/core/actions/MoveAction.ts @@ -90,8 +90,8 @@ module TK.SpaceTac { return this.distance_per_power * ship.grid.getUnit(); } - checkLocationTarget(ship: Ship, target: Target): boolean { - if (!ship.grid.check(target) || ship.grid.measure(ship.location, target) < 1e-8) { + protected checkLocationTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { + if (!ship.grid.check(target) || ship.grid.measure(from, target) < 1e-8) { return false; } diff --git a/src/core/actions/ToggleAction.ts b/src/core/actions/ToggleAction.ts index da6da53..f6b6cbb 100644 --- a/src/core/actions/ToggleAction.ts +++ b/src/core/actions/ToggleAction.ts @@ -67,7 +67,7 @@ module TK.SpaceTac { return result; } - checkShipTarget(ship: Ship, target: Target): boolean { + checkShipTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { return ship.is(target.ship_id); } diff --git a/src/core/actions/TriggerAction.ts b/src/core/actions/TriggerAction.ts index 3391734..cdd1010 100644 --- a/src/core/actions/TriggerAction.ts +++ b/src/core/actions/TriggerAction.ts @@ -128,24 +128,24 @@ module TK.SpaceTac { } } - checkLocationTarget(ship: Ship, target: Target): boolean { + checkLocationTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { if (target && (this.blast > 0 || this.angle > 0)) { - return ship.grid.inRange(ship.location, target, this.range); + return ship.grid.inRange(from, target, this.range); } else { return false; } } - checkShipTarget(ship: Ship, target: Target): boolean { + checkShipTarget(ship: Ship, target: Target, from: IArenaLocation): boolean { if (this.range > 0 && ship.is(target.ship_id)) { // No self fire return false; } else { // Check if target is in range if (this.blast > 0 || this.angle > 0) { - return this.checkLocationTarget(ship, new Target(target.x, target.y)); + return this.checkLocationTarget(ship, new Target(target.x, target.y), from); } else { - return ship.grid.inRange(ship.location, target, this.range); + return ship.grid.inRange(from, target, this.range); } } } diff --git a/src/ui/battle/Targetting.ts b/src/ui/battle/Targetting.ts index 5d0699a..f5d6b98 100644 --- a/src/ui/battle/Targetting.ts +++ b/src/ui/battle/Targetting.ts @@ -263,10 +263,12 @@ module TK.SpaceTac.UI { this.updateImpactIndicators(this.impact_indicators, this.ship, this.action, this.target, this.simulation.move_location); - 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.fire_arrow.visible = true; + 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.fire_arrow.visible = true; + } } else { this.impact_area.visible = false; this.impact_indicators.visible = false; @@ -301,7 +303,7 @@ module TK.SpaceTac.UI { move_action = last_move.action; } } else { - let engine = new MoveFireSimulator(this.ship, new PixelGrid()).findEngine(); + let engine = new MoveFireSimulator(this.ship, new ArenaGrid()).findEngine(); if (engine) { move_action = engine; }