From c8c08dc4cd947c973c0e42e18f0a2fc09e7e6069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Sun, 9 Jul 2017 23:18:05 +0200 Subject: [PATCH] Fixed display when an escorted ship is added to the fleet --- .vscode/launch.json | 19 +++++++++++ TODO.md | 4 +-- src/core/ShipModel.ts | 24 ++++++++------ src/core/Shop.ts | 11 ++++--- src/core/Universe.ts | 6 +++- src/core/missions/ActiveMissions.spec.ts | 40 ++++++++++++++++++++++++ src/core/missions/ActiveMissions.ts | 28 +++++++++++++++-- src/core/missions/Mission.ts | 24 +++++++++++++- src/core/missions/MissionGenerator.ts | 27 +++++++++++++++- src/ui/map/ActiveMissionsDisplay.spec.ts | 2 +- src/ui/map/ActiveMissionsDisplay.ts | 18 ++++++++++- src/ui/map/FleetDisplay.ts | 31 ++++++++++++------ src/ui/map/MapLocationMenu.ts | 2 +- src/ui/map/MissionsDialog.ts | 5 ++- src/ui/map/UniverseMapView.ts | 31 ++++++++++++++++-- 15 files changed, 235 insertions(+), 37 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bc0674e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run game in Chrome", + "type": "chrome", + "request": "launch", + "url": "http://localhost:8012", + "webRoot": "${workspaceRoot}/out" + }, + { + "name": "Run tests in Chrome", + "type": "chrome", + "request": "launch", + "url": "http://localhost:8012/tests.html", + "webRoot": "${workspaceRoot}/out" + } + ] +} \ No newline at end of file diff --git a/TODO.md b/TODO.md index fdc9048..fd57971 100644 --- a/TODO.md +++ b/TODO.md @@ -14,10 +14,11 @@ Map/story * Fix quickly zooming in twice preventing to display some UI parts * Enemy fleet size should start low and increase with system level * Allow to change/buy ship model -* Fix galaxy generator sometimes being short on systems to create a proper level gradient (mainly in unit testing) * Add ship personality (with icons to identify ?), with reaction dialogs * Add factions and reputation * Add generated missions with rewards +* Allow to cancel secondary missions +* Forbid to end up with more than 5 ships in the fleet because of escorts * Show missions' destination near systems/locations Character sheet @@ -29,7 +30,6 @@ Character sheet * When transferring to another ship, if the item can't be equipped (unmatched requirements), the transfer is cancelled instead of trying cargo * Effective skill is sometimes not updated when upgrading base skill * Tooltip to show the sources of attributes -* Fix ship list not refreshing when escorted ship is added or removed * Forbid to modify escorted ship * Add merged cargo display for the whole fleet diff --git a/src/core/ShipModel.ts b/src/core/ShipModel.ts index ce2fa09..6433f03 100644 --- a/src/core/ShipModel.ts +++ b/src/core/ShipModel.ts @@ -48,17 +48,21 @@ module TS.SpaceTac { return result; } - // Pick a random model in the default collection - static getRandomModel(level: Number, random: RandomGenerator = new RandomGenerator()): ShipModel { - var collection = this.getDefaultCollection(); - collection = collection.filter((model: ShipModel) => { - return model.level <= level; - }); - var result = random.choice(collection); - if (!result) { - console.error("Couldn't pick a random model for level " + level.toString()); + /** + * Pick a random model in the default collection + */ + static getRandomModel(level?: number, random: RandomGenerator = new RandomGenerator()): ShipModel { + let collection = this.getDefaultCollection(); + if (level) { + collection = collection.filter(model => model.level <= level); + } + + if (collection.length == 0) { + console.error("Couldn't pick a random model"); + return new ShipModel("undefined", "Undefined"); + } else { + return random.choice(collection); } - return result; } } } diff --git a/src/core/Shop.ts b/src/core/Shop.ts index 5f94786..e094c8d 100644 --- a/src/core/Shop.ts +++ b/src/core/Shop.ts @@ -121,10 +121,13 @@ module TS.SpaceTac { * Returns true on success */ acceptMission(mission: Mission, player: Player): boolean { - if (player.missions.secondary.length < 2 && remove(this.missions, mission)) { - mission.fleet = player.fleet; - add(player.missions.secondary, mission); - return true; + if (contains(this.missions, mission)) { + if (player.missions.addSecondary(mission, player.fleet)) { + remove(this.missions, mission); + return true; + } else { + return false; + } } else { return false; } diff --git a/src/core/Universe.ts b/src/core/Universe.ts index 6fa4b03..8fb1fe8 100644 --- a/src/core/Universe.ts +++ b/src/core/Universe.ts @@ -212,7 +212,11 @@ module TS.SpaceTac { // Choose two systems to be the lowest and highest danger zones (not connected directly) let lowest = this.random.choice(this.stars.filter(star => star.getLinks().length > 1)); - let highest = this.random.choice(this.stars.filter(star => star != lowest && !star.getLinkTo(lowest))); + let highest_choices = this.stars.filter(star => star != lowest && !star.getLinkTo(lowest)); + if (highest_choices.length == 0) { + highest_choices = this.stars.filter(star => star != lowest); + } + let highest = this.random.choice(highest_choices); highest.level = maximal; // Make danger gradients diff --git a/src/core/missions/ActiveMissions.spec.ts b/src/core/missions/ActiveMissions.spec.ts index 3487f66..3833b2a 100644 --- a/src/core/missions/ActiveMissions.spec.ts +++ b/src/core/missions/ActiveMissions.spec.ts @@ -55,5 +55,45 @@ module TS.SpaceTac.Specs { ]); expect(missions.main).toBeNull(); }) + + it("builds a hash to help monitor status changes", function () { + let universe = new Universe(); + universe.generate(4); + let fleet = new Fleet(); + fleet.setLocation(universe.getStartLocation(), true); + + let missions = new ActiveMissions(); + let hash = missions.getHash(); + function checkChanged(info: string, expected = true) { + let new_hash = missions.getHash(); + expect(new_hash != hash).toBe(expected, info); + hash = new_hash; + expect(missions.getHash()).toEqual(hash, "Stable after " + info); + } + checkChanged("Stable at init", false); + + missions.startMainStory(universe, fleet); + checkChanged("Main story started"); + + let mission = new Mission(universe, fleet); + mission.addPart(new MissionPartConversation(mission, [new Ship()])); + mission.addPart(new MissionPartConversation(mission, [new Ship()])); + missions.addSecondary(mission, fleet); + checkChanged("Secondary mission accepted"); + + expect(mission.getIndex()).toBe(0); + missions.checkStatus(); + expect(mission.getIndex()).toBe(1); + checkChanged("First conversation ended"); + + expect(missions.secondary.length).toBe(1); + missions.checkStatus(); + expect(missions.secondary.length).toBe(0); + checkChanged("Second conversation ended - mission removed"); + + nn(missions.main).current_part.forceComplete(); + missions.checkStatus(); + checkChanged("Main mission progress"); + }); }) } diff --git a/src/core/missions/ActiveMissions.ts b/src/core/missions/ActiveMissions.ts index d56269c..83a4b82 100644 --- a/src/core/missions/ActiveMissions.ts +++ b/src/core/missions/ActiveMissions.ts @@ -5,15 +5,30 @@ module TS.SpaceTac { export class ActiveMissions { main: Mission | null = null secondary: Mission[] = [] - - constructor() { - } + nextid = 2 /** * Start the main story arc */ startMainStory(universe: Universe, fleet: Fleet) { this.main = new MainStory(universe, fleet); + this.main.setStarted(1); + } + + /** + * Add a secondary mission to the pool + * + * Returns true on success + */ + addSecondary(mission: Mission, fleet: Fleet): boolean { + if (!mission.main && this.secondary.length < 2) { + mission.fleet = fleet; + this.secondary.push(mission); + mission.setStarted(this.nextid++); + return true; + } else { + return false; + } } /** @@ -40,5 +55,12 @@ module TS.SpaceTac { } this.secondary = this.secondary.filter(mission => mission.checkStatus()); } + + /** + * Get a hash that will change when any active mission changes status + */ + getHash(): number { + return sum(this.getCurrent().map(mission => mission.id * 10000 + mission.getIndex())); + } } } diff --git a/src/core/missions/Mission.ts b/src/core/missions/Mission.ts index d354b3d..3cdc688 100644 --- a/src/core/missions/Mission.ts +++ b/src/core/missions/Mission.ts @@ -24,6 +24,9 @@ module TS.SpaceTac { // Title of this mission (should be kept short) title: string + // Numerical identifier + id = -1 + constructor(universe: Universe, fleet = new Fleet(), main = false) { this.universe = universe; this.fleet = fleet; @@ -48,6 +51,25 @@ module TS.SpaceTac { return part; } + /** + * Get the index of current part + */ + getIndex(): number { + return this.parts.indexOf(this.current_part); + } + + /** + * Set the mission as started (start the first part) + */ + setStarted(id: number): void { + if (this.id < 0) { + this.id = id; + if (this.current_part) { + this.current_part.onStarted(); + } + } + } + /** * Check the status for current part, and move on to next part if necessary. * @@ -59,7 +81,7 @@ module TS.SpaceTac { } else if (this.current_part.checkCompleted()) { this.current_part.onEnded(); - let current_index = this.parts.indexOf(this.current_part); + let current_index = this.getIndex(); if (current_index < 0 || current_index >= this.parts.length - 1) { this.completed = true; return false; diff --git a/src/core/missions/MissionGenerator.ts b/src/core/missions/MissionGenerator.ts index f522f64..87bacb7 100644 --- a/src/core/missions/MissionGenerator.ts +++ b/src/core/missions/MissionGenerator.ts @@ -1,4 +1,19 @@ module TS.SpaceTac { + const POOL_SHIP_NAMES = [ + "Zert", + "Ob'tec", + "Paayk", + "Fen_amr", + "TempZst", + "croNt", + "Appn", + "Vertix", + "Opan-vel", + "Yz-aol", + "Arkant", + "PNX", + ] + /** * Random generator of secondary missions that can be taken from */ @@ -29,12 +44,22 @@ module TS.SpaceTac { return result; } + /** + * Generate a new ship + */ + private generateShip() { + let generator = new ShipGenerator(this.random); + let result = generator.generate(this.level, null, true); + result.name = `${this.random.choice(POOL_SHIP_NAMES)}-${this.random.randInt(10, 999)}`; + return result; + } + /** * Generate an escort mission */ generateEscort(): Mission { let mission = new Mission(this.universe); - let ship = new Ship(); + let ship = this.generateShip(); let dest_star = minBy(this.around.star.getNeighbors(), star => Math.abs(star.level - this.level)); let destination = this.random.choice(dest_star.locations); mission.addPart(new MissionPartEscort(mission, destination, ship)); diff --git a/src/ui/map/ActiveMissionsDisplay.spec.ts b/src/ui/map/ActiveMissionsDisplay.spec.ts index b47bff9..93efe48 100644 --- a/src/ui/map/ActiveMissionsDisplay.spec.ts +++ b/src/ui/map/ActiveMissionsDisplay.spec.ts @@ -14,7 +14,7 @@ module TS.SpaceTac.UI.Specs { mission.addPart(new MissionPart(mission, "Get back to base")); missions.secondary = [mission]; - display.update(); + display.checkUpdate(); expect(container.children.length).toBe(2); expect(container.children[0] instanceof Phaser.Image).toBe(true); checkText(container.children[1], "Get back to base"); diff --git a/src/ui/map/ActiveMissionsDisplay.ts b/src/ui/map/ActiveMissionsDisplay.ts index 4990788..3ffbd93 100644 --- a/src/ui/map/ActiveMissionsDisplay.ts +++ b/src/ui/map/ActiveMissionsDisplay.ts @@ -6,18 +6,34 @@ module TS.SpaceTac.UI { */ export class ActiveMissionsDisplay extends UIComponent { private missions: ActiveMissions + private hash: number constructor(parent: BaseView, missions: ActiveMissions) { super(parent, 520, 240); this.missions = missions; + this.hash = missions.getHash(); this.update(); } + /** + * Check if the active missions' status changed + */ + checkUpdate(): boolean { + let new_hash = this.missions.getHash(); + if (new_hash != this.hash) { + this.hash = new_hash; + this.update(); + return true; + } else { + return false; + } + } + /** * Update the current missions list */ - update() { + private update() { this.clearContent(); let active = this.missions.getCurrent(); diff --git a/src/ui/map/FleetDisplay.ts b/src/ui/map/FleetDisplay.ts index f978dff..a564ca6 100644 --- a/src/ui/map/FleetDisplay.ts +++ b/src/ui/map/FleetDisplay.ts @@ -13,9 +13,10 @@ module TS.SpaceTac.UI { * Group to display a fleet */ export class FleetDisplay extends Phaser.Group { - private map: UniverseMapView; - private fleet: Fleet; - private tween: Phaser.Tween; + private map: UniverseMapView + private fleet: Fleet + private tween: Phaser.Tween + private ship_count = 0 constructor(parent: UniverseMapView, fleet: Fleet) { super(parent.game); @@ -23,12 +24,7 @@ module TS.SpaceTac.UI { this.map = parent; this.fleet = fleet; - fleet.ships.forEach((ship, index) => { - let offset = LOCATIONS[index]; - let sprite = this.game.add.image(offset[0], offset[1] + 150, `ship-${ship.model.code}-sprite`, 0, this); - sprite.scale.set(64 / sprite.width); - sprite.anchor.set(0.5, 0.5); - }); + this.updateShipSprites(); if (fleet.location) { this.position.set(fleet.location.star.x + fleet.location.x, fleet.location.star.y + fleet.location.y); @@ -39,6 +35,23 @@ module TS.SpaceTac.UI { this.loopOrbit(); } + /** + * Update the ship sprites + */ + updateShipSprites() { + if (this.ship_count != this.fleet.ships.length) { + this.removeAll(true); + this.fleet.ships.forEach((ship, index) => { + let offset = LOCATIONS[index]; + let sprite = this.game.add.image(offset[0], offset[1] + 150, `ship-${ship.model.code}-sprite`, 0, this); + sprite.scale.set(64 / sprite.width); + sprite.anchor.set(0.5, 0.5); + }); + + this.ship_count = this.fleet.ships.length; + } + } + get location(): StarLocation { return this.fleet.location || new StarLocation(); } diff --git a/src/ui/map/MapLocationMenu.ts b/src/ui/map/MapLocationMenu.ts index f96cde9..6c720e9 100644 --- a/src/ui/map/MapLocationMenu.ts +++ b/src/ui/map/MapLocationMenu.ts @@ -36,7 +36,7 @@ module TS.SpaceTac.UI { if (location.shop) { let shop = location.shop; actions.push(["Go to dockyard", () => view.openShop()]); - actions.push(["Show jobs", () => new MissionsDialog(view, shop, view.player)]); + actions.push(["Show jobs", () => view.openMissions()]); } switch (location.type) { diff --git a/src/ui/map/MissionsDialog.ts b/src/ui/map/MissionsDialog.ts index 24392e9..a49b204 100644 --- a/src/ui/map/MissionsDialog.ts +++ b/src/ui/map/MissionsDialog.ts @@ -6,13 +6,15 @@ module TS.SpaceTac.UI { shop: Shop player: Player location: StarLocation + on_change: Function - constructor(view: BaseView, shop: Shop, player: Player) { + constructor(view: BaseView, shop: Shop, player: Player, on_change?: Function) { super(view); this.shop = shop; this.player = player; this.location = player.fleet.location || new StarLocation(); + this.on_change = on_change || (() => null); this.refresh(); } @@ -46,6 +48,7 @@ module TS.SpaceTac.UI { this.addMission(offset, mission.title, "Reward: ???", 2, () => { this.shop.acceptMission(mission, this.player); this.refresh(); + this.on_change(); }); offset += 110; }); diff --git a/src/ui/map/UniverseMapView.ts b/src/ui/map/UniverseMapView.ts index 18bb8be..f336e6b 100644 --- a/src/ui/map/UniverseMapView.ts +++ b/src/ui/map/UniverseMapView.ts @@ -155,6 +155,19 @@ module TS.SpaceTac.UI { */ refresh() { this.setZoom(this.zoom); + this.character_sheet.updateFleet(this.player.fleet); + this.player_fleet.updateShipSprites(); + } + + /** + * Check active missions. + * + * When any mission status changes, a refresh is triggered. + */ + checkMissionsUpdate() { + if (this.missions.checkUpdate()) { + this.refresh(); + } } /** @@ -172,10 +185,12 @@ module TS.SpaceTac.UI { this.actions.setFromLocation(this.player.fleet.location, this); - this.missions.update(); + this.missions.checkUpdate(); this.conversation.updateFromMissions(this.player.missions, () => { this.player.missions.checkStatus(); - this.missions.update(); + if (this.missions.checkUpdate()) { + this.refresh(); + } }); if (interactive) { @@ -279,6 +294,18 @@ module TS.SpaceTac.UI { } } + /** + * Open the missions dialog (job posting) + * + * This will only work if current location has a dockyard + */ + openMissions(): void { + let location = this.player.fleet.location; + if (this.interactive && location && location.shop) { + new MissionsDialog(this, location.shop, this.player, () => this.checkMissionsUpdate()); + } + } + /** * Move the fleet to another location */