1
0
Fork 0
This commit is contained in:
Michaël Lemaire 2018-07-09 11:38:42 +02:00
parent 7e5572c1d9
commit b4302da855
13 changed files with 66 additions and 41 deletions

10
TODO.md
View file

@ -33,12 +33,14 @@ Character sheet
--------------- ---------------
* Improve attribute tooltips * Improve attribute tooltips
* Fix action tooltips showing battle information ("not enough power"...)
* Implement sliders for personality traits * Implement sliders for personality traits
* Center the portraits when there are less than 5 * Center the portraits when there are less than 5
Battle Battle
------ ------
* Fix tactical information being hidden when changing selected action
* Improve arena ships layering (sometimes information is displayed behind other sprites) * Improve arena ships layering (sometimes information is displayed behind other sprites)
* In the ship tooltip, show power cost, toggled and overheat states * In the ship tooltip, show power cost, toggled and overheat states
* Display shield (and its (dis)appearance) * Display shield (and its (dis)appearance)
@ -46,7 +48,13 @@ Battle
* Add a voluntary retreat option * Add a voluntary retreat option
* Toggle bar/text display in power section of action bar * Toggle bar/text display in power section of action bar
* Show a cooldown indicator on move action icon, if the simulation would cause the engine to overheat * Show a cooldown indicator on move action icon, if the simulation would cause the engine to overheat
* Add an hexagonal grid (optional, may be enforced only on mobile) and work in units of this grid * [WIP] Add an hexagonal grid
* Use distances in units of this grid (add a multiplier to be compatible with no grid mode)
* Fix repel effect to snap to grid (beware of two ships being moved to the same location!)
* Fix engine targetting producing out-of-grid coordinates because of exclusion areas
* Display the grid in targetting mode, with colors (replaces range hint)
* Fix move-fire simulator's scanCircle
* Fix tactical AI scanArea
* Add engine trail effect, and sound * Add engine trail effect, and sound
* Find incentives to move from starting position (permanent drones or anomalies?) * 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) * Mark targetting in error when target is refused by the action (there is already an arrow for this)

View file

