diff --git a/src/view/TestGame.ts b/src/view/TestGame.ts index c9e46f9..f215f5d 100644 --- a/src/view/TestGame.ts +++ b/src/view/TestGame.ts @@ -1,4 +1,5 @@ /// +/// module TS.SpaceTac.View.Specs { // Test game wrapper (use instead of jasmine 'it') @@ -42,4 +43,14 @@ module TS.SpaceTac.View.Specs { func(battleview); }, battleview, player, battle); } + + // Test game wrapper, with a map initialized on a random universe + export function inmapview_it(desc: string, func: (mapview: UniverseMapView) => void) { + var mapview = new UniverseMapView(); + var session = new Game.GameSession(); + session.startNewGame(); + ingame_it(desc, (game: Phaser.Game, state: Phaser.State) => { + func(mapview); + }, mapview, session.universe, session.player); + } } diff --git a/src/view/map/FleetDisplay.spec.ts b/src/view/map/FleetDisplay.spec.ts new file mode 100644 index 0000000..bf78b6d --- /dev/null +++ b/src/view/map/FleetDisplay.spec.ts @@ -0,0 +1,18 @@ +module TS.SpaceTac.View.Specs { + describe("FleetDisplay", () => { + inmapview_it("orbits the fleet around its current location", mapview => { + let fleet = mapview.player_fleet; + + fleet.loopOrbit(); + expect(fleet.rotation).toBe(0); + + mapview.game.tweens.update(); + let tween = first(mapview.game.tweens.getAll(), tw => tw.target == fleet); + let tweendata = tween.generateData(0.1); + expect(tweendata.length).toEqual(3); + expect(tweendata[0].rotation).toBeCloseTo(-Math.PI * 2 / 3); + expect(tweendata[1].rotation).toBeCloseTo(-Math.PI * 4 / 3); + expect(tweendata[2].rotation).toBeCloseTo(-Math.PI * 2); + }); + }); +} diff --git a/src/view/map/FleetDisplay.ts b/src/view/map/FleetDisplay.ts new file mode 100644 index 0000000..01aa414 --- /dev/null +++ b/src/view/map/FleetDisplay.ts @@ -0,0 +1,92 @@ +module TS.SpaceTac.View { + const SCALING = 0.00005; + const LOCATIONS: [number, number][] = [ + [80, 0], + [0, -50], + [0, 50], + [-80, 0], + [0, 0], + ]; + const PI2 = Math.PI * 2; + + /** + * Group to display a fleet + */ + export class FleetDisplay extends Phaser.Group { + private map: UniverseMapView; + private fleet: Game.Fleet; + private tween: Phaser.Tween; + + constructor(parent: UniverseMapView, fleet: Game.Fleet) { + super(parent.game); + + 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}-sprite`, 0, this); + sprite.anchor.set(0.5, 0.5); + }); + + this.position.set(fleet.location.star.x + fleet.location.x, fleet.location.star.y + fleet.location.y); + this.scale.set(SCALING, SCALING); + + this.tween = this.game.tweens.create(this); + this.loopOrbit(); + } + + /** + * Animate to a given position in orbit of its current star location + */ + goToOrbitPoint(angle: number, speed = 1, then: Function | null = null, ease = false) { + this.tween.stop(false); + this.rotation %= PI2; + + let target = -angle; + while (target >= this.rotation) { + target -= PI2; + } + let distance = Math.abs(target - this.rotation) / PI2; + this.tween = this.game.tweens.create(this).to({ rotation: target }, 30000 * distance / speed, ease ? Phaser.Easing.Cubic.In : Phaser.Easing.Linear.None); + if (then) { + this.tween.onComplete.addOnce(then); + } + this.tween.start(); + } + + /** + * Make the fleet loop in orbit + */ + loopOrbit() { + this.goToOrbitPoint(this.rotation + PI2, 1, () => { + this.loopOrbit(); + }); + } + + /** + * Make the fleet move to another location in the same system + */ + moveToLocation(location: Game.StarLocation) { + if (location != this.fleet.location) { + let dx = location.x - this.fleet.location.x; + let dy = location.y - this.fleet.location.y; + let distance = Math.sqrt(dx * dx + dy * dy); + let angle = Math.atan2(dx, dy); + this.goToOrbitPoint(angle - Math.PI / 2, 20, () => { + let tween = this.game.tweens.create(this.position).to({ x: this.x + dx, y: this.y + dy }, 10000 * distance, Phaser.Easing.Cubic.Out); + tween.onComplete.addOnce(() => { + this.fleet.setLocation(location); + if (this.fleet.battle) { + this.game.state.start("router"); + } else { + this.map.updateInfo(); + this.loopOrbit(); + } + }); + tween.start(); + }, true); + } + } + } +} \ No newline at end of file diff --git a/src/view/map/StarSystemDisplay.ts b/src/view/map/StarSystemDisplay.ts index c012edf..2d24865 100644 --- a/src/view/map/StarSystemDisplay.ts +++ b/src/view/map/StarSystemDisplay.ts @@ -2,42 +2,52 @@ module TS.SpaceTac.View { // Group to display a star system export class StarSystemDisplay extends Phaser.Image { starsystem: Game.Star; + player: Game.Player; + fleet_display: FleetDisplay; + locations: [Game.StarLocation, Phaser.Image, Phaser.Image][] = []; constructor(parent: UniverseMapView, starsystem: Game.Star) { super(parent.game, starsystem.x, starsystem.y, "map-starsystem-background"); + this.anchor.set(0.5, 0.5); let scale = this.width; this.scale.set(starsystem.radius * 2 / scale); this.starsystem = starsystem; + this.player = parent.player; + this.fleet_display = parent.player_fleet; // Show boundary this.addCircle(starsystem.radius); // Show locations - starsystem.locations.forEach(location => { + starsystem.locations.map(location => { let location_sprite: Phaser.Image | null = null; + let fleet_move = () => this.fleet_display.moveToLocation(location); if (location.type == Game.StarLocationType.STAR) { - location_sprite = this.addImage(location.x, location.y, "map-location-star"); + location_sprite = this.addImage(location.x, location.y, "map-location-star", fleet_move); } else if (location.type == Game.StarLocationType.PLANET) { - location_sprite = this.addImage(location.x, location.y, "map-location-planet"); + location_sprite = this.addImage(location.x, location.y, "map-location-planet", fleet_move); location_sprite.rotation = Math.atan2(location.y, location.x); this.addCircle(Math.sqrt(location.x * location.x + location.y * location.y), 1); } else if (location.type == Game.StarLocationType.WARP) { - location_sprite = this.addImage(location.x, location.y, "map-location-warp"); + location_sprite = this.addImage(location.x, location.y, "map-location-warp", fleet_move); } if (location_sprite) { - let key = parent.player.hasVisitedLocation(location) ? (location.encounter ? "map-state-enemy" : "map-state-clear") : "map-state-unknown"; - this.addImage(location.x + 0.005, location.y + 0.005, key); + let key = this.getVisitedKey(location); + let status_badge = this.addImage(location.x + 0.005, location.y + 0.005, key); + this.locations.push([location, location_sprite, status_badge]); } }); } - addImage(x: number, y: number, key: string): Phaser.Image { - let image = this.game.add.image(x / this.scale.x, y / this.scale.y, key); + addImage(x: number, y: number, key: string, onclick: Function | null = null): Phaser.Image { + x /= this.scale.x; + y /= this.scale.y; + let image = onclick ? this.game.add.button(x, y, key, onclick) : this.game.add.image(x, y, key); image.anchor.set(0.5, 0.5); this.addChild(image); return image; @@ -50,5 +60,21 @@ module TS.SpaceTac.View { this.addChild(circle); return circle; } + + /** + * Return the sprite code to use for visited status. + */ + getVisitedKey(location: Game.StarLocation) { + return this.player.hasVisitedLocation(location) ? (location.encounter ? "map-state-enemy" : "map-state-clear") : "map-state-unknown"; + } + + /** + * Update displayed information, and fog of war + */ + updateInfo() { + this.locations.forEach(info => { + info[2].loadTexture(this.getVisitedKey(info[0])); + }); + } } } diff --git a/src/view/map/UniverseMapView.ts b/src/view/map/UniverseMapView.ts index b8e367c..e18286f 100644 --- a/src/view/map/UniverseMapView.ts +++ b/src/view/map/UniverseMapView.ts @@ -14,6 +14,9 @@ module TS.SpaceTac.View { starsystems: StarSystemDisplay[] = []; starlinks: Phaser.Graphics[] = []; + // Fleets + player_fleet: FleetDisplay; + // Zoom level zoom = 0; @@ -44,9 +47,13 @@ module TS.SpaceTac.View { }); this.starlinks.forEach(starlink => this.group.addChild(starlink)); + this.player_fleet = new FleetDisplay(this, this.player.fleet); + this.starsystems = this.universe.stars.map(star => new StarSystemDisplay(this, star)); this.starsystems.forEach(starsystem => this.group.addChild(starsystem)); + this.group.addChild(this.player_fleet); + this.setZoom(2); this.add.button(1830, 100, "map-zoom-in", () => this.setZoom(this.zoom + 1)).anchor.set(0.5, 0.5); this.add.button(1830, 980, "map-zoom-out", () => this.setZoom(this.zoom - 1)).anchor.set(0.5, 0.5); @@ -65,6 +72,11 @@ module TS.SpaceTac.View { super.shutdown(); } + // Update info on all star systems (fog of war, available data...) + updateInfo() { + this.starsystems.forEach(system => system.updateInfo()); + } + // Reveal the whole map (this is a cheat) revealAll(): void { this.universe.stars.forEach(star => {