1
0
Fork 0

Started introduction view, with a brief campaign story

This commit is contained in:
Michaël Lemaire 2017-06-01 01:11:29 +02:00
parent d41b9f3d2c
commit 17120ee0f3
16 changed files with 296 additions and 73 deletions

View file

@ -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".

View file

@ -22,6 +22,34 @@
inkscape:export-ydpi="96">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient4934">
<stop
style="stop-color:#1d2831;stop-opacity:1;"
offset="0"
id="stop4930" />
<stop
id="stop4938"
offset="0.10928228"
style="stop-color:#4f585f;stop-opacity:1" />
<stop
style="stop-color:#1d2831;stop-opacity:1"
offset="0.35770971"
id="stop4944" />
<stop
style="stop-color:#1d2831;stop-opacity:1"
offset="0.64699304"
id="stop4942" />
<stop
style="stop-color:#393f44;stop-opacity:1"
offset="0.8964678"
id="stop4940" />
<stop
style="stop-color:#1d2831;stop-opacity:1"
offset="1"
id="stop4932" />
</linearGradient>
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
@ -244,6 +272,15 @@
result="composite2"
id="feComposite9549" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4934"
id="linearGradient4936"
x1="480.44397"
y1="4.0790515"
x2="503.60327"
y2="27.135599"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
@ -252,9 +289,9 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="1651.1031"
inkscape:cy="985.51155"
inkscape:zoom="3.959798"
inkscape:cx="1838.5705"
inkscape:cy="1007.2957"
inkscape:document-units="px"
inkscape:current-layer="layer5"
showgrid="false"
@ -523,7 +560,7 @@
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Load dialog"
style="display:inline">
style="display:none">
<rect
style="fill:#1e3959;fill-opacity:1;fill-rule:evenodd;stroke:#8fbcbd;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.96170211"
id="rect4557"
@ -716,33 +753,42 @@
id="layer5"
inkscape:label="Fullscreen button"
style="display:inline">
<rect
style="display:inline;fill:#1e3959;fill-opacity:1;fill-rule:evenodd;stroke:#8fbcbd;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.96470588"
id="rect6016"
width="23.386084"
height="23.386084"
x="480.28333"
y="3.9142845"
<g
id="g4950"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/button-fullscreen.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="display:inline;fill:#8fbdbe;fill-opacity:0.96470588;fill-rule:evenodd;stroke:#b6cfd0;stroke-width:0.17029287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.53617021"
d="m 481.54179,5.0555342 h 5.20002 l -5.20002,5.2000148 z"
id="rect6020"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/button-fullscreen.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path6023"
d="m 502.61094,26.158088 h -5.20002 l 5.20002,-5.20002 z"
style="display:inline;fill:#8fbdbe;fill-opacity:0.96470588;fill-rule:evenodd;stroke:#b6cfd0;stroke-width:0.17029287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.53617021"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/button-fullscreen.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
inkscape:export-ydpi="96">
<rect
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/button-fullscreen.png"
y="3.9142845"
x="480.28333"
height="23.386084"
width="23.386084"
id="rect6016"
style="display:inline;fill:url(#linearGradient4936);fill-opacity:1;fill-rule:evenodd;stroke:#5785bc;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
transform="translate(-0.049883,0.03392527)"
id="g4928">
<path
style="display:inline;fill:#4b8ad4;fill-opacity:1;fill-rule:evenodd;stroke:#6a8cb1;stroke-width:0.17;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 481.54179,5.0555342 h 5.20002 l -5.20002,5.2000148 z"
id="rect6020"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/button-fullscreen.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<use
x="0"
y="0"
xlink:href="#rect6020"
id="use4924"
width="100%"
height="100%"
transform="rotate(180,492.02625,15.573401)" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -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');

View file

@ -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 () {

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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));
}
/**

View file

@ -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];
}
}
}

26
src/ui/intro/IntroView.ts Normal file
View file

@ -0,0 +1,26 @@
/// <reference path="../BaseView.ts"/>
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());
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -9,11 +9,12 @@ module TS.SpaceTac.UI.Specs {
let view = <MainMenu>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);
});
});
}

View file

@ -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 = <MainUI>this.game;
gameui.session.startNewGame();
gameui.session.startNewGame(false);
this.game.state.start("router");
}