1
0
Fork 0

typescript: Added strict null checks

This commit is contained in:
Michaël Lemaire 2017-03-09 18:11:00 +01:00
parent e64e3955b3
commit d3e12fa8e1
78 changed files with 461 additions and 363 deletions

1
TODO
View file

@ -1,4 +1,3 @@
* Enable strict null checking in typescript
* Ensure that tweens and particle emitters get destroyed once animation is done (or view changes) * Ensure that tweens and particle emitters get destroyed once animation is done (or view changes)
* Highlight ships that will be included as target of current action * Highlight ships that will be included as target of current action
* Controls: Do not focus on ship while targetting for area effects (dissociate hover and target) * Controls: Do not focus on ship while targetting for area effects (dissociate hover and target)

View file

@ -9,7 +9,7 @@
"homepage": "", "homepage": "",
"private": true, "private": true,
"dependencies": { "dependencies": {
"phaser": "2.6.2", "phaser-ce": "2.7.3",
"jasmine-core": "jasmine#^2.5.2", "jasmine-core": "jasmine#^2.5.2",
"deep-diff": "0.3.0" "deep-diff": "0.3.0"
} }

View file

@ -22,13 +22,13 @@
"bower": "~1.8", "bower": "~1.8",
"codecov": "~1.0", "codecov": "~1.0",
"jasmine": "~2.5", "jasmine": "~2.5",
"karma": "~1.4", "karma": "~1.5",
"karma-coverage": "~1.1", "karma-coverage": "~1.1",
"karma-jasmine": "~1.1", "karma-jasmine": "~1.1",
"karma-phantomjs-launcher": "~1.0", "karma-phantomjs-launcher": "~1.0",
"remap-istanbul": "~0.8", "remap-istanbul": "~0.9",
"live-server": "~1.2", "live-server": "~1.2",
"typescript": "~2.2", "typescript": "~2.2",
"typings": "~1.4" "typings": "~2.1"
} }
} }

@ -1 +1 @@
Subproject commit db61f921e8afa9beca31bf1de1d30870f48a17e4 Subproject commit 79e3c649cf7a411d06af7372aa825518e0d8df30

View file

