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
* Fix action tooltips showing battle information ("not enough power"...)
* Implement sliders for personality traits
* Center the portraits when there are less than 5
Battle
------
* Fix tactical information being hidden when changing selected action
* 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)
@ -46,7 +48,13 @@ Battle
* Add a voluntary retreat option
* 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
* 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
* 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)

View File

@ -8,6 +8,17 @@ module TK.SpaceTac {
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
*

View File

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

View File

@ -4,7 +4,7 @@ module TK.SpaceTac {
*/
export class Battle {
// Grid for the arena
grid?: IArenaGrid
grid: IArenaGrid
// Battle outcome, if the battle has ended
outcome: BattleOutcome | null = null
@ -309,9 +309,7 @@ module TK.SpaceTac {
y -= dy * total_length * 0.5;
for (var i = 0; i < fleet.ships.length; i++) {
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].setArenaFacingAngle(facing_angle);
}

View File

@ -6,13 +6,13 @@ module TK.SpaceTac.Specs {
TestTools.setShipModel(ship, 100, 0, ship_ap);
TestTools.addEngine(ship, engine_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];
}
test.case("finds a suitable engine to make an approach", check => {
let ship = new Ship();
let simulator = new MoveFireSimulator(ship);
let simulator = new MoveFireSimulator(ship, new PixelGrid());
check.equals(simulator.findEngine(), null, "no engine");
let engine1 = TestTools.addEngine(ship, 100);
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 => {
let [ship, simulator, action] = simpleWeaponCase(10, 2, 3);
let result = simulator.simulateAction(action, new Target(ship.arena_x + 5, ship.arena_y, null));
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, false, 'can_fire');
check.same(result.total_fire_ap, 3, 'total_fire_ap');
check.equals(result.success, true, 'success');
check.equals(result.need_move, false, 'need_move');
check.equals(result.need_fire, true, 'need_fire');
check.equals(result.can_fire, false, '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, null), ap: 3, possible: false }
@ -58,14 +58,15 @@ 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, null));
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.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.total_move_ap, 1);
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.need_fire, true, 'need_fire');
check.equals(result.can_fire, true, 'can_fire');
check.equals(result.total_fire_ap, 3, 'total_fire_ap');
let move_action = ship.actions.listAll().filter(action => action instanceof MoveAction)[0];
check.equals(result.parts, [
@ -75,7 +76,7 @@ module TK.SpaceTac.Specs {
});
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);
check.equals(imaterialize(result), [
@ -183,7 +184,7 @@ module TK.SpaceTac.Specs {
TestTools.setShipModel(enemy, 2, 1);
let engine = TestTools.addEngine(ship, 80);
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 diffs = simulator.getExpectedDiffs(nn(ship.getBattle()), result);
check.equals(diffs, [

View File

@ -50,7 +50,7 @@ module TK.SpaceTac {
// Playing ship
private ship: Ship,
// Coordinates grid
private grid?: IArenaGrid
private grid: IArenaGrid
) { }
/**
@ -129,12 +129,8 @@ module TK.SpaceTac {
let move_action: MoveAction | null = null;
result.move_location = Target.newFromShip(this.ship);
if (action instanceof MoveAction) {
let corrected_target = action.applyReachableRange(this.ship, target, move_margin);
corrected_target = action.applyExclusion(this.ship, corrected_target);
if (corrected_target) {
result.need_move = target.getDistanceTo(this.ship.location) > 0;
move_target = corrected_target;
}
result.need_move = true;
move_target = target;
move_action = action;
} else {
move_action = this.findEngine();
@ -152,15 +148,19 @@ module TK.SpaceTac {
}
}
}
if (move_target && arenaDistance(move_target, this.ship.location) < 0.000001) {
result.need_move = false;
if (move_target && move_action && result.need_move) {
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
if (result.need_move && move_target && move_action) {
result.total_move_ap = move_action.getPowerUsage(this.ship, move_target);
result.can_move = ap > 0;
result.can_end_move = result.total_move_ap <= ap;
result.can_move = result.can_move && (ap > 0);
result.can_end_move = result.can_move && (result.total_move_ap <= ap);
result.move_location = move_target;
// 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 });

View File

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

View File

@ -3,7 +3,7 @@ module TK.SpaceTac {
export class TestTools {
// 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 fleet2 = new Fleet(new Player("Defender"));
@ -15,6 +15,9 @@ module TK.SpaceTac {
}
var battle = new Battle(fleet1, fleet2);
if (!hexgrid) {
battle.grid = new PixelGrid();
}
battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 1, 0));
battle.play_order = fleet1.ships.concat(fleet2.ships);
battle.setPlayingShip(battle.play_order[0]);

View File

@ -125,13 +125,12 @@ module TK.SpaceTac {
}
checkLocationTarget(ship: Ship, target: Target): Target | null {
target = this.applyReachableRange(ship, target);
target = this.applyExclusion(ship, target);
return target.getDistanceTo(ship.location) > 0 ? target : null;
let fixed_target = this.applyExclusion(ship, this.applyReachableRange(ship, target));
return fixed_target.getDistanceTo(target) < 1e-8 ? target : null;
}
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);
return [new ShipMoveDiff(ship, ship.location, destination, this)];
}

View File

@ -2,6 +2,7 @@ module TK.SpaceTac.Specs {
testing("Maneuver", test => {
test.case("uses move-fire simulation to build a list of battle diffs", check => {
let battle = new Battle();
battle.grid = new PixelGrid();
let ship1 = battle.fleets[0].addShip();
let ship2 = 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 => {
let battle = new Battle();
battle.grid = new PixelGrid();
let ship = battle.fleets[0].addShip();
let weapon = TestTools.addWeapon(ship, 50, 5, 100);
let action = weapon;
@ -202,8 +203,8 @@ module TK.SpaceTac.Specs {
TestTools.addEngine(ship, 100);
let weapon = TestTools.addWeapon(ship, 100, 1, 100, 10);
let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(200, 0), 0.5);
check.nears(maneuver.simulation.move_location.x, 100.5, 1);
let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(200, 0));
check.equals(maneuver.simulation.move_location.x, 100);
check.equals(maneuver.simulation.move_location.y, 0);
check.equals(TacticalAIHelpers.evaluateClustering(ship, battle, maneuver), 0);

View File

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

View File

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