Added auto-saving to cloud, and loading cloud saves
This commit is contained in:
parent
ed86f57529
commit
27302267b9
3
TODO
3
TODO
|
@ -50,7 +50,6 @@
|
||||||
* Menu: fix background stars aggregating at right side when the game is not focused
|
* Menu: fix background stars aggregating at right side when the game is not focused
|
||||||
* Add ship personality (with icons to identify ?)
|
* Add ship personality (with icons to identify ?)
|
||||||
* Tutorial
|
* Tutorial
|
||||||
* Campaign save slots, with auto-save
|
|
||||||
* Missions/quests system
|
* Missions/quests system
|
||||||
* Main story arc
|
* Main story arc
|
||||||
|
|
||||||
|
@ -58,5 +57,3 @@ Later, if possible:
|
||||||
* Replays
|
* Replays
|
||||||
* Multiplayer
|
* Multiplayer
|
||||||
* Formation or deployment phase
|
* Formation or deployment phase
|
||||||
* Saving to external file
|
|
||||||
* Saving to cloud
|
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"phaser": "2.6.2",
|
"phaser": "2.6.2",
|
||||||
"parse": "1.9.2",
|
"parse": "1.9.2",
|
||||||
"jasmine-core": "jasmine#^2.5.2",
|
"jasmine-core": "jasmine#^2.5.2"
|
||||||
"deep-diff": "0.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game {
|
.game {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
@ -34,7 +34,6 @@
|
||||||
window.oncontextmenu = function (e) { e.preventDefault(); };
|
window.oncontextmenu = function (e) { e.preventDefault(); };
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
window.ui = new TS.SpaceTac.MainUI();
|
window.ui = new TS.SpaceTac.MainUI();
|
||||||
window.connection = new TS.SpaceTac.Multi.Connection(window.ui);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -7,22 +7,23 @@
|
||||||
|
|
||||||
<link rel="stylesheet" href="vendor/jasmine-core/lib/jasmine-core/jasmine.css">
|
<link rel="stylesheet" href="vendor/jasmine-core/lib/jasmine-core/jasmine.css">
|
||||||
<style>
|
<style>
|
||||||
canvas {
|
canvas {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div style="display: none; visibility: hidden; height: 0; overflow: hidden;">
|
<div style="display: none; visibility: hidden; height: 0; overflow: hidden;">
|
||||||
<div id="-space-tac" class="game"></div>
|
<div id="-space-tac" class="game"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="vendor/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
<script src="vendor/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||||
<script src="vendor/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
<script src="vendor/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
||||||
<script src="vendor/jasmine-core/lib/jasmine-core/boot.js"></script>
|
<script src="vendor/jasmine-core/lib/jasmine-core/boot.js"></script>
|
||||||
<script src="vendor/phaser/build/phaser.min.js"></script>
|
<script src="vendor/parse/parse.min.js"></script>
|
||||||
<script src="build.js"></script>
|
<script src="vendor/phaser/build/phaser.min.js"></script>
|
||||||
|
<script src="build.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -4,6 +4,7 @@
|
||||||
"description": "A tactical RPG set in space",
|
"description": "A tactical RPG set in space",
|
||||||
"main": "src/build.js",
|
"main": "src/build.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"shell": "${SHELL} || true",
|
||||||
"postinstall": "bower install && typings install",
|
"postinstall": "bower install && typings install",
|
||||||
"build": "tsc -p .",
|
"build": "tsc -p .",
|
||||||
"pretest": "tsc -p .",
|
"pretest": "tsc -p .",
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
"author": "Michael Lemaire",
|
"author": "Michael Lemaire",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babel-polyfill": "^6.23.0",
|
||||||
"bower": "~1.8",
|
"bower": "~1.8",
|
||||||
"codecov": "~2.1",
|
"codecov": "~2.1",
|
||||||
"jasmine": "~2.5",
|
"jasmine": "~2.5",
|
||||||
|
@ -26,9 +28,9 @@
|
||||||
"karma-coverage": "~1.1",
|
"karma-coverage": "~1.1",
|
||||||
"karma-jasmine": "~1.1",
|
"karma-jasmine": "~1.1",
|
||||||
"karma-phantomjs-launcher": "~1.0",
|
"karma-phantomjs-launcher": "~1.0",
|
||||||
"remap-istanbul": "~0.9",
|
|
||||||
"live-server": "~1.2",
|
"live-server": "~1.2",
|
||||||
|
"remap-istanbul": "~0.9",
|
||||||
"typescript": "~2.3",
|
"typescript": "~2.3",
|
||||||
"typings": "~2.1"
|
"typings": "~2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// karma.conf.js
|
// karma.conf.js
|
||||||
module.exports = function(config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
basePath: '../..',
|
basePath: '../..',
|
||||||
frameworks: ['jasmine'],
|
frameworks: ['jasmine'],
|
||||||
|
@ -12,14 +12,16 @@ module.exports = function(config) {
|
||||||
'out/build.js': ['coverage']
|
'out/build.js': ['coverage']
|
||||||
},
|
},
|
||||||
coverageReporter: {
|
coverageReporter: {
|
||||||
type : 'json',
|
type: 'json',
|
||||||
dir : 'out/coverage/',
|
dir: 'out/coverage/',
|
||||||
subdir: '.',
|
subdir: '.',
|
||||||
file: 'coverage.json'
|
file: 'coverage.json'
|
||||||
},
|
},
|
||||||
|
|
||||||
files: [
|
files: [
|
||||||
|
'node_modules/babel-polyfill/dist/polyfill.js',
|
||||||
'out/vendor/phaser/build/phaser.js',
|
'out/vendor/phaser/build/phaser.js',
|
||||||
|
'out/vendor/parse/parse.min.js',
|
||||||
'out/build.js'
|
'out/build.js'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -80,6 +80,14 @@ module TS.SpaceTac {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current game session, and redirect to view router
|
||||||
|
*/
|
||||||
|
setSession(session: GameSession): void {
|
||||||
|
this.session = session;
|
||||||
|
this.state.start("router");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load current game from local browser storage
|
* Load current game from local browser storage
|
||||||
*/
|
*/
|
||||||
|
@ -99,5 +107,24 @@ module TS.SpaceTac {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an hopefully unique device identifier
|
||||||
|
*/
|
||||||
|
getDeviceId(): string | null {
|
||||||
|
if (this.storage) {
|
||||||
|
const key = "spacetac-device-id";
|
||||||
|
let stored = this.storage.getItem(key);
|
||||||
|
if (stored) {
|
||||||
|
return stored;
|
||||||
|
} else {
|
||||||
|
let generated = RandomGenerator.global.id(20);
|
||||||
|
this.storage.setItem(key, generated);
|
||||||
|
return generated;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 28bf87b126d13f3144955cba4c07bbe03c65e224
|
Subproject commit bc39ed78ef474e42cb20b3dbfc3dffa80c07ffe7
|
|
@ -1,17 +1,34 @@
|
||||||
module TS.SpaceTac {
|
module TS.SpaceTac {
|
||||||
// A game session, binding a universe and a player
|
/**
|
||||||
|
* A game session, binding a universe and a player
|
||||||
|
*
|
||||||
|
* This represents the current state of game
|
||||||
|
*/
|
||||||
export class GameSession {
|
export class GameSession {
|
||||||
|
// "Hopefully"" unique session id
|
||||||
|
id: string
|
||||||
|
|
||||||
// Game universe
|
// Game universe
|
||||||
universe: Universe;
|
universe: Universe
|
||||||
|
|
||||||
// Current connected player
|
// Current connected player
|
||||||
player: Player;
|
player: Player
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.id = RandomGenerator.global.id(20);
|
||||||
this.universe = new Universe();
|
this.universe = new Universe();
|
||||||
this.player = new Player(this.universe);
|
this.player = new Player(this.universe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an indicative description of the session (to help identify game saves)
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
let level = this.player.fleet.getLevel();
|
||||||
|
let ships = this.player.fleet.ships.length;
|
||||||
|
return `Level ${level} - ${ships} ships`;
|
||||||
|
}
|
||||||
|
|
||||||
// Load a game state from a string
|
// Load a game state from a string
|
||||||
static loadFromString(serialized: string): GameSession {
|
static loadFromString(serialized: string): GameSession {
|
||||||
var serializer = new Serializer(TS.SpaceTac);
|
var serializer = new Serializer(TS.SpaceTac);
|
||||||
|
|
86
src/multi/Connection.spec.ts
Normal file
86
src/multi/Connection.spec.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
module TS.SpaceTac.Multi.Specs {
|
||||||
|
describe("Connection", function () {
|
||||||
|
async_it("finds an unused token", async function () {
|
||||||
|
let storage = new FakeRemoteStorage();
|
||||||
|
let connection = new Connection("test", storage);
|
||||||
|
|
||||||
|
let token = await connection.getUnusedToken(5);
|
||||||
|
expect(token.length).toBe(5);
|
||||||
|
|
||||||
|
await storage.upsert("sessioninfo", { token: token }, {});
|
||||||
|
|
||||||
|
spyOn(connection, "generateToken").and.returnValues(token, "123456");
|
||||||
|
|
||||||
|
let other = await connection.getUnusedToken(5);
|
||||||
|
expect(other).toEqual("123456");
|
||||||
|
});
|
||||||
|
|
||||||
|
async_it("loads a session by its id", async function () {
|
||||||
|
let session = new GameSession();
|
||||||
|
let serializer = new Serializer(TS.SpaceTac);
|
||||||
|
let storage = new FakeRemoteStorage();
|
||||||
|
let connection = new Connection("test", storage);
|
||||||
|
|
||||||
|
let result = await connection.loadById("abc");
|
||||||
|
expect(result).toBeNull();
|
||||||
|
|
||||||
|
await storage.upsert("session", { ref: "abc" }, { data: serializer.serialize(session) });
|
||||||
|
|
||||||
|
result = await connection.loadById("abc");
|
||||||
|
expect(result).toEqual(session);
|
||||||
|
result = await connection.loadById("abcd");
|
||||||
|
expect(result).toBeNull();
|
||||||
|
|
||||||
|
// even from another device
|
||||||
|
let other = new Connection("notest", storage);
|
||||||
|
result = await other.loadById("abc");
|
||||||
|
expect(result).toEqual(session);
|
||||||
|
|
||||||
|
// do not load if it is not a GameSession
|
||||||
|
await storage.upsert("session", { ref: "abcd" }, { data: serializer.serialize(new Player()) });
|
||||||
|
result = await connection.loadById("abcd");
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
async_it("lists saves from a device", async function () {
|
||||||
|
let storage = new FakeRemoteStorage();
|
||||||
|
let connection = new Connection("test", storage);
|
||||||
|
|
||||||
|
let result = await connection.listSaves();
|
||||||
|
expect(result).toEqual({});
|
||||||
|
|
||||||
|
await storage.upsert("sessioninfo", { device: "test", ref: "abc" }, { info: "ABC" });
|
||||||
|
await storage.upsert("sessioninfo", { device: "other", ref: "abcd" }, { info: "ABCD" });
|
||||||
|
await storage.upsert("sessioninfo", { device: "test", ref: "cba" }, { info: "CBA" });
|
||||||
|
|
||||||
|
result = await connection.listSaves();
|
||||||
|
expect(result).toEqual({ abc: "ABC", cba: "CBA" });
|
||||||
|
});
|
||||||
|
|
||||||
|
async_it("publishes saves and retrieves them by token", async function () {
|
||||||
|
let session = new GameSession();
|
||||||
|
let storage = new FakeRemoteStorage();
|
||||||
|
let connection = new Connection("test", storage);
|
||||||
|
|
||||||
|
let saves = await connection.listSaves();
|
||||||
|
expect(items(saves).length).toEqual(0);
|
||||||
|
|
||||||
|
let token = await connection.publish(session, "TEST");
|
||||||
|
|
||||||
|
saves = await connection.listSaves();
|
||||||
|
expect(items(saves).length).toEqual(1);
|
||||||
|
|
||||||
|
let loaded = await connection.loadByToken(token);
|
||||||
|
expect(loaded).toEqual(session);
|
||||||
|
|
||||||
|
let newtoken = await connection.publish(nn(loaded), "TEST");
|
||||||
|
expect(token).toEqual(newtoken);
|
||||||
|
|
||||||
|
loaded = await connection.loadByToken(token);
|
||||||
|
expect(loaded).toEqual(session);
|
||||||
|
|
||||||
|
saves = await connection.listSaves();
|
||||||
|
expect(items(saves).length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,42 +1,53 @@
|
||||||
/// <reference path="Parse.d.ts" />
|
|
||||||
|
|
||||||
module TS.SpaceTac.Multi {
|
module TS.SpaceTac.Multi {
|
||||||
/**
|
/**
|
||||||
* Multiplayer connection to a Parse server
|
* Multiplayer connection to a Parse server
|
||||||
*/
|
*/
|
||||||
export class Connection {
|
export class Connection {
|
||||||
ui: MainUI
|
device_id: string
|
||||||
serializer = new Serializer(TS.SpaceTac)
|
serializer = new Serializer(TS.SpaceTac)
|
||||||
model_session = Parse.Object.extend("SpaceTacSession")
|
|
||||||
token_chars = "abcdefghjkmnpqrstuvwxyz123456789"
|
token_chars = "abcdefghjkmnpqrstuvwxyz123456789"
|
||||||
|
storage: IRemoteStorage
|
||||||
|
|
||||||
constructor(ui: MainUI) {
|
constructor(device_id: string, storage: IRemoteStorage) {
|
||||||
this.ui = ui;
|
this.device_id = device_id;
|
||||||
|
this.storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
Parse.initialize("thunderk.net");
|
/**
|
||||||
Parse.serverURL = 'https://rs.thunderk.net/parse';
|
* Generate a random token
|
||||||
|
*/
|
||||||
|
generateToken(length: number): string {
|
||||||
|
return range(length).map(() => RandomGenerator.global.choice(<any>this.token_chars)).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an unused session token
|
* Find an unused session token
|
||||||
*/
|
*/
|
||||||
getUnusedToken(length = 5): string {
|
async getUnusedToken(length = 5): Promise<string> {
|
||||||
let token = range(length).map(() => RandomGenerator.global.choice(<any>this.token_chars)).join("");
|
let token = this.generateToken(length);
|
||||||
// TODO check if it is unused on server
|
let existing = await this.storage.search("sessioninfo", { token: token });
|
||||||
|
if (existing.length > 0) {
|
||||||
|
token = await this.getUnusedToken(length + 1);
|
||||||
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish current session to remote server, and return a session token
|
* Publish a session to remote server, and return an invitation token
|
||||||
*/
|
*/
|
||||||
publish(): string {
|
async publish(session: GameSession, description: string): Promise<string> {
|
||||||
let session = new this.model_session();
|
await this.storage.upsert("session", { ref: session.id }, { data: this.serializer.serialize(session) });
|
||||||
let token = this.getUnusedToken();
|
|
||||||
|
|
||||||
session.set("token", token);
|
let now = new Date();
|
||||||
session.set("data", this.serializer.serialize(this.ui.session));
|
let date = now.toISOString().substr(0, 10) + " " + now.toTimeString().substr(0, 5);
|
||||||
|
let info = `${date}\n${description}`;
|
||||||
|
|
||||||
session.save();
|
let sessinfo = await this.storage.find("sessioninfo", { ref: session.id, device: this.device_id });
|
||||||
|
let token: string = sessinfo ? sessinfo.token : "";
|
||||||
|
if (token.length == 0) {
|
||||||
|
token = await this.getUnusedToken();
|
||||||
|
}
|
||||||
|
await this.storage.upsert("sessioninfo", { ref: session.id, device: this.device_id, token: token }, { info: info });
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
@ -44,21 +55,35 @@ module TS.SpaceTac.Multi {
|
||||||
/**
|
/**
|
||||||
* Load a session from a remote server, by its token
|
* Load a session from a remote server, by its token
|
||||||
*/
|
*/
|
||||||
load(token: string): void {
|
async loadByToken(token: string): Promise<GameSession | null> {
|
||||||
let query = new Parse.Query(this.model_session);
|
let info = await this.storage.find("sessioninfo", { token: token });
|
||||||
query.equalTo("token", token);
|
if (info) {
|
||||||
query.find({
|
return this.loadById(info.ref);
|
||||||
success: (results: any) => {
|
} else {
|
||||||
if (results.length == 1) {
|
return null;
|
||||||
let data = results[0].get("data");
|
}
|
||||||
let session = this.serializer.unserialize(data);
|
}
|
||||||
if (session instanceof GameSession) {
|
|
||||||
this.ui.session = session;
|
/**
|
||||||
this.ui.state.start('router');
|
* Load a session from a remote server, by its id
|
||||||
}
|
*/
|
||||||
}
|
async loadById(id: string): Promise<GameSession | null> {
|
||||||
|
let session = await this.storage.find("session", { ref: id });
|
||||||
|
if (session) {
|
||||||
|
let loaded = this.serializer.unserialize(session.data);
|
||||||
|
if (loaded instanceof GameSession) {
|
||||||
|
return loaded;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List cloud saves, associated with current device
|
||||||
|
*/
|
||||||
|
async listSaves(): Promise<{ [id: string]: string }> {
|
||||||
|
let results = await this.storage.search("sessioninfo", { device: this.device_id });
|
||||||
|
return dict(results.map(obj => <[string, string]>[obj.ref, obj.info]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
51
src/multi/RemoteStorage.spec.ts
Normal file
51
src/multi/RemoteStorage.spec.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
module TS.SpaceTac.Multi.Specs {
|
||||||
|
describe("FakeRemoteStorage", function () {
|
||||||
|
async_it("can fetch a single record", async function () {
|
||||||
|
let storage = new FakeRemoteStorage();
|
||||||
|
|
||||||
|
let result = await storage.find("test", { key: 5 });
|
||||||
|
expect(result).toBeNull();
|
||||||
|
|
||||||
|
await storage.upsert("test", { key: 5 }, { text: "thingy" });
|
||||||
|
|
||||||
|
result = await storage.find("test", { key: 5 });
|
||||||
|
expect(result).toEqual({ key: 5, text: "thingy" });
|
||||||
|
|
||||||
|
result = await storage.find("test", { key: 6 });
|
||||||
|
expect(result).toBeNull();
|
||||||
|
|
||||||
|
result = await storage.find("test", { key: 5, text: "thingy" });
|
||||||
|
expect(result).toEqual({ key: 5, text: "thingy" });
|
||||||
|
|
||||||
|
result = await storage.find("notest", { key: 5 });
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
async_it("inserts or updates objects", async function () {
|
||||||
|
let storage = new FakeRemoteStorage();
|
||||||
|
|
||||||
|
let result = await storage.search("test", { key: 5 });
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
|
||||||
|
await storage.upsert("test", { key: 5 }, {});
|
||||||
|
|
||||||
|
result = await storage.search("test", { key: 5 });
|
||||||
|
expect(result).toEqual([{ key: 5 }]);
|
||||||
|
|
||||||
|
await storage.upsert("test", { key: 5 }, { text: "thingy" });
|
||||||
|
|
||||||
|
result = await storage.search("test", { key: 5 });
|
||||||
|
expect(result).toEqual([{ key: 5, text: "thingy" }]);
|
||||||
|
|
||||||
|
await storage.upsert("test", { key: 5 }, { text: "other thingy" });
|
||||||
|
|
||||||
|
result = await storage.search("test", { key: 5 });
|
||||||
|
expect(result).toEqual([{ key: 5, text: "other thingy" }]);
|
||||||
|
|
||||||
|
await storage.upsert("test", { key: 5, text: "things" }, {});
|
||||||
|
|
||||||
|
result = await storage.search("test", { key: 5 });
|
||||||
|
expect(result.sort((a: any, b: any) => cmp(a.text, b.text))).toEqual([{ key: 5, text: "other thingy" }, { key: 5, text: "things" }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
125
src/multi/RemoteStorage.ts
Normal file
125
src/multi/RemoteStorage.ts
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/// <reference path="Parse.d.ts" />
|
||||||
|
|
||||||
|
module TS.SpaceTac.Multi {
|
||||||
|
/**
|
||||||
|
* Interface for a remote storage, used for networking/multiplayer features
|
||||||
|
*/
|
||||||
|
export interface IRemoteStorage {
|
||||||
|
/**
|
||||||
|
* Search through a collection for equality of some fields
|
||||||
|
*/
|
||||||
|
search(collection: string, fields: any): Promise<any[]>
|
||||||
|
/**
|
||||||
|
* Find a single object with equality of some fields
|
||||||
|
*/
|
||||||
|
find(collection: string, fields: any): Promise<any>
|
||||||
|
/**
|
||||||
|
* Insert or update an object in a collection, based on some unicity fields
|
||||||
|
*/
|
||||||
|
upsert(collection: string, unicity: any, additional: any): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote storage using the Parse protocol
|
||||||
|
*/
|
||||||
|
export class ParseRemoteStorage implements IRemoteStorage {
|
||||||
|
constructor() {
|
||||||
|
Parse.initialize("thunderk.net");
|
||||||
|
Parse.serverURL = 'https://rs.thunderk.net/parse';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack a Parse.Object to a javascript object
|
||||||
|
*/
|
||||||
|
static unpack(obj: Parse.Object): Object {
|
||||||
|
return obj.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Parse model for a given collection name.
|
||||||
|
*/
|
||||||
|
private getModel(collection: string): any {
|
||||||
|
return Parse.Object.extend("spacetac" + collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(collection: string, fields: any) {
|
||||||
|
let query = new Parse.Query(this.getModel(collection));
|
||||||
|
iteritems(fields, (key, value) => {
|
||||||
|
query.equalTo(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = await query.find();
|
||||||
|
return results.map(ParseRemoteStorage.unpack);
|
||||||
|
}
|
||||||
|
|
||||||
|
async find(collection: string, fields: any) {
|
||||||
|
let results = await this.search(collection, fields);
|
||||||
|
if (results.length == 1) {
|
||||||
|
return results[0];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsert(collection: string, unicity: any, additional: any) {
|
||||||
|
let query = new Parse.Query(this.getModel(collection));
|
||||||
|
iteritems(unicity, (key, value) => {
|
||||||
|
query.equalTo(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = await query.find();
|
||||||
|
let model = this.getModel(collection);
|
||||||
|
let base = new model();
|
||||||
|
if (results.length == 1) {
|
||||||
|
base = results[0];
|
||||||
|
} else {
|
||||||
|
iteritems(unicity, (key, value) => {
|
||||||
|
base.set(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
iteritems(additional, (key, value) => {
|
||||||
|
base.set(key, value);
|
||||||
|
});
|
||||||
|
await base.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fake remote storage in memory (for testing purposes)
|
||||||
|
*/
|
||||||
|
export class FakeRemoteStorage implements IRemoteStorage {
|
||||||
|
collections: { [collection: string]: any[] } = {}
|
||||||
|
getCollection(name: string): any {
|
||||||
|
let collection = this.collections[name];
|
||||||
|
if (collection) {
|
||||||
|
return collection;
|
||||||
|
} else {
|
||||||
|
this.collections[name] = [];
|
||||||
|
return this.collections[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async search(collection: string, fields: any) {
|
||||||
|
let objects = this.getCollection(collection);
|
||||||
|
let result = objects.filter((obj: any) => !any(items(fields), ([key, value]) => obj[key] != value));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
async find(collection: string, fields: any) {
|
||||||
|
let results = await this.search(collection, fields);
|
||||||
|
if (results.length == 1) {
|
||||||
|
return results[0];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async upsert(collection: string, unicity: any, additional: any) {
|
||||||
|
let existing = await this.find(collection, unicity);
|
||||||
|
let base = existing || copy(unicity);
|
||||||
|
copyfields(additional, base);
|
||||||
|
if (!existing) {
|
||||||
|
let objects = this.getCollection(collection);
|
||||||
|
objects.push(base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,5 +88,32 @@ module TS.SpaceTac.UI {
|
||||||
let layer = this.add.group(this.layers);
|
let layer = this.add.group(this.layers);
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a network connection to the backend server
|
||||||
|
*/
|
||||||
|
getConnection(): Multi.Connection {
|
||||||
|
let device_id = this.gameui.getDeviceId();
|
||||||
|
if (device_id) {
|
||||||
|
return new Multi.Connection(device_id, new Multi.ParseRemoteStorage());
|
||||||
|
} else {
|
||||||
|
// TODO Should warn the user !
|
||||||
|
return new Multi.Connection("fake", new Multi.FakeRemoteStorage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-save current session to cloud
|
||||||
|
*
|
||||||
|
* This may be called at key points during the gameplay
|
||||||
|
*/
|
||||||
|
autoSave(): void {
|
||||||
|
let session = this.gameui.session;
|
||||||
|
let connection = this.getConnection();
|
||||||
|
connection.publish(session, session.getDescription())
|
||||||
|
.then(() => this.messages.addMessage("Auto-saved to cloud"))
|
||||||
|
.catch(console.error)
|
||||||
|
//.catch(() => this.messages.addMessage("Error saving game to cloud"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ module TS.SpaceTac.UI.Specs {
|
||||||
baseview: BaseView;
|
baseview: BaseView;
|
||||||
battleview: BattleView;
|
battleview: BattleView;
|
||||||
mapview: UniverseMapView;
|
mapview: UniverseMapView;
|
||||||
|
multistorage: Multi.FakeRemoteStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +36,12 @@ module TS.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
let [state, stateargs] = buildView(testgame);
|
let [state, stateargs] = buildView(testgame);
|
||||||
|
|
||||||
|
if (state instanceof BaseView) {
|
||||||
|
testgame.multistorage = new Multi.FakeRemoteStorage();
|
||||||
|
let connection = new Multi.Connection(RandomGenerator.global.id(12), testgame.multistorage);
|
||||||
|
spyOn(state, "getConnection").and.returnValue(connection);
|
||||||
|
}
|
||||||
|
|
||||||
let orig_create = bound(state, "create");
|
let orig_create = bound(state, "create");
|
||||||
spyOn(state, "create").and.callFake(() => {
|
spyOn(state, "create").and.callFake(() => {
|
||||||
orig_create();
|
orig_create();
|
||||||
|
|
|
@ -77,7 +77,7 @@ module TS.SpaceTac.UI {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
Tools.setHoverClick(this, show_info, hide_info, () => this.processClick());
|
UITools.setHoverClick(this, show_info, hide_info, () => this.processClick());
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
this.updateActiveStatus(true);
|
this.updateActiveStatus(true);
|
||||||
|
|
|
@ -62,7 +62,7 @@ module TS.SpaceTac.UI {
|
||||||
this.addChild(this.effects);
|
this.addChild(this.effects);
|
||||||
|
|
||||||
// Handle input on ship sprite
|
// Handle input on ship sprite
|
||||||
Tools.setHoverClick(this.sprite,
|
UITools.setHoverClick(this.sprite,
|
||||||
() => this.battleview.cursorOnShip(ship),
|
() => this.battleview.cursorOnShip(ship),
|
||||||
() => this.battleview.cursorOffShip(ship),
|
() => this.battleview.cursorOffShip(ship),
|
||||||
() => this.battleview.cursorClicked()
|
() => this.battleview.cursorClicked()
|
||||||
|
|
|
@ -66,7 +66,7 @@ module TS.SpaceTac.UI {
|
||||||
level.anchor.set(0.5, 0.5);
|
level.anchor.set(0.5, 0.5);
|
||||||
this.addChild(level);
|
this.addChild(level);
|
||||||
|
|
||||||
Tools.setHoverClick(this, () => list.battleview.cursorOnShip(ship), () => list.battleview.cursorOffShip(ship), () => list.battleview.cursorClicked());
|
UITools.setHoverClick(this, () => list.battleview.cursorOnShip(ship), () => list.battleview.cursorOffShip(ship), () => list.battleview.cursorClicked());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update attributes from associated ship
|
// Update attributes from associated ship
|
||||||
|
|
|
@ -111,17 +111,17 @@ module TS.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
static rotationTween(tween: Phaser.Tween, dest: number, speed = 1, easing = Phaser.Easing.Cubic.InOut, property = "rotation"): number {
|
static rotationTween(tween: Phaser.Tween, dest: number, speed = 1, easing = Phaser.Easing.Cubic.InOut, property = "rotation"): number {
|
||||||
// Immediately change the object's current rotation to be in range (-pi,pi)
|
// Immediately change the object's current rotation to be in range (-pi,pi)
|
||||||
let value = Tools.normalizeAngle(tween.target[property]);
|
let value = UITools.normalizeAngle(tween.target[property]);
|
||||||
tween.target[property] = value;
|
tween.target[property] = value;
|
||||||
|
|
||||||
// Compute destination angle
|
// Compute destination angle
|
||||||
dest = Tools.normalizeAngle(dest);
|
dest = UITools.normalizeAngle(dest);
|
||||||
if (value - dest > Math.PI) {
|
if (value - dest > Math.PI) {
|
||||||
dest += 2 * Math.PI;
|
dest += 2 * Math.PI;
|
||||||
} else if (value - dest < -Math.PI) {
|
} else if (value - dest < -Math.PI) {
|
||||||
dest -= 2 * Math.PI;
|
dest -= 2 * Math.PI;
|
||||||
}
|
}
|
||||||
let distance = Math.abs(Tools.normalizeAngle(dest - value)) / Math.PI;
|
let distance = Math.abs(UITools.normalizeAngle(dest - value)) / Math.PI;
|
||||||
let duration = distance * 1000 / speed;
|
let duration = distance * 1000 / speed;
|
||||||
|
|
||||||
// Update the tween
|
// Update the tween
|
||||||
|
|
|
@ -37,7 +37,7 @@ module TS.SpaceTac.UI {
|
||||||
this.background.endFill();
|
this.background.endFill();
|
||||||
}
|
}
|
||||||
|
|
||||||
let [x, y] = Tools.positionInside({ x: this.anchorpoint[0], y: this.anchorpoint[1], width: width, height: height }, { x: 0, y: 0, width: this.view.getWidth(), height: this.view.getHeight() });
|
let [x, y] = UITools.positionInside({ x: this.anchorpoint[0], y: this.anchorpoint[1], width: width, height: height }, { x: 0, y: 0, width: this.view.getWidth(), height: this.view.getHeight() });
|
||||||
if (x != this.x || y != this.y) {
|
if (x != this.x || y != this.y) {
|
||||||
this.position.set(x, y);
|
this.position.set(x, y);
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ module TS.SpaceTac.UI {
|
||||||
* When the component is hovered, the function is called to allow filling the tooltip container
|
* When the component is hovered, the function is called to allow filling the tooltip container
|
||||||
*/
|
*/
|
||||||
bind(obj: Phaser.Button, func: (container: Phaser.Group) => boolean): void {
|
bind(obj: Phaser.Button, func: (container: Phaser.Group) => boolean): void {
|
||||||
Tools.setHoverClick(obj,
|
UITools.setHoverClick(obj,
|
||||||
// enter
|
// enter
|
||||||
() => {
|
() => {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
25
src/ui/common/UILabel.ts
Normal file
25
src/ui/common/UILabel.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/// <reference path="UIComponent.ts" />
|
||||||
|
|
||||||
|
module TS.SpaceTac.UI {
|
||||||
|
/**
|
||||||
|
* UI component to display a text
|
||||||
|
*/
|
||||||
|
export class UILabel extends UIComponent {
|
||||||
|
private content: Phaser.Text
|
||||||
|
|
||||||
|
constructor(parent: UIComponent, width: number, height: number, content = "", fontsize = 20, fontcolor = "#FFFFFF") {
|
||||||
|
super(parent, width, height);
|
||||||
|
|
||||||
|
this.content = new Phaser.Text(this.game, width / 2, height / 2, content, { align: "center", font: `${fontsize}px Arial`, fill: fontcolor })
|
||||||
|
this.content.anchor.set(0.5, 0.5);
|
||||||
|
this.addInternalChild(this.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the label content
|
||||||
|
*/
|
||||||
|
setContent(text: string): void {
|
||||||
|
this.content.text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ module TS.SpaceTac.UI {
|
||||||
private content: Phaser.Text
|
private content: Phaser.Text
|
||||||
private maxlength: number
|
private maxlength: number
|
||||||
|
|
||||||
constructor(parent: UIComponent, width: number, height: number, maxlength?: number) {
|
constructor(parent: UIComponent, width: number, height: number, maxlength?: number, fontcolor = "#FFFFFF") {
|
||||||
super(parent, width, height);
|
super(parent, width, height);
|
||||||
|
|
||||||
let input_bg = new Phaser.Image(this.game, 0, 0, "common-transparent");
|
let input_bg = new Phaser.Image(this.game, 0, 0, "common-transparent");
|
||||||
|
@ -19,7 +19,7 @@ module TS.SpaceTac.UI {
|
||||||
this.addInternalChild(input_bg);
|
this.addInternalChild(input_bg);
|
||||||
|
|
||||||
let fontsize = Math.ceil(height * 0.8);
|
let fontsize = Math.ceil(height * 0.8);
|
||||||
this.content = new Phaser.Text(this.game, width / 2, height / 2, "", { align: "center", font: `${fontsize}px Arial`, fill: "#FFFFFF" });
|
this.content = new Phaser.Text(this.game, width / 2, height / 2, "", { align: "center", font: `${fontsize}px Arial`, fill: fontcolor });
|
||||||
this.content.anchor.set(0.5, 0.5);
|
this.content.anchor.set(0.5, 0.5);
|
||||||
this.addInternalChild(this.content);
|
this.addInternalChild(this.content);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module TS.SpaceTac.UI.Specs {
|
module TS.SpaceTac.UI.Specs {
|
||||||
describe("Tools", function () {
|
describe("UITools", function () {
|
||||||
let testgame = setupEmptyView();
|
let testgame = setupEmptyView();
|
||||||
|
|
||||||
it("keeps objects inside bounds", function () {
|
it("keeps objects inside bounds", function () {
|
||||||
|
@ -8,19 +8,19 @@ module TS.SpaceTac.UI.Specs {
|
||||||
image.drawEllipse(50, 25, 50, 25);
|
image.drawEllipse(50, 25, 50, 25);
|
||||||
image.endFill();
|
image.endFill();
|
||||||
|
|
||||||
Tools.keepInside(image, { x: 0, y: 0, width: 200, height: 200 });
|
UITools.keepInside(image, { x: 0, y: 0, width: 200, height: 200 });
|
||||||
|
|
||||||
expect(image.x).toBe(100);
|
expect(image.x).toBe(100);
|
||||||
expect(image.y).toBe(100);
|
expect(image.y).toBe(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("normalizes angles", function () {
|
it("normalizes angles", function () {
|
||||||
expect(Tools.normalizeAngle(0)).toEqual(0);
|
expect(UITools.normalizeAngle(0)).toEqual(0);
|
||||||
expect(Tools.normalizeAngle(0.1)).toBeCloseTo(0.1, 0.000001);
|
expect(UITools.normalizeAngle(0.1)).toBeCloseTo(0.1, 0.000001);
|
||||||
expect(Tools.normalizeAngle(Math.PI)).toBeCloseTo(Math.PI, 0.000001);
|
expect(UITools.normalizeAngle(Math.PI)).toBeCloseTo(Math.PI, 0.000001);
|
||||||
expect(Tools.normalizeAngle(Math.PI + 0.5)).toBeCloseTo(-Math.PI + 0.5, 0.000001);
|
expect(UITools.normalizeAngle(Math.PI + 0.5)).toBeCloseTo(-Math.PI + 0.5, 0.000001);
|
||||||
expect(Tools.normalizeAngle(-Math.PI)).toBeCloseTo(Math.PI, 0.000001);
|
expect(UITools.normalizeAngle(-Math.PI)).toBeCloseTo(Math.PI, 0.000001);
|
||||||
expect(Tools.normalizeAngle(-Math.PI - 0.5)).toBeCloseTo(Math.PI - 0.5, 0.000001);
|
expect(UITools.normalizeAngle(-Math.PI - 0.5)).toBeCloseTo(Math.PI - 0.5, 0.000001);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles hover and click on desktops and mobile targets", function (done) {
|
it("handles hover and click on desktops and mobile targets", function (done) {
|
||||||
|
@ -36,7 +36,7 @@ module TS.SpaceTac.UI.Specs {
|
||||||
spyOn(funcs, "enter");
|
spyOn(funcs, "enter");
|
||||||
spyOn(funcs, "leave");
|
spyOn(funcs, "leave");
|
||||||
spyOn(funcs, "click");
|
spyOn(funcs, "click");
|
||||||
Tools.setHoverClick(button, funcs.enter, funcs.leave, funcs.click, 50, 100);
|
UITools.setHoverClick(button, funcs.enter, funcs.leave, funcs.click, 50, 100);
|
||||||
return [button, funcs];
|
return [button, funcs];
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ module TS.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common UI tools functions
|
// Common UI tools functions
|
||||||
export class Tools {
|
export class UITools {
|
||||||
/**
|
/**
|
||||||
* Get the position of an object, adjusted to remain inside a container
|
* Get the position of an object, adjusted to remain inside a container
|
||||||
*/
|
*/
|
||||||
|
@ -36,7 +36,7 @@ module TS.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
static keepInside(obj: Phaser.Button | Phaser.Sprite | Phaser.Image | Phaser.Group | Phaser.Graphics, rect: IBounded) {
|
static keepInside(obj: Phaser.Button | Phaser.Sprite | Phaser.Image | Phaser.Group | Phaser.Graphics, rect: IBounded) {
|
||||||
let objbounds = obj.getBounds();
|
let objbounds = obj.getBounds();
|
||||||
let [x, y] = Tools.positionInside({ x: obj.x, y: obj.y, width: objbounds.width, height: objbounds.height }, rect);
|
let [x, y] = UITools.positionInside({ x: obj.x, y: obj.y, width: objbounds.width, height: objbounds.height }, rect);
|
||||||
|
|
||||||
if (x != obj.x || y != obj.y) {
|
if (x != obj.x || y != obj.y) {
|
||||||
obj.position.set(x, y);
|
obj.position.set(x, y);
|
|
@ -103,6 +103,9 @@ module TS.SpaceTac.UI {
|
||||||
this.inputs.bindCheat("r", "Reveal whole map", this.revealAll);
|
this.inputs.bindCheat("r", "Reveal whole map", this.revealAll);
|
||||||
|
|
||||||
this.setZoom(2);
|
this.setZoom(2);
|
||||||
|
|
||||||
|
// Trigger an auto-save any time we go back to the map
|
||||||
|
this.autoSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,16 +5,91 @@ module TS.SpaceTac.UI {
|
||||||
* Dialog to load a saved game, or join an online one
|
* Dialog to load a saved game, or join an online one
|
||||||
*/
|
*/
|
||||||
export class LoadDialog extends UIComponent {
|
export class LoadDialog extends UIComponent {
|
||||||
|
saves: [string, string][] = []
|
||||||
|
save_selected = 0
|
||||||
|
save_name: UILabel
|
||||||
|
token_input: UITextInput
|
||||||
|
|
||||||
constructor(parent: MainMenu) {
|
constructor(parent: MainMenu) {
|
||||||
super(parent, 1344, 566, "menu-load-bg");
|
super(parent, 1344, 566, "menu-load-bg");
|
||||||
|
|
||||||
this.addButton(600, 115, () => null, "common-arrow", "common-arrow", 180);
|
this.addButton(600, 115, () => this.paginateSave(-1), "common-arrow", "common-arrow", 180);
|
||||||
this.addButton(1038, 115, () => null, "common-arrow", "common-arrow", 0);
|
this.addButton(1038, 115, () => this.paginateSave(1), "common-arrow", "common-arrow", 0);
|
||||||
this.addButton(1224, 115, () => null, "common-button-cancel");
|
this.addButton(1224, 115, () => this.load(), "common-button-ok");
|
||||||
this.addButton(1224, 341, () => null, "common-button-cancel");
|
this.addButton(1224, 341, () => this.join(), "common-button-ok");
|
||||||
|
|
||||||
let input = new UITextInput(this, 468, 68, 10);
|
this.save_name = new UILabel(this, 351, 185, "", 32, "#000000");
|
||||||
input.setPosition(585, 304);
|
this.save_name.setPosition(645, 28);
|
||||||
|
|
||||||
|
this.token_input = new UITextInput(this, 468, 68, 10, "#000000");
|
||||||
|
this.token_input.setPosition(585, 304);
|
||||||
|
|
||||||
|
this.refreshSaves();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh available save games
|
||||||
|
*/
|
||||||
|
private refreshSaves(): void {
|
||||||
|
let connection = this.view.getConnection();
|
||||||
|
|
||||||
|
// TODO include local save
|
||||||
|
// TODO Disable interaction, with loading icon
|
||||||
|
|
||||||
|
connection.listSaves().then(results => {
|
||||||
|
this.saves = items(results).sort(([id1, info1], [id2, info2]) => cmp(info2, info1));
|
||||||
|
this.setCurrentSave(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current selected save game
|
||||||
|
*/
|
||||||
|
private setCurrentSave(position: number): void {
|
||||||
|
if (this.saves.length == 0) {
|
||||||
|
this.save_name.setContent("No save game found");
|
||||||
|
} else {
|
||||||
|
this.save_selected = clamp(position, 0, this.saves.length - 1);
|
||||||
|
|
||||||
|
let [saveid, saveinfo] = this.saves[this.save_selected];
|
||||||
|
this.save_name.setContent(saveinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the selected save
|
||||||
|
*/
|
||||||
|
private paginateSave(offset: number) {
|
||||||
|
this.setCurrentSave(this.save_selected + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join an online game
|
||||||
|
*/
|
||||||
|
private join(): void {
|
||||||
|
let token = this.token_input.getContent();
|
||||||
|
let connection = this.view.getConnection();
|
||||||
|
|
||||||
|
connection.loadByToken(token).then(session => {
|
||||||
|
if (session) {
|
||||||
|
this.view.gameui.setSession(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load selected save game
|
||||||
|
*/
|
||||||
|
private load(): void {
|
||||||
|
if (this.save_selected >= 0 && this.saves.length > this.save_selected) {
|
||||||
|
let connection = this.view.getConnection();
|
||||||
|
let [saveid, saveinfo] = this.saves[this.save_selected];
|
||||||
|
connection.loadById(saveid).then(session => {
|
||||||
|
if (session) {
|
||||||
|
this.view.gameui.setSession(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,11 @@
|
||||||
"out": "out/build.js",
|
"out": "out/build.js",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2015.promise",
|
||||||
|
"es5"
|
||||||
|
],
|
||||||
"target": "es5"
|
"target": "es5"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|
Loading…
Reference in a new issue