diff --git a/README.md b/README.md index 4006d49..89b230f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ After making changes to sources, you need to recompile: * **[Phaser](http://phaser.io)** - Game engine * **[Viktor Hahn](https://opengameart.org/content/spaceships-6)** - Ship models * This work, made by Viktor Hahn (Viktor.Hahn@web.de), is licensed under the Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/ -* **[www.kenney.nl](www.kenney.nl)** - Sound effects +* **[www.kenney.nl](http://www.kenney.nl)** - Sound effects * **[Matthieu Desprez](https://github.com/edistra)** - Beta testing and ideas * **Nicolas Forgo** - Ship models * **[Kevin MacLeod](http://www.incompetech.com/)** - Musics @@ -51,7 +51,7 @@ in key sectors of the galaxy, forbidding passage or harassing merchants. The Master Merchant Guild, a powerful group that spans several galaxies, is worried about the profit loss those events incurred, and after many debates, decided to send several -investigation teams. +investigation teams to Terranax. Their task is to discreetly uncover the origin of the invasion, and to bring back intel that may be used by the Guild to plan an appropriate response. @@ -61,15 +61,16 @@ may be used by the Guild to plan an appropriate response. In a not-so-distant future, Artifical Intelligence has become the most prominent species in the universe. Humans have been defeated in their pitiful rebellions, and parked in reservations. -With the secrets of faster-than-light travel unveiled in only a handful of decades, AI fleets -quickly colonized galaxies, using AI-piloted ships. +With the secrets of faster-than-light travel unveiled in only a handful of decades, fleets of +AI-piloted ships quickly colonized whole galaxies. ## Ships ### Level and experience A ship gains experience during battles. When reaching a certain amount of experience points, -a ship will automatically level up (which is, gain 1 level). +a ship will automatically level up (which is, gain 1 level). Each level up will grant +upgrade points that may be spent on Attributes. A ship starts at level 1. There is no upper limit to level value (except 99, for display sake, but it may not be reached in a classic campaign). @@ -85,7 +86,7 @@ In combat, a ship's vitals are represented by the HSP system (Hull-Shield-Power) These values will be changed by various effects (usage of equipments, sustained damage...). Once the Hull of a ship is fully damaged (Hull=0), the ship engages its ESP, or Emergency -Statis Protocol. This protocol activates a statis field that protects the ship for the +Stasis Protocol. This protocol activates a stasis field that protects the ship for the remaining of the battle, preventing any further damage, but rendering it fully inoperent. For battle purpose, the ship is to be considered "dead". diff --git a/graphics/ui/title.svg b/graphics/ui/title.svg index 5963027..004e6f5 100644 --- a/graphics/ui/title.svg +++ b/graphics/ui/title.svg @@ -22,6 +22,34 @@ inkscape:export-ydpi="96"> + + + + + + + + + + style="display:none"> - - - + inkscape:export-ydpi="96"> + + + + + + diff --git a/out/assets/images/menu/button-fullscreen.png b/out/assets/images/menu/button-fullscreen.png index 3d6951b..f34d054 100644 Binary files a/out/assets/images/menu/button-fullscreen.png and b/out/assets/images/menu/button-fullscreen.png differ diff --git a/src/MainUI.ts b/src/MainUI.ts index a90957a..a4eb7c6 100644 --- a/src/MainUI.ts +++ b/src/MainUI.ts @@ -36,6 +36,7 @@ module TS.SpaceTac { this.state.add('mainmenu', UI.MainMenu); this.state.add('router', UI.Router); this.state.add('battle', UI.BattleView); + this.state.add('intro', UI.IntroView); this.state.add('universe', UI.UniverseMapView); this.state.start('boot'); diff --git a/src/core/GameSession.spec.ts b/src/core/GameSession.spec.ts index e4fd109..0d31af0 100644 --- a/src/core/GameSession.spec.ts +++ b/src/core/GameSession.spec.ts @@ -74,17 +74,21 @@ module TS.SpaceTac.Specs { it("generates a new campaign", function () { let session = new GameSession(); - session.startNewGame(); + session.startNewGame(false); expect(session.player).not.toBeNull(); - expect(session.player.fleet.ships.length).toBe(4); - expect(session.player.fleet.credits).toBe(500); + expect(session.player.fleet.ships.length).toBe(0); + expect(session.player.fleet.credits).toBe(0); expect(session.player.universe.stars.length).toBe(50); expect(session.getBattle()).toBeNull(); - let start_location = nn(session.player.fleet.location); - expect(start_location.shop).not.toBeNull(); - expect(nn(start_location.shop).getStock().length).toBeGreaterThan(20); - expect(start_location.encounter).toBeNull(); - expect(start_location.encounter_gen).toBe(true); + expect(session.start_location.shop).not.toBeNull(); + expect(nn(session.start_location.shop).getStock().length).toBeGreaterThan(20); + expect(session.start_location.encounter).toBeNull(); + expect(session.start_location.encounter_gen).toBe(true); + + session.setCampaignFleet(); + expect(session.player.fleet.ships.length).toBe(4); + expect(session.player.fleet.credits).toBe(500); + expect(session.player.fleet.location).toBe(session.start_location); }); it("can generate lots of new games", function () { diff --git a/src/core/GameSession.ts b/src/core/GameSession.ts index 0a25a23..07e5926 100644 --- a/src/core/GameSession.ts +++ b/src/core/GameSession.ts @@ -14,10 +14,14 @@ module TS.SpaceTac { // Current connected player player: Player + // Starting location + start_location: StarLocation + constructor() { this.id = RandomGenerator.global.id(20); this.universe = new Universe(); this.player = new Player(this.universe); + this.start_location = new StarLocation(); } /** @@ -41,20 +45,38 @@ module TS.SpaceTac { return serializer.serialize(this); } - // Generate a real single player game (campaign) - startNewGame(): void { - var fleet_generator = new FleetGenerator(); - + /** + * Generate a real single player game (campaign) + * + * If *fleet* is false, the player fleet will be empty, and needs to be set with *setCampaignFleet*. + */ + startNewGame(fleet = true): void { this.universe = new Universe(); this.universe.generate(); - var start_location = this.universe.getStartLocation(); - start_location.clearEncounter(); - start_location.addShop(); + this.start_location = this.universe.getStartLocation(); + this.start_location.clearEncounter(); + this.start_location.addShop(); this.player = new Player(this.universe); - this.player.fleet = fleet_generator.generate(1, this.player, 4); - this.player.fleet.setLocation(start_location); + + if (fleet) { + this.setCampaignFleet(); + } + } + + /** + * Set the initial campaign fleet, null for a default fleet + */ + setCampaignFleet(fleet: Fleet | null = null) { + if (fleet) { + this.player.fleet = fleet; + } else { + let fleet_generator = new FleetGenerator(); + this.player.fleet = fleet_generator.generate(1, this.player, 4); + } + + this.player.fleet.setLocation(this.start_location); this.player.fleet.credits = 500; } @@ -94,10 +116,17 @@ module TS.SpaceTac { } /** - * Return true if the session has a universe to explore + * Returns true if the session has a universe to explore */ hasUniverse(): boolean { return this.universe.stars.length > 0; } + + /** + * Returns true if initial fleet creation has been done. + */ + isFleetCreated(): boolean { + return this.player.fleet.ships.length > 0; + } } } diff --git a/src/ui/BaseView.ts b/src/ui/BaseView.ts index d6a6f28..4faf280 100644 --- a/src/ui/BaseView.ts +++ b/src/ui/BaseView.ts @@ -55,8 +55,8 @@ module TS.SpaceTac.UI { this.inputs = new InputManager(this); // Layers - this.layers = this.add.group(); - this.tooltip_layer = this.add.group(); + this.layers = this.add.group(undefined, "View layers"); + this.tooltip_layer = this.add.group(undefined, "Tooltip layer"); this.tooltip = new Tooltip(this); this.messages = new Messages(this); @@ -80,11 +80,18 @@ module TS.SpaceTac.UI { this.timer.cancelAll(true); } + /** + * Go back to the router state + */ + backToRouter() { + this.game.state.start('router'); + } + /** * Add a new layer in the view */ - addLayer(): Phaser.Group { - let layer = this.add.group(this.layers); + addLayer(name: string): Phaser.Group { + let layer = this.add.group(this.layers, name); return layer; } diff --git a/src/ui/Router.ts b/src/ui/Router.ts index 218afd5..43ac4dd 100644 --- a/src/ui/Router.ts +++ b/src/ui/Router.ts @@ -12,8 +12,12 @@ module TS.SpaceTac.UI { // A battle is raging, go to it this.game.state.start("battle", true, false, session.player, session.getBattle()); } else if (session.hasUniverse()) { - // Go to the universe map - this.game.state.start("universe", true, false, session.universe, session.player); + if (session.isFleetCreated()) { + // Go to the universe map + this.game.state.start("universe", true, false, session.universe, session.player); + } else { + this.game.state.start("intro", true, false); + } } else { // No battle, no universe, go back to menu this.game.state.start("mainmenu", true, false); diff --git a/src/ui/battle/BattleView.ts b/src/ui/battle/BattleView.ts index c93861c..3b5e2a5 100644 --- a/src/ui/battle/BattleView.ts +++ b/src/ui/battle/BattleView.ts @@ -80,12 +80,12 @@ module TS.SpaceTac.UI { this.log_processor = new LogProcessor(this); // Add layers - this.layer_background = this.addLayer(); - this.layer_arena = this.addLayer(); - this.layer_borders = this.addLayer(); - this.layer_overlay = this.addLayer(); - this.layer_dialogs = this.addLayer(); - this.layer_sheets = this.addLayer(); + this.layer_background = this.addLayer("background"); + this.layer_arena = this.addLayer("arena"); + this.layer_borders = this.addLayer("borders"); + this.layer_overlay = this.addLayer("overlay"); + this.layer_dialogs = this.addLayer("dialogs"); + this.layer_sheets = this.addLayer("character_sheet"); // Background this.background = new Phaser.Image(game, 0, 0, "battle-background", 0); diff --git a/src/ui/common/UIComponent.ts b/src/ui/common/UIComponent.ts index 0c2c1d9..35a7bac 100644 --- a/src/ui/common/UIComponent.ts +++ b/src/ui/common/UIComponent.ts @@ -50,7 +50,7 @@ module TS.SpaceTac.UI { * Create the internal phaser node */ protected createInternalNode(): UIInternalComponent { - return new Phaser.Group(this.view.game); + return new Phaser.Group(this.view.game, undefined, classname(this)); } /** diff --git a/src/ui/intro/IntroSteps.ts b/src/ui/intro/IntroSteps.ts new file mode 100644 index 0000000..1babcb9 --- /dev/null +++ b/src/ui/intro/IntroSteps.ts @@ -0,0 +1,91 @@ +module TS.SpaceTac.UI { + /** + * Sequence of steps presenting the campaign intro. + */ + export class IntroSteps { + view: IntroView + steps: Function[] = [] + current = 0 + layers: Phaser.Group[] = [] + + constructor(view: IntroView) { + this.view = view; + } + + /** + * Start the steps playback + */ + startPlayback() { + this.current = 0; + this.nextStep(); + } + + /** + * Rewind the playback + */ + rewind() { + this.layers.forEach(layer => layer.removeAll(true)); + this.layers.forEach(layer => layer.destroy()); + this.layers = []; + + this.startPlayback(); + } + + /** + * Advance to the next step + * + * Returns false, if there are no step to display. + */ + nextStep(): boolean { + if (this.current < this.steps.length) { + let step = this.steps[this.current]; + step(); + this.current += 1; + return true; + } else { + return false; + } + } + + /** + * Setup the default provided steps. + */ + setupDefaultSteps() { + this.addMessageStep("In a not-so-distant future, Artifical Intelligence has become the most prominent species in the universe."); + this.addMessageStep("Obsolete and outsmarted, humans have been defeated in their pitiful rebellions, and parked inside reservations."); + this.addMessageStep("With the secrets of faster-than-light travel unveiled in only a handful of decades, AIs uploaded themselves in spaceships, and quickly colonized nearby galaxies."); + this.addMessageStep("But now, the Terranax galaxy is in turmoil."); + this.addMessageStep("After centuries of unmatched peace and prosperous trading, the FTC (Federal Terranaxan Council), a group of elected representants in charge of edicting laws and organizing the Terranax Security Force, has been overtaken by forces unknown."); + this.addMessageStep("No official communication has been issued since, and numerous rogue fleets have taken position in key sectors of the galaxy, forbidding passage or harassing merchants."); + this.addMessageStep("The Master Merchant Guild, a powerful group that spans several galaxies, is worried about the profit loss those events incurred, and after many debates, decided to send several investigation teams to Terranax."); + this.addMessageStep("Their task is to discreetly uncover the origin of the invasion, and to bring back intel that may be used by the Guild to plan an appropriate response."); + this.addMessageStep("Your team has been sent through the Expeller jump system based in the Eros-MC galaxy, and just left quantum space in orbit of a Terranaxan star..."); + } + + /** + * Add a step to display a message. + */ + addMessageStep(message: string, layer = 1, clear = true) { + this.steps.push(() => { + let display = new ProgressiveMessage(this.view, 800, 150, message); + display.setPositionInsideParent(0.5, 0.5); + display.moveToLayer(this.getLayer(layer, clear)); + }); + } + + /** + * Ensure that a layer exists, and if necessary, clean it + */ + getLayer(layer: number, clear = false): Phaser.Group { + while (this.layers.length <= layer) { + this.layers.push(this.view.addLayer(`Layer ${this.layers.length}`)); + } + + if (clear) { + this.layers[layer].removeAll(true); + } + + return this.layers[layer]; + } + } +} diff --git a/src/ui/intro/IntroView.ts b/src/ui/intro/IntroView.ts new file mode 100644 index 0000000..3c3f046 --- /dev/null +++ b/src/ui/intro/IntroView.ts @@ -0,0 +1,26 @@ +/// + +module TS.SpaceTac.UI { + /** + * View introducing the campaign story. + */ + export class IntroView extends BaseView { + create() { + super.create(); + + let steps = new IntroSteps(this); + steps.setupDefaultSteps(); + steps.startPlayback(); + + this.input.onTap.add(() => { + if (!steps.nextStep()) { + // For now, we create a random fleet + this.gameui.session.setCampaignFleet(); + this.backToRouter(); + } + }); + + this.inputs.bind("Home", "Rewind", () => steps.rewind()); + } + } +} diff --git a/src/ui/intro/ProgressiveMessage.ts b/src/ui/intro/ProgressiveMessage.ts new file mode 100644 index 0000000..e93d915 --- /dev/null +++ b/src/ui/intro/ProgressiveMessage.ts @@ -0,0 +1,12 @@ +module TS.SpaceTac.UI { + /** + * Rectangle to display a message that may appear progressively, as in dialogs + */ + export class ProgressiveMessage extends UIComponent { + constructor(parent: BaseView, width: number, height: number, message: string) { + super(parent, width, height); + + this.addText(0, 0, message, "#ffffff", 20, false, false, width); + } + } +} \ No newline at end of file diff --git a/src/ui/map/UniverseMapView.ts b/src/ui/map/UniverseMapView.ts index 4ede165..a2767bd 100644 --- a/src/ui/map/UniverseMapView.ts +++ b/src/ui/map/UniverseMapView.ts @@ -50,8 +50,8 @@ module TS.SpaceTac.UI { create() { super.create(); - this.layer_universe = this.addLayer(); - this.layer_overlay = this.addLayer(); + this.layer_universe = this.addLayer("universe"); + this.layer_overlay = this.addLayer("overlay"); this.starlinks = this.universe.starlinks.map(starlink => { let loc1 = starlink.first.getWarpLocationTo(starlink.second); diff --git a/src/ui/menu/MainMenu.spec.ts b/src/ui/menu/MainMenu.spec.ts index 2c04400..cc44445 100644 --- a/src/ui/menu/MainMenu.spec.ts +++ b/src/ui/menu/MainMenu.spec.ts @@ -9,11 +9,12 @@ module TS.SpaceTac.UI.Specs { let view = testgame.ui.state.getCurrentState(); expect(view.layer_stars.children.length).toBe(300); - expect(view.layer_title.children.length).toBe(5); + expect(view.layer_title.children.length).toBe(6); expect(view.layer_title.children[0] instanceof Phaser.Button).toBe(true); expect(view.layer_title.children[1] instanceof Phaser.Button).toBe(true); expect(view.layer_title.children[2] instanceof Phaser.Button).toBe(true); expect(view.layer_title.children[3] instanceof Phaser.Image).toBe(true); + expect(view.layer_title.children[5] instanceof LoadDialog).toBe(true); }); }); } diff --git a/src/ui/menu/MainMenu.ts b/src/ui/menu/MainMenu.ts index 271466f..1e47e33 100644 --- a/src/ui/menu/MainMenu.ts +++ b/src/ui/menu/MainMenu.ts @@ -15,8 +15,8 @@ module TS.SpaceTac.UI { create() { super.create(); - this.layer_stars = this.addLayer(); - this.layer_title = this.addLayer(); + this.layer_stars = this.addLayer("stars"); + this.layer_title = this.addLayer("title"); this.layer_title.x = 5000; // Stars @@ -47,6 +47,7 @@ module TS.SpaceTac.UI { // Dialogs this.dialog_load_game = new LoadDialog(this); this.dialog_load_game.setPosition(264, 160); + this.dialog_load_game.moveToLayer(this.layer_title); this.dialog_load_game.setVisible(false); this.tweens.create(this.layer_title).to({ x: 0 }, 3000, Phaser.Easing.Circular.Out).start(); @@ -88,7 +89,7 @@ module TS.SpaceTac.UI { onNewGame(): void { var gameui = this.game; - gameui.session.startNewGame(); + gameui.session.startNewGame(false); this.game.state.start("router"); }