@ -8,6 +8,17 @@ module TK.SpaceTac {
snap(loc: IArenaLocation): IArenaLocation; snap(loc: IArenaLocation): IArenaLocation;
} }
/**
* Pixel grid
*
* This will only round the coordinates to the pixels
*/
export class PixelGrid implements IArenaGrid {
snap(loc: IArenaLocation): IArenaLocation {
return new ArenaLocation(Math.round(loc.x), Math.round(loc.y));
}
}
/** /**
* Hexagonal unbounded arena grid * Hexagonal unbounded arena grid
* *

View file

@ -36,6 +36,7 @@ module TK.SpaceTac {
var ship5 = new Ship(fleet2, "F2S2"); var ship5 = new Ship(fleet2, "F2S2");
var battle = new Battle(fleet1, fleet2, 1000, 500); var battle = new Battle(fleet1, fleet2, 1000, 500);
battle.grid = new PixelGrid();
battle.placeShips(); battle.placeShips();
check.nears(ship1.arena_x, 250); check.nears(ship1.arena_x, 250);

View file

@ -4,7 +4,7 @@ module TK.SpaceTac {
*/ */
export class Battle { export class Battle {
// Grid for the arena // Grid for the arena
grid?: IArenaGrid grid: IArenaGrid
// Battle outcome, if the battle has ended // Battle outcome, if the battle has ended
outcome: BattleOutcome | null = null outcome: BattleOutcome | null = null
@ -309,9 +309,7 @@ module TK.SpaceTac {
y -= dy * total_length * 0.5; y -= dy * total_length * 0.5;
for (var i = 0; i < fleet.ships.length; i++) { for (var i = 0; i < fleet.ships.length; i++) {
let location = new ArenaLocation(x + i * dx * spacing, y + i * dy * spacing); let location = new ArenaLocation(x + i * dx * spacing, y + i * dy * spacing);
if (this.grid) { location = this.grid.snap(location);
location = this.grid.snap(location);
}
fleet.ships[i].setArenaPosition(location.x, location.y); fleet.ships[i].setArenaPosition(location.x, location.y);
fleet.ships[i].setArenaFacingAngle(facing_angle); fleet.ships[i].setArenaFacingAngle(facing_angle);
} }

View file

@ -6,13 +6,13 @@ module TK.SpaceTac.Specs {
TestTools.setShipModel(ship, 100, 0, ship_ap); TestTools.setShipModel(ship, 100, 0, ship_ap);
TestTools.addEngine(ship, engine_distance); 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 });
let simulator = new MoveFireSimulator(ship); let simulator = new MoveFireSimulator(ship, new PixelGrid());
return [ship, simulator, action]; return [ship, simulator, action];
} }
test.case("finds a suitable engine to make an approach", check => { test.case("finds a suitable engine to make an approach", check => {
let ship = new Ship(); let ship = new Ship();
let simulator = new MoveFireSimulator(ship); let simulator = new MoveFireSimulator(ship, new PixelGrid());
check.equals(simulator.findEngine(), null, "no engine"); check.equals(simulator.findEngine(), null, "no engine");
let engine1 = TestTools.addEngine(ship, 100); let engine1 = TestTools.addEngine(ship, 100);
engine1.configureCooldown(1, 1); engine1.configureCooldown(1, 1);
@ -44,11 +44,11 @@ module TK.SpaceTac.Specs {
test.case("can't fire when in range, but not enough AP", check => { test.case("can't fire when in range, but not enough AP", check => {
let [ship, simulator, action] = simpleWeaponCase(10, 2, 3); 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, null));
check.same(result.success, true, 'success'); check.equals(result.success, true, 'success');
check.same(result.need_move, false, 'need_move'); check.equals(result.need_move, false, 'need_move');
check.same(result.need_fire, true, 'need_fire'); check.equals(result.need_fire, true, 'need_fire');
check.same(result.can_fire, false, 'can_fire'); check.equals(result.can_fire, false, 'can_fire');
check.same(result.total_fire_ap, 3, 'total_fire_ap'); check.equals(result.total_fire_ap, 3, 'total_fire_ap');
check.equals(result.parts, [ 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, null), ap: 3, possible: false }
@ -58,14 +58,15 @@ module TK.SpaceTac.Specs {
test.case("moves straight to get within range", check => { test.case("moves straight to get within range", check => {
let [ship, simulator, action] = simpleWeaponCase(); 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, null));
check.same(result.success, true, 'success'); check.equals(result.success, true, 'success');
check.same(result.need_move, true, 'need_move'); check.equals(result.need_move, true, 'need_move');
check.same(result.can_end_move, true, 'can_end_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, null));
check.equals(result.total_move_ap, 1); check.equals(result.total_move_ap, 1);
check.same(result.need_fire, true, 'need_fire'); check.equals(result.need_fire, true, 'need_fire');
check.same(result.can_fire, true, 'can_fire'); check.equals(result.can_fire, true, 'can_fire');
check.same(result.total_fire_ap, 3, 'total_fire_ap'); check.equals(result.total_fire_ap, 3, 'total_fire_ap');
let move_action = ship.actions.listAll().filter(action => action instanceof MoveAction)[0]; let move_action = ship.actions.listAll().filter(action => action instanceof MoveAction)[0];
check.equals(result.parts, [ check.equals(result.parts, [
@ -75,7 +76,7 @@ module TK.SpaceTac.Specs {
}); });
test.case("scans a circle for move targets", check => { test.case("scans a circle for move targets", check => {
let simulator = new MoveFireSimulator(new Ship()); let simulator = new MoveFireSimulator(new Ship(), new PixelGrid());
let result = simulator.scanCircle(50, 30, 10, 1, 1); let result = simulator.scanCircle(50, 30, 10, 1, 1);
check.equals(imaterialize(result), [ check.equals(imaterialize(result), [
@ -183,7 +184,7 @@ module TK.SpaceTac.Specs {
TestTools.setShipModel(enemy, 2, 1); TestTools.setShipModel(enemy, 2, 1);
let engine = TestTools.addEngine(ship, 80); let engine = TestTools.addEngine(ship, 80);
let weapon = TestTools.addWeapon(ship, 5, 1, 150); let weapon = TestTools.addWeapon(ship, 5, 1, 150);
let simulator = new MoveFireSimulator(ship); let simulator = new MoveFireSimulator(ship, new PixelGrid());
let result = simulator.simulateAction(weapon, Target.newFromShip(enemy), 5); let result = simulator.simulateAction(weapon, Target.newFromShip(enemy), 5);
let diffs = simulator.getExpectedDiffs(nn(ship.getBattle()), result); let diffs = simulator.getExpectedDiffs(nn(ship.getBattle()), result);
check.equals(diffs, [ check.equals(diffs, [

View file

@ -50,7 +50,7 @@ module TK.SpaceTac {
// Playing ship // Playing ship
private ship: Ship, private ship: Ship,
// Coordinates grid // Coordinates grid
private grid?: IArenaGrid private grid: IArenaGrid
) { } ) { }
/** /**
@ -129,12 +129,8 @@ module TK.SpaceTac {
let move_action: MoveAction | null = null; let move_action: MoveAction | null = null;
result.move_location = Target.newFromShip(this.ship); result.move_location = Target.newFromShip(this.ship);
if (action instanceof MoveAction) { if (action instanceof MoveAction) {
let corrected_target = action.applyReachableRange(this.ship, target, move_margin); result.need_move = true;
corrected_target = action.applyExclusion(this.ship, corrected_target); move_target = target;
if (corrected_target) {
result.need_move = target.getDistanceTo(this.ship.location) > 0;
move_target = corrected_target;
}
move_action = action; move_action = action;
} else { } else {
move_action = this.findEngine(); move_action = this.findEngine();
@ -152,15 +148,19 @@ module TK.SpaceTac {
} }
} }
} }
if (move_target && arenaDistance(move_target, this.ship.location) < 0.000001) { if (move_target && move_action && result.need_move) {
result.need_move = false; if (arenaDistance(move_target, this.ship.location) < 1e-8) {
result.need_move = false;
} else {
result.can_move = bool(move_action.checkTarget(this.ship, move_target));
}
} }
// Check move AP // Check move AP
if (result.need_move && move_target && move_action) { if (result.need_move && move_target && move_action) {
result.total_move_ap = move_action.getPowerUsage(this.ship, move_target); result.total_move_ap = move_action.getPowerUsage(this.ship, move_target);
result.can_move = ap > 0; result.can_move = result.can_move && (ap > 0);
result.can_end_move = result.total_move_ap <= ap; result.can_end_move = result.can_move && (result.total_move_ap <= ap);
result.move_location = move_target; result.move_location = move_target;
// TODO Split in "this turn" part and "next turn" part if needed // 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 }); result.parts.push({ action: move_action, target: move_target, ap: result.total_move_ap, possible: result.can_move });

View file

@ -67,8 +67,8 @@ module TK.SpaceTac {
/** /**
* Snap to battle grid * Snap to battle grid
*/ */
snap(grid?: IArenaGrid): Target { snap(grid: IArenaGrid): Target {
if (!grid || this.ship_id) { if (this.ship_id) {
return this; return this;
} else { } else {
let location = grid.snap(this); let location = grid.snap(this);

View file

@ -3,7 +3,7 @@ module TK.SpaceTac {
export class TestTools { export class TestTools {
// Create a battle between two fleets, with a fixed play order (owned ships, then enemy ships) // Create a battle between two fleets, with a fixed play order (owned ships, then enemy ships)
static createBattle(own_ships = 1, enemy_ships = 1): Battle { static createBattle(own_ships = 1, enemy_ships = 1, hexgrid = false): Battle {
var fleet1 = new Fleet(new Player("Attacker")); var fleet1 = new Fleet(new Player("Attacker"));
var fleet2 = new Fleet(new Player("Defender")); var fleet2 = new Fleet(new Player("Defender"));
@ -15,6 +15,9 @@ module TK.SpaceTac {
} }
var battle = new Battle(fleet1, fleet2); var battle = new Battle(fleet1, fleet2);
if (!hexgrid) {
battle.grid = new PixelGrid();
}
battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 1, 0)); battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 1, 0));
battle.play_order = fleet1.ships.concat(fleet2.ships); battle.play_order = fleet1.ships.concat(fleet2.ships);
battle.setPlayingShip(battle.play_order[0]); battle.setPlayingShip(battle.play_order[0]);

View file

@ -125,13 +125,12 @@ module TK.SpaceTac {
} }
checkLocationTarget(ship: Ship, target: Target): Target | null { checkLocationTarget(ship: Ship, target: Target): Target | null {
target = this.applyReachableRange(ship, target); let fixed_target = this.applyExclusion(ship, this.applyReachableRange(ship, target));
target = this.applyExclusion(ship, target); return fixed_target.getDistanceTo(target) < 1e-8 ? target : null;
return target.getDistanceTo(ship.location) > 0 ? target : null;
} }
protected getSpecificDiffs(ship: Ship, battle: Battle, target: Target): BaseBattleDiff[] { protected getSpecificDiffs(ship: Ship, battle: Battle, target: Target): BaseBattleDiff[] {
let angle = (arenaDistance(target, ship.location) < 0.00001) ? ship.arena_angle : arenaAngle(ship.location, target); let angle = (arenaDistance(target, ship.location) < 1e-8) ? ship.arena_angle : arenaAngle(ship.location, target);
let destination = new ArenaLocationAngle(target.x, target.y, angle); let destination = new ArenaLocationAngle(target.x, target.y, angle);
return [new ShipMoveDiff(ship, ship.location, destination, this)]; return [new ShipMoveDiff(ship, ship.location, destination, this)];
} }

View file

@ -2,6 +2,7 @@ module TK.SpaceTac.Specs {
testing("Maneuver", test => { testing("Maneuver", test => {
test.case("uses move-fire simulation to build a list of battle diffs", check => { test.case("uses move-fire simulation to build a list of battle diffs", check => {
let battle = new Battle(); let battle = new Battle();
battle.grid = new PixelGrid();
let ship1 = battle.fleets[0].addShip(); let ship1 = battle.fleets[0].addShip();
let ship2 = battle.fleets[1].addShip(); let ship2 = battle.fleets[1].addShip();
let ship3 = battle.fleets[1].addShip(); let ship3 = battle.fleets[1].addShip();

View file

@ -108,6 +108,7 @@ module TK.SpaceTac.Specs {
test.case("evaluates turn cost", check => { test.case("evaluates turn cost", check => {
let battle = new Battle(); let battle = new Battle();
battle.grid = new PixelGrid();
let ship = battle.fleets[0].addShip(); let ship = battle.fleets[0].addShip();
let weapon = TestTools.addWeapon(ship, 50, 5, 100); let weapon = TestTools.addWeapon(ship, 50, 5, 100);
let action = weapon; let action = weapon;
@ -202,8 +203,8 @@ module TK.SpaceTac.Specs {
TestTools.addEngine(ship, 100); TestTools.addEngine(ship, 100);
let weapon = TestTools.addWeapon(ship, 100, 1, 100, 10); let weapon = TestTools.addWeapon(ship, 100, 1, 100, 10);
let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(200, 0), 0.5); let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(200, 0));
check.nears(maneuver.simulation.move_location.x, 100.5, 1); check.equals(maneuver.simulation.move_location.x, 100);
check.equals(maneuver.simulation.move_location.y, 0); check.equals(maneuver.simulation.move_location.y, 0);
check.equals(TacticalAIHelpers.evaluateClustering(ship, battle, maneuver), 0); check.equals(TacticalAIHelpers.evaluateClustering(ship, battle, maneuver), 0);

View file

@ -82,6 +82,8 @@ module TK.SpaceTac.UI.Specs {
view.splash = false; view.splash = false;
let battle = Battle.newQuickRandom(); let battle = Battle.newQuickRandom();
battle.grid = new PixelGrid();
battle.placeShips();
let player = new Player(); let player = new Player();
nn(battle.playing_ship).fleet.setPlayer(player); nn(battle.playing_ship).fleet.setPlayer(player);

View file

@ -300,7 +300,7 @@ module TK.SpaceTac.UI {
move_action = <MoveAction>last_move.action; move_action = <MoveAction>last_move.action;
} }
} else { } else {
let engine = new MoveFireSimulator(this.ship).findEngine(); let engine = new MoveFireSimulator(this.ship, new PixelGrid()).findEngine();
if (engine) { if (engine) {
move_action = engine; move_action = engine;
} }