1
0
Fork 0

Removed references from battle state to universe state, causing large serialized data, slowing down AI work

This commit is contained in:
Michaël Lemaire 2018-01-16 01:08:24 +01:00
parent 4e155b8556
commit fa98a57ee2
44 changed files with 456 additions and 253 deletions

View file

@ -102,7 +102,6 @@ Common UI
Technical Technical
--------- ---------
* Remove references from battle internals (ships, fleets...) to universe (it causes large serialized battles in campaign mode)
* Pack all images in atlases, and split them by stage * Pack all images in atlases, and split them by stage
* Pack sounds * Pack sounds
* Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile * Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile

View file

@ -269,7 +269,7 @@ module TK.SpaceTac {
check.equals(battle.canPlay(player), false); check.equals(battle.canPlay(player), false);
ship.fleet.player = player; ship.fleet.setPlayer(player);
check.equals(battle.canPlay(player), true); check.equals(battle.canPlay(player), true);
}); });
@ -358,9 +358,19 @@ module TK.SpaceTac {
let loaded = serializer.unserialize(data); let loaded = serializer.unserialize(data);
check.equals(loaded.ai_playing, false); check.equals(loaded.ai_playing, false, "ai playing is reset");
battle.ai_playing = false; battle.ai_playing = false;
check.equals(loaded, battle); check.equals(loaded, battle, "unserialized == initial");
let session = new GameSession();
session.startNewGame();
session.start_location.setupEncounter();
session.start_location.enterLocation(session.player.fleet);
let battle1 = nn(session.getBattle());
let data1 = serializer.serialize(battle1);
let ratio = data.length / data1.length;
check.greaterorequal(ratio, 1.2, `quick battle serialized size (${data.length}) should be larger than campaign's (${data1.length})`);
}); });
test.case("can revert the last action", check => { test.case("can revert the last action", check => {

View file

@ -6,9 +6,6 @@ module TK.SpaceTac {
// Battle outcome, if the battle has ended // Battle outcome, if the battle has ended
outcome: BattleOutcome | null = null outcome: BattleOutcome | null = null
// Battle cheats
cheats: BattleCheats
// Statistics // Statistics
stats: BattleStats stats: BattleStats
@ -43,7 +40,7 @@ module TK.SpaceTac {
// Indicator that an AI is playing // Indicator that an AI is playing
ai_playing = false ai_playing = false
constructor(fleet1 = new Fleet(new Player(undefined, "Attacker")), fleet2 = new Fleet(new Player(undefined, "Defender")), width = 1808, height = 948) { constructor(fleet1 = new Fleet(new Player("Attacker")), fleet2 = new Fleet(new Player("Defender")), width = 1808, height = 948) {
this.fleets = [fleet1, fleet2]; this.fleets = [fleet1, fleet2];
this.ships = new RObjectContainer(fleet1.ships.concat(fleet2.ships)); this.ships = new RObjectContainer(fleet1.ships.concat(fleet2.ships));
this.play_order = []; this.play_order = [];
@ -52,7 +49,6 @@ module TK.SpaceTac {
this.log = new BattleLog(); this.log = new BattleLog();
this.stats = new BattleStats(); this.stats = new BattleStats();
this.cheats = new BattleCheats(this, fleet1.player);
this.fleets.forEach((fleet: Fleet) => { this.fleets.forEach((fleet: Fleet) => {
fleet.setBattle(this); fleet.setBattle(this);
@ -123,23 +119,26 @@ module TK.SpaceTac {
/** /**
* Return an iterator over ships allies of (or owned by) a player * Return an iterator over ships allies of (or owned by) a player
*/ */
iallies(player: Player, alive_only = false): Iterator<Ship> { iallies(ship: Ship, alive_only = false): Iterator<Ship> {
return ifilter(this.iships(alive_only), ship => ship.getPlayer() === player); return ifilter(this.iships(alive_only), iship => iship.fleet.player.is(ship.fleet.player));
} }
/** /**
* Return an iterator over ships enemy of a player * Return an iterator over ships enemy of a player
*/ */
ienemies(player: Player, alive_only = false): Iterator<Ship> { ienemies(ship: Ship, alive_only = false): Iterator<Ship> {
return ifilter(this.iships(alive_only), ship => ship.getPlayer() !== player); return ifilter(this.iships(alive_only), iship => !iship.fleet.player.is(ship.fleet.player));
} }
// Check if a player is able to play /**
// This can be used by the UI to determine if player interaction is allowed * Check if a player is able to play
*
* This can be used by the UI to determine if player interaction is allowed
*/
canPlay(player: Player): boolean { canPlay(player: Player): boolean {
if (this.ended) { if (this.ended) {
return false; return false;
} else if (this.playing_ship && this.playing_ship.getPlayer() == player) { } else if (this.playing_ship && player.is(this.playing_ship.fleet.player)) {
return this.playing_ship.isAbleToPlay(false); return this.playing_ship.isAbleToPlay(false);
} else { } else {
return false; return false;

View file

@ -2,8 +2,10 @@ module TK.SpaceTac.Specs {
testing("BattleCheats", test => { testing("BattleCheats", test => {
test.case("wins a battle", check => { test.case("wins a battle", check => {
let battle = Battle.newQuickRandom(); let battle = Battle.newQuickRandom();
let cheats = new BattleCheats(battle, battle.fleets[0].player);
cheats.win();
battle.cheats.win();
check.equals(battle.ended, true, "ended"); check.equals(battle.ended, true, "ended");
check.same(nn(battle.outcome).winner, battle.fleets[0], "winner"); check.same(nn(battle.outcome).winner, battle.fleets[0], "winner");
check.equals(any(battle.fleets[1].ships, ship => ship.alive), false, "all enemies dead"); check.equals(any(battle.fleets[1].ships, ship => ship.alive), false, "all enemies dead");
@ -11,8 +13,10 @@ module TK.SpaceTac.Specs {
test.case("loses a battle", check => { test.case("loses a battle", check => {
let battle = Battle.newQuickRandom(); let battle = Battle.newQuickRandom();
let cheats = new BattleCheats(battle, battle.fleets[0].player);
cheats.lose();
battle.cheats.lose();
check.equals(battle.ended, true, "ended"); check.equals(battle.ended, true, "ended");
check.same(nn(battle.outcome).winner, battle.fleets[1], "winner"); check.same(nn(battle.outcome).winner, battle.fleets[1], "winner");
check.equals(any(battle.fleets[0].ships, ship => ship.alive), false, "all allies dead"); check.equals(any(battle.fleets[0].ships, ship => ship.alive), false, "all allies dead");
@ -23,9 +27,12 @@ module TK.SpaceTac.Specs {
let ship = new Ship(); let ship = new Ship();
TestTools.setShipPlaying(battle, ship); TestTools.setShipPlaying(battle, ship);
ship.upgradeSkill("skill_materials"); ship.upgradeSkill("skill_materials");
let cheats = new BattleCheats(battle, battle.fleets[0].player);
check.equals(ship.listEquipment(), []); check.equals(ship.listEquipment(), []);
battle.cheats.equip("Iron Hull");
cheats.equip("Iron Hull");
let result = ship.listEquipment(); let result = ship.listEquipment();
check.equals(result.length, 1); check.equals(result.length, 1);
check.containing(result[0], { name: "Iron Hull", level: 1 }); check.containing(result[0], { name: "Iron Hull", level: 1 });

View file

@ -18,7 +18,7 @@ module TK.SpaceTac {
*/ */
win(): void { win(): void {
iforeach(this.battle.iships(), ship => { iforeach(this.battle.iships(), ship => {
if (ship.fleet.player != this.player) { if (!this.player.is(ship.fleet.player)) {
ship.setDead(); ship.setDead();
} }
}); });
@ -30,11 +30,11 @@ module TK.SpaceTac {
*/ */
lose(): void { lose(): void {
iforeach(this.battle.iships(), ship => { iforeach(this.battle.iships(), ship => {
if (ship.fleet.player == this.player) { if (this.player.is(ship.fleet.player)) {
ship.setDead(); ship.setDead();
} }
}); });
this.battle.endBattle(first(this.battle.fleets, fleet => fleet.player != this.player)); this.battle.endBattle(first(this.battle.fleets, fleet => !this.player.is(fleet.player)));
} }
/** /**

View file

@ -27,7 +27,7 @@ module TK.SpaceTac {
this.loot = []; this.loot = [];
battle.fleets.forEach(fleet => { battle.fleets.forEach(fleet => {
if (this.winner && this.winner.player != fleet.player) { if (this.winner && !this.winner.player.is(fleet.player)) {
fleet.ships.forEach(ship => { fleet.ships.forEach(ship => {
var luck = random.random(); var luck = random.random();
if (luck > 0.9) { if (luck > 0.9) {

View file

@ -49,37 +49,92 @@ module TK.SpaceTac {
test.case("changes location, only using jumps to travel between systems", check => { test.case("changes location, only using jumps to travel between systems", check => {
let fleet = new Fleet(); let fleet = new Fleet();
let system1 = new Star(); let universe = new Universe();
let system2 = new Star(); let system1 = universe.addStar();
let jump1 = new StarLocation(system1, StarLocationType.WARP); let system2 = universe.addStar();
let jump2 = new StarLocation(system2, StarLocationType.WARP); let jump1 = system1.addLocation(StarLocationType.WARP);
let jump2 = system2.addLocation(StarLocationType.WARP);
jump1.setJumpDestination(jump2); jump1.setJumpDestination(jump2);
jump2.setJumpDestination(jump1); jump2.setJumpDestination(jump1);
let other1 = new StarLocation(system1, StarLocationType.PLANET); let other1 = system1.addLocation(StarLocationType.PLANET);
universe.updateLocations();
let result = fleet.setLocation(other1); let result = fleet.move(other1);
check.equals(result, true); check.in("cannot move from nowhere", check => {
check.same(fleet.location, other1); check.equals(result, false);
check.equals(fleet.location, null);
});
result = fleet.setLocation(jump2); fleet.setLocation(other1);
check.equals(result, false); check.in("force set to other1", check => {
check.same(fleet.location, other1); check.equals(fleet.location, other1.id);
});
result = fleet.setLocation(jump1); result = fleet.move(jump2);
check.equals(result, true); check.in("other1=>jump2", check => {
check.same(fleet.location, jump1); check.equals(result, false);
check.equals(fleet.location, other1.id);
});
result = fleet.setLocation(jump2); result = fleet.move(jump1);
check.equals(result, true); check.in("other1=>jump1", check => {
check.same(fleet.location, jump2); check.equals(result, true);
check.equals(fleet.location, jump1.id);
});
result = fleet.setLocation(other1); result = fleet.move(jump2);
check.equals(result, false); check.in("jump1=>jump2", check => {
check.same(fleet.location, jump2); check.equals(result, true);
check.equals(fleet.location, jump2.id);
});
result = fleet.setLocation(jump1); result = fleet.move(other1);
check.equals(result, true); check.in("jump2=>other1", check => {
check.same(fleet.location, jump1); check.equals(result, false);
check.equals(fleet.location, jump2.id);
});
result = fleet.move(jump1);
check.in("jump2=>jump1", check => {
check.equals(result, true);
check.equals(fleet.location, jump1.id);
});
});
test.case("registers presence in locations, and keeps track of visited locations", check => {
let fleet = new Fleet();
let universe = new Universe();
let star = universe.addStar();
let loc1 = star.addLocation(StarLocationType.PLANET);
let loc2 = star.addLocation(StarLocationType.PLANET);
let loc3 = star.addLocation(StarLocationType.PLANET);
universe.updateLocations();
function checks(desc: string, fleets1: Fleet[], fleets2: Fleet[], fleets3: Fleet[], visited: RObjectId[]) {
check.in(desc, check => {
check.equals(loc1.fleets, fleets1, "loc1 fleets");
check.equals(loc2.fleets, fleets2, "loc2 fleets");
check.equals(loc3.fleets, fleets3, "loc3 fleets");
check.equals(fleet.visited, visited, "visited");
});
}
checks("initial", [], [], [], []);
fleet.setLocation(loc1);
checks("first move to loc1", [fleet], [], [], [loc1.id]);
fleet.setLocation(loc1);
checks("already in loc1", [fleet], [], [], [loc1.id]);
fleet.setLocation(loc2);
checks("first move to loc2", [], [fleet], [], [loc2.id, loc1.id]);
fleet.setLocation(loc3);
checks("first move to loc3", [], [], [fleet], [loc3.id, loc2.id, loc1.id]);
fleet.setLocation(loc2);
checks("go back to loc2", [], [fleet], [], [loc2.id, loc3.id, loc1.id]);
}); });
test.case("checks if a fleet is alive", check => { test.case("checks if a fleet is alive", check => {

View file

@ -4,58 +4,98 @@ module TK.SpaceTac {
*/ */
export class Fleet { export class Fleet {
// Fleet owner // Fleet owner
player: Player; player: Player
// Fleet name
name: string
// List of ships // List of ships
ships: Ship[]; ships: Ship[]
// Current fleet location // Current fleet location
location: StarLocation | null = null; location: RObjectId | null = null
previous_location: StarLocation | null = null;
// Visited locations (ordered by last visited)
visited: RObjectId[] = []
// 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 | null = null; battle: Battle | null = 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 = new Player()) { constructor(player = new Player()) {
this.player = player; this.player = player;
this.name = player ? player.name : "Fleet";
this.ships = []; this.ships = [];
} }
jasmineToString(): string { jasmineToString(): string {
return `${this.player.name}'s fleet [${this.ships.map(ship => ship.getName()).join(",")}]`; return `${this.name} [${this.ships.map(ship => ship.getName()).join(",")}]`;
} }
/** /**
* Set the current location of the fleet * Set the owner player
*/
setPlayer(player: Player): void {
this.player = player;
}
/**
* Set a location as visited
*/
setVisited(location: StarLocation): void {
remove(this.visited, location.id);
this.visited.unshift(location.id);
}
/**
* Move the fleet to another location, checking that the move is physically possible
* *
* Returns true on success * Returns true on success
*/ */
setLocation(location: StarLocation, force = false): boolean { move(to: StarLocation): boolean {
if (!force && this.location && location.star != this.location.star && (this.location.type != StarLocationType.WARP || this.location.jump_dest != location)) { if (!this.location) {
return false; return false;
} }
this.previous_location = this.location; let source = to.universe.locations.get(this.location);
this.location = location; if (!source) {
this.player.setVisited(this.location); return false;
// Check encounter
var battle = this.location.enterLocation(this.player.fleet);
if (battle) {
this.player.setBattle(battle);
} }
if (source.star != to.star) {
// Need to jump, check conditions
if (source.type != StarLocationType.WARP || source.jump_dest != to) {
return false;
}
}
this.setLocation(to);
return true; return true;
} }
/**
* Set the current location of the fleet, without condition
*/
setLocation(location: StarLocation): void {
if (this.location) {
let previous = location.universe.locations.get(this.location);
if (previous) {
previous.removeFleet(this);
}
}
this.location = location.id;
this.setVisited(location);
location.addFleet(this);
}
/** /**
* Add a ship this fleet * Add a ship this fleet
*/ */
addShip(ship = new Ship(null, `${this.player.name} ${this.ships.length + 1}`)): Ship { addShip(ship = new Ship(null, `${this.name} ${this.ships.length + 1}`)): Ship {
if (ship.fleet && ship.fleet != this) { if (ship.fleet && ship.fleet != this) {
remove(ship.fleet.ships, ship); remove(ship.fleet.ships, ship);
} }

View file

@ -40,8 +40,11 @@ module TK.SpaceTac.Specs {
let session = new GameSession(); let session = new GameSession();
check.equals(session.getBattle(), null); check.equals(session.getBattle(), null);
// Victory case
let location1 = new StarLocation(); let location1 = new StarLocation();
let location2 = new StarLocation(location1.star);
session.universe.locations = new RObjectContainer([location1, location2]);
// Victory case
location1.encounter = new Fleet(); location1.encounter = new Fleet();
session.player.fleet.setLocation(location1); session.player.fleet.setLocation(location1);
check.notequals(session.getBattle(), null); check.notequals(session.getBattle(), null);
@ -56,7 +59,6 @@ module TK.SpaceTac.Specs {
check.called(spyloot, 1); check.called(spyloot, 1);
// Defeat case // Defeat case
let location2 = new StarLocation(location1.star);
location2.encounter = new Fleet(); location2.encounter = new Fleet();
session.player.fleet.setLocation(location2); session.player.fleet.setLocation(location2);
check.notequals(session.getBattle(), null); check.notequals(session.getBattle(), null);
@ -78,7 +80,7 @@ module TK.SpaceTac.Specs {
check.notequals(session.player, null); check.notequals(session.player, null);
check.equals(session.player.fleet.ships.length, 0); check.equals(session.player.fleet.ships.length, 0);
check.equals(session.player.fleet.credits, 0); check.equals(session.player.fleet.credits, 0);
check.equals(session.player.universe.stars.length, 50); check.equals(session.universe.stars.length, 50);
check.equals(session.getBattle(), null); check.equals(session.getBattle(), null);
check.equals(session.start_location.shop, null); check.equals(session.start_location.shop, null);
check.equals(session.start_location.encounter, null); check.equals(session.start_location.encounter, null);
@ -87,7 +89,47 @@ module TK.SpaceTac.Specs {
session.setCampaignFleet(); session.setCampaignFleet();
check.equals(session.player.fleet.ships.length, 2); check.equals(session.player.fleet.ships.length, 2);
check.equals(session.player.fleet.credits, 0); check.equals(session.player.fleet.credits, 0);
check.same(session.player.fleet.location, session.start_location); check.equals(session.player.fleet.location, session.start_location.id);
});
test.case("can revert battle", check => {
let session = new GameSession();
let star = session.universe.addStar();
let loc1 = star.addLocation(StarLocationType.PLANET);
loc1.clearEncounter();
let loc2 = star.addLocation(StarLocationType.PLANET);
loc2.encounter_random = new SkewedRandomGenerator([0], true);
session.universe.updateLocations();
session.fleet.setLocation(loc1);
check.in("init in loc1", check => {
check.equals(session.getBattle(), null, "bound battle");
check.equals(session.fleet.location, loc1.id, "fleet location");
check.equals(session.player.hasVisitedLocation(loc2), false, "visited");
});
session.fleet.setLocation(loc2);
check.in("move to loc2", check => {
check.notequals(session.getBattle(), null, "bound battle");
check.equals(session.fleet.location, loc2.id, "fleet location");
check.equals(session.player.hasVisitedLocation(loc2), true, "visited");
});
let enemy = loc2.encounter;
session.revertBattle();
check.in("reverted", check => {
check.equals(session.getBattle(), null, "bound battle");
check.equals(session.fleet.location, loc1.id, "fleet location");
check.equals(session.player.hasVisitedLocation(loc2), true, "visited");
});
session.fleet.setLocation(loc2);
check.in("move to loc2 again", check => {
check.notequals(session.getBattle(), null, "bound battle");
check.equals(session.fleet.location, loc2.id, "fleet location");
check.equals(session.player.hasVisitedLocation(loc2), true, "visited");
check.same(nn(session.getBattle()).fleets[1], nn(enemy), "same enemy");
});
}); });
/*test.case("can generate lots of new games", check => { /*test.case("can generate lots of new games", check => {

View file

@ -32,11 +32,18 @@ module TK.SpaceTac {
constructor() { constructor() {
this.id = RandomGenerator.global.id(20); this.id = RandomGenerator.global.id(20);
this.universe = new Universe(); this.universe = new Universe();
this.player = new Player(this.universe); this.player = new Player();
this.reactions = new PersonalityReactions(); this.reactions = new PersonalityReactions();
this.start_location = new StarLocation(); this.start_location = new StarLocation();
} }
/**
* Get the currently played fleet
*/
get fleet(): Fleet {
return this.player.fleet;
}
/** /**
* Get an indicative description of the session (to help identify game saves) * Get an indicative description of the session (to help identify game saves)
*/ */
@ -71,7 +78,7 @@ module TK.SpaceTac {
this.start_location.clearEncounter(); this.start_location.clearEncounter();
this.start_location.removeShop(); this.start_location.removeShop();
this.player = new Player(this.universe); this.player = new Player();
this.reactions = new PersonalityReactions(); this.reactions = new PersonalityReactions();
@ -88,34 +95,52 @@ module TK.SpaceTac {
setCampaignFleet(fleet: Fleet | null = null, story = true) { setCampaignFleet(fleet: Fleet | null = null, story = true) {
if (fleet) { if (fleet) {
this.player.fleet = fleet; this.player.fleet = fleet;
fleet.player = this.player; fleet.setPlayer(this.player);
} else { } else {
let fleet_generator = new FleetGenerator(); let fleet_generator = new FleetGenerator();
this.player.fleet = fleet_generator.generate(1, this.player, 2); this.player.fleet = fleet_generator.generate(1, this.player, 2);
} }
this.player.fleet.setLocation(this.start_location, true); this.player.fleet.setLocation(this.start_location);
if (story) { if (story) {
this.player.missions.startMainStory(this.universe, this.player.fleet); this.player.missions.startMainStory(this.universe, this.player.fleet);
} }
} }
// Start a new "quick battle" game /**
* Start a new "quick battle" game
*/
startQuickBattle(with_ai: boolean = false): void { startQuickBattle(with_ai: boolean = false): void {
this.universe = new Universe();
let battle = Battle.newQuickRandom(true, RandomGenerator.global.randInt(1, 10)); let battle = Battle.newQuickRandom(true, RandomGenerator.global.randInt(1, 10));
this.player = battle.fleets[0].player; battle.fleets[0].setPlayer(this.player);
this.player.setBattle(battle); this.player.setBattle(battle);
this.reactions = new PersonalityReactions(); this.reactions = new PersonalityReactions();
} }
// Get currently played battle, null when none is in progress /**
* Get currently played battle, null when none is in progress
*/
getBattle(): Battle | null { getBattle(): Battle | null {
return this.player.getBattle(); return this.player.getBattle();
} }
/** /**
* Set the end of current battle * Get the main fleet's location
*/
getLocation(): StarLocation {
return this.universe.getLocation(this.player.fleet.location) || new StarLocation();
}
/**
* Set the end of current battle.
*
* This will reset the fleet, grant experience, and create loot.
*
* The battle will still be bound to the session (exitBattle or revertBattle should be called after).
*/ */
setBattleEnded() { setBattleEnded() {
let battle = this.getBattle(); let battle = this.getBattle();
@ -133,13 +158,34 @@ module TK.SpaceTac {
} }
// If the battle happened in a star location, keep it informed // If the battle happened in a star location, keep it informed
let location = this.player.fleet.location; let location = this.universe.getLocation(this.player.fleet.location);
if (location) { if (location) {
location.resolveEncounter(battle.outcome); location.resolveEncounter(battle.outcome);
} }
} }
} }
/**
* Exit the current battle unconditionally, if any
*
* This does not apply retreat penalties, or battle outcome, only unbind the battle from current session
*/
exitBattle(): void {
this.player.setBattle(null);
}
/**
* Revert current battle, and put the player's fleet to its previous location, as if the battle never happened
*/
revertBattle(): void {
this.exitBattle();
let previous_location = this.universe.getLocation(this.fleet.visited[1]);
if (previous_location) {
this.fleet.setLocation(previous_location);
}
}
/** /**
* Returns true if the session has an universe to explore (campaign mode) * Returns true if the session has an universe to explore (campaign mode)
*/ */

View file

@ -51,15 +51,17 @@ module TK.SpaceTac.Specs {
test.case("checks for friendly fire", check => { test.case("checks for friendly fire", check => {
let condition = BUILTIN_REACTION_POOL['friendly_fire'][0]; let condition = BUILTIN_REACTION_POOL['friendly_fire'][0];
let battle = new Battle(); let battle = new Battle();
let player = new Player();
battle.fleets[0].setPlayer(player);
let ship1a = battle.fleets[0].addShip(); let ship1a = battle.fleets[0].addShip();
let ship1b = battle.fleets[0].addShip(); let ship1b = battle.fleets[0].addShip();
let ship2a = battle.fleets[1].addShip(); let ship2a = battle.fleets[1].addShip();
let ship2b = battle.fleets[1].addShip(); let ship2b = battle.fleets[1].addShip();
check.equals(condition(ship1a.getPlayer(), battle, ship1a, new ShipDamageDiff(ship1a, 50, 10)), [], "self shoot"); check.equals(condition(player, battle, ship1a, new ShipDamageDiff(ship1a, 50, 10)), [], "self shoot");
check.equals(condition(ship1a.getPlayer(), battle, ship1a, new ShipDamageDiff(ship1b, 50, 10)), [ship1b, ship1a]); check.equals(condition(player, battle, ship1a, new ShipDamageDiff(ship1b, 50, 10)), [ship1b, ship1a]);
check.equals(condition(ship1a.getPlayer(), battle, ship1a, new ShipDamageDiff(ship2a, 50, 10)), [], "enemy shoot"); check.equals(condition(player, battle, ship1a, new ShipDamageDiff(ship2a, 50, 10)), [], "enemy shoot");
check.equals(condition(ship1a.getPlayer(), battle, ship2a, new ShipDamageDiff(ship2a, 50, 10)), [], "other player event"); check.equals(condition(player, battle, ship2a, new ShipDamageDiff(ship2a, 50, 10)), [], "other player event");
}) })
}) })
} }

View file

@ -93,9 +93,9 @@ module TK.SpaceTac {
*/ */
function cond_friendly_fire(player: Player, battle: Battle | null, ship: Ship | null, event: BaseBattleDiff | null): Ship[] { function cond_friendly_fire(player: Player, battle: Battle | null, ship: Ship | null, event: BaseBattleDiff | null): Ship[] {
if (battle && ship && event) { if (battle && ship && event) {
if (event instanceof ShipDamageDiff && player.is(ship.getPlayer()) && !ship.is(event.ship_id)) { if (event instanceof ShipDamageDiff && player.is(ship.fleet.player) && !ship.is(event.ship_id)) {
let hurt = battle.getShip(event.ship_id); let hurt = battle.getShip(event.ship_id);
return (hurt && hurt.getPlayer().is(player)) ? [hurt, ship] : []; return (hurt && player.is(hurt.fleet.player)) ? [hurt, ship] : [];
} else { } else {
return []; return [];
} }

View file

@ -2,12 +2,14 @@ module TK.SpaceTac {
testing("Player", test => { testing("Player", test => {
test.case("keeps track of visited locations", check => { test.case("keeps track of visited locations", check => {
let player = new Player(); let player = new Player();
let star1 = new Star(); let universe = new Universe();
let star2 = new Star(); let star1 = universe.addStar();
let loc1a = new StarLocation(star1); let star2 = universe.addStar();
let loc1b = new StarLocation(star1); let loc1a = star1.addLocation(StarLocationType.PLANET);
let loc2a = new StarLocation(star2); let loc1b = star1.addLocation(StarLocationType.PLANET);
let loc2b = new StarLocation(star2); let loc2a = star2.addLocation(StarLocationType.PLANET);
let loc2b = star2.addLocation(StarLocationType.PLANET);
universe.updateLocations();
function checkVisited(s1 = false, s2 = false, v1a = false, v1b = false, v2a = false, v2b = false) { function checkVisited(s1 = false, s2 = false, v1a = false, v1b = false, v2a = false, v2b = false) {
check.same(player.hasVisitedSystem(star1), s1); check.same(player.hasVisitedSystem(star1), s1);
@ -20,51 +22,17 @@ module TK.SpaceTac {
checkVisited(); checkVisited();
player.setVisited(loc1b); player.fleet.setLocation(loc1b);
checkVisited(true, false, false, true, false, false); checkVisited(true, false, false, true, false, false);
player.setVisited(loc1a); player.fleet.setLocation(loc1a);
checkVisited(true, false, true, true, false, false); checkVisited(true, false, true, true, false, false);
player.setVisited(loc2a); player.fleet.setLocation(loc2a);
checkVisited(true, true, true, true, true, false); checkVisited(true, true, true, true, true, false);
player.setVisited(loc2a); player.fleet.setLocation(loc2a);
checkVisited(true, true, true, true, true, false); checkVisited(true, true, true, true, true, false);
}); });
test.case("reverts battle", check => {
let player = new Player();
let star = new Star();
let loc1 = new StarLocation(star);
loc1.clearEncounter();
let loc2 = new StarLocation(star);
loc2.encounter_random = new SkewedRandomGenerator([0], true);
player.fleet.setLocation(loc1);
check.equals(player.getBattle(), null);
check.same(player.fleet.location, loc1);
player.fleet.setLocation(loc2);
check.notequals(player.getBattle(), null);
check.same(player.fleet.location, loc2);
check.equals(player.hasVisitedLocation(loc2), true);
let enemy = loc2.encounter;
player.revertBattle();
check.equals(player.getBattle(), null);
check.same(player.fleet.location, loc1);
check.equals(player.hasVisitedLocation(loc2), true);
player.fleet.setLocation(loc2);
check.notequals(player.getBattle(), null);
check.same(player.fleet.location, loc2);
check.equals(player.hasVisitedLocation(loc2), true);
check.same(nn(player.getBattle()).fleets[1], nn(enemy));
});
}); });
} }

View file

@ -8,57 +8,54 @@ module TK.SpaceTac {
// Player's name // Player's name
name: string name: string
// Universe in which we are playing // Bound fleet
universe: Universe
// Current fleet
fleet: Fleet fleet: Fleet
// List of visited star systems
visited: StarLocation[] = []
// Active missions // Active missions
missions = new ActiveMissions() missions = new ActiveMissions()
// Create a player, with an empty fleet // Create a player, with an empty fleet
constructor(universe: Universe = new Universe(), name = "Player") { constructor(name = "Player", fleet?: Fleet) {
super(); super();
this.universe = universe;
this.name = name; this.name = name;
this.fleet = new Fleet(this); this.fleet = fleet || new Fleet(this);
this.fleet.setPlayer(this);
} }
// Create a quick random player, with a fleet, for testing purposes // Create a quick random player, with a fleet, for testing purposes
static newQuickRandom(name: string, level = 1, shipcount = 4, upgrade = false): Player { static newQuickRandom(name: string, level = 1, shipcount = 4, upgrade = false): Player {
let player = new Player(new Universe(), name); let player = new Player(name);
let generator = new FleetGenerator(); let generator = new FleetGenerator();
player.fleet = generator.generate(level, player, shipcount, upgrade); player.fleet = generator.generate(level, player, shipcount, upgrade);
return player; return player;
} }
/**
* Get a cheats object
*/
getCheats(): BattleCheats | null {
let battle = this.getBattle();
if (battle) {
return new BattleCheats(battle, this);
} else {
return null;
}
}
/** /**
* Return true if the player has visited at least one location in a given system. * Return true if the player has visited at least one location in a given system.
*/ */
hasVisitedSystem(system: Star): boolean { hasVisitedSystem(system: Star): boolean {
return any(this.visited, location => location.star == system); return intersection(this.fleet.visited, system.locations.map(loc => loc.id)).length > 0;
} }
/** /**
* Return true if the player has visited a given star location. * Return true if the player has visited a given star location.
*/ */
hasVisitedLocation(location: StarLocation): boolean { hasVisitedLocation(location: StarLocation): boolean {
return contains(this.visited, location); return contains(this.fleet.visited, location.id);
}
/**
* Set a star location as visited.
*
* This should always be called for any location, even if it was already marked visited.
*/
setVisited(location: StarLocation): void {
add(this.visited, location);
this.missions.checkStatus();
} }
// Get currently played battle, null when none is in progress // Get currently played battle, null when none is in progress
@ -69,25 +66,5 @@ module TK.SpaceTac {
this.fleet.setBattle(battle); this.fleet.setBattle(battle);
this.missions.checkStatus(); this.missions.checkStatus();
} }
/**
* Exit the current battle unconditionally, if any
*
* This does not apply retreat penalties, or battle outcome, only unbind the battle from current session
*/
exitBattle(): void {
this.setBattle(null);
}
/**
* Revert current battle, and put the player's fleet to its previous location, as if the battle never happened
*/
revertBattle(): void {
this.exitBattle();
if (this.fleet.previous_location) {
this.fleet.setLocation(this.fleet.previous_location);
}
}
} }
} }

View file

@ -120,7 +120,9 @@ module TK.SpaceTac {
this.play_priority = gen.random() * this.attributes.maneuvrability.get(); this.play_priority = gen.random() * this.attributes.maneuvrability.get();
} }
// Return the player owning this ship /**
* Return the player that plays this ship
*/
getPlayer(): Player { getPlayer(): Player {
return this.fleet.player; return this.fleet.player;
} }
@ -129,7 +131,7 @@ module TK.SpaceTac {
* Check if a player is playing this ship * Check if a player is playing this ship
*/ */
isPlayedBy(player: Player): boolean { isPlayedBy(player: Player): boolean {
return this.getPlayer().is(player); return player.is(this.fleet.player);
} }
// get the current battle this ship is engaged in // get the current battle this ship is engaged in

View file

@ -1,3 +1,5 @@
/// <reference path="../common/RObject.ts" />
module TK.SpaceTac { module TK.SpaceTac {
export enum StarLocationType { export enum StarLocationType {
STAR, STAR,
@ -7,34 +9,41 @@ module TK.SpaceTac {
STATION STATION
} }
// Point of interest in a star system /**
export class StarLocation { * Point of interest in a star system
*/
export class StarLocation extends RObject {
// Parent star system // Parent star system
star: Star; star: Star
// Type of location // Type of location
type: StarLocationType; type: StarLocationType
// Location in the star system // Location in the star system
x: number; x: number
y: number; y: number
// Absolute location in the universe // Absolute location in the universe
universe_x: number; universe_x: number
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 | null; jump_dest: StarLocation | null
// Fleets present at this location (excluding the encounter for now)
fleets: Fleet[] = []
// Enemy encounter // Enemy encounter
encounter: Fleet | null = null; encounter: Fleet | null = null
encounter_gen = false; encounter_gen = false
encounter_random = RandomGenerator.global; encounter_random = RandomGenerator.global
// Shop to buy/sell equipment // Shop to buy/sell equipment
shop: Shop | null = null; shop: Shop | null = null
constructor(star = new 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) {
super();
this.star = star; this.star = star;
this.type = type; this.type = type;
this.x = x; this.x = x;
@ -44,6 +53,13 @@ module TK.SpaceTac {
this.jump_dest = null; this.jump_dest = null;
} }
/**
* Get the universe containing this location
*/
get universe(): Universe {
return this.star.universe;
}
/** /**
* Add a shop in this location * Add a shop in this location
*/ */
@ -58,6 +74,22 @@ module TK.SpaceTac {
this.shop = null; this.shop = null;
} }
/**
* Add a fleet to the list of fleets present in this system
*/
addFleet(fleet: Fleet): void {
if (add(this.fleets, fleet)) {
this.enterLocation(fleet);
}
}
/**
* Remove a fleet from the list of fleets present in this system
*/
removeFleet(fleet: Fleet): void {
remove(this.fleets, fleet);
}
/** /**
* Check if the location is clear of encounter * Check if the location is clear of encounter
*/ */
@ -134,7 +166,7 @@ module TK.SpaceTac {
variations = [[this.star.level, 4], [this.star.level - 1, 5], [this.star.level + 1, 3], [this.star.level + 3, 2]]; variations = [[this.star.level, 4], [this.star.level - 1, 5], [this.star.level + 1, 3], [this.star.level + 3, 2]];
} }
let [level, enemies] = this.encounter_random.choice(variations); let [level, enemies] = this.encounter_random.choice(variations);
this.encounter = fleet_generator.generate(level, new Player(this.star.universe, "Enemy"), enemies, true); this.encounter = fleet_generator.generate(level, new Player("Enemy"), enemies, true);
} }
/** /**

View file

@ -4,8 +4,8 @@ module TK.SpaceTac {
// Create a battle between two fleets, with a fixed play order (owned ships, then enemy ships) // Create a battle between two fleets, with a fixed play order (owned ships, then enemy ships)
static createBattle(own_ships = 1, enemy_ships = 1): Battle { static createBattle(own_ships = 1, enemy_ships = 1): Battle {
var fleet1 = new Fleet(new Player(undefined, "Attacker")); var fleet1 = new Fleet(new Player("Attacker"));
var fleet2 = new Fleet(new Player(undefined, "Defender")); var fleet2 = new Fleet(new Player("Defender"));
while (own_ships--) { while (own_ships--) {
fleet1.addShip(); fleet1.addShip();

View file

@ -9,6 +9,9 @@ module TK.SpaceTac {
// List of links between star systems // List of links between star systems
starlinks: StarLink[] = [] starlinks: StarLink[] = []
// Collection of all star locations
locations = new RObjectContainer<StarLocation>()
// Radius of the universe // Radius of the universe
radius = 5 radius = 5
@ -25,6 +28,13 @@ module TK.SpaceTac {
return result; return result;
} }
/**
* Update the locations list
*/
updateLocations(): void {
this.locations = new RObjectContainer(flatten(this.stars.map(star => star.locations)));
}
/** /**
* Generates a random universe, with star systems and locations of interest * Generates a random universe, with star systems and locations of interest
* *
@ -54,6 +64,7 @@ module TK.SpaceTac {
this.stars.forEach((star: Star) => { this.stars.forEach((star: Star) => {
star.generate(this.random); star.generate(this.random);
}); });
this.updateLocations();
this.addShops(); this.addShops();
} }
@ -256,5 +267,12 @@ module TK.SpaceTac {
let star = minBy(this.stars, star => star.level); let star = minBy(this.stars, star => star.level);
return star.locations[0]; return star.locations[0];
} }
/**
* Get a location from its ID
*/
getLocation(id: RObjectId | null): StarLocation | null {
return id === null ? null : this.locations.get(id);
}
} }
} }

View file

@ -58,8 +58,7 @@ module TK.SpaceTac {
let battle = ship.getBattle(); let battle = ship.getBattle();
if (battle) { if (battle) {
let harmful = any(this.effects, effect => !effect.isBeneficial()); let harmful = any(this.effects, effect => !effect.isBeneficial());
let player = ship.getPlayer(); let ships = imaterialize(harmful ? battle.ienemies(ship, true) : ifilter(battle.iallies(ship, true), iship => !iship.is(ship)));
let ships = imaterialize(harmful ? battle.ienemies(player, true) : ifilter(battle.iallies(player, true), iship => iship != ship));
let nearest = minBy(ships, iship => arenaDistance(ship.location, iship.location)); let nearest = minBy(ships, iship => arenaDistance(ship.location, iship.location));
return Target.newFromShip(nearest); return Target.newFromShip(nearest);
} else { } else {

View file

@ -66,7 +66,7 @@ module TK.SpaceTac {
* Produce all "direct hit" weapon shots. * Produce all "direct hit" weapon shots.
*/ */
static produceDirectShots(ship: Ship, battle: Battle): TacticalProducer { static produceDirectShots(ship: Ship, battle: Battle): TacticalProducer {
let enemies = ifilter(battle.iships(), iship => iship.alive && iship.getPlayer() !== ship.getPlayer()); let enemies = battle.ienemies(ship, true);
let weapons = ifilter(getPlayableActions(ship), action => action instanceof TriggerAction); let weapons = ifilter(getPlayableActions(ship), action => action instanceof TriggerAction);
return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy))); return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy)));
} }
@ -88,7 +88,7 @@ module TK.SpaceTac {
static produceInterestingBlastShots(ship: Ship, battle: Battle): TacticalProducer { static produceInterestingBlastShots(ship: Ship, battle: Battle): TacticalProducer {
// TODO Work with groups of 3, 4 ... // TODO Work with groups of 3, 4 ...
let weapons = <Iterator<TriggerAction>>ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0); let weapons = <Iterator<TriggerAction>>ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0);
let enemies = battle.ienemies(ship.getPlayer(), true); let enemies = battle.ienemies(ship, true);
// FIXME This produces duplicates (x, y) and (y, x) // FIXME This produces duplicates (x, y) and (y, x)
let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2); let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2);
let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.blast * 2); let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.blast * 2);
@ -173,7 +173,7 @@ module TK.SpaceTac {
* Evaluate the effect on health to the enemy, between -1 and 1 * Evaluate the effect on health to the enemy, between -1 and 1
*/ */
static evaluateEnemyHealth(ship: Ship, battle: Battle, maneuver: Maneuver): number { static evaluateEnemyHealth(ship: Ship, battle: Battle, maneuver: Maneuver): number {
let enemies = imaterialize(battle.ienemies(ship.getPlayer(), true)); let enemies = imaterialize(battle.ienemies(ship, true));
return -TacticalAIHelpers.evaluateHealthEffect(maneuver, enemies); return -TacticalAIHelpers.evaluateHealthEffect(maneuver, enemies);
} }
@ -181,7 +181,7 @@ module TK.SpaceTac {
* Evaluate the effect on health to allied ships, between -1 and 1 * Evaluate the effect on health to allied ships, between -1 and 1
*/ */
static evaluateAllyHealth(ship: Ship, battle: Battle, maneuver: Maneuver): number { static evaluateAllyHealth(ship: Ship, battle: Battle, maneuver: Maneuver): number {
let allies = imaterialize(battle.iallies(ship.getPlayer(), true)); let allies = imaterialize(battle.iallies(ship, true));
return TacticalAIHelpers.evaluateHealthEffect(maneuver, allies); return TacticalAIHelpers.evaluateHealthEffect(maneuver, allies);
} }

View file

@ -60,7 +60,7 @@ module TK.SpaceTac.Specs {
let universe = new Universe(); let universe = new Universe();
universe.generate(4); universe.generate(4);
let fleet = new Fleet(); let fleet = new Fleet();
fleet.setLocation(universe.getStartLocation(), true); fleet.setLocation(universe.getStartLocation());
let missions = new ActiveMissions(); let missions = new ActiveMissions();
let hash = missions.getHash(); let hash = missions.getHash();

View file

@ -10,15 +10,17 @@ module TK.SpaceTac.Specs {
}); });
} }
function goTo(fleet: Fleet, location: StarLocation, win_encounter = true) { function goTo(session: GameSession, location: StarLocation, win_encounter = true) {
fleet.setLocation(location, true); session.fleet.setLocation(location);
if (fleet.battle) {
fleet.battle.endBattle(win_encounter ? fleet : fleet.battle.fleets[1]); let battle = session.getBattle();
if (battle) {
battle.endBattle(win_encounter ? session.fleet : battle.fleets[1]);
if (win_encounter) { if (win_encounter) {
fleet.player.exitBattle(); session.exitBattle();
location.clearEncounter(); location.clearEncounter();
} else { } else {
fleet.player.revertBattle(); session.revertBattle();
} }
} }
} }
@ -36,7 +38,7 @@ module TK.SpaceTac.Specs {
(<MissionPartConversation>story.current_part).skip(); (<MissionPartConversation>story.current_part).skip();
checkPart(story, 1, /^Find your contact in .*$/); checkPart(story, 1, /^Find your contact in .*$/);
goTo(fleet, (<MissionPartGoTo>story.current_part).destination); goTo(session, (<MissionPartGoTo>story.current_part).destination);
checkPart(story, 2, /^Speak with your contact/); checkPart(story, 2, /^Speak with your contact/);
(<MissionPartConversation>story.current_part).skip(); (<MissionPartConversation>story.current_part).skip();
@ -45,7 +47,7 @@ module TK.SpaceTac.Specs {
check.same(fleet.ships.length, fleet_size + 1); check.same(fleet.ships.length, fleet_size + 1);
check.same(fleet.ships[fleet_size].critical, true); check.same(fleet.ships[fleet_size].critical, true);
check.greater(fleet.ships[fleet_size].getAttribute("hull_capacity"), 0); check.greater(fleet.ships[fleet_size].getAttribute("hull_capacity"), 0);
goTo(fleet, (<MissionPartEscort>story.current_part).destination); goTo(session, (<MissionPartEscort>story.current_part).destination);
checkPart(story, 4, /^Listen to .*$/); checkPart(story, 4, /^Listen to .*$/);
(<MissionPartConversation>story.current_part).skip(); (<MissionPartConversation>story.current_part).skip();

View file

@ -14,7 +14,7 @@ module TK.SpaceTac {
super(universe, fleet, true); super(universe, fleet, true);
let random = RandomGenerator.global; let random = RandomGenerator.global;
let start_location = nn(fleet.location); let start_location = nn(universe.getLocation(fleet.location));
let mission_generator = new MissionGenerator(universe, start_location); let mission_generator = new MissionGenerator(universe, start_location);
// Arrival // Arrival
@ -26,7 +26,7 @@ module TK.SpaceTac {
// Get in touch with our contact // Get in touch with our contact
let contact_location = randomLocation(random, [start_location.star], [start_location]); let contact_location = randomLocation(random, [start_location.star], [start_location]);
let contact_character = mission_generator.generateShip(1); let contact_character = mission_generator.generateShip(1);
contact_character.fleet.setLocation(contact_location, true); contact_character.fleet.setLocation(contact_location);
this.addPart(new MissionPartGoTo(this, contact_location, `Find your contact in ${contact_location.star.name}`, MissionPartDestinationHint.SYSTEM)); this.addPart(new MissionPartGoTo(this, contact_location, `Find your contact in ${contact_location.star.name}`, MissionPartDestinationHint.SYSTEM));
conversation = this.addPart(new MissionPartConversation(this, [contact_character], "Speak with your contact")); conversation = this.addPart(new MissionPartConversation(this, [contact_character], "Speak with your contact"));
conversation.addPiece(contact_character, "Finally, you came!"); conversation.addPiece(contact_character, "Finally, you came!");

View file

@ -15,7 +15,7 @@ module TK.SpaceTac.Specs {
part.onStarted(); part.onStarted();
check.equals(destination.isClear(), false); check.equals(destination.isClear(), false);
fleet.setLocation(destination, true); fleet.setLocation(destination);
check.same(part.checkCompleted(), false, "Encounter not clear"); check.same(part.checkCompleted(), false, "Encounter not clear");
destination.clearEncounter(); destination.clearEncounter();
@ -28,7 +28,7 @@ module TK.SpaceTac.Specs {
let universe = new Universe(); let universe = new Universe();
let fleet = new Fleet(); let fleet = new Fleet();
fleet.setLocation(destination, true); fleet.setLocation(destination);
let part = new MissionPartCleanLocation(new Mission(universe, fleet), destination); let part = new MissionPartCleanLocation(new Mission(universe, fleet), destination);
check.equals(fleet.battle, null); check.equals(fleet.battle, null);

View file

@ -18,7 +18,7 @@ module TK.SpaceTac {
onStarted(): void { onStarted(): void {
this.destination.setupEncounter(); this.destination.setupEncounter();
if (this.fleet.location == this.destination) { if (this.destination.is(this.fleet.location)) {
// Already there, re-enter the location to start the fight // Already there, re-enter the location to start the fight
let battle = this.destination.enterLocation(this.fleet); let battle = this.destination.enterLocation(this.fleet);
if (battle) { if (battle) {

View file

@ -16,16 +16,16 @@ module TK.SpaceTac.Specs {
part.onStarted(); part.onStarted();
check.contains(fleet.ships, ship); check.contains(fleet.ships, ship);
fleet.setLocation(destination, true); fleet.setLocation(destination);
check.same(part.checkCompleted(), false, "Encounter not clear"); check.same(part.checkCompleted(), false, "Encounter not clear");
destination.clearEncounter(); destination.clearEncounter();
check.same(part.checkCompleted(), true, "Encouter cleared"); check.same(part.checkCompleted(), true, "Encouter cleared");
fleet.setLocation(new StarLocation(), true); fleet.setLocation(new StarLocation());
check.same(part.checkCompleted(), false, "Went to another system"); check.same(part.checkCompleted(), false, "Went to another system");
fleet.setLocation(destination, true); fleet.setLocation(destination);
check.same(part.checkCompleted(), true, "Back at destination"); check.same(part.checkCompleted(), true, "Back at destination");
check.contains(fleet.ships, ship); check.contains(fleet.ships, ship);

View file

@ -11,16 +11,16 @@ module TK.SpaceTac.Specs {
check.equals(part.title, "Go to Atanax system"); check.equals(part.title, "Go to Atanax system");
check.same(part.checkCompleted(), false, "Init location"); check.same(part.checkCompleted(), false, "Init location");
fleet.setLocation(destination, true); fleet.setLocation(destination);
check.same(part.checkCompleted(), false, "Encounter not clear"); check.same(part.checkCompleted(), false, "Encounter not clear");
destination.clearEncounter(); destination.clearEncounter();
check.same(part.checkCompleted(), true, "Encouter cleared"); check.same(part.checkCompleted(), true, "Encouter cleared");
fleet.setLocation(new StarLocation(), true); fleet.setLocation(new StarLocation());
check.same(part.checkCompleted(), false, "Went to another system"); check.same(part.checkCompleted(), false, "Went to another system");
fleet.setLocation(destination, true); fleet.setLocation(destination);
check.same(part.checkCompleted(), true, "Back at destination"); check.same(part.checkCompleted(), true, "Back at destination");
}) })

View file

@ -25,12 +25,12 @@ module TK.SpaceTac {
} }
checkCompleted(): boolean { checkCompleted(): boolean {
return this.fleet.location === this.destination && this.destination.isClear(); return this.destination.is(this.fleet.location) && this.destination.isClear();
} }
forceComplete(): void { forceComplete(): void {
this.destination.clearEncounter(); this.destination.clearEncounter();
this.fleet.setLocation(this.destination, true); this.fleet.setLocation(this.destination);
} }
getLocationHint(): Star | StarLocation | null { getLocationHint(): Star | StarLocation | null {

View file

@ -90,7 +90,8 @@ module TK.SpaceTac.UI.Specs {
view.splash = false; view.splash = false;
let battle = Battle.newQuickRandom(); let battle = Battle.newQuickRandom();
let player = battle.playing_ship ? battle.playing_ship.getPlayer() : new Player(); let player = new Player();
nn(battle.playing_ship).fleet.setPlayer(player);
return [view, [player, battle]]; return [view, [player, battle]];
}); });

View file

@ -13,7 +13,9 @@ module TK.SpaceTac.UI.Specs {
check.equals(bar.action_icons.length, 0); check.equals(bar.action_icons.length, 0);
// Ship with no equipment (only endturn action) // Ship with no equipment (only endturn action)
testgame.view.player = ship.getPlayer(); let player = new Player();
ship.fleet.setPlayer(player);
testgame.view.player = player;
bar.setShip(ship); bar.setShip(ship);
check.equals(bar.action_icons.length, 1); check.equals(bar.action_icons.length, 1);
check.equals(bar.action_icons[0].action.code, "endturn"); check.equals(bar.action_icons[0].action.code, "endturn");

View file

@ -225,7 +225,7 @@ module TK.SpaceTac.UI {
setShip(ship: Ship | null): void { setShip(ship: Ship | null): void {
this.clearAll(); this.clearAll();
if (ship && ship.getPlayer().is(this.battleview.player) && ship.alive) { if (ship && this.battleview.player.is(ship.fleet.player) && ship.alive) {
var actions = ship.getAvailableActions(); var actions = ship.getAvailableActions();
actions.forEach((action: BaseAction) => { actions.forEach((action: BaseAction) => {
this.addAction(ship, action); this.addAction(ship, action);

View file

@ -46,7 +46,7 @@ module TK.SpaceTac.UI {
this.battleview = parent.view; this.battleview = parent.view;
this.ship = ship; this.ship = ship;
this.enemy = !this.ship.getPlayer().is(this.battleview.player); this.enemy = !this.battleview.player.is(this.ship.fleet.player);
// Add effects radius // Add effects radius
this.effects_radius = new Phaser.Graphics(this.game); this.effects_radius = new Phaser.Graphics(this.game);
@ -121,7 +121,7 @@ module TK.SpaceTac.UI {
this.updateEffectsRadius(); this.updateEffectsRadius();
// Set location // Set location
if (this.battleview.battle.cycle == 1 && this.battleview.battle.play_index == 0 && ship.alive && ship.fleet.player === this.battleview.player) { if (this.battleview.battle.cycle == 1 && this.battleview.battle.play_index == 0 && ship.alive && this.battleview.player.is(ship.fleet.player)) {
this.position.set(ship.arena_x - 500 * Math.cos(ship.arena_angle), ship.arena_y - 500 * Math.sin(ship.arena_angle)); this.position.set(ship.arena_x - 500 * Math.cos(ship.arena_angle), ship.arena_y - 500 * Math.sin(ship.arena_angle));
this.moveTo(ship.arena_x, ship.arena_y, ship.arena_angle); this.moveTo(ship.arena_x, ship.arena_y, ship.arena_angle);
} else { } else {

View file

@ -20,7 +20,7 @@ module TK.SpaceTac.UI {
this.player1.visible = false; this.player1.visible = false;
this.message.addChild(this.player1); this.message.addChild(this.player1);
let player1_name = view.game.add.text(-240, 22, fleet1.player.name, { font: `bold 22pt SpaceTac`, fill: "#154d13" }); let player1_name = view.game.add.text(-240, 22, fleet1.name, { font: `bold 22pt SpaceTac`, fill: "#154d13" });
player1_name.anchor.set(0.5); player1_name.anchor.set(0.5);
player1_name.angle = -48; player1_name.angle = -48;
this.player1.addChild(player1_name); this.player1.addChild(player1_name);
@ -41,7 +41,7 @@ module TK.SpaceTac.UI {
this.player2.visible = false; this.player2.visible = false;
this.message.addChild(this.player2); this.message.addChild(this.player2);
let player2_name = view.game.add.text(-240, 22, fleet2.player.name, { font: `bold 22pt SpaceTac`, fill: "#651713" }); let player2_name = view.game.add.text(-240, 22, fleet2.name, { font: `bold 22pt SpaceTac`, fill: "#651713" });
player2_name.anchor.set(0.5); player2_name.anchor.set(0.5);
player2_name.angle = -228; player2_name.angle = -228;
this.player2.addChild(player2_name); this.player2.addChild(player2_name);

View file

@ -136,8 +136,8 @@ module TK.SpaceTac.UI {
this.inputs.bind("Escape", "Cancel action", () => this.action_bar.actionEnded()); this.inputs.bind("Escape", "Cancel action", () => this.action_bar.actionEnded());
range(10).forEach(i => this.inputs.bind(`Numpad${i % 10}`, `Action/target ${i}`, () => this.numberPressed(i))); range(10).forEach(i => this.inputs.bind(`Numpad${i % 10}`, `Action/target ${i}`, () => this.numberPressed(i)));
range(10).forEach(i => this.inputs.bind(`Digit${i % 10}`, `Action/target ${i}`, () => this.numberPressed(i))); range(10).forEach(i => this.inputs.bind(`Digit${i % 10}`, `Action/target ${i}`, () => this.numberPressed(i)));
this.inputs.bindCheat("w", "Win current battle", () => this.actual_battle.cheats.win()); this.inputs.bindCheat("w", "Win current battle", () => nn(this.player.getCheats()).win());
this.inputs.bindCheat("x", "Lose current battle", () => this.actual_battle.cheats.lose()); this.inputs.bindCheat("x", "Lose current battle", () => nn(this.player.getCheats()).lose());
this.inputs.bindCheat("a", "Use AI to play", () => this.playAI()); this.inputs.bindCheat("a", "Use AI to play", () => this.playAI());
// "Battle" animation, then start processing the log // "Battle" animation, then start processing the log
@ -286,7 +286,7 @@ module TK.SpaceTac.UI {
cursorClicked(): void { cursorClicked(): void {
if (this.targetting.active) { if (this.targetting.active) {
this.validationPressed(); this.validationPressed();
} else if (this.ship_hovered && this.ship_hovered.getPlayer().is(this.player) && this.interacting) { } else if (this.ship_hovered && this.player.is(this.ship_hovered.fleet.player) && this.interacting) {
this.character_sheet.show(this.ship_hovered, undefined, undefined, false); this.character_sheet.show(this.ship_hovered, undefined, undefined, false);
this.setShipHovered(null); this.setShipHovered(null);
} }
@ -351,7 +351,7 @@ module TK.SpaceTac.UI {
if (battle.outcome) { if (battle.outcome) {
this.setInteractionEnabled(false); this.setInteractionEnabled(false);
this.gameui.session.setBattleEnded(); this.session.setBattleEnded();
battle.stats.processLog(battle.log, this.player.fleet); battle.stats.processLog(battle.log, this.player.fleet);
@ -365,7 +365,7 @@ module TK.SpaceTac.UI {
* Exit the battle, and go back to map * Exit the battle, and go back to map
*/ */
exitBattle() { exitBattle() {
this.player.exitBattle(); this.session.exitBattle();
this.game.state.start('router'); this.game.state.start('router');
} }
@ -373,7 +373,7 @@ module TK.SpaceTac.UI {
* Revert the battle, and go back to map * Revert the battle, and go back to map
*/ */
revertBattle() { revertBattle() {
this.player.revertBattle(); this.session.revertBattle();
this.game.state.start('router'); this.game.state.start('router');
} }
} }

View file

@ -35,7 +35,7 @@ module TK.SpaceTac.UI {
refreshContent(): void { refreshContent(): void {
let parent = this.battleview; let parent = this.battleview;
let outcome = this.outcome; let outcome = this.outcome;
let victory = outcome.winner && (outcome.winner.player == this.player); let victory = outcome.winner && this.player.is(outcome.winner.player);
this.clearContent(); this.clearContent();

View file

@ -7,13 +7,15 @@ module TK.SpaceTac.UI.Specs {
function createList(): ShipList { function createList(): ShipList {
let view = testgame.view; let view = testgame.view;
let battle = new Battle(); let battle = new Battle();
let player = new Player();
battle.fleets[0].setPlayer(player);
let tactical_mode = new Toggle(); let tactical_mode = new Toggle();
let ship_buttons = { let ship_buttons = {
cursorOnShip: nop, cursorOnShip: nop,
cursorOffShip: nop, cursorOffShip: nop,
cursorClicked: nop, cursorClicked: nop,
}; };
let list = new ShipList(view, battle, battle.fleets[0].player, tactical_mode, ship_buttons); let list = new ShipList(view, battle, player, tactical_mode, ship_buttons);
return list; return list;
} }

View file

@ -5,7 +5,6 @@ module TK.SpaceTac.UI.Specs {
test.case("fills ship details", check => { test.case("fills ship details", check => {
let tooltip = new ShipTooltip(testgame.view); let tooltip = new ShipTooltip(testgame.view);
let ship = testgame.view.battle.play_order[2]; let ship = testgame.view.battle.play_order[2];
ship.fleet.player.name = "Phil";
ship.name = "Fury"; ship.name = "Fury";
ship.model = new ShipModel("fake", "Fury"); ship.model = new ShipModel("fake", "Fury");
ship.listEquipment().forEach(equ => equ.detach()); ship.listEquipment().forEach(equ => equ.detach());

View file

@ -30,7 +30,7 @@ module TK.SpaceTac.UI {
portrait.scale.set(0.5); portrait.scale.set(0.5);
}); });
let enemy = !ship.getPlayer().is(this.battleview.player); let enemy = !this.battleview.player.is(ship.fleet.player);
builder.text(ship.getName(), 168, 0, { color: enemy ? "#cc0d00" : "#ffffff", size: 22, bold: true }); builder.text(ship.getName(), 168, 0, { color: enemy ? "#cc0d00" : "#ffffff", size: 22, bold: true });
if (ship.alive) { if (ship.alive) {

View file

@ -139,7 +139,7 @@ module TK.SpaceTac.UI {
style.image_caption = ship.getName(false); style.image_caption = ship.getName(false);
style.image_size = 256; style.image_size = 256;
let own = ship.getPlayer() == this.view.gameui.session.player; let own = this.view.gameui.session.player.is(ship.fleet.player);
this.setCurrentMessage(style, content, 900, 300, own ? 0.1 : 0.9, own ? 0.2 : 0.8); this.setCurrentMessage(style, content, 900, 300, own ? 0.1 : 0.9, own ? 0.2 : 0.8);
} }

View file

@ -26,8 +26,9 @@ module TK.SpaceTac.UI {
this.updateShipSprites(); this.updateShipSprites();
if (fleet.location) { let location = this.map.universe.getLocation(fleet.location);
this.position.set(fleet.location.star.x + fleet.location.x, fleet.location.star.y + fleet.location.y); if (location) {
this.position.set(location.star.x + location.x, location.star.y + location.y);
} }
this.scale.set(SCALING, SCALING); this.scale.set(SCALING, SCALING);
@ -54,7 +55,7 @@ module TK.SpaceTac.UI {
} }
get location(): StarLocation { get location(): StarLocation {
return this.fleet.location || new StarLocation(); return this.map.universe.getLocation(this.fleet.location) || new StarLocation();
} }
/** /**
@ -90,9 +91,10 @@ module TK.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, on_finished: Function | null = null) { moveToLocation(location: StarLocation, speed = 1, on_leave: ((duration: number) => any) | null = null, on_finished: Function | null = null) {
if (this.fleet.location && location != this.fleet.location) { let fleet_location = this.map.universe.getLocation(this.fleet.location);
let dx = location.universe_x - this.fleet.location.universe_x; if (fleet_location && this.fleet.move(location)) {
let dy = location.universe_y - this.fleet.location.universe_y; let dx = location.universe_x - fleet_location.universe_x;
let dy = location.universe_y - fleet_location.universe_y;
let distance = Math.sqrt(dx * dx + dy * dy); let distance = Math.sqrt(dx * dx + dy * dy);
let angle = Math.atan2(dx, dy); let angle = Math.atan2(dx, dy);
this.map.current_location.setFleetMoving(true); this.map.current_location.setFleetMoving(true);
@ -103,7 +105,6 @@ module TK.SpaceTac.UI {
} }
let tween = this.game.tweens.create(this.position).to({ x: this.x + dx, y: this.y + dy }, duration, Phaser.Easing.Cubic.Out); let tween = this.game.tweens.create(this.position).to({ x: this.x + dx, y: this.y + dy }, duration, Phaser.Easing.Cubic.Out);
tween.onComplete.addOnce(() => { tween.onComplete.addOnce(() => {
this.fleet.setLocation(location);
if (this.fleet.battle) { if (this.fleet.battle) {
this.game.state.start("router"); this.game.state.start("router");
} else { } else {

View file

@ -13,7 +13,7 @@ module TK.SpaceTac.UI {
this.shop = shop; this.shop = shop;
this.player = player; this.player = player;
this.location = player.fleet.location || new StarLocation(); this.location = view.session.getLocation();
this.on_change = on_change || (() => null); this.on_change = on_change || (() => null);
this.refresh(); this.refresh();

View file

@ -4,7 +4,7 @@ module TK.SpaceTac.UI.Specs {
test.case("displays a badge with the current state for a star location", check => { test.case("displays a badge with the current state for a star location", check => {
let mapview = testgame.view; let mapview = testgame.view;
let location = nn(mapview.player.fleet.location); let location = mapview.player_fleet.location;
let ssdisplay = nn(first(mapview.starsystems, ss => ss.starsystem == location.star)); let ssdisplay = nn(first(mapview.starsystems, ss => ss.starsystem == location.star));
@ -15,7 +15,7 @@ module TK.SpaceTac.UI.Specs {
ssdisplay.updateInfo(2, true); ssdisplay.updateInfo(2, true);
check.equals(ldisplay[2].name, "map-status-unvisited"); check.equals(ldisplay[2].name, "map-status-unvisited");
mapview.player.setVisited(ldisplay[0]); mapview.player.fleet.visited.push(ldisplay[0].id);
ssdisplay.updateInfo(2, true); ssdisplay.updateInfo(2, true);
check.equals(ldisplay[2].name, "map-status-enemy"); check.equals(ldisplay[2].name, "map-status-enemy");

View file

@ -50,7 +50,7 @@ module TK.SpaceTac.UI {
let visited = this.player.hasVisitedLocation(location); let visited = this.player.hasVisitedLocation(location);
let shop = (visited && !location.encounter && location.shop) ? " (dockyard present)" : ""; let shop = (visited && !location.encounter && location.shop) ? " (dockyard present)" : "";
if (location == this.player.fleet.location) { if (location.is(this.player.fleet.location)) {
return `Current fleet location${shop}`; return `Current fleet location${shop}`;
} else { } else {
let loctype = StarLocationType[location.type].toLowerCase(); let loctype = StarLocationType[location.type].toLowerCase();

View file

@ -210,7 +210,7 @@ module TK.SpaceTac.UI {
this.starsystems.forEach(system => system.updateInfo(this.zoom, system.starsystem == current_star)); this.starsystems.forEach(system => system.updateInfo(this.zoom, system.starsystem == current_star));
this.actions.setFromLocation(this.player.fleet.location, this); this.actions.setFromLocation(this.session.getLocation(), this);
this.missions.checkUpdate(); this.missions.checkUpdate();
this.conversation.updateFromMissions(this.player.missions, () => this.checkMissionsUpdate()); this.conversation.updateFromMissions(this.player.missions, () => this.checkMissionsUpdate());
@ -226,7 +226,7 @@ module TK.SpaceTac.UI {
revealAll(): void { revealAll(): void {
this.universe.stars.forEach(star => { this.universe.stars.forEach(star => {
star.locations.forEach(location => { star.locations.forEach(location => {
this.player.setVisited(location); this.player.fleet.setVisited(location);
}); });
}); });
this.refresh(); this.refresh();
@ -276,7 +276,7 @@ module TK.SpaceTac.UI {
* Set the current zoom level (0, 1 or 2) * Set the current zoom level (0, 1 or 2)
*/ */
setZoom(level: number, duration = 500) { setZoom(level: number, duration = 500) {
let current_star = this.player.fleet.location ? this.player.fleet.location.star : null; let current_star = this.session.getLocation().star;
if (!current_star || level <= 0) { if (!current_star || level <= 0) {
this.setCamera(0, 0, this.universe.radius * 2, duration); this.setCamera(0, 0, this.universe.radius * 2, duration);
this.setLinksAlpha(1, duration); this.setLinksAlpha(1, duration);
@ -300,7 +300,7 @@ module TK.SpaceTac.UI {
* This will only work if current location is a warp * This will only work if current location is a warp
*/ */
doJump(): void { doJump(): void {
let location = this.player.fleet.location; let location = this.session.getLocation();
if (this.interactive && location && location.type == StarLocationType.WARP && location.jump_dest) { if (this.interactive && location && location.type == StarLocationType.WARP && location.jump_dest) {
let dest_location = location.jump_dest; let dest_location = location.jump_dest;
let dest_star = dest_location.star; let dest_star = dest_location.star;
@ -321,7 +321,7 @@ module TK.SpaceTac.UI {
* This will only work if current location has a dockyard * This will only work if current location has a dockyard
*/ */
openShop(): void { openShop(): void {
let location = this.player.fleet.location; let location = this.session.getLocation();
if (this.interactive && location && location.shop) { if (this.interactive && location && location.shop) {
this.character_sheet.setShop(location.shop); this.character_sheet.setShop(location.shop);
this.character_sheet.show(this.player.fleet.ships[0]); this.character_sheet.show(this.player.fleet.ships[0]);
@ -334,7 +334,7 @@ module TK.SpaceTac.UI {
* This will only work if current location has a dockyard * This will only work if current location has a dockyard
*/ */
openMissions(): void { openMissions(): void {
let location = this.player.fleet.location; let location = this.session.getLocation();
if (this.interactive && location && location.shop) { if (this.interactive && location && location.shop) {
new MissionsDialog(this, location.shop, this.player, () => this.checkMissionsUpdate()); new MissionsDialog(this, location.shop, this.player, () => this.checkMissionsUpdate());
} }
@ -344,7 +344,7 @@ module TK.SpaceTac.UI {
* Move the fleet to another location * Move the fleet to another location
*/ */
moveToLocation(dest: StarLocation): void { moveToLocation(dest: StarLocation): void {
if (this.interactive && dest != this.player.fleet.location) { if (this.interactive && !dest.is(this.player.fleet.location)) {
this.setInteractionEnabled(false); this.setInteractionEnabled(false);
this.player_fleet.moveToLocation(dest, 1, null, () => { this.player_fleet.moveToLocation(dest, 1, null, () => {
this.setInteractionEnabled(true); this.setInteractionEnabled(true);