@ -1,8 +1,8 @@
module TS.SpaceTac { module TS.SpaceTac {
describe("Battle", function () { describe("Battle", function () {
it("defines play order by initiative throws", function () { it("defines play order by initiative throws", function () {
var fleet1 = new Fleet(null); var fleet1 = new Fleet();
var fleet2 = new Fleet(null); var fleet2 = new Fleet();
var ship1 = new Ship(fleet1, "F1S1"); var ship1 = new Ship(fleet1, "F1S1");
ship1.setAttribute("initiative", 2); ship1.setAttribute("initiative", 2);
@ -26,8 +26,8 @@ module TS.SpaceTac {
}); });
it("places ships on lines, facing the arena center", function () { it("places ships on lines, facing the arena center", function () {
var fleet1 = new Fleet(null); var fleet1 = new Fleet();
var fleet2 = new Fleet(null); var fleet2 = new Fleet();
var ship1 = new Ship(fleet1, "F1S1"); var ship1 = new Ship(fleet1, "F1S1");
var ship2 = new Ship(fleet1, "F1S2"); var ship2 = new Ship(fleet1, "F1S2");
@ -60,8 +60,8 @@ module TS.SpaceTac {
}); });
it("advances to next ship in play order", function () { it("advances to next ship in play order", function () {
var fleet1 = new Fleet(null); var fleet1 = new Fleet();
var fleet2 = new Fleet(null); var fleet2 = new Fleet();
var ship1 = new Ship(fleet1, "F1S1"); var ship1 = new Ship(fleet1, "F1S1");
var ship2 = new Ship(fleet1, "F1S2"); var ship2 = new Ship(fleet1, "F1S2");
@ -113,8 +113,8 @@ module TS.SpaceTac {
}); });
it("calls startTurn on ships", function () { it("calls startTurn on ships", function () {
var fleet1 = new Fleet(null); var fleet1 = new Fleet();
var fleet2 = new Fleet(null); var fleet2 = new Fleet();
var ship1 = new Ship(fleet1, "F1S1"); var ship1 = new Ship(fleet1, "F1S1");
var ship2 = new Ship(fleet1, "F1S2"); var ship2 = new Ship(fleet1, "F1S2");

View file

@ -20,8 +20,8 @@ module TS.SpaceTac {
turn: number; turn: number;
// Current ship whose turn it is to play // Current ship whose turn it is to play
playing_ship_index: number; playing_ship_index: number | null;
playing_ship: Ship; playing_ship: Ship | null;
// List of deployed drones // List of deployed drones
drones: Drone[] = []; drones: Drone[] = [];
@ -34,9 +34,9 @@ module TS.SpaceTac {
timer = Timer.global; timer = Timer.global;
// Create a battle between two fleets // Create a battle between two fleets
constructor(fleet1: Fleet = null, fleet2: Fleet = null, width = 1780, height = 948) { constructor(fleet1 = new Fleet(), fleet2 = new Fleet(), width = 1780, height = 948) {
this.log = new BattleLog(); this.log = new BattleLog();
this.fleets = [fleet1 || new Fleet(), fleet2 || new Fleet()]; this.fleets = [fleet1, fleet2];
this.play_order = []; this.play_order = [];
this.playing_ship_index = null; this.playing_ship_index = null;
this.playing_ship = null; this.playing_ship = null;
@ -129,7 +129,7 @@ module TS.SpaceTac {
} }
// Ends a battle and sets the outcome // Ends a battle and sets the outcome
endBattle(winner: Fleet, log: boolean = true) { endBattle(winner: Fleet | null, log: boolean = true) {
this.ended = true; this.ended = true;
this.outcome = new BattleOutcome(winner); this.outcome = new BattleOutcome(winner);
if (winner) { if (winner) {
@ -153,7 +153,7 @@ module TS.SpaceTac {
this.endBattle(null, log); this.endBattle(null, log);
} else if (alive_fleets === 1) { } else if (alive_fleets === 1) {
// We have a winner // We have a winner
var winner: Fleet = null; var winner: Fleet | null = null;
this.fleets.forEach((fleet: Fleet) => { this.fleets.forEach((fleet: Fleet) => {
if (fleet.isAlive()) { if (fleet.isAlive()) {
winner = fleet; winner = fleet;
@ -198,7 +198,7 @@ module TS.SpaceTac {
this.playing_ship.startTurn(); this.playing_ship.startTurn();
} }
if (log) { if (log && previous_ship && this.playing_ship) {
this.log.add(new ShipChangeEvent(previous_ship, this.playing_ship)); this.log.add(new ShipChangeEvent(previous_ship, this.playing_ship));
} }
} }
@ -207,11 +207,13 @@ module TS.SpaceTac {
* Make an AI play the current ship * Make an AI play the current ship
*/ */
playAI(ai: AbstractAI | null = null) { playAI(ai: AbstractAI | null = null) {
if (!ai) { if (this.playing_ship) {
// TODO Use an AI adapted to the fleet if (!ai) {
ai = new BullyAI(this.playing_ship, this.timer); // TODO Use an AI adapted to the fleet
ai = new BullyAI(this.playing_ship, this.timer);
}
ai.play();
} }
ai.play();
} }
// Start the battle // Start the battle

View file

@ -3,7 +3,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Check a single game log event // Check a single game log event
function checkEvent(got: BaseLogEvent, ship: Ship, code: string, function checkEvent(got: BaseLogEvent, ship: Ship, code: string,
target_ship: Ship = null, target_x: number = null, target_y: number = null): void { target_ship: Ship | null = null, target_x: number | null = null, target_y: number | null = null): void {
if (target_ship) { if (target_ship) {
if (target_x === null) { if (target_x === null) {
target_x = target_ship.arena_x; target_x = target_ship.arena_x;
@ -15,23 +15,27 @@ module TS.SpaceTac {
expect(got.ship).toBe(ship); expect(got.ship).toBe(ship);
expect(got.code).toEqual(code); expect(got.code).toEqual(code);
expect(got.target.ship).toBe(target_ship); if (got.target) {
if (target_x === null) { expect(got.target.ship).toBe(target_ship);
expect(got.target.x).toBeNull(); if (target_x === null) {
expect(got.target.x).toBeNull();
} else {
expect(got.target.x).toBeCloseTo(target_x, 0.000001);
}
if (target_y === null) {
expect(got.target.y).toBeNull();
} else {
expect(got.target.y).toBeCloseTo(target_y, 0.000001);
}
} else { } else {
expect(got.target.x).toBeCloseTo(target_x, 0.000001); fail("Got no target");
}
if (target_y === null) {
expect(got.target.y).toBeNull();
} else {
expect(got.target.y).toBeCloseTo(target_y, 0.000001);
} }
} }
// Fake event // Fake event
class FakeEvent extends BaseLogEvent { class FakeEvent extends BaseLogEvent {
constructor() { constructor() {
super("fake"); super("fake", new Ship());
} }
} }
@ -68,7 +72,8 @@ module TS.SpaceTac {
}); });
it("can receive simulated initial state events", function () { it("can receive simulated initial state events", function () {
var battle = Battle.newQuickRandom(); let battle = Battle.newQuickRandom();
let playing = nn(battle.playing_ship);
battle.log.clear(); battle.log.clear();
battle.log.addFilter("value"); battle.log.addFilter("value");
expect(battle.log.events.length).toBe(0); expect(battle.log.events.length).toBe(0);
@ -80,7 +85,7 @@ module TS.SpaceTac {
checkEvent(battle.log.events[i], battle.play_order[i], "move", null, checkEvent(battle.log.events[i], battle.play_order[i], "move", null,
battle.play_order[i].arena_x, battle.play_order[i].arena_y); battle.play_order[i].arena_x, battle.play_order[i].arena_y);
} }
checkEvent(battle.log.events[8], battle.playing_ship, "ship_change", battle.playing_ship); checkEvent(battle.log.events[8], playing, "ship_change", playing);
}); });
}); });
} }

View file

@ -5,12 +5,12 @@ module TS.SpaceTac {
draw: boolean; draw: boolean;
// Victorious fleet // Victorious fleet
winner: Fleet; winner: Fleet | null;
// Retrievable loot // Retrievable loot
loot: Equipment[]; loot: Equipment[];
constructor(winner: Fleet) { constructor(winner: Fleet | null) {
this.winner = winner; this.winner = winner;
this.draw = winner ? false : true; this.draw = winner ? false : true;
this.loot = []; this.loot = [];
@ -27,8 +27,10 @@ module TS.SpaceTac {
var count = random.randInt(0, ship.getEquipmentCount()); var count = random.randInt(0, ship.getEquipmentCount());
while (count > 0) { while (count > 0) {
var salvaged = ship.getRandomEquipment(random); var salvaged = ship.getRandomEquipment(random);
salvaged.detach(); if (salvaged) {
this.loot.push(salvaged); salvaged.detach();
this.loot.push(salvaged);
}
count--; count--;
} }
@ -61,7 +63,7 @@ module TS.SpaceTac {
// Generate a special loot item for the winner fleet // Generate a special loot item for the winner fleet
// The equipment will be in the dead ship range // The equipment will be in the dead ship range
generateLootItem(random: RandomGenerator, base_level: number): Equipment { generateLootItem(random: RandomGenerator, base_level: number): Equipment | null {
var generator = this.getLootGenerator(random); var generator = this.getLootGenerator(random);
var level = new IntegerRange(base_level - 1, base_level + 1); var level = new IntegerRange(base_level - 1, base_level + 1);
return generator.generate(level); return generator.generate(level);

View file

@ -2,10 +2,10 @@ module TS.SpaceTac {
// Piece of equipment to attach in slots // Piece of equipment to attach in slots
export class Equipment { export class Equipment {
// Actual slot this equipment is attached to // Actual slot this equipment is attached to
attached_to: Slot; attached_to: Slot | null = null;
// Type of slot this equipment can fit in // Type of slot this equipment can fit in
slot: SlotType; slot: SlotType | null;
// Identifiable equipment code (may be used by UI to customize visual effects) // Identifiable equipment code (may be used by UI to customize visual effects)
code: string; code: string;
@ -41,7 +41,7 @@ module TS.SpaceTac {
target_effects: BaseEffect[]; target_effects: BaseEffect[];
// Basic constructor // Basic constructor
constructor(slot: SlotType = null, code: string = null) { constructor(slot: SlotType | null = null, code = "equiment") {
this.slot = slot; this.slot = slot;
this.code = code; this.code = code;
this.name = code; this.name = code;

View file

@ -10,17 +10,17 @@ module TS.SpaceTac {
ships: Ship[]; ships: Ship[];
// Current fleet location // Current fleet location
location: StarLocation; location: StarLocation | null;
// Current battle in which the fleet is engaged (null if not fighting) // Current battle in which the fleet is engaged (null if not fighting)
battle: Battle; battle: Battle | null;
// Amount of credits available // Amount of credits available
credits = 0; credits = 0;
// Create a fleet, bound to a player // Create a fleet, bound to a player
constructor(player: Player = null) { constructor(player = new Player()) {
this.player = player || new Player(); this.player = player;
this.ships = []; this.ships = [];
this.location = null; this.location = null;
this.battle = null; this.battle = null;
@ -59,7 +59,7 @@ module TS.SpaceTac {
} }
// Set the current battle // Set the current battle
setBattle(battle: Battle): void { setBattle(battle: Battle | null): void {
this.battle = battle; this.battle = battle;
} }

View file

@ -4,12 +4,12 @@ module TS.SpaceTac {
// Random generator to use // Random generator to use
random: RandomGenerator; random: RandomGenerator;
constructor(random: RandomGenerator = new RandomGenerator()) { constructor(random = RandomGenerator.global) {
this.random = random; this.random = random;
} }
// Generate a fleet of a given level // Generate a fleet of a given level
generate(level: number, player: Player = null, ship_count: number = 3): Fleet { generate(level: number, player?: Player, ship_count = 3): Fleet {
var fleet = new Fleet(player); var fleet = new Fleet(player);
var ship_generator = new ShipGenerator(this.random); var ship_generator = new ShipGenerator(this.random);

View file

@ -12,7 +12,7 @@ module TS.SpaceTac.Specs {
* Apply deterministic game steps * Apply deterministic game steps
*/ */
function applyGameSteps(session: GameSession): void { function applyGameSteps(session: GameSession): void {
var battle = session.getBattle(); var battle = nn(session.getBattle());
battle.advanceToNextShip(); battle.advanceToNextShip();
// TODO Make some fixed moves (AI?) // TODO Make some fixed moves (AI?)
battle.endBattle(battle.fleets[0]); battle.endBattle(battle.fleets[0]);

View file

@ -48,7 +48,7 @@ module TS.SpaceTac {
} }
// Get currently played battle, null when none is in progress // Get currently played battle, null when none is in progress
getBattle(): Battle { getBattle(): Battle | null {
return this.player.getBattle(); return this.player.getBattle();
} }

View file

@ -17,11 +17,14 @@ module TS.SpaceTac.Specs {
generator.random = new SkewedRandomGenerator([0.5]); generator.random = new SkewedRandomGenerator([0.5]);
var equipment = generator.generate(new IntegerRange(3, 6)); var equipment = generator.generate(new IntegerRange(3, 6));
if (equipment) {
expect(equipment.slot).toBe(SlotType.Shield); expect(equipment.slot).toBe(SlotType.Shield);
expect(equipment.name).toEqual("Hexagrid Shield"); expect(equipment.name).toEqual("Hexagrid Shield");
expect(equipment.min_level).toBe(5); expect(equipment.min_level).toBe(5);
expect(equipment.ap_usage).toBeCloseTo(6.2727, 0.00001); expect(equipment.ap_usage).toBeCloseTo(6.2727, 0.00001);
} else {
fail("No equipment generated");
}
}); });
}); });
} }

View file

@ -35,22 +35,22 @@ module TS.SpaceTac {
// If slot is specified, it will generate an equipment for this slot type specifically // If slot is specified, it will generate an equipment for this slot type specifically
// If level is specified, it will generate an equipment with level requirement inside this range // If level is specified, it will generate an equipment with level requirement inside this range
// If no equipment could be generated from available templates, null is returned // If no equipment could be generated from available templates, null is returned
generate(level: IntegerRange = null, slot: SlotType = null): Equipment { generate(level: IntegerRange | null = null, slot: SlotType | null = null): Equipment | null {
// Generate equipments matching conditions, with each template // Generate equipments matching conditions, with each template
var equipments: Equipment[] = []; var equipments: Equipment[] = [];
this.templates.forEach((template: LootTemplate) => { this.templates.forEach((template: LootTemplate) => {
if (slot !== null && slot !== template.slot) { if (slot && slot != template.slot) {
return; return;
} }
var equipment: Equipment; var equipment: Equipment | null;
if (level === null) { if (level) {
equipment = template.generate(this.random);
} else {
equipment = template.generateInLevelRange(level, this.random); equipment = template.generateInLevelRange(level, this.random);
} else {
equipment = template.generate(this.random);
} }
if (equipment !== null) { if (equipment) {
equipments.push(equipment); equipments.push(equipment);
} }
}); });

View file

@ -61,7 +61,7 @@ module TS.SpaceTac.Specs {
var template = new LootTemplate(SlotType.Weapon, "Bulletator"); var template = new LootTemplate(SlotType.Weapon, "Bulletator");
template.min_level = new IntegerRange(4, 7); template.min_level = new IntegerRange(4, 7);
var result: Range; var result: any;
result = template.getPowerRangeForLevel(new IntegerRange(4, 7)); result = template.getPowerRangeForLevel(new IntegerRange(4, 7));
expect(result.min).toBe(0); expect(result.min).toBe(0);

View file

@ -46,7 +46,7 @@ module TS.SpaceTac {
} }
// Set a capability requirement // Set a capability requirement
addRequirement(capability: keyof ShipAttributes, min: number, max: number = null): void { addRequirement(capability: keyof ShipAttributes, min: number, max: number | null = null): void {
this.requirements[capability] = new IntegerRange(min, max); this.requirements[capability] = new IntegerRange(min, max);
} }
@ -70,7 +70,10 @@ module TS.SpaceTac {
result.ap_usage = this.ap_usage.getProportional(power); result.ap_usage = this.ap_usage.getProportional(power);
result.min_level = this.min_level.getProportional(power); result.min_level = this.min_level.getProportional(power);
result.action = this.getActionForEquipment(result); let action = this.getActionForEquipment(result)
if (action) {
result.action = action;
}
iteritems(this.requirements, (key: string, requirement: IntegerRange) => { iteritems(this.requirements, (key: string, requirement: IntegerRange) => {
if (requirement) { if (requirement) {
@ -89,7 +92,7 @@ module TS.SpaceTac {
} }
// Find the power range that will result in the level range // Find the power range that will result in the level range
getPowerRangeForLevel(level: IntegerRange): Range { getPowerRangeForLevel(level: IntegerRange): Range | null {
if (level.min > this.min_level.max || level.max < this.min_level.min) { if (level.min > this.min_level.max || level.max < this.min_level.min) {
return null; return null;
} else { } else {
@ -113,7 +116,7 @@ module TS.SpaceTac {
// Generate an equipment that will have its level requirement in the given range // Generate an equipment that will have its level requirement in the given range
// May return null if level range is not compatible with the template // May return null if level range is not compatible with the template
generateInLevelRange(level: IntegerRange, random = RandomGenerator.global): Equipment { generateInLevelRange(level: IntegerRange, random = RandomGenerator.global): Equipment | null {
var random_range = this.getPowerRangeForLevel(level); var random_range = this.getPowerRangeForLevel(level);
if (random_range) { if (random_range) {
var power = random.random() * (random_range.max - random_range.min) + random_range.min; var power = random.random() * (random_range.max - random_range.min) + random_range.min;
@ -126,7 +129,7 @@ module TS.SpaceTac {
/** /**
* Convenience function to add a modulated effect to the equipment * Convenience function to add a modulated effect to the equipment
*/ */
addEffect(effect: BaseEffect, min_value: number, max_value: number = null, target = true) { addEffect(effect: BaseEffect, min_value: number, max_value: number | null = null, target = true) {
var template = new EffectTemplate(effect); var template = new EffectTemplate(effect);
template.addModifier("value", new IntegerRange(min_value, max_value)); template.addModifier("value", new IntegerRange(min_value, max_value));
if (target) { if (target) {
@ -139,8 +142,8 @@ module TS.SpaceTac {
/** /**
* Convenience function to add a modulated sticky effect to the equipment * Convenience function to add a modulated sticky effect to the equipment
*/ */
addStickyEffect(effect: BaseEffect, min_value: number, max_value: number = null, min_duration: number = 1, addStickyEffect(effect: BaseEffect, min_value: number, max_value: number | null = null, min_duration: number = 1,
max_duration: number = null, on_stick = false, on_turn_start = false, target = true): void { max_duration: number | null = null, on_stick = false, on_turn_start = false, target = true): void {
var template = new EffectTemplate(new StickyEffect(effect, 0, on_stick, on_turn_start)); var template = new EffectTemplate(new StickyEffect(effect, 0, on_stick, on_turn_start));
template.addModifier("value", new IntegerRange(min_value, max_value)); template.addModifier("value", new IntegerRange(min_value, max_value));
template.addModifier("duration", new IntegerRange(min_duration, max_duration)); template.addModifier("duration", new IntegerRange(min_duration, max_duration));
@ -177,7 +180,7 @@ module TS.SpaceTac {
} }
// Method to reimplement to assign an action to a generated equipment // Method to reimplement to assign an action to a generated equipment
protected getActionForEquipment(equipment: Equipment): BaseAction { protected getActionForEquipment(equipment: Equipment): BaseAction | null {
return null; return null;
} }
} }

View file

@ -21,8 +21,9 @@ module TS.SpaceTac.Specs {
let engine2 = TestTools.addEngine(ship, 120); let engine2 = TestTools.addEngine(ship, 120);
let engine3 = TestTools.addEngine(ship, 150); let engine3 = TestTools.addEngine(ship, 150);
let engine4 = TestTools.addEngine(ship, 70); let engine4 = TestTools.addEngine(ship, 70);
expect(simulator.findBestEngine()).toBe(engine3); let best = simulator.findBestEngine();
expect(simulator.findBestEngine().distance).toBe(150); expect(best).toBe(engine3);
expect((<Equipment>best).distance).toBe(150);
}); });
it("fires directly when in range", function () { it("fires directly when in range", function () {
@ -36,7 +37,7 @@ module TS.SpaceTac.Specs {
expect(result.total_fire_ap).toBe(3, 'total_fire_ap'); expect(result.total_fire_ap).toBe(3, 'total_fire_ap');
expect(result.parts).toEqual([ expect(result.parts).toEqual([
{ action: jasmine.objectContaining({ code: "fire-null" }), target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 3, possible: true } { action: jasmine.objectContaining({ code: "fire-equiment" }), target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 3, possible: true }
]); ]);
}); });
@ -50,7 +51,7 @@ module TS.SpaceTac.Specs {
expect(result.total_fire_ap).toBe(3, 'total_fire_ap'); expect(result.total_fire_ap).toBe(3, 'total_fire_ap');
expect(result.parts).toEqual([ expect(result.parts).toEqual([
{ action: jasmine.objectContaining({ code: "fire-null" }), target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 3, possible: false } { action: jasmine.objectContaining({ code: "fire-equiment" }), target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 3, possible: false }
]); ]);
}); });
@ -68,7 +69,7 @@ module TS.SpaceTac.Specs {
expect(result.parts).toEqual([ expect(result.parts).toEqual([
{ action: jasmine.objectContaining({ code: "move" }), target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 1, possible: true }, { action: jasmine.objectContaining({ code: "move" }), target: new Target(ship.arena_x + 5, ship.arena_y, null), ap: 1, possible: true },
{ action: jasmine.objectContaining({ code: "fire-null" }), target: new Target(ship.arena_x + 15, ship.arena_y, null), ap: 3, possible: true } { action: jasmine.objectContaining({ code: "fire-equiment" }), target: new Target(ship.arena_x + 15, ship.arena_y, null), ap: 3, possible: true }
]); ]);
}); });
@ -86,7 +87,7 @@ module TS.SpaceTac.Specs {
expect(result.parts).toEqual([ expect(result.parts).toEqual([
{ action: jasmine.objectContaining({ code: "move" }), target: new Target(ship.arena_x + 10, ship.arena_y, null), ap: 2, possible: true }, { action: jasmine.objectContaining({ code: "move" }), target: new Target(ship.arena_x + 10, ship.arena_y, null), ap: 2, possible: true },
{ action: jasmine.objectContaining({ code: "fire-null" }), target: new Target(ship.arena_x + 18, ship.arena_y, null), ap: 2, possible: false } { action: jasmine.objectContaining({ code: "fire-equiment" }), target: new Target(ship.arena_x + 18, ship.arena_y, null), ap: 2, possible: false }
]); ]);
}); });

View file

@ -13,7 +13,7 @@ module TS.SpaceTac {
} }
// Get a new unique name from available choices // Get a new unique name from available choices
getName(): string { getName(): string | null {
if (this.choices.length === 0) { if (this.choices.length === 0) {
return null; return null;
} }

View file

@ -63,10 +63,10 @@ module TS.SpaceTac {
} }
// Get currently played battle, null when none is in progress // Get currently played battle, null when none is in progress
getBattle(): Battle { getBattle(): Battle | null {
return this.fleet.battle; return this.fleet.battle;
} }
setBattle(battle: Battle): void { setBattle(battle: Battle | null): void {
this.fleet.setBattle(battle); this.fleet.setBattle(battle);
} }

View file

@ -8,12 +8,12 @@ module TS.SpaceTac {
max: number; max: number;
// Create a range of values // Create a range of values
constructor(min: number, max: number = null) { constructor(min: number, max: number | null = null) {
this.set(min, max); this.set(min, max);
} }
// Change the range // Change the range
set(min: number, max: number = null) { set(min: number, max: number | null = null) {
this.min = min; this.min = min;
if (max === null) { if (max === null) {
this.max = this.min; this.max = this.min;

View file

@ -95,7 +95,7 @@ module TS.SpaceTac {
upgrade_points = 0; upgrade_points = 0;
// Create a new ship inside a fleet // Create a new ship inside a fleet
constructor(fleet: Fleet = null, name = "Ship") { constructor(fleet: Fleet | null = null, name = "Ship") {
this.fleet = fleet || new Fleet(); this.fleet = fleet || new Fleet();
this.level = 1; this.level = 1;
this.name = name; this.name = name;
@ -146,20 +146,12 @@ module TS.SpaceTac {
// Return the player owning this ship // Return the player owning this ship
getPlayer(): Player { getPlayer(): Player {
if (this.fleet) { return this.fleet.player;
return this.fleet.player;
} else {
return null;
}
} }
// get the current battle this ship is engaged in // get the current battle this ship is engaged in
getBattle(): Battle { getBattle(): Battle | null {
if (this.fleet) { return this.fleet.battle;
return this.fleet.battle;
} else {
return null;
}
} }
// Get the list of actions available // Get the list of actions available
@ -257,7 +249,7 @@ module TS.SpaceTac {
// Initialize the action points counter // Initialize the action points counter
// This should be called once at the start of a battle // This should be called once at the start of a battle
// If no value is provided, the attribute ap_initial will be used // If no value is provided, the attribute ap_initial will be used
initializeActionPoints(value: number = null): void { initializeActionPoints(value: number | null = null): void {
if (value === null) { if (value === null) {
value = this.attributes.power_initial.get(); value = this.attributes.power_initial.get();
} }
@ -267,7 +259,7 @@ module TS.SpaceTac {
// Recover action points // Recover action points
// This should be called once at the end of a turn // This should be called once at the end of a turn
// If no value is provided, the current attribute ap_recovery will be used // If no value is provided, the current attribute ap_recovery will be used
recoverActionPoints(value: number = null): void { recoverActionPoints(value: number | null = null): void {
if (this.alive) { if (this.alive) {
if (value === null) { if (value === null) {
value = this.attributes.power_recovery.get(); value = this.attributes.power_recovery.get();
@ -510,7 +502,7 @@ module TS.SpaceTac {
* List all equipments attached to slots of a given type (any slot type if null) * List all equipments attached to slots of a given type (any slot type if null)
*/ */
listEquipment(slottype: SlotType | null = null): Equipment[] { listEquipment(slottype: SlotType | null = null): Equipment[] {
return this.slots.filter(slot => slot.attached && (slottype == null || slot.type == slottype)).map(slot => slot.attached); return nna(this.slots.filter(slot => slot.attached && (slottype == null || slot.type == slottype)).map(slot => slot.attached));
} }
// Get the number of attached equipments // Get the number of attached equipments
@ -525,13 +517,13 @@ module TS.SpaceTac {
} }
// Get a random attached equipment, null if no equipment is attached // Get a random attached equipment, null if no equipment is attached
getRandomEquipment(random = RandomGenerator.global): Equipment { getRandomEquipment(random = RandomGenerator.global): Equipment | null {
var count = this.getEquipmentCount(); var count = this.getEquipmentCount();
if (count === 0) { if (count === 0) {
return null; return null;
} else { } else {
var picked = random.randInt(0, count - 1); var picked = random.randInt(0, count - 1);
var result: Equipment = null; var result: Equipment | null = null;
var index = 0; var index = 0;
this.slots.forEach((slot: Slot) => { this.slots.forEach((slot: Slot) => {
if (slot.attached) { if (slot.attached) {

View file

@ -4,14 +4,13 @@ module TS.SpaceTac {
// Random number generator used // Random number generator used
random: RandomGenerator; random: RandomGenerator;
// Create a default ship generator constructor(random = RandomGenerator.global) {
constructor(random: RandomGenerator = null) { this.random = random;
this.random = random || new RandomGenerator();
} }
// Generate a ship of a given level // Generate a ship of a given level
// The ship will not be named, nor will be a member of any fleet // The ship will not be named, nor will be a member of any fleet
generate(level: number, model: ShipModel = null): Ship { generate(level: number, model: ShipModel | null = null): Ship {
var result = new Ship(); var result = new Ship();
var loot = new LootGenerator(this.random); var loot = new LootGenerator(this.random);
@ -30,7 +29,9 @@ module TS.SpaceTac {
// Fill equipment slots // Fill equipment slots
result.slots.forEach((slot: Slot) => { result.slots.forEach((slot: Slot) => {
var equipment = loot.generate(new IntegerRange(level, level), slot.type); var equipment = loot.generate(new IntegerRange(level, level), slot.type);
slot.attach(equipment); if (equipment) {
slot.attach(equipment);
}
}); });
return result; return result;

View file

@ -24,7 +24,7 @@ module TS.SpaceTac {
// Get the default ship model collection available in-game // Get the default ship model collection available in-game
static getDefaultCollection(): ShipModel[] { static getDefaultCollection(): ShipModel[] {
// TODO Store in cache // TODO Store in cache
var result = []; var result: ShipModel[] = [];
result.push(new ShipModel("scout", 1, 2, SlotType.Hull, SlotType.Power, SlotType.Power, SlotType.Engine, SlotType.Weapon)); result.push(new ShipModel("scout", 1, 2, SlotType.Hull, SlotType.Power, SlotType.Power, SlotType.Engine, SlotType.Weapon));

View file

@ -17,7 +17,7 @@ module TS.SpaceTac {
type: SlotType; type: SlotType;
// Currently attached equipment, null if none // Currently attached equipment, null if none
attached: Equipment; attached: Equipment | null;
// Create an empty slot for a ship // Create an empty slot for a ship
constructor(ship: Ship, type: SlotType) { constructor(ship: Ship, type: SlotType) {
@ -27,7 +27,7 @@ module TS.SpaceTac {
} }
// Attach an equipment in this slot // Attach an equipment in this slot
attach(equipment: Equipment): Equipment | null { attach(equipment: Equipment): Equipment {
if (this.type === equipment.slot && equipment.canBeEquipped(this.ship)) { if (this.type === equipment.slot && equipment.canBeEquipped(this.ship)) {
this.attached = equipment; this.attached = equipment;
equipment.attached_to = this; equipment.attached_to = this;
@ -35,11 +35,8 @@ module TS.SpaceTac {
if (this.ship) { if (this.ship) {
this.ship.updateAttributes(); this.ship.updateAttributes();
} }
return equipment;
} else {
return null;
} }
return equipment;
} }
} }

View file

@ -75,7 +75,7 @@ module TS.SpaceTac {
// Base level for encounters in this system // Base level for encounters in this system
level: number; level: number;
constructor(universe: Universe = null, x = 0, y = 0, name = "") { constructor(universe: Universe | null = null, x = 0, y = 0, name = "") {
this.universe = universe || new Universe(); this.universe = universe || new Universe();
this.x = x; this.x = x;
this.y = y; this.y = y;

View file

@ -36,7 +36,7 @@ module TS.SpaceTac {
} }
// Get the other side of the link, for a given side // Get the other side of the link, for a given side
getPeer(star: Star): Star { getPeer(star: Star): Star | null {
if (star === this.first) { if (star === this.first) {
return this.second; return this.second;
} else if (star === this.second) { } else if (star === this.second) {

View file

@ -1,7 +1,7 @@
module TS.SpaceTac.Specs { module TS.SpaceTac.Specs {
describe("StarLocation", () => { describe("StarLocation", () => {
it("removes generated encounters that lose", function () { it("removes generated encounters that lose", function () {
var location = new StarLocation(null, StarLocationType.PLANET, 0, 0); var location = new StarLocation(undefined, StarLocationType.PLANET, 0, 0);
var fleet = new Fleet(); var fleet = new Fleet();
var random = new SkewedRandomGenerator([0]); var random = new SkewedRandomGenerator([0]);
var battle = location.enterLocation(fleet, random); var battle = location.enterLocation(fleet, random);
@ -9,13 +9,13 @@ module TS.SpaceTac.Specs {
expect(location.encounter).not.toBeNull(); expect(location.encounter).not.toBeNull();
expect(battle).not.toBeNull(); expect(battle).not.toBeNull();
battle.endBattle(fleet); nn(battle).endBattle(fleet);
expect(location.encounter).toBeNull(); expect(location.encounter).toBeNull();
}); });
it("leaves generated encounters that win", function () { it("leaves generated encounters that win", function () {
var location = new StarLocation(null, StarLocationType.PLANET, 0, 0); var location = new StarLocation(undefined, StarLocationType.PLANET, 0, 0);
var fleet = new Fleet(); var fleet = new Fleet();
var random = new SkewedRandomGenerator([0]); var random = new SkewedRandomGenerator([0]);
var battle = location.enterLocation(fleet, random); var battle = location.enterLocation(fleet, random);
@ -23,7 +23,7 @@ module TS.SpaceTac.Specs {
expect(location.encounter).not.toBeNull(); expect(location.encounter).not.toBeNull();
expect(battle).not.toBeNull(); expect(battle).not.toBeNull();
battle.endBattle(location.encounter); nn(battle).endBattle(location.encounter);
expect(location.encounter).not.toBeNull(); expect(location.encounter).not.toBeNull();
}); });

View file

@ -24,14 +24,14 @@ module TS.SpaceTac {
universe_y: number; universe_y: number;
// Destination for jump, if its a WARP location // Destination for jump, if its a WARP location
jump_dest: StarLocation; jump_dest: StarLocation | null;
// Enemy encounter // Enemy encounter
encounter: Fleet; encounter: Fleet | null;
encounter_gen: boolean; encounter_gen: boolean;
constructor(star: Star, type: StarLocationType = StarLocationType.PLANET, x: number = 0, y: number = 0) { constructor(star = new Star(), type: StarLocationType = StarLocationType.PLANET, x: number = 0, y: number = 0) {
this.star = star || new Star(); this.star = star;
this.type = type; this.type = type;
this.x = x; this.x = x;
this.y = y; this.y = y;
@ -52,14 +52,14 @@ module TS.SpaceTac {
// Call this when first probing a location to generate the possible encounter // Call this when first probing a location to generate the possible encounter
// Returns the encountered fleet, null if no encounter happens // Returns the encountered fleet, null if no encounter happens
tryGenerateEncounter(random = RandomGenerator.global): Fleet { tryGenerateEncounter(random = RandomGenerator.global): Fleet | null {
if (!this.encounter_gen) { if (!this.encounter_gen) {
this.encounter_gen = true; this.encounter_gen = true;
if (random.random() < 0.8) { if (random.random() < 0.8) {
var fleet_generator = new FleetGenerator(random); var fleet_generator = new FleetGenerator(random);
var ship_count = random.randInt(1, 5); var ship_count = random.randInt(1, 5);
this.encounter = fleet_generator.generate(this.star.level, null, ship_count); this.encounter = fleet_generator.generate(this.star.level, undefined, ship_count);
} }
} }
@ -69,7 +69,7 @@ module TS.SpaceTac {
// Call this when entering a location to generate the possible encounter // Call this when entering a location to generate the possible encounter
// *fleet* is the player fleet, entering the location // *fleet* is the player fleet, entering the location
// Returns the engaged battle, null if no encounter happens // Returns the engaged battle, null if no encounter happens
enterLocation(fleet: Fleet, random = RandomGenerator.global): Battle { enterLocation(fleet: Fleet, random = RandomGenerator.global): Battle | null {
var encounter = this.tryGenerateEncounter(random); var encounter = this.tryGenerateEncounter(random);
if (encounter) { if (encounter) {
var battle = new Battle(fleet, encounter); var battle = new Battle(fleet, encounter);

View file

@ -60,17 +60,17 @@ module TS.SpaceTac.Specs {
var warps = getWarps(universe.stars[0]); var warps = getWarps(universe.stars[0]);
expect(warps.length).toBe(2); expect(warps.length).toBe(2);
expect(warps[0].jump_dest.star).toBe(universe.stars[1]); expect(nn(warps[0].jump_dest).star).toBe(universe.stars[1]);
expect(warps[1].jump_dest.star).toBe(universe.stars[2]); expect(nn(warps[1].jump_dest).star).toBe(universe.stars[2]);
expect(universe.stars[0].getWarpLocationTo(universe.stars[1])).toBe(warps[0]); expect(universe.stars[0].getWarpLocationTo(universe.stars[1])).toBe(warps[0]);
expect(universe.stars[0].getWarpLocationTo(universe.stars[2])).toBe(warps[1]); expect(universe.stars[0].getWarpLocationTo(universe.stars[2])).toBe(warps[1]);
warps = getWarps(universe.stars[1]); warps = getWarps(universe.stars[1]);
expect(warps.length).toBe(1); expect(warps.length).toBe(1);
expect(warps[0].jump_dest.star).toBe(universe.stars[0]); expect(nn(warps[0].jump_dest).star).toBe(universe.stars[0]);
expect(universe.stars[1].getWarpLocationTo(universe.stars[2])).toBeNull(); expect(universe.stars[1].getWarpLocationTo(universe.stars[2])).toBeNull();
warps = getWarps(universe.stars[2]); warps = getWarps(universe.stars[2]);
expect(warps.length).toBe(1); expect(warps.length).toBe(1);
expect(warps[0].jump_dest.star).toBe(universe.stars[0]); expect(nn(warps[0].jump_dest).star).toBe(universe.stars[0]);
}); });
}); });
} }

View file

@ -46,7 +46,7 @@ module TS.SpaceTac {
continue; continue;
} }
star.name = names.getName(); star.name = names.getName() || "Star";
result.push(star); result.push(star);
count--; count--;
@ -104,15 +104,12 @@ module TS.SpaceTac {
} }
// Get the star nearest to another // Get the star nearest to another
getNearestTo(star: Star, others: Star[] = null): Star { getNearestTo(star: Star, others = this.stars): Star | null {
if (others === null) {
others = this.stars;
}
if (others.length === 0) { if (others.length === 0) {
return null; return null;
} else { } else {
var mindist = this.radius * 2.0; var mindist = this.radius * 2.0;
var nearest: Star = null; var nearest: Star | null = null;
others.forEach((istar: Star) => { others.forEach((istar: Star) => {
if (istar !== star) { if (istar !== star) {
var dist = star.getDistanceTo(istar); var dist = star.getDistanceTo(istar);

View file

@ -11,10 +11,10 @@ module TS.SpaceTac {
needs_target: boolean; needs_target: boolean;
// Equipment that triggers this action // Equipment that triggers this action
equipment: Equipment; equipment: Equipment | null;
// Create the action // Create the action
constructor(code: string, name: string, needs_target: boolean, equipment: Equipment = null) { constructor(code: string, name: string, needs_target: boolean, equipment: Equipment | null = null) {
this.code = code; this.code = code;
this.name = name; this.name = name;
this.needs_target = needs_target; this.needs_target = needs_target;
@ -28,7 +28,7 @@ module TS.SpaceTac {
* *
* Returns an informative message indicating why the action cannot be used, null otherwise * Returns an informative message indicating why the action cannot be used, null otherwise
*/ */
checkCannotBeApplied(ship: Ship, remaining_ap: number = null): string | null { checkCannotBeApplied(ship: Ship, remaining_ap: number | null = null): string | null {
let battle = ship.getBattle(); let battle = ship.getBattle();
if (battle && battle.playing_ship !== ship) { if (battle && battle.playing_ship !== ship) {
// Ship is not playing // Ship is not playing
@ -48,7 +48,7 @@ module TS.SpaceTac {
} }
// Get the number of action points the action applied to a target would use // Get the number of action points the action applied to a target would use
getActionPointsUsage(ship: Ship, target: Target): number { getActionPointsUsage(ship: Ship, target: Target | null): number {
if (this.equipment) { if (this.equipment) {
return this.equipment.ap_usage; return this.equipment.ap_usage;
} else { } else {
@ -76,7 +76,7 @@ module TS.SpaceTac {
// Method to check if a target is applicable for this action // Method to check if a target is applicable for this action
// Will call checkLocationTarget or checkShipTarget by default // Will call checkLocationTarget or checkShipTarget by default
checkTarget(ship: Ship, target: Target): Target { checkTarget(ship: Ship, target: Target | null): Target | null {
if (this.checkCannotBeApplied(ship)) { if (this.checkCannotBeApplied(ship)) {
return null; return null;
} else if (target) { } else if (target) {
@ -92,18 +92,18 @@ module TS.SpaceTac {
// Method to reimplement to check if a space target is applicable // Method to reimplement to check if a space target is applicable
// Must return null if the target can't be applied, an altered target, or the original target // Must return null if the target can't be applied, an altered target, or the original target
checkLocationTarget(ship: Ship, target: Target): Target { checkLocationTarget(ship: Ship, target: Target): Target | null {
return null; return null;
} }
// Method to reimplement to check if a ship target is applicable // Method to reimplement to check if a ship target is applicable
// Must return null if the target can't be applied, an altered target, or the original target // Must return null if the target can't be applied, an altered target, or the original target
checkShipTarget(ship: Ship, target: Target): Target { checkShipTarget(ship: Ship, target: Target): Target | null {
return null; return null;
} }
// Apply an action, returning true if it was successful // Apply an action, returning true if it was successful
apply(ship: Ship, target: Target): boolean { apply(ship: Ship, target: Target | null): boolean {
let reject = this.checkCannotBeApplied(ship); let reject = this.checkCannotBeApplied(ship);
if (reject == null) { if (reject == null) {
let checked_target = this.checkTarget(ship, target); let checked_target = this.checkTarget(ship, target);
@ -127,7 +127,7 @@ module TS.SpaceTac {
} }
// Method to reimplement to apply a action // Method to reimplement to apply a action
protected customApply(ship: Ship, target: Target) { protected customApply(ship: Ship, target: Target | null) {
} }
} }
} }

View file

@ -5,6 +5,8 @@ module TS.SpaceTac {
* Action to deploy a drone in space * Action to deploy a drone in space
*/ */
export class DeployDroneAction extends BaseAction { export class DeployDroneAction extends BaseAction {
equipment: Equipment;
constructor(equipment: Equipment) { constructor(equipment: Equipment) {
super("deploy-" + equipment.code, "Deploy", true, equipment); super("deploy-" + equipment.code, "Deploy", true, equipment);
} }

View file

@ -6,14 +6,17 @@ module TS.SpaceTac {
// Boolean set to true if the weapon can target space // Boolean set to true if the weapon can target space
can_target_space: boolean; can_target_space: boolean;
// Equipment cannot be null
equipment: Equipment;
constructor(equipment: Equipment, can_target_space = false, name = "Fire") { constructor(equipment: Equipment, can_target_space = false, name = "Fire") {
super("fire-" + equipment.code, name, true, equipment); super("fire-" + equipment.code, name, true, equipment);
this.can_target_space = can_target_space; this.can_target_space = can_target_space;
} }
checkLocationTarget(ship: Ship, target: Target): Target { checkLocationTarget(ship: Ship, target: Target): Target | null {
if (this.can_target_space) { if (target && this.can_target_space) {
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.equipment.distance); target = target.constraintInRange(ship.arena_x, ship.arena_y, this.equipment.distance);
return target; return target;
} else { } else {
@ -21,8 +24,8 @@ module TS.SpaceTac {
} }
} }
checkShipTarget(ship: Ship, target: Target): Target { checkShipTarget(ship: Ship, target: Target): Target | null {
if (ship.getPlayer() === target.ship.getPlayer()) { if (target.ship && ship.getPlayer() === target.ship.getPlayer()) {
// No friendly fire // No friendly fire
return null; return null;
} else { } else {
@ -40,10 +43,11 @@ module TS.SpaceTac {
/** /**
* Collect the effects applied by this action * Collect the effects applied by this action
*/ */
getEffects(battle: Battle, ship: Ship, target: Target): [Ship, BaseEffect][] { getEffects(ship: Ship, target: Target): [Ship, BaseEffect][] {
let result: [Ship, BaseEffect][] = []; let result: [Ship, BaseEffect][] = [];
let blast = this.getBlastRadius(ship); let blast = this.getBlastRadius(ship);
let ships = blast ? battle.collectShipsInCircle(target, blast, true) : ((target.ship && target.ship.alive) ? [target.ship] : []); let battle = ship.getBattle();
let ships = (blast && battle) ? battle.collectShipsInCircle(target, blast, true) : ((target.ship && target.ship.alive) ? [target.ship] : []);
ships.forEach(ship => { ships.forEach(ship => {
this.equipment.target_effects.forEach(effect => result.push([ship, effect])); this.equipment.target_effects.forEach(effect => result.push([ship, effect]));
}); });
@ -58,7 +62,7 @@ module TS.SpaceTac {
ship.addBattleEvent(new FireEvent(ship, this.equipment, target)); ship.addBattleEvent(new FireEvent(ship, this.equipment, target));
// Apply effects // Apply effects
let effects = this.getEffects(ship.getBattle(), ship, target); let effects = this.getEffects(ship, target);
effects.forEach(([ship, effect]) => effect.applyOnShip(ship)); effects.forEach(([ship, effect]) => effect.applyOnShip(ship));
} }
} }

View file

@ -29,7 +29,7 @@ module TS.SpaceTac {
it("forbids targetting a ship", function () { it("forbids targetting a ship", function () {
var ship1 = new Ship(null, "Test1"); var ship1 = new Ship(null, "Test1");
var ship2 = new Ship(null, "Test2"); var ship2 = new Ship(null, "Test2");
var action = new MoveAction(null); var action = new MoveAction(new Equipment());
var result = action.checkTarget(ship1, Target.newFromShip(ship1)); var result = action.checkTarget(ship1, Target.newFromShip(ship1));
expect(result).toBeNull(); expect(result).toBeNull();
@ -74,9 +74,10 @@ module TS.SpaceTac {
expect(battle.log.events[1].code).toEqual("move"); expect(battle.log.events[1].code).toEqual("move");
expect(battle.log.events[1].ship).toBe(ship); expect(battle.log.events[1].ship).toBe(ship);
expect(battle.log.events[1].target.ship).toBeNull(); let target: any = battle.log.events[1].target;
expect(battle.log.events[1].target.x).toBeCloseTo(3.535533, 0.00001); expect(target.ship).toBeNull();
expect(battle.log.events[1].target.y).toBeCloseTo(3.535533, 0.00001); expect(target.x).toBeCloseTo(3.535533, 0.00001);
expect(target.y).toBeCloseTo(3.535533, 0.00001);
}); });
it("can't move too much near another ship", function () { it("can't move too much near another ship", function () {

View file

@ -5,13 +5,16 @@ module TS.SpaceTac {
// Safety distance from other ships // Safety distance from other ships
safety_distance: number; safety_distance: number;
// Equipment cannot be null (engine)
equipment: Equipment;
constructor(equipment: Equipment) { constructor(equipment: Equipment) {
super("move", "Move", true, equipment); super("move", "Move", true, equipment);
this.safety_distance = 50; this.safety_distance = 50;
} }
checkCannotBeApplied(ship: Ship, remaining_ap: number = null): string | null { checkCannotBeApplied(ship: Ship, remaining_ap: number | null = null): string | null {
let base = super.checkCannotBeApplied(ship, Infinity); let base = super.checkCannotBeApplied(ship, Infinity);
if (base) { if (base) {
return base; return base;

View file

@ -76,18 +76,23 @@ module TS.SpaceTac {
// console.debug(`Turn ${battle.turn} - Ship ${battle.play_order.indexOf(playing)} - Player ${battle.fleets.indexOf(playing.fleet)}`); // console.debug(`Turn ${battle.turn} - Ship ${battle.play_order.indexOf(playing)} - Player ${battle.fleets.indexOf(playing.fleet)}`);
let ai = (playing.fleet == battle.fleets[0]) ? this.ai1 : this.ai2; if (playing) {
ai.timer = Timer.synchronous; let ai = (playing.fleet == battle.fleets[0]) ? this.ai1 : this.ai2;
ai.ship = playing; ai.timer = Timer.synchronous;
ai.play(); ai.ship = playing;
ai.play();
} else {
console.error("No ship playing");
break;
}
if (!battle.ended && battle.playing_ship == playing) { if (!battle.ended && battle.playing_ship == playing) {
console.error(`${ai.name} did not end its turn !`); console.error("AI did not end its turn !");
battle.advanceToNextShip(); battle.advanceToNextShip();
} }
} }
if (battle.ended && !battle.outcome.draw) { if (battle.ended && !battle.outcome.draw && battle.outcome.winner) {
this.update(battle.fleets.indexOf(battle.outcome.winner)); this.update(battle.fleets.indexOf(battle.outcome.winner));
} else { } else {
this.update(-1); this.update(-1);
@ -101,7 +106,8 @@ module TS.SpaceTac {
* Setup the duel HTML page * Setup the duel HTML page
*/ */
static setup(element: HTMLElement) { static setup(element: HTMLElement) {
let ais = [new BullyAI(null), new TacticalAI(null), new AbstractAI(null)]; let fakeship = new Ship();
let ais = [new BullyAI(fakeship), new TacticalAI(fakeship), new AbstractAI(fakeship)];
ais.forEach((ai, idx) => { ais.forEach((ai, idx) => {
let selects = element.getElementsByTagName("select"); let selects = element.getElementsByTagName("select");
for (let i = 0; i < selects.length; i++) { for (let i = 0; i < selects.length; i++) {
@ -122,11 +128,12 @@ module TS.SpaceTac {
console.clear(); console.clear();
let ai1 = parseInt(element.getElementsByTagName("select").item(0).value); let ai1 = parseInt(element.getElementsByTagName("select").item(0).value);
let ai2 = parseInt(element.getElementsByTagName("select").item(1).value); let ai2 = parseInt(element.getElementsByTagName("select").item(1).value);
AIDuel.current = new AIDuel(ais[ai1], ais[ai2]); let duel = new AIDuel(ais[ai1], ais[ai2]);
AIDuel.current.start(() => { AIDuel.current = duel;
element.getElementsByClassName("win1").item(0).textContent = AIDuel.current.win1.toString(); duel.start(() => {
element.getElementsByClassName("win2").item(0).textContent = AIDuel.current.win2.toString(); element.getElementsByClassName("win1").item(0).textContent = duel.win1.toString();
element.getElementsByClassName("draw").item(0).textContent = AIDuel.current.draw.toString(); element.getElementsByClassName("win2").item(0).textContent = duel.win2.toString();
element.getElementsByClassName("draw").item(0).textContent = duel.draw.toString();
}); });
button.textContent = "Stop !"; button.textContent = "Stop !";
} }

View file

@ -22,7 +22,7 @@ module TS.SpaceTac {
// When the queue is empty, the ship will end its turn. // When the queue is empty, the ship will end its turn.
private workqueue: Function[]; private workqueue: Function[];
constructor(ship: Ship, timer = Timer.global, name: string = null) { constructor(ship: Ship, timer = Timer.global, name?: string) {
this.name = name || classname(this); this.name = name || classname(this);
this.ship = ship; this.ship = ship;
this.workqueue = []; this.workqueue = [];
@ -52,7 +52,7 @@ module TS.SpaceTac {
} }
// Add a work item to the work queue // Add a work item to the work queue
addWorkItem(item: Function, delay = 100): void { addWorkItem(item: Function | null, delay = 100): void {
if (this.timer.isSynchronous()) { if (this.timer.isSynchronous()) {
if (item) { if (item) {
item(); item();
@ -90,7 +90,9 @@ module TS.SpaceTac {
} else { } else {
// Take the first item // Take the first item
var item = this.workqueue.shift(); var item = this.workqueue.shift();
item(); if (item) {
item();
}
} }
} else { } else {
this.endTurn(); this.endTurn();
@ -108,7 +110,6 @@ module TS.SpaceTac {
if (this.ship.playing) { if (this.ship.playing) {
let battle = this.ship.getBattle(); let battle = this.ship.getBattle();
this.ship.endTurn(); this.ship.endTurn();
this.ship = null;
if (battle) { if (battle) {
battle.advanceToNextShip(); battle.advanceToNextShip();
} }

View file

@ -56,9 +56,13 @@ module TS.SpaceTac.Specs {
enemy.arena_x = 3; enemy.arena_x = 3;
enemy.arena_y = 0; enemy.arena_y = 0;
var result = ai.checkBullyManeuver(enemy, weapon); var result = ai.checkBullyManeuver(enemy, weapon);
expect(result.simulation.need_move).toBe(false); if (result) {
expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy)); expect(result.simulation.need_move).toBe(false);
expect(result.equipment).toBe(weapon); expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy));
expect(result.equipment).toBe(weapon);
} else {
fail("No maneuver proposed");
}
// enemy out of range, but moving can bring it in range // enemy out of range, but moving can bring it in range
ship.values.power.set(8); ship.values.power.set(8);
@ -67,9 +71,13 @@ module TS.SpaceTac.Specs {
enemy.arena_x = 6; enemy.arena_x = 6;
enemy.arena_y = 0; enemy.arena_y = 0;
result = ai.checkBullyManeuver(enemy, weapon); result = ai.checkBullyManeuver(enemy, weapon);
expect(result.simulation.move_location).toEqual(Target.newFromLocation(3, 0)); if (result) {
expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy)); expect(result.simulation.move_location).toEqual(Target.newFromLocation(3, 0));
expect(result.equipment).toBe(weapon); expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy));
expect(result.equipment).toBe(weapon);
} else {
fail("No maneuver proposed");
}
// enemy out of range, but moving can bring it in range, except for the safety margin // enemy out of range, but moving can bring it in range, except for the safety margin
ai.move_margin = 0.1; ai.move_margin = 0.1;
@ -110,7 +118,7 @@ module TS.SpaceTac.Specs {
expect(result).toBeNull(); expect(result).toBeNull();
// no engine, can't move // no engine, can't move
ship.slots[0].attached.detach(); engine.detach();
ship.values.power.set(8); ship.values.power.set(8);
ship.arena_x = 1; ship.arena_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
@ -155,7 +163,7 @@ module TS.SpaceTac.Specs {
var engine = TestTools.addEngine(ai.ship, 100); var engine = TestTools.addEngine(ai.ship, 100);
(<MoveAction>engine.action).safety_distance = 20; (<MoveAction>engine.action).safety_distance = 20;
var maneuver: BullyManeuver; var maneuver: BullyManeuver | null;
battle.fleets[1].ships.forEach((ship: Ship) => { battle.fleets[1].ships.forEach((ship: Ship) => {
ai.ship.setArenaPosition(0, 0); ai.ship.setArenaPosition(0, 0);
@ -172,10 +180,18 @@ module TS.SpaceTac.Specs {
// Move towards an enemy (up to minimal distance) // Move towards an enemy (up to minimal distance)
ai.ship.setArenaPosition(30, 0); ai.ship.setArenaPosition(30, 0);
maneuver = ai.getFallbackManeuver(); maneuver = ai.getFallbackManeuver();
expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(25, 0)); if (maneuver) {
expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(25, 0));
} else {
fail("No maneuver proposed");
}
ai.ship.setArenaPosition(25, 0); ai.ship.setArenaPosition(25, 0);
maneuver = ai.getFallbackManeuver(); maneuver = ai.getFallbackManeuver();
expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(22.5, 0)); if (maneuver) {
expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(22.5, 0));
} else {
fail("No maneuver proposed");
}
}); });
it("applies the chosen move", function () { it("applies the chosen move", function () {

View file

@ -18,7 +18,7 @@ module TS.SpaceTac {
if (this.ship.getValue("power") > 0) { if (this.ship.getValue("power") > 0) {
this.addWorkItem(() => { this.addWorkItem(() => {
var maneuvers = this.listAllManeuvers(); var maneuvers = this.listAllManeuvers();
var maneuver: BullyManeuver; var maneuver: BullyManeuver | null;
if (maneuvers.length > 0) { if (maneuvers.length > 0) {
maneuver = this.pickManeuver(maneuvers); maneuver = this.pickManeuver(maneuvers);
@ -39,11 +39,14 @@ module TS.SpaceTac {
listAllEnemies(): Ship[] { listAllEnemies(): Ship[] {
var result: Ship[] = []; var result: Ship[] = [];
this.ship.getBattle().play_order.forEach((ship: Ship) => { let battle = this.ship.getBattle();
if (ship.alive && ship.getPlayer() !== this.ship.getPlayer()) { if (battle) {
result.push(ship); battle.play_order.forEach((ship: Ship) => {
} if (ship.alive && ship.getPlayer() !== this.ship.getPlayer()) {
}); result.push(ship);
}
});
}
return result; return result;
} }
@ -73,7 +76,7 @@ module TS.SpaceTac {
} }
// Get an equipped engine to make a move // Get an equipped engine to make a move
getEngine(): Equipment { getEngine(): Equipment | null {
var engines = this.ship.listEquipment(SlotType.Engine); var engines = this.ship.listEquipment(SlotType.Engine);
if (engines.length === 0) { if (engines.length === 0) {
return null; return null;
@ -95,7 +98,7 @@ module TS.SpaceTac {
} }
// When no bully action is available, pick a random enemy, and go towards it // When no bully action is available, pick a random enemy, and go towards it
getFallbackManeuver(): BullyManeuver { getFallbackManeuver(): BullyManeuver | null {
var enemies = this.listAllEnemies(); var enemies = this.listAllEnemies();
if (enemies.length === 0) { if (enemies.length === 0) {
return null; return null;
@ -107,12 +110,20 @@ module TS.SpaceTac {
var target = Target.newFromShip(picked); var target = Target.newFromShip(picked);
var distance = target.getDistanceTo(Target.newFromShip(this.ship)); var distance = target.getDistanceTo(Target.newFromShip(this.ship));
var engine = this.getEngine(); var engine = this.getEngine();
var safety_distance = (<MoveAction>engine.action).safety_distance; if (engine) {
if (distance > safety_distance) { // Don't move too close var safety_distance = (<MoveAction>engine.action).safety_distance;
target = target.constraintInRange(this.ship.arena_x, this.ship.arena_y, if (distance > safety_distance) { // Don't move too close
(distance - safety_distance) * APPROACH_FACTOR); target = target.constraintInRange(this.ship.arena_x, this.ship.arena_y,
target = engine.action.checkLocationTarget(this.ship, target); (distance - safety_distance) * APPROACH_FACTOR);
return new BullyManeuver(this.ship, engine, target); let loctarget = engine.action.checkLocationTarget(this.ship, target);
if (loctarget) {
return new BullyManeuver(this.ship, engine, loctarget);
} else {
return null;
}
} else {
return null;
}
} else { } else {
return null; return null;
} }
@ -120,7 +131,7 @@ module TS.SpaceTac {
// Pick a maneuver from a list of available ones // Pick a maneuver from a list of available ones
// By default, it chooses the nearest enemy // By default, it chooses the nearest enemy
pickManeuver(available: BullyManeuver[]): BullyManeuver { pickManeuver(available: BullyManeuver[]): BullyManeuver | null {
if (available.length === 0) { if (available.length === 0) {
return null; return null;
} }
@ -134,7 +145,7 @@ module TS.SpaceTac {
} }
// Effectively apply the chosen maneuver // Effectively apply the chosen maneuver
applyManeuver(maneuver: BullyManeuver): void { applyManeuver(maneuver: BullyManeuver | null): void {
if (maneuver) { if (maneuver) {
this.addWorkItem(() => { this.addWorkItem(() => {
maneuver.apply(); maneuver.apply();

View file

@ -15,7 +15,7 @@ module TS.SpaceTac.Specs {
// producer of FixedManeuver from a list of scores // producer of FixedManeuver from a list of scores
let producer = (...scores: number[]) => imap(iarray(scores), score => new FixedManeuver(score)); let producer = (...scores: number[]) => imap(iarray(scores), score => new FixedManeuver(score));
let applied = []; let applied: number[] = [];
beforeEach(function () { beforeEach(function () {
applied = []; applied = [];

View file

@ -52,12 +52,16 @@ module TS.SpaceTac {
while (done < 1000 && this.producers.length > 0) { while (done < 1000 && this.producers.length > 0) {
// Produce a maneuver // Produce a maneuver
let maneuver: Maneuver; let maneuver: Maneuver | null = null;
let producer = this.producers.shift(); let producer = this.producers.shift();
[maneuver, producer] = producer(); if (producer) {
[maneuver, producer] = producer();
}
if (maneuver) { if (maneuver) {
this.producers.push(producer); if (producer) {
this.producers.push(producer);
}
// Evaluate the maneuver // Evaluate the maneuver
let score = this.evaluate(maneuver); let score = this.evaluate(maneuver);
@ -88,7 +92,7 @@ module TS.SpaceTac {
TacticalAIHelpers.produceBlastShots, TacticalAIHelpers.produceBlastShots,
TacticalAIHelpers.produceRandomMoves, TacticalAIHelpers.produceRandomMoves,
] ]
producers.forEach(producer => this.producers.push(producer(this.ship, this.ship.getBattle()))); producers.forEach(producer => this.producers.push(producer(this.ship, this.ship.getBattle() || new Battle())));
} }
/** /**
@ -101,6 +105,7 @@ module TS.SpaceTac {
scaled(TacticalAIHelpers.evaluateDamageToEnemy, 30), scaled(TacticalAIHelpers.evaluateDamageToEnemy, 30),
scaled(TacticalAIHelpers.evaluateClustering, 3), scaled(TacticalAIHelpers.evaluateClustering, 3),
] ]
// TODO evaluator typing is lost
evaluators.forEach(evaluator => this.evaluators.push((maneuver: Maneuver) => evaluator(this.ship, this.ship.getBattle(), maneuver))); evaluators.forEach(evaluator => this.evaluators.push((maneuver: Maneuver) => evaluator(this.ship, this.ship.getBattle(), maneuver)));
} }
} }

View file

@ -72,7 +72,7 @@ module TS.SpaceTac {
} }
let damage = 0; let damage = 0;
let dead = 0; let dead = 0;
let effects = action.getEffects(battle, ship, maneuver.target); let effects = action.getEffects(ship, maneuver.target);
effects.forEach(([ship, effect]) => { effects.forEach(([ship, effect]) => {
if (effect instanceof DamageEffect && contains(enemies, ship)) { if (effect instanceof DamageEffect && contains(enemies, ship)) {
let [shield, hull] = effect.getEffectiveDamage(ship); let [shield, hull] = effect.getEffectiveDamage(ship);

View file

@ -14,21 +14,21 @@ module TS.SpaceTac.Equipments {
* *
* Be aware that *min_distance* means the MAXIMAL reachable distance, but on a low-power loot ! * Be aware that *min_distance* means the MAXIMAL reachable distance, but on a low-power loot !
*/ */
setDeployDistance(min_distance: number, max_distance: number = null): void { setDeployDistance(min_distance: number, max_distance: number | null = null): void {
this.distance = new Range(min_distance, max_distance); this.distance = new Range(min_distance, max_distance);
} }
/** /**
* Set the effect radius of the deployed drone * Set the effect radius of the deployed drone
*/ */
setEffectRadius(min_radius: number, max_radius: number = null): void { setEffectRadius(min_radius: number, max_radius: number | null = null): void {
this.blast = new IntegerRange(min_radius, max_radius); this.blast = new IntegerRange(min_radius, max_radius);
} }
/** /**
* Set the drone lifetime * Set the drone lifetime
*/ */
setLifetime(min_lifetime: number, max_lifetime: number = null): void { setLifetime(min_lifetime: number, max_lifetime: number | null = null): void {
this.duration = new IntegerRange(min_lifetime, max_lifetime); this.duration = new IntegerRange(min_lifetime, max_lifetime);
} }

View file

@ -6,7 +6,7 @@ module TS.SpaceTac.Equipments {
// Boolean set to true if the weapon can target space // Boolean set to true if the weapon can target space
can_target_space: boolean; can_target_space: boolean;
constructor(name: string, min_damage: number = 0, max_damage: number = null) { constructor(name: string, min_damage: number = 0, max_damage: number | null = null) {
super(SlotType.Weapon, name); super(SlotType.Weapon, name);
this.can_target_space = false; this.can_target_space = false;
@ -18,13 +18,13 @@ module TS.SpaceTac.Equipments {
// Set the range for this weapon // Set the range for this weapon
// Pay attention that *min_distance* means the MAXIMAL reachable distance, but on a low-power loot // Pay attention that *min_distance* means the MAXIMAL reachable distance, but on a low-power loot
setRange(min_distance: number, max_distance: number = null, can_target_space: boolean = false): void { setRange(min_distance: number, max_distance: number | null = null, can_target_space = false): void {
this.distance = new Range(min_distance, max_distance); this.distance = new Range(min_distance, max_distance);
this.can_target_space = can_target_space; this.can_target_space = can_target_space;
} }
// Set the effect radius (blast) for this weapon // Set the effect radius (blast) for this weapon
setBlast(min_blast: number, max_blast: number = null): void { setBlast(min_blast: number, max_blast: number | null = null): void {
this.blast = new IntegerRange(min_blast, max_blast); this.blast = new IntegerRange(min_blast, max_blast);
} }

View file

@ -5,18 +5,36 @@ module TS.SpaceTac {
code: string; code: string;
// The ship causing the event (the one whose turn it is to play) // The ship causing the event (the one whose turn it is to play)
ship: Ship; ship: Ship | null;
// Target of the event // Target of the event
target: Target; target: Target | null;
// Boolean at true if the event is used to set initial battle conditions // Boolean at true if the event is used to set initial battle conditions
initial = false; initial = false;
constructor(code: string, ship: Ship = null, target: Target = null) { constructor(code: string, ship: Ship | null = null, target: Target | null = null) {
this.code = code; this.code = code;
this.ship = ship; this.ship = ship;
this.target = target; this.target = target;
} }
} }
// Base class for a BattleLog event linked to a ship
export class BaseLogShipEvent extends BaseLogEvent {
ship: Ship;
constructor(code: string, ship: Ship, target: Target | null = null) {
super(code, ship, target);
}
}
// Base class for a BattleLog event linked to a ship, and with a target
export class BaseLogShipTargetEvent extends BaseLogShipEvent {
target: Target;
constructor(code: string, ship: Ship, target: Target) {
super(code, ship, target);
}
}
} }

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a ship takes damage // Event logged when a ship takes damage
export class DamageEvent extends BaseLogEvent { export class DamageEvent extends BaseLogShipEvent {
// Damage to hull // Damage to hull
hull: number; hull: number;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a ship is dead // Event logged when a ship is dead
export class DeathEvent extends BaseLogEvent { export class DeathEvent extends BaseLogShipEvent {
constructor(ship: Ship) { constructor(ship: Ship) {
super("death", ship); super("death", ship);
} }

View file

@ -4,7 +4,7 @@ module TS.SpaceTac {
/** /**
* Event logged when a drone applies its effects * Event logged when a drone applies its effects
*/ */
export class DroneAppliedEvent extends BaseLogEvent { export class DroneAppliedEvent extends BaseLogShipEvent {
// Pointer to the drone // Pointer to the drone
drone: Drone; drone: Drone;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a drone is deployed by a ship // Event logged when a drone is deployed by a ship
export class DroneDeployedEvent extends BaseLogEvent { export class DroneDeployedEvent extends BaseLogShipEvent {
// Pointer to the drone // Pointer to the drone
drone: Drone; drone: Drone;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a drone is destroyed // Event logged when a drone is destroyed
export class DroneDestroyedEvent extends BaseLogEvent { export class DroneDestroyedEvent extends BaseLogShipEvent {
// Pointer to the drone // Pointer to the drone
drone: Drone; drone: Drone;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a sticky effect is added to a ship // Event logged when a sticky effect is added to a ship
export class EffectAddedEvent extends BaseLogEvent { export class EffectAddedEvent extends BaseLogShipEvent {
// Pointer to the effect // Pointer to the effect
effect: StickyEffect; effect: StickyEffect;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a sticky effect is added to a ship // Event logged when a sticky effect is added to a ship
export class EffectDurationChangedEvent extends BaseLogEvent { export class EffectDurationChangedEvent extends BaseLogShipEvent {
// Pointer to the effect // Pointer to the effect
effect: StickyEffect; effect: StickyEffect;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a sticky effect is removed from a ship // Event logged when a sticky effect is removed from a ship
export class EffectRemovedEvent extends BaseLogEvent { export class EffectRemovedEvent extends BaseLogShipEvent {
// Pointer to the effect // Pointer to the effect
effect: StickyEffect; effect: StickyEffect;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a weapon is used on a target // Event logged when a weapon is used on a target
export class FireEvent extends BaseLogEvent { export class FireEvent extends BaseLogShipTargetEvent {
// Weapon used // Weapon used
weapon: Equipment; weapon: Equipment;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a ship moves // Event logged when a ship moves
export class MoveEvent extends BaseLogEvent { export class MoveEvent extends BaseLogShipTargetEvent {
// New facing angle, in radians // New facing angle, in radians
facing_angle: number; facing_angle: number;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Battle event, when a ship turn ended, and advanced to a new one // Battle event, when a ship turn ended, and advanced to a new one
export class ShipChangeEvent extends BaseLogEvent { export class ShipChangeEvent extends BaseLogShipEvent {
// Ship that starts playing // Ship that starts playing
new_ship: Ship; new_ship: Ship;

View file

@ -2,7 +2,7 @@
module TS.SpaceTac { module TS.SpaceTac {
// Event logged when a ship value or attribute changed // Event logged when a ship value or attribute changed
export class ValueChangeEvent extends BaseLogEvent { export class ValueChangeEvent extends BaseLogShipEvent {
// Saved version of the current value // Saved version of the current value
value: ShipValue; value: ShipValue;

View file

@ -45,12 +45,11 @@ module TS.SpaceTac.UI.Specs {
afterEach(function () { afterEach(function () {
let ui = testgame.ui; let ui = testgame.ui;
window.requestAnimationFrame(() => ui.destroy()); window.requestAnimationFrame(() => {
if (ui) {
testgame.ui = null; ui.destroy();
testgame.baseview = null; }
testgame.battleview = null; });
testgame.mapview = null;
}); });
return testgame; return testgame;
@ -74,7 +73,7 @@ module TS.SpaceTac.UI.Specs {
testgame.battleview = new BattleView(); testgame.battleview = new BattleView();
let battle = Battle.newQuickRandom(); let battle = Battle.newQuickRandom();
let player = battle.playing_ship.getPlayer(); let player = battle.playing_ship ? battle.playing_ship.getPlayer() : new Player();
return [testgame.battleview, [player, battle]]; return [testgame.battleview, [player, battle]];
}); });

View file

@ -18,7 +18,7 @@ module TS.SpaceTac.UI {
icon_waiting: Phaser.Image; icon_waiting: Phaser.Image;
// Current ship, whose actions are displayed // Current ship, whose actions are displayed
ship: Ship; ship: Ship | null;
ship_power_capacity: number; ship_power_capacity: number;
ship_power_value: number; ship_power_value: number;
@ -170,7 +170,7 @@ module TS.SpaceTac.UI {
* *power_usage* is the consumption of currently selected action. * *power_usage* is the consumption of currently selected action.
*/ */
updateSelectedActionPower(power_usage: number): void { updateSelectedActionPower(power_usage: number): void {
var remaining_ap = this.ship.values.power.get() - power_usage; var remaining_ap = this.ship ? (this.ship.values.power.get() - power_usage) : 0;
if (remaining_ap < 0) { if (remaining_ap < 0) {
remaining_ap = 0; remaining_ap = 0;
} }

View file

@ -23,7 +23,7 @@ module TS.SpaceTac.UI {
fading: boolean; fading: boolean;
// Current targetting // Current targetting
private targetting: Targetting; private targetting: Targetting | null;
// Action icon - image representing the action // Action icon - image representing the action
private layer_icon: Phaser.Image; private layer_icon: Phaser.Image;
@ -101,7 +101,9 @@ module TS.SpaceTac.UI {
this.bar.actionStarted(); this.bar.actionStarted();
// Update range hint // Update range hint
this.battleview.arena.range_hint.setPrimary(this.ship, this.action); if (this.battleview.arena.range_hint) {
this.battleview.arena.range_hint.setPrimary(this.ship, this.action);
}
// Update fading statuses // Update fading statuses
this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.ship, null)); this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.ship, null));
@ -110,13 +112,18 @@ module TS.SpaceTac.UI {
this.setSelected(true); this.setSelected(true);
if (this.action.needs_target) { if (this.action.needs_target) {
// Switch to targetting mode (will apply action when a target is selected) let sprite = this.battleview.arena.findShipSprite(this.ship);
this.targetting = this.battleview.enterTargettingMode(); if (sprite) {
this.targetting.setSource(this.battleview.arena.findShipSprite(this.ship)); // Switch to targetting mode (will apply action when a target is selected)
this.targetting.targetSelected.add(this.processSelection, this); this.targetting = this.battleview.enterTargettingMode();
this.targetting.targetHovered.add(this.processHover, this); if (this.targetting) {
if (this.action instanceof MoveAction) { this.targetting.setSource(sprite);
this.targetting.setApIndicatorsInterval(this.action.getDistanceByActionPoint(this.ship)); this.targetting.targetSelected.add(this.processSelection, this);
this.targetting.targetHovered.add(this.processHover, this);
if (this.action instanceof MoveAction) {
this.targetting.setApIndicatorsInterval(this.action.getDistanceByActionPoint(this.ship));
}
}
} }
} else { } else {
// No target needed, apply action immediately // No target needed, apply action immediately
@ -127,13 +134,15 @@ module TS.SpaceTac.UI {
// Called when a target is hovered // Called when a target is hovered
// This will check the target against current action and adjust it if needed // This will check the target against current action and adjust it if needed
processHover(target: Target): void { processHover(target: Target): void {
target = this.action.checkTarget(this.ship, target); let correct_target = this.action.checkTarget(this.ship, target);
this.targetting.setTarget(target, false, this.action.getBlastRadius(this.ship)); if (this.targetting) {
this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.ship, target)); this.targetting.setTarget(correct_target, false, this.action.getBlastRadius(this.ship));
}
this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.ship, correct_target));
} }
// Called when a target is selected // Called when a target is selected
processSelection(target: Target): void { processSelection(target: Target | null): void {
if (this.action.apply(this.ship, target)) { if (this.action.apply(this.ship, target)) {
this.bar.actionEnded(); this.bar.actionEnded();
} }

View file

@ -10,13 +10,14 @@ module TS.SpaceTac.UI.Specs {
let tooltip = bar.tooltip; let tooltip = bar.tooltip;
bar.clearAll(); bar.clearAll();
let a1 = bar.addAction(battleview.battle.playing_ship, new MoveAction(new Equipment())); let ship = nn(battleview.battle.playing_ship);
a1.action.equipment.name = "Engine"; let a1 = bar.addAction(ship, new MoveAction(new Equipment()));
nn(a1.action.equipment).name = "Engine";
a1.action.name = "Move"; a1.action.name = "Move";
let a2 = bar.addAction(battleview.battle.playing_ship, new FireWeaponAction(new Equipment())); let a2 = bar.addAction(ship, new FireWeaponAction(new Equipment()));
a2.action.equipment.name = "Weapon"; nn(a2.action.equipment).name = "Weapon";
a2.action.name = "Fire"; a2.action.name = "Fire";
let a3 = bar.addAction(battleview.battle.playing_ship, new EndTurnAction()); let a3 = bar.addAction(ship, new EndTurnAction());
a3.action.name = "End turn"; a3.action.name = "End turn";
tooltip.setAction(a1); tooltip.setAction(a1);

View file

@ -39,7 +39,7 @@ module TS.SpaceTac.UI {
} }
// Set current action to display, null to hide // Set current action to display, null to hide
setAction(action: ActionIcon): void { setAction(action: ActionIcon | null): void {
if (action) { if (action) {
if (this.icon) { if (this.icon) {
this.icon.destroy(true); this.icon.destroy(true);

View file

@ -21,9 +21,9 @@ module TS.SpaceTac.UI {
private drone_sprites: ArenaDrone[] = []; private drone_sprites: ArenaDrone[] = [];
// Currently hovered ship // Currently hovered ship
private hovered: ArenaShip; private hovered: ArenaShip | null;
// Currently playing ship // Currently playing ship
private playing: ArenaShip; private playing: ArenaShip | null;
// Layer for particles // Layer for particles
layer_weapon_effects: Phaser.Group; layer_weapon_effects: Phaser.Group;
@ -35,7 +35,7 @@ module TS.SpaceTac.UI {
this.battleview = battleview; this.battleview = battleview;
this.playing = null; this.playing = null;
this.hovered = null; this.hovered = null;
this.range_hint = null; this.range_hint = new RangeHint(this);
var offset_x = 133; var offset_x = 133;
var offset_y = 132; var offset_y = 132;
@ -60,9 +60,8 @@ module TS.SpaceTac.UI {
}, null); }, null);
this.position.set(offset_x, offset_y); this.position.set(offset_x, offset_y);
this.addChild(this.background);
this.range_hint = new RangeHint(this); this.addChild(this.background);
this.addChild(this.range_hint); this.addChild(this.range_hint);
this.init(); this.init();
@ -100,8 +99,8 @@ module TS.SpaceTac.UI {
} }
// Find the sprite for a ship // Find the sprite for a ship
findShipSprite(ship: Ship): ArenaShip { findShipSprite(ship: Ship): ArenaShip | null {
var result: ArenaShip = null; var result: ArenaShip | null = null;
this.ship_sprites.forEach((sprite: ArenaShip) => { this.ship_sprites.forEach((sprite: ArenaShip) => {
if (sprite.ship === ship) { if (sprite.ship === ship) {
result = sprite; result = sprite;
@ -111,27 +110,36 @@ module TS.SpaceTac.UI {
} }
// Set the hovered state on a ship sprite // Set the hovered state on a ship sprite
setShipHovered(ship: Ship): void { setShipHovered(ship: Ship | null): void {
if (this.hovered) { if (this.hovered) {
this.hovered.setHovered(false); this.hovered.setHovered(false);
} }
var arena_ship = this.findShipSprite(ship);
if (arena_ship) { if (ship) {
arena_ship.setHovered(true); var arena_ship = this.findShipSprite(ship);
if (arena_ship) {
arena_ship.setHovered(true);
}
this.hovered = arena_ship;
} else {
this.hovered = null;
} }
this.hovered = arena_ship;
} }
// Set the playing state on a ship sprite // Set the playing state on a ship sprite
setShipPlaying(ship: Ship): void { setShipPlaying(ship: Ship | null): void {
if (this.playing) { if (this.playing) {
this.playing.setPlaying(false); this.playing.setPlaying(false);
this.playing = null;
} }
var arena_ship = this.findShipSprite(ship);
if (arena_ship) { if (ship) {
arena_ship.setPlaying(true); var arena_ship = this.findShipSprite(ship);
if (arena_ship) {
arena_ship.setPlaying(true);
}
this.playing = arena_ship;
} }
this.playing = arena_ship;
this.battleview.gameui.audio.playOnce("battle-ship-change"); this.battleview.gameui.audio.playOnce("battle-ship-change");
} }

View file

@ -5,8 +5,8 @@ module TS.SpaceTac.UI.Specs {
let testgame = setupBattleview(); let testgame = setupBattleview();
it("adds effects display", function () { it("adds effects display", function () {
let ship = testgame.battleview.battle.playing_ship; let ship = nn(testgame.battleview.battle.playing_ship);
let sprite = testgame.battleview.arena.findShipSprite(ship); let sprite = nn(testgame.battleview.arena.findShipSprite(ship));
expect(sprite.effects.children.length).toBe(0); expect(sprite.effects.children.length).toBe(0);

View file

@ -13,7 +13,7 @@ module TS.SpaceTac.UI.Specs {
expect(battleview.targetting).toBeNull(); expect(battleview.targetting).toBeNull();
// Enter targetting mode // Enter targetting mode
var result = battleview.enterTargettingMode(); var result = nn(battleview.enterTargettingMode());
expect(battleview.targetting).toBeTruthy(); expect(battleview.targetting).toBeTruthy();
expect(result).toBe(battleview.targetting); expect(result).toBe(battleview.targetting);
@ -32,7 +32,7 @@ module TS.SpaceTac.UI.Specs {
battleview.cursorInSpace(8, 4); battleview.cursorInSpace(8, 4);
expect(battleview.ship_hovered).toBeNull(); expect(battleview.ship_hovered).toBeNull();
expect(battleview.targetting.target_corrected).toEqual(Target.newFromLocation(8, 4)); expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromLocation(8, 4));
// Process a click on space // Process a click on space
battleview.cursorClicked(); battleview.cursorClicked();
@ -40,20 +40,20 @@ module TS.SpaceTac.UI.Specs {
// Forward ship hovering // Forward ship hovering
battleview.cursorOnShip(battleview.battle.play_order[0]); battleview.cursorOnShip(battleview.battle.play_order[0]);
expect(battleview.ship_hovered).toEqual(battleview.battle.playing_ship); expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
expect(battleview.targetting.target_corrected).toEqual(Target.newFromShip(battleview.battle.playing_ship)); expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
// Don't leave a ship we're not hovering // Don't leave a ship we're not hovering
battleview.cursorOffShip(battleview.battle.play_order[1]); battleview.cursorOffShip(battleview.battle.play_order[1]);
expect(battleview.ship_hovered).toEqual(battleview.battle.playing_ship); expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
expect(battleview.targetting.target_corrected).toEqual(Target.newFromShip(battleview.battle.playing_ship)); expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
// Don't move in space while on ship // Don't move in space while on ship
battleview.cursorInSpace(1, 3); battleview.cursorInSpace(1, 3);
expect(battleview.ship_hovered).toEqual(battleview.battle.playing_ship); expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
expect(battleview.targetting.target_corrected).toEqual(Target.newFromShip(battleview.battle.playing_ship)); expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
// Process a click on ship // Process a click on ship
battleview.cursorClicked(); battleview.cursorClicked();
@ -62,7 +62,7 @@ module TS.SpaceTac.UI.Specs {
battleview.cursorOffShip(battleview.battle.play_order[0]); battleview.cursorOffShip(battleview.battle.play_order[0]);
expect(battleview.ship_hovered).toBeNull(); expect(battleview.ship_hovered).toBeNull();
expect(battleview.targetting.target_corrected).toBeNull(); expect(nn(battleview.targetting).target_corrected).toBeNull();
// Quit targetting // Quit targetting
battleview.exitTargettingMode(); battleview.exitTargettingMode();
@ -73,7 +73,7 @@ module TS.SpaceTac.UI.Specs {
battleview.cursorInSpace(8, 4); battleview.cursorInSpace(8, 4);
expect(battleview.ship_hovered).toBeNull(); expect(battleview.ship_hovered).toBeNull();
battleview.cursorOnShip(battleview.battle.play_order[0]); battleview.cursorOnShip(battleview.battle.play_order[0]);
expect(battleview.ship_hovered).toEqual(battleview.battle.playing_ship); expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
// Quit twice don't do anything // Quit twice don't do anything
battleview.exitTargettingMode(); battleview.exitTargettingMode();
@ -81,12 +81,12 @@ module TS.SpaceTac.UI.Specs {
// Check collected targetting events // Check collected targetting events
expect(hovered).toEqual([ expect(hovered).toEqual([
Target.newFromLocation(8, 4), Target.newFromLocation(8, 4),
Target.newFromShip(battleview.battle.playing_ship), Target.newFromShip(battleview.battle.play_order[0]),
null null
]); ]);
expect(clicked).toEqual([ expect(clicked).toEqual([
Target.newFromLocation(8, 4), Target.newFromLocation(8, 4),
Target.newFromShip(battleview.battle.playing_ship), Target.newFromShip(battleview.battle.play_order[0]),
]); ]);
}); });
}); });

View file

@ -17,10 +17,10 @@ module TS.SpaceTac.UI {
arena: Arena; arena: Arena;
// Background image // Background image
background: Phaser.Image; background: Phaser.Image | null;
// Targetting mode (null if we're not in this mode) // Targetting mode (null if we're not in this mode)
targetting: Targetting; targetting: Targetting | null;
// Ship list // Ship list
ship_list: ShipList; ship_list: ShipList;
@ -29,7 +29,7 @@ module TS.SpaceTac.UI {
action_bar: ActionBar; action_bar: ActionBar;
// Currently hovered ship // Currently hovered ship
ship_hovered: Ship; ship_hovered: Ship | null;
// Ship tooltip // Ship tooltip
ship_tooltip: ShipTooltip; ship_tooltip: ShipTooltip;
@ -51,7 +51,6 @@ module TS.SpaceTac.UI {
this.battle = battle; this.battle = battle;
this.targetting = null; this.targetting = null;
this.ship_hovered = null; this.ship_hovered = null;
this.log_processor = null;
this.background = null; this.background = null;
this.battle.timer = this.timer; this.battle.timer = this.timer;
@ -97,7 +96,7 @@ module TS.SpaceTac.UI {
this.battle.endBattle(this.player.fleet); this.battle.endBattle(this.player.fleet);
}); });
this.inputs.bindCheat(Phaser.Keyboard.A, "Use AI to play", () => { this.inputs.bindCheat(Phaser.Keyboard.A, "Use AI to play", () => {
if (this.interacting) { if (this.interacting && this.battle.playing_ship) {
this.setInteractionEnabled(false); this.setInteractionEnabled(false);
this.battle.playAI(new TacticalAI(this.battle.playing_ship)); this.battle.playAI(new TacticalAI(this.battle.playing_ship));
} }
@ -111,22 +110,9 @@ module TS.SpaceTac.UI {
shutdown() { shutdown() {
this.exitTargettingMode(); this.exitTargettingMode();
if (this.log_processor) { this.log_processor.destroy();
this.log_processor.destroy(); this.ui.destroy();
this.log_processor = null; this.arena.destroy();
}
if (this.ui) {
this.ui.destroy();
this.ui = null;
}
if (this.arena) {
this.arena.destroy();
this.arena = null;
}
this.battle = null;
super.shutdown(); super.shutdown();
} }
@ -178,7 +164,7 @@ module TS.SpaceTac.UI {
} }
// Set the currently hovered ship // Set the currently hovered ship
setShipHovered(ship: Ship): void { setShipHovered(ship: Ship | null): void {
this.ship_hovered = ship; this.ship_hovered = ship;
this.arena.setShipHovered(ship); this.arena.setShipHovered(ship);
this.ship_list.setHovered(ship); this.ship_list.setHovered(ship);
@ -201,7 +187,7 @@ module TS.SpaceTac.UI {
// Enter targetting mode // Enter targetting mode
// While in this mode, the Targetting object will receive hover and click events, and handle them // While in this mode, the Targetting object will receive hover and click events, and handle them
enterTargettingMode(): Targetting { enterTargettingMode(): Targetting | null {
if (!this.interacting) { if (!this.interacting) {
return null; return null;
} }

View file

@ -101,7 +101,7 @@ module TS.SpaceTac.UI {
this.processDroneDestroyedEvent(event); this.processDroneDestroyedEvent(event);
} else if (event instanceof DroneAppliedEvent) { } else if (event instanceof DroneAppliedEvent) {
this.processDroneAppliedEvent(event); this.processDroneAppliedEvent(event);
} else if (event.code == "effectadd" || event.code == "effectduration" || event.code == "effectdel") { } else if (event instanceof EffectAddedEvent || event instanceof EffectRemovedEvent ||  event instanceof EffectDurationChangedEvent) {
this.processEffectEvent(event); this.processEffectEvent(event);
} }
} }
@ -116,8 +116,13 @@ module TS.SpaceTac.UI {
// Playing ship changed // Playing ship changed
private processShipChangeEvent(event: ShipChangeEvent): void { private processShipChangeEvent(event: ShipChangeEvent): void {
this.view.arena.setShipPlaying(event.target.ship); if (event.target && event.target.ship) {
this.view.ship_list.setPlaying(event.target.ship); this.view.arena.setShipPlaying(event.target.ship);
this.view.ship_list.setPlaying(event.target.ship);
} else {
this.view.arena.setShipPlaying(null);
this.view.ship_list.setPlaying(null);
}
if (this.battle.canPlay(this.view.player)) { if (this.battle.canPlay(this.view.player)) {
// Player turn // Player turn
@ -196,7 +201,7 @@ module TS.SpaceTac.UI {
private processEndBattleEvent(event: EndBattleEvent): void { private processEndBattleEvent(event: EndBattleEvent): void {
this.view.setInteractionEnabled(false); this.view.setInteractionEnabled(false);
if (event.outcome.winner.player === this.view.player) { if (event.outcome.winner && event.outcome.winner.player === this.view.player) {
// Victory ! // Victory !
// TODO Loot screen // TODO Loot screen
this.view.player.exitBattle(); this.view.player.exitBattle();
@ -207,7 +212,7 @@ module TS.SpaceTac.UI {
} }
// Sticky effect on ship added, changed or removed // Sticky effect on ship added, changed or removed
private processEffectEvent(event: BaseLogEvent): void { private processEffectEvent(event: EffectAddedEvent | EffectRemovedEvent | EffectDurationChangedEvent): void {
var item = this.view.ship_list.findItem(event.ship); var item = this.view.ship_list.findItem(event.ship);
if (item) { if (item) {
item.updateEffects(); item.updateEffects();

View file

@ -8,7 +8,7 @@ module TS.SpaceTac.UI {
circle: Phaser.Graphics; circle: Phaser.Graphics;
// Stored information of primary circle, when secondary one overrides it // Stored information of primary circle, when secondary one overrides it
primary: Phaser.Circle; primary: Phaser.Circle | null;
constructor(parent: Arena) { constructor(parent: Arena) {
super(parent.game, parent); super(parent.game, parent);

View file

@ -8,10 +8,10 @@ module TS.SpaceTac.UI {
ships: ShipListItem[]; ships: ShipListItem[];
// Playing ship // Playing ship
playing: ShipListItem; playing: ShipListItem | null;
// Hovered ship // Hovered ship
hovered: ShipListItem; hovered: ShipListItem | null;
// Create an empty action bar // Create an empty action bar
constructor(battleview: BattleView) { constructor(battleview: BattleView) {
@ -57,8 +57,8 @@ module TS.SpaceTac.UI {
// Find an item for a ship // Find an item for a ship
// Returns null if not found // Returns null if not found
findItem(ship: Ship): ShipListItem { findItem(ship: Ship): ShipListItem | null {
var found: ShipListItem = null; var found: ShipListItem | null = null;
this.ships.forEach((item: ShipListItem) => { this.ships.forEach((item: ShipListItem) => {
if (item.ship === ship) { if (item.ship === ship) {
found = item; found = item;
@ -71,7 +71,7 @@ module TS.SpaceTac.UI {
findPlayPosition(ship: Ship): number { findPlayPosition(ship: Ship): number {
var battle = this.battleview.battle; var battle = this.battleview.battle;
var idx = battle.play_order.indexOf(ship); var idx = battle.play_order.indexOf(ship);
var diff = idx - battle.playing_ship_index; var diff = idx - (battle.playing_ship_index || 0);
if (diff < 0) { if (diff < 0) {
diff += battle.play_order.length; diff += battle.play_order.length;
} }
@ -100,19 +100,26 @@ module TS.SpaceTac.UI {
} }
// Set the currently playing ship // Set the currently playing ship
setPlaying(ship: Ship): void { setPlaying(ship: Ship | null): void {
this.playing = this.findItem(ship); if (ship) {
this.playing = this.findItem(ship);
} else {
this.playing = null;
}
this.updateItemsLocation(); this.updateItemsLocation();
} }
// Set the currently hovered ship // Set the currently hovered ship
setHovered(ship: Ship): void { setHovered(ship: Ship | null): void {
if (this.hovered) { if (this.hovered) {
this.hovered.setHovered(false); this.hovered.setHovered(false);
this.hovered = null;
} }
this.hovered = this.findItem(ship); if (ship) {
if (this.hovered) { this.hovered = this.findItem(ship);
this.hovered.setHovered(true); if (this.hovered) {
this.hovered.setHovered(true);
}
} }
} }
} }

View file

@ -3,11 +3,11 @@ module TS.SpaceTac.UI {
// Allows to pick a target for an action // Allows to pick a target for an action
export class Targetting { export class Targetting {
// Initial target (as pointed by the user) // Initial target (as pointed by the user)
target_initial: Target; target_initial: Target | null;
line_initial: Phaser.Graphics; line_initial: Phaser.Graphics;
// Corrected target (applying action rules) // Corrected target (applying action rules)
target_corrected: Target; target_corrected: Target | null;
line_corrected: Phaser.Graphics; line_corrected: Phaser.Graphics;
// Circle for effect radius // Circle for effect radius
@ -25,13 +25,13 @@ module TS.SpaceTac.UI {
ap_indicators: Phaser.Image[] = []; ap_indicators: Phaser.Image[] = [];
// Access to the parent battle view // Access to the parent battle view
private battleview: BattleView; private battleview: BattleView | null;
// Source of the targetting // Source of the targetting
private source: PIXI.DisplayObject; private source: PIXI.DisplayObject | null;
// Create a default targetting mode // Create a default targetting mode
constructor(battleview: BattleView) { constructor(battleview: BattleView | null) {
this.battleview = battleview; this.battleview = battleview;
this.targetHovered = new Phaser.Signal(); this.targetHovered = new Phaser.Signal();
this.targetSelected = new Phaser.Signal(); this.targetSelected = new Phaser.Signal();
@ -116,6 +116,10 @@ module TS.SpaceTac.UI {
// Update the AP indicators display // Update the AP indicators display
updateApIndicators() { updateApIndicators() {
if (!this.battleview || !this.source || !this.target_corrected) {
return;
}
// Get indicator count // Get indicator count
let count = 0; let count = 0;
let distance = 0; let distance = 0;
@ -139,10 +143,11 @@ module TS.SpaceTac.UI {
// Spread indicators // Spread indicators
if (count > 0 && distance > 0) { if (count > 0 && distance > 0) {
let dx = this.ap_interval * (this.target_corrected.x - this.source.x) / distance; let source = this.source;
let dy = this.ap_interval * (this.target_corrected.y - this.source.y) / distance; let dx = this.ap_interval * (this.target_corrected.x - source.x) / distance;
let dy = this.ap_interval * (this.target_corrected.y - source.y) / distance;
this.ap_indicators.forEach((indicator, index) => { this.ap_indicators.forEach((indicator, index) => {
indicator.position.set(this.source.x + dx * index, this.source.y + dy * index); indicator.position.set(source.x + dx * index, source.y + dy * index);
}); });
} }
} }
@ -153,7 +158,7 @@ module TS.SpaceTac.UI {
} }
// Set a target from a target object // Set a target from a target object
setTarget(target: Target, dispatch: boolean = true, blast_radius: number = 0): void { setTarget(target: Target | null, dispatch: boolean = true, blast_radius: number = 0): void {
this.target_corrected = target; this.target_corrected = target;
this.blast_radius = blast_radius; this.blast_radius = blast_radius;
if (dispatch) { if (dispatch) {

View file

@ -4,7 +4,7 @@ module TS.SpaceTac.UI {
private game: MainUI; private game: MainUI;
private music: Phaser.Sound; private music: Phaser.Sound | null;
constructor(game: MainUI) { constructor(game: MainUI) {
this.game = game; this.game = game;

View file

@ -33,7 +33,7 @@ module TS.SpaceTac.UI {
this.bind(Phaser.Keyboard.M, "Toggle sound", () => { this.bind(Phaser.Keyboard.M, "Toggle sound", () => {
this.game.audio.toggleMute(); this.game.audio.toggleMute();
}); });
this.bind(Phaser.Keyboard.NUMPAD_ADD, null, () => { this.bind(Phaser.Keyboard.NUMPAD_ADD, "", () => {
if (this.cheats_enabled) { if (this.cheats_enabled) {
this.cheat = !this.cheat; this.cheat = !this.cheat;
this.game.displayMessage(this.cheat ? "Cheats enabled" : "Cheats disabled"); this.game.displayMessage(this.cheat ? "Cheats enabled" : "Cheats disabled");
@ -48,9 +48,9 @@ module TS.SpaceTac.UI {
// Bind a key to a cheat action // Bind a key to a cheat action
bindCheat(key: number, desc: string, action: Function): void { bindCheat(key: number, desc: string, action: Function): void {
this.bind(key, null, () => { this.bind(key, `Cheat: ${desc}`, () => {
if (this.cheat) { if (this.cheat) {
console.warn("Cheat ! " + desc); console.warn(`Cheat ! ${desc}`);
action(); action();
} }
}); });

View file

@ -11,11 +11,15 @@ module TS.SpaceTac.UI.Specs {
mapview.game.tweens.update(); mapview.game.tweens.update();
let tween = first(mapview.game.tweens.getAll(), tw => tw.target == fleet); let tween = first(mapview.game.tweens.getAll(), tw => tw.target == fleet);
let tweendata = tween.generateData(0.1); if (tween) {
expect(tweendata.length).toEqual(3); let tweendata = tween.generateData(0.1);
expect(tweendata[0].rotation).toBeCloseTo(-Math.PI * 2 / 3); expect(tweendata.length).toEqual(3);
expect(tweendata[1].rotation).toBeCloseTo(-Math.PI * 4 / 3); expect(tweendata[0].rotation).toBeCloseTo(-Math.PI * 2 / 3);
expect(tweendata[2].rotation).toBeCloseTo(-Math.PI * 2); expect(tweendata[1].rotation).toBeCloseTo(-Math.PI * 4 / 3);
expect(tweendata[2].rotation).toBeCloseTo(-Math.PI * 2);
} else {
fail("No tween found");
}
}); });
}); });
} }

View file

@ -29,7 +29,9 @@ module TS.SpaceTac.UI {
sprite.anchor.set(0.5, 0.5); sprite.anchor.set(0.5, 0.5);
}); });
this.position.set(fleet.location.star.x + fleet.location.x, fleet.location.star.y + fleet.location.y); if (fleet.location) {
this.position.set(fleet.location.star.x + fleet.location.x, fleet.location.star.y + fleet.location.y);
}
this.scale.set(SCALING, SCALING); this.scale.set(SCALING, SCALING);
this.tween = this.game.tweens.create(this); this.tween = this.game.tweens.create(this);
@ -68,8 +70,8 @@ module TS.SpaceTac.UI {
/** /**
* Make the fleet move to another location in the same system * Make the fleet move to another location in the same system
*/ */
moveToLocation(location: StarLocation, speed = 1, on_leave: (duration: number) => any | null = null) { moveToLocation(location: StarLocation, speed = 1, on_leave: ((duration: number) => any) | null = null) {
if (location != this.fleet.location) { if (this.fleet.location && location != this.fleet.location) {
let dx = location.universe_x - this.fleet.location.universe_x; let dx = location.universe_x - this.fleet.location.universe_x;
let dy = location.universe_y - this.fleet.location.universe_y; let dy = location.universe_y - this.fleet.location.universe_y;
let distance = Math.sqrt(dx * dx + dy * dy); let distance = Math.sqrt(dx * dx + dy * dy);

View file

@ -6,10 +6,10 @@ module TS.SpaceTac.UI {
*/ */
export class UniverseMapView extends BaseView { export class UniverseMapView extends BaseView {
// Displayed universe // Displayed universe
universe: Universe; universe = new Universe();
// Interacting player // Interacting player
player: Player; player = new Player();
// Star systems // Star systems
group: Phaser.Group; group: Phaser.Group;
@ -52,9 +52,11 @@ module TS.SpaceTac.UI {
let loc2 = starlink.second.getWarpLocationTo(starlink.first); let loc2 = starlink.second.getWarpLocationTo(starlink.first);
let result = new Phaser.Graphics(this.game); let result = new Phaser.Graphics(this.game);
result.lineStyle(0.005, 0x8bbeff); if (loc1 && loc2) {
result.moveTo(starlink.first.x - 0.5 + loc1.x, starlink.first.y - 0.5 + loc1.y); result.lineStyle(0.005, 0x8bbeff);
result.lineTo(starlink.second.x - 0.5 + loc2.x, starlink.second.y - 0.5 + loc2.y); result.moveTo(starlink.first.x - 0.5 + loc1.x, starlink.first.y - 0.5 + loc1.y);
result.lineTo(starlink.second.x - 0.5 + loc2.x, starlink.second.y - 0.5 + loc2.y);
}
return result; return result;
}); });
this.starlinks.forEach(starlink => this.group.addChild(starlink)); this.starlinks.forEach(starlink => this.group.addChild(starlink));
@ -92,8 +94,8 @@ module TS.SpaceTac.UI {
* Leaving the view, unbind and destroy * Leaving the view, unbind and destroy
*/ */
shutdown() { shutdown() {
this.universe = null; this.universe = new Universe();
this.player = null; this.player = new Player();
super.shutdown(); super.shutdown();
} }
@ -105,7 +107,7 @@ module TS.SpaceTac.UI {
this.starsystems.forEach(system => system.updateInfo()); this.starsystems.forEach(system => system.updateInfo());
let location = this.player.fleet.location; let location = this.player.fleet.location;
if (location.type == StarLocationType.WARP) { if (location && location.type == StarLocationType.WARP) {
let angle = Math.atan2(location.y, location.x); let angle = Math.atan2(location.y, location.x);
this.button_jump.scale.set(location.star.radius * 0.002, location.star.radius * 0.002); this.button_jump.scale.set(location.star.radius * 0.002, location.star.radius * 0.002);
this.button_jump.position.set(location.star.x + location.x + 0.02 * Math.cos(angle), location.star.y + location.y + 0.02 * Math.sin(angle)); this.button_jump.position.set(location.star.x + location.x + 0.02 * Math.cos(angle), location.star.y + location.y + 0.02 * Math.sin(angle));
@ -140,8 +142,8 @@ module TS.SpaceTac.UI {
* Set the current zoom level (0, 1 or 2) * Set the current zoom level (0, 1 or 2)
*/ */
setZoom(level: number) { setZoom(level: number) {
let current_star = this.player.fleet.location.star; let current_star = this.player.fleet.location ? this.player.fleet.location.star : null;
if (level <= 0) { if (!current_star || level <= 0) {
this.setCamera(0, 0, this.universe.radius * 2); this.setCamera(0, 0, this.universe.radius * 2);
this.zoom = 0; this.zoom = 0;
} else if (level == 1) { } else if (level == 1) {
@ -158,7 +160,7 @@ module TS.SpaceTac.UI {
* Do the jump animation to another system * Do the jump animation to another system
*/ */
doJump() { doJump() {
if (this.player.fleet.location.type == StarLocationType.WARP && this.player.fleet.location.jump_dest) { if (this.player.fleet.location && this.player.fleet.location.type == StarLocationType.WARP && this.player.fleet.location.jump_dest) {
Animation.setVisibility(this.game, this.button_jump, false, 300); Animation.setVisibility(this.game, this.button_jump, false, 300);
let dest_location = this.player.fleet.location.jump_dest; let dest_location = this.player.fleet.location.jump_dest;

View file

@ -10,7 +10,7 @@
"removeComments": true, "removeComments": true,
"preserveConstEnums": true, "preserveConstEnums": true,
"out": "out/build.js", "out": "out/build.js",
"strictNullChecks": false, "strictNullChecks": true,
"sourceMap": true, "sourceMap": true,
"target": "es5" "target": "es5"
}, },

View file

@ -1,8 +1,8 @@
{ {
"name": "succession", "name": "spacetac",
"dependencies": {}, "dependencies": {},
"globalDependencies": { "globalDependencies": {
"jasmine": "registry:dt/jasmine#2.5.0+20161003201800", "jasmine": "registry:dt/jasmine#2.5.0+20161003201800",
"phaser": "github:photonstorm/phaser/typescript/typings.json#v2.6.2" "phaser": "github:thunderk/phaser-ce/typescript/typings.json#v2.7.3a"
} }
} }