diff --git a/TODO.md b/TODO.md index 3961a35..d24ff87 100644 --- a/TODO.md +++ b/TODO.md @@ -102,7 +102,6 @@ Common UI 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 sounds * Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile diff --git a/src/core/Battle.spec.ts b/src/core/Battle.spec.ts index ee04ad7..4d06895 100644 --- a/src/core/Battle.spec.ts +++ b/src/core/Battle.spec.ts @@ -269,7 +269,7 @@ module TK.SpaceTac { check.equals(battle.canPlay(player), false); - ship.fleet.player = player; + ship.fleet.setPlayer(player); check.equals(battle.canPlay(player), true); }); @@ -358,9 +358,19 @@ module TK.SpaceTac { 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; - 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 => { diff --git a/src/core/Battle.ts b/src/core/Battle.ts index 322f8a2..fcc1ad7 100644 --- a/src/core/Battle.ts +++ b/src/core/Battle.ts @@ -6,9 +6,6 @@ module TK.SpaceTac { // Battle outcome, if the battle has ended outcome: BattleOutcome | null = null - // Battle cheats - cheats: BattleCheats - // Statistics stats: BattleStats @@ -43,7 +40,7 @@ module TK.SpaceTac { // Indicator that an AI is playing 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.ships = new RObjectContainer(fleet1.ships.concat(fleet2.ships)); this.play_order = []; @@ -52,7 +49,6 @@ module TK.SpaceTac { this.log = new BattleLog(); this.stats = new BattleStats(); - this.cheats = new BattleCheats(this, fleet1.player); this.fleets.forEach((fleet: Fleet) => { fleet.setBattle(this); @@ -123,23 +119,26 @@ module TK.SpaceTac { /** * Return an iterator over ships allies of (or owned by) a player */ - iallies(player: Player, alive_only = false): Iterator { - return ifilter(this.iships(alive_only), ship => ship.getPlayer() === player); + iallies(ship: Ship, alive_only = false): Iterator { + return ifilter(this.iships(alive_only), iship => iship.fleet.player.is(ship.fleet.player)); } /** * Return an iterator over ships enemy of a player */ - ienemies(player: Player, alive_only = false): Iterator { - return ifilter(this.iships(alive_only), ship => ship.getPlayer() !== player); + ienemies(ship: Ship, alive_only = false): Iterator { + 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 { if (this.ended) { 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); } else { return false; diff --git a/src/core/BattleCheats.spec.ts b/src/core/BattleCheats.spec.ts index bf4e7c4..8e99910 100644 --- a/src/core/BattleCheats.spec.ts +++ b/src/core/BattleCheats.spec.ts @@ -2,8 +2,10 @@ module TK.SpaceTac.Specs { testing("BattleCheats", test => { test.case("wins a battle", check => { 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.same(nn(battle.outcome).winner, battle.fleets[0], "winner"); 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 => { 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.same(nn(battle.outcome).winner, battle.fleets[1], "winner"); 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(); TestTools.setShipPlaying(battle, ship); ship.upgradeSkill("skill_materials"); + let cheats = new BattleCheats(battle, battle.fleets[0].player); check.equals(ship.listEquipment(), []); - battle.cheats.equip("Iron Hull"); + + cheats.equip("Iron Hull"); + let result = ship.listEquipment(); check.equals(result.length, 1); check.containing(result[0], { name: "Iron Hull", level: 1 }); diff --git a/src/core/BattleCheats.ts b/src/core/BattleCheats.ts index f4ca3f4..dd50c7c 100644 --- a/src/core/BattleCheats.ts +++ b/src/core/BattleCheats.ts @@ -18,7 +18,7 @@ module TK.SpaceTac { */ win(): void { iforeach(this.battle.iships(), ship => { - if (ship.fleet.player != this.player) { + if (!this.player.is(ship.fleet.player)) { ship.setDead(); } }); @@ -30,11 +30,11 @@ module TK.SpaceTac { */ lose(): void { iforeach(this.battle.iships(), ship => { - if (ship.fleet.player == this.player) { + if (this.player.is(ship.fleet.player)) { 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))); } /** diff --git a/src/core/BattleOutcome.ts b/src/core/BattleOutcome.ts index 2acdff9..7fbbcb2 100644 --- a/src/core/BattleOutcome.ts +++ b/src/core/BattleOutcome.ts @@ -27,7 +27,7 @@ module TK.SpaceTac { this.loot = []; 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 => { var luck = random.random(); if (luck > 0.9) { diff --git a/src/core/Fleet.spec.ts b/src/core/Fleet.spec.ts index 8f20d5c..0b51652 100644 --- a/src/core/Fleet.spec.ts +++ b/src/core/Fleet.spec.ts @@ -49,37 +49,92 @@ module TK.SpaceTac { test.case("changes location, only using jumps to travel between systems", check => { let fleet = new Fleet(); - let system1 = new Star(); - let system2 = new Star(); - let jump1 = new StarLocation(system1, StarLocationType.WARP); - let jump2 = new StarLocation(system2, StarLocationType.WARP); + let universe = new Universe(); + let system1 = universe.addStar(); + let system2 = universe.addStar(); + let jump1 = system1.addLocation(StarLocationType.WARP); + let jump2 = system2.addLocation(StarLocationType.WARP); jump1.setJumpDestination(jump2); jump2.setJumpDestination(jump1); - let other1 = new StarLocation(system1, StarLocationType.PLANET); + let other1 = system1.addLocation(StarLocationType.PLANET); + universe.updateLocations(); - let result = fleet.setLocation(other1); - check.equals(result, true); - check.same(fleet.location, other1); + let result = fleet.move(other1); + check.in("cannot move from nowhere", check => { + check.equals(result, false); + check.equals(fleet.location, null); + }); - result = fleet.setLocation(jump2); - check.equals(result, false); - check.same(fleet.location, other1); + fleet.setLocation(other1); + check.in("force set to other1", check => { + check.equals(fleet.location, other1.id); + }); - result = fleet.setLocation(jump1); - check.equals(result, true); - check.same(fleet.location, jump1); + result = fleet.move(jump2); + check.in("other1=>jump2", check => { + check.equals(result, false); + check.equals(fleet.location, other1.id); + }); - result = fleet.setLocation(jump2); - check.equals(result, true); - check.same(fleet.location, jump2); + result = fleet.move(jump1); + check.in("other1=>jump1", check => { + check.equals(result, true); + check.equals(fleet.location, jump1.id); + }); - result = fleet.setLocation(other1); - check.equals(result, false); - check.same(fleet.location, jump2); + result = fleet.move(jump2); + check.in("jump1=>jump2", check => { + check.equals(result, true); + check.equals(fleet.location, jump2.id); + }); - result = fleet.setLocation(jump1); - check.equals(result, true); - check.same(fleet.location, jump1); + result = fleet.move(other1); + check.in("jump2=>other1", check => { + 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 => { diff --git a/src/core/Fleet.ts b/src/core/Fleet.ts index 961b6c5..1c0281c 100644 --- a/src/core/Fleet.ts +++ b/src/core/Fleet.ts @@ -4,58 +4,98 @@ module TK.SpaceTac { */ export class Fleet { // Fleet owner - player: Player; + player: Player + + // Fleet name + name: string // List of ships - ships: Ship[]; + ships: Ship[] // Current fleet location - location: StarLocation | null = null; - previous_location: StarLocation | null = null; + location: RObjectId | null = null + + // Visited locations (ordered by last visited) + visited: RObjectId[] = [] // Current battle in which the fleet is engaged (null if not fighting) - battle: Battle | null = null; + battle: Battle | null = null // Amount of credits available - credits = 0; + credits = 0 // Create a fleet, bound to a player constructor(player = new Player()) { this.player = player; + this.name = player ? player.name : "Fleet"; this.ships = []; } 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 */ - setLocation(location: StarLocation, force = false): boolean { - if (!force && this.location && location.star != this.location.star && (this.location.type != StarLocationType.WARP || this.location.jump_dest != location)) { + move(to: StarLocation): boolean { + if (!this.location) { return false; } - this.previous_location = this.location; - this.location = location; - this.player.setVisited(this.location); - - // Check encounter - var battle = this.location.enterLocation(this.player.fleet); - if (battle) { - this.player.setBattle(battle); + let source = to.universe.locations.get(this.location); + if (!source) { + return false; } + 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; } + /** + * 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 */ - 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) { remove(ship.fleet.ships, ship); } diff --git a/src/core/GameSession.spec.ts b/src/core/GameSession.spec.ts index b4ffdd2..0ad1b7b 100644 --- a/src/core/GameSession.spec.ts +++ b/src/core/GameSession.spec.ts @@ -40,8 +40,11 @@ module TK.SpaceTac.Specs { let session = new GameSession(); check.equals(session.getBattle(), null); - // Victory case let location1 = new StarLocation(); + let location2 = new StarLocation(location1.star); + session.universe.locations = new RObjectContainer([location1, location2]); + + // Victory case location1.encounter = new Fleet(); session.player.fleet.setLocation(location1); check.notequals(session.getBattle(), null); @@ -56,7 +59,6 @@ module TK.SpaceTac.Specs { check.called(spyloot, 1); // Defeat case - let location2 = new StarLocation(location1.star); location2.encounter = new Fleet(); session.player.fleet.setLocation(location2); check.notequals(session.getBattle(), null); @@ -78,7 +80,7 @@ module TK.SpaceTac.Specs { check.notequals(session.player, null); check.equals(session.player.fleet.ships.length, 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.start_location.shop, null); check.equals(session.start_location.encounter, null); @@ -87,7 +89,47 @@ module TK.SpaceTac.Specs { session.setCampaignFleet(); check.equals(session.player.fleet.ships.length, 2); 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 => { diff --git a/src/core/GameSession.ts b/src/core/GameSession.ts index 9b8f070..a8e86be 100644 --- a/src/core/GameSession.ts +++ b/src/core/GameSession.ts @@ -32,11 +32,18 @@ module TK.SpaceTac { constructor() { this.id = RandomGenerator.global.id(20); this.universe = new Universe(); - this.player = new Player(this.universe); + this.player = new Player(); this.reactions = new PersonalityReactions(); 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) */ @@ -71,7 +78,7 @@ module TK.SpaceTac { this.start_location.clearEncounter(); this.start_location.removeShop(); - this.player = new Player(this.universe); + this.player = new Player(); this.reactions = new PersonalityReactions(); @@ -88,34 +95,52 @@ module TK.SpaceTac { setCampaignFleet(fleet: Fleet | null = null, story = true) { if (fleet) { this.player.fleet = fleet; - fleet.player = this.player; + fleet.setPlayer(this.player); } else { let fleet_generator = new FleetGenerator(); 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) { 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 { + this.universe = new Universe(); + 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.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 { 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() { let battle = this.getBattle(); @@ -133,13 +158,34 @@ module TK.SpaceTac { } // 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) { 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) */ diff --git a/src/core/PersonalityReactions.spec.ts b/src/core/PersonalityReactions.spec.ts index f108acd..e7f049b 100644 --- a/src/core/PersonalityReactions.spec.ts +++ b/src/core/PersonalityReactions.spec.ts @@ -51,15 +51,17 @@ module TK.SpaceTac.Specs { test.case("checks for friendly fire", check => { let condition = BUILTIN_REACTION_POOL['friendly_fire'][0]; let battle = new Battle(); + let player = new Player(); + battle.fleets[0].setPlayer(player); let ship1a = battle.fleets[0].addShip(); let ship1b = battle.fleets[0].addShip(); let ship2a = 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(ship1a.getPlayer(), 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(ship1a.getPlayer(), battle, ship2a, new ShipDamageDiff(ship2a, 50, 10)), [], "other player event"); + check.equals(condition(player, battle, ship1a, new ShipDamageDiff(ship1a, 50, 10)), [], "self shoot"); + check.equals(condition(player, battle, ship1a, new ShipDamageDiff(ship1b, 50, 10)), [ship1b, ship1a]); + check.equals(condition(player, battle, ship1a, new ShipDamageDiff(ship2a, 50, 10)), [], "enemy shoot"); + check.equals(condition(player, battle, ship2a, new ShipDamageDiff(ship2a, 50, 10)), [], "other player event"); }) }) } diff --git a/src/core/PersonalityReactions.ts b/src/core/PersonalityReactions.ts index 2654221..d260310 100644 --- a/src/core/PersonalityReactions.ts +++ b/src/core/PersonalityReactions.ts @@ -93,9 +93,9 @@ module TK.SpaceTac { */ function cond_friendly_fire(player: Player, battle: Battle | null, ship: Ship | null, event: BaseBattleDiff | null): Ship[] { 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); - return (hurt && hurt.getPlayer().is(player)) ? [hurt, ship] : []; + return (hurt && player.is(hurt.fleet.player)) ? [hurt, ship] : []; } else { return []; } diff --git a/src/core/Player.spec.ts b/src/core/Player.spec.ts index 4456410..7ebd5c2 100644 --- a/src/core/Player.spec.ts +++ b/src/core/Player.spec.ts @@ -2,12 +2,14 @@ module TK.SpaceTac { testing("Player", test => { test.case("keeps track of visited locations", check => { let player = new Player(); - let star1 = new Star(); - let star2 = new Star(); - let loc1a = new StarLocation(star1); - let loc1b = new StarLocation(star1); - let loc2a = new StarLocation(star2); - let loc2b = new StarLocation(star2); + let universe = new Universe(); + let star1 = universe.addStar(); + let star2 = universe.addStar(); + let loc1a = star1.addLocation(StarLocationType.PLANET); + let loc1b = star1.addLocation(StarLocationType.PLANET); + 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) { check.same(player.hasVisitedSystem(star1), s1); @@ -20,51 +22,17 @@ module TK.SpaceTac { checkVisited(); - player.setVisited(loc1b); - + player.fleet.setLocation(loc1b); checkVisited(true, false, false, true, false, false); - player.setVisited(loc1a); - + player.fleet.setLocation(loc1a); checkVisited(true, false, true, true, false, false); - player.setVisited(loc2a); - + player.fleet.setLocation(loc2a); checkVisited(true, true, true, true, true, false); - player.setVisited(loc2a); - + player.fleet.setLocation(loc2a); 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)); - }); }); } \ No newline at end of file diff --git a/src/core/Player.ts b/src/core/Player.ts index cc27735..ba3906b 100644 --- a/src/core/Player.ts +++ b/src/core/Player.ts @@ -8,57 +8,54 @@ module TK.SpaceTac { // Player's name name: string - // Universe in which we are playing - universe: Universe - - // Current fleet + // Bound fleet fleet: Fleet - // List of visited star systems - visited: StarLocation[] = [] - // Active missions missions = new ActiveMissions() // Create a player, with an empty fleet - constructor(universe: Universe = new Universe(), name = "Player") { + constructor(name = "Player", fleet?: Fleet) { super(); - this.universe = universe; 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 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(); player.fleet = generator.generate(level, player, shipcount, upgrade); 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. */ 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. */ hasVisitedLocation(location: StarLocation): boolean { - return contains(this.visited, location); - } - - /** - * 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(); + return contains(this.fleet.visited, location.id); } // Get currently played battle, null when none is in progress @@ -69,25 +66,5 @@ module TK.SpaceTac { this.fleet.setBattle(battle); 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); - } - } } } diff --git a/src/core/Ship.ts b/src/core/Ship.ts index fcbbee6..c7d7977 100644 --- a/src/core/Ship.ts +++ b/src/core/Ship.ts @@ -120,7 +120,9 @@ module TK.SpaceTac { this.play_priority = gen.random() * this.attributes.maneuvrability.get(); } - // Return the player owning this ship + /** + * Return the player that plays this ship + */ getPlayer(): Player { return this.fleet.player; } @@ -129,7 +131,7 @@ module TK.SpaceTac { * Check if a player is playing this ship */ isPlayedBy(player: Player): boolean { - return this.getPlayer().is(player); + return player.is(this.fleet.player); } // get the current battle this ship is engaged in diff --git a/src/core/StarLocation.ts b/src/core/StarLocation.ts index 7ecec02..5902d3f 100644 --- a/src/core/StarLocation.ts +++ b/src/core/StarLocation.ts @@ -1,3 +1,5 @@ +/// + module TK.SpaceTac { export enum StarLocationType { STAR, @@ -7,34 +9,41 @@ module TK.SpaceTac { 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 - star: Star; + star: Star // Type of location - type: StarLocationType; + type: StarLocationType // Location in the star system - x: number; - y: number; + x: number + y: number // Absolute location in the universe - universe_x: number; - universe_y: number; + universe_x: number + universe_y: number // 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 - encounter: Fleet | null = null; - encounter_gen = false; - encounter_random = RandomGenerator.global; + encounter: Fleet | null = null + encounter_gen = false + encounter_random = RandomGenerator.global // 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) { + super(); + this.star = star; this.type = type; this.x = x; @@ -44,6 +53,13 @@ module TK.SpaceTac { this.jump_dest = null; } + /** + * Get the universe containing this location + */ + get universe(): Universe { + return this.star.universe; + } + /** * Add a shop in this location */ @@ -58,6 +74,22 @@ module TK.SpaceTac { 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 */ @@ -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]]; } 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); } /** diff --git a/src/core/TestTools.ts b/src/core/TestTools.ts index 7c4419c..158d95d 100644 --- a/src/core/TestTools.ts +++ b/src/core/TestTools.ts @@ -4,8 +4,8 @@ module TK.SpaceTac { // 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 { - var fleet1 = new Fleet(new Player(undefined, "Attacker")); - var fleet2 = new Fleet(new Player(undefined, "Defender")); + var fleet1 = new Fleet(new Player("Attacker")); + var fleet2 = new Fleet(new Player("Defender")); while (own_ships--) { fleet1.addShip(); diff --git a/src/core/Universe.ts b/src/core/Universe.ts index 5b5666b..c637012 100644 --- a/src/core/Universe.ts +++ b/src/core/Universe.ts @@ -9,6 +9,9 @@ module TK.SpaceTac { // List of links between star systems starlinks: StarLink[] = [] + // Collection of all star locations + locations = new RObjectContainer() + // Radius of the universe radius = 5 @@ -25,6 +28,13 @@ module TK.SpaceTac { 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 * @@ -54,6 +64,7 @@ module TK.SpaceTac { this.stars.forEach((star: Star) => { star.generate(this.random); }); + this.updateLocations(); this.addShops(); } @@ -256,5 +267,12 @@ module TK.SpaceTac { let star = minBy(this.stars, star => star.level); return star.locations[0]; } + + /** + * Get a location from its ID + */ + getLocation(id: RObjectId | null): StarLocation | null { + return id === null ? null : this.locations.get(id); + } } } diff --git a/src/core/actions/TriggerAction.ts b/src/core/actions/TriggerAction.ts index 6694089..8f7ee8a 100644 --- a/src/core/actions/TriggerAction.ts +++ b/src/core/actions/TriggerAction.ts @@ -58,8 +58,7 @@ module TK.SpaceTac { let battle = ship.getBattle(); if (battle) { let harmful = any(this.effects, effect => !effect.isBeneficial()); - let player = ship.getPlayer(); - let ships = imaterialize(harmful ? battle.ienemies(player, true) : ifilter(battle.iallies(player, true), iship => iship != ship)); + let ships = imaterialize(harmful ? battle.ienemies(ship, true) : ifilter(battle.iallies(ship, true), iship => !iship.is(ship))); let nearest = minBy(ships, iship => arenaDistance(ship.location, iship.location)); return Target.newFromShip(nearest); } else { diff --git a/src/core/ai/TacticalAIHelpers.ts b/src/core/ai/TacticalAIHelpers.ts index 4dc270c..7b3177f 100644 --- a/src/core/ai/TacticalAIHelpers.ts +++ b/src/core/ai/TacticalAIHelpers.ts @@ -66,7 +66,7 @@ module TK.SpaceTac { * Produce all "direct hit" weapon shots. */ 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); 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 { // TODO Work with groups of 3, 4 ... let weapons = >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) 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); @@ -173,7 +173,7 @@ module TK.SpaceTac { * Evaluate the effect on health to the enemy, between -1 and 1 */ 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); } @@ -181,7 +181,7 @@ module TK.SpaceTac { * Evaluate the effect on health to allied ships, between -1 and 1 */ 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); } diff --git a/src/core/missions/ActiveMissions.spec.ts b/src/core/missions/ActiveMissions.spec.ts index 9de31fb..e17a262 100644 --- a/src/core/missions/ActiveMissions.spec.ts +++ b/src/core/missions/ActiveMissions.spec.ts @@ -60,7 +60,7 @@ module TK.SpaceTac.Specs { let universe = new Universe(); universe.generate(4); let fleet = new Fleet(); - fleet.setLocation(universe.getStartLocation(), true); + fleet.setLocation(universe.getStartLocation()); let missions = new ActiveMissions(); let hash = missions.getHash(); diff --git a/src/core/missions/MainStory.spec.ts b/src/core/missions/MainStory.spec.ts index f753f9a..2652243 100644 --- a/src/core/missions/MainStory.spec.ts +++ b/src/core/missions/MainStory.spec.ts @@ -10,15 +10,17 @@ module TK.SpaceTac.Specs { }); } - function goTo(fleet: Fleet, location: StarLocation, win_encounter = true) { - fleet.setLocation(location, true); - if (fleet.battle) { - fleet.battle.endBattle(win_encounter ? fleet : fleet.battle.fleets[1]); + function goTo(session: GameSession, location: StarLocation, win_encounter = true) { + session.fleet.setLocation(location); + + let battle = session.getBattle(); + if (battle) { + battle.endBattle(win_encounter ? session.fleet : battle.fleets[1]); if (win_encounter) { - fleet.player.exitBattle(); + session.exitBattle(); location.clearEncounter(); } else { - fleet.player.revertBattle(); + session.revertBattle(); } } } @@ -36,7 +38,7 @@ module TK.SpaceTac.Specs { (story.current_part).skip(); checkPart(story, 1, /^Find your contact in .*$/); - goTo(fleet, (story.current_part).destination); + goTo(session, (story.current_part).destination); checkPart(story, 2, /^Speak with your contact/); (story.current_part).skip(); @@ -45,7 +47,7 @@ module TK.SpaceTac.Specs { check.same(fleet.ships.length, fleet_size + 1); check.same(fleet.ships[fleet_size].critical, true); check.greater(fleet.ships[fleet_size].getAttribute("hull_capacity"), 0); - goTo(fleet, (story.current_part).destination); + goTo(session, (story.current_part).destination); checkPart(story, 4, /^Listen to .*$/); (story.current_part).skip(); diff --git a/src/core/missions/MainStory.ts b/src/core/missions/MainStory.ts index 5971c75..b768a79 100644 --- a/src/core/missions/MainStory.ts +++ b/src/core/missions/MainStory.ts @@ -14,7 +14,7 @@ module TK.SpaceTac { super(universe, fleet, true); 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); // Arrival @@ -26,7 +26,7 @@ module TK.SpaceTac { // Get in touch with our contact let contact_location = randomLocation(random, [start_location.star], [start_location]); 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)); conversation = this.addPart(new MissionPartConversation(this, [contact_character], "Speak with your contact")); conversation.addPiece(contact_character, "Finally, you came!"); diff --git a/src/core/missions/MissionPartCleanLocation.spec.ts b/src/core/missions/MissionPartCleanLocation.spec.ts index 210ccc6..b70c8a6 100644 --- a/src/core/missions/MissionPartCleanLocation.spec.ts +++ b/src/core/missions/MissionPartCleanLocation.spec.ts @@ -15,7 +15,7 @@ module TK.SpaceTac.Specs { part.onStarted(); check.equals(destination.isClear(), false); - fleet.setLocation(destination, true); + fleet.setLocation(destination); check.same(part.checkCompleted(), false, "Encounter not clear"); destination.clearEncounter(); @@ -28,7 +28,7 @@ module TK.SpaceTac.Specs { let universe = new Universe(); let fleet = new Fleet(); - fleet.setLocation(destination, true); + fleet.setLocation(destination); let part = new MissionPartCleanLocation(new Mission(universe, fleet), destination); check.equals(fleet.battle, null); diff --git a/src/core/missions/MissionPartCleanLocation.ts b/src/core/missions/MissionPartCleanLocation.ts index 5ca47f1..53a1672 100644 --- a/src/core/missions/MissionPartCleanLocation.ts +++ b/src/core/missions/MissionPartCleanLocation.ts @@ -18,7 +18,7 @@ module TK.SpaceTac { onStarted(): void { 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 let battle = this.destination.enterLocation(this.fleet); if (battle) { diff --git a/src/core/missions/MissionPartEscort.spec.ts b/src/core/missions/MissionPartEscort.spec.ts index 873a0d0..6e69613 100644 --- a/src/core/missions/MissionPartEscort.spec.ts +++ b/src/core/missions/MissionPartEscort.spec.ts @@ -16,16 +16,16 @@ module TK.SpaceTac.Specs { part.onStarted(); check.contains(fleet.ships, ship); - fleet.setLocation(destination, true); + fleet.setLocation(destination); check.same(part.checkCompleted(), false, "Encounter not clear"); destination.clearEncounter(); 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"); - fleet.setLocation(destination, true); + fleet.setLocation(destination); check.same(part.checkCompleted(), true, "Back at destination"); check.contains(fleet.ships, ship); diff --git a/src/core/missions/MissionPartGoTo.spec.ts b/src/core/missions/MissionPartGoTo.spec.ts index 97418a9..5dcc84f 100644 --- a/src/core/missions/MissionPartGoTo.spec.ts +++ b/src/core/missions/MissionPartGoTo.spec.ts @@ -11,16 +11,16 @@ module TK.SpaceTac.Specs { check.equals(part.title, "Go to Atanax system"); check.same(part.checkCompleted(), false, "Init location"); - fleet.setLocation(destination, true); + fleet.setLocation(destination); check.same(part.checkCompleted(), false, "Encounter not clear"); destination.clearEncounter(); 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"); - fleet.setLocation(destination, true); + fleet.setLocation(destination); check.same(part.checkCompleted(), true, "Back at destination"); }) diff --git a/src/core/missions/MissionPartGoTo.ts b/src/core/missions/MissionPartGoTo.ts index 1d40382..d103031 100644 --- a/src/core/missions/MissionPartGoTo.ts +++ b/src/core/missions/MissionPartGoTo.ts @@ -25,12 +25,12 @@ module TK.SpaceTac { } checkCompleted(): boolean { - return this.fleet.location === this.destination && this.destination.isClear(); + return this.destination.is(this.fleet.location) && this.destination.isClear(); } forceComplete(): void { this.destination.clearEncounter(); - this.fleet.setLocation(this.destination, true); + this.fleet.setLocation(this.destination); } getLocationHint(): Star | StarLocation | null { diff --git a/src/ui/TestGame.ts b/src/ui/TestGame.ts index 1b4e705..d011768 100644 --- a/src/ui/TestGame.ts +++ b/src/ui/TestGame.ts @@ -90,7 +90,8 @@ module TK.SpaceTac.UI.Specs { view.splash = false; 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]]; }); diff --git a/src/ui/battle/ActionBar.spec.ts b/src/ui/battle/ActionBar.spec.ts index 4ca222e..8e997cc 100644 --- a/src/ui/battle/ActionBar.spec.ts +++ b/src/ui/battle/ActionBar.spec.ts @@ -13,7 +13,9 @@ module TK.SpaceTac.UI.Specs { check.equals(bar.action_icons.length, 0); // 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); check.equals(bar.action_icons.length, 1); check.equals(bar.action_icons[0].action.code, "endturn"); diff --git a/src/ui/battle/ActionBar.ts b/src/ui/battle/ActionBar.ts index 8191699..41086af 100644 --- a/src/ui/battle/ActionBar.ts +++ b/src/ui/battle/ActionBar.ts @@ -225,7 +225,7 @@ module TK.SpaceTac.UI { setShip(ship: Ship | null): void { 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(); actions.forEach((action: BaseAction) => { this.addAction(ship, action); diff --git a/src/ui/battle/ArenaShip.ts b/src/ui/battle/ArenaShip.ts index 4815a9a..cbb7c34 100644 --- a/src/ui/battle/ArenaShip.ts +++ b/src/ui/battle/ArenaShip.ts @@ -46,7 +46,7 @@ module TK.SpaceTac.UI { this.battleview = parent.view; 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 this.effects_radius = new Phaser.Graphics(this.game); @@ -121,7 +121,7 @@ module TK.SpaceTac.UI { this.updateEffectsRadius(); // 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.moveTo(ship.arena_x, ship.arena_y, ship.arena_angle); } else { diff --git a/src/ui/battle/BattleSplash.ts b/src/ui/battle/BattleSplash.ts index 2dc3069..2cdfb2a 100644 --- a/src/ui/battle/BattleSplash.ts +++ b/src/ui/battle/BattleSplash.ts @@ -20,7 +20,7 @@ module TK.SpaceTac.UI { this.player1.visible = false; 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.angle = -48; this.player1.addChild(player1_name); @@ -41,7 +41,7 @@ module TK.SpaceTac.UI { this.player2.visible = false; 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.angle = -228; this.player2.addChild(player2_name); diff --git a/src/ui/battle/BattleView.ts b/src/ui/battle/BattleView.ts index 49091cd..8cba1e3 100644 --- a/src/ui/battle/BattleView.ts +++ b/src/ui/battle/BattleView.ts @@ -136,8 +136,8 @@ module TK.SpaceTac.UI { 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(`Digit${i % 10}`, `Action/target ${i}`, () => this.numberPressed(i))); - this.inputs.bindCheat("w", "Win current battle", () => this.actual_battle.cheats.win()); - this.inputs.bindCheat("x", "Lose current battle", () => this.actual_battle.cheats.lose()); + this.inputs.bindCheat("w", "Win current battle", () => nn(this.player.getCheats()).win()); + this.inputs.bindCheat("x", "Lose current battle", () => nn(this.player.getCheats()).lose()); this.inputs.bindCheat("a", "Use AI to play", () => this.playAI()); // "Battle" animation, then start processing the log @@ -286,7 +286,7 @@ module TK.SpaceTac.UI { cursorClicked(): void { if (this.targetting.active) { 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.setShipHovered(null); } @@ -351,7 +351,7 @@ module TK.SpaceTac.UI { if (battle.outcome) { this.setInteractionEnabled(false); - this.gameui.session.setBattleEnded(); + this.session.setBattleEnded(); battle.stats.processLog(battle.log, this.player.fleet); @@ -365,7 +365,7 @@ module TK.SpaceTac.UI { * Exit the battle, and go back to map */ exitBattle() { - this.player.exitBattle(); + this.session.exitBattle(); this.game.state.start('router'); } @@ -373,7 +373,7 @@ module TK.SpaceTac.UI { * Revert the battle, and go back to map */ revertBattle() { - this.player.revertBattle(); + this.session.revertBattle(); this.game.state.start('router'); } } diff --git a/src/ui/battle/OutcomeDialog.ts b/src/ui/battle/OutcomeDialog.ts index 09dc1c1..e4b472d 100644 --- a/src/ui/battle/OutcomeDialog.ts +++ b/src/ui/battle/OutcomeDialog.ts @@ -35,7 +35,7 @@ module TK.SpaceTac.UI { refreshContent(): void { let parent = this.battleview; 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(); diff --git a/src/ui/battle/ShipList.spec.ts b/src/ui/battle/ShipList.spec.ts index b76b01b..86a5342 100644 --- a/src/ui/battle/ShipList.spec.ts +++ b/src/ui/battle/ShipList.spec.ts @@ -7,13 +7,15 @@ module TK.SpaceTac.UI.Specs { function createList(): ShipList { let view = testgame.view; let battle = new Battle(); + let player = new Player(); + battle.fleets[0].setPlayer(player); let tactical_mode = new Toggle(); let ship_buttons = { cursorOnShip: nop, cursorOffShip: 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; } diff --git a/src/ui/battle/ShipTooltip.spec.ts b/src/ui/battle/ShipTooltip.spec.ts index e9ff635..0a7d91b 100644 --- a/src/ui/battle/ShipTooltip.spec.ts +++ b/src/ui/battle/ShipTooltip.spec.ts @@ -5,7 +5,6 @@ module TK.SpaceTac.UI.Specs { test.case("fills ship details", check => { let tooltip = new ShipTooltip(testgame.view); let ship = testgame.view.battle.play_order[2]; - ship.fleet.player.name = "Phil"; ship.name = "Fury"; ship.model = new ShipModel("fake", "Fury"); ship.listEquipment().forEach(equ => equ.detach()); diff --git a/src/ui/battle/ShipTooltip.ts b/src/ui/battle/ShipTooltip.ts index 0514576..c653e6c 100644 --- a/src/ui/battle/ShipTooltip.ts +++ b/src/ui/battle/ShipTooltip.ts @@ -30,7 +30,7 @@ module TK.SpaceTac.UI { 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 }); if (ship.alive) { diff --git a/src/ui/common/UIConversation.ts b/src/ui/common/UIConversation.ts index 803ca62..234eeb2 100644 --- a/src/ui/common/UIConversation.ts +++ b/src/ui/common/UIConversation.ts @@ -139,7 +139,7 @@ module TK.SpaceTac.UI { style.image_caption = ship.getName(false); 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); } diff --git a/src/ui/map/FleetDisplay.ts b/src/ui/map/FleetDisplay.ts index bdd89f9..140718e 100644 --- a/src/ui/map/FleetDisplay.ts +++ b/src/ui/map/FleetDisplay.ts @@ -26,8 +26,9 @@ module TK.SpaceTac.UI { this.updateShipSprites(); - if (fleet.location) { - this.position.set(fleet.location.star.x + fleet.location.x, fleet.location.star.y + fleet.location.y); + let location = this.map.universe.getLocation(fleet.location); + if (location) { + this.position.set(location.star.x + location.x, location.star.y + location.y); } this.scale.set(SCALING, SCALING); @@ -54,7 +55,7 @@ module TK.SpaceTac.UI { } 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 */ 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 dx = location.universe_x - this.fleet.location.universe_x; - let dy = location.universe_y - this.fleet.location.universe_y; + let fleet_location = this.map.universe.getLocation(this.fleet.location); + if (fleet_location && this.fleet.move(location)) { + 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 angle = Math.atan2(dx, dy); 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); tween.onComplete.addOnce(() => { - this.fleet.setLocation(location); if (this.fleet.battle) { this.game.state.start("router"); } else { diff --git a/src/ui/map/MissionsDialog.ts b/src/ui/map/MissionsDialog.ts index 96bf968..4ce361d 100644 --- a/src/ui/map/MissionsDialog.ts +++ b/src/ui/map/MissionsDialog.ts @@ -13,7 +13,7 @@ module TK.SpaceTac.UI { this.shop = shop; this.player = player; - this.location = player.fleet.location || new StarLocation(); + this.location = view.session.getLocation(); this.on_change = on_change || (() => null); this.refresh(); diff --git a/src/ui/map/StarSystemDisplay.spec.ts b/src/ui/map/StarSystemDisplay.spec.ts index adac832..39a82e1 100644 --- a/src/ui/map/StarSystemDisplay.spec.ts +++ b/src/ui/map/StarSystemDisplay.spec.ts @@ -4,7 +4,7 @@ module TK.SpaceTac.UI.Specs { test.case("displays a badge with the current state for a star location", check => { 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)); @@ -15,7 +15,7 @@ module TK.SpaceTac.UI.Specs { ssdisplay.updateInfo(2, true); 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); check.equals(ldisplay[2].name, "map-status-enemy"); diff --git a/src/ui/map/StarSystemDisplay.ts b/src/ui/map/StarSystemDisplay.ts index 3e5349f..af1f1a3 100644 --- a/src/ui/map/StarSystemDisplay.ts +++ b/src/ui/map/StarSystemDisplay.ts @@ -50,7 +50,7 @@ module TK.SpaceTac.UI { let visited = this.player.hasVisitedLocation(location); 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}`; } else { let loctype = StarLocationType[location.type].toLowerCase(); diff --git a/src/ui/map/UniverseMapView.ts b/src/ui/map/UniverseMapView.ts index 9bae4ad..b6ac100 100644 --- a/src/ui/map/UniverseMapView.ts +++ b/src/ui/map/UniverseMapView.ts @@ -210,7 +210,7 @@ module TK.SpaceTac.UI { 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.conversation.updateFromMissions(this.player.missions, () => this.checkMissionsUpdate()); @@ -226,7 +226,7 @@ module TK.SpaceTac.UI { revealAll(): void { this.universe.stars.forEach(star => { star.locations.forEach(location => { - this.player.setVisited(location); + this.player.fleet.setVisited(location); }); }); this.refresh(); @@ -276,7 +276,7 @@ module TK.SpaceTac.UI { * Set the current zoom level (0, 1 or 2) */ 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) { this.setCamera(0, 0, this.universe.radius * 2, duration); this.setLinksAlpha(1, duration); @@ -300,7 +300,7 @@ module TK.SpaceTac.UI { * This will only work if current location is a warp */ doJump(): void { - let location = this.player.fleet.location; + let location = this.session.getLocation(); if (this.interactive && location && location.type == StarLocationType.WARP && location.jump_dest) { let dest_location = location.jump_dest; let dest_star = dest_location.star; @@ -321,7 +321,7 @@ module TK.SpaceTac.UI { * This will only work if current location has a dockyard */ openShop(): void { - let location = this.player.fleet.location; + let location = this.session.getLocation(); if (this.interactive && location && location.shop) { this.character_sheet.setShop(location.shop); 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 */ openMissions(): void { - let location = this.player.fleet.location; + let location = this.session.getLocation(); if (this.interactive && location && location.shop) { new MissionsDialog(this, location.shop, this.player, () => this.checkMissionsUpdate()); } @@ -344,7 +344,7 @@ module TK.SpaceTac.UI { * Move the fleet to another location */ 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.player_fleet.moveToLocation(dest, 1, null, () => { this.setInteractionEnabled(true);