Migration to Phaser 3
This commit is contained in:
parent
e20be0ed6e
commit
811bfa182d
14
TODO.md
14
TODO.md
|
@ -1,6 +1,17 @@
|
||||||
To-Do-list
|
To-Do-list
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
Phaser 3 migration
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Pause the game when the window isn't focused (except in headless)
|
||||||
|
* Fit the game in window size
|
||||||
|
* Fix top-right messages positions
|
||||||
|
* Make the AI-thinking loader work again
|
||||||
|
* Fix the character sheet layout
|
||||||
|
* Fix the crash in gatling animation
|
||||||
|
* Fix valuebar requiring to be in root display list
|
||||||
|
|
||||||
Menu/settings/saves
|
Menu/settings/saves
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -20,6 +31,7 @@ Map/story
|
||||||
* Forbid to end up with more than 5 ships in the fleet because of escorts
|
* Forbid to end up with more than 5 ships in the fleet because of escorts
|
||||||
* Fix problems when several dialogs are active at the same time
|
* Fix problems when several dialogs are active at the same time
|
||||||
* Add a zoom level, to see the location only
|
* Add a zoom level, to see the location only
|
||||||
|
* Restore the progressive text effect
|
||||||
|
|
||||||
Character sheet
|
Character sheet
|
||||||
---------------
|
---------------
|
||||||
|
@ -94,6 +106,7 @@ Common UI
|
||||||
* Fix tooltip remaining when the hovered object is hidden by animations
|
* Fix tooltip remaining when the hovered object is hidden by animations
|
||||||
* If ProgressiveMessage animation performance is bad, show the text directly
|
* If ProgressiveMessage animation performance is bad, show the text directly
|
||||||
* Add caret/focus and configurable background to text input
|
* Add caret/focus and configurable background to text input
|
||||||
|
* Release keybord grabbing when UITextInput is hidden or loses focus
|
||||||
* Mobile: think UI layout so that fingers do not block the view (right and left handed)
|
* Mobile: think UI layout so that fingers do not block the view (right and left handed)
|
||||||
* Mobile: display tooltips larger and on the side of screen where the finger is not
|
* Mobile: display tooltips larger and on the side of screen where the finger is not
|
||||||
* Mobile: targetting in two times, using a draggable target indicator
|
* Mobile: targetting in two times, using a draggable target indicator
|
||||||
|
@ -103,6 +116,7 @@ Technical
|
||||||
|
|
||||||
* Pack sounds
|
* Pack sounds
|
||||||
* Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile
|
* Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile
|
||||||
|
* Add cache for image texture lookup (getImageInfo)
|
||||||
|
|
||||||
Network
|
Network
|
||||||
-------
|
-------
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 169 B |
BIN
data/stage2/image/battle/overlay.png
Normal file
BIN
data/stage2/image/battle/overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 B |
|
@ -1,6 +1,6 @@
|
||||||
var handler = {
|
var handler = {
|
||||||
get(target, name) {
|
get(target, name) {
|
||||||
return function () { }
|
return new Proxy({}, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var Phaser = new Proxy({}, handler);
|
var Phaser = new Proxy({}, handler);
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
<div class=".fontLoader">.</div>
|
<div class=".fontLoader">.</div>
|
||||||
|
|
||||||
<script src="vendor/parse/parse.min.js"></script>
|
<script src="vendor/parse/parse.min.js"></script>
|
||||||
<script src="vendor/phaser/phaser.min.js"></script>
|
<script src="vendor/phaser/phaser.js"></script>
|
||||||
<script src="build.js"></script>
|
<script src="build.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -1375,8 +1375,7 @@
|
||||||
"eventemitter3": {
|
"eventemitter3": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
|
||||||
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
|
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"expand-braces": {
|
"expand-braces": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
@ -3317,11 +3316,6 @@
|
||||||
"request": "2.81.0"
|
"request": "2.81.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hoek": {
|
|
||||||
"version": "5.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz",
|
|
||||||
"integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw=="
|
|
||||||
},
|
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
|
||||||
|
@ -5289,9 +5283,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"phaser": {
|
"phaser": {
|
||||||
"version": "2.6.2",
|
"version": "3.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/phaser/-/phaser-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/phaser/-/phaser-3.9.0.tgz",
|
||||||
"integrity": "sha1-6zkSFyWiFJxJ9GtdFEMYwivAkkk="
|
"integrity": "sha1-CsZGUDGwoUpK9DIPr5Xzn2cVmdc=",
|
||||||
|
"requires": {
|
||||||
|
"eventemitter3": "3.1.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
|
|
|
@ -37,10 +37,9 @@
|
||||||
"uglify-js": "^3.3.23"
|
"uglify-js": "^3.3.23"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hoek": "^5.0.3",
|
|
||||||
"jasmine-core": "^3.1.0",
|
"jasmine-core": "^3.1.0",
|
||||||
"parse": "^1.11.0",
|
"parse": "^1.11.0",
|
||||||
"phaser": "2.6.2",
|
"phaser": "^3.9.0",
|
||||||
"process-pool": "^0.3.5"
|
"process-pool": "^0.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
runfile.js
16
runfile.js
|
@ -82,11 +82,10 @@ async function pack(stage) {
|
||||||
let items = files.map(file => {
|
let items = files.map(file => {
|
||||||
let fname = path.basename(file);
|
let fname = path.basename(file);
|
||||||
return {
|
return {
|
||||||
type: "atlasJSONHash",
|
type: "atlas",
|
||||||
key: fname,
|
key: fname,
|
||||||
atlasURL: `assets/${fname}.json?t=${Date.now()}`,
|
atlasURL: `assets/${fname}.json?t=${Date.now()}`,
|
||||||
textureURL: `assets/${fname}.png`,
|
textureURL: `assets/${fname}.png`
|
||||||
atlasData: null
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,7 +97,7 @@ async function pack(stage) {
|
||||||
return {
|
return {
|
||||||
type: "audio",
|
type: "audio",
|
||||||
key: key,
|
key: key,
|
||||||
urls: [`assets/${key}.${ext}?t=${Date.now()}`],
|
url: `assets/${key}.${ext}?t=${Date.now()}`,
|
||||||
autoDecode: (ext == 'mp3')
|
autoDecode: (ext == 'mp3')
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
@ -116,7 +115,7 @@ async function pack(stage) {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let packdata = {};
|
let packdata = {};
|
||||||
packdata[`stage${stage}`] = items;
|
packdata[`stage${stage}`] = { files: items };
|
||||||
await new Promise(resolve => fs.writeFile(`out/assets/pack${stage}.json`, JSON.stringify(packdata), 'utf8', resolve));
|
await new Promise(resolve => fs.writeFile(`out/assets/pack${stage}.json`, JSON.stringify(packdata), 'utf8', resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +142,7 @@ async function vendors() {
|
||||||
console.log("Copying vendors...");
|
console.log("Copying vendors...");
|
||||||
shell.rm('-rf', 'out/vendor');
|
shell.rm('-rf', 'out/vendor');
|
||||||
shell.mkdir('-p', 'out/vendor');
|
shell.mkdir('-p', 'out/vendor');
|
||||||
shell.cp('-R', 'node_modules/phaser/build', 'out/vendor/phaser');
|
shell.cp('-R', 'node_modules/phaser/dist', 'out/vendor/phaser');
|
||||||
shell.cp('-R', 'node_modules/parse/dist', 'out/vendor/parse');
|
shell.cp('-R', 'node_modules/parse/dist', 'out/vendor/parse');
|
||||||
shell.cp('-R', 'node_modules/jasmine-core/lib/jasmine-core', 'out/vendor/jasmine');
|
shell.cp('-R', 'node_modules/jasmine-core/lib/jasmine-core', 'out/vendor/jasmine');
|
||||||
}
|
}
|
||||||
|
@ -181,7 +180,10 @@ async function optimize() {
|
||||||
async function deploy(task) {
|
async function deploy(task) {
|
||||||
await build(true);
|
await build(true);
|
||||||
await optimize();
|
await optimize();
|
||||||
await exec("rsync -avz --delete ./out/ hosting.thunderk.net:/srv/website/spacetac/");
|
|
||||||
|
let branch = await run('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe', async: true });
|
||||||
|
let suffix = (branch == "master") ? "" : "x";
|
||||||
|
await exec(`rsync -avz --delete ./out/ hosting.thunderk.net:/srv/website/spacetac${suffix}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
113
src/MainUI.ts
113
src/MainUI.ts
|
@ -10,8 +10,8 @@ if (typeof window != "undefined") {
|
||||||
if (typeof global != "undefined") {
|
if (typeof global != "undefined") {
|
||||||
// In node, does not extend Phaser classes
|
// In node, does not extend Phaser classes
|
||||||
var handler = {
|
var handler = {
|
||||||
get(target: any, name: any) {
|
get(target: any, name: any): any {
|
||||||
return function () { }
|
return new Proxy({}, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
global.Phaser = new Proxy({}, handler);
|
global.Phaser = new Proxy({}, handler);
|
||||||
|
@ -29,22 +29,23 @@ module TK.SpaceTac {
|
||||||
session: GameSession
|
session: GameSession
|
||||||
session_token: string | null
|
session_token: string | null
|
||||||
|
|
||||||
// Audio manager
|
|
||||||
audio!: UI.Audio
|
|
||||||
|
|
||||||
// Game options
|
// Game options
|
||||||
options!: UI.GameOptions
|
options!: UI.GameOptions
|
||||||
|
|
||||||
// Storage used
|
// Storage used
|
||||||
storage: Storage
|
storage: Storage
|
||||||
|
|
||||||
// Headless mode
|
// Debug mode
|
||||||
headless: boolean
|
debug = false
|
||||||
|
|
||||||
constructor(headless: boolean = false) {
|
constructor(headless: boolean = false) {
|
||||||
super(1920, 1080, headless ? Phaser.HEADLESS : Phaser.AUTO, '-space-tac');
|
super({
|
||||||
|
width: 1920,
|
||||||
this.headless = headless;
|
height: 1080,
|
||||||
|
type: headless ? Phaser.HEADLESS : Phaser.AUTO,
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
parent: '-space-tac'
|
||||||
|
});
|
||||||
|
|
||||||
this.storage = localStorage;
|
this.storage = localStorage;
|
||||||
|
|
||||||
|
@ -52,32 +53,55 @@ module TK.SpaceTac {
|
||||||
this.session_token = null;
|
this.session_token = null;
|
||||||
|
|
||||||
if (!headless) {
|
if (!headless) {
|
||||||
this.state.onStateChange.add((state: string) => console.log(`View change: ${state}`));
|
this.scene.add('boot', UI.Boot);
|
||||||
|
this.scene.add('loading', UI.AssetLoading);
|
||||||
|
this.scene.add('mainmenu', UI.MainMenu);
|
||||||
|
this.scene.add('router', UI.Router);
|
||||||
|
this.scene.add('battle', UI.BattleView);
|
||||||
|
this.scene.add('intro', UI.IntroView);
|
||||||
|
this.scene.add('creation', UI.FleetCreationView);
|
||||||
|
this.scene.add('universe', UI.UniverseMapView);
|
||||||
|
|
||||||
this.state.add('boot', UI.Boot);
|
this.goToScene('boot');
|
||||||
this.state.add('loading', UI.AssetLoading);
|
|
||||||
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('creation', UI.FleetCreationView);
|
|
||||||
this.state.add('universe', UI.UniverseMapView);
|
|
||||||
|
|
||||||
this.state.start('boot');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boot() {
|
boot() {
|
||||||
if (this.renderType == Phaser.HEADLESS) {
|
|
||||||
this.headless = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.boot();
|
super.boot();
|
||||||
|
|
||||||
this.audio = new UI.Audio(this);
|
|
||||||
this.options = new UI.GameOptions(this);
|
this.options = new UI.GameOptions(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get headless(): boolean {
|
||||||
|
return this.config.renderType == Phaser.HEADLESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the audio manager for current scene
|
||||||
|
*/
|
||||||
|
get audio(): UI.Audio {
|
||||||
|
let scene = this.getActiveScene();
|
||||||
|
if (scene) {
|
||||||
|
return scene.audio;
|
||||||
|
} else {
|
||||||
|
return new UI.Audio(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently active scene
|
||||||
|
*/
|
||||||
|
getActiveScene(): UI.BaseView | null {
|
||||||
|
let active = first(<string[]>keys(this.scene.scenes), key => this.scene.isActive(key));
|
||||||
|
if (active) {
|
||||||
|
let scene = this.scene.getScene(active);
|
||||||
|
return (scene instanceof UI.BaseView) ? scene : null;
|
||||||
|
} else if (this.headless) {
|
||||||
|
return this.scene.scenes[0];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the game session
|
* Reset the game session
|
||||||
*/
|
*/
|
||||||
|
@ -90,10 +114,23 @@ module TK.SpaceTac {
|
||||||
* Display a popup message in current view
|
* Display a popup message in current view
|
||||||
*/
|
*/
|
||||||
displayMessage(message: string) {
|
displayMessage(message: string) {
|
||||||
let state = <UI.BaseView>this.state.getCurrentState();
|
iteritems(<any>this.scene.keys, (key: string, scene: UI.BaseView) => {
|
||||||
if (state) {
|
if (scene.messages && this.scene.isVisible(key)) {
|
||||||
state.messages.addMessage(message);
|
scene.messages.addMessage(message);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the active scene
|
||||||
|
*/
|
||||||
|
goToScene(name: string): void {
|
||||||
|
this.scene.scenes.forEach(scene => {
|
||||||
|
if (this.scene.isActive(scene)) {
|
||||||
|
scene.shutdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.scene.start(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,7 +138,7 @@ module TK.SpaceTac {
|
||||||
*/
|
*/
|
||||||
quitGame() {
|
quitGame() {
|
||||||
this.resetSession();
|
this.resetSession();
|
||||||
this.state.start('router');
|
this.goToScene('router');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,7 +161,7 @@ module TK.SpaceTac {
|
||||||
setSession(session: GameSession, token?: string): void {
|
setSession(session: GameSession, token?: string): void {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.session_token = token || null;
|
this.session_token = token || null;
|
||||||
this.state.start("router");
|
this.goToScene("router");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,7 +208,9 @@ module TK.SpaceTac {
|
||||||
* Check if the game is currently fullscreen
|
* Check if the game is currently fullscreen
|
||||||
*/
|
*/
|
||||||
isFullscreen(): boolean {
|
isFullscreen(): boolean {
|
||||||
return this.scale.isFullScreen;
|
// FIXME
|
||||||
|
return false;
|
||||||
|
//return this.scale.isFullScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,13 +219,15 @@ module TK.SpaceTac {
|
||||||
* Returns true if the result is fullscreen
|
* Returns true if the result is fullscreen
|
||||||
*/
|
*/
|
||||||
toggleFullscreen(active: boolean | null = null): boolean {
|
toggleFullscreen(active: boolean | null = null): boolean {
|
||||||
if (active === false || (active !== true && this.isFullscreen())) {
|
// FIXME
|
||||||
|
/*if (active === false || (active !== true && this.isFullscreen())) {
|
||||||
this.scale.stopFullScreen();
|
this.scale.stopFullScreen();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
this.scale.startFullScreen(true);
|
this.scale.startFullScreen(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}*/
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit fc4bde326c2dcb4be03380ac29bac8d12b015821
|
Subproject commit 628ae0e4dc1b738ba3764506648d83ecca815cf5
|
|
@ -332,7 +332,7 @@ module TK.SpaceTac {
|
||||||
*/
|
*/
|
||||||
getAreaEffects(ship: Ship): [Ship | Drone, BaseEffect][] {
|
getAreaEffects(ship: Ship): [Ship | Drone, BaseEffect][] {
|
||||||
let drone_effects = this.drones.list().map(drone => {
|
let drone_effects = this.drones.list().map(drone => {
|
||||||
// FIXME Should apply filterImpactedShips from drone action
|
// TODO Should apply filterImpactedShips from drone action
|
||||||
if (drone.isInRange(ship.arena_x, ship.arena_y)) {
|
if (drone.isInRange(ship.arena_x, ship.arena_y)) {
|
||||||
return drone.effects.map((effect): [Ship | Drone, BaseEffect] => [drone, effect]);
|
return drone.effects.map((effect): [Ship | Drone, BaseEffect] => [drone, effect]);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -89,7 +89,7 @@ module TK.SpaceTac {
|
||||||
// TODO Work with groups of 3, 4 ...
|
// TODO Work with groups of 3, 4 ...
|
||||||
let weapons = <Iterator<TriggerAction>>ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0);
|
let weapons = <Iterator<TriggerAction>>ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0);
|
||||||
let enemies = battle.ienemies(ship, true);
|
let enemies = battle.ienemies(ship, true);
|
||||||
// FIXME This produces duplicates (x, y) and (y, x)
|
// TODO This produces duplicates (x, y) and (y, x)
|
||||||
let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2);
|
let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2);
|
||||||
let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.blast * 2);
|
let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.blast * 2);
|
||||||
let result = imap(candidates, ([weapon, [e1, e2]]) => new Maneuver(ship, weapon, Target.newFromLocation((e1.arena_x + e2.arena_x) / 2, (e1.arena_y + e2.arena_y) / 2)));
|
let result = imap(candidates, ([weapon, [e1, e2]]) => new Maneuver(ship, weapon, Target.newFromLocation((e1.arena_x + e2.arena_x) / 2, (e1.arena_y + e2.arena_y) / 2)));
|
||||||
|
|
948
src/lib/p2.d.ts
vendored
948
src/lib/p2.d.ts
vendored
|
@ -1,948 +0,0 @@
|
||||||
// Type definitions for p2.js v0.6.0
|
|
||||||
// Project: https://github.com/schteppe/p2.js/
|
|
||||||
|
|
||||||
declare module p2 {
|
|
||||||
|
|
||||||
export class AABB {
|
|
||||||
|
|
||||||
constructor(options?: {
|
|
||||||
upperBound?: number[];
|
|
||||||
lowerBound?: number[];
|
|
||||||
});
|
|
||||||
|
|
||||||
setFromPoints(points: number[][], position: number[], angle: number, skinSize: number): void;
|
|
||||||
copy(aabb: AABB): void;
|
|
||||||
extend(aabb: AABB): void;
|
|
||||||
overlaps(aabb: AABB): boolean;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Broadphase {
|
|
||||||
|
|
||||||
static AABB: number;
|
|
||||||
static BOUNDING_CIRCLE: number;
|
|
||||||
|
|
||||||
static NAIVE: number;
|
|
||||||
static SAP: number;
|
|
||||||
|
|
||||||
static boundingRadiusCheck(bodyA: Body, bodyB: Body): boolean;
|
|
||||||
static aabbCheck(bodyA: Body, bodyB: Body): boolean;
|
|
||||||
static canCollide(bodyA: Body, bodyB: Body): boolean;
|
|
||||||
|
|
||||||
constructor(type: number);
|
|
||||||
|
|
||||||
type: number;
|
|
||||||
result: Body[];
|
|
||||||
world: World;
|
|
||||||
boundingVolumeType: number;
|
|
||||||
|
|
||||||
setWorld(world: World): void;
|
|
||||||
getCollisionPairs(world: World): Body[];
|
|
||||||
boundingVolumeCheck(bodyA: Body, bodyB: Body): boolean;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GridBroadphase extends Broadphase {
|
|
||||||
|
|
||||||
constructor(options?: {
|
|
||||||
xmin?: number;
|
|
||||||
xmax?: number;
|
|
||||||
ymin?: number;
|
|
||||||
ymax?: number;
|
|
||||||
nx?: number;
|
|
||||||
ny?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
xmin: number;
|
|
||||||
xmax: number;
|
|
||||||
ymin: number;
|
|
||||||
ymax: number;
|
|
||||||
nx: number;
|
|
||||||
ny: number;
|
|
||||||
binsizeX: number;
|
|
||||||
binsizeY: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NativeBroadphase extends Broadphase {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Narrowphase {
|
|
||||||
|
|
||||||
contactEquations: ContactEquation[];
|
|
||||||
frictionEquations: FrictionEquation[];
|
|
||||||
enableFriction: boolean;
|
|
||||||
slipForce: number;
|
|
||||||
frictionCoefficient: number;
|
|
||||||
surfaceVelocity: number;
|
|
||||||
reuseObjects: boolean;
|
|
||||||
resuableContactEquations: any[];
|
|
||||||
reusableFrictionEquations: any[];
|
|
||||||
restitution: number;
|
|
||||||
stiffness: number;
|
|
||||||
relaxation: number;
|
|
||||||
frictionStiffness: number;
|
|
||||||
frictionRelaxation: number;
|
|
||||||
enableFrictionReduction: boolean;
|
|
||||||
contactSkinSize: number;
|
|
||||||
|
|
||||||
collidedLastStep(bodyA: Body, bodyB: Body): boolean;
|
|
||||||
reset(): void;
|
|
||||||
createContactEquation(bodyA: Body, bodyB: Body, shapeA: Shape, shapeB: Shape): ContactEquation;
|
|
||||||
createFrictionFromContact(c: ContactEquation): FrictionEquation;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SAPBroadphase extends Broadphase {
|
|
||||||
|
|
||||||
axisList: Body[];
|
|
||||||
axisIndex: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Constraint {
|
|
||||||
|
|
||||||
static DISTANCE: number;
|
|
||||||
static GEAR: number;
|
|
||||||
static LOCK: number;
|
|
||||||
static PRISMATIC: number;
|
|
||||||
static REVOLUTE: number;
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, type: number, options?: {
|
|
||||||
collideConnected?: boolean;
|
|
||||||
wakeUpBodies?: boolean;
|
|
||||||
});
|
|
||||||
|
|
||||||
type: number;
|
|
||||||
equeations: Equation[];
|
|
||||||
bodyA: Body;
|
|
||||||
bodyB: Body;
|
|
||||||
collideConnected: boolean;
|
|
||||||
|
|
||||||
update(): void;
|
|
||||||
setStiffness(stiffness: number): void;
|
|
||||||
setRelaxation(relaxation: number): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DistanceConstraint extends Constraint {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, type: number, options?: {
|
|
||||||
collideConnected?: boolean;
|
|
||||||
wakeUpBodies?: boolean;
|
|
||||||
distance?: number;
|
|
||||||
localAnchorA?: number[];
|
|
||||||
localAnchorB?: number[];
|
|
||||||
maxForce?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
localAnchorA: number[];
|
|
||||||
localAnchorB: number[];
|
|
||||||
distance: number;
|
|
||||||
maxForce: number;
|
|
||||||
upperLimitEnabled: boolean;
|
|
||||||
upperLimit: number;
|
|
||||||
lowerLimitEnabled: boolean;
|
|
||||||
lowerLimit: number;
|
|
||||||
position: number;
|
|
||||||
|
|
||||||
setMaxForce(f: number): void;
|
|
||||||
getMaxForce(): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GearConstraint extends Constraint {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, type: number, options?: {
|
|
||||||
collideConnected?: boolean;
|
|
||||||
wakeUpBodies?: boolean;
|
|
||||||
angle?: number;
|
|
||||||
ratio?: number;
|
|
||||||
maxTorque?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
ratio: number;
|
|
||||||
angle: number;
|
|
||||||
|
|
||||||
setMaxTorque(torque: number): void;
|
|
||||||
getMaxTorque(): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LockConstraint extends Constraint {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, type: number, options?: {
|
|
||||||
collideConnected?: boolean;
|
|
||||||
wakeUpBodies?: boolean;
|
|
||||||
localOffsetB?: number[];
|
|
||||||
localAngleB?: number;
|
|
||||||
maxForce?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
setMaxForce(force: number): void;
|
|
||||||
getMaxForce(): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PrismaticConstraint extends Constraint {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, type: number, options?: {
|
|
||||||
collideConnected?: boolean;
|
|
||||||
wakeUpBodies?: boolean;
|
|
||||||
maxForce?: number;
|
|
||||||
localAnchorA?: number[];
|
|
||||||
localAnchorB?: number[];
|
|
||||||
localAxisA?: number[];
|
|
||||||
disableRotationalLock?: boolean;
|
|
||||||
upperLimit?: number;
|
|
||||||
lowerLimit?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
localAnchorA: number[];
|
|
||||||
localAnchorB: number[];
|
|
||||||
localAxisA: number[];
|
|
||||||
position: number;
|
|
||||||
velocity: number;
|
|
||||||
lowerLimitEnabled: boolean;
|
|
||||||
upperLimitEnabled: boolean;
|
|
||||||
lowerLimit: number;
|
|
||||||
upperLimit: number;
|
|
||||||
upperLimitEquation: ContactEquation;
|
|
||||||
lowerLimitEquation: ContactEquation;
|
|
||||||
motorEquation: Equation;
|
|
||||||
motorEnabled: boolean;
|
|
||||||
motorSpeed: number;
|
|
||||||
|
|
||||||
enableMotor(): void;
|
|
||||||
disableMotor(): void;
|
|
||||||
setLimits(lower: number, upper: number): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RevoluteConstraint extends Constraint {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, type: number, options?: {
|
|
||||||
collideConnected?: boolean;
|
|
||||||
wakeUpBodies?: boolean;
|
|
||||||
worldPivot?: number[];
|
|
||||||
localPivotA?: number[];
|
|
||||||
localPivotB?: number[];
|
|
||||||
maxForce?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
pivotA: number[];
|
|
||||||
pivotB: number[];
|
|
||||||
motorEquation: RotationalVelocityEquation;
|
|
||||||
motorEnabled: boolean;
|
|
||||||
angle: number;
|
|
||||||
lowerLimitEnabled: boolean;
|
|
||||||
upperLimitEnabled: boolean;
|
|
||||||
lowerLimit: number;
|
|
||||||
upperLimit: number;
|
|
||||||
upperLimitEquation: ContactEquation;
|
|
||||||
lowerLimitEquation: ContactEquation;
|
|
||||||
|
|
||||||
enableMotor(): void;
|
|
||||||
disableMotor(): void;
|
|
||||||
motorIsEnabled(): boolean;
|
|
||||||
setLimits(lower: number, upper: number): void;
|
|
||||||
setMotorSpeed(speed: number): void;
|
|
||||||
getMotorSpeed(): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AngleLockEquation extends Equation {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, options?: {
|
|
||||||
angle?: number;
|
|
||||||
ratio?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
computeGq(): number;
|
|
||||||
setRatio(ratio: number): number;
|
|
||||||
setMaxTorque(torque: number): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContactEquation extends Equation {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body);
|
|
||||||
|
|
||||||
contactPointA: number[];
|
|
||||||
penetrationVec: number[];
|
|
||||||
contactPointB: number[];
|
|
||||||
normalA: number[];
|
|
||||||
restitution: number;
|
|
||||||
firstImpact: boolean;
|
|
||||||
shapeA: Shape;
|
|
||||||
shapeB: Shape;
|
|
||||||
|
|
||||||
computeB(a: number, b: number, h: number): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Equation {
|
|
||||||
|
|
||||||
static DEFAULT_STIFFNESS: number;
|
|
||||||
static DEFAULT_RELAXATION: number;
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, minForce?: number, maxForce?: number);
|
|
||||||
|
|
||||||
minForce: number;
|
|
||||||
maxForce: number;
|
|
||||||
bodyA: Body;
|
|
||||||
bodyB: Body;
|
|
||||||
stiffness: number;
|
|
||||||
relaxation: number;
|
|
||||||
G: number[];
|
|
||||||
offset: number;
|
|
||||||
a: number;
|
|
||||||
b: number;
|
|
||||||
epsilon: number;
|
|
||||||
timeStep: number;
|
|
||||||
needsUpdate: boolean;
|
|
||||||
multiplier: number;
|
|
||||||
relativeVelocity: number;
|
|
||||||
enabled: boolean;
|
|
||||||
|
|
||||||
gmult(G: number[], vi: number[], wi: number[], vj: number[], wj: number[]): number;
|
|
||||||
computeB(a: number, b: number, h: number): number;
|
|
||||||
computeGq(): number;
|
|
||||||
computeGW(): number;
|
|
||||||
computeGWlambda(): number;
|
|
||||||
computeGiMf(): number;
|
|
||||||
computeGiMGt(): number;
|
|
||||||
addToWlambda(deltalambda: number): number;
|
|
||||||
computeInvC(eps: number): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FrictionEquation extends Equation {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, slipForce: number);
|
|
||||||
|
|
||||||
contactPointA: number[];
|
|
||||||
contactPointB: number[];
|
|
||||||
t: number[];
|
|
||||||
shapeA: Shape;
|
|
||||||
shapeB: Shape;
|
|
||||||
frictionCoefficient: number;
|
|
||||||
|
|
||||||
setSlipForce(slipForce: number): number;
|
|
||||||
getSlipForce(): number;
|
|
||||||
computeB(a: number, b: number, h: number): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RotationalLockEquation extends Equation {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, options?: {
|
|
||||||
angle?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
angle: number;
|
|
||||||
|
|
||||||
computeGq(): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RotationalVelocityEquation extends Equation {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body);
|
|
||||||
|
|
||||||
computeB(a: number, b: number, h: number): number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EventEmitter {
|
|
||||||
|
|
||||||
on(type: string, listener: Function, context: any): EventEmitter;
|
|
||||||
has(type: string, listener: Function): boolean;
|
|
||||||
off(type: string, listener: Function): EventEmitter;
|
|
||||||
emit(event: any): EventEmitter;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContactMaterialOptions {
|
|
||||||
|
|
||||||
friction: number;
|
|
||||||
restitution: number;
|
|
||||||
stiffness: number;
|
|
||||||
relaxation: number;
|
|
||||||
frictionStiffness: number;
|
|
||||||
frictionRelaxation: number;
|
|
||||||
surfaceVelocity: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContactMaterial {
|
|
||||||
|
|
||||||
static idCounter: number;
|
|
||||||
|
|
||||||
constructor(materialA: Material, materialB: Material, options?: ContactMaterialOptions);
|
|
||||||
|
|
||||||
id: number;
|
|
||||||
materialA: Material;
|
|
||||||
materialB: Material;
|
|
||||||
friction: number;
|
|
||||||
restitution: number;
|
|
||||||
stiffness: number;
|
|
||||||
relaxation: number;
|
|
||||||
frictionStuffness: number;
|
|
||||||
frictionRelaxation: number;
|
|
||||||
surfaceVelocity: number;
|
|
||||||
contactSkinSize: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Material {
|
|
||||||
|
|
||||||
static idCounter: number;
|
|
||||||
|
|
||||||
constructor(id: number);
|
|
||||||
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class vec2 {
|
|
||||||
|
|
||||||
static crossLength(a: number[], b: number[]): number;
|
|
||||||
static crossVZ(out: number[], vec: number[], zcomp: number): number;
|
|
||||||
static crossZV(out: number[], zcomp: number, vec: number[]): number;
|
|
||||||
static rotate(out: number[], a: number[], angle: number): void;
|
|
||||||
static rotate90cw(out: number[], a: number[]): number;
|
|
||||||
static centroid(out: number[], a: number[], b: number[], c: number[]): number[];
|
|
||||||
static create(): number[];
|
|
||||||
static clone(a: number[]): number[];
|
|
||||||
static fromValues(x: number, y: number): number[];
|
|
||||||
static copy(out: number[], a: number[]): number[];
|
|
||||||
static set(out: number[], x: number, y: number): number[];
|
|
||||||
static toLocalFrame(out: number[], worldPoint: number[], framePosition: number[], frameAngle: number): void;
|
|
||||||
static toGlobalFrame(out: number[], localPoint: number[], framePosition: number[], frameAngle: number): void;
|
|
||||||
static add(out: number[], a: number[], b: number[]): number[];
|
|
||||||
static subtract(out: number[], a: number[], b: number[]): number[];
|
|
||||||
static sub(out: number[], a: number[], b: number[]): number[];
|
|
||||||
static multiply(out: number[], a: number[], b: number[]): number[];
|
|
||||||
static mul(out: number[], a: number[], b: number[]): number[];
|
|
||||||
static divide(out: number[], a: number[], b: number[]): number[];
|
|
||||||
static div(out: number[], a: number[], b: number[]): number[];
|
|
||||||
static scale(out: number[], a: number[], b: number): number[];
|
|
||||||
static distance(a: number[], b: number[]): number;
|
|
||||||
static dist(a: number[], b: number[]): number;
|
|
||||||
static squaredDistance(a: number[], b: number[]): number;
|
|
||||||
static sqrDist(a: number[], b: number[]): number;
|
|
||||||
static length(a: number[]): number;
|
|
||||||
static len(a: number[]): number;
|
|
||||||
static squaredLength(a: number[]): number;
|
|
||||||
static sqrLen(a: number[]): number;
|
|
||||||
static negate(out: number[], a: number[]): number[];
|
|
||||||
static normalize(out: number[], a: number[]): number[];
|
|
||||||
static dot(a: number[], b: number[]): number;
|
|
||||||
static str(a: number[]): string;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BodyOptions {
|
|
||||||
|
|
||||||
mass: number;
|
|
||||||
position: number[];
|
|
||||||
velocity: number[];
|
|
||||||
angle: number;
|
|
||||||
angularVelocity: number;
|
|
||||||
force: number[];
|
|
||||||
angularForce: number;
|
|
||||||
fixedRotation: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Body extends EventEmitter {
|
|
||||||
|
|
||||||
sleepyEvent: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
sleepEvent: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
wakeUpEvent: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
static DYNAMIC: number;
|
|
||||||
static STATIC: number;
|
|
||||||
static KINEMATIC: number;
|
|
||||||
static AWAKE: number;
|
|
||||||
static SLEEPY: number;
|
|
||||||
static SLEEPING: number;
|
|
||||||
|
|
||||||
constructor(options?: BodyOptions);
|
|
||||||
|
|
||||||
id: number;
|
|
||||||
world: World;
|
|
||||||
shapes: Shape[];
|
|
||||||
shapeOffsets: number[][];
|
|
||||||
shapeAngles: number[];
|
|
||||||
mass: number;
|
|
||||||
invMass: number;
|
|
||||||
inertia: number;
|
|
||||||
invInertia: number;
|
|
||||||
invMassSolve: number;
|
|
||||||
invInertiaSolve: number;
|
|
||||||
fixedRotation: number;
|
|
||||||
position: number[];
|
|
||||||
interpolatedPosition: number[];
|
|
||||||
interpolatedAngle: number;
|
|
||||||
previousPosition: number[];
|
|
||||||
previousAngle: number;
|
|
||||||
velocity: number[];
|
|
||||||
vlambda: number[];
|
|
||||||
wlambda: number[];
|
|
||||||
angle: number;
|
|
||||||
angularVelocity: number;
|
|
||||||
force: number[];
|
|
||||||
angularForce: number;
|
|
||||||
damping: number;
|
|
||||||
angularDamping: number;
|
|
||||||
type: number;
|
|
||||||
boundingRadius: number;
|
|
||||||
aabb: AABB;
|
|
||||||
aabbNeedsUpdate: boolean;
|
|
||||||
allowSleep: boolean;
|
|
||||||
wantsToSleep: boolean;
|
|
||||||
sleepState: number;
|
|
||||||
sleepSpeedLimit: number;
|
|
||||||
sleepTimeLimit: number;
|
|
||||||
gravityScale: number;
|
|
||||||
|
|
||||||
updateSolveMassProperties(): void;
|
|
||||||
setDensity(density: number): void;
|
|
||||||
getArea(): number;
|
|
||||||
getAABB(): AABB;
|
|
||||||
updateAABB(): void;
|
|
||||||
updateBoundingRadius(): void;
|
|
||||||
addShape(shape: Shape, offset?: number[], angle?: number): void;
|
|
||||||
removeShape(shape: Shape): boolean;
|
|
||||||
updateMassProperties(): void;
|
|
||||||
applyForce(force: number[], worldPoint: number[]): void;
|
|
||||||
toLocalFrame(out: number[], worldPoint: number[]): void;
|
|
||||||
toWorldFrame(out: number[], localPoint: number[]): void;
|
|
||||||
fromPolygon(path: number[][], options?: {
|
|
||||||
optimalDecomp?: boolean;
|
|
||||||
skipSimpleCheck?: boolean;
|
|
||||||
removeCollinearPoints?: any; //boolean | number
|
|
||||||
}): boolean;
|
|
||||||
adjustCenterOfMass(): void;
|
|
||||||
setZeroForce(): void;
|
|
||||||
resetConstraintVelocity(): void;
|
|
||||||
applyDamping(dy: number): void;
|
|
||||||
wakeUp(): void;
|
|
||||||
sleep(): void;
|
|
||||||
sleepTick(time: number, dontSleep: boolean, dt: number): void;
|
|
||||||
getVelocityFromPosition(story: number[], dt: number): number[];
|
|
||||||
getAngularVelocityFromPosition(timeStep: number): number;
|
|
||||||
overlaps(body: Body): boolean;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Spring {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, options?: {
|
|
||||||
|
|
||||||
stiffness?: number;
|
|
||||||
damping?: number;
|
|
||||||
localAnchorA?: number[];
|
|
||||||
localAnchorB?: number[];
|
|
||||||
worldAnchorA?: number[];
|
|
||||||
worldAnchorB?: number[];
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
stiffness: number;
|
|
||||||
damping: number;
|
|
||||||
bodyA: Body;
|
|
||||||
bodyB: Body;
|
|
||||||
|
|
||||||
applyForce(): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LinearSpring extends Spring {
|
|
||||||
|
|
||||||
localAnchorA: number[];
|
|
||||||
localAnchorB: number[];
|
|
||||||
restLength: number;
|
|
||||||
|
|
||||||
setWorldAnchorA(worldAnchorA: number[]): void;
|
|
||||||
setWorldAnchorB(worldAnchorB: number[]): void;
|
|
||||||
getWorldAnchorA(result: number[]): number[];
|
|
||||||
getWorldAnchorB(result: number[]): number[];
|
|
||||||
applyForce(): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RotationalSpring extends Spring {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, bodyB: Body, options?: {
|
|
||||||
restAngle?: number;
|
|
||||||
stiffness?: number;
|
|
||||||
damping?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
restAngle: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Capsule extends Shape {
|
|
||||||
|
|
||||||
constructor(length?: number, radius?: number);
|
|
||||||
|
|
||||||
length: number;
|
|
||||||
radius: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Circle extends Shape {
|
|
||||||
|
|
||||||
constructor(radius: number);
|
|
||||||
|
|
||||||
radius: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Convex extends Shape {
|
|
||||||
|
|
||||||
static triangleArea(a: number[], b: number[], c: number[]): number;
|
|
||||||
|
|
||||||
constructor(vertices: number[][], axes: number[]);
|
|
||||||
|
|
||||||
vertices: number[][];
|
|
||||||
axes: number[];
|
|
||||||
centerOfMass: number[];
|
|
||||||
triangles: number[];
|
|
||||||
boundingRadius: number;
|
|
||||||
|
|
||||||
projectOntoLocalAxis(localAxis: number[], result: number[]): void;
|
|
||||||
projectOntoWorldAxis(localAxis: number[], shapeOffset: number[], shapeAngle: number, result: number[]): void;
|
|
||||||
|
|
||||||
updateCenterOfMass(): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Heightfield extends Shape {
|
|
||||||
|
|
||||||
constructor(data: number[], options?: {
|
|
||||||
minValue?: number;
|
|
||||||
maxValue?: number;
|
|
||||||
elementWidth: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
data: number[];
|
|
||||||
maxValue: number;
|
|
||||||
minValue: number;
|
|
||||||
elementWidth: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Shape {
|
|
||||||
|
|
||||||
static idCounter: number;
|
|
||||||
static CIRCLE: number;
|
|
||||||
static PARTICLE: number;
|
|
||||||
static PLANE: number;
|
|
||||||
static CONVEX: number;
|
|
||||||
static LINE: number;
|
|
||||||
static RECTANGLE: number;
|
|
||||||
static CAPSULE: number;
|
|
||||||
static HEIGHTFIELD: number;
|
|
||||||
|
|
||||||
constructor(type: number);
|
|
||||||
|
|
||||||
type: number;
|
|
||||||
id: number;
|
|
||||||
boundingRadius: number;
|
|
||||||
collisionGroup: number;
|
|
||||||
collisionMask: number;
|
|
||||||
material: Material;
|
|
||||||
area: number;
|
|
||||||
sensor: boolean;
|
|
||||||
|
|
||||||
computeMomentOfInertia(mass: number): number;
|
|
||||||
updateBoundingRadius(): number;
|
|
||||||
updateArea(): void;
|
|
||||||
computeAABB(out: AABB, position: number[], angle: number): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Line extends Shape {
|
|
||||||
|
|
||||||
constructor(length?: number);
|
|
||||||
|
|
||||||
length: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Particle extends Shape {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Plane extends Shape {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Rectangle extends Shape {
|
|
||||||
|
|
||||||
static sameDimensions(a: Rectangle, b: Rectangle): boolean;
|
|
||||||
|
|
||||||
constructor(width?: number, height?: number);
|
|
||||||
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Solver extends EventEmitter {
|
|
||||||
|
|
||||||
static GS: number;
|
|
||||||
static ISLAND: number;
|
|
||||||
|
|
||||||
constructor(options?: {}, type?: number);
|
|
||||||
|
|
||||||
type: number;
|
|
||||||
equations: Equation[];
|
|
||||||
equationSortFunction: Equation; //Equation | boolean
|
|
||||||
|
|
||||||
solve(dy: number, world: World): void;
|
|
||||||
solveIsland(dy: number, island: Island): void;
|
|
||||||
sortEquations(): void;
|
|
||||||
addEquation(eq: Equation): void;
|
|
||||||
addEquations(eqs: Equation[]): void;
|
|
||||||
removeEquation(eq: Equation): void;
|
|
||||||
removeAllEquations(): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GSSolver extends Solver {
|
|
||||||
|
|
||||||
constructor(options?: {
|
|
||||||
iterations?: number;
|
|
||||||
tolerance?: number;
|
|
||||||
});
|
|
||||||
|
|
||||||
iterations: number;
|
|
||||||
tolerance: number;
|
|
||||||
useZeroRHS: boolean;
|
|
||||||
frictionIterations: number;
|
|
||||||
usedIterations: number;
|
|
||||||
|
|
||||||
solve(h: number, world: World): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OverlapKeeper {
|
|
||||||
|
|
||||||
constructor(bodyA: Body, shapeA: Shape, bodyB: Body, shapeB: Shape);
|
|
||||||
|
|
||||||
shapeA: Shape;
|
|
||||||
shapeB: Shape;
|
|
||||||
bodyA: Body;
|
|
||||||
bodyB: Body;
|
|
||||||
|
|
||||||
tick(): void;
|
|
||||||
setOverlapping(bodyA: Body, shapeA: Shape, bodyB: Body, shapeB: Body): void;
|
|
||||||
bodiesAreOverlapping(bodyA: Body, bodyB: Body): boolean;
|
|
||||||
set(bodyA: Body, shapeA: Shape, bodyB: Body, shapeB: Shape): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TupleDictionary {
|
|
||||||
|
|
||||||
data: number[];
|
|
||||||
keys: number[];
|
|
||||||
|
|
||||||
getKey(id1: number, id2: number): string;
|
|
||||||
getByKey(key: number): number;
|
|
||||||
get(i: number, j: number): number;
|
|
||||||
set(i: number, j: number, value: number): number;
|
|
||||||
reset(): void;
|
|
||||||
copy(dict: TupleDictionary): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Utils {
|
|
||||||
|
|
||||||
static appendArray<T>(a: Array<T>, b: Array<T>): Array<T>;
|
|
||||||
static chanceRoll(chance: number): boolean;
|
|
||||||
static defaults(options: any, defaults: any): any;
|
|
||||||
static extend(a: any, b: any): void;
|
|
||||||
static randomChoice(choice1: any, choice2: any): any;
|
|
||||||
static rotateArray(matrix: any[], direction: any): any[];
|
|
||||||
static splice<T>(array: Array<T>, index: number, howMany: number): void;
|
|
||||||
static shuffle<T>(array: T[]): T[];
|
|
||||||
static transposeArray<T>(array: T[]): T[];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Island {
|
|
||||||
|
|
||||||
equations: Equation[];
|
|
||||||
bodies: Body[];
|
|
||||||
|
|
||||||
reset(): void;
|
|
||||||
getBodies(result: any): Body[];
|
|
||||||
wantsToSleep(): boolean;
|
|
||||||
sleep(): boolean;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IslandManager extends Solver {
|
|
||||||
|
|
||||||
static getUnvisitedNode(nodes: Node[]): IslandNode; // IslandNode | boolean
|
|
||||||
|
|
||||||
equations: Equation[];
|
|
||||||
islands: Island[];
|
|
||||||
nodes: IslandNode[];
|
|
||||||
|
|
||||||
visit(node: IslandNode, bds: Body[], eqs: Equation[]): void;
|
|
||||||
bfs(root: IslandNode, bds: Body[], eqs: Equation[]): void;
|
|
||||||
split(world: World): Island[];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IslandNode {
|
|
||||||
|
|
||||||
constructor(body: Body);
|
|
||||||
|
|
||||||
body: Body;
|
|
||||||
neighbors: IslandNode[];
|
|
||||||
equations: Equation[];
|
|
||||||
visited: boolean;
|
|
||||||
|
|
||||||
reset(): void;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class World extends EventEmitter {
|
|
||||||
|
|
||||||
postStepEvent: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
addBodyEvent: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
removeBodyEvent: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
addSpringEvent: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
impactEvent: {
|
|
||||||
type: string;
|
|
||||||
bodyA: Body;
|
|
||||||
bodyB: Body;
|
|
||||||
shapeA: Shape;
|
|
||||||
shapeB: Shape;
|
|
||||||
contactEquation: ContactEquation;
|
|
||||||
};
|
|
||||||
|
|
||||||
postBroadphaseEvent: {
|
|
||||||
type: string;
|
|
||||||
pairs: Body[];
|
|
||||||
};
|
|
||||||
|
|
||||||
beginContactEvent: {
|
|
||||||
type: string;
|
|
||||||
shapeA: Shape;
|
|
||||||
shapeB: Shape;
|
|
||||||
bodyA: Body;
|
|
||||||
bodyB: Body;
|
|
||||||
contactEquations: ContactEquation[];
|
|
||||||
};
|
|
||||||
|
|
||||||
endContactEvent: {
|
|
||||||
type: string;
|
|
||||||
shapeA: Shape;
|
|
||||||
shapeB: Shape;
|
|
||||||
bodyA: Body;
|
|
||||||
bodyB: Body;
|
|
||||||
};
|
|
||||||
|
|
||||||
preSolveEvent: {
|
|
||||||
type: string;
|
|
||||||
contactEquations: ContactEquation[];
|
|
||||||
frictionEquations: FrictionEquation[];
|
|
||||||
};
|
|
||||||
|
|
||||||
static NO_SLEEPING: number;
|
|
||||||
static BODY_SLEEPING: number;
|
|
||||||
static ISLAND_SLEEPING: number;
|
|
||||||
|
|
||||||
static integrateBody(body: Body, dy: number): void;
|
|
||||||
|
|
||||||
constructor(options?: {
|
|
||||||
solver?: Solver;
|
|
||||||
gravity?: number[];
|
|
||||||
broadphase?: Broadphase;
|
|
||||||
islandSplit?: boolean;
|
|
||||||
doProfiling?: boolean;
|
|
||||||
});
|
|
||||||
|
|
||||||
springs: Spring[];
|
|
||||||
bodies: Body[];
|
|
||||||
solver: Solver;
|
|
||||||
narrowphase: Narrowphase;
|
|
||||||
islandManager: IslandManager;
|
|
||||||
gravity: number[];
|
|
||||||
frictionGravity: number;
|
|
||||||
useWorldGravityAsFrictionGravity: boolean;
|
|
||||||
useFrictionGravityOnZeroGravity: boolean;
|
|
||||||
doProfiling: boolean;
|
|
||||||
lastStepTime: number;
|
|
||||||
broadphase: Broadphase;
|
|
||||||
constraints: Constraint[];
|
|
||||||
defaultMaterial: Material;
|
|
||||||
defaultContactMaterial: ContactMaterial;
|
|
||||||
lastTimeStep: number;
|
|
||||||
applySpringForces: boolean;
|
|
||||||
applyDamping: boolean;
|
|
||||||
applyGravity: boolean;
|
|
||||||
solveConstraints: boolean;
|
|
||||||
contactMaterials: ContactMaterial[];
|
|
||||||
time: number;
|
|
||||||
stepping: boolean;
|
|
||||||
islandSplit: boolean;
|
|
||||||
emitImpactEvent: boolean;
|
|
||||||
sleepMode: number;
|
|
||||||
|
|
||||||
addConstraint(c: Constraint): void;
|
|
||||||
addContactMaterial(contactMaterial: ContactMaterial): void;
|
|
||||||
removeContactMaterial(cm: ContactMaterial): void;
|
|
||||||
getContactMaterial(materialA: Material, materialB: Material): ContactMaterial; // ContactMaterial | boolean
|
|
||||||
removeConstraint(c: Constraint): void;
|
|
||||||
step(dy: number, timeSinceLastCalled?: number, maxSubSteps?: number): void;
|
|
||||||
runNarrowphase(np: Narrowphase, bi: Body, si: Shape, xi: any[], ai: number, bj: Body, sj: Shape, xj: any[], aj: number, cm: number, glen: number): void;
|
|
||||||
addSpring(s: Spring): void;
|
|
||||||
removeSpring(s: Spring): void;
|
|
||||||
addBody(body: Body): void;
|
|
||||||
removeBody(body: Body): void;
|
|
||||||
getBodyByID(id: number): Body; //Body | boolean
|
|
||||||
disableBodyCollision(bodyA: Body, bodyB: Body): void;
|
|
||||||
enableBodyCollision(bodyA: Body, bodyB: Body): void;
|
|
||||||
clear(): void;
|
|
||||||
clone(): World;
|
|
||||||
hitTest(worldPoint: number[], bodies: Body[], precision: number): Body[];
|
|
||||||
setGlobalEquationParameters(parameters: {
|
|
||||||
relaxation?: number;
|
|
||||||
stiffness?: number;
|
|
||||||
}): void;
|
|
||||||
setGlobalStiffness(stiffness: number): void;
|
|
||||||
setGlobalRelaxation(relaxation: number): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
87395
src/lib/phaser.d.ts
vendored
87395
src/lib/phaser.d.ts
vendored
File diff suppressed because it is too large
Load diff
1855
src/lib/pixi.d.ts
vendored
1855
src/lib/pixi.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
@ -2,12 +2,7 @@
|
||||||
|
|
||||||
module TK.SpaceTac.UI.Specs {
|
module TK.SpaceTac.UI.Specs {
|
||||||
testing("AssetLoading", test => {
|
testing("AssetLoading", test => {
|
||||||
let testgame = setupSingleView(test, () => [new AssetLoading(), []]);
|
let testgame = setupSingleView(test, () => [new AssetLoading({}), []]);
|
||||||
|
|
||||||
test.case("loads correctly", check => {
|
|
||||||
check.equals(testgame.ui.state.current, "test");
|
|
||||||
// TODO test asset loading
|
|
||||||
});
|
|
||||||
|
|
||||||
test.case("builds cache keys from path", check => {
|
test.case("builds cache keys from path", check => {
|
||||||
check.equals(AssetLoading.getKey("dir/file-path"), "dir-file-path");
|
check.equals(AssetLoading.getKey("dir/file-path"), "dir-file-path");
|
||||||
|
|
|
@ -26,19 +26,28 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(range = AssetLoadingRange.NONE) {
|
init(data: any) {
|
||||||
super.init();
|
super.init(data);
|
||||||
|
|
||||||
this.required = range;
|
this.required = data ? data.range : AssetLoadingRange.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
let bg = this.add.image(643, 435, "preload-background");
|
let bg = this.add.image(643, 435, "preload-background");
|
||||||
|
bg.setOrigin(0);
|
||||||
let bar = this.add.image(643, 435, "preload-bar");
|
let bar = this.add.image(643, 435, "preload-bar");
|
||||||
this.load.setPreloadSprite(bar);
|
bar.setOrigin(0);
|
||||||
|
let mask = this.make.graphics({ x: bar.x, y: bar.y, add: false });
|
||||||
|
mask.fillStyle(0xffffff);
|
||||||
|
bar.setMask(new Phaser.Display.Masks.GeometryMask(this, mask));
|
||||||
|
|
||||||
|
this.load.on('progress', (value: number) => {
|
||||||
|
mask.clear();
|
||||||
|
mask.fillRect(0, 0, value * bar.width, bar.height);
|
||||||
|
});
|
||||||
|
|
||||||
let text = this.add.text(this.getMidWidth(), 466, "... Loading ...", { font: "normal 36pt SpaceTac", fill: "#dbeff9" });
|
let text = this.add.text(this.getMidWidth(), 466, "... Loading ...", { font: "normal 36pt SpaceTac", fill: "#dbeff9" });
|
||||||
text.anchor.set(0.5);
|
text.setOrigin(0.5);
|
||||||
|
|
||||||
if (this.required >= AssetLoadingRange.MENU && AssetLoading.loaded < AssetLoadingRange.MENU) {
|
if (this.required >= AssetLoadingRange.MENU && AssetLoading.loaded < AssetLoadingRange.MENU) {
|
||||||
console.log("Loading menu assets");
|
console.log("Loading menu assets");
|
||||||
|
@ -58,15 +67,13 @@ module TK.SpaceTac.UI {
|
||||||
console.log("Loading campaign assets");
|
console.log("Loading campaign assets");
|
||||||
this.load.pack("stage3", `assets/pack3.json?t=${Date.now()}`);
|
this.load.pack("stage3", `assets/pack3.json?t=${Date.now()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.load.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
super.create();
|
super.create();
|
||||||
|
|
||||||
AssetLoading.loaded = Math.max(AssetLoading.loaded, this.required);
|
AssetLoading.loaded = Math.max(AssetLoading.loaded, this.required);
|
||||||
this.game.state.start("router");
|
this.backToRouter();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getKey(path: string): string {
|
static getKey(path: string): string {
|
||||||
|
@ -74,11 +81,19 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSheet(path: string, frame_width: number, frame_height = frame_width) {
|
loadSheet(path: string, frame_width: number, frame_height = frame_width) {
|
||||||
this.load.spritesheet(AssetLoading.getKey(path), "images/" + path, frame_width, frame_height);
|
this.load.spritesheet(AssetLoading.getKey(path), "images/" + path, {
|
||||||
|
frameWidth: frame_width,
|
||||||
|
frameHeight: frame_height,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAnimation(path: string, frame_width: number, frame_height = frame_width, count?: number) {
|
loadAnimation(path: string, frame_width: number, frame_height = frame_width, count: number) {
|
||||||
this.load.spritesheet(AssetLoading.getKey(path), "images/" + path, frame_width, frame_height, count);
|
this.load.spritesheet(AssetLoading.getKey(path), "images/" + path, {
|
||||||
|
frameWidth: frame_width,
|
||||||
|
frameHeight: frame_height,
|
||||||
|
startFrame: 0,
|
||||||
|
endFrame: count - 1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let testgame = setupEmptyView(test);
|
let testgame = setupEmptyView(test);
|
||||||
|
|
||||||
test.case("initializes variables", check => {
|
test.case("initializes variables", check => {
|
||||||
let view = <BaseView>testgame.ui.state.getCurrentState();
|
let view = nn(testgame.ui.getActiveScene());
|
||||||
|
|
||||||
check.equals(view.messages instanceof Messages, true);
|
check.equals(view.messages instanceof Messages, true);
|
||||||
check.equals(view.inputs instanceof InputManager, true);
|
check.equals(view.inputs instanceof InputManager, true);
|
||||||
|
|
|
@ -2,42 +2,47 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Base class for all game views
|
* Base class for all game views
|
||||||
*/
|
*/
|
||||||
export class BaseView extends Phaser.State {
|
export class BaseView extends Phaser.Scene {
|
||||||
// Link to the root UI
|
// Link to the root UI
|
||||||
gameui!: MainUI
|
gameui!: MainUI
|
||||||
|
|
||||||
// Message notifications
|
// Message notifications
|
||||||
|
messages_layer!: UIContainer
|
||||||
messages!: Messages
|
messages!: Messages
|
||||||
|
|
||||||
|
// Audio system
|
||||||
|
audio!: Audio
|
||||||
|
|
||||||
// Input and key bindings
|
// Input and key bindings
|
||||||
inputs!: InputManager
|
inputs!: InputManager
|
||||||
|
|
||||||
// Animations
|
// Animations
|
||||||
animations!: Animations
|
animations!: Animations
|
||||||
|
particles!: ParticleSystem
|
||||||
|
|
||||||
// Timing
|
// Timing
|
||||||
timer!: Timer
|
timer!: Timer
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
tooltip_layer!: Phaser.Group
|
tooltip_layer!: UIContainer
|
||||||
tooltip!: Tooltip
|
tooltip!: Tooltip
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
layers!: Phaser.Group
|
layers!: UIContainer
|
||||||
|
|
||||||
// Modal dialogs
|
// Modal dialogs
|
||||||
dialogs_layer!: Phaser.Group
|
dialogs_layer!: UIContainer
|
||||||
dialogs_opened: UIDialog[] = []
|
dialogs_opened: UIDialog[] = []
|
||||||
|
|
||||||
// Verbose debug output
|
// Verbose debug output
|
||||||
readonly debug = false
|
debug = false
|
||||||
|
|
||||||
// Get the size of display
|
// Get the size of display
|
||||||
getWidth(): number {
|
getWidth(): number {
|
||||||
return this.game.width || 1280;
|
return this.cameras.main.width;
|
||||||
}
|
}
|
||||||
getHeight(): number {
|
getHeight(): number {
|
||||||
return this.game.height || 720;
|
return this.cameras.main.height;
|
||||||
}
|
}
|
||||||
getMidWidth(): number {
|
getMidWidth(): number {
|
||||||
return this.getWidth() / 2;
|
return this.getWidth() / 2;
|
||||||
|
@ -46,28 +51,38 @@ module TK.SpaceTac.UI {
|
||||||
return this.getHeight() / 2;
|
return this.getHeight() / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(...args: any[]) {
|
init(data: object) {
|
||||||
this.gameui = <MainUI>this.game;
|
console.log(`Starting scene ${classname(this)}`);
|
||||||
|
|
||||||
|
this.gameui = <MainUI>this.sys.game;
|
||||||
this.timer = new Timer(this.gameui.headless);
|
this.timer = new Timer(this.gameui.headless);
|
||||||
|
this.animations = new Animations(this.tweens);
|
||||||
|
this.particles = new ParticleSystem(this);
|
||||||
|
this.inputs = new InputManager(this);
|
||||||
|
this.audio = new Audio(this);
|
||||||
|
this.debug = this.gameui.debug;
|
||||||
|
|
||||||
|
this.events.once("shutdown", () => this.shutdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown() {
|
||||||
|
console.log(`Shutting down scene ${classname(this)}`);
|
||||||
|
|
||||||
|
this.inputs.destroy();
|
||||||
|
this.audio.stopMusic();
|
||||||
|
this.timer.cancelAll(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
// Phaser config
|
|
||||||
this.game.stage.backgroundColor = 0x000000;
|
|
||||||
this.game.stage.disableVisibilityChange = this.gameui.headless;
|
|
||||||
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
|
|
||||||
this.scale.fullScreenScaleMode = Phaser.ScaleManager.SHOW_ALL;
|
|
||||||
this.input.maxPointers = 1;
|
|
||||||
|
|
||||||
// Tools
|
|
||||||
this.animations = new Animations(this.game.tweens);
|
|
||||||
this.inputs = new InputManager(this);
|
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
this.layers = this.add.group(undefined, "View layers");
|
this.layers = this.add.container(0, 0);
|
||||||
this.dialogs_layer = this.add.group(undefined, "Dialogs layer");
|
this.layers.setName("View layers");
|
||||||
this.tooltip_layer = this.add.group(undefined, "Tooltip layer");
|
this.dialogs_layer = this.add.container(0, 0);
|
||||||
|
this.dialogs_layer.setName("Dialogs layer");
|
||||||
|
this.tooltip_layer = this.add.container(0, 0);
|
||||||
|
this.tooltip_layer.setName("Tooltip layer");
|
||||||
this.tooltip = new Tooltip(this);
|
this.tooltip = new Tooltip(this);
|
||||||
|
this.messages_layer = this.add.container(0, 0);
|
||||||
this.messages = new Messages(this);
|
this.messages = new Messages(this);
|
||||||
this.dialogs_opened = [];
|
this.dialogs_opened = [];
|
||||||
|
|
||||||
|
@ -81,21 +96,8 @@ module TK.SpaceTac.UI {
|
||||||
(<any>window).view = this;
|
(<any>window).view = this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.create();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdown() {
|
|
||||||
this.audio.stopMusic();
|
|
||||||
|
|
||||||
super.shutdown();
|
|
||||||
|
|
||||||
this.timer.cancelAll(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
get audio() {
|
|
||||||
return this.gameui.audio;
|
|
||||||
}
|
|
||||||
get options() {
|
get options() {
|
||||||
return this.gameui.options;
|
return this.gameui.options;
|
||||||
}
|
}
|
||||||
|
@ -107,18 +109,22 @@ module TK.SpaceTac.UI {
|
||||||
* Go back to the router state
|
* Go back to the router state
|
||||||
*/
|
*/
|
||||||
backToRouter() {
|
backToRouter() {
|
||||||
this.game.state.start('router');
|
this.scene.start('router');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a layer in the view, by its name
|
* Get or create a layer in the view, by its name
|
||||||
*/
|
*/
|
||||||
getLayer(name: string): Phaser.Group {
|
getLayer(name: string): UIContainer {
|
||||||
let layer = <Phaser.Group>this.layers.getByName(name);
|
let layer = this.layers.getByName(name);
|
||||||
if (!layer) {
|
if (layer && layer instanceof UIContainer) {
|
||||||
layer = this.add.group(this.layers, name);
|
return layer;
|
||||||
|
} else {
|
||||||
|
let layer = new UIContainer(this);
|
||||||
|
layer.setName(name);
|
||||||
|
this.layers.add(layer);
|
||||||
|
return layer;
|
||||||
}
|
}
|
||||||
return layer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,81 +187,43 @@ module TK.SpaceTac.UI {
|
||||||
* Check if the mouse is inside a given area
|
* Check if the mouse is inside a given area
|
||||||
*/
|
*/
|
||||||
isMouseInside(area: IBounded): boolean {
|
isMouseInside(area: IBounded): boolean {
|
||||||
let pos = this.input.mousePointer.position;
|
let pos = this.input.activePointer.position;
|
||||||
return pos.x >= area.x && pos.x < area.x + area.width && pos.y >= area.y && pos.y < area.y + area.height;
|
return pos.x >= area.x && pos.x < area.x + area.width && pos.y >= area.y && pos.y < area.y + area.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a simple text
|
|
||||||
*/
|
|
||||||
newText(content: string, x = 0, y = 0, size = 16, color = "#ffffff", shadow = true, bold = false, center = true, vcenter = center, width = 0): Phaser.Text {
|
|
||||||
let style = { font: `${bold ? "bold " : ""}${size}pt SpaceTac`, fill: color, align: center ? "center" : "left" };
|
|
||||||
let text = new Phaser.Text(this.game, x, y, content, style);
|
|
||||||
text.anchor.set(center ? 0.5 : 0, vcenter ? 0.5 : 0);
|
|
||||||
if (width) {
|
|
||||||
text.wordWrap = true;
|
|
||||||
text.wordWrapWidth = width;
|
|
||||||
}
|
|
||||||
if (shadow) {
|
|
||||||
text.setShadow(3, 4, "rgba(0,0,0,0.6)", 6);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a new image from an atlas name
|
* Get a new image from an atlas name
|
||||||
*/
|
*/
|
||||||
newImage(name: string, x = 0, y = 0): Phaser.Image {
|
newImage(name: string, x = 0, y = 0): UIImage {
|
||||||
let info = this.getImageInfo(name);
|
let info = this.getImageInfo(name);
|
||||||
let result = this.game.add.image(x, y, info.key, info.frame);
|
let result = this.add.image(x, y, info.key, info.frame);
|
||||||
result.name = name;
|
result.name = name;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a new button from an atlas name
|
|
||||||
*/
|
|
||||||
newButton(name: string, x = 0, y = 0, onclick?: Function): Phaser.Button {
|
|
||||||
let info = this.getImageInfo(name);
|
|
||||||
let button = new Phaser.Button(this.game, x, y, info.key, onclick || nop, null, info.frame, info.frame);
|
|
||||||
let clickable = bool(onclick);
|
|
||||||
button.input.useHandCursor = clickable;
|
|
||||||
if (clickable) {
|
|
||||||
UIComponent.setButtonSound(button);
|
|
||||||
}
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an image from an atlas name
|
* Update an image from an atlas name
|
||||||
*/
|
*/
|
||||||
changeImage(image: Phaser.Image | Phaser.Button, name: string): void {
|
changeImage(image: UIImage, name: string): void {
|
||||||
let info = this.getImageInfo(name);
|
let info = this.getImageInfo(name);
|
||||||
image.name = name;
|
image.setName(name);
|
||||||
if (image instanceof Phaser.Button) {
|
image.setTexture(info.key, info.frame);
|
||||||
image.loadTexture(info.key);
|
|
||||||
image.setFrames(info.frame, info.frame);
|
|
||||||
} else {
|
|
||||||
image.loadTexture(info.key, info.frame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an image from atlases
|
* Get an image from atlases
|
||||||
*/
|
*/
|
||||||
getImageInfo(name: string): { key: string, frame: number, exists: boolean } {
|
getImageInfo(name: string): { key: string, frame: number | string, exists: boolean } {
|
||||||
// TODO Cache
|
if (this.textures.exists(name)) {
|
||||||
if (this.game.cache.checkImageKey(name)) {
|
|
||||||
return { key: name, frame: 0, exists: true };
|
return { key: name, frame: 0, exists: true };
|
||||||
} else {
|
} else {
|
||||||
for (let j = 1; j <= 3; j++) {
|
for (let j = 1; j <= 3; j++) {
|
||||||
let i = 1;
|
let i = 1;
|
||||||
while (this.game.cache.checkImageKey(`atlas${j}-${i}`)) {
|
while (this.textures.exists(`atlas${j}-${i}`)) {
|
||||||
let data = this.game.cache.getFrameData(`atlas${j}-${i}`);
|
let frames = this.textures.get(`atlas${j}-${i}`).getFrameNames();
|
||||||
let frames = data.getFrames();
|
let frame = first(frames, frame => AssetLoading.getKey(frame) == `data-stage${j}-image-${name}`);
|
||||||
let frame = first(frames, frame => AssetLoading.getKey(frame.name) == `data-stage${j}-image-${name}`);
|
|
||||||
if (frame) {
|
if (frame) {
|
||||||
return { key: `atlas${j}-${i}`, frame: frame.index, exists: true };
|
return { key: `atlas${j}-${i}`, frame: frame, exists: true };
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@ -270,5 +238,12 @@ module TK.SpaceTac.UI {
|
||||||
getFirstImage(...names: string[]): string {
|
getFirstImage(...names: string[]): string {
|
||||||
return first(names, name => this.getImageInfo(name).key.substr(0, 9) != '-missing-') || names[names.length - 1];
|
return first(names, name => this.getImageInfo(name).key.substr(0, 9) != '-missing-') || names[names.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the scene is paused
|
||||||
|
*/
|
||||||
|
isPaused(): boolean {
|
||||||
|
return this.time.paused;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
module TK.SpaceTac.UI.Specs {
|
module TK.SpaceTac.UI.Specs {
|
||||||
testing("Boot", test => {
|
testing("Boot", test => {
|
||||||
let testgame = setupSingleView(test, () => [new Boot(), []]);
|
let testgame = setupSingleView(test, () => [new Boot({}), {}]);
|
||||||
|
|
||||||
test.case("places empty loading background", check => {
|
test.case("places empty loading background", check => {
|
||||||
check.equals(testgame.ui.world.children.length, 1);
|
check.equals(testgame.view.children.length, 1);
|
||||||
check.equals(testgame.ui.world.children[0] instanceof Phaser.Image, true);
|
check.equals(testgame.view.children.list[0] instanceof Phaser.GameObjects.Image, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,23 +4,16 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* It is responsible to prepare the screen, and the asset loading.
|
* It is responsible to prepare the screen, and the asset loading.
|
||||||
*/
|
*/
|
||||||
export class Boot extends Phaser.State {
|
export class Boot extends Phaser.Scene {
|
||||||
preload() {
|
preload() {
|
||||||
if (!(<MainUI>this.game).headless) {
|
this.load.image("preload-background", "images/preload/bar-background.png");
|
||||||
this.load.image("preload-background", "images/preload/bar-background.png");
|
this.load.image("preload-bar", "images/preload/bar-content.png");
|
||||||
this.load.image("preload-bar", "images/preload/bar-content.png");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
this.game.stage.backgroundColor = 0x000000;
|
|
||||||
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
|
|
||||||
this.scale.fullScreenScaleMode = Phaser.ScaleManager.SHOW_ALL;
|
|
||||||
this.input.maxPointers = 1;
|
|
||||||
|
|
||||||
this.add.image(643, 435, "preload-background");
|
this.add.image(643, 435, "preload-background");
|
||||||
|
|
||||||
this.game.state.start("router");
|
this.scene.start("router");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
module TK.SpaceTac.UI.Specs {
|
module TK.SpaceTac.UI.Specs {
|
||||||
testing("Router", test => {
|
testing("Router", test => {
|
||||||
let testgame = setupSingleView(test, () => [new Router(), []]);
|
let testgame = setupSingleView(test, () => [new Router({}), {}]);
|
||||||
|
|
||||||
test.case("loads correctly", check => {
|
test.case("loads correctly", check => {
|
||||||
check.equals(testgame.ui.state.current, "test");
|
check.instance(testgame.ui.getActiveScene(), Router, "active scene should be Router");
|
||||||
// TODO test routing
|
// TODO test routing
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,19 +6,20 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* If needed, it will go back to the asset loading state.
|
* If needed, it will go back to the asset loading state.
|
||||||
*/
|
*/
|
||||||
export class Router extends Phaser.State {
|
export class Router extends BaseView {
|
||||||
create() {
|
create() {
|
||||||
var ui = <MainUI>this.game;
|
super.create();
|
||||||
var session = ui.session;
|
|
||||||
|
let session = this.session;
|
||||||
|
|
||||||
if (session.getBattle()) {
|
if (session.getBattle()) {
|
||||||
// A battle is raging, go to it
|
// A battle is raging, go to it
|
||||||
this.goToState("battle", AssetLoadingRange.BATTLE, session.player, session.getBattle());
|
this.goToState("battle", AssetLoadingRange.BATTLE, { player: session.player, battle: session.getBattle() });
|
||||||
} else if (session.hasUniverse()) {
|
} else if (session.hasUniverse()) {
|
||||||
// Campaign mode
|
// Campaign mode
|
||||||
if (session.isFleetCreated()) {
|
if (session.isFleetCreated()) {
|
||||||
// Go to the universe map
|
// Go to the universe map
|
||||||
this.goToState("universe", AssetLoadingRange.CAMPAIGN, session.universe, session.player);
|
this.goToState("universe", AssetLoadingRange.CAMPAIGN, { player: session.player, universe: session.universe });
|
||||||
} else if (session.isIntroViewed()) {
|
} else if (session.isIntroViewed()) {
|
||||||
// Build initial fleet
|
// Build initial fleet
|
||||||
this.goToState("creation", AssetLoadingRange.CAMPAIGN);
|
this.goToState("creation", AssetLoadingRange.CAMPAIGN);
|
||||||
|
@ -32,11 +33,11 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goToState(name: string, asset_range: AssetLoadingRange, ...args: any[]) {
|
goToState(name: string, asset_range: AssetLoadingRange, data?: object) {
|
||||||
if (AssetLoading.isRangeLoaded(this.game, asset_range)) {
|
if (AssetLoading.isRangeLoaded(this.game, asset_range)) {
|
||||||
this.game.state.start(name, true, false, ...args);
|
this.scene.start(name, data);
|
||||||
} else {
|
} else {
|
||||||
this.game.state.start("loading", true, false, asset_range);
|
this.scene.start("loading", { range: asset_range });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,22 @@
|
||||||
/// <reference path="../common/Testing.ts" />
|
/// <reference path="../common/Testing.ts" />
|
||||||
|
|
||||||
module TK.SpaceTac.UI.Specs {
|
module TK.SpaceTac.UI.Specs {
|
||||||
let test_ui: MainUI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to hold references to test objects (used as singleton in "describe" blocks)
|
* Class to hold references to test objects (used as singleton in "describe" blocks)
|
||||||
*
|
*
|
||||||
* Attributes should only be accessed from inside corresponding "it" blocks (they are initialized by the setup).
|
* Attributes should only be accessed from inside corresponding "it" blocks (they are initialized by the setup).
|
||||||
*/
|
*/
|
||||||
export class TestGame<T extends Phaser.State> {
|
export class TestGame<T extends Phaser.Scene> {
|
||||||
ui!: MainUI;
|
ui!: MainUI;
|
||||||
view!: T;
|
view!: T;
|
||||||
multistorage!: Multi.FakeRemoteStorage;
|
multistorage!: Multi.FakeRemoteStorage;
|
||||||
state!: string;
|
|
||||||
clock!: FakeClock;
|
clock!: FakeClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup a headless test UI, with a single view started.
|
* Setup a headless test UI, with a single view started.
|
||||||
*/
|
*/
|
||||||
export function setupSingleView<T extends Phaser.State>(test: TestSuite, buildView: () => [T, any[]]) {
|
export function setupSingleView<T extends Phaser.Scene & { create: Function }>(test: TestSuite, buildView: () => [T, object]) {
|
||||||
let testgame = new TestGame<T>();
|
let testgame = new TestGame<T>();
|
||||||
|
|
||||||
test.asetup(() => new Promise((resolve, reject) => {
|
test.asetup(() => new Promise((resolve, reject) => {
|
||||||
|
@ -27,47 +24,26 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.patch(console, "log", null);
|
check.patch(console, "log", null);
|
||||||
check.patch(console, "warn", null);
|
check.patch(console, "warn", null);
|
||||||
|
|
||||||
if (!test_ui) {
|
testgame.ui = new MainUI(true);
|
||||||
test_ui = new MainUI(true);
|
|
||||||
|
|
||||||
if (test_ui.load) {
|
let [scene, scenedata] = buildView();
|
||||||
check.patch(test_ui.load, 'image', null);
|
|
||||||
check.patch(test_ui.load, 'audio', null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testgame.ui = test_ui;
|
if (scene instanceof BaseView) {
|
||||||
testgame.ui.resetSession();
|
|
||||||
|
|
||||||
let [state, stateargs] = buildView();
|
|
||||||
|
|
||||||
if (state instanceof BaseView) {
|
|
||||||
testgame.multistorage = new Multi.FakeRemoteStorage();
|
testgame.multistorage = new Multi.FakeRemoteStorage();
|
||||||
let connection = new Multi.Connection(RandomGenerator.global.id(12), testgame.multistorage);
|
let connection = new Multi.Connection(RandomGenerator.global.id(12), testgame.multistorage);
|
||||||
check.patch(state, "getConnection", () => connection);
|
check.patch(scene, "getConnection", () => connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
let orig_create = bound(state, "create");
|
let orig_create = bound(scene, "create");
|
||||||
check.patch(state, "create", () => {
|
check.patch(scene, "create", () => {
|
||||||
orig_create();
|
orig_create();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
testgame.ui.state.add("test", state);
|
testgame.ui.scene.add("test", scene, true, scenedata);
|
||||||
testgame.ui.state.start("test", true, false, ...stateargs);
|
|
||||||
|
|
||||||
testgame.state = "test_initial";
|
testgame.view = scene;
|
||||||
check.patch(testgame.ui.state, "start", (name: string) => {
|
}), () => testgame.ui.destroy(true));
|
||||||
testgame.state = name;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!testgame.ui.isBooted) {
|
|
||||||
testgame.ui.device.canvas = true;
|
|
||||||
testgame.ui.boot();
|
|
||||||
}
|
|
||||||
|
|
||||||
testgame.view = state;
|
|
||||||
}));
|
|
||||||
|
|
||||||
return testgame;
|
return testgame;
|
||||||
}
|
}
|
||||||
|
@ -77,7 +53,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
*/
|
*/
|
||||||
export function setupEmptyView(test: TestSuite): TestGame<BaseView> {
|
export function setupEmptyView(test: TestSuite): TestGame<BaseView> {
|
||||||
return setupSingleView(test, () => {
|
return setupSingleView(test, () => {
|
||||||
return [new BaseView(), []];
|
return [new BaseView({}), {}];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,14 +62,14 @@ module TK.SpaceTac.UI.Specs {
|
||||||
*/
|
*/
|
||||||
export function setupBattleview(test: TestSuite): TestGame<BattleView> {
|
export function setupBattleview(test: TestSuite): TestGame<BattleView> {
|
||||||
return setupSingleView(test, () => {
|
return setupSingleView(test, () => {
|
||||||
let view = new BattleView();
|
let view = new BattleView({});
|
||||||
view.splash = false;
|
view.splash = false;
|
||||||
|
|
||||||
let battle = Battle.newQuickRandom();
|
let battle = Battle.newQuickRandom();
|
||||||
let player = new Player();
|
let player = new Player();
|
||||||
nn(battle.playing_ship).fleet.setPlayer(player);
|
nn(battle.playing_ship).fleet.setPlayer(player);
|
||||||
|
|
||||||
return [view, [player, battle]];
|
return [view, { player, battle }];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,11 +78,11 @@ module TK.SpaceTac.UI.Specs {
|
||||||
*/
|
*/
|
||||||
export function setupMapview(test: TestSuite): TestGame<UniverseMapView> {
|
export function setupMapview(test: TestSuite): TestGame<UniverseMapView> {
|
||||||
return setupSingleView(test, () => {
|
return setupSingleView(test, () => {
|
||||||
let mapview = new UniverseMapView();
|
let mapview = new UniverseMapView({});
|
||||||
let session = new GameSession();
|
let session = new GameSession();
|
||||||
session.startNewGame();
|
session.startNewGame();
|
||||||
|
|
||||||
return [mapview, [session.universe, session.player]];
|
return [mapview, { universe: session.universe, player: session.player }];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,9 +90,9 @@ module TK.SpaceTac.UI.Specs {
|
||||||
* Crawn through the children of a node
|
* Crawn through the children of a node
|
||||||
*/
|
*/
|
||||||
export function crawlChildren(node: UIContainer, recursive: boolean, callback: (child: any) => void): void {
|
export function crawlChildren(node: UIContainer, recursive: boolean, callback: (child: any) => void): void {
|
||||||
node.children.forEach(child => {
|
node.list.forEach(child => {
|
||||||
callback(child);
|
callback(child);
|
||||||
if (recursive && (child instanceof Phaser.Group || child instanceof Phaser.Image)) {
|
if (recursive && child instanceof UIContainer) {
|
||||||
crawlChildren(child, true, callback);
|
crawlChildren(child, true, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -128,7 +104,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
export function collectImages(node: UIContainer, recursive = true): (string | null)[] {
|
export function collectImages(node: UIContainer, recursive = true): (string | null)[] {
|
||||||
let result: (string | null)[] = [];
|
let result: (string | null)[] = [];
|
||||||
crawlChildren(node, recursive, child => {
|
crawlChildren(node, recursive, child => {
|
||||||
if (child instanceof Phaser.Image) {
|
if (child instanceof UIImage) {
|
||||||
result.push(child.name || null);
|
result.push(child.name || null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -141,7 +117,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
export function collectTexts(node: UIContainer, recursive = true): (string | null)[] {
|
export function collectTexts(node: UIContainer, recursive = true): (string | null)[] {
|
||||||
let result: (string | null)[] = [];
|
let result: (string | null)[] = [];
|
||||||
crawlChildren(node, recursive, child => {
|
crawlChildren(node, recursive, child => {
|
||||||
if (child instanceof Phaser.Text) {
|
if (child instanceof UIText) {
|
||||||
result.push(child.text || null);
|
result.push(child.text || null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -152,20 +128,19 @@ module TK.SpaceTac.UI.Specs {
|
||||||
* Check a given text node
|
* Check a given text node
|
||||||
*/
|
*/
|
||||||
export function checkText(check: TestContext, node: any, content: string): void {
|
export function checkText(check: TestContext, node: any, content: string): void {
|
||||||
check.equals(node instanceof Phaser.Text, true);
|
if (check.instance(node, UIText, "node should be an UIText")) {
|
||||||
|
check.equals(node.text, content);
|
||||||
let tnode = <Phaser.Text>node;
|
}
|
||||||
check.equals(tnode.text, content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that a layer contains the given component at a given index
|
* Check that a layer contains the given component at a given index
|
||||||
*/
|
*/
|
||||||
export function checkComponentInLayer(check: TestContext, layer: Phaser.Group, index: number, component: UIComponent) {
|
export function checkComponentInLayer(check: TestContext, layer: UIContainer, index: number, component: UIComponent) {
|
||||||
if (index >= layer.children.length) {
|
if (index >= layer.list.length) {
|
||||||
check.fail(`Not enough children in group ${layer.name} for ${component} at index ${index}`);
|
check.fail(`Not enough children in group ${layer.name} for ${component} at index ${index}`);
|
||||||
} else {
|
} else {
|
||||||
let child = layer.children[index];
|
let child = layer.list[index];
|
||||||
if (child !== (<any>component).container) {
|
if (child !== (<any>component).container) {
|
||||||
check.fail(`${component} is not at index ${index} in ${layer.name}`);
|
check.fail(`${component} is not at index ${index} in ${layer.name}`);
|
||||||
}
|
}
|
||||||
|
@ -175,8 +150,8 @@ module TK.SpaceTac.UI.Specs {
|
||||||
/**
|
/**
|
||||||
* Simulate a click on a button
|
* Simulate a click on a button
|
||||||
*/
|
*/
|
||||||
export function testClick(button: Phaser.Button): void {
|
export function testClick(button: UIButton): void {
|
||||||
button.onInputDown.dispatch();
|
button.emit("pointerdown");
|
||||||
button.onInputUp.dispatch();
|
button.emit("pointerup");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,15 +38,16 @@ module TK.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
function checkpoints(desc: string, available = 0, using = 0, used = 0) {
|
function checkpoints(desc: string, available = 0, using = 0, used = 0) {
|
||||||
check.in(desc, check => {
|
check.in(desc, check => {
|
||||||
check.same(bar.power_icons.children.length, available + using + used, "icon count");
|
check.same(bar.power_icons.length, available + using + used, "icon count");
|
||||||
bar.power_icons.children.forEach((child, idx) => {
|
bar.power_icons.list.forEach((child, idx) => {
|
||||||
let img = <Phaser.Image>child;
|
if (check.instance(child, UIImage, `${idx} icon should be an image`)) {
|
||||||
if (idx < available) {
|
if (idx < available) {
|
||||||
check.equals(img.name, "battle-actionbar-power-available", `icon ${idx}`);
|
check.equals(child.name, "battle-actionbar-power-available", `icon ${idx}`);
|
||||||
} else if (idx < available + using) {
|
} else if (idx < available + using) {
|
||||||
check.equals(img.name, "battle-actionbar-power-move", `icon ${idx}`);
|
check.equals(child.name, "battle-actionbar-power-move", `icon ${idx}`);
|
||||||
} else {
|
} else {
|
||||||
check.equals(img.name, "battle-actionbar-power-used", `icon ${idx}`);
|
check.equals(child.name, "battle-actionbar-power-used", `icon ${idx}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
|
/// <reference path="../common/UIContainer.ts" />
|
||||||
|
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
// Bar with all available action icons displayed
|
/**
|
||||||
export class ActionBar extends Phaser.Group {
|
* Bar on the border of screen to display all available action icons
|
||||||
|
*/
|
||||||
|
export class ActionBar extends UIContainer {
|
||||||
// Link to the parent battleview
|
// Link to the parent battleview
|
||||||
battleview: BattleView
|
battleview: BattleView
|
||||||
|
|
||||||
// List of action icons
|
// List of action icons
|
||||||
actions: Phaser.Group
|
actions: UIContainer
|
||||||
action_icons: ActionIcon[]
|
action_icons: ActionIcon[]
|
||||||
|
|
||||||
// Power indicator
|
// Power indicator
|
||||||
power: Phaser.Group
|
power: UIContainer
|
||||||
power_icons!: Phaser.Group
|
power_icons!: UIContainer
|
||||||
|
|
||||||
// Indicator of interaction disabled
|
// Indicator of interaction disabled
|
||||||
icon_waiting: Phaser.Image
|
icon_waiting: UIImage
|
||||||
|
|
||||||
// Current ship, whose actions are displayed
|
// Current ship, whose actions are displayed
|
||||||
ship: Ship | null
|
ship: Ship | null
|
||||||
|
@ -23,7 +27,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// Create an empty action bar
|
// Create an empty action bar
|
||||||
constructor(battleview: BattleView) {
|
constructor(battleview: BattleView) {
|
||||||
super(battleview.game);
|
super(battleview);
|
||||||
|
|
||||||
this.battleview = battleview;
|
this.battleview = battleview;
|
||||||
this.action_icons = [];
|
this.action_icons = [];
|
||||||
|
@ -34,27 +38,26 @@ module TK.SpaceTac.UI {
|
||||||
let builder = new UIBuilder(battleview, this);
|
let builder = new UIBuilder(battleview, this);
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
builder.image("battle-actionbar-background");
|
let base = builder.image("battle-actionbar-background");
|
||||||
|
|
||||||
// Group for actions
|
// Group for actions
|
||||||
this.actions = builder.group("actions", 86, 6);
|
this.actions = builder.container("actions", 86, 6);
|
||||||
builder.in(this.actions).image("battle-actionbar-actions-background");
|
builder.in(this.actions).image("battle-actionbar-actions-background");
|
||||||
|
|
||||||
// Power bar
|
// Power bar
|
||||||
this.power = builder.group("power", 1466, 0);
|
this.power = builder.container("power", 1466, 0);
|
||||||
builder.in(this.power, builder => {
|
builder.in(this.power, builder => {
|
||||||
builder.image("battle-actionbar-power-background", 0, 6);
|
builder.image("battle-actionbar-power-background", 0, 6);
|
||||||
this.power_icons = builder.group("power icons", 50, 14);
|
this.power_icons = builder.container("power icons", 50, 14);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Playing ship
|
// Playing ship
|
||||||
builder.image("battle-actionbar-ship", 1735);
|
builder.image("battle-actionbar-ship", 1735);
|
||||||
|
|
||||||
// Waiting icon
|
// Waiting icon
|
||||||
this.icon_waiting = new Phaser.Image(this.game, this.width / 2, this.height / 2, "common-waiting", 0);
|
this.icon_waiting = builder.image("common-waiting", base.width / 2, base.height / 2, true);
|
||||||
this.icon_waiting.anchor.set(0.5, 0.5);
|
// FIXME
|
||||||
this.icon_waiting.animations.add("loop").play(9, true);
|
//this.icon_waiting.animations.add("loop").play(9, true);
|
||||||
this.add(this.icon_waiting);
|
|
||||||
|
|
||||||
// Options button
|
// Options button
|
||||||
builder.button("battle-actionbar-button-menu", 0, 0, () => battleview.showOptions(), "Game options");
|
builder.button("battle-actionbar-button-menu", 0, 0, () => battleview.showOptions(), "Game options");
|
||||||
|
@ -105,7 +108,7 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.setInteractive(false);
|
this.setInteractivity(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,7 +121,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Set the interactivity state
|
* Set the interactivity state
|
||||||
*/
|
*/
|
||||||
setInteractive(interactive: boolean) {
|
setInteractivity(interactive: boolean) {
|
||||||
if (this.interactive != interactive) {
|
if (this.interactive != interactive) {
|
||||||
this.interactive = interactive;
|
this.interactive = interactive;
|
||||||
|
|
||||||
|
@ -167,7 +170,7 @@ module TK.SpaceTac.UI {
|
||||||
let power_capacity = this.ship ? this.ship.getAttribute("power_capacity") : 0;
|
let power_capacity = this.ship ? this.ship.getAttribute("power_capacity") : 0;
|
||||||
let power_value = this.ship ? this.ship.getValue("power") : 0;
|
let power_value = this.ship ? this.ship.getValue("power") : 0;
|
||||||
|
|
||||||
let current_power = this.power_icons.children.length;
|
let current_power = this.power_icons.length;
|
||||||
|
|
||||||
if (current_power > power_capacity) {
|
if (current_power > power_capacity) {
|
||||||
destroyChildren(this.power_icons, power_capacity, current_power);
|
destroyChildren(this.power_icons, power_capacity, current_power);
|
||||||
|
@ -175,14 +178,13 @@ module TK.SpaceTac.UI {
|
||||||
range(power_capacity - current_power).forEach(i => {
|
range(power_capacity - current_power).forEach(i => {
|
||||||
let x = (current_power + i) % 5;
|
let x = (current_power + i) % 5;
|
||||||
let y = ((current_power + i) - x) / 5;
|
let y = ((current_power + i) - x) / 5;
|
||||||
let image = this.battleview.newImage("battle-actionbar-power-used", x * 43, y * 22);
|
let image = new UIBuilder(this.battleview, this.power_icons).image("battle-actionbar-power-used", x * 43, y * 22);
|
||||||
this.power_icons.add(image);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let remaining_power = power_value - move_power - fire_power;
|
let remaining_power = power_value - move_power - fire_power;
|
||||||
this.power_icons.children.forEach((obj, idx) => {
|
this.power_icons.list.forEach((obj, idx) => {
|
||||||
let img = <Phaser.Image>obj;
|
let img = <UIImage>obj;
|
||||||
if (idx < remaining_power) {
|
if (idx < remaining_power) {
|
||||||
this.battleview.changeImage(img, "battle-actionbar-power-available");
|
this.battleview.changeImage(img, "battle-actionbar-power-available");
|
||||||
} else if (idx < remaining_power + move_power) {
|
} else if (idx < remaining_power + move_power) {
|
||||||
|
|
|
@ -77,15 +77,15 @@ module TK.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
check.equals(icon.img_bottom.name, "battle-actionbar-bottom-enabled", "initial");
|
check.equals(icon.img_bottom.name, "battle-actionbar-bottom-enabled", "initial");
|
||||||
check.equals(icon.img_power.name, "battle-actionbar-consumption-enabled", "initial");
|
check.equals(icon.img_power.name, "battle-actionbar-consumption-enabled", "initial");
|
||||||
check.equals(icon.img_sticky.name, "battle-actionbar-sticky-untoggled", "initial");
|
check.equals(icon.img_cooldown.name, "battle-actionbar-sticky-untoggled", "initial");
|
||||||
check.same(icon.img_sticky.visible, true, "initial");
|
check.same(icon.img_cooldown.visible, true, "initial");
|
||||||
|
|
||||||
ship.actions.toggle(action, true);
|
ship.actions.toggle(action, true);
|
||||||
icon.refresh();
|
icon.refresh();
|
||||||
check.equals(icon.img_bottom.name, "battle-actionbar-bottom-toggled", "initial");
|
check.equals(icon.img_bottom.name, "battle-actionbar-bottom-toggled", "initial");
|
||||||
check.equals(icon.img_power.name, "battle-actionbar-consumption-toggled", "initial");
|
check.equals(icon.img_power.name, "battle-actionbar-consumption-toggled", "initial");
|
||||||
check.equals(icon.img_sticky.name, "battle-actionbar-sticky-toggled", "initial");
|
check.equals(icon.img_cooldown.name, "battle-actionbar-sticky-toggled", "initial");
|
||||||
check.same(icon.img_sticky.visible, true, "initial");
|
check.same(icon.img_cooldown.visible, true, "initial");
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("displays overheat/cooldown", check => {
|
test.case("displays overheat/cooldown", check => {
|
||||||
|
@ -96,29 +96,29 @@ module TK.SpaceTac.UI.Specs {
|
||||||
action.configureCooldown(1, 3);
|
action.configureCooldown(1, 3);
|
||||||
TestTools.setShipModel(ship, 100, 0, 5, 1, [action]);
|
TestTools.setShipModel(ship, 100, 0, 5, 1, [action]);
|
||||||
let icon = new ActionIcon(bar, ship, action, 0);
|
let icon = new ActionIcon(bar, ship, action, 0);
|
||||||
check.same(icon.img_sticky.visible, false, "initial");
|
check.same(icon.img_cooldown.visible, false, "initial");
|
||||||
check.equals(icon.img_sticky.name, "battle-actionbar-sticky-untoggled", "initial");
|
check.equals(icon.img_cooldown.name, "battle-actionbar-sticky-untoggled", "initial");
|
||||||
check.same(icon.img_sticky.children.length, 0, "initial");
|
check.same(icon.img_cooldown_group.length, 1, "initial");
|
||||||
|
|
||||||
icon.refresh(action);
|
icon.refresh(action);
|
||||||
check.same(icon.img_sticky.visible, true, "overheat");
|
check.same(icon.img_cooldown.visible, true, "overheat");
|
||||||
check.equals(icon.img_sticky.name, "battle-actionbar-sticky-overheat", "overheat");
|
check.equals(icon.img_cooldown.name, "battle-actionbar-sticky-overheat", "overheat");
|
||||||
check.same(icon.img_sticky.children.length, 3, "overheat");
|
check.same(icon.img_cooldown_group.length, 4, "overheat");
|
||||||
|
|
||||||
action.configureCooldown(1, 12);
|
action.configureCooldown(1, 12);
|
||||||
TestTools.setShipModel(ship, 100, 0, 5, 1, [action]);
|
TestTools.setShipModel(ship, 100, 0, 5, 1, [action]);
|
||||||
icon.refresh(action);
|
icon.refresh(action);
|
||||||
check.same(icon.img_sticky.visible, true, "superheat");
|
check.same(icon.img_cooldown.visible, true, "superheat");
|
||||||
check.equals(icon.img_sticky.name, "battle-actionbar-sticky-overheat", "superheat");
|
check.equals(icon.img_cooldown.name, "battle-actionbar-sticky-overheat", "superheat");
|
||||||
check.same(icon.img_sticky.children.length, 5, "superheat");
|
check.same(icon.img_cooldown_group.length, 6, "superheat");
|
||||||
|
|
||||||
action.configureCooldown(1, 4);
|
action.configureCooldown(1, 4);
|
||||||
TestTools.setShipModel(ship, 100, 0, 5, 1, [action]);
|
TestTools.setShipModel(ship, 100, 0, 5, 1, [action]);
|
||||||
ship.actions.getCooldown(action).use();
|
ship.actions.getCooldown(action).use();
|
||||||
icon.refresh(action);
|
icon.refresh(action);
|
||||||
check.same(icon.img_sticky.visible, true, "cooling");
|
check.same(icon.img_cooldown.visible, true, "cooling");
|
||||||
check.equals(icon.img_sticky.name, "battle-actionbar-sticky-disabled", "cooling");
|
check.equals(icon.img_cooldown.name, "battle-actionbar-sticky-disabled", "cooling");
|
||||||
check.same(icon.img_sticky.children.length, 4, "cooling");
|
check.same(icon.img_cooldown_group.length, 5, "cooling");
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("displays currently targetting", check => {
|
test.case("displays currently targetting", check => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ module TK.SpaceTac.UI {
|
||||||
view: BattleView
|
view: BattleView
|
||||||
|
|
||||||
// Container
|
// Container
|
||||||
container: Phaser.Button
|
container: UIButton
|
||||||
|
|
||||||
// Related ship
|
// Related ship
|
||||||
ship: Ship
|
ship: Ship
|
||||||
|
@ -25,40 +25,39 @@ module TK.SpaceTac.UI {
|
||||||
cooldown = 0
|
cooldown = 0
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
img_targetting!: Phaser.Image
|
img_targetting!: UIImage
|
||||||
img_top: Phaser.Image | null = null
|
img_top: UIImage | null = null
|
||||||
img_bottom: Phaser.Image
|
img_bottom: UIImage
|
||||||
img_power: Phaser.Image
|
img_power: UIImage
|
||||||
img_sticky: Phaser.Image
|
img_cooldown_group: UIContainer
|
||||||
img_action: Phaser.Image
|
img_cooldown: UIImage
|
||||||
|
img_action: UIImage
|
||||||
|
|
||||||
// Indicators
|
// Indicators
|
||||||
text_power!: Phaser.Text
|
text_power!: UIText
|
||||||
|
|
||||||
constructor(bar: ActionBar, ship: Ship, action: BaseAction, position: number) {
|
constructor(bar: ActionBar, ship: Ship, action: BaseAction, position: number) {
|
||||||
this.bar = bar;
|
this.bar = bar;
|
||||||
this.view = bar.battleview;
|
this.view = bar.battleview;
|
||||||
|
|
||||||
let info = this.view.getImageInfo("battle-actionbar-frame-disabled");
|
let builder = new UIBuilder(this.view);
|
||||||
this.container = this.view.add.button(0, 0, info.key, () => this.processClick(), undefined, info.frame, info.frame);
|
this.container = builder.button("battle-actionbar-frame-disabled", 0, 0, () => this.processClick(), filler => {
|
||||||
this.container.anchor.set(0.5);
|
ActionTooltip.fill(filler, this.ship, this.action, position);
|
||||||
this.container.input.useHandCursor = false;
|
return true;
|
||||||
|
}, undefined, { center: true });
|
||||||
|
builder = builder.in(this.container);
|
||||||
|
|
||||||
this.ship = ship;
|
this.ship = ship;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
|
|
||||||
let builder = new UIBuilder(this.view, this.container);
|
|
||||||
|
|
||||||
// Action icon
|
// Action icon
|
||||||
this.img_action = builder.image(`action-${action.code}`);
|
this.img_action = builder.image(`action-${action.code}`, 0, 0, true);
|
||||||
this.img_action.anchor.set(0.5);
|
this.img_action.setScale(0.35);
|
||||||
this.img_action.scale.set(0.35);
|
this.img_action.setAlpha(0.2);
|
||||||
this.img_action.alpha = 0.2;
|
|
||||||
|
|
||||||
// Hotkey indicator
|
// Hotkey indicator
|
||||||
if (!(action instanceof EndTurnAction)) {
|
if (!(action instanceof EndTurnAction)) {
|
||||||
this.img_top = builder.image("battle-actionbar-hotkey", 0, -47);
|
this.img_top = builder.image("battle-actionbar-hotkey", 0, -47, true);
|
||||||
this.img_top.anchor.set(0.5);
|
|
||||||
builder.in(this.img_top, builder => {
|
builder.in(this.img_top, builder => {
|
||||||
builder.text(`${(position + 1) % 10}`, 0, 0, {
|
builder.text(`${(position + 1) % 10}`, 0, 0, {
|
||||||
size: 12, color: "#d1d1d1", shadow: true, center: true, vcenter: true
|
size: 12, color: "#d1d1d1", shadow: true, center: true, vcenter: true
|
||||||
|
@ -67,19 +66,16 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom indicator
|
// Bottom indicator
|
||||||
this.img_bottom = builder.image("battle-actionbar-bottom-disabled", 0, 40);
|
this.img_bottom = builder.image("battle-actionbar-bottom-disabled", 0, 40, true);
|
||||||
this.img_bottom.anchor.set(0.5);
|
|
||||||
builder.in(this.img_bottom, builder => {
|
builder.in(this.img_bottom, builder => {
|
||||||
this.img_targetting = builder.image("battle-actionbar-bottom-targetting", 0, 12);
|
this.img_targetting = builder.image("battle-actionbar-bottom-targetting", 0, 12);
|
||||||
this.img_targetting.anchor.set(0.5);
|
this.img_targetting.setVisible(false);
|
||||||
this.img_targetting.visible = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Left indicator
|
// Left indicator
|
||||||
this.selected = false;
|
this.selected = false;
|
||||||
this.img_power = builder.image("battle-actionbar-consumption-disabled", -46);
|
this.img_power = builder.image("battle-actionbar-consumption-disabled", -46, 0, true);
|
||||||
this.img_power.anchor.set(0.5);
|
this.img_power.setVisible(false);
|
||||||
this.img_power.visible = false;
|
|
||||||
builder.in(this.img_power, builder => {
|
builder.in(this.img_power, builder => {
|
||||||
this.text_power = builder.text("", -2, 4, {
|
this.text_power = builder.text("", -2, 4, {
|
||||||
size: 16, color: "#ffdd4b", shadow: true, center: true, vcenter: true
|
size: 16, color: "#ffdd4b", shadow: true, center: true, vcenter: true
|
||||||
|
@ -87,15 +83,8 @@ module TK.SpaceTac.UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Right indicator
|
// Right indicator
|
||||||
this.img_sticky = builder.image("battle-actionbar-sticky-untoggled", 46);
|
this.img_cooldown_group = builder.container("cooldown", 46, 0, action instanceof ToggleAction);
|
||||||
this.img_sticky.anchor.set(0.5);
|
this.img_cooldown = builder.in(this.img_cooldown_group).image("battle-actionbar-sticky-untoggled", 46, 0, true);
|
||||||
this.img_sticky.visible = action instanceof ToggleAction;
|
|
||||||
|
|
||||||
// Events
|
|
||||||
this.view.tooltip.bind(this.container, filler => {
|
|
||||||
ActionTooltip.fill(filler, this.ship, this.action, position);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
@ -111,9 +100,9 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Move to a given layer and position
|
* Move to a given layer and position
|
||||||
*/
|
*/
|
||||||
moveTo(layer: Phaser.Group, x = 0, y = 0): void {
|
moveTo(layer: UIContainer, x = 0, y = 0): void {
|
||||||
layer.add(this.container);
|
layer.add(this.container);
|
||||||
this.container.position.set(x, y);
|
this.container.setPosition(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,7 +171,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// inputs
|
// inputs
|
||||||
if (disabled != this.disabled) {
|
if (disabled != this.disabled) {
|
||||||
this.container.input.useHandCursor = !disabled;
|
//this.container.input.useHandCursor = !disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// frame
|
// frame
|
||||||
|
@ -193,11 +182,7 @@ module TK.SpaceTac.UI {
|
||||||
} else if (fading) {
|
} else if (fading) {
|
||||||
name = "battle-actionbar-frame-fading";
|
name = "battle-actionbar-frame-fading";
|
||||||
}
|
}
|
||||||
|
this.container.setBaseImage(name);
|
||||||
let info = this.view.getImageInfo(name);
|
|
||||||
this.container.name = name;
|
|
||||||
this.container.loadTexture(info.key);
|
|
||||||
this.container.setFrames(info.frame, info.frame, info.frame, info.frame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// action icon
|
// action icon
|
||||||
|
@ -226,10 +211,10 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// left
|
// left
|
||||||
let cost = this.action.getPowerUsage(this.ship, null);
|
let cost = this.action.getPowerUsage(this.ship, null);
|
||||||
this.img_power.visible = bool(cost);
|
this.img_power.setVisible(bool(cost));
|
||||||
this.text_power.text = `${Math.abs(cost)}\n${cost < 0 ? "+" : "-"}`;
|
this.text_power.setText(`${Math.abs(cost)}\n${cost < 0 ? "+" : "-"}`);
|
||||||
this.text_power.fill = (cost > 0) ? "#ffdd4b" : "#dbe748";
|
this.text_power.setColor((cost > 0) ? "#ffdd4b" : "#dbe748");
|
||||||
this.text_power.alpha = disabled ? 0.2 : 1;
|
this.text_power.setAlpha(disabled ? 0.2 : 1);
|
||||||
if (disabled != this.disabled || selected != this.selected || toggled != this.toggled) {
|
if (disabled != this.disabled || selected != this.selected || toggled != this.toggled) {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
this.view.changeImage(this.img_power, "battle-actionbar-consumption-disabled");
|
this.view.changeImage(this.img_power, "battle-actionbar-consumption-disabled");
|
||||||
|
@ -244,27 +229,28 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// right
|
// right
|
||||||
if (toggled != this.toggled || disabled != this.disabled || heat != this.cooldown) {
|
if (toggled != this.toggled || disabled != this.disabled || heat != this.cooldown) {
|
||||||
destroyChildren(this.img_sticky);
|
let builder = new UIBuilder(this.view, this.img_cooldown_group);
|
||||||
|
destroyChildren(this.img_cooldown_group, 1);
|
||||||
if (this.action instanceof ToggleAction) {
|
if (this.action instanceof ToggleAction) {
|
||||||
if (toggled) {
|
if (toggled) {
|
||||||
this.view.changeImage(this.img_sticky, "battle-actionbar-sticky-toggled");
|
builder.change(this.img_cooldown, "battle-actionbar-sticky-toggled");
|
||||||
} else {
|
} else {
|
||||||
this.view.changeImage(this.img_sticky, "battle-actionbar-sticky-untoggled");
|
builder.change(this.img_cooldown, "battle-actionbar-sticky-untoggled");
|
||||||
}
|
}
|
||||||
this.img_sticky.visible = !disabled;
|
this.img_cooldown.visible = !disabled;
|
||||||
} else if (heat) {
|
} else if (heat) {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
this.view.changeImage(this.img_sticky, "battle-actionbar-sticky-disabled");
|
builder.change(this.img_cooldown, "battle-actionbar-sticky-disabled");
|
||||||
} else {
|
} else {
|
||||||
this.view.changeImage(this.img_sticky, "battle-actionbar-sticky-overheat");
|
builder.change(this.img_cooldown, "battle-actionbar-sticky-overheat");
|
||||||
}
|
}
|
||||||
range(Math.min(heat - 1, 4)).forEach(i => {
|
range(Math.min(heat - 1, 4)).forEach(i => {
|
||||||
this.img_sticky.addChild(this.view.newImage("battle-actionbar-cooldown-one", 0, 2 - i * 7));
|
builder.image("battle-actionbar-cooldown-one", 0, 2 - i * 7);
|
||||||
});
|
});
|
||||||
this.img_sticky.addChild(this.view.newImage("battle-actionbar-cooldown-front", -4, -20));
|
builder.image("battle-actionbar-cooldown-front", -4, -20);
|
||||||
this.img_sticky.visible = true;
|
this.img_cooldown.visible = true;
|
||||||
} else {
|
} else {
|
||||||
this.img_sticky.visible = false;
|
this.img_cooldown.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,23 +14,23 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let action3 = ship.actions.addCustom(new EndTurnAction());
|
let action3 = ship.actions.addCustom(new EndTurnAction());
|
||||||
|
|
||||||
ActionTooltip.fill(tooltip.getBuilder(), ship, action1, 0);
|
ActionTooltip.fill(tooltip.getBuilder(), ship, action1, 0);
|
||||||
checkText(check, (<any>tooltip).container.content.children[1], "Use Thruster");
|
checkText(check, tooltip.container.content.list[1], "Use Thruster");
|
||||||
checkText(check, (<any>tooltip).container.content.children[2], "Cost: 1 power per 0km");
|
checkText(check, tooltip.container.content.list[2], "Cost: 1 power per 0km");
|
||||||
checkText(check, (<any>tooltip).container.content.children[3], "Move: 0km per power point (safety: 120km)");
|
checkText(check, tooltip.container.content.list[3], "Move: 0km per power point (safety: 120km)");
|
||||||
checkText(check, (<any>tooltip).container.content.children[4], "[ 1 ]");
|
checkText(check, tooltip.container.content.list[4], "[ 1 ]");
|
||||||
|
|
||||||
tooltip.hide();
|
tooltip.hide();
|
||||||
ActionTooltip.fill(tooltip.getBuilder(), ship, action2, 1);
|
ActionTooltip.fill(tooltip.getBuilder(), ship, action2, 1);
|
||||||
checkText(check, (<any>tooltip).container.content.children[1], "Fire Superweapon");
|
checkText(check, tooltip.container.content.list[1], "Fire Superweapon");
|
||||||
checkText(check, (<any>tooltip).container.content.children[2], "Cost: 2 power");
|
checkText(check, tooltip.container.content.list[2], "Cost: 2 power");
|
||||||
checkText(check, (<any>tooltip).container.content.children[3], "Fire (power 2, range 50km):\n• do 12 damage on target");
|
checkText(check, tooltip.container.content.list[3], "Fire (power 2, range 50km):\n• do 12 damage on target");
|
||||||
checkText(check, (<any>tooltip).container.content.children[4], "[ 2 ]");
|
checkText(check, tooltip.container.content.list[4], "[ 2 ]");
|
||||||
|
|
||||||
tooltip.hide();
|
tooltip.hide();
|
||||||
ActionTooltip.fill(tooltip.getBuilder(), ship, action3, 2);
|
ActionTooltip.fill(tooltip.getBuilder(), ship, action3, 2);
|
||||||
checkText(check, (<any>tooltip).container.content.children[1], "End turn");
|
checkText(check, tooltip.container.content.list[1], "End turn");
|
||||||
checkText(check, (<any>tooltip).container.content.children[2], "End the current ship's turn.\nWill also generate power and cool down equipments.");
|
checkText(check, tooltip.container.content.list[2], "End the current ship's turn.\nWill also generate power and cool down equipments.");
|
||||||
checkText(check, (<any>tooltip).container.content.children[3], "[ space ]");
|
checkText(check, tooltip.container.content.list[3], "[ space ]");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ module TK.SpaceTac.UI {
|
||||||
let builder = filler.styled({ size: 20 });
|
let builder = filler.styled({ size: 20 });
|
||||||
|
|
||||||
let icon = builder.image(`action-${action.code}`);
|
let icon = builder.image(`action-${action.code}`);
|
||||||
icon.scale.set(0.5);
|
icon.setScale(0.5);
|
||||||
|
|
||||||
builder.text(action.getTitle(ship), 150, 0, { size: 24 });
|
builder.text(action.getTitle(ship), 150, 0, { size: 24 });
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,7 @@ module TK.SpaceTac.UI {
|
||||||
range_hint: RangeHint
|
range_hint: RangeHint
|
||||||
|
|
||||||
// Input capture
|
// Input capture
|
||||||
private mouse_capture?: Phaser.Button
|
private mouse_capture?: UIImage
|
||||||
|
|
||||||
// Input callback to receive mouse move events
|
|
||||||
private input_callback: any = null
|
|
||||||
|
|
||||||
// List of ship sprites
|
// List of ship sprites
|
||||||
private ship_sprites: ArenaShip[] = []
|
private ship_sprites: ArenaShip[] = []
|
||||||
|
@ -32,43 +29,46 @@ module TK.SpaceTac.UI {
|
||||||
private playing: ArenaShip | null
|
private playing: ArenaShip | null
|
||||||
|
|
||||||
// Layer for particles
|
// Layer for particles
|
||||||
container: Phaser.Group
|
container: UIContainer
|
||||||
layer_garbage: Phaser.Group
|
layer_garbage: UIContainer
|
||||||
layer_hints: Phaser.Group
|
layer_hints: UIContainer
|
||||||
layer_drones: Phaser.Group
|
layer_drones: UIContainer
|
||||||
layer_ships: Phaser.Group
|
layer_ships: UIContainer
|
||||||
layer_weapon_effects: Phaser.Group
|
layer_weapon_effects: UIContainer
|
||||||
layer_targetting: Phaser.Group
|
layer_targetting: UIContainer
|
||||||
|
|
||||||
// Callbacks to receive cursor events
|
// Callbacks to receive cursor events
|
||||||
callbacks_hover: ((location: ArenaLocation | null, ship: Ship | null) => void)[] = []
|
callbacks_hover: ((location: ArenaLocation | null, ship: Ship | null) => void)[] = []
|
||||||
callbacks_click: (() => void)[] = []
|
callbacks_click: (() => void)[] = []
|
||||||
|
|
||||||
// Create a graphical arena for ship sprites to fight in a 2D space
|
// Create a graphical arena for ship sprites to fight in a 2D space
|
||||||
constructor(view: BattleView, container?: Phaser.Group) {
|
constructor(view: BattleView, container?: UIContainer) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.playing = null;
|
this.playing = null;
|
||||||
this.hovered = null;
|
this.hovered = null;
|
||||||
this.range_hint = new RangeHint(this);
|
this.range_hint = new RangeHint(this);
|
||||||
|
|
||||||
this.container = container || new Phaser.Group(view.game, undefined, "arena");
|
let builder = new UIBuilder(view, container);
|
||||||
this.container.position.set(this.boundaries.x, this.boundaries.y);
|
if (!container) {
|
||||||
|
container = builder.container("arena");
|
||||||
|
builder = builder.in(container);
|
||||||
|
}
|
||||||
|
this.container = container;
|
||||||
|
container.setPosition(this.boundaries.x, this.boundaries.y);
|
||||||
|
|
||||||
this.setupMouseCapture();
|
this.setupMouseCapture();
|
||||||
|
|
||||||
this.layer_garbage = this.container.add(new Phaser.Group(view.game, undefined, "garbage"));
|
this.layer_garbage = builder.container("garbage");
|
||||||
this.layer_hints = this.container.add(new Phaser.Group(view.game, undefined, "hints"));
|
this.layer_hints = builder.container("hints");
|
||||||
this.layer_drones = this.container.add(new Phaser.Group(view.game, undefined, "drones"));
|
this.layer_drones = builder.container("drones");
|
||||||
this.layer_ships = this.container.add(new Phaser.Group(view.game, undefined, "ships"));
|
this.layer_ships = builder.container("ships");
|
||||||
this.layer_weapon_effects = this.container.add(new Phaser.Group(view.game, undefined, "effects"));
|
this.layer_weapon_effects = builder.container("effects");
|
||||||
this.layer_targetting = this.container.add(new Phaser.Group(view.game, undefined, "targetting"));
|
this.layer_targetting = builder.container("targetting");
|
||||||
|
|
||||||
this.range_hint.setLayer(this.layer_hints);
|
this.range_hint.setLayer(this.layer_hints);
|
||||||
this.addShipSprites();
|
this.addShipSprites();
|
||||||
view.battle.drones.list().forEach(drone => this.addDrone(drone, false));
|
view.battle.drones.list().forEach(drone => this.addDrone(drone, false));
|
||||||
|
|
||||||
this.container.onDestroy.add(() => this.destroy());
|
|
||||||
|
|
||||||
view.log_processor.register(diff => this.checkDroneDeployed(diff));
|
view.log_processor.register(diff => this.checkDroneDeployed(diff));
|
||||||
view.log_processor.register(diff => this.checkDroneRecalled(diff));
|
view.log_processor.register(diff => this.checkDroneRecalled(diff));
|
||||||
view.log_processor.watchForShipChange(ship => {
|
view.log_processor.watchForShipChange(ship => {
|
||||||
|
@ -83,7 +83,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Move to a specific layer
|
* Move to a specific layer
|
||||||
*/
|
*/
|
||||||
moveToLayer(layer: Phaser.Group): void {
|
moveToLayer(layer: UIContainer): void {
|
||||||
layer.add(this.container);
|
layer.add(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,35 +93,31 @@ module TK.SpaceTac.UI {
|
||||||
setupMouseCapture() {
|
setupMouseCapture() {
|
||||||
let view = this.view;
|
let view = this.view;
|
||||||
|
|
||||||
let info = view.getImageInfo("battle-arena-background");
|
let background = new UIBuilder(view, this.container).image("battle-arena-background");
|
||||||
var background = new Phaser.Button(view.game, 0, 0, info.key, undefined, undefined, info.frame, info.frame);
|
background.setName("mouse-capture");
|
||||||
background.name = "mouse-capture";
|
background.setScale(this.boundaries.width / background.width, this.boundaries.height / background.height)
|
||||||
background.scale.set(this.boundaries.width / background.width, this.boundaries.height / background.height);
|
|
||||||
this.mouse_capture = background;
|
|
||||||
|
|
||||||
// Capture clicks on background
|
// Capture clicks on background
|
||||||
background.onInputUp.add(() => {
|
background.setInteractive();
|
||||||
|
background.on("pointerup", () => {
|
||||||
this.callbacks_click.forEach(callback => callback());
|
this.callbacks_click.forEach(callback => callback());
|
||||||
});
|
});
|
||||||
background.onInputOut.add(() => {
|
background.on("pointerout", () => {
|
||||||
this.callbacks_hover.forEach(callback => callback(null, null));
|
this.callbacks_hover.forEach(callback => callback(null, null));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch mouse move to capture hovering over background
|
// Watch mouse move to capture hovering over background
|
||||||
this.input_callback = this.view.input.addMoveCallback((pointer: Phaser.Pointer) => {
|
view.input.on("pointermove", (pointer: Phaser.Input.Pointer) => {
|
||||||
if (this.view.dialogs_opened.length > 0 || this.view.character_sheet.isOpened() || this.view.layer_overlay.length > 0) {
|
if (this.view.dialogs_opened.length > 0 || this.view.character_sheet.isOpened() || this.view.layer_overlay.length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let point = new Phaser.Point();
|
let location = new ArenaLocation(pointer.x, pointer.y);
|
||||||
if (view.input.hitTest(background, pointer, point)) {
|
let ship = this.getShip(location);
|
||||||
let location = new ArenaLocation(point.x * background.scale.x, point.y * background.scale.y);
|
this.callbacks_hover.forEach(callback => callback(location, ship));
|
||||||
let ship = this.getShip(location);
|
|
||||||
this.callbacks_hover.forEach(callback => callback(location, ship));
|
|
||||||
}
|
|
||||||
}, null);
|
}, null);
|
||||||
|
|
||||||
this.container.add(this.mouse_capture);
|
this.mouse_capture = background;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -136,16 +132,6 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Call when the arena is destroyed to properly remove input handlers
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
if (this.input_callback) {
|
|
||||||
this.view.input.deleteMoveCallback(this.input_callback);
|
|
||||||
this.input_callback = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the sprites for all ships
|
* Add the sprites for all ships
|
||||||
*/
|
*/
|
||||||
|
@ -244,15 +230,16 @@ module TK.SpaceTac.UI {
|
||||||
this.drone_sprites.push(sprite);
|
this.drone_sprites.push(sprite);
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
sprite.position.set(owner.arena_x, owner.arena_y);
|
sprite.radius.setAlpha(0);
|
||||||
sprite.sprite.rotation = owner.arena_angle;
|
sprite.setPosition(owner.arena_x, owner.arena_y);
|
||||||
let move_duration = Animations.moveInSpace(sprite, drone.x, drone.y, angle, sprite.sprite);
|
sprite.sprite.setRotation(owner.arena_angle);
|
||||||
this.view.tweens.create(sprite.radius).from({ alpha: 0 }, 500, Phaser.Easing.Cubic.In, true, move_duration);
|
let move_duration = this.view.animations.moveInSpace(sprite, drone.x, drone.y, angle, sprite.sprite);
|
||||||
|
this.view.animations.addAnimation(sprite.radius, { alpha: 1 }, 500, "Cubic.easeIn", move_duration);
|
||||||
|
|
||||||
return move_duration + 500;
|
return move_duration + 500;
|
||||||
} else {
|
} else {
|
||||||
sprite.position.set(drone.x, drone.y);
|
sprite.setPosition(drone.x, drone.y);
|
||||||
sprite.sprite.rotation = angle;
|
sprite.setRotation(angle);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Drone sprite in the arena
|
* Drone sprite in the arena
|
||||||
*/
|
*/
|
||||||
export class ArenaDrone extends Phaser.Group {
|
export class ArenaDrone extends UIContainer {
|
||||||
// Link to view
|
// Link to view
|
||||||
view: BattleView
|
view: BattleView
|
||||||
|
|
||||||
|
@ -10,43 +10,36 @@ module TK.SpaceTac.UI {
|
||||||
drone: Drone
|
drone: Drone
|
||||||
|
|
||||||
// Sprite
|
// Sprite
|
||||||
sprite: Phaser.Button
|
sprite: UIButton
|
||||||
|
|
||||||
// Radius
|
// Radius
|
||||||
radius: Phaser.Graphics
|
radius: UIGraphics
|
||||||
|
|
||||||
// Activation effect
|
// Activation effect
|
||||||
activation: Phaser.Graphics
|
activation: UIGraphics
|
||||||
|
|
||||||
constructor(battleview: BattleView, drone: Drone) {
|
constructor(battleview: BattleView, drone: Drone) {
|
||||||
super(battleview.game);
|
super(battleview);
|
||||||
|
|
||||||
this.view = battleview;
|
this.view = battleview;
|
||||||
this.drone = drone;
|
this.drone = drone;
|
||||||
|
|
||||||
this.radius = new Phaser.Graphics(this.game, 0, 0);
|
let builder = new UIBuilder(battleview, this);
|
||||||
|
|
||||||
|
this.radius = builder.graphics("radius");
|
||||||
|
this.radius.fillStyle(0xe9f2f9, 0.1);
|
||||||
|
this.radius.fillCircle(0, 0, drone.radius);
|
||||||
this.radius.lineStyle(2, 0xe9f2f9, 0.5);
|
this.radius.lineStyle(2, 0xe9f2f9, 0.5);
|
||||||
this.radius.beginFill(0xe9f2f9, 0.1);
|
this.radius.strokeCircle(0, 0, drone.radius);
|
||||||
this.radius.drawCircle(0, 0, drone.radius * 2);
|
|
||||||
this.radius.endFill();
|
|
||||||
this.add(this.radius);
|
|
||||||
|
|
||||||
this.activation = new Phaser.Graphics(this.game, 0, 0);
|
this.activation = builder.graphics("activation", 0, 0, false);
|
||||||
|
this.activation.fillStyle(0xe9f2f9, 0.0);
|
||||||
|
this.activation.fillCircle(0, 0, drone.radius);
|
||||||
this.activation.lineStyle(2, 0xe9f2f9, 0.7);
|
this.activation.lineStyle(2, 0xe9f2f9, 0.7);
|
||||||
this.activation.beginFill(0xe9f2f9, 0.0);
|
this.activation.strokeCircle(0, 0, drone.radius);
|
||||||
this.activation.drawCircle(0, 0, drone.radius * 2);
|
|
||||||
this.activation.endFill();
|
|
||||||
this.activation.visible = false;
|
|
||||||
this.add(this.activation);
|
|
||||||
|
|
||||||
this.sprite = this.view.newButton(`action-${drone.code}`);
|
this.sprite = builder.button(`action-${drone.code}`, 0, 0, undefined, () => this.drone.getDescription(), undefined, { center: true });
|
||||||
this.sprite.anchor.set(0.5, 0.5);
|
this.sprite.setScale(0.1, 0.1);
|
||||||
this.sprite.scale.set(0.1, 0.1);
|
|
||||||
this.add(this.sprite);
|
|
||||||
|
|
||||||
this.view.tooltip.bindDynamicText(this.sprite, () => {
|
|
||||||
return this.drone.getDescription();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,11 +48,9 @@ module TK.SpaceTac.UI {
|
||||||
* Return the animation duration
|
* Return the animation duration
|
||||||
*/
|
*/
|
||||||
setApplied(): number {
|
setApplied(): number {
|
||||||
this.activation.scale.set(0.001, 0.001);
|
this.activation.setScale(0.001, 0.001);
|
||||||
this.activation.visible = true;
|
this.activation.visible = true;
|
||||||
let tween = this.game.tweens.create(this.activation.scale).to({ x: 1, y: 1 }, 500);
|
let tween = this.view.animations.addAnimation(this.activation, { scaleX: 1, scaleY: 1 }, 500).then(() => this.activation.setVisible(false));
|
||||||
tween.onComplete.addOnce(() => this.activation.visible = false);
|
|
||||||
tween.start();
|
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,14 +60,8 @@ module TK.SpaceTac.UI {
|
||||||
* Return the animation duration
|
* Return the animation duration
|
||||||
*/
|
*/
|
||||||
setDestroyed(): number {
|
setDestroyed(): number {
|
||||||
this.game.tweens.create(this).to({ alpha: 0.3 }, 300).delay(200).start();
|
this.view.animations.addAnimation<UIContainer>(this, { alpha: 0.3 }, 300, undefined, 200);
|
||||||
|
this.view.animations.addAnimation(this.radius, { scaleX: 0, scaleY: 0 }, 500).then(() => this.destroy());
|
||||||
let tween = this.game.tweens.create(this.radius.scale).to({ x: 0, y: 0 }, 500);
|
|
||||||
tween.onComplete.addOnce(() => {
|
|
||||||
this.destroy();
|
|
||||||
});
|
|
||||||
tween.start();
|
|
||||||
|
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +69,7 @@ module TK.SpaceTac.UI {
|
||||||
* Set the tactical mode display
|
* Set the tactical mode display
|
||||||
*/
|
*/
|
||||||
setTacticalMode(active: boolean) {
|
setTacticalMode(active: boolean) {
|
||||||
this.sprite.scale.set(active ? 0.2 : 0.1);
|
this.sprite.setScale(active ? 0.2 : 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,12 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let ship = nn(testgame.view.battle.playing_ship);
|
let ship = nn(testgame.view.battle.playing_ship);
|
||||||
let sprite = nn(testgame.view.arena.findShipSprite(ship));
|
let sprite = nn(testgame.view.arena.findShipSprite(ship));
|
||||||
|
|
||||||
check.equals(sprite.effects_messages.children.length, 0);
|
check.equals(sprite.effects_messages.list.length, 0);
|
||||||
|
|
||||||
sprite.displayAttributeChanged(new ShipAttributeDiff(ship, "power_capacity", { cumulative: -4 }, {}));
|
sprite.displayAttributeChanged(new ShipAttributeDiff(ship, "power_capacity", { cumulative: -4 }, {}));
|
||||||
|
|
||||||
check.equals(sprite.effects_messages.children.length, 1);
|
check.equals(sprite.effects_messages.list.length, 1);
|
||||||
let t1 = <Phaser.Text>sprite.effects_messages.getChildAt(0);
|
check.equals(collectTexts(sprite.effects_messages), ["power capacity -4"]);
|
||||||
check.equals(t1.text, "power capacity -4");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("adds sticky effects display", check => {
|
test.case("adds sticky effects display", check => {
|
||||||
|
@ -22,25 +21,25 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let ship = nn(battle.playing_ship);
|
let ship = nn(battle.playing_ship);
|
||||||
let sprite = nn(testgame.view.arena.findShipSprite(ship));
|
let sprite = nn(testgame.view.arena.findShipSprite(ship));
|
||||||
|
|
||||||
check.equals(sprite.active_effects_display.children.length, 0);
|
check.equals(sprite.active_effects_display.list.length, 0);
|
||||||
|
|
||||||
let effect1 = new StickyEffect(new BaseEffect("test"));
|
let effect1 = new StickyEffect(new BaseEffect("test"));
|
||||||
battle.applyDiffs([new ShipEffectAddedDiff(ship, effect1)]);
|
battle.applyDiffs([new ShipEffectAddedDiff(ship, effect1)]);
|
||||||
testgame.view.log_processor.processPending();
|
testgame.view.log_processor.processPending();
|
||||||
check.equals(sprite.active_effects_display.children.length, 1);
|
check.equals(sprite.active_effects_display.list.length, 1);
|
||||||
|
|
||||||
let effect2 = new StickyEffect(new BaseEffect("test"));
|
let effect2 = new StickyEffect(new BaseEffect("test"));
|
||||||
battle.applyDiffs([new ShipEffectAddedDiff(ship, effect2)]);
|
battle.applyDiffs([new ShipEffectAddedDiff(ship, effect2)]);
|
||||||
testgame.view.log_processor.processPending();
|
testgame.view.log_processor.processPending();
|
||||||
check.equals(sprite.active_effects_display.children.length, 2);
|
check.equals(sprite.active_effects_display.list.length, 2);
|
||||||
|
|
||||||
battle.applyDiffs([new ShipEffectRemovedDiff(ship, effect1)]);
|
battle.applyDiffs([new ShipEffectRemovedDiff(ship, effect1)]);
|
||||||
testgame.view.log_processor.processPending();
|
testgame.view.log_processor.processPending();
|
||||||
check.equals(sprite.active_effects_display.children.length, 1);
|
check.equals(sprite.active_effects_display.list.length, 1);
|
||||||
|
|
||||||
battle.applyDiffs([new ShipEffectRemovedDiff(ship, effect2)]);
|
battle.applyDiffs([new ShipEffectRemovedDiff(ship, effect2)]);
|
||||||
testgame.view.log_processor.processPending();
|
testgame.view.log_processor.processPending();
|
||||||
check.equals(sprite.active_effects_display.children.length, 0);
|
check.equals(sprite.active_effects_display.list.length, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Ship sprite in the arena, with corresponding HUD
|
* Ship sprite in the arena, with corresponding HUD
|
||||||
*/
|
*/
|
||||||
export class ArenaShip extends Phaser.Group {
|
export class ArenaShip extends UIContainer {
|
||||||
// Link to the view
|
// Link to the view
|
||||||
arena: Arena
|
arena: Arena
|
||||||
battleview: BattleView
|
battleview: BattleView
|
||||||
|
@ -14,21 +14,21 @@ module TK.SpaceTac.UI {
|
||||||
enemy: boolean
|
enemy: boolean
|
||||||
|
|
||||||
// Ship sprite
|
// Ship sprite
|
||||||
sprite: Phaser.Image
|
sprite: UIImage
|
||||||
|
|
||||||
// Stasis effect
|
// Stasis effect
|
||||||
stasis: Phaser.Image
|
stasis: UIImage
|
||||||
|
|
||||||
// HSP display
|
// HSP display
|
||||||
hsp: Phaser.Image
|
hsp: UIContainer
|
||||||
power_text: Phaser.Text
|
power_text: UIText
|
||||||
life_hull: UIGroup
|
life_hull: UIContainer
|
||||||
life_shield: UIGroup
|
life_shield: UIContainer
|
||||||
life_evasion: UIGroup
|
life_evasion: UIContainer
|
||||||
toggle_hsp: Toggle
|
toggle_hsp: Toggle
|
||||||
|
|
||||||
// Play order
|
// Play order
|
||||||
play_order: Phaser.Text
|
play_order: UIText
|
||||||
toggle_play_order: Toggle
|
toggle_play_order: Toggle
|
||||||
|
|
||||||
// Frames to indicate the owner, if the ship is hovered, and if it is hovered
|
// Frames to indicate the owner, if the ship is hovered, and if it is hovered
|
||||||
|
@ -36,24 +36,24 @@ module TK.SpaceTac.UI {
|
||||||
frame_hover: UIImage
|
frame_hover: UIImage
|
||||||
|
|
||||||
// Effects display
|
// Effects display
|
||||||
active_effects_display: Phaser.Group
|
active_effects_display: UIContainer
|
||||||
effects_radius: Phaser.Graphics
|
effects_radius: UIGraphics
|
||||||
effects_messages: Phaser.Group
|
effects_messages: UIContainer
|
||||||
effects_messages_toggle: Toggle
|
effects_messages_toggle: Toggle
|
||||||
|
|
||||||
// Create a ship sprite usable in the Arena
|
// Create a ship sprite usable in the Arena
|
||||||
constructor(parent: Arena, ship: Ship) {
|
constructor(parent: Arena, ship: Ship) {
|
||||||
super(parent.game);
|
super(parent.view);
|
||||||
this.arena = parent;
|
this.arena = parent;
|
||||||
this.battleview = parent.view;
|
this.battleview = parent.view;
|
||||||
let builder = new UIBuilder(parent.view).in(this);
|
|
||||||
|
let builder = new UIBuilder(this.battleview).in(this);
|
||||||
|
|
||||||
this.ship = ship;
|
this.ship = ship;
|
||||||
this.enemy = !this.battleview.player.is(this.ship.fleet.player);
|
this.enemy = !this.battleview.player.is(this.ship.fleet.player);
|
||||||
|
|
||||||
// Add effects radius
|
// Add effects radius
|
||||||
this.effects_radius = new Phaser.Graphics(this.game);
|
this.effects_radius = builder.graphics("effect-radius");
|
||||||
this.add(this.effects_radius);
|
|
||||||
|
|
||||||
// Add frame indicating which side this ship is on
|
// Add frame indicating which side this ship is on
|
||||||
this.frame_owner = builder.image(this.enemy ? "battle-hud-ship-enemy" : "battle-hud-ship-own", 0, 0, true);
|
this.frame_owner = builder.image(this.enemy ? "battle-hud-ship-enemy" : "battle-hud-ship-own", 0, 0, true);
|
||||||
|
@ -71,12 +71,13 @@ module TK.SpaceTac.UI {
|
||||||
this.stasis.visible = !ship.alive;
|
this.stasis.visible = !ship.alive;
|
||||||
|
|
||||||
// HSP display
|
// HSP display
|
||||||
this.hsp = builder.image("battle-hud-hsp-background", 0, 34, true);
|
this.hsp = builder.container("hsp", 0, 34);
|
||||||
|
builder.in(this.hsp).image("battle-hud-hsp-background", 0, 0, true);
|
||||||
this.power_text = builder.in(this.hsp).text(`${ship.getValue("power")}`, -42, 2,
|
this.power_text = builder.in(this.hsp).text(`${ship.getValue("power")}`, -42, 2,
|
||||||
{ size: 13, color: "#ffdd4b", bold: true, shadow: true, center: true });
|
{ size: 13, color: "#ffdd4b", bold: true, shadow: true, center: true });
|
||||||
this.life_hull = builder.in(this.hsp).group("hull");
|
this.life_hull = builder.in(this.hsp).container("hull");
|
||||||
this.life_shield = builder.in(this.hsp).group("shield");
|
this.life_shield = builder.in(this.hsp).container("shield");
|
||||||
this.life_evasion = builder.in(this.hsp).group("evasion");
|
this.life_evasion = builder.in(this.hsp).container("evasion");
|
||||||
this.toggle_hsp = this.battleview.animations.newVisibilityToggle(this.hsp, 200, false);
|
this.toggle_hsp = this.battleview.animations.newVisibilityToggle(this.hsp, 200, false);
|
||||||
|
|
||||||
// Play order display
|
// Play order display
|
||||||
|
@ -85,11 +86,8 @@ module TK.SpaceTac.UI {
|
||||||
this.toggle_play_order = this.battleview.animations.newVisibilityToggle(play_order, 200, false);
|
this.toggle_play_order = this.battleview.animations.newVisibilityToggle(play_order, 200, false);
|
||||||
|
|
||||||
// Effects display
|
// Effects display
|
||||||
this.active_effects_display = new Phaser.Group(this.game);
|
this.active_effects_display = builder.container("active-effects", 0, -44);
|
||||||
this.active_effects_display.position.set(0, -44);
|
this.effects_messages = builder.container("effects-messages");
|
||||||
this.add(this.active_effects_display);
|
|
||||||
this.effects_messages = new Phaser.Group(this.game);
|
|
||||||
this.add(this.effects_messages);
|
|
||||||
this.effects_messages_toggle = this.battleview.animations.newVisibilityToggle(this.effects_messages, 500, false);
|
this.effects_messages_toggle = this.battleview.animations.newVisibilityToggle(this.effects_messages, 500, false);
|
||||||
|
|
||||||
this.updatePlayOrder();
|
this.updatePlayOrder();
|
||||||
|
@ -101,10 +99,10 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// Set location
|
// Set location
|
||||||
if (this.battleview.battle.cycle == 1 && this.battleview.battle.play_index == 0 && ship.alive && this.battleview.player.is(ship.fleet.player)) {
|
if (this.battleview.battle.cycle == 1 && this.battleview.battle.play_index == 0 && ship.alive && this.battleview.player.is(ship.fleet.player)) {
|
||||||
this.position.set(ship.arena_x - 500 * Math.cos(ship.arena_angle), ship.arena_y - 500 * Math.sin(ship.arena_angle));
|
this.setPosition(ship.arena_x - 500 * Math.cos(ship.arena_angle), ship.arena_y - 500 * Math.sin(ship.arena_angle));
|
||||||
this.moveTo(ship.arena_x, ship.arena_y, ship.arena_angle);
|
this.moveToArenaLocation(ship.arena_x, ship.arena_y, ship.arena_angle);
|
||||||
} else {
|
} else {
|
||||||
this.moveTo(ship.arena_x, ship.arena_y, ship.arena_angle, false);
|
this.moveToArenaLocation(ship.arena_x, ship.arena_y, ship.arena_angle, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log processing
|
// Log processing
|
||||||
|
@ -243,8 +241,8 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
} else if (diff instanceof ShipMoveDiff) {
|
} else if (diff instanceof ShipMoveDiff) {
|
||||||
let func = async (animate: boolean, timer: Timer) => {
|
let func = async (animate: boolean, timer: Timer) => {
|
||||||
this.moveTo(diff.start.x, diff.start.y, diff.start.angle, false);
|
this.moveToArenaLocation(diff.start.x, diff.start.y, diff.start.angle, false);
|
||||||
let duration = this.moveTo(diff.end.x, diff.end.y, diff.end.angle, animate, !!diff.engine);
|
let duration = this.moveToArenaLocation(diff.end.x, diff.end.y, diff.end.angle, animate, !!diff.engine);
|
||||||
if (duration && animate) {
|
if (duration && animate) {
|
||||||
await timer.sleep(duration);
|
await timer.sleep(duration);
|
||||||
}
|
}
|
||||||
|
@ -325,9 +323,9 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* Return the duration of animation
|
* Return the duration of animation
|
||||||
*/
|
*/
|
||||||
moveTo(x: number, y: number, facing_angle: number, animate = true, engine = true): number {
|
moveToArenaLocation(x: number, y: number, facing_angle: number, animate = true, engine = true): number {
|
||||||
if (animate) {
|
if (animate) {
|
||||||
let animation = engine ? Animations.moveInSpace : Animations.moveTo;
|
let animation = bound(this.arena.view.animations, engine ? "moveInSpace" : "moveTo");
|
||||||
let duration = animation(this, x, y, facing_angle, this.sprite);
|
let duration = animation(this, x, y, facing_angle, this.sprite);
|
||||||
return duration;
|
return duration;
|
||||||
} else {
|
} else {
|
||||||
|
@ -346,11 +344,14 @@ module TK.SpaceTac.UI {
|
||||||
this.effects_messages.removeAll(true);
|
this.effects_messages.removeAll(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = new Phaser.Text(this.game, 0, 20 * this.effects_messages.children.length, message, { font: "14pt SpaceTac", fill: beneficial ? "#afe9c6" : "#e9afaf" });
|
let builder = new UIBuilder(this.arena.view, this.effects_messages);
|
||||||
this.effects_messages.addChild(text);
|
|
||||||
|
let text = builder.text(message, 0, 20 * this.effects_messages.length, {
|
||||||
|
color: beneficial ? "#afe9c6" : "#e9afaf"
|
||||||
|
})
|
||||||
|
|
||||||
let arena = this.battleview.arena.getBoundaries();
|
let arena = this.battleview.arena.getBoundaries();
|
||||||
this.effects_messages.position.set(
|
this.effects_messages.setPosition(
|
||||||
(this.ship.arena_x < 100) ? -35 : ((this.ship.arena_x > arena.width - 100) ? (35 - this.effects_messages.width) : (-this.effects_messages.width * 0.5)),
|
(this.ship.arena_x < 100) ? -35 : ((this.ship.arena_x > arena.width - 100) ? (35 - this.effects_messages.width) : (-this.effects_messages.width * 0.5)),
|
||||||
(this.ship.arena_y < arena.height * 0.9) ? 50 : (-50 - this.effects_messages.height)
|
(this.ship.arena_y < arena.height * 0.9) ? 50 : (-50 - this.effects_messages.height)
|
||||||
);
|
);
|
||||||
|
@ -449,7 +450,6 @@ module TK.SpaceTac.UI {
|
||||||
effects.forEach((effect, index) => {
|
effects.forEach((effect, index) => {
|
||||||
let name = effect.isBeneficial() ? "battle-hud-ship-effect-good" : "battle-hud-ship-effect-bad";
|
let name = effect.isBeneficial() ? "battle-hud-ship-effect-good" : "battle-hud-ship-effect-bad";
|
||||||
let dot = this.battleview.newImage(name, positions[index] - 35, 0);
|
let dot = this.battleview.newImage(name, positions[index] - 35, 0);
|
||||||
dot.anchor.set(0.5, 0.5);
|
|
||||||
this.active_effects_display.add(dot);
|
this.active_effects_display.add(dot);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -463,9 +463,8 @@ module TK.SpaceTac.UI {
|
||||||
this.ship.actions.listToggled().forEach(action => {
|
this.ship.actions.listToggled().forEach(action => {
|
||||||
let color = (action instanceof VigilanceAction) ? 0xf4bf42 : 0xe9f2f9;
|
let color = (action instanceof VigilanceAction) ? 0xf4bf42 : 0xe9f2f9;
|
||||||
this.effects_radius.lineStyle(2, color, 0.5);
|
this.effects_radius.lineStyle(2, color, 0.5);
|
||||||
this.effects_radius.beginFill(color, 0.1);
|
this.effects_radius.fillStyle(color, 0.1);
|
||||||
this.effects_radius.drawCircle(0, 0, action.radius * 2);
|
this.effects_radius.fillCircle(0, 0, action.radius);
|
||||||
this.effects_radius.endFill();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,44 +9,48 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Create and animate splash component, returns when the animation is ended
|
* Create and animate splash component, returns when the animation is ended
|
||||||
*/
|
*/
|
||||||
private async components(builder: UIBuilder): Promise<void> {
|
private async components(builder: UIBuilder, container: UIContainer): Promise<void> {
|
||||||
let base = builder.image("battle-splash-message-off", this.view.getMidWidth(), this.view.getMidHeight(), true);
|
container.setScale(0.8);
|
||||||
|
|
||||||
let message = builder.in(base).image("battle-splash-message-on", 0, 0, true);
|
builder.image("battle-splash-message-off", 0, 0, true);
|
||||||
|
|
||||||
|
let message = builder.image("battle-splash-message-on", 0, 0, true);
|
||||||
message.visible = false;
|
message.visible = false;
|
||||||
|
|
||||||
let player1 = builder.in(base).image("battle-splash-moving-part", 0, -50, true);
|
let player1 = builder.container("player1", 0, -50, false);
|
||||||
player1.visible = false;
|
builder.in(player1, builder => {
|
||||||
|
builder.image("battle-splash-moving-part", 0, 0, true);
|
||||||
|
|
||||||
let player1_name = builder.in(player1).text(this.fleet1.name, -224, 0, { size: 22, bold: true, color: "#154d13" });
|
let player1_name = builder.text(this.fleet1.name, -224, 0, { size: 22, bold: true, color: "#154d13" });
|
||||||
player1_name.angle = -48;
|
player1_name.angle = -48;
|
||||||
|
|
||||||
this.fleet1.ships.forEach((ship, index) => {
|
this.fleet1.ships.forEach((ship, index) => {
|
||||||
let ship_card = builder.in(player1).image("battle-splash-ship-card", -86 + index * 96, -72, true);
|
let ship_card = builder.image("battle-splash-ship-card", -86 + index * 96, -72, true);
|
||||||
let ship_portrait = builder.in(ship_card).image(`ship-${ship.model.code}-portrait`, 0, 0, true);
|
let ship_portrait = builder.in(ship_card).image(`ship-${ship.model.code}-portrait`, 0, 0, true);
|
||||||
ship_portrait.scale.set(0.3);
|
ship_portrait.setScale(0.3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let player2 = builder.in(base).image("battle-splash-moving-part", 0, 50, true);
|
let player2 = builder.container("player2", 0, 50, false);
|
||||||
player2.angle = 180;
|
player2.setAngle(180);
|
||||||
player2.visible = false;
|
builder.in(player2, builder => {
|
||||||
|
builder.image("battle-splash-moving-part", 0, 0, true);
|
||||||
|
|
||||||
let player2_name = builder.in(player2).text(this.fleet2.name, -224, 0, { size: 22, bold: true, color: "#651713" });
|
let player2_name = builder.text(this.fleet2.name, -224, 0, { size: 22, bold: true, color: "#651713" });
|
||||||
player2_name.angle = -228;
|
player2_name.angle = -228;
|
||||||
|
|
||||||
this.fleet2.ships.forEach((ship, index) => {
|
this.fleet2.ships.forEach((ship, index) => {
|
||||||
let ship_card = builder.in(player2).image("battle-splash-ship-card", -86 + index * 96, -72, true);
|
let ship_card = builder.image("battle-splash-ship-card", -86 + index * 96, -72, true);
|
||||||
let ship_portrait = builder.in(ship_card).image(`ship-${ship.model.code}-portrait`, 0, 0, true);
|
let ship_portrait = builder.in(ship_card).image(`ship-${ship.model.code}-portrait`, 0, 0, true);
|
||||||
ship_portrait.angle = 180;
|
ship_portrait.setAngle(180);
|
||||||
ship_portrait.scale.set(0.3);
|
ship_portrait.setScale(0.3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Animations
|
||||||
let anims = this.view.animations;
|
let anims = this.view.animations;
|
||||||
|
|
||||||
base.visible = true;
|
await anims.addAnimation(container, { scaleX: 1, scaleY: 1 }, 300, 'Bounce.easeOut');
|
||||||
base.scale.set(0.8);
|
|
||||||
|
|
||||||
await anims.addAnimation(base.scale, { x: 1, y: 1 }, 300, Phaser.Easing.Bounce.Out);
|
|
||||||
|
|
||||||
this.view.timer.schedule(600, () => {
|
this.view.timer.schedule(600, () => {
|
||||||
message.visible = true;
|
message.visible = true;
|
||||||
|
@ -61,38 +65,38 @@ module TK.SpaceTac.UI {
|
||||||
player1.visible = true;
|
player1.visible = true;
|
||||||
player2.x = 2000;
|
player2.x = 2000;
|
||||||
player2.visible = true;
|
player2.visible = true;
|
||||||
anims.addAnimation(player2, { x: 147 }, 600, Phaser.Easing.Bounce.Out, 400);
|
anims.addAnimation(player2, { x: 147 }, 600, 'Bounce.easeOut', 400);
|
||||||
await anims.addAnimation(player1, { x: -150 }, 600, Phaser.Easing.Bounce.Out, 400);
|
await anims.addAnimation(player1, { x: -150 }, 600, 'Bounce.easeOut', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an overlay, returns when it is clicked
|
* Create an overlay, returns when it is clicked
|
||||||
*/
|
*/
|
||||||
overlay(builder: UIBuilder): Promise<void> {
|
overlay(builder: UIBuilder): Promise<UIButton> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let overlay = builder.button("translucent-black", 0, 0, resolve);
|
let overlay = builder.button("battle-overlay", this.view.getMidWidth(), this.view.getMidHeight(), () => resolve(overlay), undefined, undefined, { center: true });
|
||||||
overlay.input.useHandCursor = true;
|
overlay.setScale(this.view.getWidth() / overlay.width, this.view.getHeight() / overlay.height);
|
||||||
overlay.scale.set(this.view.getWidth() / overlay.width, this.view.getHeight() / overlay.height);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the animation
|
* Start the animation
|
||||||
*/
|
*/
|
||||||
start(parent?: UIGroup): Promise<void> {
|
start(parent?: UIContainer): Promise<void> {
|
||||||
let builder = new UIBuilder(this.view, parent);
|
let builder = new UIBuilder(this.view, parent);
|
||||||
let group = builder.group("splash");
|
let overlay = this.overlay(builder);
|
||||||
|
|
||||||
let overlay = this.overlay(builder.in(group));
|
let container = builder.container("splash", this.view.getMidWidth(), this.view.getMidHeight());
|
||||||
let components = this.components(builder.in(group));
|
let components = this.components(builder.in(container), container);
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
overlay.then(() => {
|
overlay.then(overlayobj => {
|
||||||
group.visible = false;
|
container.visible = false;
|
||||||
|
overlayobj.destroy();
|
||||||
}),
|
}),
|
||||||
components
|
components
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
group.destroy(true);
|
container.destroy();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,17 +27,17 @@ module TK.SpaceTac.UI {
|
||||||
multi!: MultiBattle
|
multi!: MultiBattle
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
layer_background!: Phaser.Group
|
layer_background!: UIContainer
|
||||||
layer_arena!: Phaser.Group
|
layer_arena!: UIContainer
|
||||||
layer_borders!: Phaser.Group
|
layer_borders!: UIContainer
|
||||||
layer_overlay!: Phaser.Group
|
layer_overlay!: UIContainer
|
||||||
layer_sheets!: Phaser.Group
|
layer_sheets!: UIContainer
|
||||||
|
|
||||||
// Battleground container
|
// Battleground container
|
||||||
arena!: Arena
|
arena!: Arena
|
||||||
|
|
||||||
// Background image
|
// Background image
|
||||||
background!: Phaser.Image | null
|
background!: UIImage | null
|
||||||
|
|
||||||
// Targetting mode (null if we're not in this mode)
|
// Targetting mode (null if we're not in this mode)
|
||||||
targetting!: Targetting
|
targetting!: Targetting
|
||||||
|
@ -70,12 +70,12 @@ module TK.SpaceTac.UI {
|
||||||
splash = true
|
splash = true
|
||||||
|
|
||||||
// Init the view, binding it to a specific battle
|
// Init the view, binding it to a specific battle
|
||||||
init(player: Player, battle: Battle) {
|
init(data: { player: Player, battle: Battle }) {
|
||||||
super.init();
|
super.init(data);
|
||||||
|
|
||||||
this.player = player;
|
this.player = data.player;
|
||||||
this.actual_battle = battle;
|
this.actual_battle = data.battle;
|
||||||
this.battle = duplicate(battle, <any>TK.SpaceTac);
|
this.battle = duplicate(data.battle, <any>TK.SpaceTac);
|
||||||
this.ship_hovered = null;
|
this.ship_hovered = null;
|
||||||
this.background = null;
|
this.background = null;
|
||||||
this.multi = new MultiBattle();
|
this.multi = new MultiBattle();
|
||||||
|
@ -113,20 +113,20 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// Add UI elements
|
// Add UI elements
|
||||||
this.action_bar = new ActionBar(this);
|
this.action_bar = new ActionBar(this);
|
||||||
this.action_bar.position.set(0, this.getHeight() - 132);
|
this.action_bar.setPosition(0, this.getHeight() - 132);
|
||||||
this.ship_list = new ShipList(this, this.battle, this.player, this.toggle_tactical_mode, this,
|
this.ship_list = new ShipList(this, this.battle, this.player, this.toggle_tactical_mode, this,
|
||||||
this.layer_borders, this.getWidth() - 112, 0);
|
this.layer_borders, this.getWidth() - 112, 0);
|
||||||
this.ship_list.bindToLog(this.log_processor);
|
this.ship_list.bindToLog(this.log_processor);
|
||||||
this.ship_tooltip = new ShipTooltip(this);
|
this.ship_tooltip = new ShipTooltip(this);
|
||||||
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.DISPLAY);
|
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.DISPLAY);
|
||||||
this.layer_sheets.add(this.character_sheet);
|
this.character_sheet.moveToLayer(this.layer_sheets);
|
||||||
|
|
||||||
// Targetting info
|
// Targetting info
|
||||||
this.targetting = new Targetting(this, this.action_bar, this.toggle_tactical_mode, this.arena.range_hint);
|
this.targetting = new Targetting(this, this.action_bar, this.toggle_tactical_mode, this.arena.range_hint);
|
||||||
this.targetting.moveToLayer(this.arena.layer_targetting);
|
this.targetting.moveToLayer(this.arena.layer_targetting);
|
||||||
|
|
||||||
// BGM
|
// BGM
|
||||||
this.gameui.audio.startMusic("mechanolith", 0.2);
|
this.audio.startMusic("mechanolith", 0.2);
|
||||||
|
|
||||||
// Key mapping
|
// Key mapping
|
||||||
this.inputs.bind("t", "Show tactical view", () => this.toggle_tactical_mode.manipulate("keyboard")(3000));
|
this.inputs.bind("t", "Show tactical view", () => this.toggle_tactical_mode.manipulate("keyboard")(3000));
|
||||||
|
@ -313,7 +313,7 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled != this.interacting) {
|
if (enabled != this.interacting) {
|
||||||
this.action_bar.setInteractive(enabled);
|
this.action_bar.setInteractivity(enabled);
|
||||||
this.exitTargettingMode();
|
this.exitTargettingMode();
|
||||||
this.interacting = enabled;
|
this.interacting = enabled;
|
||||||
|
|
||||||
|
@ -364,7 +364,7 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
exitBattle() {
|
exitBattle() {
|
||||||
this.session.exitBattle();
|
this.session.exitBattle();
|
||||||
this.game.state.start('router');
|
this.backToRouter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -372,7 +372,7 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
revertBattle() {
|
revertBattle() {
|
||||||
this.session.revertBattle();
|
this.session.revertBattle();
|
||||||
this.game.state.start('router');
|
this.backToRouter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ module TK.SpaceTac.UI {
|
||||||
start() {
|
start() {
|
||||||
if (!this.view.gameui.headless) {
|
if (!this.view.gameui.headless) {
|
||||||
this.log.play(async diff => {
|
this.log.play(async diff => {
|
||||||
while (this.view.game.paused) {
|
while (this.view.isPaused()) {
|
||||||
await this.view.timer.sleep(500);
|
await this.view.timer.sleep(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,11 @@ module TK.SpaceTac.UI {
|
||||||
* Shortcut to add a single action button at the bottom of dialog
|
* Shortcut to add a single action button at the bottom of dialog
|
||||||
*/
|
*/
|
||||||
addActionButton(x: number, text: string, tooltip: string, action: Function) {
|
addActionButton(x: number, text: string, tooltip: string, action: Function) {
|
||||||
let button = this.addButton(x, 885, action, "common-dialog-textbutton", tooltip);
|
let button = this.content.button("common-dialog-textbutton", x, 885, action, tooltip, undefined, {
|
||||||
button.addChild(this.addText(0, 0, text, "#d9e0e5"));
|
center: true,
|
||||||
|
text: text,
|
||||||
|
text_style: { color: "#d9e0e5" }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,16 +40,16 @@ module TK.SpaceTac.UI {
|
||||||
let outcome = this.outcome;
|
let outcome = this.outcome;
|
||||||
let victory = outcome.winner && this.player.is(outcome.winner.player);
|
let victory = outcome.winner && this.player.is(outcome.winner.player);
|
||||||
|
|
||||||
this.clearContent();
|
this.content.clear();
|
||||||
|
|
||||||
this.addImage(747, 180, victory ? "battle-outcome-title-victory" : "battle-outcome-title-defeat");
|
this.content.image(victory ? "battle-outcome-title-victory" : "battle-outcome-title-defeat", 747, 180, true);
|
||||||
|
|
||||||
this.addText(815, 320, "You", "#ffffff", 20);
|
this.content.text("You", 815, 320, { color: "#ffffff", size: 20 });
|
||||||
this.addText(1015, 320, "Enemy", "#ffffff", 20);
|
this.content.text("Enemy", 1015, 320, { color: "#ffffff", size: 20 });
|
||||||
this.stats.getImportant(10).forEach((stat, index) => {
|
this.stats.getImportant(10).forEach((stat, index) => {
|
||||||
this.addText(530, 364 + 40 * index, stat.name, "#ffffff", 20);
|
this.content.text(stat.name, 530, 364 + 40 * index, { color: "#ffffff", size: 20 });
|
||||||
this.addText(815, 364 + 40 * index, stat.attacker.toString(), "#8ba883", 20, true);
|
this.content.text(stat.attacker.toString(), 815, 364 + 40 * index, { color: "#8ba883", size: 20, bold: true });
|
||||||
this.addText(1015, 364 + 40 * index, stat.defender.toString(), "#cd6767", 20, true);
|
this.content.text(stat.defender.toString(), 1015, 364 + 40 * index, { color: "#cd6767", size: 20, bold: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.battleview.session.hasUniverse()) {
|
if (!this.battleview.session.hasUniverse()) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ module TK.SpaceTac.UI {
|
||||||
private view: BaseView
|
private view: BaseView
|
||||||
|
|
||||||
// Visual information
|
// Visual information
|
||||||
private info: Phaser.Graphics
|
private info: UIGraphics
|
||||||
|
|
||||||
// Size of the arena
|
// Size of the arena
|
||||||
private width: number
|
private width: number
|
||||||
|
@ -20,15 +20,14 @@ module TK.SpaceTac.UI {
|
||||||
this.width = boundaries.width;
|
this.width = boundaries.width;
|
||||||
this.height = boundaries.height;
|
this.height = boundaries.height;
|
||||||
|
|
||||||
this.info = new Phaser.Graphics(arena.game, 0, 0);
|
this.info = new UIGraphics(arena.view, "info", false);
|
||||||
this.info.visible = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the layer in which the info will be displayed
|
* Set the layer in which the info will be displayed
|
||||||
*/
|
*/
|
||||||
setLayer(layer: Phaser.Group, x = 0, y = 0) {
|
setLayer(layer: UIContainer, x = 0, y = 0) {
|
||||||
this.info.position.set(x, y);
|
this.info.setPosition(x, y);
|
||||||
layer.add(this.info);
|
layer.add(this.info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,23 +48,23 @@ module TK.SpaceTac.UI {
|
||||||
this.info.clear();
|
this.info.clear();
|
||||||
|
|
||||||
if (radius) {
|
if (radius) {
|
||||||
this.info.beginFill(nocolor);
|
this.info.fillStyle(nocolor);
|
||||||
this.info.drawRect(0, 0, this.width, this.height);
|
this.info.fillRect(0, 0, this.width, this.height);
|
||||||
|
|
||||||
this.info.beginFill(yescolor);
|
this.info.fillStyle(yescolor);
|
||||||
this.info.drawCircle(ship.arena_x, ship.arena_y, radius * 2);
|
this.info.fillCircle(ship.arena_x, ship.arena_y, radius);
|
||||||
|
|
||||||
if (action instanceof MoveAction) {
|
if (action instanceof MoveAction) {
|
||||||
let exclusions = action.getExclusionAreas(ship);
|
let exclusions = action.getExclusionAreas(ship);
|
||||||
|
|
||||||
this.info.beginFill(nocolor);
|
this.info.fillStyle(nocolor);
|
||||||
this.info.drawRect(0, 0, this.width, exclusions.hard_border);
|
this.info.fillRect(0, 0, this.width, exclusions.hard_border);
|
||||||
this.info.drawRect(0, this.height - exclusions.hard_border, this.width, exclusions.hard_border);
|
this.info.fillRect(0, this.height - exclusions.hard_border, this.width, exclusions.hard_border);
|
||||||
this.info.drawRect(0, exclusions.hard_border, exclusions.hard_border, this.height - exclusions.hard_border * 2);
|
this.info.fillRect(0, exclusions.hard_border, exclusions.hard_border, this.height - exclusions.hard_border * 2);
|
||||||
this.info.drawRect(this.width - exclusions.hard_border, exclusions.hard_border, exclusions.hard_border, this.height - exclusions.hard_border * 2);
|
this.info.fillRect(this.width - exclusions.hard_border, exclusions.hard_border, exclusions.hard_border, this.height - exclusions.hard_border * 2);
|
||||||
|
|
||||||
exclusions.obstacles.forEach(obstacle => {
|
exclusions.obstacles.forEach(obstacle => {
|
||||||
this.info.drawCircle(obstacle.x, obstacle.y, exclusions.effective_obstacle * 2);
|
this.info.fillCircle(obstacle.x, obstacle.y, exclusions.effective_obstacle);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,22 +46,22 @@ module TK.SpaceTac.UI.Specs {
|
||||||
list.setShipsFromBattle(battle, false);
|
list.setShipsFromBattle(battle, false);
|
||||||
check.in("ship added in the other fleet", check => {
|
check.in("ship added in the other fleet", check => {
|
||||||
check.equals(list.items.length, 2, "item count");
|
check.equals(list.items.length, 2, "item count");
|
||||||
check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(2, 843), "first ship position");
|
check.equals(nn(list.findItem(battle.play_order[0])).location, { x: 2, y: 843 }, "first ship position");
|
||||||
check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 744), "second ship position");
|
check.equals(nn(list.findItem(battle.play_order[1])).location, { x: 2, y: 744 }, "second ship position");
|
||||||
});
|
});
|
||||||
|
|
||||||
battle.setPlayingShip(battle.play_order[0]);
|
battle.setPlayingShip(battle.play_order[0]);
|
||||||
list.refresh(false);
|
list.refresh(false);
|
||||||
check.in("started", check => {
|
check.in("started", check => {
|
||||||
check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(-14, 962), "first ship position");
|
check.equals(nn(list.findItem(battle.play_order[0])).location, { x: -14, y: 962 }, "first ship position");
|
||||||
check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 843), "second ship position");
|
check.equals(nn(list.findItem(battle.play_order[1])).location, { x: 2, y: 843 }, "second ship position");
|
||||||
});
|
});
|
||||||
|
|
||||||
battle.advanceToNextShip();
|
battle.advanceToNextShip();
|
||||||
list.refresh(false);
|
list.refresh(false);
|
||||||
check.in("end turn", check => {
|
check.in("end turn", check => {
|
||||||
check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(2, 843), "first ship position");
|
check.equals(nn(list.findItem(battle.play_order[0])).location, { x: 2, y: 843 }, "first ship position");
|
||||||
check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(-14, 962), "second ship position");
|
check.equals(nn(list.findItem(battle.play_order[1])).location, { x: -14, y: 962 }, "second ship position");
|
||||||
});
|
});
|
||||||
|
|
||||||
ship = battle.fleets[1].addShip();
|
ship = battle.fleets[1].addShip();
|
||||||
|
@ -71,9 +71,9 @@ module TK.SpaceTac.UI.Specs {
|
||||||
list.setShipsFromBattle(battle, false);
|
list.setShipsFromBattle(battle, false);
|
||||||
check.in("third ship added", check => {
|
check.in("third ship added", check => {
|
||||||
check.equals(list.items.length, 3, "item count");
|
check.equals(list.items.length, 3, "item count");
|
||||||
check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(-14, 962), "first ship position");
|
check.equals(nn(list.findItem(battle.play_order[0])).location, { x: -14, y: 962 }, "first ship position");
|
||||||
check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 843), "second ship position");
|
check.equals(nn(list.findItem(battle.play_order[1])).location, { x: 2, y: 843 }, "second ship position");
|
||||||
check.equals(nn(list.findItem(battle.play_order[2])).position, new Phaser.Point(2, 744), "third ship position");
|
check.equals(nn(list.findItem(battle.play_order[2])).location, { x: 2, y: 744 }, "third ship position");
|
||||||
});
|
});
|
||||||
|
|
||||||
let dead = battle.play_order[1];
|
let dead = battle.play_order[1];
|
||||||
|
@ -81,9 +81,9 @@ module TK.SpaceTac.UI.Specs {
|
||||||
list.refresh(false);
|
list.refresh(false);
|
||||||
check.in("ship dead", check => {
|
check.in("ship dead", check => {
|
||||||
check.equals(list.items.length, 3, "item count");
|
check.equals(list.items.length, 3, "item count");
|
||||||
check.equals(nn(list.findItem(battle.play_order[0])).position, new Phaser.Point(-14, 962), "first ship position");
|
check.equals(nn(list.findItem(battle.play_order[0])).location, { x: -14, y: 962 }, "first ship position");
|
||||||
check.equals(nn(list.findItem(dead)).position, new Phaser.Point(200, 843), "dead ship position");
|
check.equals(nn(list.findItem(dead)).location, { x: 200, y: 843 }, "dead ship position");
|
||||||
check.equals(nn(list.findItem(battle.play_order[1])).position, new Phaser.Point(2, 843), "second ship position");
|
check.equals(nn(list.findItem(battle.play_order[1])).location, { x: 2, y: 843 }, "second ship position");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@ module TK.SpaceTac.UI {
|
||||||
ship_buttons: IShipButton
|
ship_buttons: IShipButton
|
||||||
|
|
||||||
// Container
|
// Container
|
||||||
container: Phaser.Image
|
container: UIContainer
|
||||||
|
|
||||||
// List of ship items
|
// List of ship items
|
||||||
items: ShipListItem[]
|
items: ShipListItem[]
|
||||||
|
@ -25,14 +25,16 @@ module TK.SpaceTac.UI {
|
||||||
hovered: ShipListItem | null
|
hovered: ShipListItem | null
|
||||||
|
|
||||||
// Info button
|
// Info button
|
||||||
info_button: Phaser.Button
|
info_button: UIButton
|
||||||
|
|
||||||
constructor(view: BaseView, battle: Battle, player: Player, tactical_mode: Toggle, ship_buttons: IShipButton, parent?: UIContainer, x = 0, y = 0) {
|
constructor(view: BaseView, battle: Battle, player: Player, tactical_mode: Toggle, ship_buttons: IShipButton, parent?: UIContainer, x = 0, y = 0) {
|
||||||
let builder = new UIBuilder(view, parent);
|
let builder = new UIBuilder(view, parent);
|
||||||
this.container = builder.image("battle-shiplist-background", x, y);
|
this.container = builder.container("shiplist", x, y);
|
||||||
|
|
||||||
|
builder = builder.in(this.container);
|
||||||
|
builder.image("battle-shiplist-background", 0, 0);
|
||||||
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
// TODO Should use an UI game state, not the actual game state
|
|
||||||
this.battle = battle;
|
this.battle = battle;
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.ship_buttons = ship_buttons;
|
this.ship_buttons = ship_buttons;
|
||||||
|
@ -40,13 +42,8 @@ module TK.SpaceTac.UI {
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.hovered = null;
|
this.hovered = null;
|
||||||
|
|
||||||
let info = view.getImageInfo("battle-shiplist-info-button");
|
// FIXME
|
||||||
this.info_button = new Phaser.Button(view.game, 0, 0, info.key, undefined, undefined, info.frame, info.frame);
|
this.info_button = builder.button("battle-shiplist-info-button", 0, 0, () => null, "Tactical display", on => tactical_mode.manipulate("shiplist")(on));
|
||||||
this.view.inputs.setHoverClick(this.info_button,
|
|
||||||
() => tactical_mode.manipulate("shiplist")(true),
|
|
||||||
() => tactical_mode.manipulate("shiplist")(false),
|
|
||||||
() => null);
|
|
||||||
this.container.addChild(this.info_button);
|
|
||||||
|
|
||||||
this.setShipsFromBattle(battle);
|
this.setShipsFromBattle(battle);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +100,7 @@ module TK.SpaceTac.UI {
|
||||||
var owned = ship.isPlayedBy(this.player);
|
var owned = ship.isPlayedBy(this.player);
|
||||||
var result = new ShipListItem(this, 200, this.container.height / 2, ship, owned, this.ship_buttons);
|
var result = new ShipListItem(this, 200, this.container.height / 2, ship, owned, this.ship_buttons);
|
||||||
this.items.push(result);
|
this.items.push(result);
|
||||||
this.container.addChild(result);
|
this.container.add(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,15 +122,16 @@ module TK.SpaceTac.UI {
|
||||||
item.visible = false;
|
item.visible = false;
|
||||||
} else {
|
} else {
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
item.moveTo(-14, 962, animate ? 1000 : 0);
|
item.moveAt(-14, 962, animate ? 1000 : 0);
|
||||||
} else {
|
} else {
|
||||||
item.moveTo(2, 942 - position * 99, animate ? 1000 : 0);
|
item.moveAt(2, 942 - position * 99, animate ? 1000 : 0);
|
||||||
}
|
}
|
||||||
item.visible = true;
|
item.visible = true;
|
||||||
this.container.setChildIndex(item, position);
|
item.setZ(99 - position);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item.moveTo(200, item.y, animate ? 1000 : 0);
|
item.setZ(100);
|
||||||
|
item.moveAt(200, item.y, animate ? 1000 : 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
// One item in a ship list (used in BattleView)
|
/**
|
||||||
export class ShipListItem extends Phaser.Button {
|
* One item in a ship list (used in BattleView)
|
||||||
|
*/
|
||||||
|
export class ShipListItem extends UIContainer {
|
||||||
// Reference to the view
|
// Reference to the view
|
||||||
view: BaseView
|
view: BaseView
|
||||||
|
|
||||||
|
@ -8,43 +10,40 @@ module TK.SpaceTac.UI {
|
||||||
ship: Ship
|
ship: Ship
|
||||||
|
|
||||||
// Player indicator
|
// Player indicator
|
||||||
player_indicator: Phaser.Image
|
player_indicator: UIImage
|
||||||
|
|
||||||
// Portrait
|
// Portrait
|
||||||
portrait: Phaser.Image
|
portrait: UIImage
|
||||||
|
|
||||||
// Damage flashing indicator
|
// Damage flashing indicator
|
||||||
damage_indicator: Phaser.Image
|
damage_indicator: UIImage
|
||||||
|
|
||||||
// Hover indicator
|
// Hover indicator
|
||||||
hover_indicator: Phaser.Image
|
hover_indicator: UIImage
|
||||||
|
|
||||||
// Create a ship button for the battle ship list
|
// Create a ship button for the battle ship list
|
||||||
constructor(list: ShipList, x: number, y: number, ship: Ship, owned: boolean, ship_buttons: IShipButton) {
|
constructor(list: ShipList, x: number, y: number, ship: Ship, owned: boolean, ship_buttons: IShipButton) {
|
||||||
let info = list.view.getImageInfo("battle-shiplist-item-background");
|
// TODO Make it an UIButton
|
||||||
super(list.view.game, x, y, info.key, undefined, undefined, info.frame, info.frame);
|
super(list.view, x, y);
|
||||||
this.view = list.view;
|
this.view = list.view;
|
||||||
|
|
||||||
this.ship = ship;
|
this.ship = ship;
|
||||||
|
|
||||||
this.player_indicator = this.view.newImage(owned ? "battle-hud-ship-own-mini" : "battle-hud-ship-enemy-mini", 102, 52);
|
let builder = new UIBuilder(list.view, this);
|
||||||
this.player_indicator.anchor.set(0.5);
|
|
||||||
this.player_indicator.angle = -90;
|
|
||||||
this.addChild(this.player_indicator);
|
|
||||||
|
|
||||||
this.portrait = this.view.newImage(`ship-${ship.model.code}-sprite`, 52, 52);
|
builder.image("battle-shiplist-item-background");
|
||||||
this.portrait.anchor.set(0.5);
|
|
||||||
this.portrait.scale.set(0.8);
|
|
||||||
this.portrait.angle = 180;
|
|
||||||
this.addChild(this.portrait);
|
|
||||||
|
|
||||||
this.damage_indicator = this.view.newImage("battle-shiplist-damage", 8, 9);
|
this.player_indicator = builder.image(owned ? "battle-hud-ship-own-mini" : "battle-hud-ship-enemy-mini", 102, 52, true);
|
||||||
this.damage_indicator.alpha = 0;
|
this.player_indicator.setAngle(-90);
|
||||||
this.addChild(this.damage_indicator);
|
|
||||||
|
|
||||||
this.hover_indicator = this.view.newImage("battle-shiplist-hover", 7, 8);
|
this.portrait = builder.image(`ship-${ship.model.code}-sprite`, 52, 52, true);
|
||||||
|
this.portrait.setScale(0.8)
|
||||||
|
this.portrait.setAngle(180);
|
||||||
|
|
||||||
|
this.damage_indicator = builder.image("battle-shiplist-damage", 8, 9);
|
||||||
|
this.damage_indicator.visible = false;
|
||||||
|
|
||||||
|
this.hover_indicator = builder.image("battle-shiplist-hover", 7, 8);
|
||||||
this.hover_indicator.visible = false;
|
this.hover_indicator.visible = false;
|
||||||
this.addChild(this.hover_indicator);
|
|
||||||
|
|
||||||
this.view.inputs.setHoverClick(this,
|
this.view.inputs.setHoverClick(this,
|
||||||
() => ship_buttons.cursorOnShip(ship),
|
() => ship_buttons.cursorOnShip(ship),
|
||||||
|
@ -53,22 +52,38 @@ module TK.SpaceTac.UI {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flash a damage indicator
|
get location(): { x: number, y: number } {
|
||||||
setDamageHit() {
|
return { x: this.x, y: this.y };
|
||||||
this.game.tweens.create(this.damage_indicator).to({ alpha: 1 }, 100).to({ alpha: 0 }, 150).repeatAll(2).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to a given location on screen
|
/**
|
||||||
moveTo(x: number, y: number, duration: number) {
|
* Flash a damage indicator
|
||||||
|
*/
|
||||||
|
setDamageHit() {
|
||||||
|
this.view.tweens.add({
|
||||||
|
targets: this.damage_indicator,
|
||||||
|
duration: 100,
|
||||||
|
alpha: 1,
|
||||||
|
repeat: 2,
|
||||||
|
yoyo: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to a given location on screen
|
||||||
|
*/
|
||||||
|
moveAt(x: number, y: number, duration: number) {
|
||||||
if (duration && (this.x != x || this.y != y)) {
|
if (duration && (this.x != x || this.y != y)) {
|
||||||
this.view.animations.addAnimation(this, { x: x, y: y }, duration);
|
this.view.animations.addAnimation<UIContainer>(this, { x: x, y: y }, duration);
|
||||||
} else {
|
} else {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the hovered status
|
/**
|
||||||
|
* Set the hovered status
|
||||||
|
*/
|
||||||
setHovered(hovered: boolean) {
|
setHovered(hovered: boolean) {
|
||||||
this.view.animations.setVisible(this.hover_indicator, hovered, 200);
|
this.view.animations.setVisible(this.hover_indicator, hovered, 200);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ module TK.SpaceTac.UI {
|
||||||
let portrait_bg = builder.image("battle-tooltip-ship-portrait", 0, 0);
|
let portrait_bg = builder.image("battle-tooltip-ship-portrait", 0, 0);
|
||||||
builder.in(portrait_bg, builder => {
|
builder.in(portrait_bg, builder => {
|
||||||
let portrait = builder.image(`ship-${ship.model.code}-portrait`, 1, 1);
|
let portrait = builder.image(`ship-${ship.model.code}-portrait`, 1, 1);
|
||||||
portrait.scale.set(0.75);
|
portrait.setScale(0.75);
|
||||||
});
|
});
|
||||||
|
|
||||||
let enemy = !this.battleview.player.is(ship.fleet.player);
|
let enemy = !this.battleview.player.is(ship.fleet.player);
|
||||||
|
@ -46,7 +46,7 @@ module TK.SpaceTac.UI {
|
||||||
ship.actions.listAll().forEach(action => {
|
ship.actions.listAll().forEach(action => {
|
||||||
if (!(action instanceof EndTurnAction) && !(action instanceof MoveAction)) {
|
if (!(action instanceof EndTurnAction) && !(action instanceof MoveAction)) {
|
||||||
let icon = builder.image(`action-${action.code}`, 0, iy);
|
let icon = builder.image(`action-${action.code}`, 0, iy);
|
||||||
icon.scale.set(0.15);
|
icon.setScale(0.15);
|
||||||
builder.text(action.name, 46, iy + 8);
|
builder.text(action.name, 46, iy + 8);
|
||||||
iy += 40;
|
iy += 40;
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
let sprite = this.battleview.arena.findShipSprite(ship);
|
let sprite = this.battleview.arena.findShipSprite(ship);
|
||||||
if (sprite) {
|
if (sprite) {
|
||||||
this.container.show(sprite.frame_owner.getBounds());
|
this.container.show(UITools.getBounds(sprite.frame_owner));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.called(collect, [
|
check.called(collect, [
|
||||||
[ship, new Target(20, 10), ship.location]
|
[ship, new Target(20, 10), ship.location]
|
||||||
])
|
])
|
||||||
check.equals(targetting.impact_indicators.children.length, 3);
|
check.equals(targetting.impact_indicators.length, 3);
|
||||||
check.equals(targetting.impact_indicators.visible, true);
|
check.equals(targetting.impact_indicators.visible, true);
|
||||||
|
|
||||||
targetting.updateImpactIndicators(impacts, ship, action, new Target(20, 11));
|
targetting.updateImpactIndicators(impacts, ship, action, new Target(20, 11));
|
||||||
|
@ -68,7 +68,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.called(collect, [
|
check.called(collect, [
|
||||||
[ship, new Target(20, 11), ship.location]
|
[ship, new Target(20, 11), ship.location]
|
||||||
])
|
])
|
||||||
check.equals(targetting.impact_indicators.children.length, 2);
|
check.equals(targetting.impact_indicators.length, 2);
|
||||||
check.equals(targetting.impact_indicators.visible, true);
|
check.equals(targetting.impact_indicators.visible, true);
|
||||||
|
|
||||||
targetting.updateImpactIndicators(impacts, ship, action, new Target(20, 12));
|
targetting.updateImpactIndicators(impacts, ship, action, new Target(20, 12));
|
||||||
|
@ -109,12 +109,15 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.equals(targetting.container.visible, true);
|
check.equals(targetting.container.visible, true);
|
||||||
check.equals(targetting.drawn_info.visible, true);
|
check.equals(targetting.drawn_info.visible, true);
|
||||||
check.equals(targetting.fire_arrow.visible, true);
|
check.equals(targetting.fire_arrow.visible, true);
|
||||||
check.containing(targetting.fire_arrow.position, { x: 156, y: 65 });
|
check.equals(targetting.fire_arrow.x, 156);
|
||||||
|
check.equals(targetting.fire_arrow.y, 65);
|
||||||
check.nears(targetting.fire_arrow.rotation, 0.534594, 5);
|
check.nears(targetting.fire_arrow.rotation, 0.534594, 5);
|
||||||
check.equals(targetting.impact_area.visible, true);
|
check.equals(targetting.impact_area.visible, true);
|
||||||
check.containing(targetting.impact_area.position, { x: 156, y: 65 });
|
check.equals(targetting.impact_area.x, 156);
|
||||||
|
check.equals(targetting.impact_area.y, 65);
|
||||||
check.equals(targetting.move_ghost.visible, true);
|
check.equals(targetting.move_ghost.visible, true);
|
||||||
check.containing(targetting.move_ghost.position, { x: 80, y: 20 });
|
check.equals(targetting.move_ghost.x, 80);
|
||||||
|
check.equals(targetting.move_ghost.y, 20);
|
||||||
check.nears(targetting.move_ghost.rotation, 0.534594, 5);
|
check.nears(targetting.move_ghost.rotation, 0.534594, 5);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
export class Targetting {
|
export class Targetting {
|
||||||
// Container group
|
// Container group
|
||||||
container: Phaser.Group
|
container: UIContainer
|
||||||
|
|
||||||
// Current action
|
// Current action
|
||||||
ship: Ship | null = null
|
ship: Ship | null = null
|
||||||
|
@ -19,13 +19,13 @@ module TK.SpaceTac.UI {
|
||||||
effects: BaseBattleDiff[] = []
|
effects: BaseBattleDiff[] = []
|
||||||
|
|
||||||
// Move and fire lines
|
// Move and fire lines
|
||||||
drawn_info: Phaser.Graphics
|
drawn_info: UIGraphics
|
||||||
move_ghost: Phaser.Image
|
move_ghost: UIImage
|
||||||
fire_arrow: Phaser.Image
|
fire_arrow: UIImage
|
||||||
|
|
||||||
// Impact area
|
// Impact area
|
||||||
impact_area: Phaser.Graphics
|
impact_area: UIGraphics
|
||||||
impact_indicators: Phaser.Group
|
impact_indicators: UIContainer
|
||||||
|
|
||||||
// Collaborators to update
|
// Collaborators to update
|
||||||
actionbar: ActionBar
|
actionbar: ActionBar
|
||||||
|
@ -41,34 +41,29 @@ module TK.SpaceTac.UI {
|
||||||
this.tactical_mode = tactical_mode.manipulate("targetting");
|
this.tactical_mode = tactical_mode.manipulate("targetting");
|
||||||
this.range_hint = range_hint;
|
this.range_hint = range_hint;
|
||||||
|
|
||||||
this.container = view.add.group();
|
let builder = new UIBuilder(view);
|
||||||
|
this.container = builder.container("targetting");
|
||||||
|
builder = builder.in(this.container);
|
||||||
|
|
||||||
// Visual effects
|
// Visual effects
|
||||||
this.drawn_info = new Phaser.Graphics(view.game, 0, 0);
|
this.impact_area = builder.graphics("impact-area");
|
||||||
this.drawn_info.visible = false;
|
this.impact_area.setVisible(false);
|
||||||
this.move_ghost = view.newImage("common-transparent");
|
this.drawn_info = builder.graphics("lines");
|
||||||
this.move_ghost.anchor.set(0.5, 0.5);
|
this.drawn_info.setVisible(false);
|
||||||
this.move_ghost.alpha = 0.8;
|
this.move_ghost = builder.image("common-transparent", 0, 0, true);
|
||||||
this.move_ghost.visible = false;
|
this.move_ghost.setAlpha(0.8);
|
||||||
this.fire_arrow = this.view.newImage("battle-hud-simulator-ok");
|
this.move_ghost.setVisible(false);
|
||||||
this.fire_arrow.anchor.set(1, 0.5);
|
this.fire_arrow = builder.image("battle-hud-simulator-ok");
|
||||||
this.fire_arrow.visible = false;
|
this.fire_arrow.setOrigin(1, 0.5);
|
||||||
this.impact_indicators = new Phaser.Group(view.game);
|
this.fire_arrow.setVisible(false);
|
||||||
this.impact_indicators.visible = false;
|
this.impact_indicators = builder.container("impact-indicators");
|
||||||
this.impact_area = new Phaser.Graphics(view.game);
|
this.impact_indicators.setVisible(false);
|
||||||
this.impact_area.visible = false;
|
|
||||||
|
|
||||||
this.container.add(this.impact_area);
|
|
||||||
this.container.add(this.drawn_info);
|
|
||||||
this.container.add(this.move_ghost);
|
|
||||||
this.container.add(this.fire_arrow);
|
|
||||||
this.container.add(this.impact_indicators);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to a given view layer
|
* Move to a given view layer
|
||||||
*/
|
*/
|
||||||
moveToLayer(layer: Phaser.Group): void {
|
moveToLayer(layer: UIContainer): void {
|
||||||
layer.add(this.container);
|
layer.add(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,11 +80,16 @@ module TK.SpaceTac.UI {
|
||||||
drawVector(color: number, x1: number, y1: number, x2: number, y2: number, gradation = 0) {
|
drawVector(color: number, x1: number, y1: number, x2: number, y2: number, gradation = 0) {
|
||||||
let line = this.drawn_info;
|
let line = this.drawn_info;
|
||||||
line.lineStyle(6, color);
|
line.lineStyle(6, color);
|
||||||
|
line.beginPath();
|
||||||
line.moveTo(x1, y1);
|
line.moveTo(x1, y1);
|
||||||
line.lineTo(x2, y2);
|
line.lineTo(x2, y2);
|
||||||
|
line.strokePath();
|
||||||
|
line.beginPath();
|
||||||
line.lineStyle(2, 0x000000, 0.6);
|
line.lineStyle(2, 0x000000, 0.6);
|
||||||
line.moveTo(x1, y1);
|
line.moveTo(x1, y1);
|
||||||
line.lineTo(x2, y2);
|
line.lineTo(x2, y2);
|
||||||
|
line.closePath();
|
||||||
|
line.strokePath();
|
||||||
line.visible = true;
|
line.visible = true;
|
||||||
|
|
||||||
if (gradation) {
|
if (gradation) {
|
||||||
|
@ -125,7 +125,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Update impact indicators (highlighting impacted ships, with success factor)
|
* Update impact indicators (highlighting impacted ships, with success factor)
|
||||||
*/
|
*/
|
||||||
updateImpactIndicators(impacts: Phaser.Group, ship: Ship, action: BaseAction, target: Target, source: IArenaLocation = ship.location): void {
|
updateImpactIndicators(impacts: UIContainer, ship: Ship, action: BaseAction, target: Target, source: IArenaLocation = ship.location): void {
|
||||||
let ships = action.getImpactedShips(ship, target, source);
|
let ships = action.getImpactedShips(ship, target, source);
|
||||||
if (ships.length) {
|
if (ships.length) {
|
||||||
// TODO differential
|
// TODO differential
|
||||||
|
@ -143,7 +143,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Update impact graphics (area display)
|
* Update impact graphics (area display)
|
||||||
*/
|
*/
|
||||||
updateImpactArea(area: Phaser.Graphics, action: BaseAction): void {
|
updateImpactArea(area: UIGraphics, action: BaseAction): void {
|
||||||
area.clear();
|
area.clear();
|
||||||
|
|
||||||
let color = 0;
|
let color = 0;
|
||||||
|
@ -168,24 +168,20 @@ module TK.SpaceTac.UI {
|
||||||
if (radius) {
|
if (radius) {
|
||||||
if (angle) {
|
if (angle) {
|
||||||
area.lineStyle(2, color, 0.6);
|
area.lineStyle(2, color, 0.6);
|
||||||
area.beginFill(color, 0.2);
|
area.fillStyle(color, 0.2);
|
||||||
area.arc(0, 0, radius, angle, -angle, true);
|
area.arc(0, 0, radius, angle, -angle, true);
|
||||||
area.endFill();
|
|
||||||
|
|
||||||
area.lineStyle(1, color, 0.3);
|
area.lineStyle(1, color, 0.3);
|
||||||
area.beginFill(color, 0.1);
|
area.fillStyle(color, 0.1);
|
||||||
area.arc(0, 0, radius * 0.95, angle * 0.95, -angle * 0.95, true);
|
area.arc(0, 0, radius * 0.95, angle * 0.95, -angle * 0.95, true);
|
||||||
area.endFill();
|
|
||||||
} else {
|
} else {
|
||||||
area.lineStyle(2, color, 0.6);
|
area.lineStyle(2, color, 0.6);
|
||||||
area.beginFill(color, 0.2);
|
area.fillStyle(color, 0.2);
|
||||||
area.drawCircle(0, 0, radius * 2);
|
area.fillCircle(0, 0, radius);
|
||||||
area.endFill();
|
|
||||||
|
|
||||||
area.lineStyle(1, color, 0.3);
|
area.lineStyle(1, color, 0.3);
|
||||||
area.beginFill(color, 0.1);
|
area.fillStyle(color, 0.1);
|
||||||
area.drawCircle(0, 0, radius * 2 * 0.95);
|
area.fillCircle(0, 0, radius * 0.95);
|
||||||
area.endFill();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +210,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
if (simulation.need_move) {
|
if (simulation.need_move) {
|
||||||
this.move_ghost.visible = true;
|
this.move_ghost.visible = true;
|
||||||
this.move_ghost.position.set(simulation.move_location.x, simulation.move_location.y);
|
this.move_ghost.setPosition(simulation.move_location.x, simulation.move_location.y);
|
||||||
this.move_ghost.rotation = angle;
|
this.move_ghost.rotation = angle;
|
||||||
} else {
|
} else {
|
||||||
this.move_ghost.visible = false;
|
this.move_ghost.visible = false;
|
||||||
|
@ -222,18 +218,18 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
if (simulation.need_fire) {
|
if (simulation.need_fire) {
|
||||||
if (this.action instanceof TriggerAction && this.action.angle) {
|
if (this.action instanceof TriggerAction && this.action.angle) {
|
||||||
this.impact_area.position.set(simulation.move_location.x, simulation.move_location.y);
|
this.impact_area.setPosition(simulation.move_location.x, simulation.move_location.y);
|
||||||
this.impact_area.rotation = arenaAngle(simulation.move_location, simulation.fire_location);
|
this.impact_area.setRotation(arenaAngle(simulation.move_location, simulation.fire_location));
|
||||||
} else {
|
} else {
|
||||||
this.impact_area.position.set(this.target.x, this.target.y);
|
this.impact_area.setPosition(this.target.x, this.target.y);
|
||||||
}
|
}
|
||||||
this.impact_area.alpha = simulation.can_fire ? 1 : 0.5;
|
this.impact_area.alpha = simulation.can_fire ? 1 : 0.5;
|
||||||
this.impact_area.visible = true;
|
this.impact_area.visible = true;
|
||||||
|
|
||||||
this.updateImpactIndicators(this.impact_indicators, this.ship, this.action, this.target, this.simulation.move_location);
|
this.updateImpactIndicators(this.impact_indicators, this.ship, this.action, this.target, this.simulation.move_location);
|
||||||
|
|
||||||
this.fire_arrow.position.set(this.target.x, this.target.y);
|
this.fire_arrow.setPosition(this.target.x, this.target.y);
|
||||||
this.fire_arrow.rotation = angle;
|
this.fire_arrow.setRotation(angle);
|
||||||
this.view.changeImage(this.fire_arrow, simulation.complete ? "battle-hud-simulator-ok" : "battle-hud-simulator-power");
|
this.view.changeImage(this.fire_arrow, simulation.complete ? "battle-hud-simulator-ok" : "battle-hud-simulator-power");
|
||||||
this.fire_arrow.visible = true;
|
this.fire_arrow.visible = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -243,8 +239,8 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.drawVector(0x888888, this.ship.arena_x, this.ship.arena_y, this.target.x, this.target.y);
|
this.drawVector(0x888888, this.ship.arena_x, this.ship.arena_y, this.target.x, this.target.y);
|
||||||
this.fire_arrow.position.set(this.target.x, this.target.y);
|
this.fire_arrow.setPosition(this.target.x, this.target.y);
|
||||||
this.fire_arrow.rotation = angle;
|
this.fire_arrow.setRotation(angle);
|
||||||
this.view.changeImage(this.fire_arrow, "battle-hud-simulator-failed");
|
this.view.changeImage(this.fire_arrow, "battle-hud-simulator-failed");
|
||||||
this.fire_arrow.visible = true;
|
this.fire_arrow.visible = true;
|
||||||
this.impact_area.visible = false;
|
this.impact_area.visible = false;
|
||||||
|
|
|
@ -2,15 +2,17 @@ module TK.SpaceTac.UI.Specs {
|
||||||
testing("WeaponEffect", test => {
|
testing("WeaponEffect", test => {
|
||||||
let testgame = setupBattleview(test);
|
let testgame = setupBattleview(test);
|
||||||
let clock = test.clock();
|
let clock = test.clock();
|
||||||
|
let t = 0;
|
||||||
|
|
||||||
function checkEmitters(step: string, expected: number) {
|
function checkEmitters(step: string, expected: number) {
|
||||||
test.check.same(testgame.view.arena.layer_weapon_effects.children.length, expected, `${step} - layer children`);
|
test.check.same(testgame.view.arena.layer_weapon_effects.length, expected, `${step} - layer children`);
|
||||||
test.check.same(keys(testgame.view.game.particles.emitters).length, expected, `${step} - registered emitters`);
|
//test.check.same(keys(testgame.view.particles.emitters).length, expected, `${step} - registered emitters`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fastForward(milliseconds: number) {
|
function fastForward(milliseconds: number) {
|
||||||
|
t += milliseconds;
|
||||||
clock.forward(milliseconds);
|
clock.forward(milliseconds);
|
||||||
testgame.ui.updateLogic(milliseconds);
|
testgame.ui.headlessStep(t, milliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
test.case("displays shield hit effect", check => {
|
test.case("displays shield hit effect", check => {
|
||||||
|
@ -18,16 +20,22 @@ module TK.SpaceTac.UI.Specs {
|
||||||
battleview.timer = new Timer();
|
battleview.timer = new Timer();
|
||||||
|
|
||||||
let effect = new WeaponEffect(battleview.arena, new Ship(), new Target(0, 0), new TriggerAction("weapon"));
|
let effect = new WeaponEffect(battleview.arena, new Ship(), new Target(0, 0), new TriggerAction("weapon"));
|
||||||
effect.shieldImpactEffect({ x: 10, y: 10 }, { x: 20, y: 15 }, 1000, 3000, true);
|
effect.shieldImpactEffect({ x: 10, y: 10 }, { x: 20, y: 15 }, 500, 3000, true);
|
||||||
|
|
||||||
let layer = battleview.arena.layer_weapon_effects;
|
let layer = battleview.arena.layer_weapon_effects;
|
||||||
check.equals(layer.children.length, 2);
|
check.equals(layer.length, 1);
|
||||||
|
|
||||||
check.equals(layer.children[0] instanceof Phaser.Image, true);
|
clock.forward(600);
|
||||||
check.nears(layer.children[0].rotation, -2.677945044588987, 10);
|
check.equals(layer.length, 2);
|
||||||
check.containing(layer.children[0].position, { x: 20, y: 15 });
|
|
||||||
|
|
||||||
check.equals(layer.children[1] instanceof Phaser.Particles.Arcade.Emitter, true);
|
let child = layer.list[0];
|
||||||
|
if (check.instance(child, UIImage, "first child is an image")) {
|
||||||
|
check.nears(child.rotation, -2.677945044588987, 10);
|
||||||
|
check.equals(child.x, 20, "x");
|
||||||
|
check.equals(child.y, 15, "y");
|
||||||
|
}
|
||||||
|
|
||||||
|
check.instance(layer.list[1], Phaser.GameObjects.Particles.ParticleEmitterManager, "second child is an emitter");
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("displays gatling gun effect", check => {
|
test.case("displays gatling gun effect", check => {
|
||||||
|
@ -39,8 +47,8 @@ module TK.SpaceTac.UI.Specs {
|
||||||
effect.gunEffect();
|
effect.gunEffect();
|
||||||
|
|
||||||
let layer = battleview.arena.layer_weapon_effects;
|
let layer = battleview.arena.layer_weapon_effects;
|
||||||
check.equals(layer.children.length, 1);
|
check.equals(layer.length, 1);
|
||||||
check.equals(layer.children[0] instanceof Phaser.Particles.Arcade.Emitter, true);
|
check.instance(layer.list[0], Phaser.GameObjects.Particles.ParticleEmitterManager, "first child is an emitter");
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("displays shield and hull effect on impacted ships", check => {
|
test.case("displays shield and hull effect on impacted ships", check => {
|
||||||
|
@ -102,16 +110,17 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.equals(result, 200);
|
check.equals(result, 200);
|
||||||
|
|
||||||
let layer = battleview.arena.layer_weapon_effects;
|
let layer = battleview.arena.layer_weapon_effects;
|
||||||
check.equals(layer.children.length, 1);
|
check.equals(layer.length, 1);
|
||||||
check.same(layer.children[0] instanceof Phaser.Image, true, "is image");
|
let image = layer.list[0];
|
||||||
let image = <Phaser.Image>layer.children[0];
|
if (check.instance(image, UIImage, "first child is an image")) {
|
||||||
check.equals(image.name, "battle-effects-laser");
|
check.equals(image.name, "battle-effects-laser");
|
||||||
//check.equals(image.width, 300);
|
//check.equals(image.width, 300);
|
||||||
check.equals(image.x, 20);
|
check.equals(image.x, 20);
|
||||||
check.equals(image.y, 30);
|
check.equals(image.y, 30);
|
||||||
check.nears(image.rotation, Math.PI / 4);
|
check.nears(image.rotation, Math.PI / 4);
|
||||||
|
}
|
||||||
|
|
||||||
let values = battleview.animations.simulate(image, "rotation", 4, result);
|
let values = battleview.animations.simulate(image, "rotation", 4);
|
||||||
check.nears(values[0], Math.PI / 4);
|
check.nears(values[0], Math.PI / 4);
|
||||||
check.nears(values[1], 0);
|
check.nears(values[1], 0);
|
||||||
check.nears(values[2], -Math.PI / 4);
|
check.nears(values[2], -Math.PI / 4);
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
// Particle that is rotated to always face its ongoing direction
|
|
||||||
class BulletParticle extends Phaser.Particle {
|
|
||||||
update(): void {
|
|
||||||
super.update();
|
|
||||||
this.rotation = Math.atan2(this.body.velocity.y, this.body.velocity.x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visual effects renderer for weapons.
|
* Visual effects renderer for weapons.
|
||||||
*/
|
*/
|
||||||
|
@ -22,7 +14,10 @@ module TK.SpaceTac.UI {
|
||||||
private timer: Timer
|
private timer: Timer
|
||||||
|
|
||||||
// Display group in which to display the visual effects
|
// Display group in which to display the visual effects
|
||||||
private layer: Phaser.Group
|
private layer: UIContainer
|
||||||
|
|
||||||
|
// Builder for images
|
||||||
|
private builder: UIBuilder
|
||||||
|
|
||||||
// Firing ship
|
// Firing ship
|
||||||
private ship: Ship
|
private ship: Ship
|
||||||
|
@ -41,6 +36,7 @@ module TK.SpaceTac.UI {
|
||||||
this.view = arena.view;
|
this.view = arena.view;
|
||||||
this.timer = arena.view.timer;
|
this.timer = arena.view.timer;
|
||||||
this.layer = arena.layer_weapon_effects;
|
this.layer = arena.layer_weapon_effects;
|
||||||
|
this.builder = new UIBuilder(arena.view, this.layer);
|
||||||
this.ship = ship;
|
this.ship = ship;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
|
@ -116,32 +112,28 @@ module TK.SpaceTac.UI {
|
||||||
shieldImpactEffect(from: IArenaLocation, ship: IArenaLocation, delay: number, duration: number, particles = false) {
|
shieldImpactEffect(from: IArenaLocation, ship: IArenaLocation, delay: number, duration: number, particles = false) {
|
||||||
let angle = Math.atan2(from.y - ship.y, from.x - ship.x);
|
let angle = Math.atan2(from.y - ship.y, from.x - ship.x);
|
||||||
|
|
||||||
let effect = this.view.newImage("battle-effects-shield-impact", ship.x, ship.y);
|
let effect = this.builder.image("battle-effects-shield-impact", ship.x, ship.y, true);
|
||||||
effect.alpha = 0;
|
effect.setAlpha(0);
|
||||||
effect.rotation = angle;
|
effect.setRotation(angle);
|
||||||
effect.anchor.set(0.5, 0.5);
|
|
||||||
this.layer.add(effect);
|
|
||||||
|
|
||||||
let tween1 = this.ui.add.tween(effect).to({ alpha: 1 }, 100).delay(delay);
|
let tween1 = this.view.animations.addAnimation(effect, { alpha: 1 }, 100, undefined, delay);
|
||||||
let tween2 = this.ui.add.tween(effect).to({ alpha: 0 }, 100).delay(duration);
|
let tween2 = this.view.animations.addAnimation(effect, { alpha: 0 }, 100, undefined, delay + duration);
|
||||||
tween1.chain(tween2);
|
tween2.then(() => effect.destroy());
|
||||||
tween2.onComplete.addOnce(() => effect.destroy());
|
|
||||||
tween1.start();
|
|
||||||
|
|
||||||
if (particles) {
|
if (particles) {
|
||||||
let image = this.view.getImageInfo("battle-effects-hot");
|
this.timer.schedule(delay, () => {
|
||||||
let emitter = this.ui.add.emitter(ship.x + Math.cos(angle) * 35, ship.y + Math.sin(angle) * 35, 30);
|
this.builder.particles({
|
||||||
emitter.minParticleScale = 0.7;
|
key: "battle-effects-hot",
|
||||||
emitter.maxParticleScale = 1.2;
|
source: { x: ship.x + Math.cos(angle) * 40, y: ship.y + Math.sin(angle) * 40, radius: 10 },
|
||||||
emitter.gravity = 0;
|
emitDuration: 500,
|
||||||
emitter.makeParticles(image.key, image.frame);
|
count: 50,
|
||||||
emitter.setSize(10, 10);
|
lifetime: 400,
|
||||||
emitter.setRotation(0, 0);
|
fading: true,
|
||||||
emitter.setXSpeed(-Math.cos(angle) * 20, -Math.cos(angle) * 80);
|
direction: { minangle: Math.PI + angle - 0.3, maxangle: Math.PI + angle + 0.3 },
|
||||||
emitter.setYSpeed(-Math.sin(angle) * 20, -Math.sin(angle) * 80);
|
scale: { min: 0.7, max: 1.2 },
|
||||||
this.timer.schedule(delay, () => emitter.start(false, 200, 30, duration * 0.8 / 30));
|
speed: { min: 20, max: 80 }
|
||||||
this.layer.add(emitter);
|
});
|
||||||
this.timer.schedule(delay + duration + 5000, () => emitter.destroy());
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,19 +143,17 @@ module TK.SpaceTac.UI {
|
||||||
hullImpactEffect(from: IArenaLocation, ship: IArenaLocation, delay: number, duration: number) {
|
hullImpactEffect(from: IArenaLocation, ship: IArenaLocation, delay: number, duration: number) {
|
||||||
let angle = Math.atan2(from.y - ship.y, from.x - ship.x);
|
let angle = Math.atan2(from.y - ship.y, from.x - ship.x);
|
||||||
|
|
||||||
let image = this.view.getImageInfo("battle-effects-hot");
|
this.builder.particles({
|
||||||
let emitter = this.ui.add.emitter(ship.x + Math.cos(angle) * 10, ship.y + Math.sin(angle) * 10, 30);
|
key: "battle-effects-hot",
|
||||||
emitter.minParticleScale = 1.0;
|
source: { x: ship.x + Math.cos(angle) * 40, y: ship.y + Math.sin(angle) * 40, radius: 7 },
|
||||||
emitter.maxParticleScale = 2.0;
|
emitDuration: 500,
|
||||||
emitter.gravity = 0;
|
count: 50,
|
||||||
emitter.makeParticles(image.key, image.frame);
|
lifetime: 400,
|
||||||
emitter.setSize(15, 15);
|
fading: true,
|
||||||
emitter.setRotation(0, 0);
|
direction: { minangle: Math.PI + angle - 0.3, maxangle: Math.PI + angle + 0.3 },
|
||||||
emitter.setXSpeed(-Math.cos(angle) * 120, -Math.cos(angle) * 260);
|
scale: { min: 1, max: 2 },
|
||||||
emitter.setYSpeed(-Math.sin(angle) * 120, -Math.sin(angle) * 260);
|
speed: { min: 120, max: 260 }
|
||||||
this.timer.schedule(delay, () => emitter.start(false, 200, 30, duration * 0.8 / 30));
|
});
|
||||||
this.layer.add(emitter);
|
|
||||||
this.timer.schedule(delay + duration + 5000, () => emitter.destroy());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,34 +162,26 @@ module TK.SpaceTac.UI {
|
||||||
defaultEffect(): number {
|
defaultEffect(): number {
|
||||||
this.ui.audio.playOnce("battle-weapon-missile-launch");
|
this.ui.audio.playOnce("battle-weapon-missile-launch");
|
||||||
|
|
||||||
let missile = this.view.newImage("battle-effects-default", this.source.x, this.source.y);
|
let missile = this.builder.image("battle-effects-default", this.source.x, this.source.y, true);
|
||||||
missile.anchor.set(0.5, 0.5);
|
missile.setRotation(arenaAngle(this.source, this.destination));
|
||||||
missile.rotation = arenaAngle(this.source, this.destination);
|
|
||||||
this.layer.add(missile);
|
|
||||||
|
|
||||||
let blast_radius = this.action.blast;
|
let blast_radius = this.action.blast;
|
||||||
|
|
||||||
let projectile_duration = arenaDistance(this.source, this.destination) * 1.5;
|
let projectile_duration = arenaDistance(this.source, this.destination) * 1.5;
|
||||||
let tween = this.ui.tweens.create(missile);
|
this.view.animations.addAnimation(missile, { x: this.destination.x, y: this.destination.y }, projectile_duration || 1).then(() => {
|
||||||
tween.to({ x: this.destination.x, y: this.destination.y }, projectile_duration || 1);
|
|
||||||
tween.onComplete.addOnce(() => {
|
|
||||||
missile.destroy();
|
missile.destroy();
|
||||||
if (blast_radius > 0) {
|
if (blast_radius > 0) {
|
||||||
this.ui.audio.playOnce("battle-weapon-missile-explosion");
|
this.ui.audio.playOnce("battle-weapon-missile-explosion");
|
||||||
|
|
||||||
let blast = this.view.newImage("battle-effects-blast", this.destination.x, this.destination.y);
|
let blast = this.builder.image("battle-effects-blast", this.destination.x, this.destination.y, true);
|
||||||
let scaling = blast_radius * 2 / (blast.width * 0.9);
|
let scaling = blast_radius * 2 / (blast.width * 0.9);
|
||||||
blast.anchor.set(0.5, 0.5);
|
blast.setScale(0.001);
|
||||||
blast.scale.set(0.001, 0.001);
|
Promise.all([
|
||||||
let tween1 = this.ui.tweens.create(blast.scale).to({ x: scaling, y: scaling }, 1500, Phaser.Easing.Quintic.Out);
|
this.view.animations.addAnimation(blast, { alpha: 0 }, 1450, "Quad.easeIn"),
|
||||||
tween1.onComplete.addOnce(() => blast.destroy());
|
this.view.animations.addAnimation(blast, { scaleX: scaling, scaleY: scaling }, 1500, "Quint.easeOut"),
|
||||||
tween1.start();
|
]).then(() => blast.destroy());
|
||||||
let tween2 = this.ui.tweens.create(blast).to({ alpha: 0 }, 1450, Phaser.Easing.Quadratic.In);
|
|
||||||
tween2.start();
|
|
||||||
this.layer.add(blast);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tween.start();
|
|
||||||
|
|
||||||
return projectile_duration + (blast_radius ? 1500 : 0);
|
return projectile_duration + (blast_radius ? 1500 : 0);
|
||||||
}
|
}
|
||||||
|
@ -212,15 +194,11 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
this.view.audio.playOnce("battle-weapon-laser");
|
this.view.audio.playOnce("battle-weapon-laser");
|
||||||
|
|
||||||
let laser = this.view.newImage("battle-effects-laser", source.x, source.y);
|
let laser = this.builder.image("battle-effects-laser", source.x, source.y);
|
||||||
laser.anchor.set(0, 0.5);
|
laser.setOrigin(0, 0.5);
|
||||||
laser.rotation = start_angle;
|
laser.setRotation(start_angle);
|
||||||
laser.scale.set(radius / laser.width);
|
laser.setScale(radius / laser.width);
|
||||||
this.layer.add(laser);
|
this.view.animations.addAnimation(laser, { rotation: end_angle }, duration).then(() => laser.destroy());
|
||||||
|
|
||||||
let tween = this.view.tweens.create(laser).to({ rotation: end_angle }, duration);
|
|
||||||
tween.onComplete.addOnce(() => laser.destroy());
|
|
||||||
tween.start();
|
|
||||||
|
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
@ -236,25 +214,26 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
let angle = arenaAngle(this.source, this.target);
|
let angle = arenaAngle(this.source, this.target);
|
||||||
let distance = arenaDistance(this.source, this.target);
|
let distance = arenaDistance(this.source, this.target);
|
||||||
let image = this.view.getImageInfo("battle-effects-bullets");
|
let guard = 35 + (has_shield ? 80 : 40);
|
||||||
let emitter = this.ui.add.emitter(this.source.x + Math.cos(angle) * 35, this.source.y + Math.sin(angle) * 35, 10);
|
|
||||||
let speed = 2000;
|
|
||||||
emitter.particleClass = BulletParticle;
|
|
||||||
emitter.gravity = 0;
|
|
||||||
emitter.setSize(5, 5);
|
|
||||||
emitter.setRotation(0, 0);
|
|
||||||
emitter.setXSpeed(Math.cos(angle) * speed, Math.cos(angle) * speed);
|
|
||||||
emitter.setYSpeed(Math.sin(angle) * speed, Math.sin(angle) * speed);
|
|
||||||
emitter.makeParticles(image.key, image.frame);
|
|
||||||
let guard = 50 + (has_shield ? 80 : 40);
|
|
||||||
if (guard + 1 > distance) {
|
if (guard + 1 > distance) {
|
||||||
guard = distance - 1;
|
guard = distance - 1;
|
||||||
}
|
}
|
||||||
emitter.start(false, 1000 * (distance - guard) / speed, 50, 10);
|
let speed = 2000;
|
||||||
this.layer.add(emitter);
|
let duration = 500;
|
||||||
this.timer.schedule(5000, () => emitter.destroy());
|
let lifetime = 1000 * (distance - guard) / speed;
|
||||||
|
this.builder.particles({
|
||||||
|
key: "battle-effects-bullets",
|
||||||
|
source: { x: this.source.x + Math.cos(angle) * 35, y: this.source.y + Math.sin(angle) * 35, radius: 3 },
|
||||||
|
emitDuration: duration,
|
||||||
|
count: 50,
|
||||||
|
lifetime: lifetime,
|
||||||
|
direction: { minangle: angle, maxangle: angle },
|
||||||
|
scale: { min: 1, max: 1 },
|
||||||
|
speed: { min: speed, max: speed },
|
||||||
|
facing: ParticleFacingMode.ALWAYS
|
||||||
|
});
|
||||||
|
|
||||||
return 1000;
|
return lifetime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,11 @@ module TK.SpaceTac.UI {
|
||||||
* Draw the portrait (anchored at the center)
|
* Draw the portrait (anchored at the center)
|
||||||
*/
|
*/
|
||||||
draw(builder: UIBuilder, x: number, y: number, onselect: () => void): UIButton {
|
draw(builder: UIBuilder, x: number, y: number, onselect: () => void): UIButton {
|
||||||
let button = builder.button("character-portrait", x, y, onselect, this.ship.getName(), identity);
|
let button = builder.button("character-portrait", x, y, onselect, this.ship.getName(), identity, { center: true });
|
||||||
button.anchor.set(0.5);
|
|
||||||
|
|
||||||
builder.in(button, builder => {
|
builder.in(button, builder => {
|
||||||
let portrait = builder.image(`ship-${this.ship.model.code}-portrait`, 0, 0, true);
|
let portrait = builder.image(`ship-${this.ship.model.code}-portrait`, 0, 0, true);
|
||||||
portrait.scale.set(0.5);
|
portrait.setScale(0.5);
|
||||||
});
|
});
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
|
|
|
@ -9,7 +9,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.patch(view, "getWidth", () => 1240);
|
check.patch(view, "getWidth", () => 1240);
|
||||||
let sheet = new CharacterSheet(view, CharacterSheetMode.DISPLAY);
|
let sheet = new CharacterSheet(view, CharacterSheetMode.DISPLAY);
|
||||||
|
|
||||||
check.equals(sheet.x, -1240);
|
check.equals(sheet.container.x, -1240);
|
||||||
|
|
||||||
let fleet = new Fleet();
|
let fleet = new Fleet();
|
||||||
let ship1 = fleet.addShip();
|
let ship1 = fleet.addShip();
|
||||||
|
@ -19,13 +19,13 @@ module TK.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
sheet.show(ship1, false);
|
sheet.show(ship1, false);
|
||||||
|
|
||||||
check.equals(sheet.x, 0);
|
check.equals(sheet.container.x, 0);
|
||||||
check.equals(sheet.group_portraits.length, 2);
|
check.equals(sheet.group_portraits.length, 2);
|
||||||
|
|
||||||
check.equals(sheet.text_name && sheet.text_name.text, "Ship 1");
|
check.equals(sheet.text_name && sheet.text_name.text, "Ship 1");
|
||||||
|
|
||||||
let portrait = as(Phaser.Button, sheet.group_portraits.getChildAt(1));
|
let portrait = as(UIButton, sheet.group_portraits.getAt(1));
|
||||||
portrait.onInputUp.dispatch();
|
portrait.emit("pointerup");
|
||||||
|
|
||||||
check.equals(sheet.text_name && sheet.text_name.text, "Ship 2");
|
check.equals(sheet.text_name && sheet.text_name.text, "Ship 2");
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Character sheet, displaying ship characteristics
|
* Character sheet, displaying ship characteristics
|
||||||
*/
|
*/
|
||||||
export class CharacterSheet extends Phaser.Image {
|
export class CharacterSheet {
|
||||||
// Global sheet mode
|
// Global sheet mode
|
||||||
mode: CharacterSheetMode
|
mode: CharacterSheetMode
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ module TK.SpaceTac.UI {
|
||||||
view: BaseView
|
view: BaseView
|
||||||
|
|
||||||
// UI components builder
|
// UI components builder
|
||||||
|
container: UIContainer
|
||||||
builder: UIBuilder
|
builder: UIBuilder
|
||||||
|
|
||||||
// Close/validate button
|
// Close/validate button
|
||||||
|
@ -26,11 +27,11 @@ module TK.SpaceTac.UI {
|
||||||
xhidden = -2000
|
xhidden = -2000
|
||||||
|
|
||||||
// Groups
|
// Groups
|
||||||
group_level: Phaser.Group
|
group_level: UIContainer
|
||||||
group_portraits: Phaser.Group
|
group_portraits: UIContainer
|
||||||
group_attributes: Phaser.Image
|
group_attributes: UIContainer
|
||||||
group_actions: Phaser.Image
|
group_actions: UIContainer
|
||||||
group_upgrades: Phaser.Group
|
group_upgrades: UIContainer
|
||||||
|
|
||||||
// Currently displayed fleet
|
// Currently displayed fleet
|
||||||
fleet?: Fleet
|
fleet?: Fleet
|
||||||
|
@ -40,34 +41,40 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// Variable data
|
// Variable data
|
||||||
personality?: CharacterPersonality
|
personality?: CharacterPersonality
|
||||||
image_portrait: Phaser.Image
|
image_portrait: UIImage
|
||||||
text_model: Phaser.Text
|
text_model: UIText
|
||||||
text_description: Phaser.Text
|
text_description: UIText
|
||||||
text_name?: Phaser.Text
|
text_name?: UIText
|
||||||
text_level: Phaser.Text
|
text_level: UIText
|
||||||
text_upgrade_points: Phaser.Text
|
text_upgrade_points: UIText
|
||||||
valuebar_experience: ValueBar
|
valuebar_experience: ValueBar
|
||||||
|
|
||||||
constructor(view: BaseView, mode: CharacterSheetMode, onclose?: Function) {
|
constructor(view: BaseView, mode: CharacterSheetMode, onclose?: Function) {
|
||||||
super(view.game, 0, 0, view.getImageInfo("character-sheet").key, view.getImageInfo("character-sheet").frame);
|
this.view = view;
|
||||||
|
this.mode = mode;
|
||||||
|
|
||||||
|
let builder = new UIBuilder(view);
|
||||||
|
this.container = builder.container("character-sheet");
|
||||||
|
|
||||||
|
builder = builder.in(this.container);
|
||||||
|
let bg = builder.image("character-sheet");
|
||||||
|
bg.setInteractive();
|
||||||
|
|
||||||
|
this.builder = builder.styled({ color: "#dce9f9", size: 16, shadow: true });
|
||||||
|
|
||||||
|
|
||||||
if (!onclose) {
|
if (!onclose) {
|
||||||
onclose = () => this.hide();
|
onclose = () => this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.view = view;
|
|
||||||
this.mode = mode;
|
|
||||||
this.builder = new UIBuilder(view, this).styled({ color: "#dce9f9", size: 16, shadow: true });
|
|
||||||
|
|
||||||
this.xhidden = -this.view.getWidth();
|
this.xhidden = -this.view.getWidth();
|
||||||
this.x = this.xhidden;
|
this.container.x = this.xhidden;
|
||||||
this.inputEnabled = true;
|
|
||||||
|
|
||||||
this.image_portrait = this.builder.image("translucent", 435, 271, true);
|
this.image_portrait = this.builder.image("translucent", 435, 271, true);
|
||||||
|
|
||||||
this.builder.image("character-entry", 24, 740);
|
this.builder.image("character-entry", 24, 740);
|
||||||
|
|
||||||
this.group_portraits = this.builder.group("portraits", 90, 755);
|
this.group_portraits = this.builder.container("portraits", 90, 755);
|
||||||
|
|
||||||
let model_bg = this.builder.image("character-ship-model", 434, 500, true);
|
let model_bg = this.builder.image("character-ship-model", 434, 500, true);
|
||||||
this.text_model = this.builder.in(model_bg).text("", 0, 0, { size: 28 });
|
this.text_model = this.builder.in(model_bg).text("", 0, 0, { size: 28 });
|
||||||
|
@ -75,10 +82,10 @@ module TK.SpaceTac.UI {
|
||||||
let description_bg = this.builder.image("character-ship-description", 434, 654, true);
|
let description_bg = this.builder.image("character-ship-description", 434, 654, true);
|
||||||
this.text_description = this.builder.in(description_bg).text("", 0, 0, { color: "#a0afc3", width: 510 });
|
this.text_description = this.builder.in(description_bg).text("", 0, 0, { color: "#a0afc3", width: 510 });
|
||||||
|
|
||||||
this.group_attributes = this.builder.image("character-ship-column-left", 28, 28);
|
this.group_attributes = this.builder.container("attributes", 28, 28);
|
||||||
this.group_actions = this.builder.image("character-ship-column-right", 698, 28);
|
this.group_actions = this.builder.container("actions", 698, 28);
|
||||||
|
|
||||||
this.group_level = this.builder.group("level");
|
this.group_level = this.builder.container("level");
|
||||||
let points_bg = this.builder.in(this.group_level).image("character-level-upgrades", 582, 986);
|
let points_bg = this.builder.in(this.group_level).image("character-level-upgrades", 582, 986);
|
||||||
this.builder.in(points_bg, builder => {
|
this.builder.in(points_bg, builder => {
|
||||||
builder.text("Upgrade points", 46, 10, { center: false, vcenter: false });
|
builder.text("Upgrade points", 46, 10, { center: false, vcenter: false });
|
||||||
|
@ -90,7 +97,7 @@ module TK.SpaceTac.UI {
|
||||||
this.text_level = this.builder.in(level_bg).text("", 0, 4, { size: 28 });
|
this.text_level = this.builder.in(level_bg).text("", 0, 4, { size: 28 });
|
||||||
this.valuebar_experience = this.builder.in(level_bg).valuebar("character-level-experience", -level_bg.width * 0.5, -level_bg.height * 0.5);
|
this.valuebar_experience = this.builder.in(level_bg).valuebar("character-level-experience", -level_bg.width * 0.5, -level_bg.height * 0.5);
|
||||||
|
|
||||||
this.group_upgrades = this.builder.group("upgrades");
|
this.group_upgrades = this.builder.container("upgrades");
|
||||||
|
|
||||||
if (this.mode == CharacterSheetMode.CREATION) {
|
if (this.mode == CharacterSheetMode.CREATION) {
|
||||||
this.builder.in(this.builder.image("character-section-title", 180, 30, false)).text("Ship", 80, 45, { color: "#dce9f9", size: 32 });
|
this.builder.in(this.builder.image("character-section-title", 180, 30, false)).text("Ship", 80, 45, { color: "#dce9f9", size: 32 });
|
||||||
|
@ -120,8 +127,7 @@ module TK.SpaceTac.UI {
|
||||||
} else {
|
} else {
|
||||||
this.text_name = this.builder.in(this.builder.image("character-name-display", 434, 940, true)).text("", 0, 0, { size: 28 });
|
this.text_name = this.builder.in(this.builder.image("character-name-display", 434, 940, true)).text("", 0, 0, { size: 28 });
|
||||||
|
|
||||||
this.close_button = this.builder.button("character-close-button", 1920, 0, onclose, "Close the character sheet");
|
this.close_button = this.builder.button("character-close-button", 1837, 0, onclose, "Close the character sheet");
|
||||||
this.close_button.anchor.set(1, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshUpgrades();
|
this.refreshUpgrades();
|
||||||
|
@ -129,6 +135,13 @@ module TK.SpaceTac.UI {
|
||||||
this.refreshActions();
|
this.refreshActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the sheet to a specific layer
|
||||||
|
*/
|
||||||
|
moveToLayer(layer: UIContainer): void {
|
||||||
|
layer.add(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the sheet should be interactive
|
* Check if the sheet should be interactive
|
||||||
*/
|
*/
|
||||||
|
@ -194,7 +207,7 @@ module TK.SpaceTac.UI {
|
||||||
builder.styled({ center: false, vcenter: false }).in(initial, builder => {
|
builder.styled({ center: false, vcenter: false }).in(initial, builder => {
|
||||||
builder.text("Base equipment", 32, 8, { color: "#e2e9d1" });
|
builder.text("Base equipment", 32, 8, { color: "#e2e9d1" });
|
||||||
|
|
||||||
builder.in(builder.group("attributes"), builder => {
|
builder.in(builder.container("attributes"), builder => {
|
||||||
let effects = cfilter(ship.model.getEffects(1, []), AttributeEffect);
|
let effects = cfilter(ship.model.getEffects(1, []), AttributeEffect);
|
||||||
effects.forEach(effect => {
|
effects.forEach(effect => {
|
||||||
let button = builder.button(`attribute-${effect.attrcode}`, 0, 8, undefined,
|
let button = builder.button(`attribute-${effect.attrcode}`, 0, 8, undefined,
|
||||||
|
@ -207,14 +220,14 @@ module TK.SpaceTac.UI {
|
||||||
builder.distribute("x", 236, 870);
|
builder.distribute("x", 236, 870);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.in(builder.group("actions"), builder => {
|
builder.in(builder.container("actions"), builder => {
|
||||||
let actions = ship.model.getActions(1, []);
|
let actions = ship.model.getActions(1, []);
|
||||||
actions.forEach(action => {
|
actions.forEach(action => {
|
||||||
let button = builder.button("translucent", 0, 66, undefined, action.getEffectsDescription());
|
let button = builder.button("translucent", 0, 66, undefined, action.getEffectsDescription());
|
||||||
|
|
||||||
builder.in(button, builder => {
|
builder.in(button, builder => {
|
||||||
let icon = builder.image(`action-${action.code}`);
|
let icon = builder.image(`action-${action.code}`);
|
||||||
icon.scale.set(0.1875);
|
icon.setScale(0.1875);
|
||||||
if (actions.length < 5) {
|
if (actions.length < 5) {
|
||||||
builder.text(`${action.name}`, 56, 12, { size: 16 });
|
builder.text(`${action.name}`, 56, 12, { size: 16 });
|
||||||
}
|
}
|
||||||
|
@ -259,11 +272,13 @@ module TK.SpaceTac.UI {
|
||||||
let builder = this.builder.in(this.group_attributes);
|
let builder = this.builder.in(this.group_attributes);
|
||||||
builder.clear();
|
builder.clear();
|
||||||
|
|
||||||
|
builder.image("character-ship-column-left", 0, 0);
|
||||||
|
|
||||||
builder.text("Attributes", 74, 20, { color: "#a3bbd9" });
|
builder.text("Attributes", 74, 20, { color: "#a3bbd9" });
|
||||||
|
|
||||||
if (this.ship) {
|
if (this.ship) {
|
||||||
let ship = this.ship;
|
let ship = this.ship;
|
||||||
builder.in(builder.group("items"), builder => {
|
builder.in(builder.container("items"), builder => {
|
||||||
keys(SHIP_ATTRIBUTES).forEach(attribute => {
|
keys(SHIP_ATTRIBUTES).forEach(attribute => {
|
||||||
let button = builder.button(`attribute-${attribute}`, 24, 0, undefined,
|
let button = builder.button(`attribute-${attribute}`, 24, 0, undefined,
|
||||||
ship.getAttributeDescription(attribute));
|
ship.getAttributeDescription(attribute));
|
||||||
|
@ -282,16 +297,18 @@ module TK.SpaceTac.UI {
|
||||||
let builder = this.builder.in(this.group_actions);
|
let builder = this.builder.in(this.group_actions);
|
||||||
builder.clear();
|
builder.clear();
|
||||||
|
|
||||||
|
builder.image("character-ship-column-right", 0, 0);
|
||||||
|
|
||||||
builder.text("Actions", 74, 20, { color: "#a3bbd9" });
|
builder.text("Actions", 74, 20, { color: "#a3bbd9" });
|
||||||
|
|
||||||
if (this.ship) {
|
if (this.ship) {
|
||||||
let ship = this.ship;
|
let ship = this.ship;
|
||||||
builder.in(builder.group("items"), builder => {
|
builder.in(builder.container("items"), builder => {
|
||||||
let actions = ship.actions.listAll().filter(action => !(action instanceof EndTurnAction));
|
let actions = ship.actions.listAll().filter(action => !(action instanceof EndTurnAction));
|
||||||
actions.forEach(action => {
|
actions.forEach(action => {
|
||||||
let button = builder.button(`action-${action.code}`, 24, 0, undefined,
|
let button = builder.button(`action-${action.code}`, 24, 0, undefined,
|
||||||
action.getEffectsDescription());
|
action.getEffectsDescription());
|
||||||
button.scale.set(0.375);
|
button.setScale(0.375);
|
||||||
});
|
});
|
||||||
builder.distribute("y", 40, 688);
|
builder.distribute("y", 40, 688);
|
||||||
});
|
});
|
||||||
|
@ -310,7 +327,7 @@ module TK.SpaceTac.UI {
|
||||||
let button: UIButton;
|
let button: UIButton;
|
||||||
button = new CharacterPortrait(ship).draw(builder, 64 + idx * 140, 64, () => {
|
button = new CharacterPortrait(ship).draw(builder, 64 + idx * 140, 64, () => {
|
||||||
if (button) {
|
if (button) {
|
||||||
builder.select(button);
|
button.toggle(true, UIButtonUnicity.EXCLUSIVE_MIN);
|
||||||
this.ship = ship;
|
this.ship = ship;
|
||||||
|
|
||||||
this.refreshShipInfo();
|
this.refreshShipInfo();
|
||||||
|
@ -321,7 +338,7 @@ module TK.SpaceTac.UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ship == this.ship) {
|
if (ship == this.ship) {
|
||||||
builder.switch(button, true);
|
button.toggle(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -331,7 +348,7 @@ module TK.SpaceTac.UI {
|
||||||
* Check if the sheet is shown
|
* Check if the sheet is shown
|
||||||
*/
|
*/
|
||||||
isOpened(): boolean {
|
isOpened(): boolean {
|
||||||
return this.x != this.xhidden;
|
return this.container.x != this.xhidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -355,9 +372,14 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
this.game.tweens.create(this).to({ x: this.xshown }, 400, Phaser.Easing.Circular.InOut, true);
|
this.view.tweens.add({
|
||||||
|
targets: this.container,
|
||||||
|
x: this.xshown,
|
||||||
|
duration: 400,
|
||||||
|
easing: 'Circ.easeInOut'
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.x = this.xshown;
|
this.container.x = this.xshown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,9 +390,14 @@ module TK.SpaceTac.UI {
|
||||||
this.view.audio.playOnce("ui-dialog-close");
|
this.view.audio.playOnce("ui-dialog-close");
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
this.game.tweens.create(this).to({ x: this.xhidden }, 400, Phaser.Easing.Circular.InOut, true);
|
this.view.tweens.add({
|
||||||
|
targets: this.container,
|
||||||
|
x: this.xhidden,
|
||||||
|
duration: 400,
|
||||||
|
ease: 'Circ.easeInOut'
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.x = this.xhidden;
|
this.container.x = this.xhidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
module TK.SpaceTac.UI.Specs {
|
module TK.SpaceTac.UI.Specs {
|
||||||
testing("CharacterUpgrade", test => {
|
testing("CharacterUpgrade", test => {
|
||||||
let testgame = setupSingleView(test, () => [new BaseView(), []]);
|
let testgame = setupEmptyView(test);
|
||||||
|
|
||||||
test.acase("fills tooltip content", async check => {
|
test.acase("fills tooltip content", async check => {
|
||||||
let ship = new Ship();
|
let ship = new Ship();
|
||||||
|
@ -19,7 +19,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let tooltip = new TooltipContainer(testgame.view);
|
let tooltip = new TooltipContainer(testgame.view);
|
||||||
let builder = new TooltipBuilder(tooltip);
|
let builder = new TooltipBuilder(tooltip);
|
||||||
display.fillTooltip(builder);
|
display.fillTooltip(builder);
|
||||||
check.equals(cfilter(tooltip.content.children, Phaser.Text).map(child => child.text), [
|
check.equals(collectTexts(tooltip.content), [
|
||||||
"Test Upgrade",
|
"Test Upgrade",
|
||||||
"Permanent effects:",
|
"Permanent effects:",
|
||||||
"• hull capacity +10",
|
"• hull capacity +10",
|
||||||
|
@ -35,7 +35,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
display.fillTooltip(builder);
|
display.fillTooltip(builder);
|
||||||
check.equals(cfilter(tooltip.content.children, Phaser.Text).map(child => child.text), [
|
check.equals(collectTexts(tooltip.content), [
|
||||||
"Test Upgrade",
|
"Test Upgrade",
|
||||||
"Fire (power 1, range 50km):",
|
"Fire (power 1, range 50km):",
|
||||||
"• do 10 damage on target",
|
"• do 10 damage on target",
|
||||||
|
|
|
@ -21,7 +21,7 @@ module TK.SpaceTac.UI {
|
||||||
let button = builder.button("character-upgrade", x, y, undefined, tooltip, selector);
|
let button = builder.button("character-upgrade", x, y, undefined, tooltip, selector);
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
builder.switch(button, true);
|
button.toggle(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.in(button, builder => {
|
builder.in(button, builder => {
|
||||||
|
@ -30,7 +30,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
let icon = builder.image(this.getIcon(), 40, 40, true);
|
let icon = builder.image(this.getIcon(), 40, 40, true);
|
||||||
if (icon.width && icon.width > 64) {
|
if (icon.width && icon.width > 64) {
|
||||||
icon.scale.set(64 / icon.width);
|
icon.setScale(64 / icon.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
range(this.upgrade.cost || 0).forEach(i => {
|
range(this.upgrade.cost || 0).forEach(i => {
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
|
|
||||||
module TK.SpaceTac.UI.Specs {
|
module TK.SpaceTac.UI.Specs {
|
||||||
testing("FleetCreationView", test => {
|
testing("FleetCreationView", test => {
|
||||||
let testgame = setupSingleView(test, () => [new FleetCreationView, []]);
|
let testgame = setupSingleView(test, () => [new FleetCreationView({}), []]);
|
||||||
|
|
||||||
test.acase("validates the fleet creation", async check => {
|
test.acase("validates the fleet creation", async check => {
|
||||||
|
let mock_router = check.patch(testgame.view, "backToRouter");
|
||||||
|
|
||||||
check.same(testgame.ui.session.isFleetCreated(), false, "no fleet created");
|
check.same(testgame.ui.session.isFleetCreated(), false, "no fleet created");
|
||||||
check.same(testgame.ui.session.player.fleet.ships.length, 0, "empty session fleet");
|
check.same(testgame.ui.session.player.fleet.ships.length, 0, "empty session fleet");
|
||||||
check.same(testgame.view.dialogs_layer.children.length, 0, "no dialogs");
|
check.same(testgame.view.dialogs_layer.length, 0, "no dialogs");
|
||||||
check.same(testgame.view.character_sheet.fleet, testgame.view.built_fleet);
|
check.same(testgame.view.character_sheet.fleet, testgame.view.built_fleet);
|
||||||
check.same(testgame.view.built_fleet.ships.length, 2, "initial fleet should have two ships");
|
check.same(testgame.view.built_fleet.ships.length, 2, "initial fleet should have two ships");
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
await dialog.forceResult(false);
|
await dialog.forceResult(false);
|
||||||
check.same(testgame.view.dialogs_opened.length, 0, "confirmation dialog destroyed after 'no'");
|
check.same(testgame.view.dialogs_opened.length, 0, "confirmation dialog destroyed after 'no'");
|
||||||
check.same(testgame.ui.session.isFleetCreated(), false, "still no fleet created after 'no'");
|
check.same(testgame.ui.session.isFleetCreated(), false, "still no fleet created after 'no'");
|
||||||
check.equals(testgame.state, "test_initial");
|
check.called(mock_router, 0);
|
||||||
|
|
||||||
// close sheet, click on yes in confirmation dialog
|
// close sheet, click on yes in confirmation dialog
|
||||||
testClick(testgame.view.character_sheet.close_button);
|
testClick(testgame.view.character_sheet.close_button);
|
||||||
|
@ -30,7 +32,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.same(testgame.view.dialogs_opened.length, 0, "confirmation dialog destroyed after 'yes'");
|
check.same(testgame.view.dialogs_opened.length, 0, "confirmation dialog destroyed after 'yes'");
|
||||||
check.same(testgame.ui.session.isFleetCreated(), true, "fleet created");
|
check.same(testgame.ui.session.isFleetCreated(), true, "fleet created");
|
||||||
check.same(testgame.ui.session.player.fleet.ships.length, 2, "session fleet now has two ships");
|
check.same(testgame.ui.session.player.fleet.ships.length, 2, "session fleet now has two ships");
|
||||||
check.equals(testgame.state, "router");
|
check.called(mock_router, 1);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.CREATION, () => this.validateFleet());
|
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.CREATION, () => this.validateFleet());
|
||||||
this.character_sheet.show(this.built_fleet.ships[0], false);
|
this.character_sheet.show(this.built_fleet.ships[0], false);
|
||||||
this.getLayer("characters").add(this.character_sheet);
|
this.character_sheet.moveToLayer(this.getLayer("characters"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -55,15 +55,15 @@ module TK.SpaceTac.UI.Specs {
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("animates rotation", check => {
|
test.case("animates rotation", check => {
|
||||||
let obj = { rotation: -Math.PI * 2.5 };
|
let obj = new UIBuilder(testgame.view).image("test");
|
||||||
let tween = testgame.ui.tweens.create(obj);
|
obj.setRotation(-Math.PI * 2.5);
|
||||||
let result = Animations.rotationTween(tween, Math.PI * 0.25, 1, Phaser.Easing.Linear.None);
|
let result = testgame.view.animations.rotationTween(obj, Math.PI * 0.25, 1, "Linear");
|
||||||
check.equals(result, 750);
|
check.equals(result, 750);
|
||||||
check.equals(tween.generateData(4), [
|
let points = testgame.view.animations.simulate(obj, "rotation", 4);
|
||||||
{ rotation: -Math.PI * 0.25 },
|
check.nears(points[0], -Math.PI * 0.5);
|
||||||
{ rotation: 0 },
|
check.nears(points[1], -Math.PI * 0.25);
|
||||||
{ rotation: Math.PI * 0.25 },
|
check.nears(points[2], 0);
|
||||||
]);
|
check.nears(points[3], Math.PI * 0.25);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ module TK.SpaceTac.UI {
|
||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
rotation: number
|
rotation: number
|
||||||
game: Phaser.Game
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,10 +22,10 @@ module TK.SpaceTac.UI {
|
||||||
* This is a wrapper around phaser's tweens.
|
* This is a wrapper around phaser's tweens.
|
||||||
*/
|
*/
|
||||||
export class Animations {
|
export class Animations {
|
||||||
private tweens: Phaser.TweenManager
|
private tweens: Phaser.Tweens.TweenManager
|
||||||
private immediate = false
|
private immediate = false
|
||||||
|
|
||||||
constructor(tweens: Phaser.TweenManager) {
|
constructor(tweens: Phaser.Tweens.TweenManager) {
|
||||||
this.tweens = tweens;
|
this.tweens = tweens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,14 +39,11 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a tween on an object.
|
* Kill previous tweens from an object
|
||||||
*
|
|
||||||
* If a previous tween is running for this object, it will be stopped, and a new one will be created.
|
|
||||||
*/
|
*/
|
||||||
private createTween(obj: any): Phaser.Tween {
|
killPrevious(obj: object): void {
|
||||||
this.tweens.removeFrom(obj, false);
|
// TODO Only updated properties
|
||||||
let result = this.tweens.create(obj);
|
this.tweens.killTweensOf(obj);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,10 +51,16 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* This may be heavy work and should only be done in testing code.
|
* This may be heavy work and should only be done in testing code.
|
||||||
*/
|
*/
|
||||||
simulate(obj: any, property: string, points = 5, duration = 1000): number[] {
|
simulate(obj: any, property: string, points = 5): number[] {
|
||||||
let tween = first(this.tweens.getAll().concat((<any>this.tweens)._add), tween => tween.target === obj && !tween.pendingDelete);
|
this.tweens.preUpdate();
|
||||||
|
let tween = first(this.tweens.getTweensOf(obj), tween => tween.isPlaying());
|
||||||
if (tween) {
|
if (tween) {
|
||||||
return [obj[property]].concat(tween.generateData(1000 * (points - 1) / duration).map(data => data[property]));
|
let tween_obj = tween;
|
||||||
|
tween_obj.update(0, 0);
|
||||||
|
return range(points).map(i => {
|
||||||
|
tween_obj.seek(i / (points - 1));
|
||||||
|
return obj[property];
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -68,28 +70,33 @@ module TK.SpaceTac.UI {
|
||||||
* Display an object, with opacity transition
|
* Display an object, with opacity transition
|
||||||
*/
|
*/
|
||||||
show(obj: IAnimationFadeable, duration = 1000, alpha = 1): void {
|
show(obj: IAnimationFadeable, duration = 1000, alpha = 1): void {
|
||||||
|
this.killPrevious(obj);
|
||||||
|
|
||||||
if (!obj.visible) {
|
if (!obj.visible) {
|
||||||
obj.alpha = 0;
|
obj.alpha = 0;
|
||||||
obj.visible = true;
|
obj.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duration && !this.immediate) {
|
let onComplete: Function | undefined;
|
||||||
let tween = this.createTween(obj);
|
if (obj.input) {
|
||||||
tween.to({ alpha: alpha }, duration);
|
let input = obj.input;
|
||||||
if (obj.input) {
|
onComplete = () => {
|
||||||
let input = obj.input;
|
input.enabled = true
|
||||||
tween.onComplete.addOnce(() => {
|
|
||||||
input.enabled = true
|
|
||||||
obj.freezeFrames = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
tween.start();
|
|
||||||
} else {
|
|
||||||
this.tweens.removeFrom(obj, false);
|
|
||||||
obj.alpha = alpha;
|
|
||||||
if (obj.input) {
|
|
||||||
obj.input.enabled = true;
|
|
||||||
obj.freezeFrames = false;
|
obj.freezeFrames = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration && !this.immediate) {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: obj,
|
||||||
|
alpha: alpha,
|
||||||
|
duration: duration,
|
||||||
|
onComplete: onComplete
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
obj.alpha = alpha;
|
||||||
|
if (onComplete) {
|
||||||
|
onComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,6 +105,8 @@ module TK.SpaceTac.UI {
|
||||||
* Hide an object, with opacity transition
|
* Hide an object, with opacity transition
|
||||||
*/
|
*/
|
||||||
hide(obj: IAnimationFadeable, duration = 1000, alpha = 0): void {
|
hide(obj: IAnimationFadeable, duration = 1000, alpha = 0): void {
|
||||||
|
this.killPrevious(obj);
|
||||||
|
|
||||||
if (obj.changeStateFrame) {
|
if (obj.changeStateFrame) {
|
||||||
obj.changeStateFrame("Out");
|
obj.changeStateFrame("Out");
|
||||||
obj.freezeFrames = true;
|
obj.freezeFrames = true;
|
||||||
|
@ -107,16 +116,18 @@ module TK.SpaceTac.UI {
|
||||||
obj.input.enabled = false;
|
obj.input.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let onComplete = () => obj.visible = alpha > 0;
|
||||||
|
|
||||||
if (duration && !this.immediate) {
|
if (duration && !this.immediate) {
|
||||||
let tween = this.createTween(obj);
|
this.tweens.add({
|
||||||
tween.to({ alpha: alpha }, duration);
|
targets: obj,
|
||||||
if (alpha == 0) {
|
alpha: alpha,
|
||||||
tween.onComplete.addOnce(() => obj.visible = false);
|
duration: duration,
|
||||||
}
|
onComplete: onComplete
|
||||||
tween.start();
|
});
|
||||||
} else {
|
} else {
|
||||||
obj.alpha = alpha;
|
obj.alpha = alpha;
|
||||||
obj.visible = alpha > 0;
|
onComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,15 +154,19 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Add an asynchronous animation to an object.
|
* Add an asynchronous animation to an object.
|
||||||
*/
|
*/
|
||||||
addAnimation(obj: any, properties: any, duration: number, ease: Function = Phaser.Easing.Linear.None, delay = 0): Promise<void> {
|
addAnimation<T extends object>(obj: T, properties: Partial<T>, duration: number, ease = "Linear", delay = 0, loop = 1, yoyo = false): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let tween = this.createTween(obj);
|
this.killPrevious(obj);
|
||||||
tween.to(properties, duration, ease, false, delay);
|
|
||||||
tween.onComplete.addOnce(() => {
|
this.tweens.add(merge<object>({
|
||||||
this.tweens.remove(tween);
|
targets: obj,
|
||||||
resolve();
|
ease: ease,
|
||||||
});
|
duration: duration,
|
||||||
tween.start();
|
delay: delay,
|
||||||
|
loop: loop - 1,
|
||||||
|
onComplete: resolve,
|
||||||
|
yoyo: yoyo
|
||||||
|
}, properties));
|
||||||
|
|
||||||
// By security, if the tween is destroyed before completion, we resolve the promise using the timer
|
// By security, if the tween is destroyed before completion, we resolve the promise using the timer
|
||||||
Timer.global.schedule(delay + duration, resolve);
|
Timer.global.schedule(delay + duration, resolve);
|
||||||
|
@ -178,10 +193,10 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* Returns the duration
|
* Returns the duration
|
||||||
*/
|
*/
|
||||||
static rotationTween(tween: Phaser.Tween, dest: number, speed = 1, easing = Phaser.Easing.Cubic.InOut, property = "rotation"): number {
|
rotationTween(obj: Phaser.GameObjects.Components.Transform, dest: number, speed = 1, easing = "Cubic.easeInOut"): 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 = UITools.normalizeAngle(tween.target[property]);
|
let value = UITools.normalizeAngle(obj.rotation);
|
||||||
tween.target[property] = value;
|
obj.setRotation(value);
|
||||||
|
|
||||||
// Compute destination angle
|
// Compute destination angle
|
||||||
dest = UITools.normalizeAngle(dest);
|
dest = UITools.normalizeAngle(dest);
|
||||||
|
@ -193,10 +208,8 @@ module TK.SpaceTac.UI {
|
||||||
let distance = Math.abs(UITools.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
|
// Tween
|
||||||
let changes: any = {};
|
this.addAnimation(obj, { rotation: dest }, duration, easing);
|
||||||
changes[property] = dest;
|
|
||||||
tween.to(changes, duration, easing);
|
|
||||||
|
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
@ -206,15 +219,10 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* Returns the animation duration.
|
* Returns the animation duration.
|
||||||
*/
|
*/
|
||||||
static moveTo(obj: PhaserGraphics, x: number, y: number, angle: number, rotated_obj = obj, ease = true): number {
|
moveTo(obj: Phaser.GameObjects.Components.Transform, x: number, y: number, angle: number, rotated_obj = obj, ease = true): number {
|
||||||
let tween_rot = obj.game.tweens.create(rotated_obj);
|
let duration_rot = this.rotationTween(rotated_obj, angle, 0.5);
|
||||||
let duration_rot = Animations.rotationTween(tween_rot, angle, 0.5);
|
|
||||||
let tween_pos = obj.game.tweens.create(obj);
|
|
||||||
let duration_pos = arenaDistance(obj, { x: x, y: y }) * 2;
|
let duration_pos = arenaDistance(obj, { x: x, y: y }) * 2;
|
||||||
tween_pos.to({ x: x, y: y }, duration_pos, ease ? Phaser.Easing.Quadratic.InOut : undefined);
|
this.addAnimation(obj, { x: x, y: y }, duration_pos, ease ? "Quad.easeInOut" : "Linear");
|
||||||
|
|
||||||
tween_rot.start();
|
|
||||||
tween_pos.start();
|
|
||||||
return Math.max(duration_rot, duration_pos);
|
return Math.max(duration_rot, duration_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,32 +231,38 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* Returns the animation duration.
|
* Returns the animation duration.
|
||||||
*/
|
*/
|
||||||
static moveInSpace(obj: PhaserGraphics, x: number, y: number, angle: number, rotated_obj = obj): number {
|
moveInSpace(obj: Phaser.GameObjects.Components.Transform, x: number, y: number, angle: number, rotated_obj = obj): number {
|
||||||
if (x == obj.x && y == obj.y) {
|
if (x == obj.x && y == obj.y) {
|
||||||
let tween = obj.game.tweens.create(rotated_obj);
|
this.killPrevious(obj);
|
||||||
let duration = Animations.rotationTween(tween, angle, 0.5);
|
return this.rotationTween(rotated_obj, angle, 0.5);
|
||||||
tween.start();
|
|
||||||
return duration;
|
|
||||||
} else {
|
} else {
|
||||||
|
this.killPrevious(obj);
|
||||||
|
this.killPrevious(rotated_obj);
|
||||||
let distance = Target.newFromLocation(obj.x, obj.y).getDistanceTo(Target.newFromLocation(x, y));
|
let distance = Target.newFromLocation(obj.x, obj.y).getDistanceTo(Target.newFromLocation(x, y));
|
||||||
var tween = obj.game.tweens.create(obj);
|
|
||||||
let duration = Math.sqrt(distance / 1000) * 3000;
|
let duration = Math.sqrt(distance / 1000) * 3000;
|
||||||
let curve_force = distance * 0.4;
|
let curve_force = distance * 0.4;
|
||||||
tween.to({
|
|
||||||
x: [obj.x + Math.cos(rotated_obj.rotation) * curve_force, x - Math.cos(angle) * curve_force, x],
|
|
||||||
y: [obj.y + Math.sin(rotated_obj.rotation) * curve_force, y - Math.sin(angle) * curve_force, y]
|
|
||||||
}, duration, Phaser.Easing.Sinusoidal.InOut);
|
|
||||||
tween.interpolation((v: any, k: any) => Phaser.Math.bezierInterpolation(v, k));
|
|
||||||
let prevx = obj.x;
|
let prevx = obj.x;
|
||||||
let prevy = obj.y;
|
let prevy = obj.y;
|
||||||
tween.onUpdateCallback(() => {
|
let xpts = [obj.x, obj.x + Math.cos(rotated_obj.rotation) * curve_force, x - Math.cos(angle) * curve_force, x];
|
||||||
if (prevx != obj.x || prevy != obj.y) {
|
let ypts = [obj.y, obj.y + Math.sin(rotated_obj.rotation) * curve_force, y - Math.sin(angle) * curve_force, y];
|
||||||
rotated_obj.rotation = Math.atan2(obj.y - prevy, obj.x - prevx);
|
let fobj = { t: 0 };
|
||||||
|
this.tweens.add({
|
||||||
|
targets: [fobj],
|
||||||
|
t: 1,
|
||||||
|
duration: duration,
|
||||||
|
ease: "Sine.easeInOut",
|
||||||
|
onUpdate: () => {
|
||||||
|
obj.setPosition(
|
||||||
|
Phaser.Math.Interpolation.CubicBezier(fobj.t, xpts[0], xpts[1], xpts[2], xpts[3]),
|
||||||
|
Phaser.Math.Interpolation.CubicBezier(fobj.t, ypts[0], ypts[1], ypts[2], ypts[3]),
|
||||||
|
)
|
||||||
|
if (prevx != obj.x || prevy != obj.y) {
|
||||||
|
rotated_obj.setRotation(Math.atan2(obj.y - prevy, obj.x - prevx));
|
||||||
|
}
|
||||||
|
prevx = obj.x;
|
||||||
|
prevy = obj.y;
|
||||||
}
|
}
|
||||||
prevx = obj.x;
|
})
|
||||||
prevy = obj.y;
|
|
||||||
});
|
|
||||||
tween.start();
|
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,85 @@
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
// Utility functions for sounds
|
class AudioSettings {
|
||||||
|
main_volume = 1
|
||||||
|
music_volume = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions to play sounds and musics
|
||||||
|
*/
|
||||||
export class Audio {
|
export class Audio {
|
||||||
private game: MainUI
|
private static SETTINGS = new AudioSettings();
|
||||||
private music: Phaser.Sound | null = null
|
private music: Phaser.Sound.BaseSound | undefined
|
||||||
private music_volume = 1
|
|
||||||
private music_playing_volume = 1
|
private music_playing_volume = 1
|
||||||
|
|
||||||
constructor(game: MainUI) {
|
constructor(private view: BaseView | null) {
|
||||||
this.game = game;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the sound system is up and running
|
/**
|
||||||
isActive(): boolean {
|
* Check if the sound system is active, and return a manager to operate with it
|
||||||
return !this.game.headless && this.game.sound.context;
|
*/
|
||||||
|
private getManager(): Phaser.Sound.BaseSoundManager | null {
|
||||||
|
if (this.view) {
|
||||||
|
return this.view.sound;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play a ponctual sound
|
/**
|
||||||
|
* Check if an audio key is present in cache
|
||||||
|
*/
|
||||||
|
hasCache(key: string): boolean {
|
||||||
|
return this.view ? this.view.cache.audio.has(key) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a single sound effect (fire-and-forget)
|
||||||
|
*/
|
||||||
playOnce(key: string): void {
|
playOnce(key: string): void {
|
||||||
if (this.isActive()) {
|
let manager = this.getManager();
|
||||||
this.game.sound.play(key);
|
if (manager) {
|
||||||
|
if (this.hasCache(key)) {
|
||||||
|
manager.play(key);
|
||||||
|
} else {
|
||||||
|
console.warn("Missing sound", key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a background music
|
/**
|
||||||
|
* Start a background music in repeat
|
||||||
|
*/
|
||||||
startMusic(key: string, volume = 1): void {
|
startMusic(key: string, volume = 1): void {
|
||||||
key = "music-" + key;
|
let manager = this.getManager();
|
||||||
if (this.isActive()) {
|
if (manager) {
|
||||||
this.stopMusic();
|
this.stopMusic();
|
||||||
|
|
||||||
if (!this.music) {
|
if (!this.music) {
|
||||||
this.music_playing_volume = volume;
|
key = "music-" + key;
|
||||||
this.music = this.game.sound.play(key, volume * this.music_volume, true);
|
if (this.hasCache(key)) {
|
||||||
|
this.music_playing_volume = volume;
|
||||||
|
this.music = manager.add(key, {
|
||||||
|
volume: volume * Audio.SETTINGS.music_volume,
|
||||||
|
loop: true
|
||||||
|
});
|
||||||
|
this.music.play();
|
||||||
|
} else {
|
||||||
|
console.warn("Missing music", key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop currently playing background music
|
/**
|
||||||
|
* Stop currently playing background music
|
||||||
|
*/
|
||||||
stopMusic(): void {
|
stopMusic(): void {
|
||||||
if (this.isActive()) {
|
let music = this.music;
|
||||||
if (this.music) {
|
if (music) {
|
||||||
this.music.stop();
|
music.stop();
|
||||||
this.music = null;
|
music.destroy();
|
||||||
}
|
this.music = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,19 +87,18 @@ module TK.SpaceTac.UI {
|
||||||
* Get the main volume (0-1)
|
* Get the main volume (0-1)
|
||||||
*/
|
*/
|
||||||
getMainVolume(): number {
|
getMainVolume(): number {
|
||||||
if (this.isActive()) {
|
return Audio.SETTINGS.main_volume;
|
||||||
return this.game.sound.volume;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the main volume (0-1)
|
* Set the main volume (0-1)
|
||||||
*/
|
*/
|
||||||
setMainVolume(value: number) {
|
setMainVolume(value: number) {
|
||||||
if (this.isActive()) {
|
Audio.SETTINGS.main_volume = clamp(value, 0, 1);
|
||||||
this.game.sound.volume = clamp(value, 0, 1);
|
|
||||||
|
let manager = this.getManager();
|
||||||
|
if (manager) {
|
||||||
|
manager.volume = Audio.SETTINGS.main_volume;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,17 +106,22 @@ module TK.SpaceTac.UI {
|
||||||
* Get the music volume (0-1)
|
* Get the music volume (0-1)
|
||||||
*/
|
*/
|
||||||
getMusicVolume(): number {
|
getMusicVolume(): number {
|
||||||
return this.music_volume;
|
return Audio.SETTINGS.music_volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the music volume (0-1)
|
* Set the music volume (0-1)
|
||||||
*/
|
*/
|
||||||
setMusicVolume(value: number) {
|
setMusicVolume(value: number) {
|
||||||
this.music_volume = value;
|
Audio.SETTINGS.music_volume = clamp(value, 0, 1);
|
||||||
if (this.isActive()) {
|
|
||||||
if (this.music) {
|
let music = this.music;
|
||||||
this.music.volume = value * this.music_playing_volume;
|
if (music) {
|
||||||
|
// TODO Set music volume
|
||||||
|
if (value) {
|
||||||
|
music.resume();
|
||||||
|
} else {
|
||||||
|
music.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ module TK.SpaceTac.UI.Specs {
|
||||||
test.case("handles hover and click on desktops and mobile targets", check => {
|
test.case("handles hover and click on desktops and mobile targets", check => {
|
||||||
let inputs = testgame.view.inputs;
|
let inputs = testgame.view.inputs;
|
||||||
|
|
||||||
let pointer = new Phaser.Pointer(testgame.ui, 0);
|
let pointer = new Phaser.Input.Pointer(testgame.view.input.manager, 0);
|
||||||
function newButton(): [Phaser.Button, { enter: Mock<Function>, leave: Mock<Function>, click: Mock<Function> }] {
|
function newButton(): [UIImage, { enter: Mock<Function>, leave: Mock<Function>, click: Mock<Function> }] {
|
||||||
let button = new Phaser.Button(testgame.ui);
|
let button = new UIImage(testgame.view, 0, 0, "fake");
|
||||||
let mocks = {
|
let mocks = {
|
||||||
enter: check.mockfunc("enter"),
|
enter: check.mockfunc("enter"),
|
||||||
leave: check.mockfunc("leave"),
|
leave: check.mockfunc("leave"),
|
||||||
|
@ -18,118 +18,85 @@ module TK.SpaceTac.UI.Specs {
|
||||||
(<any>inputs).hovered = null;
|
(<any>inputs).hovered = null;
|
||||||
return [button, mocks];
|
return [button, mocks];
|
||||||
}
|
}
|
||||||
let enter = (button: Phaser.Button) => (<any>button.input)._pointerOverHandler(pointer);
|
let enter = (button: UIImage) => button.emit("pointerover", pointer);
|
||||||
let leave = (button: Phaser.Button) => (<any>button.input)._pointerOutHandler(pointer);
|
let leave = (button: UIImage) => button.emit("pointerout", pointer);
|
||||||
let press = (button: Phaser.Button) => button.onInputDown.dispatch(button, pointer);
|
let press = (button: UIImage) => button.emit("pointerdown", pointer);
|
||||||
let release = (button: Phaser.Button) => button.onInputUp.dispatch(button, pointer);
|
let release = (button: UIImage) => button.emit("pointerup", pointer);
|
||||||
let destroy = (button: Phaser.Button) => button.events.onDestroy.dispatch();
|
let destroy = (button: UIImage) => button.emit("destroy");
|
||||||
|
|
||||||
// Simple click on desktop
|
|
||||||
let [button, mocks] = newButton();
|
let [button, mocks] = newButton();
|
||||||
enter(button);
|
check.in("Simple click on desktop", check => {
|
||||||
press(button);
|
enter(button);
|
||||||
release(button);
|
press(button);
|
||||||
check.called(mocks.enter, 0);
|
release(button);
|
||||||
check.called(mocks.leave, 0);
|
check.called(mocks.enter, 0);
|
||||||
check.called(mocks.click, 1);
|
check.called(mocks.leave, 0);
|
||||||
|
check.called(mocks.click, 1);
|
||||||
|
});
|
||||||
|
|
||||||
// Simple click on mobile
|
|
||||||
[button, mocks] = newButton();
|
[button, mocks] = newButton();
|
||||||
press(button);
|
check.in("Simple click on mobile", check => {
|
||||||
release(button);
|
press(button);
|
||||||
check.called(mocks.enter, 1);
|
release(button);
|
||||||
check.called(mocks.leave, 1);
|
check.called(mocks.enter, 1);
|
||||||
check.called(mocks.click, 1);
|
check.called(mocks.leave, 1);
|
||||||
|
check.called(mocks.click, 1);
|
||||||
|
});
|
||||||
|
|
||||||
// Leaves on destroy
|
|
||||||
[button, mocks] = newButton();
|
[button, mocks] = newButton();
|
||||||
press(button);
|
check.in("Leaves on destroy", check => {
|
||||||
clock.forward(150);
|
press(button);
|
||||||
check.called(mocks.enter, 1);
|
clock.forward(150);
|
||||||
check.called(mocks.leave, 0);
|
check.called(mocks.enter, 1);
|
||||||
check.called(mocks.click, 0);
|
check.called(mocks.leave, 0);
|
||||||
destroy(button);
|
check.called(mocks.click, 0);
|
||||||
check.called(mocks.enter, 0);
|
destroy(button);
|
||||||
check.called(mocks.leave, 1);
|
check.called(mocks.enter, 0);
|
||||||
check.called(mocks.click, 0);
|
check.called(mocks.leave, 1);
|
||||||
press(button);
|
check.called(mocks.click, 0);
|
||||||
release(button);
|
press(button);
|
||||||
check.called(mocks.enter, 0);
|
release(button);
|
||||||
check.called(mocks.leave, 0);
|
check.called(mocks.enter, 0);
|
||||||
check.called(mocks.click, 0);
|
check.called(mocks.leave, 0);
|
||||||
|
check.called(mocks.click, 0);
|
||||||
|
});
|
||||||
|
|
||||||
// Force-leave when hovering another button without clean leaving a first one
|
check.in("Force-leave when hovering another button without clean leaving a first one", check => {
|
||||||
let [button1, funcs1] = newButton();
|
let [button1, funcs1] = newButton();
|
||||||
let [button2, funcs2] = newButton();
|
let [button2, funcs2] = newButton();
|
||||||
enter(button1);
|
enter(button1);
|
||||||
clock.forward(150);
|
clock.forward(150);
|
||||||
check.called(funcs1.enter, 1);
|
check.called(funcs1.enter, 1);
|
||||||
check.called(funcs1.leave, 0);
|
check.called(funcs1.leave, 0);
|
||||||
check.called(funcs1.click, 0);
|
check.called(funcs1.click, 0);
|
||||||
enter(button2);
|
enter(button2);
|
||||||
check.called(funcs1.enter, 0);
|
check.called(funcs1.enter, 0);
|
||||||
check.called(funcs1.leave, 1);
|
check.called(funcs1.leave, 1);
|
||||||
check.called(funcs1.click, 0);
|
check.called(funcs1.click, 0);
|
||||||
check.called(funcs2.enter, 0);
|
check.called(funcs2.enter, 0);
|
||||||
check.called(funcs2.leave, 0);
|
check.called(funcs2.leave, 0);
|
||||||
check.called(funcs2.click, 0);
|
check.called(funcs2.click, 0);
|
||||||
clock.forward(150);
|
clock.forward(150);
|
||||||
check.called(funcs1.enter, 0);
|
check.called(funcs1.enter, 0);
|
||||||
check.called(funcs1.leave, 0);
|
check.called(funcs1.leave, 0);
|
||||||
check.called(funcs1.click, 0);
|
check.called(funcs1.click, 0);
|
||||||
check.called(funcs2.enter, 1);
|
check.called(funcs2.enter, 1);
|
||||||
check.called(funcs2.leave, 0);
|
check.called(funcs2.leave, 0);
|
||||||
check.called(funcs2.click, 0);
|
check.called(funcs2.click, 0);
|
||||||
|
});
|
||||||
|
|
||||||
// Hold to hover on mobile
|
|
||||||
[button, mocks] = newButton();
|
[button, mocks] = newButton();
|
||||||
button.onInputDown.dispatch(button, pointer);
|
check.in("Hold to hover on mobile", check => {
|
||||||
clock.forward(150);
|
button.emit("pointerdown", pointer);
|
||||||
check.called(mocks.enter, 1);
|
clock.forward(150);
|
||||||
check.called(mocks.leave, 0);
|
check.called(mocks.enter, 1);
|
||||||
check.called(mocks.click, 0);
|
check.called(mocks.leave, 0);
|
||||||
button.onInputUp.dispatch(button, pointer);
|
check.called(mocks.click, 0);
|
||||||
check.called(mocks.enter, 0);
|
button.emit("pointerup", pointer);
|
||||||
check.called(mocks.leave, 1);
|
check.called(mocks.enter, 0);
|
||||||
check.called(mocks.click, 0);
|
check.called(mocks.leave, 1);
|
||||||
});
|
check.called(mocks.click, 0);
|
||||||
|
});
|
||||||
test.case("handles drag and drop", check => {
|
|
||||||
let builder = new UIBuilder(testgame.view);
|
|
||||||
let button = builder.button("test", 0, 0, () => null, "test tooltip");
|
|
||||||
let tooltip = (<any>testgame.view.tooltip).container;
|
|
||||||
|
|
||||||
check.same(button.inputEnabled, true, "input should be enabled initially");
|
|
||||||
check.same(button.input.draggable, false, "dragging should be disabled initially");
|
|
||||||
|
|
||||||
let x = 0;
|
|
||||||
testgame.view.inputs.setDragDrop(button, () => x += 1, () => x -= 1);
|
|
||||||
|
|
||||||
check.same(button.inputEnabled, true, "input should still be enabled");
|
|
||||||
check.same(button.input.draggable, true, "dragging should be enabled");
|
|
||||||
|
|
||||||
check.same(tooltip.visible, false, "tooltip hidden initially");
|
|
||||||
button.onInputOver.dispatch(button, testgame.ui.input.pointer1);
|
|
||||||
clock.forward(1000);
|
|
||||||
check.same(tooltip.visible, true, "tooltip shown");
|
|
||||||
|
|
||||||
check.same(x, 0, "initial state");
|
|
||||||
button.events.onDragStart.dispatch();
|
|
||||||
check.same(x, 1, "dragged");
|
|
||||||
check.same(tooltip.visible, false, "tooltip hidden on dragging");
|
|
||||||
button.events.onDragStop.dispatch();
|
|
||||||
check.same(x, 0, "dropped");
|
|
||||||
|
|
||||||
testgame.view.inputs.setDragDrop(button);
|
|
||||||
|
|
||||||
button.events.onDragStart.dispatch();
|
|
||||||
check.same(x, 0, "drag signal should be disabled");
|
|
||||||
check.same(button.inputEnabled, true, "input should remain enabled");
|
|
||||||
check.same(button.input.draggable, false, "dragging should be disabled at the end");
|
|
||||||
|
|
||||||
testgame.view.inputs.setDragDrop(button, () => x += 1, () => x -= 1);
|
|
||||||
button.events.onDragStart.dispatch();
|
|
||||||
check.same(x, 1, "drag signal should be dispatch once");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ module TK.SpaceTac.UI {
|
||||||
private debug = false
|
private debug = false
|
||||||
private view: BaseView
|
private view: BaseView
|
||||||
private game: MainUI
|
private game: MainUI
|
||||||
private input: Phaser.Input
|
private input: Phaser.Input.InputManager
|
||||||
|
|
||||||
private cheats_allowed: boolean
|
private cheats_allowed: boolean
|
||||||
private cheat: boolean
|
private cheat: boolean
|
||||||
|
|
||||||
private hovered: Phaser.Button | null = null
|
private hovered: UIButton | UIContainer | UIImage | null = null
|
||||||
|
|
||||||
private binds: { [key: string]: KeyPressedCallback } = {}
|
private binds: { [key: string]: KeyPressedCallback } = {}
|
||||||
|
|
||||||
|
@ -23,19 +23,17 @@ module TK.SpaceTac.UI {
|
||||||
constructor(view: BaseView) {
|
constructor(view: BaseView) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.game = view.gameui;
|
this.game = view.gameui;
|
||||||
this.input = view.input;
|
this.input = view.input.manager;
|
||||||
this.cheats_allowed = true;
|
this.cheats_allowed = true;
|
||||||
this.cheat = false;
|
this.cheat = false;
|
||||||
|
|
||||||
this.input.reset(true);
|
|
||||||
|
|
||||||
// Default mappings
|
// Default mappings
|
||||||
this.bind("s", "Quick save", () => {
|
this.bind("s", "Quick save", () => {
|
||||||
this.game.saveGame();
|
this.game.saveGame();
|
||||||
});
|
});
|
||||||
this.bind("l", "Quick load", () => {
|
this.bind("l", "Quick load", () => {
|
||||||
this.game.loadGame();
|
this.game.loadGame();
|
||||||
this.game.state.start("router");
|
this.view.backToRouter();
|
||||||
});
|
});
|
||||||
this.bind("m", "Toggle sound", () => {
|
this.bind("m", "Toggle sound", () => {
|
||||||
this.game.options.setNumberValue("mainvolume", this.game.options.getNumberValue("mainvolume") > 0 ? 0 : 1);
|
this.game.options.setNumberValue("mainvolume", this.game.options.getNumberValue("mainvolume") > 0 ? 0 : 1);
|
||||||
|
@ -51,7 +49,7 @@ module TK.SpaceTac.UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.game.headless) {
|
if (!this.game.headless) {
|
||||||
this.input.keyboard.addCallbacks(this, undefined, (event: KeyboardEvent) => {
|
this.input.keyboard.on("keyup", (event: KeyboardEvent) => {
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
}
|
}
|
||||||
|
@ -68,6 +66,13 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the bindings
|
||||||
|
*/
|
||||||
|
destroy(): void {
|
||||||
|
this.input.keyboard.removeAllListeners("keyup");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind a key to a specific action.
|
* Bind a key to a specific action.
|
||||||
*/
|
*/
|
||||||
|
@ -126,45 +131,11 @@ module TK.SpaceTac.UI {
|
||||||
* Force the cursor out of currently hovered object
|
* Force the cursor out of currently hovered object
|
||||||
*/
|
*/
|
||||||
private forceLeaveHovered() {
|
private forceLeaveHovered() {
|
||||||
if (this.hovered && this.hovered.data.hover_pointer) {
|
if (this.hovered && this.hovered.data) {
|
||||||
(<any>this.hovered.input)._pointerOutHandler(this.hovered.data.hover_pointer);
|
let pointer = this.hovered.data.get("pointer");
|
||||||
}
|
if (pointer) {
|
||||||
}
|
this.hovered.emit("pointerout", pointer);
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup dragging on an UI component
|
|
||||||
*
|
|
||||||
* If no drag or drop function is defined, dragging is disabled
|
|
||||||
*
|
|
||||||
* If update function is defined, it will receive (a lot of) cursor moves while dragging
|
|
||||||
*/
|
|
||||||
setDragDrop(obj: Phaser.Button | Phaser.Image, drag?: Function, drop?: Function, update?: Function): void {
|
|
||||||
obj.events.onDragStart.removeAll();
|
|
||||||
obj.events.onDragStop.removeAll();
|
|
||||||
obj.events.onDragUpdate.removeAll();
|
|
||||||
|
|
||||||
if (drag && drop) {
|
|
||||||
obj.inputEnabled = true;
|
|
||||||
obj.input.enableDrag(false, true);
|
|
||||||
|
|
||||||
obj.events.onDragStart.add(() => {
|
|
||||||
this.forceLeaveHovered();
|
|
||||||
this.view.audio.playOnce("ui-drag");
|
|
||||||
drag();
|
|
||||||
});
|
|
||||||
|
|
||||||
obj.events.onDragStop.add(() => {
|
|
||||||
this.view.audio.playOnce("ui-drop");
|
|
||||||
drop();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
obj.events.onDragUpdate.add(() => {
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
obj.input.disableDrag();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,17 +143,24 @@ module TK.SpaceTac.UI {
|
||||||
* Setup hover/click handlers on an UI element
|
* Setup hover/click handlers on an UI element
|
||||||
*
|
*
|
||||||
* This is done in a way that should be compatible with touch-enabled screen
|
* This is done in a way that should be compatible with touch-enabled screen
|
||||||
*
|
|
||||||
* Returns functions that may be used to force the behavior
|
|
||||||
*/
|
*/
|
||||||
setHoverClick(obj: Phaser.Button, enter: Function = nop, leave: Function = nop, click: Function = nop, hovertime = 300, holdtime = 600) {
|
setHoverClick(obj: UIButton | UIContainer | UIImage, enter: Function = nop, leave: Function = nop, click: Function = nop, hovertime = 300, holdtime = 600, sound = false): void {
|
||||||
let holdstart = Timer.nowMs();
|
let holdstart = Timer.nowMs();
|
||||||
let enternext: Function | null = null;
|
let enternext: Function | null = null;
|
||||||
let entercalled = false;
|
let entercalled = false;
|
||||||
let cursorinside = false;
|
let cursorinside = false;
|
||||||
let destroyed = false;
|
let destroyed = false;
|
||||||
|
|
||||||
obj.input.useHandCursor = true;
|
obj.setDataEnabled();
|
||||||
|
|
||||||
|
if (obj instanceof UIImage) {
|
||||||
|
obj.setInteractive();
|
||||||
|
} else if (!(obj instanceof UIButton)) {
|
||||||
|
let bounds = obj.getBounds();
|
||||||
|
bounds.x -= obj.x;
|
||||||
|
bounds.y -= obj.y;
|
||||||
|
obj.setInteractive(bounds, Phaser.Geom.Rectangle.Contains);
|
||||||
|
}
|
||||||
|
|
||||||
let prevententer = () => {
|
let prevententer = () => {
|
||||||
if (enternext != null) {
|
if (enternext != null) {
|
||||||
|
@ -210,15 +188,13 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.events) {
|
obj.on("destroy", () => {
|
||||||
obj.events.onDestroy.addOnce(() => {
|
destroyed = true;
|
||||||
destroyed = true;
|
effectiveleave();
|
||||||
effectiveleave();
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.onInputOver.add((_: any, pointer: Phaser.Pointer) => {
|
obj.on("pointerover", (pointer: Phaser.Input.Pointer) => {
|
||||||
if (destroyed) return;
|
if (destroyed || !UITools.isVisible(obj)) return;
|
||||||
|
|
||||||
if (this.hovered) {
|
if (this.hovered) {
|
||||||
if (this.hovered === obj) {
|
if (this.hovered === obj) {
|
||||||
|
@ -228,15 +204,13 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.hovered = obj;
|
this.hovered = obj;
|
||||||
this.hovered.data.hover_pointer = pointer;
|
this.hovered.data.set("pointer", pointer);
|
||||||
|
|
||||||
if (obj.visible && obj.alpha) {
|
cursorinside = true;
|
||||||
cursorinside = true;
|
enternext = Timer.global.schedule(hovertime, effectiveenter);
|
||||||
enternext = Timer.global.schedule(hovertime, effectiveenter);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
obj.onInputOut.add(() => {
|
obj.on("pointerout", (pointer: Phaser.Input.Pointer) => {
|
||||||
if (destroyed) return;
|
if (destroyed) return;
|
||||||
|
|
||||||
if (this.hovered === obj) {
|
if (this.hovered === obj) {
|
||||||
|
@ -247,18 +221,21 @@ module TK.SpaceTac.UI {
|
||||||
effectiveleave();
|
effectiveleave();
|
||||||
});
|
});
|
||||||
|
|
||||||
obj.onInputDown.add(() => {
|
obj.on("pointerdown", (pointer: Phaser.Input.Pointer) => {
|
||||||
if (destroyed) return;
|
if (destroyed) return;
|
||||||
|
|
||||||
if (obj.visible && obj.alpha) {
|
if (UITools.isVisible(obj)) {
|
||||||
holdstart = Timer.nowMs();
|
holdstart = Timer.nowMs();
|
||||||
|
if (sound) {
|
||||||
|
this.view.audio.playOnce("ui-button-down");
|
||||||
|
}
|
||||||
if (!cursorinside && !enternext) {
|
if (!cursorinside && !enternext) {
|
||||||
enternext = Timer.global.schedule(holdtime, effectiveenter);
|
enternext = Timer.global.schedule(holdtime, effectiveenter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
obj.onInputUp.add(() => {
|
obj.on("pointerup", (event: Phaser.Input.Pointer) => {
|
||||||
if (destroyed) return;
|
if (destroyed) return;
|
||||||
|
|
||||||
if (!cursorinside) {
|
if (!cursorinside) {
|
||||||
|
@ -269,6 +246,9 @@ module TK.SpaceTac.UI {
|
||||||
if (!cursorinside) {
|
if (!cursorinside) {
|
||||||
effectiveenter();
|
effectiveenter();
|
||||||
}
|
}
|
||||||
|
if (sound) {
|
||||||
|
this.view.audio.playOnce("ui-button-up");
|
||||||
|
}
|
||||||
click();
|
click();
|
||||||
if (!cursorinside) {
|
if (!cursorinside) {
|
||||||
effectiveleave();
|
effectiveleave();
|
||||||
|
|
|
@ -1,66 +1,64 @@
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
// A single displayed message
|
/**
|
||||||
class Message extends Phaser.Group {
|
* A single displayed message
|
||||||
|
*/
|
||||||
|
class Message extends UIContainer {
|
||||||
view: BaseView
|
view: BaseView
|
||||||
background: Phaser.Graphics
|
background: UIBackground
|
||||||
text: Phaser.Text
|
text: UIText
|
||||||
|
|
||||||
constructor(parent: Messages, text: string, duration: number) {
|
constructor(parent: Messages, text: string, duration: number) {
|
||||||
super(parent.view.game);
|
super(parent.view);
|
||||||
|
|
||||||
this.view = parent.view;
|
this.view = parent.view;
|
||||||
let builder = new UIBuilder(this.view).in(this);
|
let builder = new UIBuilder(this.view).in(this);
|
||||||
|
|
||||||
this.background = new Phaser.Graphics(this.game);
|
this.background = new UIBackground(this.view, this);
|
||||||
this.add(this.background);
|
|
||||||
|
|
||||||
this.text = builder.text(text, 0, 0, { color: "#DBEFF9", shadow: true, size: 16, center: false, vcenter: false });
|
this.text = builder.text(text, 0, 0, { color: "#DBEFF9", shadow: true, size: 16, center: false, vcenter: false });
|
||||||
|
|
||||||
this.position.set(parent.view.getWidth(), 10);
|
UITools.drawBackground(this.text, this.background, 6);
|
||||||
|
|
||||||
|
let bounds = UITools.getBounds(this);
|
||||||
|
this.setPosition(parent.view.getWidth() - bounds.width - 10, 10);
|
||||||
parent.view.timer.schedule(duration, () => this.hide());
|
parent.view.timer.schedule(duration, () => this.hide());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the message
|
/**
|
||||||
|
* Hide the message
|
||||||
|
*/
|
||||||
hide() {
|
hide() {
|
||||||
var tween = this.game.tweens.create(this);
|
this.view.animations.addAnimation<UIContainer>(this, { y: this.y + 50, alpha: 0 }, 400, "Circ.easeIn").then(() => this.destroy());
|
||||||
tween.to({ y: this.y + 50, alpha: 0 }, 400, Phaser.Easing.Circular.In);
|
|
||||||
tween.onComplete.addOnce(() => {
|
|
||||||
this.destroy();
|
|
||||||
});
|
|
||||||
tween.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
UITools.drawBackground(this.text, this.background, 6);
|
|
||||||
|
|
||||||
this.x = this.view.getWidth() - this.width - 10;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visual notifications of game-related messages (eg. "Game saved"...)
|
/**
|
||||||
|
* Visual notifications of game-related messages (eg. "Game saved"...)
|
||||||
|
*/
|
||||||
export class Messages {
|
export class Messages {
|
||||||
// Link to parent view
|
// Link to parent view
|
||||||
view: BaseView;
|
view: BaseView
|
||||||
|
|
||||||
// Main group to hold the visual messages
|
// Main group to hold the visual messages
|
||||||
container: Phaser.Group;
|
container: UIContainer
|
||||||
|
|
||||||
constructor(parent: BaseView) {
|
constructor(view: BaseView) {
|
||||||
this.view = parent;
|
this.view = view;
|
||||||
|
this.container = new UIBuilder(view, view.messages_layer).container("messages");
|
||||||
this.container = new Phaser.Group(parent.game);
|
|
||||||
parent.add.existing(this.container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new message to the notifications
|
/**
|
||||||
|
* Add a new message to the notifications
|
||||||
|
*/
|
||||||
addMessage(text: string, duration: number = 3000): void {
|
addMessage(text: string, duration: number = 3000): void {
|
||||||
this.container.forEachExists((child: Message) => {
|
let message = new Message(this, text, duration);
|
||||||
child.y += child.height + 5;
|
this.container.add(message);
|
||||||
}, this);
|
|
||||||
|
|
||||||
var message = new Message(this, text, duration);
|
let bounds = UITools.getBounds(message);
|
||||||
this.container.addChild(message);
|
|
||||||
|
cfilter(this.container.list, Message).forEach(child => {
|
||||||
|
child.y += bounds.height + 5;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,25 +9,29 @@ module TK.SpaceTac.UI.Specs {
|
||||||
new ParticleConfig(ParticleShape.DISK_HALO, ParticleColor.WHITE, 0.5, 1, 0, 5, 0)
|
new ParticleConfig(ParticleShape.DISK_HALO, ParticleColor.WHITE, 0.5, 1, 0, 5, 0)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
check.equals(particle instanceof Phaser.Image, true);
|
check.equals(particle.length, 2);
|
||||||
check.equals(particle.data.frame, 4);
|
|
||||||
check.equals(particle.data.key, "common-particles");
|
|
||||||
check.equals(particle.scale.x, 2);
|
|
||||||
check.equals(particle.scale.y, 2);
|
|
||||||
check.equals(particle.x, 10);
|
|
||||||
check.equals(particle.y, -20);
|
|
||||||
check.equals(particle.angle, 45);
|
|
||||||
|
|
||||||
check.equals(particle.children.length, 1);
|
let child = particle.list[0];
|
||||||
let subparticle = <Phaser.Image>particle.getChildAt(0);
|
if (check.instance(child, Phaser.GameObjects.Image, "first particle is an image")) {
|
||||||
check.equals(subparticle instanceof Phaser.Image, true);
|
check.equals((<any>child.data).frame, 4);
|
||||||
check.equals(subparticle.data.frame, 16);
|
check.equals((<any>child.data).key, "common-particles");
|
||||||
check.equals(subparticle.data.key, "common-particles");
|
check.equals(child.scaleX, 2);
|
||||||
check.equals(subparticle.scale.x, 0.25);
|
check.equals(child.scaleY, 2);
|
||||||
check.equals(subparticle.scale.y, 0.25);
|
check.equals(child.x, 10);
|
||||||
check.equals(subparticle.x, 2.5);
|
check.equals(child.y, -20);
|
||||||
check.equals(subparticle.y, 0);
|
check.equals(child.angle, 45);
|
||||||
check.equals(subparticle.angle, -45);
|
}
|
||||||
|
|
||||||
|
child = particle.list[1];
|
||||||
|
if (check.instance(child, Phaser.GameObjects.Image, "second particle is an image")) {
|
||||||
|
check.equals((<any>child.data).frame, 16);
|
||||||
|
check.equals((<any>child.data).key, "common-particles");
|
||||||
|
check.equals(child.scaleX, 0.5);
|
||||||
|
check.equals(child.scaleY, 0.5);
|
||||||
|
check.equals(child.x, 5);
|
||||||
|
check.equals(child.y, 0);
|
||||||
|
check.equals(child.angle, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,16 +56,16 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Get a particle image for this config
|
* Get a particle image for this config
|
||||||
*/
|
*/
|
||||||
getImage(game: Phaser.Game, scaling = 1, angle = 0): Phaser.Image {
|
getImage(view: BaseView): UIImage {
|
||||||
let frame = this.shape * 16 + this.color;
|
let frame = this.shape * 16 + this.color;
|
||||||
let result = game.add.image(0, 0, "common-particles", frame);
|
let result = view.add.image(0, 0, "common-particles", frame);
|
||||||
result.data.frame = frame;
|
result.setDataEnabled();
|
||||||
result.data.key = "common-particles";
|
(<any>result.data).frame = frame;
|
||||||
result.anchor.set(0.5);
|
(<any>result.data).key = "common-particles";
|
||||||
result.angle = angle + this.angle;
|
result.setAngle(this.angle);
|
||||||
result.alpha = this.alpha;
|
result.setAlpha(this.alpha);
|
||||||
result.scale.set(this.scale * scaling);
|
result.setScale(this.scale);
|
||||||
result.position.set(this.offsetx * scaling, this.offsety * scaling);
|
result.setPosition(this.offsetx, this.offsety);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,18 +83,14 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Build a composed particle
|
* Build a composed particle
|
||||||
*/
|
*/
|
||||||
build(configs: ParticleConfig[]): Phaser.Image {
|
build(configs: ParticleConfig[]): UIContainer {
|
||||||
if (configs.length == 0) {
|
let result = this.view.add.container(0, 0);
|
||||||
return this.view.newImage("common-transparent");
|
|
||||||
} else {
|
configs.forEach(config => {
|
||||||
let base = configs[0];
|
result.add(config.getImage(this.view));
|
||||||
let result = base.getImage(this.view.game, 1);
|
});
|
||||||
configs.slice(1).forEach(config => {
|
|
||||||
let sub = config.getImage(this.view.game, 1 / base.scale, -base.angle);
|
return result;
|
||||||
result.addChild(sub);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
107
src/ui/common/ParticleSystem.ts
Normal file
107
src/ui/common/ParticleSystem.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
module TK.SpaceTac.UI {
|
||||||
|
type Manager = Phaser.GameObjects.Particles.ParticleEmitterManager;
|
||||||
|
|
||||||
|
export enum ParticleFacingMode {
|
||||||
|
INITIAL = 1,
|
||||||
|
ALWAYS = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ParticlesConfig = {
|
||||||
|
// Key for the particle texture
|
||||||
|
key: string,
|
||||||
|
// Source of the particles
|
||||||
|
source: { x: number, y: number, radius: number },
|
||||||
|
// Total number of particles to emit
|
||||||
|
count: number,
|
||||||
|
// Duration of the emission of particles
|
||||||
|
emitDuration: number
|
||||||
|
// Lifespan of a single particle in milliseconds
|
||||||
|
lifetime: number,
|
||||||
|
// Fade the alpha during the lifespan
|
||||||
|
fading?: boolean,
|
||||||
|
// Direction of the particles for radial emission
|
||||||
|
direction: { minangle: number, maxangle: number },
|
||||||
|
// Scale of the emitted particles
|
||||||
|
scale: { min: number, max: number }
|
||||||
|
// Speed of the particles
|
||||||
|
speed: { min: number, max: number }
|
||||||
|
// Force the particle to face its direction
|
||||||
|
facing?: ParticleFacingMode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System to emit multiple particles of the same texture
|
||||||
|
*/
|
||||||
|
export class ParticleSystem {
|
||||||
|
constructor(private view: BaseView) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private getManager(key: string, parent?: UIContainer): Manager {
|
||||||
|
let info = this.view.getImageInfo(key);
|
||||||
|
let result = this.view.add.particles(info.key, info.frame);
|
||||||
|
if (parent) {
|
||||||
|
parent.add(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a batch of particles
|
||||||
|
*
|
||||||
|
* Returns the total duration in milliseconds
|
||||||
|
*/
|
||||||
|
emit(config: ParticlesConfig, parent?: UIContainer): number {
|
||||||
|
let manager = this.getManager(config.key, parent);
|
||||||
|
let emitter = manager.createEmitter({});
|
||||||
|
if (config.fading) {
|
||||||
|
emitter.setAlpha({ start: 1, end: 0 });
|
||||||
|
}
|
||||||
|
emitter.setPosition(
|
||||||
|
{ min: config.source.x - config.source.radius, max: config.source.x + config.source.radius },
|
||||||
|
{ min: config.source.y - config.source.radius, max: config.source.y + config.source.radius },
|
||||||
|
);
|
||||||
|
emitter.setSpeed({ min: config.speed.min, max: config.speed.max });
|
||||||
|
emitter.setRadial(true);
|
||||||
|
emitter.setEmitterAngle({ min: degrees(config.direction.minangle), max: degrees(config.direction.maxangle) });
|
||||||
|
emitter.setLifespan(config.lifetime);
|
||||||
|
emitter.setFrequency(config.emitDuration / config.count, 1);
|
||||||
|
emitter.setScale({ min: config.scale.min, max: config.scale.max });
|
||||||
|
if (config.facing) {
|
||||||
|
emitter.particleClass = (config.facing == ParticleFacingMode.ALWAYS) ? FacingAlwaysParticle : FacingInitialParticle;
|
||||||
|
}
|
||||||
|
this.view.timer.schedule(config.emitDuration, () => emitter.on = false);
|
||||||
|
this.view.timer.schedule(config.emitDuration + config.lifetime, () => manager.destroy());
|
||||||
|
return config.emitDuration + config.lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async version of *emit*
|
||||||
|
*/
|
||||||
|
emit_as(config: ParticlesConfig): Promise<void> {
|
||||||
|
let duration = this.emit(config);
|
||||||
|
return this.view.timer.sleep(duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Particle that is rotated to face its initial direction
|
||||||
|
*/
|
||||||
|
class FacingInitialParticle extends Phaser.GameObjects.Particles.Particle {
|
||||||
|
fire(x: number, y: number): any {
|
||||||
|
let result = super.fire(x, y);
|
||||||
|
this.rotation = Math.atan2(this.velocityY, this.velocityX);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Particle that is rotated to face its movement direction
|
||||||
|
*/
|
||||||
|
class FacingAlwaysParticle extends FacingInitialParticle {
|
||||||
|
update(delta: any, step: any, processors: any): any {
|
||||||
|
let result = super.update(delta, step, processors);
|
||||||
|
this.rotation = Math.atan2(this.velocityY, this.velocityX);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,26 +4,27 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let clock = test.clock();
|
let clock = test.clock();
|
||||||
|
|
||||||
test.case("shows near the hovered button", check => {
|
test.case("shows near the hovered button", check => {
|
||||||
let button = testgame.view.add.button();
|
let button = new UIBuilder(testgame.view).button("fake");
|
||||||
check.patch(button, "getBounds", () => new PIXI.Rectangle(100, 50, 50, 25));
|
check.patch(button, "getBounds", () => new Phaser.Geom.Rectangle(100, 50, 50, 25));
|
||||||
|
|
||||||
let tooltip = new Tooltip(testgame.view);
|
let tooltip = new Tooltip(testgame.view);
|
||||||
tooltip.bind(button, filler => true);
|
tooltip.bind(button, filler => true);
|
||||||
|
|
||||||
let container = <Phaser.Group>(<any>tooltip).container;
|
let container = tooltip.container;
|
||||||
check.patch((<any>container).content, "getBounds", () => new PIXI.Rectangle(0, 0, 32, 32));
|
check.patch(container.content, "getBounds", () => new Phaser.Geom.Rectangle(0, 0, 32, 32));
|
||||||
check.equals(container.visible, false);
|
check.equals(container.visible, false);
|
||||||
|
|
||||||
button.onInputOver.dispatch();
|
let pointer = {};
|
||||||
|
button.emit("pointerover", { pointer: pointer });
|
||||||
check.equals(container.visible, false);
|
check.equals(container.visible, false);
|
||||||
|
|
||||||
clock.forward(1000);
|
clock.forward(1000);
|
||||||
container.update();
|
container.update();
|
||||||
check.equals(container.visible, true);
|
check.equals(container.visible, true);
|
||||||
check.equals(container.x, 109);
|
check.equals(container.x, 113);
|
||||||
check.equals(container.y, 91);
|
check.equals(container.y, 91);
|
||||||
|
|
||||||
button.onInputOut.dispatch();
|
button.emit("pointerout", { pointer: pointer });
|
||||||
check.equals(container.visible, false);
|
check.equals(container.visible, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,25 +4,24 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
export type TooltipFiller = string | ((filler: TooltipBuilder) => string) | ((filler: TooltipBuilder) => boolean);
|
export type TooltipFiller = string | ((filler: TooltipBuilder) => string) | ((filler: TooltipBuilder) => boolean);
|
||||||
|
|
||||||
export class TooltipContainer extends Phaser.Group {
|
export class TooltipContainer extends UIContainer {
|
||||||
view: BaseView
|
view: BaseView
|
||||||
background: Phaser.Graphics
|
background: UIBackground
|
||||||
content: Phaser.Group
|
content: UIContainer
|
||||||
item?: IBounded
|
item?: IBounded
|
||||||
border = 10
|
border = 10
|
||||||
margin = 6
|
margin = 6
|
||||||
viewport: IBounded | null = null
|
viewport: IBounded | null = null
|
||||||
|
|
||||||
constructor(view: BaseView) {
|
constructor(view: BaseView) {
|
||||||
super(view.game);
|
super(view);
|
||||||
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
|
||||||
this.background = new Phaser.Graphics(this.game);
|
this.background = new UIBackground(view, this);
|
||||||
this.add(this.background);
|
|
||||||
|
|
||||||
this.content = new Phaser.Group(this.game);
|
this.content = new UIContainer(view);
|
||||||
this.add(this.content);
|
this.add(this.content);
|
||||||
|
|
||||||
this.view.tooltip_layer.add(this);
|
this.view.tooltip_layer.add(this);
|
||||||
|
@ -64,7 +63,7 @@ module TK.SpaceTac.UI {
|
||||||
x += this.border;
|
x += this.border;
|
||||||
y += this.border;
|
y += this.border;
|
||||||
if (x != this.x || y != this.y) {
|
if (x != this.x || y != this.y) {
|
||||||
this.position.set(x, y);
|
this.setPosition(x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +79,7 @@ module TK.SpaceTac.UI {
|
||||||
* Functions used to fill a tooltip content
|
* Functions used to fill a tooltip content
|
||||||
*/
|
*/
|
||||||
export class TooltipBuilder extends UIBuilder {
|
export class TooltipBuilder extends UIBuilder {
|
||||||
private container: TooltipContainer;
|
private content: TooltipContainer;
|
||||||
|
|
||||||
constructor(container: TooltipContainer) {
|
constructor(container: TooltipContainer) {
|
||||||
let style = new UITextStyle();
|
let style = new UITextStyle();
|
||||||
|
@ -89,17 +88,17 @@ module TK.SpaceTac.UI {
|
||||||
style.shadow = true;
|
style.shadow = true;
|
||||||
super(container.view, container.content, style);
|
super(container.view, container.content, style);
|
||||||
|
|
||||||
this.container = container;
|
this.content = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the positioning and base style of the tooltip
|
* Configure the positioning and base style of the tooltip
|
||||||
*/
|
*/
|
||||||
configure(border = 10, margin = 6, viewport: IBounded | null = null): void {
|
configure(border = 10, margin = 6, viewport: IBounded | null = null): void {
|
||||||
this.container.border = border;
|
this.content.border = border;
|
||||||
this.container.margin = margin;
|
this.content.margin = margin;
|
||||||
if (viewport) {
|
if (viewport) {
|
||||||
this.container.viewport = viewport;
|
this.content.viewport = viewport;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,8 +107,8 @@ module TK.SpaceTac.UI {
|
||||||
* Tooltip system, to display information on hover
|
* Tooltip system, to display information on hover
|
||||||
*/
|
*/
|
||||||
export class Tooltip {
|
export class Tooltip {
|
||||||
protected view: BaseView;
|
readonly view: BaseView;
|
||||||
protected container: TooltipContainer;
|
readonly container: TooltipContainer;
|
||||||
|
|
||||||
constructor(view: BaseView) {
|
constructor(view: BaseView) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
|
@ -132,13 +131,13 @@ module TK.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: (filler: TooltipBuilder) => boolean): void {
|
bind(obj: UIButton | UIImage, func: (filler: TooltipBuilder) => boolean): void {
|
||||||
this.view.inputs.setHoverClick(obj,
|
this.view.inputs.setHoverClick(obj,
|
||||||
// enter
|
// enter
|
||||||
() => {
|
() => {
|
||||||
this.hide();
|
this.hide();
|
||||||
if (func(this.getBuilder())) {
|
if (func(this.getBuilder())) {
|
||||||
this.container.show(obj.getBounds());
|
this.container.show(UITools.getBounds(obj));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// leave
|
// leave
|
||||||
|
@ -146,13 +145,13 @@ module TK.SpaceTac.UI {
|
||||||
// click
|
// click
|
||||||
() => this.hide()
|
() => this.hide()
|
||||||
);
|
);
|
||||||
obj.onInputDown.add(() => this.hide());
|
obj.on("pointerdown", () => this.hide());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind to an UI component to display a dynamic text
|
* Bind to an UI component to display a dynamic text
|
||||||
*/
|
*/
|
||||||
bindDynamicText(obj: Phaser.Button, text_getter: () => string): void {
|
bindDynamicText(obj: UIButton | UIImage, text_getter: () => string): void {
|
||||||
this.bind(obj, filler => {
|
this.bind(obj, filler => {
|
||||||
let content = text_getter();
|
let content = text_getter();
|
||||||
if (content) {
|
if (content) {
|
||||||
|
@ -167,14 +166,14 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Bind to an UI component to display a simple text
|
* Bind to an UI component to display a simple text
|
||||||
*/
|
*/
|
||||||
bindStaticText(obj: Phaser.Button, text: string): void {
|
bindStaticText(obj: UIButton | UIImage, text: string): void {
|
||||||
this.bindDynamicText(obj, () => text);
|
this.bindDynamicText(obj, () => text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a tooltip for a component
|
* Show a tooltip for a component
|
||||||
*/
|
*/
|
||||||
show(obj: Phaser.Button, content: TooltipFiller): void {
|
show(obj: UIButton, content: TooltipFiller): void {
|
||||||
let builder = this.getBuilder();
|
let builder = this.getBuilder();
|
||||||
let scontent = (typeof content == "string") ? content : content(builder);
|
let scontent = (typeof content == "string") ? content : content(builder);
|
||||||
if (typeof scontent == "string") {
|
if (typeof scontent == "string") {
|
||||||
|
@ -182,7 +181,7 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scontent) {
|
if (scontent) {
|
||||||
this.container.show(obj.getBounds());
|
this.container.show(UITools.getBounds(obj));
|
||||||
} else {
|
} else {
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
59
src/ui/common/UIBackground.ts
Normal file
59
src/ui/common/UIBackground.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
module TK.SpaceTac.UI {
|
||||||
|
/**
|
||||||
|
* Decorated background for dynamic sized content (such as tooltips)
|
||||||
|
*/
|
||||||
|
export class UIBackground {
|
||||||
|
private graphics: UIGraphics
|
||||||
|
x = 0
|
||||||
|
y = 0
|
||||||
|
width = 0
|
||||||
|
height = 0
|
||||||
|
|
||||||
|
constructor(readonly view: BaseView, readonly parent: UIContainer, readonly border = 6) {
|
||||||
|
this.graphics = new UIBuilder(view, parent).graphics("background", 0, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapt the background to cover a given content
|
||||||
|
*/
|
||||||
|
adaptToContent(content: UIContainer | UIText): void {
|
||||||
|
if (content.parentContainer != this.graphics.parentContainer) {
|
||||||
|
console.error("Content and background should have the same parent container");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounds = UITools.getBounds(content);
|
||||||
|
|
||||||
|
let x = bounds.x - this.graphics.parentContainer.x - this.border;
|
||||||
|
let y = bounds.y - this.graphics.parentContainer.y - this.border;
|
||||||
|
let width = bounds.width + 2 * this.border;
|
||||||
|
let height = bounds.height + 2 * this.border;
|
||||||
|
|
||||||
|
if (x != this.x || y != this.y || width != this.width || height != this.height) {
|
||||||
|
this.graphics.clear();
|
||||||
|
this.graphics.lineStyle(2, 0x6690a4);
|
||||||
|
this.graphics.fillStyle(0x162730);
|
||||||
|
this.graphics.fillRect(x, y, width, height);
|
||||||
|
this.graphics.strokeRect(x, y, width, height);
|
||||||
|
this.graphics.setVisible(true);
|
||||||
|
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the drawn background
|
||||||
|
*/
|
||||||
|
clear(): void {
|
||||||
|
this.graphics.setVisible(false);
|
||||||
|
this.graphics.clear();
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,17 +4,23 @@ module TK.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
function get(path: (number | string)[]): [string, any] {
|
function get(path: (number | string)[]): [string, any] {
|
||||||
let spath = `[${path.join(" -> ")}]`;
|
let spath = `[${path.join(" -> ")}]`;
|
||||||
let component: any = testgame.view.world;
|
let component: Phaser.GameObjects.GameObject | Phaser.Scene | null = testgame.view;
|
||||||
path.forEach(idx => {
|
path.forEach(idx => {
|
||||||
component = (typeof idx == "number") ? component.children[idx] : component.getByName(idx);
|
if (component instanceof Phaser.Scene) {
|
||||||
|
component = (typeof idx == "number") ? component.children.list[idx] : component.children.getByName(idx);
|
||||||
|
} else if (component instanceof Phaser.GameObjects.Container) {
|
||||||
|
component = (typeof idx == "number") ? component.list[idx] : component.getByName(idx);
|
||||||
|
} else {
|
||||||
|
component = null;
|
||||||
|
}
|
||||||
if (!component) {
|
if (!component) {
|
||||||
throw new Error(`Path not found: ${spath}`);
|
throw new Error(`Path not found: ${spath} (${idx} part)`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return [spath, component];
|
return [spath, component];
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkcomp(path: (number | string)[], ctype?: any, name?: string, attrs?: any): any {
|
function checkcomp<T extends Phaser.GameObjects.GameObject>(path: (number | string)[], ctype?: { new(...args: any[]): T }, name?: string, attrs?: Partial<T>): T {
|
||||||
let [spath, component] = get(path);
|
let [spath, component] = get(path);
|
||||||
|
|
||||||
if (typeof ctype != "undefined") {
|
if (typeof ctype != "undefined") {
|
||||||
|
@ -24,7 +30,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
test.check.equals(component.name, name, spath);
|
test.check.equals(component.name, name, spath);
|
||||||
}
|
}
|
||||||
if (typeof attrs != "undefined") {
|
if (typeof attrs != "undefined") {
|
||||||
iteritems(attrs, (key, value) => {
|
iteritems(<any>attrs, (key, value) => {
|
||||||
test.check.equals(component[key], value, spath);
|
test.check.equals(component[key], value, spath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -32,133 +38,145 @@ module TK.SpaceTac.UI.Specs {
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checktext(path: (number | string)[], attrs?: Partial<UIText>, style?: Partial<Phaser.GameObjects.Text.TextStyle>): UIText {
|
||||||
|
let text = checkcomp(path, UIText, "", attrs);
|
||||||
|
|
||||||
|
if (typeof style != "undefined") {
|
||||||
|
iteritems(<any>style, (key, value) => {
|
||||||
|
test.check.equals((<any>text.style)[key], value, `text style ${key}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
test.case("can work on view layers", check => {
|
test.case("can work on view layers", check => {
|
||||||
let builder = new UIBuilder(testgame.view, "tl1");
|
let builder = new UIBuilder(testgame.view, "tl1");
|
||||||
builder.group("tg1");
|
builder.container("tg1");
|
||||||
checkcomp(["View layers", "tl1", 0], Phaser.Group, "tg1");
|
checkcomp(["View layers", "tl1", 0], UIContainer, "tg1");
|
||||||
|
|
||||||
builder = new UIBuilder(testgame.view, "tl2");
|
builder = new UIBuilder(testgame.view, "tl2");
|
||||||
builder.group("tg2");
|
builder.container("tg2");
|
||||||
checkcomp(["View layers", "tl2", 0], Phaser.Group, "tg2");
|
checkcomp(["View layers", "tl2", 0], UIContainer, "tg2");
|
||||||
|
|
||||||
builder = new UIBuilder(testgame.view, "tl1");
|
builder = new UIBuilder(testgame.view, "tl1");
|
||||||
builder.group("tg3");
|
builder.container("tg3");
|
||||||
checkcomp(["View layers", "tl1", 0], Phaser.Group, "tg1");
|
checkcomp(["View layers", "tl1", 0], UIContainer, "tg1");
|
||||||
checkcomp(["View layers", "tl1", 1], Phaser.Group, "tg3");
|
checkcomp(["View layers", "tl1", 1], UIContainer, "tg3");
|
||||||
|
|
||||||
builder = new UIBuilder(testgame.view);
|
builder = new UIBuilder(testgame.view);
|
||||||
builder.group("tg4");
|
builder.container("tg4");
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Group, "tg4");
|
checkcomp(["View layers", "base", 0], UIContainer, "tg4");
|
||||||
|
|
||||||
builder = new UIBuilder(testgame.view);
|
builder = new UIBuilder(testgame.view);
|
||||||
builder.group("tg5");
|
builder.container("tg5");
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Group, "tg4");
|
checkcomp(["View layers", "base", 0], UIContainer, "tg4");
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Group, "tg5");
|
checkcomp(["View layers", "base", 1], UIContainer, "tg5");
|
||||||
|
|
||||||
check.equals(testgame.view.layers.children.map((child: any) => child.name), ["tl1", "tl2", "base"]);
|
check.equals(testgame.view.layers.list.map((child: any) => child.name), ["tl1", "tl2", "base"]);
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("creates component inside the parent container", check => {
|
test.case("creates component inside the parent container", check => {
|
||||||
let builder = new UIBuilder(testgame.view, testgame.view.getLayer("testlayer"));
|
let builder = new UIBuilder(testgame.view, testgame.view.getLayer("testlayer"));
|
||||||
let group = builder.group("test1");
|
let group = builder.container("test1");
|
||||||
checkcomp(["View layers", "testlayer", 0], Phaser.Group, "test1");
|
checkcomp(["View layers", "testlayer", 0], UIContainer, "test1");
|
||||||
|
|
||||||
builder = new UIBuilder(testgame.view, group);
|
builder = new UIBuilder(testgame.view, group);
|
||||||
builder.text("test2");
|
builder.text("test2");
|
||||||
checkcomp(["View layers", "testlayer", 0, 0], Phaser.Text, "", { text: "test2", parent: group });
|
checkcomp(["View layers", "testlayer", 0, 0], UIText, "", { text: "test2", parentContainer: group });
|
||||||
|
|
||||||
builder = new UIBuilder(testgame.view, "anothertestlayer");
|
builder = new UIBuilder(testgame.view, "anothertestlayer");
|
||||||
builder.text("test3");
|
builder.text("test3");
|
||||||
checkcomp(["View layers", "anothertestlayer", 0], Phaser.Text, "", { text: "test3" });
|
checkcomp(["View layers", "anothertestlayer", 0], UIText, "", { text: "test3" });
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("can clear a container", check => {
|
test.case("can clear a container", check => {
|
||||||
let builder = new UIBuilder(testgame.view);
|
let builder = new UIBuilder(testgame.view);
|
||||||
builder.group("group1", 50, 30);
|
builder.container("group1", 50, 30);
|
||||||
builder.text("text1");
|
builder.text("text1");
|
||||||
let [spath, container] = get(["View layers", "base"]);
|
let [spath, container] = get(["View layers", "base"]);
|
||||||
check.equals(container.children.length, 2);
|
if (check.instance(container, UIContainer, "is a container")) {
|
||||||
builder.clear();
|
check.equals(container.list.length, 2);
|
||||||
check.equals(container.children.length, 0);
|
builder.clear();
|
||||||
|
check.equals(container.list.length, 0);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("can create groups", check => {
|
test.case("can create containers", check => {
|
||||||
let builder = new UIBuilder(testgame.view);
|
let builder = new UIBuilder(testgame.view);
|
||||||
builder.group("group1", 50, 30);
|
builder.container("group1", 50, 30);
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Group, "group1", { x: 50, y: 30 });
|
checkcomp(["View layers", "base", 0], UIContainer, "group1", { x: 50, y: 30 });
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("can create texts", check => {
|
test.case("can create texts", check => {
|
||||||
let builder = new UIBuilder(testgame.view);
|
let builder = new UIBuilder(testgame.view);
|
||||||
builder.text("Test content", 12, 41);
|
builder.text("Test content", 12, 41);
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { text: "Test content", x: 12, y: 41 });
|
checktext(["View layers", "base", 0], { text: "Test content", x: 12, y: 41 });
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
builder.text("", 0, 0, {});
|
builder.text("", 0, 0, {});
|
||||||
builder.text("", 0, 0, { size: 61 });
|
builder.text("", 0, 0, { size: 61 });
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { cssFont: "16pt 'SpaceTac'" });
|
checktext(["View layers", "base", 0], undefined, { fontFamily: "16pt SpaceTac" });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { cssFont: "61pt 'SpaceTac'" });
|
checktext(["View layers", "base", 1], undefined, { fontFamily: "61pt SpaceTac" });
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
builder.text("", 0, 0, {});
|
builder.text("", 0, 0, {});
|
||||||
builder.text("", 0, 0, { color: "#252627" });
|
builder.text("", 0, 0, { color: "#252627" });
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { fill: "#ffffff" });
|
checktext(["View layers", "base", 0], undefined, { color: "#ffffff" });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { fill: "#252627" });
|
checktext(["View layers", "base", 1], undefined, { color: "#252627" });
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
builder.text("", 0, 0, {});
|
builder.text("", 0, 0, {});
|
||||||
builder.text("", 0, 0, { shadow: true });
|
builder.text("", 0, 0, { shadow: true });
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { shadowColor: "rgba(0,0,0,0)" });
|
checktext(["View layers", "base", 0], undefined, { shadowColor: "#000", shadowFill: false, shadowStroke: false });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { shadowColor: "rgba(0,0,0,0.6)", shadowFill: true, shadowOffsetX: 3, shadowOffsetY: 4, shadowBlur: 3, shadowStroke: true });
|
checktext(["View layers", "base", 1], undefined, { shadowColor: "rgba(0,0,0,0.6)", shadowFill: true, shadowOffsetX: 3, shadowOffsetY: 4, shadowBlur: 3, shadowStroke: true });
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
builder.text("", 0, 0, {});
|
builder.text("", 0, 0, {});
|
||||||
builder.text("", 0, 0, { stroke_width: 2, stroke_color: "#ff0000" });
|
builder.text("", 0, 0, { stroke_width: 2, stroke_color: "#ff0000" });
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { stroke: "black", strokeThickness: 0 });
|
checktext(["View layers", "base", 0], undefined, { stroke: "#fff", strokeThickness: 0 });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { stroke: "#ff0000", strokeThickness: 2 });
|
checktext(["View layers", "base", 1], undefined, { stroke: "#ff0000", strokeThickness: 2 });
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
builder.text("", 0, 0, {});
|
builder.text("", 0, 0, {});
|
||||||
builder.text("", 0, 0, { bold: true });
|
builder.text("", 0, 0, { bold: true });
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { fontWeight: "normal" });
|
checktext(["View layers", "base", 0], undefined, { fontFamily: "16pt SpaceTac" });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { fontWeight: "bold" });
|
checktext(["View layers", "base", 1], undefined, { fontFamily: "bold 16pt SpaceTac" });
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
builder.text("", 0, 0, {});
|
builder.text("", 0, 0, {});
|
||||||
builder.text("", 0, 0, { center: false });
|
builder.text("", 0, 0, { center: false });
|
||||||
builder.text("", 0, 0, { vcenter: false });
|
builder.text("", 0, 0, { vcenter: false });
|
||||||
builder.text("", 0, 0, { center: false, vcenter: false });
|
builder.text("", 0, 0, { center: false, vcenter: false });
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { anchor: new Phaser.Point(0.5, 0.5), align: "center" });
|
checktext(["View layers", "base", 0], { originX: 0.5, originY: 0.5 }, { align: "center" });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { anchor: new Phaser.Point(0, 0.5), align: "left" });
|
checktext(["View layers", "base", 1], { originX: 0, originY: 0.5 }, { align: "left" });
|
||||||
checkcomp(["View layers", "base", 2], Phaser.Text, "", { anchor: new Phaser.Point(0.5, 0), align: "center" });
|
checktext(["View layers", "base", 2], { originX: 0.5, originY: 0 }, { align: "center" });
|
||||||
checkcomp(["View layers", "base", 3], Phaser.Text, "", { anchor: new Phaser.Point(0, 0), align: "left" });
|
checktext(["View layers", "base", 3], { originX: 0, originY: 0 }, { align: "left" });
|
||||||
|
|
||||||
builder.clear();
|
builder.clear();
|
||||||
builder.text("", 0, 0, { width: 0 });
|
builder.text("", 0, 0, { width: 0 });
|
||||||
builder.text("", 0, 0, { width: 1100 });
|
builder.text("", 0, 0, { width: 1100 });
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { wordWrap: false });
|
checktext(["View layers", "base", 0], undefined, <any>{ wordWrapWidth: null });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { wordWrap: true, wordWrapWidth: 1100 });
|
checktext(["View layers", "base", 1], undefined, <any>{ wordWrapWidth: 1100 });
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("can create images", check => {
|
test.case("can create images", check => {
|
||||||
let builder = new UIBuilder(testgame.view);
|
let builder = new UIBuilder(testgame.view);
|
||||||
builder.image("test-image", 100, 50);
|
builder.image("test-image", 100, 50);
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Image, "test-image", { x: 100, y: 50, key: "__missing", inputEnabled: null });
|
checkcomp(["View layers", "base", 0], UIImage, "test-image", { x: 100, y: 50 });
|
||||||
|
|
||||||
check.patch(testgame.view, "getFirstImage", (...images: string[]) => images[1]);
|
check.patch(testgame.view, "getFirstImage", (...images: string[]) => images[1]);
|
||||||
builder.image(["test-image1", "test-image2", "test-image3"]);
|
builder.image(["test-image1", "test-image2", "test-image3"]);
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Image, "test-image2");
|
checkcomp(["View layers", "base", 1], UIImage, "test-image2");
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("can create buttons", check => {
|
test.case("can create buttons", check => {
|
||||||
let builder = new UIBuilder(testgame.view);
|
let builder = new UIBuilder(testgame.view);
|
||||||
let a = 1;
|
let a = 1;
|
||||||
let button1 = builder.button("test-image1", 100, 50, () => a += 1);
|
let button1 = builder.button("test-image1", 100, 50, () => a += 1);
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Button, "test-image1", { x: 100, y: 50, key: "__missing", inputEnabled: true });
|
checkcomp(["View layers", "base", 0], UIButton, "test-image1", { x: 100, y: 50 });
|
||||||
check.same(button1.input.useHandCursor, true, "button1 should use hand cursor");
|
|
||||||
let button2 = builder.button("test-image2", 20, 10);
|
let button2 = builder.button("test-image2", 20, 10);
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Button, "test-image2", { x: 20, y: 10, key: "__missing", inputEnabled: true });
|
checkcomp(["View layers", "base", 1], UIButton, "test-image2", { x: 20, y: 10 });
|
||||||
check.same(button2.input.useHandCursor, false, "button2 should not use hand cursor");
|
|
||||||
|
|
||||||
check.equals(a, 1);
|
check.equals(a, 1);
|
||||||
testClick(button1);
|
testClick(button1);
|
||||||
|
@ -169,81 +187,18 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.equals(a, 3);
|
check.equals(a, 3);
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("can create toggle buttons", check => {
|
|
||||||
let builder = new UIBuilder(testgame.view);
|
|
||||||
|
|
||||||
let mock = check.mockfunc("identity", (x: any) => x);
|
|
||||||
let button1 = builder.button("test-image1", 0, 0, undefined, undefined, <any>mock.func);
|
|
||||||
let button2 = builder.button("test-image2");
|
|
||||||
|
|
||||||
let result = builder.switch(button2, true);
|
|
||||||
check.equals(result, false, "button2");
|
|
||||||
check.called(mock, 0);
|
|
||||||
testClick(button2);
|
|
||||||
check.called(mock, 0);
|
|
||||||
|
|
||||||
check.in("button1 on", check => {
|
|
||||||
result = builder.switch(button1, true);
|
|
||||||
check.equals(result, true);
|
|
||||||
check.called(mock, [[true]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
check.in("button1 off", check => {
|
|
||||||
result = builder.switch(button1, false);
|
|
||||||
check.equals(result, false);
|
|
||||||
check.called(mock, [[false]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
check.in("button1 first click", check => {
|
|
||||||
testClick(button1);
|
|
||||||
check.called(mock, [[true]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
check.in("button1 second click", check => {
|
|
||||||
testClick(button1);
|
|
||||||
check.called(mock, [[false]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.case("can create shaders", check => {
|
|
||||||
let builder = new UIBuilder(testgame.view);
|
|
||||||
|
|
||||||
let shader1 = builder.shader("test-shader-1", "test-image-1");
|
|
||||||
check.equals(shader1 instanceof Phaser.Image, true);
|
|
||||||
check.equals(shader1.name, "test-image-1");
|
|
||||||
check.same(shader1.filters.length, 1, "one filter set on shader1");
|
|
||||||
|
|
||||||
let shader2 = builder.shader("test-shader-2", { width: 500, height: 300 });
|
|
||||||
check.equals(shader2 instanceof Phaser.Image, true);
|
|
||||||
/*check.equals(shader2.width, 500);
|
|
||||||
check.equals(shader2.height, 300);*/ // FIXME randomly fail on karma
|
|
||||||
check.same(shader2.filters.length, 1, "one filter set on shader2");
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
let shader3 = builder.shader("test-shader-3", "test-image-3", 50, 30, () => { return { a: i++, b: { x: 1, y: 2 } } });
|
|
||||||
check.equals(shader3.x, 50);
|
|
||||||
check.equals(shader3.y, 30);
|
|
||||||
check.equals(shader3.filters[0].uniforms["a"], { type: '1f', value: 0 }, "uniform a initial");
|
|
||||||
check.equals(shader3.filters[0].uniforms["b"], { type: '2f', value: Object({ x: 1, y: 2 }) }, "uniform b initial");
|
|
||||||
shader3.update();
|
|
||||||
check.equals(shader3.filters[0].uniforms["a"], { type: '1f', value: 1 }, "uniform a updated");
|
|
||||||
check.equals(shader3.filters[0].uniforms["b"], { type: '2f', value: Object({ x: 1, y: 2 }) }, "uniform b updated");
|
|
||||||
|
|
||||||
check.same(testgame.view.getLayer("base").children.length, 3, "view layer should have three children");
|
|
||||||
})
|
|
||||||
|
|
||||||
test.case("creates sub-builders, preserving text style", check => {
|
test.case("creates sub-builders, preserving text style", check => {
|
||||||
let base_style = new UITextStyle();
|
let base_style = new UITextStyle();
|
||||||
base_style.width = 123;
|
base_style.width = 123;
|
||||||
let builder = new UIBuilder(testgame.view, undefined, base_style);
|
let builder = new UIBuilder(testgame.view, undefined, base_style);
|
||||||
builder.text("Test 1");
|
builder.text("Test 1");
|
||||||
|
|
||||||
let group = builder.group("testgroup");
|
let group = builder.container("testgroup");
|
||||||
let subbuilder = builder.in(group);
|
let subbuilder = builder.in(group);
|
||||||
subbuilder.text("Test 2");
|
subbuilder.text("Test 2");
|
||||||
|
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { text: "Test 1", wordWrapWidth: 123 });
|
checktext(["View layers", "base", 0], { text: "Test 1" }, <any>{ wordWrapWidth: 123 });
|
||||||
checkcomp(["View layers", "base", 1, 0], Phaser.Text, "", { text: "Test 2", wordWrapWidth: 123 });
|
checktext(["View layers", "base", 1, 0], { text: "Test 2" }, <any>{ wordWrapWidth: 123 });
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("allows to alter text style", check => {
|
test.case("allows to alter text style", check => {
|
||||||
|
@ -253,38 +208,34 @@ module TK.SpaceTac.UI.Specs {
|
||||||
builder.text("t3");
|
builder.text("t3");
|
||||||
builder.text("t4", undefined, undefined, { bold: true });
|
builder.text("t4", undefined, undefined, { bold: true });
|
||||||
|
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { text: "t1", fontWeight: "normal" });
|
checktext(["View layers", "base", 0], { text: "t1" }, { fontFamily: "16pt SpaceTac" });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Text, "", { text: "t2", fontWeight: "bold" });
|
checktext(["View layers", "base", 1], { text: "t2" }, { fontFamily: "bold 16pt SpaceTac" });
|
||||||
checkcomp(["View layers", "base", 2], Phaser.Text, "", { text: "t3", fontWeight: "normal" });
|
checktext(["View layers", "base", 2], { text: "t3" }, { fontFamily: "16pt SpaceTac" });
|
||||||
checkcomp(["View layers", "base", 3], Phaser.Text, "", { text: "t4", fontWeight: "bold" });
|
checktext(["View layers", "base", 3], { text: "t4" }, { fontFamily: "bold 16pt SpaceTac" });
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("allows to change text, image or button content", check => {
|
test.case("allows to change text or image content", check => {
|
||||||
let builder = new UIBuilder(testgame.view);
|
let builder = new UIBuilder(testgame.view);
|
||||||
let text = builder.text("test-text");
|
let text = builder.text("test-text");
|
||||||
let image = builder.image("test-image");
|
let image = builder.image("test-image");
|
||||||
let button = builder.button("test-button");
|
|
||||||
|
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { text: "test-text" });
|
checkcomp(["View layers", "base", 0], UIText, "", { text: "test-text" });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Image, "test-image");
|
checkcomp(["View layers", "base", 1], UIImage, "test-image");
|
||||||
checkcomp(["View layers", "base", 2], Phaser.Button, "test-button");
|
|
||||||
|
|
||||||
builder.change(text, "test-mod-text");
|
builder.change(text, "test-mod-text");
|
||||||
builder.change(image, "test-mod-image");
|
builder.change(image, "test-mod-image");
|
||||||
builder.change(button, "test-mod-button");
|
|
||||||
|
|
||||||
checkcomp(["View layers", "base", 0], Phaser.Text, "", { text: "test-mod-text" });
|
checkcomp(["View layers", "base", 0], UIText, "", { text: "test-mod-text" });
|
||||||
checkcomp(["View layers", "base", 1], Phaser.Image, "test-mod-image");
|
checkcomp(["View layers", "base", 1], UIImage, "test-mod-image");
|
||||||
checkcomp(["View layers", "base", 2], Phaser.Button, "test-mod-button");
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test.case("distributes children along an axis", check => {
|
test.case("distributes children along an axis", check => {
|
||||||
let builder = new UIBuilder(testgame.view);
|
let builder = new UIBuilder(testgame.view);
|
||||||
builder = builder.in(builder.group("test"));
|
builder = builder.in(builder.container("test"));
|
||||||
|
|
||||||
let c1 = builder.text("");
|
let c1 = builder.text("");
|
||||||
let c2 = builder.button("test");
|
let c2 = builder.button("test");
|
||||||
let c3 = builder.group("test");
|
let c3 = builder.container("test");
|
||||||
|
|
||||||
check.equals(c1.x, 0);
|
check.equals(c1.x, 0);
|
||||||
check.equals(c1.y, 0);
|
check.equals(c1.y, 0);
|
||||||
|
@ -293,7 +244,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.equals(c3.x, 0);
|
check.equals(c3.x, 0);
|
||||||
check.equals(c3.y, 0);
|
check.equals(c3.y, 0);
|
||||||
|
|
||||||
check.patch(UITools, "getScreenBounds", (obj: any) => {
|
check.patch(UITools, "getBounds", (obj: any) => {
|
||||||
if (obj === c1) {
|
if (obj === c1) {
|
||||||
return { x: 0, y: 0, width: 100, height: 51 };
|
return { x: 0, y: 0, width: 100, height: 51 };
|
||||||
} else if (obj === c2) {
|
} else if (obj === c2) {
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
* Main way to create UI components
|
* Main way to create UI components
|
||||||
*/
|
*/
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
export type UIText = Phaser.Text
|
export type UIParticles = Phaser.GameObjects.Particles.ParticleEmitterManager
|
||||||
export type UIImage = Phaser.Image
|
export type UIBuilderParent = UIImage | UIContainer
|
||||||
export type UIButton = Phaser.Button
|
|
||||||
export type UIGroup = Phaser.Group
|
|
||||||
export type UIContainer = Phaser.Group | Phaser.Image
|
|
||||||
|
|
||||||
export type ShaderValue = number | { x: number, y: number }
|
export type ShaderValue = number | { x: number, y: number }
|
||||||
export type UIOnOffCallback = (on: boolean) => boolean
|
export type UIOnOffCallback = (on: boolean) => boolean
|
||||||
|
@ -54,49 +51,16 @@ module TK.SpaceTac.UI {
|
||||||
width = 0
|
width = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Button options
|
|
||||||
*/
|
|
||||||
export type UIButtonOptions = {
|
|
||||||
// Centering
|
|
||||||
center?: boolean
|
|
||||||
|
|
||||||
// Name of the hover picture (by default, the button name, with "-hover" appended)
|
|
||||||
hover_name?: string
|
|
||||||
|
|
||||||
// Name of the "on" picture (by default, the button name, with "-on" appended)
|
|
||||||
on_name?: string
|
|
||||||
|
|
||||||
// Whether "hover" picture should stay near the button (otherwise will be on top)
|
|
||||||
hover_bottom?: boolean
|
|
||||||
|
|
||||||
// Whether "on" picture should stay near the button (otherwise will be on top)
|
|
||||||
on_bottom?: boolean
|
|
||||||
|
|
||||||
// Text content
|
|
||||||
text?: string
|
|
||||||
text_x?: number
|
|
||||||
text_y?: number
|
|
||||||
|
|
||||||
// Text content style override
|
|
||||||
text_style?: UITextStyleI
|
|
||||||
|
|
||||||
// Icon content
|
|
||||||
icon?: string
|
|
||||||
icon_x?: number
|
|
||||||
icon_y?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main UI builder tool
|
* Main UI builder tool
|
||||||
*/
|
*/
|
||||||
export class UIBuilder {
|
export class UIBuilder {
|
||||||
view: BaseView
|
view: BaseView
|
||||||
private game: MainUI
|
private game: MainUI
|
||||||
private parent: UIContainer
|
private parent: UIBuilderParent
|
||||||
private text_style: UITextStyle
|
private text_style: UITextStyleI
|
||||||
|
|
||||||
constructor(view: BaseView, parent: UIContainer | string = "base", text_style = new UITextStyle) {
|
constructor(view: BaseView, parent: UIBuilderParent | string = "base", text_style: UITextStyleI = new UITextStyle) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.game = view.gameui;
|
this.game = view.gameui;
|
||||||
if (typeof parent == "string") {
|
if (typeof parent == "string") {
|
||||||
|
@ -112,7 +76,7 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* This new builder will inherit the style settings, and will create components in the specified parent
|
* This new builder will inherit the style settings, and will create components in the specified parent
|
||||||
*/
|
*/
|
||||||
in(container: UIContainer | string, body?: (builder: UIBuilder) => void): UIBuilder {
|
in(container: UIBuilderParent | string, body?: (builder: UIBuilder) => void): UIBuilder {
|
||||||
let result = new UIBuilder(this.view, container, this.text_style);
|
let result = new UIBuilder(this.view, container, this.text_style);
|
||||||
if (body) {
|
if (body) {
|
||||||
body(result);
|
body(result);
|
||||||
|
@ -135,35 +99,39 @@ module TK.SpaceTac.UI {
|
||||||
* Clear the current container of all component
|
* Clear the current container of all component
|
||||||
*/
|
*/
|
||||||
clear(): void {
|
clear(): void {
|
||||||
destroyChildren(this.parent);
|
if (this.parent instanceof UIImage) {
|
||||||
|
console.error("Cannot clear an image parent, use groups instead");
|
||||||
|
} else {
|
||||||
|
this.parent.removeAll(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal method to add to the parent
|
* Internal method to add to the parent
|
||||||
*/
|
*/
|
||||||
private add(child: UIText | UIImage | UIButton | UIContainer): void {
|
private add(child: UIText | UIImage | UIButton | UIContainer | UIGraphics): void {
|
||||||
if (this.parent instanceof Phaser.Group) {
|
if (this.parent instanceof UIImage) {
|
||||||
this.parent.add(child);
|
let gparent = this.parent.parentContainer;
|
||||||
} else if (this.parent instanceof Phaser.Button) {
|
if (gparent) {
|
||||||
// Protect the "on" and "hover" layers
|
let x = this.parent.x + child.x;
|
||||||
let layer = first(this.parent.children, child => child instanceof Phaser.Image && (child.name == "*on*" || child.name == "*hover*"));
|
let y = this.parent.y + child.y;
|
||||||
if (layer) {
|
child.setPosition(x, y);
|
||||||
this.parent.addChildAt(child, this.parent.getChildIndex(layer));
|
gparent.add(child);
|
||||||
} else {
|
} else {
|
||||||
this.parent.addChild(child);
|
throw new Error("no parent container");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.parent.addChild(child);
|
this.parent.add(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a group of components
|
* Add a container of other components
|
||||||
*/
|
*/
|
||||||
group(name: string, x = 0, y = 0, visible = true): UIGroup {
|
container(name: string, x = 0, y = 0, visible = true): UIContainer {
|
||||||
let result = new Phaser.Group(this.game, undefined, name);
|
let result = new UIContainer(this.view, x, y);
|
||||||
result.position.set(x, y);
|
result.setName(name);
|
||||||
result.visible = visible;
|
result.setVisible(visible);
|
||||||
this.add(result);
|
this.add(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -175,22 +143,20 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
text(content: string, x = 0, y = 0, style_changes: UITextStyleI = {}): UIText {
|
text(content: string, x = 0, y = 0, style_changes: UITextStyleI = {}): UIText {
|
||||||
let style = merge(this.text_style, style_changes);
|
let style = merge(this.text_style, style_changes);
|
||||||
let result = new Phaser.Text(this.game, x, y, content, {
|
let result = new UIText(this.view, x, y, content, {
|
||||||
font: `${style.bold ? "bold " : ""}${style.size}pt SpaceTac`,
|
|
||||||
fill: style.color,
|
fill: style.color,
|
||||||
align: style.center ? "center" : "left"
|
align: style.center ? "center" : "left"
|
||||||
});
|
});
|
||||||
result.anchor.set(style.center ? 0.5 : 0, style.vcenter ? 0.5 : 0);
|
result.setFont(`${style.bold ? "bold " : ""}${style.size}pt SpaceTac`);
|
||||||
|
result.setOrigin(style.center ? 0.5 : 0, style.vcenter ? 0.5 : 0);
|
||||||
if (style.width) {
|
if (style.width) {
|
||||||
result.wordWrap = true;
|
result.setWordWrapWidth(style.width);
|
||||||
result.wordWrapWidth = style.width;
|
|
||||||
}
|
}
|
||||||
if (style.shadow) {
|
if (style.shadow) {
|
||||||
result.setShadow(3, 4, "rgba(0,0,0,0.6)", 3);
|
result.setShadow(3, 4, "rgba(0,0,0,0.6)", 3, true, true);
|
||||||
}
|
}
|
||||||
if (style.stroke_width) {
|
if (style.stroke_width && style.stroke_color) {
|
||||||
result.stroke = style.stroke_color;
|
result.setStroke(style.stroke_color, style.stroke_width);
|
||||||
result.strokeThickness = style.stroke_width;
|
|
||||||
}
|
}
|
||||||
this.add(result);
|
this.add(result);
|
||||||
return result;
|
return result;
|
||||||
|
@ -205,10 +171,10 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = this.view.getImageInfo(name);
|
let info = this.view.getImageInfo(name);
|
||||||
let result = this.game.add.image(x, y, info.key, info.frame);
|
let result = new UIImage(this.view, x, y, info.key, info.frame);
|
||||||
result.name = name;
|
result.name = name;
|
||||||
if (centered) {
|
if (!centered) {
|
||||||
result.anchor.set(0.5);
|
result.setOrigin(0);
|
||||||
}
|
}
|
||||||
this.add(result);
|
this.add(result);
|
||||||
return result;
|
return result;
|
||||||
|
@ -220,89 +186,8 @@ module TK.SpaceTac.UI {
|
||||||
* If an image with "-hover" suffix is found in atlases, it will be used as hover mask (added as button child)
|
* If an image with "-hover" suffix is found in atlases, it will be used as hover mask (added as button child)
|
||||||
*/
|
*/
|
||||||
button(name: string, x = 0, y = 0, onclick?: Function, tooltip?: TooltipFiller, onoffcallback?: UIOnOffCallback, options: UIButtonOptions = {}): UIButton {
|
button(name: string, x = 0, y = 0, onclick?: Function, tooltip?: TooltipFiller, onoffcallback?: UIOnOffCallback, options: UIButtonOptions = {}): UIButton {
|
||||||
let info = this.view.getImageInfo(name);
|
options.text_style = merge(this.text_style, options.text_style || {});
|
||||||
let result = new Phaser.Button(this.game, x, y, info.key, undefined, null, info.frame, info.frame);
|
let result = new UIButton(this.view, name, x, y, onclick, tooltip, onoffcallback, options);
|
||||||
result.name = name;
|
|
||||||
|
|
||||||
if (options.center) {
|
|
||||||
result.anchor.set(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
let clickable = bool(onclick);
|
|
||||||
result.input.useHandCursor = clickable;
|
|
||||||
if (clickable) {
|
|
||||||
UIComponent.setButtonSound(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
let onstatus = false;
|
|
||||||
|
|
||||||
if (clickable || tooltip || onoffcallback) {
|
|
||||||
// On mask
|
|
||||||
let on_mask: Phaser.Image | null = null;
|
|
||||||
if (onoffcallback) {
|
|
||||||
let on_info = this.view.getImageInfo(options.on_name || (name + "-on"));
|
|
||||||
if (on_info.exists) {
|
|
||||||
on_mask = new Phaser.Image(this.game, 0, 0, on_info.key, on_info.frame);
|
|
||||||
on_mask.name = options.on_bottom ? "on" : "*on*";
|
|
||||||
on_mask.visible = false;
|
|
||||||
result.addChild(on_mask);
|
|
||||||
}
|
|
||||||
// TODO Find a better way to handle this (extend Button ?)
|
|
||||||
result.data.onoffcallback = (on: boolean): boolean => {
|
|
||||||
onstatus = onoffcallback(on);
|
|
||||||
if (on_mask) {
|
|
||||||
on_mask.anchor.set(result.anchor.x, result.anchor.y);
|
|
||||||
this.view.animations.setVisible(on_mask, onstatus, 100);
|
|
||||||
}
|
|
||||||
return onstatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hover mask
|
|
||||||
let hover_info = this.view.getImageInfo(options.hover_name || (name + "-hover"));
|
|
||||||
let hover_mask: Phaser.Image | null = null;
|
|
||||||
if (hover_info.exists) {
|
|
||||||
hover_mask = new Phaser.Image(this.game, 0, 0, hover_info.key, hover_info.frame);
|
|
||||||
hover_mask.name = options.hover_bottom ? "hover" : "*hover*";
|
|
||||||
hover_mask.visible = false;
|
|
||||||
result.addChild(hover_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.view.inputs.setHoverClick(result,
|
|
||||||
() => {
|
|
||||||
if (tooltip) {
|
|
||||||
this.view.tooltip.show(result, tooltip);
|
|
||||||
}
|
|
||||||
if (hover_mask) {
|
|
||||||
hover_mask.anchor.set(result.anchor.x, result.anchor.y);
|
|
||||||
this.view.animations.show(hover_mask, 100);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (tooltip) {
|
|
||||||
this.view.tooltip.hide();
|
|
||||||
}
|
|
||||||
if (hover_mask) {
|
|
||||||
this.view.animations.hide(hover_mask, 100)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (onclick) {
|
|
||||||
onclick();
|
|
||||||
} else if (onoffcallback) {
|
|
||||||
this.switch(result, !onstatus);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.text) {
|
|
||||||
this.in(result).text(options.text, options.text_x || 0, options.text_y || 0, options.text_style);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.icon) {
|
|
||||||
this.in(result).image(options.icon, options.icon_x || 0, options.icon_y || 0, options.center);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.add(result);
|
this.add(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -317,101 +202,61 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a fragment shader area, with optional fallback image
|
* Add a graphics (for drawing)
|
||||||
*/
|
*/
|
||||||
shader(name: string, base: string | { width: number, height: number }, x = 0, y = 0, updater?: () => { [name: string]: ShaderValue }): UIImage {
|
graphics(name: string, x = 0, y = 0, visible = true): UIGraphics {
|
||||||
let source = this.game.cache.getShader(name);
|
let result = new UIGraphics(this.view, name, visible, x, y);
|
||||||
source = "" + source;
|
this.add(result);
|
||||||
let uniforms: any = {};
|
|
||||||
if (updater) {
|
|
||||||
iteritems(updater(), (key, value) => {
|
|
||||||
uniforms[key] = { type: (typeof value == "number") ? "1f" : "2f", value: value };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let filter = new Phaser.Filter(this.game, uniforms, source);
|
|
||||||
let result: Phaser.Image;
|
|
||||||
if (typeof base == "string") {
|
|
||||||
result = this.image(base, x, y);
|
|
||||||
result.filters = [filter];
|
|
||||||
filter.setResolution(result.width, result.height);
|
|
||||||
} else {
|
|
||||||
result = filter.addToWorld(x, y, base.width, base.height);
|
|
||||||
this.add(result);
|
|
||||||
}
|
|
||||||
if (updater) {
|
|
||||||
result.update = () => {
|
|
||||||
iteritems(updater(), (key, value) => filter.uniforms[key].value = value);
|
|
||||||
filter.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filter.update();
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a bunch of particles
|
||||||
|
*/
|
||||||
|
particles(config: ParticlesConfig): void {
|
||||||
|
this.view.particles.emit(config, this.parent instanceof UIContainer ? this.parent : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the content of an component
|
* Change the content of an component
|
||||||
*
|
*
|
||||||
* If the component is a text, its content will be changed.
|
* If the component is a text, its content will be changed.
|
||||||
* If the component is an image or button, its texture will be changed.
|
* If the component is an image, its texture will be changed.
|
||||||
*/
|
*/
|
||||||
change(component: UIImage | UIButton | UIText, content: string): void {
|
change(component: UIImage | UIText, content: string): void {
|
||||||
if (component instanceof Phaser.Text) {
|
// TODO Should be moved custom UIImage and UIText classes
|
||||||
component.text = content;
|
if (component instanceof UIText) {
|
||||||
|
component.setText(content);
|
||||||
} else {
|
} else {
|
||||||
let info = this.view.getImageInfo(content);
|
let info = this.view.getImageInfo(content);
|
||||||
component.name = content;
|
component.setName(content);
|
||||||
if (component instanceof Phaser.Button) {
|
component.setTexture(info.key, info.frame);
|
||||||
component.loadTexture(info.key);
|
|
||||||
component.setFrames(info.frame, info.frame);
|
|
||||||
} else {
|
|
||||||
component.loadTexture(info.key, info.frame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the status on/off on a button
|
|
||||||
*
|
|
||||||
* Return the final effective status
|
|
||||||
*/
|
|
||||||
switch(button: UIButton, on: boolean): boolean {
|
|
||||||
if (button.data.onoffcallback) {
|
|
||||||
return button.data.onoffcallback(on);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a single button inside the container, toggle its "on" status, and toggle all other button to "off"
|
|
||||||
*
|
|
||||||
* This is the equivalent of radio buttons
|
|
||||||
*/
|
|
||||||
select(button: UIButton): void {
|
|
||||||
this.parent.children.forEach(child => {
|
|
||||||
if (child instanceof Phaser.Button && child.data.onoffcallback && child !== button) {
|
|
||||||
child.data.onoffcallback(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.switch(button, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evenly distribute the children of this builder along an axis
|
* Evenly distribute the children of this builder along an axis
|
||||||
*/
|
*/
|
||||||
distribute(along: "x" | "y", start: number, end: number): void {
|
distribute(along: "x" | "y", start: number, end: number): void {
|
||||||
let sizes = this.parent.children.map(child => {
|
if (!(this.parent instanceof UIContainer)) {
|
||||||
if (child instanceof Phaser.Image || child instanceof Phaser.Sprite || child instanceof Phaser.Group) {
|
throw new Error("UIBuilder.distribute only works on groups");
|
||||||
return UITools.getScreenBounds(child)[along == "x" ? "width" : "height"];
|
}
|
||||||
|
let children = this.parent.list;
|
||||||
|
|
||||||
|
let sizes = children.map(child => {
|
||||||
|
if (UITools.isSpatial(child)) {
|
||||||
|
return UITools.getBounds(child)[along == "x" ? "width" : "height"];
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let spacing = ((end - start) - sum(sizes)) / (sizes.length + 1);
|
let spacing = ((end - start) - sum(sizes)) / (sizes.length + 1);
|
||||||
let offset = start;
|
let offset = start;
|
||||||
this.parent.children.forEach((child, idx) => {
|
children.forEach((child, idx) => {
|
||||||
offset += spacing;
|
offset += spacing;
|
||||||
child[along] = Math.round(offset);
|
if (UITools.isSpatial(child)) {
|
||||||
|
child[along] = Math.round(offset);
|
||||||
|
}
|
||||||
offset += sizes[idx];
|
offset += sizes[idx];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
115
src/ui/common/UIButton.spec.ts
Normal file
115
src/ui/common/UIButton.spec.ts
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
module TK.SpaceTac.UI.Specs {
|
||||||
|
testing("UIButton", test => {
|
||||||
|
let testgame = setupEmptyView(test);
|
||||||
|
|
||||||
|
test.case("handles placement of masks", check => {
|
||||||
|
check.patch(testgame.view, "getImageInfo", (name: string) => ({ key: "test", frame: 0, exists: true }));
|
||||||
|
let builder = new UIBuilder(testgame.view);
|
||||||
|
|
||||||
|
check.in("both on top", check => {
|
||||||
|
let button = builder.button("button", 0, 0, nop, undefined, identity);
|
||||||
|
check.equals(button.length, 3);
|
||||||
|
builder.in(button).text("Test");
|
||||||
|
check.equals(button.length, 4);
|
||||||
|
check.equals(button.list[0].name, "button");
|
||||||
|
check.equals(button.list[1].type, "Text");
|
||||||
|
check.equals(button.list[2].name, "button-on");
|
||||||
|
check.equals(button.list[3].name, "button-hover");
|
||||||
|
});
|
||||||
|
|
||||||
|
check.in("hover at bottom", check => {
|
||||||
|
let button = builder.button("button", 0, 0, nop, undefined, identity, { hover_bottom: true });
|
||||||
|
check.equals(button.length, 3);
|
||||||
|
builder.in(button).text("Test");
|
||||||
|
check.equals(button.length, 4);
|
||||||
|
check.equals(button.list[0].name, "button");
|
||||||
|
check.equals(button.list[1].name, "button-hover");
|
||||||
|
check.equals(button.list[2].type, "Text");
|
||||||
|
check.equals(button.list[3].name, "button-on");
|
||||||
|
});
|
||||||
|
|
||||||
|
check.in("'on' at bottom", check => {
|
||||||
|
let button = builder.button("button", 0, 0, nop, undefined, identity, { on_bottom: true });
|
||||||
|
check.equals(button.length, 3);
|
||||||
|
builder.in(button).text("Test");
|
||||||
|
check.equals(button.length, 4);
|
||||||
|
check.equals(button.list[0].name, "button");
|
||||||
|
check.equals(button.list[1].name, "button-on");
|
||||||
|
check.equals(button.list[2].type, "Text");
|
||||||
|
check.equals(button.list[3].name, "button-hover");
|
||||||
|
});
|
||||||
|
|
||||||
|
check.in("both at bottom", check => {
|
||||||
|
let button = builder.button("button", 0, 0, nop, undefined, identity, { hover_bottom: true, on_bottom: true });
|
||||||
|
check.equals(button.length, 3);
|
||||||
|
builder.in(button).text("Test");
|
||||||
|
check.equals(button.length, 4);
|
||||||
|
check.equals(button.list[0].name, "button");
|
||||||
|
check.equals(button.list[1].name, "button-on");
|
||||||
|
check.equals(button.list[2].name, "button-hover");
|
||||||
|
check.equals(button.list[3].type, "Text");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.case("toggles on/off", check => {
|
||||||
|
let builder = new UIBuilder(testgame.view);
|
||||||
|
let m1 = check.mockfunc("m1", (on: boolean) => on);
|
||||||
|
let button1 = builder.button("b1", 0, 0, undefined, undefined, m1.func);
|
||||||
|
let m2 = check.mockfunc("m1", (on: boolean) => on);
|
||||||
|
let button2 = builder.button("b2", 0, 0, undefined, undefined, m2.func);
|
||||||
|
let m3 = check.mockfunc("m1", (on: boolean) => on);
|
||||||
|
let button3 = builder.button("b3", 0, 0, undefined, undefined, m3.func);
|
||||||
|
|
||||||
|
function verify(message: string, state1: boolean, state2: boolean, state3: boolean, called1: number, called2: number, called3: number) {
|
||||||
|
check.in(message, check => {
|
||||||
|
check.equals(button1.getState(), state1, "button1 state");
|
||||||
|
check.equals(button2.getState(), state2, "button2 state");
|
||||||
|
check.equals(button3.getState(), state3, "button3 state");
|
||||||
|
check.called(m1, called1);
|
||||||
|
check.called(m2, called2);
|
||||||
|
check.called(m3, called3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
verify("initial", false, false, false, 0, 0, 0);
|
||||||
|
|
||||||
|
button1.toggle(true);
|
||||||
|
verify("toggle on", true, false, false, 1, 0, 0);
|
||||||
|
|
||||||
|
button1.toggle(true);
|
||||||
|
verify("toggle on again", true, false, false, 0, 0, 0);
|
||||||
|
|
||||||
|
button1.toggle(false);
|
||||||
|
verify("toggle off", false, false, false, 1, 0, 0);
|
||||||
|
|
||||||
|
button1.toggle(false);
|
||||||
|
verify("toggle off again", false, false, false, 0, 0, 0);
|
||||||
|
|
||||||
|
button2.toggle(true, UIButtonUnicity.EXCLUSIVE);
|
||||||
|
verify("toggle on unicity - first", false, true, false, 0, 1, 0);
|
||||||
|
|
||||||
|
button2.toggle(true, UIButtonUnicity.EXCLUSIVE);
|
||||||
|
verify("toggle on unicity - first again", false, true, false, 0, 0, 0);
|
||||||
|
|
||||||
|
button3.toggle(true, UIButtonUnicity.EXCLUSIVE);
|
||||||
|
verify("toggle on unicity - second", false, false, true, 0, 1, 1);
|
||||||
|
|
||||||
|
button2.toggle(false, UIButtonUnicity.EXCLUSIVE);
|
||||||
|
verify("toggle off unicity - other", false, false, true, 0, 0, 0);
|
||||||
|
|
||||||
|
button3.toggle(false, UIButtonUnicity.EXCLUSIVE);
|
||||||
|
verify("toggle off unicity - currently on", false, false, false, 0, 0, 1);
|
||||||
|
|
||||||
|
button1.toggle(true);
|
||||||
|
button2.toggle(true);
|
||||||
|
button3.toggle(true);
|
||||||
|
verify("toggle all on", true, true, true, 1, 1, 1);
|
||||||
|
|
||||||
|
button2.toggle(true, UIButtonUnicity.EXCLUSIVE);
|
||||||
|
verify("toggle on unicity should shut down 2 others", false, true, false, 1, 0, 1);
|
||||||
|
|
||||||
|
button2.toggle(true, UIButtonUnicity.EXCLUSIVE_MIN);
|
||||||
|
verify("toggle off unicity min", false, true, false, 0, 0, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
211
src/ui/common/UIButton.ts
Normal file
211
src/ui/common/UIButton.ts
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
/// <reference path="UIContainer.ts" />
|
||||||
|
|
||||||
|
module TK.SpaceTac.UI {
|
||||||
|
/**
|
||||||
|
* Button options
|
||||||
|
*/
|
||||||
|
export type UIButtonOptions = {
|
||||||
|
// Centering
|
||||||
|
center?: boolean
|
||||||
|
|
||||||
|
// Name of the hover picture (by default, the button name, with "-hover" appended)
|
||||||
|
hover_name?: string
|
||||||
|
|
||||||
|
// Name of the "on" picture (by default, the button name, with "-on" appended)
|
||||||
|
on_name?: string
|
||||||
|
|
||||||
|
// Whether "hover" picture should stay near the button (otherwise will be on top)
|
||||||
|
hover_bottom?: boolean
|
||||||
|
|
||||||
|
// Whether "on" picture should stay near the button (otherwise will be on top)
|
||||||
|
on_bottom?: boolean
|
||||||
|
|
||||||
|
// Text content
|
||||||
|
text?: string
|
||||||
|
text_x?: number
|
||||||
|
text_y?: number
|
||||||
|
|
||||||
|
// Text content style override
|
||||||
|
text_style?: UITextStyleI
|
||||||
|
|
||||||
|
// Icon content
|
||||||
|
icon?: string
|
||||||
|
icon_x?: number
|
||||||
|
icon_y?: number
|
||||||
|
|
||||||
|
// Unicity setting to control other buttons in the same container
|
||||||
|
unicity?: UIButtonUnicity
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When toggling a button status, this describes the behavior of other buttons in the same container
|
||||||
|
*/
|
||||||
|
export enum UIButtonUnicity {
|
||||||
|
// Do nothing to other buttons
|
||||||
|
NONE = 0,
|
||||||
|
// Shut down other buttons when one is toggled on
|
||||||
|
EXCLUSIVE = 1,
|
||||||
|
// Shut down other buttons when one is toggled on, but prevent to shut down the one currently on
|
||||||
|
EXCLUSIVE_MIN = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button for UI, with support for hover, click, and on/off state
|
||||||
|
*/
|
||||||
|
export class UIButton extends UIContainer {
|
||||||
|
private base: UIImage
|
||||||
|
private state_on = false
|
||||||
|
readonly state_changer?: Function
|
||||||
|
private hover_mask?: UIImage
|
||||||
|
private hover_bottom = false
|
||||||
|
private on_mask?: UIImage
|
||||||
|
private on_bottom = false
|
||||||
|
private constructed = false
|
||||||
|
|
||||||
|
constructor(private view: BaseView, key: string, x = 0, y = 0, onclick?: Function, tooltip?: TooltipFiller, onoffcallback?: UIOnOffCallback, options: UIButtonOptions = {}) {
|
||||||
|
super(view, x, y);
|
||||||
|
this.setName(key);
|
||||||
|
|
||||||
|
let builder = new UIBuilder(view, this, options.text_style);
|
||||||
|
let base = builder.image(key, 0, 0, options.center);
|
||||||
|
this.add(base);
|
||||||
|
this.base = base;
|
||||||
|
|
||||||
|
let clickable = bool(onclick || onoffcallback);
|
||||||
|
let interactive = bool(clickable || tooltip);
|
||||||
|
|
||||||
|
if (interactive) {
|
||||||
|
this.setInteractive(new Phaser.Geom.Rectangle(
|
||||||
|
options.center ? 0 : base.width / 2,
|
||||||
|
options.center ? 0 : base.height / 2,
|
||||||
|
base.width,
|
||||||
|
base.height
|
||||||
|
), (rect: Phaser.Geom.Rectangle, x: number, y: number) => Phaser.Geom.Rectangle.Contains(rect, x, y) && UITools.isVisible(this));
|
||||||
|
|
||||||
|
// On mask
|
||||||
|
if (onoffcallback) {
|
||||||
|
let on_name = options.on_name || (key + "-on");
|
||||||
|
let on_info = view.getImageInfo(on_name);
|
||||||
|
if (on_info.exists) {
|
||||||
|
this.on_mask = builder.image(on_name, 0, 0, options.center);
|
||||||
|
this.on_mask.setVisible(false);
|
||||||
|
this.on_bottom = bool(options.on_bottom);
|
||||||
|
}
|
||||||
|
this.state_changer = (on: boolean): boolean => {
|
||||||
|
this.state_on = onoffcallback(on);
|
||||||
|
if (this.on_mask) {
|
||||||
|
view.animations.setVisible(this.on_mask, this.state_on, 100);
|
||||||
|
}
|
||||||
|
return this.state_on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover mask
|
||||||
|
let hover_name = options.hover_name || (key + "-hover");
|
||||||
|
let hover_info = view.getImageInfo(hover_name);
|
||||||
|
if (hover_info.exists) {
|
||||||
|
this.hover_mask = builder.image(hover_name, 0, 0, options.center);
|
||||||
|
this.hover_mask.setVisible(false);
|
||||||
|
this.hover_bottom = bool(options.hover_bottom);
|
||||||
|
if (this.hover_bottom && !this.on_bottom) {
|
||||||
|
this.moveDown(this.hover_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.inputs.setHoverClick(this,
|
||||||
|
() => {
|
||||||
|
if (tooltip) {
|
||||||
|
view.tooltip.show(this, tooltip);
|
||||||
|
}
|
||||||
|
if (this.hover_mask) {
|
||||||
|
view.animations.show(this.hover_mask, 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (tooltip) {
|
||||||
|
view.tooltip.hide();
|
||||||
|
}
|
||||||
|
if (this.hover_mask) {
|
||||||
|
view.animations.hide(this.hover_mask, 100)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (clickable && onclick) {
|
||||||
|
onclick();
|
||||||
|
} else if (onoffcallback) {
|
||||||
|
this.toggle(!this.state_on, options.unicity);
|
||||||
|
}
|
||||||
|
}, 100, undefined, clickable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.text) {
|
||||||
|
builder.text(options.text, options.text_x || 0, options.text_y || 0, options.text_style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.icon) {
|
||||||
|
builder.image(options.icon, options.icon_x || 0, options.icon_y || 0, options.center);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.constructed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(child: UIImage | UIText): UIButton {
|
||||||
|
if (this.constructed) {
|
||||||
|
// Protect the "on" and "hover" layers
|
||||||
|
let layer = first(this.list, child => (!this.hover_bottom && child == this.hover_mask) || (!this.on_bottom && child == this.on_mask));
|
||||||
|
if (layer) {
|
||||||
|
super.addAt(child, this.getIndex(layer));
|
||||||
|
} else {
|
||||||
|
super.add(child);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.add(child);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
get width(): number {
|
||||||
|
return this.base.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
get height(): number {
|
||||||
|
return this.base.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the state on/off
|
||||||
|
*/
|
||||||
|
getState(): boolean {
|
||||||
|
return this.state_on;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the base texture
|
||||||
|
*/
|
||||||
|
setBaseImage(key: string): void {
|
||||||
|
this.view.changeImage(this.base, key);
|
||||||
|
this.setName(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select this button status
|
||||||
|
*
|
||||||
|
* Returns the final state of this button
|
||||||
|
*/
|
||||||
|
toggle(on: boolean, unicity?: UIButtonUnicity): boolean {
|
||||||
|
if (on && unicity && this.parentContainer) {
|
||||||
|
this.parentContainer.list.forEach(child => {
|
||||||
|
if (child instanceof UIButton && child != this) {
|
||||||
|
child.toggle(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state_changer && (on || unicity != UIButtonUnicity.EXCLUSIVE_MIN) && on != this.state_on) {
|
||||||
|
this.state_changer(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state_on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
test.case("controls visibility", check => {
|
test.case("controls visibility", check => {
|
||||||
let component = new UIComponent(testgame.view, 50, 50);
|
let component = new UIComponent(testgame.view, 50, 50);
|
||||||
|
|
||||||
let container = <Phaser.Group>(<any>component).container;
|
let container = <UIContainer>(<any>component).container;
|
||||||
check.equals(container.visible, true);
|
check.equals(container.visible, true);
|
||||||
|
|
||||||
component.setVisible(false);
|
component.setVisible(false);
|
||||||
|
@ -17,7 +17,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
// with transition
|
// with transition
|
||||||
component.setVisible(false, 500);
|
component.setVisible(false, 500);
|
||||||
check.equals(container.visible, true);
|
check.equals(container.visible, true);
|
||||||
check.equals(testgame.view.animations.simulate(container, 'alpha'), [1, 0.5, 0]);
|
check.equals(testgame.view.animations.simulate(container, 'alpha'), [1, 0.75, 0.5, 0.25, 0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("sets position inside parent", check => {
|
test.case("sets position inside parent", check => {
|
||||||
|
|
|
@ -1,40 +1,26 @@
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
export type UIInternalComponent = Phaser.Group | Phaser.Image | Phaser.Button | Phaser.Sprite | Phaser.Graphics;
|
/**
|
||||||
|
* Union of all UI components types
|
||||||
|
*/
|
||||||
|
export type UIComponentT = UIContainer | UIImage | UIButton | UIGraphics | UIText;
|
||||||
|
|
||||||
export type UIImageInfo = string | { key: string, frame?: number, frame1?: number, frame2?: number };
|
/**
|
||||||
export type UITextInfo = { content: string, color: string, size: number, bold?: boolean };
|
* Interface to add a component to a group
|
||||||
|
*/
|
||||||
function imageFromInfo(game: Phaser.Game, info: UIImageInfo): Phaser.Image {
|
export interface UIGroupableI {
|
||||||
if (typeof info === "string") {
|
addToGroup(group: UIContainer): void;
|
||||||
info = { key: info };
|
|
||||||
}
|
|
||||||
let image = new Phaser.Image(game, 0, 0, info.key, info.frame);
|
|
||||||
image.anchor.set(0.5, 0.5);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
function textFromInfo(game: Phaser.Game, info: UITextInfo): Phaser.Text {
|
|
||||||
let style = { font: `${info.bold ? "bold " : ""}${info.size}pt SpaceTac`, fill: info.color };
|
|
||||||
let text = new Phaser.Text(game, 0, 0, info.content, style);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoFromInfo(game: Phaser.Game, info: UIImageInfo | UITextInfo): Phaser.Text | Phaser.Image {
|
|
||||||
if (info.hasOwnProperty("content")) {
|
|
||||||
return textFromInfo(game, <UITextInfo>info);
|
|
||||||
} else {
|
|
||||||
return imageFromInfo(game, <UIImageInfo>info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for UI components
|
* Base class for UI components
|
||||||
|
*
|
||||||
|
* DEPRECATED - Use UIBuilder instead
|
||||||
*/
|
*/
|
||||||
export class UIComponent {
|
export class UIComponent {
|
||||||
private background: Phaser.Image | Phaser.Graphics | null
|
private background: UIImage | UIGraphics | null
|
||||||
protected readonly view: BaseView
|
protected readonly view: BaseView
|
||||||
protected readonly parent: UIComponent | null
|
protected readonly parent: UIComponent | null
|
||||||
private readonly container: Phaser.Group
|
readonly container: UIContainer
|
||||||
protected readonly width: number
|
protected readonly width: number
|
||||||
protected readonly height: number
|
protected readonly height: number
|
||||||
protected readonly builder: UIBuilder
|
protected readonly builder: UIBuilder
|
||||||
|
@ -57,6 +43,7 @@ module TK.SpaceTac.UI {
|
||||||
} else {
|
} else {
|
||||||
this.view.add.existing(this.container);
|
this.view.add.existing(this.container);
|
||||||
}
|
}
|
||||||
|
this.container.setSize(width, height);
|
||||||
this.builder = new UIBuilder(this.view, this.container);
|
this.builder = new UIBuilder(this.view, this.container);
|
||||||
|
|
||||||
if (background_key) {
|
if (background_key) {
|
||||||
|
@ -86,46 +73,43 @@ module TK.SpaceTac.UI {
|
||||||
this.background.destroy();
|
this.background.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.background = this.addInternalChild(new Phaser.Graphics(this.game, 0, 0));
|
let rect = new Phaser.Geom.Rectangle(0, 0, this.width, this.height);
|
||||||
if (border_width) {
|
this.background = this.addInternalChild(new UIGraphics(this.view, "background"));
|
||||||
this.background.lineStyle(border_width, border);
|
this.background.addRectangle(rect, fill, border_width, border, alpha);
|
||||||
}
|
|
||||||
this.background.beginFill(fill, alpha);
|
|
||||||
this.background.drawRect(0, 0, this.width, this.height);
|
|
||||||
this.background.endFill();
|
|
||||||
|
|
||||||
if (mouse_capture) {
|
if (mouse_capture) {
|
||||||
this.background.inputEnabled = true;
|
this.background.setInteractive(rect, Phaser.Geom.Rectangle.Contains);
|
||||||
this.background.input.useHandCursor = true;
|
this.background.on("pointerup", () => mouse_capture());
|
||||||
this.background.events.onInputUp.add(() => mouse_capture());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the a parent's layer
|
* Move the a parent's layer
|
||||||
*/
|
*/
|
||||||
moveToLayer(layer: Phaser.Group) {
|
moveToLayer(layer: UIContainer) {
|
||||||
layer.add(this.container);
|
layer.add(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy the component
|
* Destroy the component
|
||||||
*/
|
*/
|
||||||
destroy(children = true) {
|
destroy() {
|
||||||
this.container.destroy(children);
|
this.container.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the internal phaser node
|
* Create the internal phaser node
|
||||||
*/
|
*/
|
||||||
protected createInternalNode(): Phaser.Group {
|
protected createInternalNode(): UIContainer {
|
||||||
return new Phaser.Group(this.view.game, undefined, classname(this));
|
let result = new UIContainer(this.view);
|
||||||
|
result.setName(classname(this));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an other internal component as child
|
* Add an other internal component as child
|
||||||
*/
|
*/
|
||||||
protected addInternalChild<T extends UIInternalComponent>(child: T): T {
|
protected addInternalChild<T extends UIComponentT>(child: T): T {
|
||||||
this.container.add(child);
|
this.container.add(child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
@ -171,7 +155,7 @@ module TK.SpaceTac.UI {
|
||||||
* Set the position in pixels.
|
* Set the position in pixels.
|
||||||
*/
|
*/
|
||||||
setPosition(x: number, y: number): void {
|
setPosition(x: number, y: number): void {
|
||||||
this.container.position.set(x, y);
|
this.container.setPosition(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -187,9 +171,9 @@ module TK.SpaceTac.UI {
|
||||||
let rx = (pwidth - width) * x;
|
let rx = (pwidth - width) * x;
|
||||||
let ry = (pheight - height) * y;
|
let ry = (pheight - height) * y;
|
||||||
if (pixelsnap) {
|
if (pixelsnap) {
|
||||||
this.container.position.set(Math.round(rx), Math.round(ry));
|
this.container.setPosition(Math.round(rx), Math.round(ry));
|
||||||
} else {
|
} else {
|
||||||
this.container.position.set(rx, ry);
|
this.container.setPosition(rx, ry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,28 +182,18 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
clearContent(): void {
|
clearContent(): void {
|
||||||
let offset = this.background ? 1 : 0;
|
let offset = this.background ? 1 : 0;
|
||||||
while (this.container.children.length > offset) {
|
while (this.container.list.length > offset) {
|
||||||
this.container.remove(this.container.children[offset], true);
|
this.container.remove(this.container.list[offset], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the standard sounds on a button
|
|
||||||
*/
|
|
||||||
static setButtonSound(button: Phaser.Button): void {
|
|
||||||
button.setDownSound(new Phaser.Sound(button.game, "ui-button-down"));
|
|
||||||
button.setUpSound(new Phaser.Sound(button.game, "ui-button-up"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a button in the component, positioning its center.
|
* Add a button in the component, positioning its center.
|
||||||
*
|
*
|
||||||
* DEPRECATED - Use UIBuilder directly
|
* DEPRECATED - Use UIBuilder directly
|
||||||
*/
|
*/
|
||||||
addButton(x: number, y: number, on_click: Function, background: string, tooltip = ""): Phaser.Button {
|
addButton(x: number, y: number, on_click: Function, background: string, tooltip = ""): UIButton {
|
||||||
let result = this.builder.button(background, x, y, on_click, tooltip);
|
return this.builder.button(background, x, y, on_click, tooltip, undefined, { center: true });
|
||||||
result.anchor.set(0.5);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -227,52 +201,19 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* DEPRECATED - Use UIBuilder directly
|
* DEPRECATED - Use UIBuilder directly
|
||||||
*/
|
*/
|
||||||
addText(x: number, y: number, content: string, color = "#ffffff", size = 16, bold = false, center = true, width = 0, vcenter = center): Phaser.Text {
|
addText(x: number, y: number, content: string, color = "#ffffff", size = 16, bold = false, center = true, width = 0, vcenter = center): UIText {
|
||||||
return this.builder.text(content, x, y, { color: color, size: size, bold: bold, center: center, width: width, vcenter: vcenter });
|
return this.builder.text(content, x, y, { color: color, size: size, bold: bold, center: center, width: width, vcenter: vcenter });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a static image, positioning its center.
|
|
||||||
*
|
|
||||||
* DEPRECATED - Use addImage instead
|
|
||||||
*/
|
|
||||||
addImageF(x: number, y: number, key: string, frame = 0, scale = 1): void {
|
|
||||||
let image = new Phaser.Image(this.container.game, x, y, key, frame);
|
|
||||||
image.anchor.set(0.5, 0.5);
|
|
||||||
image.scale.set(scale);
|
|
||||||
this.addInternalChild(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a static image, from atlases, positioning its center.
|
* Add a static image, from atlases, positioning its center.
|
||||||
*
|
*
|
||||||
* DEPRECATED - Use UIBuilder directly
|
* DEPRECATED - Use UIBuilder directly
|
||||||
*/
|
*/
|
||||||
addImage(x: number, y: number, name: string, scale = 1): Phaser.Image {
|
addImage(x: number, y: number, name: string, scale = 1): UIImage {
|
||||||
let result = this.builder.image(name, x, y);
|
let result = this.builder.image(name, x, y, true);
|
||||||
result.anchor.set(0.5);
|
result.setScale(scale);
|
||||||
result.scale.set(scale);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an animated loader (to indicate a waiting for something).
|
|
||||||
*/
|
|
||||||
addLoader(x: number, y: number, scale = 1): Phaser.Image {
|
|
||||||
let image = new Phaser.Image(this.game, x, y, "common-waiting");
|
|
||||||
image.anchor.set(0.5, 0.5);
|
|
||||||
image.scale.set(scale);
|
|
||||||
image.animations.add("loop").play(3, true);
|
|
||||||
this.addInternalChild(image);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the keyboard focus on this component.
|
|
||||||
*/
|
|
||||||
setKeyboardFocus(on_key: (key: string) => void) {
|
|
||||||
this.view.inputs.grabKeyboard(this, on_key);
|
|
||||||
// TODO release on destroy
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@ module TK.SpaceTac.UI {
|
||||||
constructor(view: BaseView, message: string) {
|
constructor(view: BaseView, message: string) {
|
||||||
super(view);
|
super(view);
|
||||||
|
|
||||||
this.addText(this.width * 0.5, this.height * 0.3, message, "#90FEE3", 32);
|
this.content.text(message, this.width * 0.5, this.height * 0.3, { color: "#90FEE3", size: 32 });
|
||||||
|
|
||||||
this.result = new Promise((resolve, reject) => {
|
this.result = new Promise((resolve, reject) => {
|
||||||
this.result_resolver = resolve;
|
this.result_resolver = resolve;
|
||||||
this.addButton(this.width * 0.4, this.height * 0.6, () => resolve(false), "common-button-cancel");
|
this.content.button("common-button-cancel", this.width * 0.4, this.height * 0.6, () => resolve(false), undefined, undefined, { center: true });
|
||||||
this.addButton(this.width * 0.6, this.height * 0.6, () => resolve(true), "common-button-ok");
|
this.content.button("common-button-ok", this.width * 0.6, this.height * 0.6, () => resolve(true), undefined, undefined, { center: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
src/ui/common/UIContainer.ts
Normal file
31
src/ui/common/UIContainer.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
module TK.SpaceTac.UI {
|
||||||
|
/**
|
||||||
|
* UI component able to contain other UI components
|
||||||
|
*/
|
||||||
|
export class UIContainer extends Phaser.GameObjects.Container {
|
||||||
|
/**
|
||||||
|
* Fixed version that does not force (0, 0) to be in bounds
|
||||||
|
*/
|
||||||
|
getBounds(output?: Phaser.Geom.Rectangle): Phaser.Geom.Rectangle {
|
||||||
|
let result: IBounded = { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
|
||||||
|
if (this.list.length > 0) {
|
||||||
|
var children = this.list;
|
||||||
|
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
var entry = children[i];
|
||||||
|
|
||||||
|
if (UITools.isSpatial(entry)) {
|
||||||
|
result = UITools.unionRects(result, entry.getBounds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof output == "undefined") {
|
||||||
|
output = new Phaser.Geom.Rectangle();
|
||||||
|
}
|
||||||
|
output.setTo(result.x, result.y, result.width, result.height);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,12 +56,13 @@ module TK.SpaceTac.UI {
|
||||||
width -= offset;
|
width -= offset;
|
||||||
|
|
||||||
let ioffset = style.padding + Math.floor(style.image_size / 2);
|
let ioffset = style.padding + Math.floor(style.image_size / 2);
|
||||||
builder.image(style.image, ioffset, ioffset);
|
builder.image(style.image, ioffset, ioffset, true);
|
||||||
|
|
||||||
if (style.image_caption) {
|
if (style.image_caption) {
|
||||||
let text_size = Math.ceil(style.text.size ? style.text.size * 0.6 : 16);
|
let text_size = Math.ceil(style.text.size ? style.text.size * 0.6 : 16);
|
||||||
builder.text(style.image_caption, ioffset, style.padding + style.image_size + text_size, {
|
builder.text(style.image_caption, ioffset, style.padding + style.image_size + text_size, {
|
||||||
size: text_size
|
size: text_size,
|
||||||
|
center: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +71,7 @@ module TK.SpaceTac.UI {
|
||||||
width: width - style.padding * 2
|
width: width - style.padding * 2
|
||||||
});
|
});
|
||||||
|
|
||||||
let i = 0;
|
/*let i = 0;
|
||||||
let colorchar = () => {
|
let colorchar = () => {
|
||||||
text.clearColors();
|
text.clearColors();
|
||||||
if (i < message.length) {
|
if (i < message.length) {
|
||||||
|
@ -79,7 +80,7 @@ module TK.SpaceTac.UI {
|
||||||
this.view.timer.schedule(10, colorchar);
|
this.view.timer.schedule(10, colorchar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
colorchar();
|
colorchar();*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ module TK.SpaceTac.UI {
|
||||||
private step = -1
|
private step = -1
|
||||||
private on_step: UIConversationCallback
|
private on_step: UIConversationCallback
|
||||||
private ended = false
|
private ended = false
|
||||||
private on_end = new Phaser.Signal()
|
private on_end = new Phaser.Events.EventEmitter()
|
||||||
|
|
||||||
constructor(parent: BaseView, on_step: UIConversationCallback) {
|
constructor(parent: BaseView, on_step: UIConversationCallback) {
|
||||||
super(parent, parent.getWidth(), parent.getHeight());
|
super(parent, parent.getWidth(), parent.getHeight());
|
||||||
|
@ -106,7 +107,7 @@ module TK.SpaceTac.UI {
|
||||||
destroy() {
|
destroy() {
|
||||||
if (!this.ended) {
|
if (!this.ended) {
|
||||||
this.ended = true;
|
this.ended = true;
|
||||||
this.on_end.dispatch();
|
this.on_end.emit("done");
|
||||||
}
|
}
|
||||||
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
|
@ -119,8 +120,8 @@ module TK.SpaceTac.UI {
|
||||||
if (this.ended) {
|
if (this.ended) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(resolve => {
|
||||||
this.on_end.addOnce(resolve);
|
this.on_end.on("done", resolve);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,36 @@ module TK.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
test.case("sets up an overlay", check => {
|
test.case("sets up an overlay", check => {
|
||||||
let view = testgame.view;
|
let view = testgame.view;
|
||||||
check.equals(view.dialogs_layer.children.length, 0);
|
check.equals(view.dialogs_layer.length, 0, "initial");
|
||||||
|
|
||||||
let dialog1 = new UIDialog(view, 10, 10, "fake");
|
let dialog1 = new UIDialog(view, "fake");
|
||||||
check.equals(view.dialogs_layer.children.length, 2);
|
check.in("one dialog", check => {
|
||||||
check.equals(view.dialogs_layer.children[0] instanceof Phaser.Button, true);
|
check.equals(view.dialogs_layer.length, 2);
|
||||||
checkComponentInLayer(check, view.dialogs_layer, 1, dialog1);
|
check.equals(view.dialogs_layer.list[0] instanceof UIImage, true);
|
||||||
|
check.same(view.dialogs_layer.list[1], dialog1.base);
|
||||||
|
});
|
||||||
|
|
||||||
let dialog2 = new UIDialog(view, 10, 10, "fake");
|
let dialog2 = new UIDialog(view, "fake");
|
||||||
check.equals(view.dialogs_layer.children.length, 3);
|
check.in("two dialogs", check => {
|
||||||
check.equals(view.dialogs_layer.children[0] instanceof Phaser.Button, true);
|
check.equals(view.dialogs_layer.length, 3);
|
||||||
checkComponentInLayer(check, view.dialogs_layer, 1, dialog1);
|
check.equals(view.dialogs_layer.list[0] instanceof UIImage, true);
|
||||||
checkComponentInLayer(check, view.dialogs_layer, 2, dialog2);
|
check.same(view.dialogs_layer.list[1], dialog1.base);
|
||||||
|
check.same(view.dialogs_layer.list[2], dialog2.base);
|
||||||
|
});
|
||||||
|
|
||||||
dialog1.close();
|
dialog1.close();
|
||||||
|
|
||||||
check.equals(view.dialogs_layer.children.length, 2);
|
check.in("one dialog closed", check => {
|
||||||
check.equals(view.dialogs_layer.children[0] instanceof Phaser.Button, true);
|
check.equals(view.dialogs_layer.length, 2);
|
||||||
checkComponentInLayer(check, view.dialogs_layer, 1, dialog2);
|
check.equals(view.dialogs_layer.list[0] instanceof UIImage, true);
|
||||||
|
check.same(view.dialogs_layer.list[1], dialog2.base);
|
||||||
|
});
|
||||||
|
|
||||||
dialog2.close();
|
dialog2.close();
|
||||||
|
|
||||||
check.equals(view.dialogs_layer.children.length, 0);
|
check.in("all dialogs closed", check => {
|
||||||
|
check.equals(view.dialogs_layer.length, 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,59 @@
|
||||||
/// <reference path="../common/UIComponent.ts" />
|
|
||||||
|
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Base class for modal dialogs
|
* Base class for modal dialogs
|
||||||
*
|
*
|
||||||
* When a modal dialog opens, an overlay is displayed behind it to prevent clicking through it
|
* When a modal dialog opens, an overlay is displayed behind it to prevent clicking through it
|
||||||
*/
|
*/
|
||||||
export class UIDialog extends UIComponent {
|
export class UIDialog {
|
||||||
constructor(parent: BaseView, width = 1495, height = 1080, background = "common-dialog") {
|
readonly base: UIContainer
|
||||||
super(parent, width, height, background);
|
readonly content: UIBuilder
|
||||||
|
readonly width: number
|
||||||
|
readonly height: number
|
||||||
|
|
||||||
if (parent.dialogs_opened.length == 0) {
|
constructor(readonly view: BaseView, background_key = "common-dialog") {
|
||||||
this.addOverlay(parent.dialogs_layer);
|
if (view.dialogs_opened.length == 0) {
|
||||||
|
this.addOverlay(view.dialogs_layer);
|
||||||
}
|
}
|
||||||
add(parent.dialogs_opened, this);
|
|
||||||
|
|
||||||
this.view.audio.playOnce("ui-dialog-open");
|
let builder = new UIBuilder(view, view.dialogs_layer);
|
||||||
|
this.base = builder.container("dialog-base");
|
||||||
|
builder = builder.in(this.base);
|
||||||
|
|
||||||
this.moveToLayer(parent.dialogs_layer);
|
let background = builder.image(background_key);
|
||||||
this.setPositionInsideParent(0.5, 0.5);
|
this.width = background.width;
|
||||||
|
this.height = background.height;
|
||||||
|
|
||||||
|
this.base.setPosition((this.view.getWidth() - this.width) / 2, (this.view.getHeight() - this.height) / 2);
|
||||||
|
|
||||||
|
this.content = builder.in(builder.container("content"));
|
||||||
|
|
||||||
|
add(view.dialogs_opened, this);
|
||||||
|
|
||||||
|
view.audio.playOnce("ui-dialog-open");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a control-capturing overlay
|
* Add an input-capturing overlay
|
||||||
*/
|
*/
|
||||||
addOverlay(layer: Phaser.Group): void {
|
addOverlay(layer: UIContainer): void {
|
||||||
let info = this.view.getImageInfo("translucent");
|
let overlay = new UIBuilder(this.view, layer).image("translucent");
|
||||||
let overlay = layer.game.add.button(0, 0, info.key, () => null, undefined, info.frame, info.frame);
|
overlay.setInteractive();
|
||||||
overlay.input.useHandCursor = false;
|
overlay.setScale(this.view.getWidth() / overlay.width, this.view.getHeight() / overlay.height);
|
||||||
overlay.scale.set(this.view.getWidth() / overlay.width, this.view.getHeight() / overlay.height);
|
|
||||||
layer.add(overlay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a close button
|
* Add a close button
|
||||||
*/
|
*/
|
||||||
addCloseButton(key = "common-dialog-close", x = 1290, y = 90): void {
|
addCloseButton(key = "common-dialog-close", x = 1290, y = 90): void {
|
||||||
this.builder.button(key, x, y, () => this.close(), "Close this dialog");
|
let builder = new UIBuilder(this.view, this.base);
|
||||||
|
builder.button(key, x, y, () => this.close(), "Close this dialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the dialog, removing the overlay if needed
|
* Close the dialog, removing the overlay if needed
|
||||||
*/
|
*/
|
||||||
close() {
|
close() {
|
||||||
this.destroy();
|
this.base.destroy();
|
||||||
|
|
||||||
this.view.audio.playOnce("ui-dialog-close");
|
this.view.audio.playOnce("ui-dialog-close");
|
||||||
|
|
||||||
|
|
27
src/ui/common/UIGraphics.ts
Normal file
27
src/ui/common/UIGraphics.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module TK.SpaceTac.UI {
|
||||||
|
/**
|
||||||
|
* UI component that supports drawing simple shapes (circles, lines...)
|
||||||
|
*/
|
||||||
|
export class UIGraphics extends Phaser.GameObjects.Graphics {
|
||||||
|
constructor(view: BaseView, name: string, visible = true, x = 0, y = 0) {
|
||||||
|
super(view, {});
|
||||||
|
this.setName(name);
|
||||||
|
this.setVisible(visible);
|
||||||
|
this.setPosition(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a rectangle
|
||||||
|
*/
|
||||||
|
addRectangle(shape: IBounded, color: number, border_width = 0, border_color?: number, alpha = 1): void {
|
||||||
|
let rect = new Phaser.Geom.Rectangle(shape.x, shape.y, shape.width, shape.height);
|
||||||
|
|
||||||
|
this.fillStyle(color, alpha);
|
||||||
|
this.fillRectShape(rect);
|
||||||
|
if (border_width && border_color) {
|
||||||
|
this.lineStyle(border_width, border_color, alpha);
|
||||||
|
this.strokeRectShape(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/ui/common/UIImage.ts
Normal file
7
src/ui/common/UIImage.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module TK.SpaceTac.UI {
|
||||||
|
/**
|
||||||
|
* UI component to display an image
|
||||||
|
*/
|
||||||
|
export class UIImage extends Phaser.GameObjects.Image {
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
/// <reference path="UIComponent.ts" />
|
|
||||||
|
|
||||||
module TK.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 SpaceTac`, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
7
src/ui/common/UIText.ts
Normal file
7
src/ui/common/UIText.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module TK.SpaceTac.UI {
|
||||||
|
/**
|
||||||
|
* UI component to display a text
|
||||||
|
*/
|
||||||
|
export class UIText extends Phaser.GameObjects.Text {
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,17 +9,17 @@ module TK.SpaceTac.UI {
|
||||||
constructor(view: BaseView, message: string, initial?: string) {
|
constructor(view: BaseView, message: string, initial?: string) {
|
||||||
super(view);
|
super(view);
|
||||||
|
|
||||||
this.addText(this.width * 0.5, this.height * 0.3, message, "#90FEE3", 32);
|
this.content.text(message, this.width * 0.5, this.height * 0.3, { color: "#90FEE3", size: 32 });
|
||||||
|
|
||||||
let input = new UITextInput(this.builder.styled({ size: 24 }), "menu-input", this.width / 2, this.height / 2, 12);
|
let input = new UITextInput(this.content.styled({ size: 24 }), "menu-input", this.width / 2, this.height / 2, 12);
|
||||||
if (initial) {
|
if (initial) {
|
||||||
input.setContent(initial);
|
input.setContent(initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.result = new Promise((resolve, reject) => {
|
this.result = new Promise((resolve, reject) => {
|
||||||
this.result_resolver = resolve;
|
this.result_resolver = resolve;
|
||||||
this.addButton(this.width * 0.4, this.height * 0.7, () => resolve(null), "common-button-cancel");
|
this.content.button("common-button-cancel", this.width * 0.4, this.height * 0.7, () => resolve(null));
|
||||||
this.addButton(this.width * 0.6, this.height * 0.7, () => resolve(input.getContent()), "common-button-ok");
|
this.content.button("common-button-ok", this.width * 0.6, this.height * 0.7, () => resolve(input.getContent()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,19 @@ module TK.SpaceTac.UI {
|
||||||
* UI component to allow the user to enter a small text
|
* UI component to allow the user to enter a small text
|
||||||
*/
|
*/
|
||||||
export class UITextInput {
|
export class UITextInput {
|
||||||
private content: Phaser.Text
|
private container: UIButton
|
||||||
private placeholder: Phaser.Text
|
private content: UIText
|
||||||
|
private placeholder: UIText
|
||||||
private maxlength: number
|
private maxlength: number
|
||||||
|
|
||||||
constructor(builder: UIBuilder, background: string, x = 0, y = 0, maxlength: number, placeholder = "") {
|
constructor(builder: UIBuilder, background: string, x = 0, y = 0, maxlength: number, placeholder = "") {
|
||||||
let input_bg = builder.image(background, x, y, true);
|
this.container = builder.button(background, x, y, () => {
|
||||||
input_bg.inputEnabled = true;
|
|
||||||
input_bg.input.useHandCursor = true;
|
|
||||||
input_bg.events.onInputUp.add(() => {
|
|
||||||
builder.view.inputs.grabKeyboard(this, key => this.processKey(key));
|
builder.view.inputs.grabKeyboard(this, key => this.processKey(key));
|
||||||
});
|
}, undefined, undefined, { center: true });
|
||||||
|
|
||||||
this.content = builder.in(input_bg).text("");
|
this.content = builder.in(this.container).text("", 0, 0, { center: true });
|
||||||
this.placeholder = builder.in(input_bg).text(placeholder);
|
this.placeholder = builder.in(this.container).text(placeholder, 0, 0, { center: true });
|
||||||
this.placeholder.alpha = 0.5;
|
this.placeholder.setAlpha(0.5);
|
||||||
this.maxlength = maxlength;
|
this.maxlength = maxlength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +41,8 @@ module TK.SpaceTac.UI {
|
||||||
* Set the current text content
|
* Set the current text content
|
||||||
*/
|
*/
|
||||||
setContent(content: string): void {
|
setContent(content: string): void {
|
||||||
this.content.text = content.slice(0, this.maxlength);
|
this.content.setText(content.slice(0, this.maxlength));
|
||||||
this.placeholder.visible = !this.content.text;
|
this.placeholder.setVisible(!this.content.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,86 +4,90 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let testgame = setupEmptyView(test);
|
let testgame = setupEmptyView(test);
|
||||||
|
|
||||||
test.case("destroys children", check => {
|
test.case("destroys children", check => {
|
||||||
let parent = testgame.view.add.group();
|
let builder = new UIBuilder(testgame.view);
|
||||||
let child1 = testgame.view.add.graphics(0, 0, parent);
|
let parent = builder.container("group");
|
||||||
let child2 = testgame.view.add.image(0, 0, "", 0, parent);
|
let child1 = builder.in(parent).graphics("graphics");
|
||||||
let child3 = testgame.view.add.button(0, 0, "", undefined, undefined, undefined, undefined, undefined, undefined, parent);
|
let child2 = builder.in(parent).image("image");
|
||||||
let child4 = testgame.view.add.text(0, 0, "", {}, parent);
|
let child3 = builder.in(parent).button("button");
|
||||||
check.equals(parent.children.length, 4);
|
let child4 = builder.in(parent).text("");
|
||||||
|
check.equals(parent.length, 4);
|
||||||
|
|
||||||
destroyChildren(parent, 1, 2);
|
destroyChildren(parent, 1, 2);
|
||||||
check.equals(parent.children.length, 2);
|
check.equals(parent.length, 2);
|
||||||
|
|
||||||
destroyChildren(parent);
|
destroyChildren(parent);
|
||||||
check.equals(parent.children.length, 0);
|
check.equals(parent.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("gets the screen boundaries of an object", check => {
|
/*test.case("gets the screen boundaries of an object", check => {
|
||||||
let parent = testgame.view.add.group();
|
let builder = new UIBuilder(testgame.view);
|
||||||
|
let parent = builder.group("group");
|
||||||
|
|
||||||
check.in("empty", check => {
|
check.in("empty", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 0, y: 0, width: 0, height: 0 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 0, y: 0, width: 0, height: 0 }, "parent");
|
||||||
});
|
});
|
||||||
|
|
||||||
let child1 = testgame.view.add.graphics(10, 20, parent);
|
let child1 = builder.in(parent).graphics("child1");
|
||||||
|
child1.setPosition(10, 20);
|
||||||
|
|
||||||
check.in("empty child", check => {
|
check.in("empty child", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 0, y: 0, width: 0, height: 0 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 0, y: 0, width: 0, height: 0 }, "parent");
|
||||||
check.containing(UITools.getScreenBounds(child1), { x: 0, y: 0, width: 0, height: 0 }, "child1");
|
check.containing(UITools.getBounds(child1), { x: 0, y: 0, width: 0, height: 0 }, "child1");
|
||||||
});
|
});
|
||||||
|
|
||||||
child1.drawRect(20, 30, 40, 45);
|
child1.addRectangle({ x: 20, y: 30, width: 40, height: 45 }, 0);
|
||||||
|
|
||||||
check.in("rectangle child", check => {
|
check.in("rectangle child", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 30, y: 50, width: 40, height: 45 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 30, y: 50, width: 40, height: 45 }, "parent");
|
||||||
check.containing(UITools.getScreenBounds(child1), { x: 30, y: 50, width: 40, height: 45 }, "child1");
|
check.containing(UITools.getBounds(child1), { x: 30, y: 50, width: 40, height: 45 }, "child1");
|
||||||
});
|
});
|
||||||
|
|
||||||
child1.scale.set(0.5, 0.2);
|
child1.setScale(0.5, 0.2);
|
||||||
|
|
||||||
check.in("scaled child", check => {
|
check.in("scaled child", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 20, y: 26, width: 20, height: 9 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 20, y: 26, width: 20, height: 9 }, "parent");
|
||||||
check.containing(UITools.getScreenBounds(child1), { x: 20, y: 26, width: 20, height: 9 }, "child1");
|
check.containing(UITools.getBounds(child1), { x: 20, y: 26, width: 20, height: 9 }, "child1");
|
||||||
});
|
});
|
||||||
|
|
||||||
let child2 = testgame.view.add.graphics(-4, -15);
|
let child2 = testgame.view.add.graphics(-4, -15);
|
||||||
child1.addChild(child2);
|
child1.addChild(child2);
|
||||||
|
|
||||||
check.in("sub child empty", check => {
|
check.in("sub child empty", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 20, y: 26, width: 20, height: 9 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 20, y: 26, width: 20, height: 9 }, "parent");
|
||||||
check.containing(UITools.getScreenBounds(child1), { x: 20, y: 26, width: 20, height: 9 }, "child1");
|
check.containing(UITools.getBounds(child1), { x: 20, y: 26, width: 20, height: 9 }, "child1");
|
||||||
check.containing(UITools.getScreenBounds(child2), { x: 0, y: 0, width: 0, height: 0 }, "child2");
|
check.containing(UITools.getBounds(child2), { x: 0, y: 0, width: 0, height: 0 }, "child2");
|
||||||
});
|
});
|
||||||
|
|
||||||
child2.drawRect(0, 0, 10, 5);
|
child2.drawRect(0, 0, 10, 5);
|
||||||
|
|
||||||
check.in("sub child rectangle", check => {
|
check.in("sub child rectangle", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 8, y: 17, width: 32, height: 18 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 8, y: 17, width: 32, height: 18 }, "parent");
|
||||||
check.containing(UITools.getScreenBounds(child1), { x: 8, y: 17, width: 32, height: 18 }, "child1");
|
check.containing(UITools.getBounds(child1), { x: 8, y: 17, width: 32, height: 18 }, "child1");
|
||||||
check.containing(UITools.getScreenBounds(child2), { x: 8, y: 17, width: 5, height: 1 }, "child2");
|
check.containing(UITools.getBounds(child2), { x: 8, y: 17, width: 5, height: 1 }, "child2");
|
||||||
});
|
});
|
||||||
|
|
||||||
let child3 = testgame.view.add.graphics(50, 51, parent);
|
let child3 = testgame.view.add.graphics(50, 51, parent);
|
||||||
|
|
||||||
check.in("second child empty", check => {
|
check.in("second child empty", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 8, y: 17, width: 42, height: 34 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 8, y: 17, width: 42, height: 34 }, "parent");
|
||||||
check.containing(UITools.getScreenBounds(child1), { x: 8, y: 17, width: 32, height: 18 }, "child1");
|
check.containing(UITools.getBounds(child1), { x: 8, y: 17, width: 32, height: 18 }, "child1");
|
||||||
check.containing(UITools.getScreenBounds(child2), { x: 8, y: 17, width: 5, height: 1 }, "child2");
|
check.containing(UITools.getBounds(child2), { x: 8, y: 17, width: 5, height: 1 }, "child2");
|
||||||
check.containing(UITools.getScreenBounds(child3), { x: 0, y: 0, width: 0, height: 0 }, "child3");
|
check.containing(UITools.getBounds(child3), { x: 0, y: 0, width: 0, height: 0 }, "child3");
|
||||||
});
|
});
|
||||||
|
|
||||||
child3.drawRect(1, 1, 1, 1);
|
child3.drawRect(1, 1, 1, 1);
|
||||||
|
|
||||||
check.in("second child pixel", check => {
|
check.in("second child pixel", check => {
|
||||||
check.containing(UITools.getScreenBounds(parent), { x: 8, y: 17, width: 44, height: 36 }, "parent");
|
check.containing(UITools.getBounds(parent), { x: 8, y: 17, width: 44, height: 36 }, "parent");
|
||||||
check.containing(UITools.getScreenBounds(child1), { x: 8, y: 17, width: 32, height: 18 }, "child1");
|
check.containing(UITools.getBounds(child1), { x: 8, y: 17, width: 32, height: 18 }, "child1");
|
||||||
check.containing(UITools.getScreenBounds(child2), { x: 8, y: 17, width: 5, height: 1 }, "child2");
|
check.containing(UITools.getBounds(child2), { x: 8, y: 17, width: 5, height: 1 }, "child2");
|
||||||
check.containing(UITools.getScreenBounds(child3), { x: 51, y: 52, width: 1, height: 1 }, "child3");
|
check.containing(UITools.getBounds(child3), { x: 51, y: 52, width: 1, height: 1 }, "child3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("keeps objects inside bounds", check => {
|
test.case("keeps objects inside bounds", check => {
|
||||||
let image = testgame.view.add.graphics(150, 100);
|
let builder = new UIBuilder(testgame.view);
|
||||||
|
let image = builder.graphics("test", 150, 100, true);
|
||||||
image.beginFill(0xff0000);
|
image.beginFill(0xff0000);
|
||||||
image.drawEllipse(50, 25, 50, 25);
|
image.drawEllipse(50, 25, 50, 25);
|
||||||
image.endFill();
|
image.endFill();
|
||||||
|
@ -108,7 +112,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
content.drawCircle(0, 0, 50);
|
content.drawCircle(0, 0, 50);
|
||||||
result = UITools.drawBackground(group, background, 3);
|
result = UITools.drawBackground(group, background, 3);
|
||||||
check.equals(result, [181, 141]);
|
check.equals(result, [181, 141]);
|
||||||
});
|
});*/
|
||||||
});
|
});
|
||||||
|
|
||||||
test.case("normalizes angles", check => {
|
test.case("normalizes angles", check => {
|
||||||
|
|
|
@ -11,28 +11,49 @@ module TK.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* This is a workaround for a removeChildren bug
|
* This is a workaround for a removeChildren bug
|
||||||
*/
|
*/
|
||||||
export function destroyChildren(obj: Phaser.Image | Phaser.Sprite | Phaser.Group, start = 0, end = obj.children.length - 1) {
|
export function destroyChildren(obj: UIContainer, start = 0, end = obj.length - 1) {
|
||||||
obj.children.slice(start, end + 1).forEach(child => (<any>child).destroy());
|
obj.list.slice(start, end + 1).forEach(child => child.destroy());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common UI tools functions
|
/**
|
||||||
|
* Common UI function to work around some Phaser limitations
|
||||||
|
*/
|
||||||
export class UITools {
|
export class UITools {
|
||||||
/**
|
/**
|
||||||
* Get the screen bounding rectanle of a displayed object
|
* Check that a game object has transform and bounds available
|
||||||
*
|
|
||||||
* This is a workaround for bugs in getLocalBounds and getBounds
|
|
||||||
*/
|
*/
|
||||||
static getScreenBounds(obj: Phaser.Image | Phaser.Sprite | Phaser.Group | Phaser.Graphics): IBounded {
|
static isSpatial(obj: any): obj is Phaser.GameObjects.Components.GetBounds & Phaser.GameObjects.Components.Transform {
|
||||||
obj.updateTransform();
|
return obj instanceof UIImage || obj instanceof UIText || obj instanceof UIContainer;
|
||||||
|
}
|
||||||
|
|
||||||
let rects: IBounded[] = [obj.getBounds()];
|
/**
|
||||||
obj.children.forEach(child => {
|
* Get the bounding rectanle of a displayed object, in screen space
|
||||||
if (child instanceof Phaser.Image || child instanceof Phaser.Sprite || child instanceof Phaser.Group || child instanceof Phaser.Graphics) {
|
*/
|
||||||
rects.push(UITools.getScreenBounds(child));
|
static getBounds(obj: UIContainer | (Phaser.GameObjects.GameObject & Phaser.GameObjects.Components.GetBounds)): IBounded {
|
||||||
|
let result: IBounded;
|
||||||
|
|
||||||
|
if (obj instanceof UIContainer) {
|
||||||
|
result = obj.getBounds();
|
||||||
|
} else {
|
||||||
|
result = obj.getBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a game object is visible
|
||||||
|
*/
|
||||||
|
static isVisible(obj: Phaser.GameObjects.GameObject & Phaser.GameObjects.Components.Visible & Phaser.GameObjects.Components.Alpha): boolean {
|
||||||
|
if (obj.visible && obj.alpha) {
|
||||||
|
if (obj.parentContainer) {
|
||||||
|
return this.isVisible(obj.parentContainer);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
return false;
|
||||||
return rects.reduce(UITools.unionRects, { x: 0, y: 0, width: 0, height: 0 });
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,12 +82,12 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Reposition an object to remain inside a container
|
* Reposition an object to remain inside a container
|
||||||
*/
|
*/
|
||||||
static keepInside(obj: Phaser.Button | Phaser.Sprite | Phaser.Image | Phaser.Group | Phaser.Graphics, rect: IBounded) {
|
static keepInside(obj: UIButton | UIImage | UIContainer, rect: IBounded) {
|
||||||
let objbounds = obj.getBounds();
|
let objbounds = UITools.getBounds(obj);
|
||||||
let [x, y] = UITools.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.setPosition(x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,26 +156,10 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Draw a background around a content
|
* Draw a background around a content
|
||||||
*/
|
*/
|
||||||
static drawBackground(content: Phaser.Group | Phaser.Text, background: Phaser.Graphics, border = 6): [number, number] {
|
static drawBackground(content: UIContainer | UIText, background: UIBackground, border = 6): [number, number] {
|
||||||
if (content.parent === background.parent) {
|
if (content.parentContainer === background.parent) {
|
||||||
let bounds = content.getLocalBounds();
|
background.adaptToContent(content);
|
||||||
|
return [background.width, background.height];
|
||||||
let x = bounds.x - border;
|
|
||||||
let y = bounds.y - border;
|
|
||||||
let width = bounds.width + 2 * border;
|
|
||||||
let height = bounds.height + 2 * border;
|
|
||||||
|
|
||||||
if (!(background.width && background.data.bg_bounds && UITools.compareRects(background.data.bg_bounds, bounds))) {
|
|
||||||
background.clear();
|
|
||||||
background.lineStyle(2, 0x6690a4);
|
|
||||||
background.beginFill(0x162730);
|
|
||||||
background.drawRect(x, y, width, height);
|
|
||||||
background.endFill();
|
|
||||||
|
|
||||||
background.data.bg_bounds = copy(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [width, height];
|
|
||||||
} else {
|
} else {
|
||||||
console.error("Cannot draw background with different parents", content, background);
|
console.error("Cannot draw background with different parents", content, background);
|
||||||
return [0, 0];
|
return [0, 0];
|
||||||
|
|
|
@ -6,17 +6,16 @@ module TK.SpaceTac.UI {
|
||||||
constructor(view: BaseView, message: string, cancel?: Function) {
|
constructor(view: BaseView, message: string, cancel?: Function) {
|
||||||
super(view);
|
super(view);
|
||||||
|
|
||||||
this.addText(this.width * 0.5, this.height * 0.3, message, "#90FEE3", 32);
|
this.content.text(message, this.width * 0.5, this.height * 0.3, { color: "#90FEE3", size: 32 });
|
||||||
this.addLoader(this.width * 0.5, this.height * 0.6);
|
//this.addLoader(this.width * 0.5, this.height * 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an error as the result of waiting.
|
* Display an error as the result of waiting.
|
||||||
*/
|
*/
|
||||||
displayError(message: string) {
|
displayError(message: string) {
|
||||||
this.clearContent();
|
this.content.clear();
|
||||||
this.addText(this.width * 0.5, this.height * 0.5, message, "#FE7069", 32);
|
this.content.text(message, this.width * 0.5, this.height * 0.5, { color: "#FE7069", size: 32 });
|
||||||
this.addCloseButton();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
export class ValueBar {
|
export class ValueBar {
|
||||||
// Phaser node
|
// Phaser node
|
||||||
node: Phaser.Image
|
node: UIImage
|
||||||
|
|
||||||
// Orientation
|
// Orientation
|
||||||
private orientation: ValueBarOrientation
|
private orientation: ValueBarOrientation
|
||||||
|
@ -35,22 +35,27 @@ module TK.SpaceTac.UI {
|
||||||
// Original size
|
// Original size
|
||||||
private original_width: number
|
private original_width: number
|
||||||
private original_height: number
|
private original_height: number
|
||||||
private crop_rect: Phaser.Rectangle
|
private crop_rect: Phaser.Geom.Rectangle
|
||||||
|
private crop_mask: Phaser.GameObjects.Graphics
|
||||||
|
|
||||||
constructor(view: BaseView, name: string, orientation: ValueBarOrientation, x = 0, y = 0) {
|
constructor(view: BaseView, name: string, orientation: ValueBarOrientation, x = 0, y = 0) {
|
||||||
this.node = view.newImage(name, x, y);
|
this.node = view.newImage(name, x, y);
|
||||||
|
if (orientation == ValueBarOrientation.WEST) {
|
||||||
|
this.node.setOrigin(1, 0);
|
||||||
|
} else if (orientation == ValueBarOrientation.NORTH) {
|
||||||
|
this.node.setOrigin(0, 1);
|
||||||
|
} else {
|
||||||
|
this.node.setOrigin(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
this.orientation = orientation;
|
this.orientation = orientation;
|
||||||
this.original_width = this.node.width;
|
this.original_width = this.node.width;
|
||||||
this.original_height = this.node.height;
|
this.original_height = this.node.height;
|
||||||
|
|
||||||
this.crop_rect = new Phaser.Rectangle(0, 0, this.original_width, this.original_height);
|
this.crop_rect = new Phaser.Geom.Rectangle(0, 0, this.original_width, this.original_height);
|
||||||
this.node.crop(this.crop_rect);
|
this.crop_mask = view.make.graphics({ x: x, y: y, add: false });
|
||||||
|
this.crop_mask.fillStyle(0xffffff);
|
||||||
if (orientation == ValueBarOrientation.WEST) {
|
this.node.setMask(new Phaser.Display.Masks.GeometryMask(view, this.crop_mask));
|
||||||
this.node.anchor.set(1, 0);
|
|
||||||
} else if (orientation == ValueBarOrientation.NORTH) {
|
|
||||||
this.node.anchor.set(0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setValue(0, 1000);
|
this.setValue(0, 1000);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +81,9 @@ module TK.SpaceTac.UI {
|
||||||
this.crop_rect.height = Math.round(this.original_height * this.proportional);
|
this.crop_rect.height = Math.round(this.original_height * this.proportional);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.node.updateCrop();
|
|
||||||
|
this.crop_mask.clear();
|
||||||
|
this.crop_mask.fillRectShape(this.crop_rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@ module TK.SpaceTac.UI {
|
||||||
view: IntroView
|
view: IntroView
|
||||||
steps: Function[] = []
|
steps: Function[] = []
|
||||||
current = 0
|
current = 0
|
||||||
layers: Phaser.Group[] = []
|
layers: UIContainer[] = []
|
||||||
|
|
||||||
constructor(view: IntroView) {
|
constructor(view: IntroView) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
|
@ -76,21 +76,24 @@ module TK.SpaceTac.UI {
|
||||||
protected galaxy(): Function {
|
protected galaxy(): Function {
|
||||||
return () => {
|
return () => {
|
||||||
let layer = this.getLayer(0);
|
let layer = this.getLayer(0);
|
||||||
let game = this.view.game;
|
let animations = this.view.animations;
|
||||||
let mwidth = this.view.getMidWidth();
|
let mwidth = this.view.getMidWidth();
|
||||||
let mheight = this.view.getMidHeight();
|
let mheight = this.view.getMidHeight();
|
||||||
|
|
||||||
let galaxy = game.add.group(layer, "galaxy");
|
let builder = new UIBuilder(this.view, layer);
|
||||||
galaxy.position.set(mwidth, mheight);
|
|
||||||
game.tweens.create(galaxy).to({ rotation: Math.PI * 2 }, 150000).loop().start();
|
|
||||||
game.tweens.create(galaxy).from({ alpha: 0 }, 3000).start();
|
|
||||||
|
|
||||||
let builder = new UIBuilder(this.view, galaxy);
|
let galaxy = builder.container("galaxy", 0, 0, false);
|
||||||
let back1 = builder.image("intro-galaxy1", 0, 0, true);
|
galaxy.setPosition(mwidth, mheight);
|
||||||
back1.scale.set(2.5);
|
animations.show(galaxy, 3000);
|
||||||
let back2 = builder.image("intro-galaxy2", 0, 0, true);
|
animations.addAnimation(galaxy, { rotation: Math.PI * 2 }, 150000, undefined, undefined, Infinity);
|
||||||
back2.scale.set(1.5);
|
|
||||||
game.tweens.create(back2).to({ rotation: Math.PI * 2 }, 300000).loop().start();
|
builder.in(galaxy, builder => {
|
||||||
|
let back1 = builder.image("intro-galaxy1", 0, 0, true);
|
||||||
|
back1.setScale(2.5);
|
||||||
|
let back2 = builder.image("intro-galaxy2", 0, 0, true);
|
||||||
|
back2.setScale(1.5);
|
||||||
|
animations.addAnimation(back2, { rotation: Math.PI * 2 }, 300000, undefined, undefined, Infinity);
|
||||||
|
});
|
||||||
|
|
||||||
let random = RandomGenerator.global;
|
let random = RandomGenerator.global;
|
||||||
range(200).forEach(i => {
|
range(200).forEach(i => {
|
||||||
|
@ -98,13 +101,20 @@ module TK.SpaceTac.UI {
|
||||||
let angle = random.random() * Math.PI * 2;
|
let angle = random.random() * Math.PI * 2;
|
||||||
let power = 0.4 + random.random() * 0.6;
|
let power = 0.4 + random.random() * 0.6;
|
||||||
|
|
||||||
let star = game.add.image(distance * Math.cos(angle), distance * Math.sin(angle),
|
let star = this.view.add.image(distance * Math.cos(angle), distance * Math.sin(angle),
|
||||||
"common-particles", 16, galaxy);
|
"common-particles", 16);
|
||||||
star.scale.set(0.15 + random.random() * 0.2);
|
star.setScale(0.15 + random.random() * 0.2);
|
||||||
star.anchor.set(0.5);
|
star.setAlpha(power * 0.5);
|
||||||
star.alpha = power * 0.5;
|
this.view.tweens.add({
|
||||||
game.tweens.create(star).to({ alpha: star.alpha + 0.5 }, 200 + random.random() * 500,
|
targets: star,
|
||||||
undefined, true, 1000 + random.random() * 3000, undefined, true).repeat(-1, 2000 + random.random() * 5000).start();
|
alpha: star.alpha + 0.5,
|
||||||
|
duration: 200 + random.random() * 500,
|
||||||
|
delay: 1000 + random.random() * 3000,
|
||||||
|
yoyo: true,
|
||||||
|
loop: Infinity,
|
||||||
|
loopDelay: 2000 + random.random() * 5000
|
||||||
|
});
|
||||||
|
galaxy.add(star);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,23 +125,27 @@ module TK.SpaceTac.UI {
|
||||||
protected exitftl(): Function {
|
protected exitftl(): Function {
|
||||||
return () => {
|
return () => {
|
||||||
let layer = this.getLayer(1);
|
let layer = this.getLayer(1);
|
||||||
|
|
||||||
let builder = new ParticleBuilder(this.view);
|
let builder = new ParticleBuilder(this.view);
|
||||||
let fleet = builder.build([
|
let fleet = builder.build([
|
||||||
new ParticleConfig(ParticleShape.TRAIL, ParticleColor.BLUEISH, 0.8, 1, 200),
|
new ParticleConfig(ParticleShape.TRAIL, ParticleColor.BLUEISH, 0.8, 1, 200),
|
||||||
new ParticleConfig(ParticleShape.FLARE, ParticleColor.CYAN, 10, 0.2, -45)
|
new ParticleConfig(ParticleShape.FLARE, ParticleColor.CYAN, 10, 0.2, -45)
|
||||||
]);
|
]);
|
||||||
fleet.position.set(this.view.getMidWidth(), this.view.getMidHeight());
|
fleet.setPosition(this.view.getMidWidth() + 1500, this.view.getMidHeight() - 750);
|
||||||
this.view.game.add.tween(fleet).from({ x: fleet.x + 1500, y: fleet.y - 750 }, 5000, Phaser.Easing.Circular.Out, true);
|
this.view.animations.addAnimation(fleet, { x: this.view.getMidWidth(), y: this.view.getMidHeight() }, 5000, "Circ.easeOut");
|
||||||
this.view.game.add.tween(fleet).to({ alpha: 0, width: 40, height: 40 }, 500, Phaser.Easing.Cubic.Out, true, 3500);
|
this.view.animations.addAnimation(fleet, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, 500, "Cubic.easeOut", 3500);
|
||||||
let flash = this.view.game.add.image(this.view.getMidWidth() + 60, this.view.getMidHeight() - 30, "common-particles", 15);
|
|
||||||
flash.anchor.set(0.5);
|
let flash = this.view.add.container(this.view.getMidWidth() + 60, this.view.getMidHeight() - 30);
|
||||||
flash.scale.set(0.1);
|
flash.setAlpha(0);
|
||||||
flash.alpha = 0;
|
flash.setScale(0.1);
|
||||||
let subflash = this.view.game.add.image(0, 0, "common-particles", 0);
|
new UIBuilder(this.view).in(flash, builder => {
|
||||||
subflash.anchor.set(0.5);
|
let sub = this.view.add.image(0, 0, "common-particles", 15);
|
||||||
subflash.scale.set(0.5);
|
flash.add(sub);
|
||||||
flash.addChild(subflash);
|
sub = this.view.add.image(0, 0, "common-particles", 0);
|
||||||
this.view.game.add.tween(flash).to({ alpha: 0.7, width: 60, height: 60 }, 300, Phaser.Easing.Quadratic.Out, true, 3500, undefined, true);
|
sub.setScale(0.5);
|
||||||
|
flash.add(sub);
|
||||||
|
});
|
||||||
|
this.view.animations.addAnimation(flash, { alpha: 0.7, scaleX: 2.5, scaleY: 2.5 }, 300, "Quad.easeOut", 3500, undefined, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +176,7 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Ensure that a layer exists, and if necessary, clean it
|
* Ensure that a layer exists, and if necessary, clean it
|
||||||
*/
|
*/
|
||||||
protected getLayer(layer: number, clear = false): Phaser.Group {
|
protected getLayer(layer: number, clear = false): UIContainer {
|
||||||
while (this.layers.length <= layer) {
|
while (this.layers.length <= layer) {
|
||||||
this.layers.push(this.view.getLayer(`Layer ${this.layers.length}`));
|
this.layers.push(this.view.getLayer(`Layer ${this.layers.length}`));
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.input.onTap.add(nextStep);
|
this.input.on("pointerup", nextStep);
|
||||||
|
|
||||||
this.inputs.bind("Home", "Rewind", () => steps.rewind());
|
this.inputs.bind("Home", "Rewind", () => steps.rewind());
|
||||||
this.inputs.bind("Space", "Next step", nextStep);
|
this.inputs.bind("Space", "Next step", nextStep);
|
||||||
|
@ -32,7 +32,7 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.gameui.audio.startMusic("division");
|
this.audio.startMusic("division");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,17 @@ module TK.SpaceTac.UI.Specs {
|
||||||
let missions = new ActiveMissions();
|
let missions = new ActiveMissions();
|
||||||
let display = new ActiveMissionsDisplay(view, missions);
|
let display = new ActiveMissionsDisplay(view, missions);
|
||||||
|
|
||||||
let container = <Phaser.Group>(<any>display).container;
|
let container = display.container;
|
||||||
check.equals(container.children.length, 0);
|
check.equals(container.length, 0);
|
||||||
|
|
||||||
let mission = new Mission(new Universe(), new Fleet());
|
let mission = new Mission(new Universe(), new Fleet());
|
||||||
mission.addPart(new MissionPart(mission, "Get back to base"));
|
mission.addPart(new MissionPart(mission, "Get back to base"));
|
||||||
missions.secondary = [mission];
|
missions.secondary = [mission];
|
||||||
|
|
||||||
display.checkUpdate();
|
display.checkUpdate();
|
||||||
check.equals(container.children.length, 2);
|
check.equals(container.length, 2);
|
||||||
check.equals(container.children[0] instanceof Phaser.Image, true);
|
check.equals(container.list[0] instanceof UIImage, true);
|
||||||
checkText(check, container.children[1], "Get back to base");
|
checkText(check, container.list[1], "Get back to base");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,45 +2,41 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Marker to show current location on the map
|
* Marker to show current location on the map
|
||||||
*/
|
*/
|
||||||
export class CurrentLocationMarker extends Phaser.Image {
|
export class CurrentLocationMarker extends UIImage {
|
||||||
private zoom = -1;
|
private zoom = -1;
|
||||||
private moving = false;
|
private moving = false;
|
||||||
private fleet: FleetDisplay;
|
private fleet: FleetDisplay;
|
||||||
|
|
||||||
constructor(parent: UniverseMapView, fleet: FleetDisplay) {
|
constructor(private view: UniverseMapView, fleet: FleetDisplay) {
|
||||||
super(parent.game, 0, 0, parent.getImageInfo("map-current-location").key, parent.getImageInfo("map-current-location").frame);
|
super(view, 0, 0, view.getImageInfo("map-current-location").key, view.getImageInfo("map-current-location").frame);
|
||||||
|
|
||||||
this.fleet = fleet;
|
this.fleet = fleet;
|
||||||
|
|
||||||
this.anchor.set(0.5, 0.5);
|
this.setOrigin(0.5, 0.5);
|
||||||
this.alpha = 0;
|
this.alpha = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
tweenTo(alpha: number, scale: number) {
|
tweenTo(alpha: number, scale: number) {
|
||||||
this.game.tweens.removeFrom(this);
|
this.view.animations.addAnimation<UIImage>(this, { alpha: alpha, scaleX: scale, scaleY: scale }, 500);
|
||||||
this.game.tweens.removeFrom(this.scale);
|
|
||||||
|
|
||||||
this.game.tweens.create(this).to({ alpha: alpha }, 500).start();
|
|
||||||
this.game.tweens.create(this.scale).to({ x: scale, y: scale }, 500).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
let scale = 1;
|
let scale = 1;
|
||||||
if (this.zoom == 2) {
|
if (this.zoom == 2) {
|
||||||
this.position.set(this.fleet.x, this.fleet.y);
|
this.setPosition(this.fleet.x, this.fleet.y);
|
||||||
scale = this.fleet.scale.x * 4;
|
scale = this.fleet.scaleX * 4;
|
||||||
} else {
|
} else {
|
||||||
this.position.set(this.fleet.location.star.x, this.fleet.location.star.y);
|
this.setPosition(this.fleet.location.star.x, this.fleet.location.star.y);
|
||||||
scale = (this.zoom == 1) ? 0.002 : 0.016;
|
scale = (this.zoom == 1) ? 0.002 : 0.016;
|
||||||
}
|
}
|
||||||
this.alpha = 0;
|
this.setAlpha(0);
|
||||||
this.scale.set(scale * 10, scale * 10);
|
this.setScale(scale * 10);
|
||||||
|
|
||||||
this.tweenTo(1, scale);
|
this.tweenTo(1, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.tweenTo(0, this.scale.x * 10);
|
this.tweenTo(0, this.scaleX * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
setZoom(level: number) {
|
setZoom(level: number) {
|
||||||
|
|
|
@ -9,17 +9,12 @@ module TK.SpaceTac.UI.Specs {
|
||||||
fleet.loopOrbit();
|
fleet.loopOrbit();
|
||||||
check.equals(fleet.rotation, 0);
|
check.equals(fleet.rotation, 0);
|
||||||
|
|
||||||
mapview.game.tweens.update();
|
let tweendata = mapview.animations.simulate(fleet, "rotation", 4);
|
||||||
let tween = first(mapview.game.tweens.getAll(), tw => tw.target == fleet);
|
check.equals(tweendata.length, 4);
|
||||||
if (tween) {
|
check.nears(tweendata[0], 0);
|
||||||
let tweendata = tween.generateData(0.1);
|
check.nears(tweendata[1], -Math.PI * 2 / 3);
|
||||||
check.equals(tweendata.length, 3);
|
check.nears(tweendata[2], Math.PI * 2 / 3);
|
||||||
check.nears(tweendata[0].rotation, -Math.PI * 2 / 3);
|
check.nears(tweendata[3], 0);
|
||||||
check.nears(tweendata[1].rotation, -Math.PI * 4 / 3);
|
|
||||||
check.nears(tweendata[2].rotation, -Math.PI * 2);
|
|
||||||
} else {
|
|
||||||
check.fail("No tween found");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,13 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Group to display a fleet
|
* Group to display a fleet
|
||||||
*/
|
*/
|
||||||
export class FleetDisplay extends Phaser.Group {
|
export class FleetDisplay extends UIContainer {
|
||||||
private map: UniverseMapView
|
private map: UniverseMapView
|
||||||
private fleet: Fleet
|
private fleet: Fleet
|
||||||
private tween: Phaser.Tween
|
|
||||||
private ship_count = 0
|
private ship_count = 0
|
||||||
|
|
||||||
constructor(parent: UniverseMapView, fleet: Fleet) {
|
constructor(parent: UniverseMapView, fleet: Fleet) {
|
||||||
super(parent.game);
|
super(parent);
|
||||||
|
|
||||||
this.map = parent;
|
this.map = parent;
|
||||||
this.fleet = fleet;
|
this.fleet = fleet;
|
||||||
|
@ -28,11 +27,10 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
let location = this.map.universe.getLocation(fleet.location);
|
let location = this.map.universe.getLocation(fleet.location);
|
||||||
if (location) {
|
if (location) {
|
||||||
this.position.set(location.star.x + location.x, location.star.y + location.y);
|
this.setPosition(location.star.x + location.x, location.star.y + location.y);
|
||||||
}
|
}
|
||||||
this.scale.set(SCALING, SCALING);
|
this.setScale(SCALING, SCALING);
|
||||||
|
|
||||||
this.tween = this.game.tweens.create(this);
|
|
||||||
this.loopOrbit();
|
this.loopOrbit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,13 +39,14 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
updateShipSprites() {
|
updateShipSprites() {
|
||||||
if (this.ship_count != this.fleet.ships.length) {
|
if (this.ship_count != this.fleet.ships.length) {
|
||||||
this.removeAll(true);
|
let builder = new UIBuilder(this.map, this);
|
||||||
|
|
||||||
|
builder.clear();
|
||||||
|
|
||||||
this.fleet.ships.forEach((ship, index) => {
|
this.fleet.ships.forEach((ship, index) => {
|
||||||
let offset = LOCATIONS[index];
|
let offset = LOCATIONS[index];
|
||||||
let sprite = this.map.newImage(`ship-${ship.model.code}-sprite`, offset[0], offset[1] + 150);
|
let sprite = builder.image(`ship-${ship.model.code}-sprite`, offset[0], offset[1] + 150, true);
|
||||||
sprite.scale.set(64 / sprite.width);
|
sprite.setScale(64 / sprite.width);
|
||||||
sprite.anchor.set(0.5, 0.5);
|
|
||||||
this.add(sprite);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ship_count = this.fleet.ships.length;
|
this.ship_count = this.fleet.ships.length;
|
||||||
|
@ -62,7 +61,7 @@ module TK.SpaceTac.UI {
|
||||||
* Animate to a given position in orbit of its current star location
|
* Animate to a given position in orbit of its current star location
|
||||||
*/
|
*/
|
||||||
goToOrbitPoint(angle: number, speed = 1, fullturns = 0, then: Function | null = null, ease = false) {
|
goToOrbitPoint(angle: number, speed = 1, fullturns = 0, then: Function | null = null, ease = false) {
|
||||||
this.tween.stop(false);
|
this.map.animations.killPrevious(this);
|
||||||
this.rotation %= PI2;
|
this.rotation %= PI2;
|
||||||
|
|
||||||
let target = -angle;
|
let target = -angle;
|
||||||
|
@ -71,11 +70,10 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
target -= PI2 * fullturns;
|
target -= PI2 * fullturns;
|
||||||
let distance = Math.abs(target - this.rotation) / 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);
|
let tween = this.map.animations.addAnimation<UIContainer>(this, { rotation: target }, 30000 * distance / speed, ease ? "Cubic.easeIn" : "Linear");
|
||||||
if (then) {
|
if (then) {
|
||||||
this.tween.onComplete.addOnce(then);
|
tween.then(() => then());
|
||||||
}
|
}
|
||||||
this.tween.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,10 +101,10 @@ module TK.SpaceTac.UI {
|
||||||
if (on_leave) {
|
if (on_leave) {
|
||||||
on_leave(duration);
|
on_leave(duration);
|
||||||
}
|
}
|
||||||
let tween = this.game.tweens.create(this.position).to({ x: this.x + dx, y: this.y + dy }, duration, Phaser.Easing.Cubic.Out);
|
let tween = this.map.animations.addAnimation<UIContainer>(this, { x: this.x + dx, y: this.y + dy }, duration, "Cubic.easeOut");
|
||||||
tween.onComplete.addOnce(() => {
|
tween.then(() => {
|
||||||
if (this.fleet.battle) {
|
if (this.fleet.battle) {
|
||||||
this.game.state.start("router");
|
this.map.backToRouter();
|
||||||
} else {
|
} else {
|
||||||
this.map.current_location.setFleetMoving(false);
|
this.map.current_location.setFleetMoving(false);
|
||||||
this.loopOrbit();
|
this.loopOrbit();
|
||||||
|
@ -116,7 +114,6 @@ module TK.SpaceTac.UI {
|
||||||
on_finished();
|
on_finished();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tween.start();
|
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,26 +4,31 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Menu to display selected map location, and associated actions
|
* Menu to display selected map location, and associated actions
|
||||||
*/
|
*/
|
||||||
export class MapLocationMenu extends UIComponent {
|
export class MapLocationMenu {
|
||||||
constructor(view: BaseView) {
|
readonly container: UIContainer
|
||||||
super(view, 478, 500);
|
private content: UIBuilder
|
||||||
|
|
||||||
|
constructor(private view: BaseView, parent?: UIContainer, x = 0, y = 0) {
|
||||||
|
let builder = new UIBuilder(view, parent);
|
||||||
|
this.container = builder.container("location-menu", x, y);
|
||||||
|
this.content = builder.in(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set information displayed, with title and actions to show in menu
|
* Set information displayed, with title and actions to show in menu
|
||||||
*/
|
*/
|
||||||
setInfo(title: string, actions: [string, Function][]) {
|
setInfo(title: string, actions: [string, Function][]) {
|
||||||
this.clearContent();
|
this.content.clear();
|
||||||
|
|
||||||
if (title) {
|
if (title) {
|
||||||
this.builder.image("map-subname", 239, 57, true);
|
this.content.image("map-subname", 239, 57, true);
|
||||||
this.builder.text(title, 239, 57, { color: "#b8d2f1", size: 22 })
|
this.content.text(title, 239, 57, { color: "#b8d2f1", size: 22 })
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let idx = actions.length - 1; idx >= 0; idx--) {
|
for (let idx = actions.length - 1; idx >= 0; idx--) {
|
||||||
let [label, action] = actions[idx];
|
let [label, action] = actions[idx];
|
||||||
this.builder.button("map-action", 172, 48 + idx * 100 + 96, action).anchor.set(0.5);
|
this.content.button("map-action", 172, 48 + idx * 100 + 96, action, undefined, undefined, { center: true });
|
||||||
this.builder.text(label, 186, 48 + idx * 100 + 136, { color: "#b8d2f1", size: 20 });
|
this.content.text(label, 186, 48 + idx * 100 + 136, { color: "#b8d2f1", size: 20 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,16 @@ module TK.SpaceTac.UI {
|
||||||
* Marker to show a mission location on the map
|
* Marker to show a mission location on the map
|
||||||
*/
|
*/
|
||||||
export class MissionLocationMarker {
|
export class MissionLocationMarker {
|
||||||
private view: BaseView
|
private builder: UIBuilder
|
||||||
private container: Phaser.Group
|
|
||||||
private markers: [StarLocation | Star, string][] = []
|
private markers: [StarLocation | Star, string][] = []
|
||||||
private zoomed = true
|
private zoomed = true
|
||||||
private current_star?: Star
|
private current_star?: Star
|
||||||
|
|
||||||
constructor(view: BaseView, parent: Phaser.Group) {
|
constructor(private view: BaseView, parent: UIContainer) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.container = view.game.add.group(parent, "mission_markers");
|
|
||||||
|
let builder = new UIBuilder(view, parent);
|
||||||
|
this.builder = builder.in(builder.container("mission_markers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,16 +36,14 @@ module TK.SpaceTac.UI {
|
||||||
* Refresh the display
|
* Refresh the display
|
||||||
*/
|
*/
|
||||||
refresh(): void {
|
refresh(): void {
|
||||||
this.container.removeAll(true);
|
this.builder.clear();
|
||||||
|
|
||||||
this.markers.forEach(([location, name], index) => {
|
this.markers.forEach(([location, name], index) => {
|
||||||
let focus = this.zoomed ? location : (location instanceof StarLocation ? location.star : location);
|
let focus = this.zoomed ? location : (location instanceof StarLocation ? location.star : location);
|
||||||
if (location !== this.current_star || !this.zoomed) {
|
if (location !== this.current_star || !this.zoomed) {
|
||||||
let marker = this.getMarker(focus, index - 1);
|
let marker = this.getMarker(focus, index - 1);
|
||||||
let image = this.view.newImage(name, marker.x, marker.y);
|
let image = this.builder.image(name, marker.x, marker.y);
|
||||||
image.scale.set(marker.scale);
|
image.setScale(marker.scale);
|
||||||
image.anchor.set(0.5);
|
|
||||||
this.container.add(image);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,7 @@ module TK.SpaceTac.UI.Specs {
|
||||||
check.patch(shop, "getMissions", () => shop_missions);
|
check.patch(shop, "getMissions", () => shop_missions);
|
||||||
|
|
||||||
function checkTexts(dialog: MissionsDialog, expected: string[]) {
|
function checkTexts(dialog: MissionsDialog, expected: string[]) {
|
||||||
let i = 0;
|
check.equals(collectTexts(dialog.base), expected);
|
||||||
let container = <Phaser.Group>(<any>dialog).container;
|
|
||||||
container.children.forEach(child => {
|
|
||||||
if (child instanceof Phaser.Text) {
|
|
||||||
check.equals(child.text, expected[i++]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
check.equals(i, expected.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let missions = new MissionsDialog(testgame.view, shop, player);
|
let missions = new MissionsDialog(testgame.view, shop, player);
|
||||||
|
|
|
@ -16,6 +16,7 @@ module TK.SpaceTac.UI {
|
||||||
this.location = view.session.getLocation();
|
this.location = view.session.getLocation();
|
||||||
this.on_change = on_change || (() => null);
|
this.on_change = on_change || (() => null);
|
||||||
|
|
||||||
|
this.addCloseButton();
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,14 +24,13 @@ module TK.SpaceTac.UI {
|
||||||
* Refresh the dialog content
|
* Refresh the dialog content
|
||||||
*/
|
*/
|
||||||
refresh() {
|
refresh() {
|
||||||
this.clearContent();
|
this.content.clear();
|
||||||
this.addCloseButton();
|
|
||||||
|
|
||||||
let offset = 160;
|
let offset = 160;
|
||||||
|
|
||||||
let active = this.player.missions.getCurrent().filter(mission => !mission.main);
|
let active = this.player.missions.getCurrent().filter(mission => !mission.main);
|
||||||
if (active.length) {
|
if (active.length) {
|
||||||
this.addText(this.width / 2, offset, "Active jobs", "#b8d2f1", 36);
|
this.content.text("Active jobs", this.width / 2, offset, { color: "#b8d2f1", size: 36 });
|
||||||
offset += 110;
|
offset += 110;
|
||||||
|
|
||||||
active.forEach(mission => {
|
active.forEach(mission => {
|
||||||
|
@ -41,7 +41,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
let proposed = this.shop.getMissions(this.location);
|
let proposed = this.shop.getMissions(this.location);
|
||||||
if (proposed.length) {
|
if (proposed.length) {
|
||||||
this.addText(this.width / 2, offset, "Proposed jobs", "#b8d2f1", 36);
|
this.content.text("Proposed jobs", this.width / 2, offset, { color: "#b8d2f1", size: 36 });
|
||||||
offset += 110;
|
offset += 110;
|
||||||
|
|
||||||
proposed.forEach(mission => {
|
proposed.forEach(mission => {
|
||||||
|
@ -62,14 +62,14 @@ module TK.SpaceTac.UI {
|
||||||
let title = mission.title;
|
let title = mission.title;
|
||||||
let subtitle = `${capitalize(MissionDifficulty[mission.difficulty])} - Reward: ${mission.getRewardText()}`;
|
let subtitle = `${capitalize(MissionDifficulty[mission.difficulty])} - Reward: ${mission.getRewardText()}`;
|
||||||
|
|
||||||
this.addImage(320, yoffset, "map-mission-standard");
|
this.content.image("map-mission-standard", 320, yoffset, true);
|
||||||
if (title) {
|
if (title) {
|
||||||
this.addText(380, yoffset - 15, title, "#d2e1f3", 22, false, false, 620, true);
|
this.content.text(title, 380, yoffset - 15, { color: "#d2e1f3", size: 22, width: 620, center: false });
|
||||||
}
|
}
|
||||||
if (subtitle) {
|
if (subtitle) {
|
||||||
this.addText(380, yoffset + 22, subtitle, "#d2e1f3", 18, false, false, 620, true);
|
this.content.text(subtitle, 380, yoffset + 22, { color: "#d2e1f3", size: 18, width: 620, center: false });
|
||||||
}
|
}
|
||||||
this.builder.button(active ? "map-mission-action-cancel" : "map-mission-action-accept", 1120, yoffset, button_callback).anchor.set(0.5);
|
this.content.button(active ? "map-mission-action-cancel" : "map-mission-action-accept", 1120, yoffset, button_callback, undefined, undefined, { center: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,99 +1,81 @@
|
||||||
module TK.SpaceTac.UI {
|
module TK.SpaceTac.UI {
|
||||||
// Group to display a star system
|
/**
|
||||||
export class StarSystemDisplay extends Phaser.Image {
|
* Group to display a star system
|
||||||
|
*/
|
||||||
|
export class StarSystemDisplay extends UIContainer {
|
||||||
view: UniverseMapView
|
view: UniverseMapView
|
||||||
builder: UIBuilder
|
builder: UIBuilder
|
||||||
circles: Phaser.Group
|
circles: UIContainer
|
||||||
starsystem: Star
|
starsystem: Star
|
||||||
player: Player
|
player: Player
|
||||||
fleet_display: FleetDisplay
|
fleet_display: FleetDisplay
|
||||||
locations: [StarLocation, Phaser.Image, Phaser.Image][] = []
|
locations: [StarLocation, UIImage | UIButton, UIImage][] = []
|
||||||
label: Phaser.Button
|
label: UIButton
|
||||||
|
|
||||||
constructor(parent: UniverseMapView, starsystem: Star) {
|
constructor(parent: UniverseMapView, starsystem: Star) {
|
||||||
super(parent.game, starsystem.x, starsystem.y, parent.getImageInfo("map-starsystem-background").key, parent.getImageInfo("map-starsystem-background").frame);
|
super(parent, starsystem.x, starsystem.y);
|
||||||
|
|
||||||
this.view = parent;
|
this.view = parent;
|
||||||
this.builder = new UIBuilder(parent, this);
|
this.builder = new UIBuilder(parent, this);
|
||||||
|
|
||||||
this.anchor.set(0.5, 0.5);
|
let base = this.builder.image("map-starsystem-background", 0, 0, true);
|
||||||
|
this.setScale(starsystem.radius * 2 / base.width);
|
||||||
let scale = this.width;
|
|
||||||
this.scale.set(starsystem.radius * 2 / scale);
|
|
||||||
|
|
||||||
this.starsystem = starsystem;
|
this.starsystem = starsystem;
|
||||||
this.player = parent.player;
|
this.player = parent.player;
|
||||||
this.fleet_display = parent.player_fleet;
|
this.fleet_display = parent.player_fleet;
|
||||||
|
|
||||||
// Show boundary
|
// Show boundary
|
||||||
this.circles = this.builder.group("circles");
|
this.circles = this.builder.container("circles");
|
||||||
let boundaries = this.builder.in(this.circles).image("map-boundaries", 0, 0, true);
|
let boundaries = this.builder.in(this.circles).image("map-boundaries", 0, 0, true);
|
||||||
boundaries.scale.set(starsystem.radius / (this.scale.x * 256));
|
boundaries.setScale(starsystem.radius / (this.scaleX * 256));
|
||||||
|
|
||||||
// Show locations
|
// Show locations
|
||||||
starsystem.locations.map(location => {
|
starsystem.locations.map(location => {
|
||||||
let location_sprite: Phaser.Image | null = null;
|
let location_sprite: UIImage | UIButton | null = null;
|
||||||
let fleet_move = () => this.view.moveToLocation(location);
|
let loctype = StarLocationType[location.type].toLowerCase();
|
||||||
|
|
||||||
if (location.type == StarLocationType.STAR) {
|
location_sprite = this.builder.button(`map-location-${loctype}`, location.x / this.scaleX, location.y / this.scaleY,
|
||||||
location_sprite = this.addImage(location.x, location.y, "map-location-star", fleet_move);
|
() => this.view.moveToLocation(location),
|
||||||
} else if (location.type == StarLocationType.PLANET) {
|
(filler: TooltipBuilder) => {
|
||||||
location_sprite = this.addImage(location.x, location.y, "map-location-planet", fleet_move);
|
let visited = this.player.hasVisitedLocation(location);
|
||||||
location_sprite.rotation = Math.atan2(location.y, location.x);
|
let shop = (visited && !location.encounter && location.shop) ? " (dockyard present)" : "";
|
||||||
this.addCircle(location.x, location.y);
|
|
||||||
} else if (location.type == StarLocationType.WARP) {
|
if (location.is(this.player.fleet.location)) {
|
||||||
location_sprite = this.addImage(location.x, location.y, "map-location-warp", fleet_move);
|
return `Current fleet location${shop}`;
|
||||||
location_sprite.rotation = Math.atan2(location.y, location.x);
|
} else {
|
||||||
|
let danger = (visited && location.encounter) ? " [enemy fleet detected !]" : "";
|
||||||
|
return `${visited ? "Visited" : "Unvisited"} ${loctype} - Move the fleet there${danger}${shop}`;
|
||||||
|
}
|
||||||
|
}, undefined, { center: true });
|
||||||
|
|
||||||
|
location_sprite.setRotation(Math.atan2(location.y, location.x));
|
||||||
|
if (location.type == StarLocationType.PLANET) {
|
||||||
|
this.addOrbit(location.x, location.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.view.tooltip.bindDynamicText(<Phaser.Button>location_sprite, () => {
|
let status = this.getBadgeFrame(location);
|
||||||
let visited = this.player.hasVisitedLocation(location);
|
let status_badge = this.builder.image(`map-status-${status}`, (location.x + 0.005) / this.scaleX, (location.y + 0.005) / this.scaleY, true);
|
||||||
let shop = (visited && !location.encounter && location.shop) ? " (dockyard present)" : "";
|
this.locations.push([location, location_sprite, status_badge]);
|
||||||
|
|
||||||
if (location.is(this.player.fleet.location)) {
|
|
||||||
return `Current fleet location${shop}`;
|
|
||||||
} else {
|
|
||||||
let loctype = StarLocationType[location.type].toLowerCase();
|
|
||||||
let danger = (visited && location.encounter) ? " [enemy fleet detected !]" : "";
|
|
||||||
return `${visited ? "Visited" : "Unvisited"} ${loctype} - Move the fleet there${danger}${shop}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (location_sprite) {
|
|
||||||
let status = this.getBadgeFrame(location);
|
|
||||||
let status_badge = this.addImage(location.x + 0.005, location.y + 0.005, `map-status-${status}`);
|
|
||||||
this.locations.push([location, location_sprite, status_badge]);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show name
|
// Show name
|
||||||
this.label = this.builder.button("map-name", 0, 460, undefined, `Level ${this.starsystem.level} starsystem`);
|
this.label = this.builder.button("map-name", 0, 460, undefined, `Level ${this.starsystem.level} starsystem`, undefined, { center: true });
|
||||||
this.label.anchor.set(0.5);
|
|
||||||
this.builder.in(this.label, builder => {
|
this.builder.in(this.label, builder => {
|
||||||
builder.text(this.starsystem.name, -30, 0, { size: 32, color: "#b8d2f1" });
|
builder.text(this.starsystem.name, -30, 0, { size: 32, color: "#b8d2f1" });
|
||||||
builder.text(this.starsystem.level.toString(), 243, 30, { size: 24, color: "#a0a0a0" });
|
builder.text(this.starsystem.level.toString(), 243, 30, { size: 24, color: "#a0a0a0" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addImage(x: number, y: number, name: string, onclick: Function | null = null): Phaser.Image {
|
|
||||||
x /= this.scale.x;
|
|
||||||
y /= this.scale.y;
|
|
||||||
let info = this.view.getImageInfo(name);
|
|
||||||
let image = onclick ? this.game.add.button(x, y, info.key, onclick, undefined, info.frame, info.frame) : this.game.add.image(x, y, info.key, info.frame);
|
|
||||||
image.anchor.set(0.5, 0.5);
|
|
||||||
this.addChild(image);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an orbit marker
|
* Add an orbit marker
|
||||||
*/
|
*/
|
||||||
addCircle(x: number, y: number): void {
|
addOrbit(x: number, y: number): void {
|
||||||
let radius = Math.sqrt(x * x + y * y);
|
let radius = Math.sqrt(x * x + y * y);
|
||||||
let angle = Math.atan2(y, x);
|
let angle = Math.atan2(y, x);
|
||||||
|
|
||||||
let circle = this.builder.in(this.circles).image("map-orbit", 0, 0, true);
|
let circle = this.builder.in(this.circles).image("map-orbit", 0, 0, true);
|
||||||
circle.scale.set(radius / (this.scale.x * 198));
|
circle.setScale(radius / (this.scaleX * 198));
|
||||||
circle.rotation = angle - 0.01;
|
circle.rotation = angle - 0.01;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +107,11 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// LOD
|
// LOD
|
||||||
let detailed = focus && level == 2;
|
let detailed = focus && level == 2;
|
||||||
this.children.filter(child => child !== this.label).forEach(child => this.view.animations.setVisible(child, detailed, 300));
|
this.list.filter(child => child !== this.label).forEach(child => {
|
||||||
|
if (child !== this.label && (child instanceof UIButton || child instanceof UIImage)) {
|
||||||
|
this.view.animations.setVisible(child, detailed, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.updateLabel(level);
|
this.updateLabel(level);
|
||||||
}
|
}
|
||||||
|
@ -137,10 +123,8 @@ module TK.SpaceTac.UI {
|
||||||
this.label.visible = this.player.hasVisitedSystem(this.starsystem);
|
this.label.visible = this.player.hasVisitedSystem(this.starsystem);
|
||||||
|
|
||||||
let factor = (zoom == 2) ? 1 : (zoom == 1 ? 5 : 15);
|
let factor = (zoom == 2) ? 1 : (zoom == 1 ? 5 : 15);
|
||||||
this.view.tweens.create(this.label.scale).to({ x: factor, y: factor }, 500, Phaser.Easing.Cubic.InOut).start();
|
|
||||||
|
|
||||||
let position = (zoom == 2) ? { x: -680, y: 440 } : { x: 0, y: (zoom == 1 ? 180 : 100) * factor };
|
let position = (zoom == 2) ? { x: -680, y: 440 } : { x: 0, y: (zoom == 1 ? 180 : 100) * factor };
|
||||||
this.view.tweens.create(this.label.position).to(position, 500, Phaser.Easing.Cubic.InOut).start();
|
this.view.animations.addAnimation(this.label, { x: position.x, y: position.y, scaleX: factor, scaleY: factor }, 500, "Cubic.easeInOut");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,15 @@ module TK.SpaceTac.UI {
|
||||||
interactive = true
|
interactive = true
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
layer_universe!: Phaser.Group
|
layer_universe!: UIContainer
|
||||||
layer_overlay!: Phaser.Group
|
layer_overlay!: UIContainer
|
||||||
|
|
||||||
// Star systems
|
// Star systems
|
||||||
starsystems: StarSystemDisplay[] = []
|
starsystems: StarSystemDisplay[] = []
|
||||||
|
|
||||||
// Links between stars
|
// Links between stars
|
||||||
starlinks_group!: Phaser.Group
|
starlinks_group!: UIContainer
|
||||||
starlinks: Phaser.Graphics[] = []
|
starlinks: UIGraphics[] = []
|
||||||
|
|
||||||
// Fleets
|
// Fleets
|
||||||
player_fleet!: FleetDisplay
|
player_fleet!: FleetDisplay
|
||||||
|
@ -44,20 +44,20 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
// Zoom level
|
// Zoom level
|
||||||
zoom = 0
|
zoom = 0
|
||||||
zoom_in!: Phaser.Button
|
zoom_in!: UIButton
|
||||||
zoom_out!: Phaser.Button
|
zoom_out!: UIButton
|
||||||
|
|
||||||
// Options button
|
// Options button
|
||||||
button_options!: Phaser.Button
|
button_options!: UIButton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init the view, binding it to a universe
|
* Init the view, binding it to a universe
|
||||||
*/
|
*/
|
||||||
init(universe: Universe, player: Player) {
|
init(data: { universe: Universe, player: Player }) {
|
||||||
super.init();
|
super.init(data);
|
||||||
|
|
||||||
this.universe = universe;
|
this.universe = data.universe;
|
||||||
this.player = player;
|
this.player = data.player;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,27 +71,29 @@ module TK.SpaceTac.UI {
|
||||||
this.layer_universe = this.getLayer("universe");
|
this.layer_universe = this.getLayer("universe");
|
||||||
this.layer_overlay = this.getLayer("overlay");
|
this.layer_overlay = this.getLayer("overlay");
|
||||||
|
|
||||||
this.starlinks_group = this.game.add.group(this.layer_universe);
|
this.starlinks_group = builder.in(this.layer_universe).container("starlinks");
|
||||||
|
this.starlinks = [];
|
||||||
this.starlinks = this.universe.starlinks.map(starlink => {
|
this.starlinks = this.universe.starlinks.map(starlink => {
|
||||||
let loc1 = starlink.first.getWarpLocationTo(starlink.second);
|
let loc1 = starlink.first.getWarpLocationTo(starlink.second);
|
||||||
let loc2 = starlink.second.getWarpLocationTo(starlink.first);
|
let loc2 = starlink.second.getWarpLocationTo(starlink.first);
|
||||||
|
|
||||||
let result = new Phaser.Graphics(this.game);
|
let result = builder.in(this.starlinks_group).graphics("starlink");
|
||||||
if (loc1 && loc2) {
|
if (loc1 && loc2) {
|
||||||
result.lineStyle(0.01, 0x6cc7ce);
|
result.lineStyle(0.01, 0x6cc7ce);
|
||||||
|
result.beginPath();
|
||||||
result.moveTo(starlink.first.x - 0.5 + loc1.x, starlink.first.y - 0.5 + loc1.y);
|
result.moveTo(starlink.first.x - 0.5 + loc1.x, starlink.first.y - 0.5 + loc1.y);
|
||||||
result.lineTo(starlink.second.x - 0.5 + loc2.x, starlink.second.y - 0.5 + loc2.y);
|
result.lineTo(starlink.second.x - 0.5 + loc2.x, starlink.second.y - 0.5 + loc2.y);
|
||||||
|
result.strokePath();
|
||||||
}
|
}
|
||||||
result.data.link = starlink;
|
result.setDataEnabled();
|
||||||
|
result.data.set("link", starlink);
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
this.starlinks.forEach(starlink => this.starlinks_group.add(starlink));
|
|
||||||
|
|
||||||
this.player_fleet = new FleetDisplay(this, this.player.fleet);
|
|
||||||
|
|
||||||
this.starsystems = this.universe.stars.map(star => new StarSystemDisplay(this, star));
|
this.starsystems = this.universe.stars.map(star => new StarSystemDisplay(this, star));
|
||||||
this.starsystems.forEach(starsystem => this.layer_universe.add(starsystem));
|
this.starsystems.forEach(starsystem => this.layer_universe.add(starsystem));
|
||||||
|
|
||||||
|
this.player_fleet = new FleetDisplay(this, this.player.fleet);
|
||||||
this.layer_universe.add(this.player_fleet);
|
this.layer_universe.add(this.player_fleet);
|
||||||
|
|
||||||
this.current_location = new CurrentLocationMarker(this, this.player_fleet);
|
this.current_location = new CurrentLocationMarker(this, this.player_fleet);
|
||||||
|
@ -99,9 +101,7 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
this.mission_markers = new MissionLocationMarker(this, this.layer_universe);
|
this.mission_markers = new MissionLocationMarker(this, this.layer_universe);
|
||||||
|
|
||||||
this.actions = new MapLocationMenu(this);
|
this.actions = new MapLocationMenu(this, this.layer_overlay, 30, 30);
|
||||||
this.actions.setPosition(30, 30);
|
|
||||||
this.actions.moveToLayer(this.layer_overlay);
|
|
||||||
|
|
||||||
this.missions = new ActiveMissionsDisplay(this, this.player.missions, this.mission_markers);
|
this.missions = new ActiveMissionsDisplay(this, this.player.missions, this.mission_markers);
|
||||||
this.missions.setPosition(20, 720);
|
this.missions.setPosition(20, 720);
|
||||||
|
@ -114,12 +114,12 @@ module TK.SpaceTac.UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.EDITION);
|
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.EDITION);
|
||||||
this.layer_overlay.add(this.character_sheet);
|
this.character_sheet.moveToLayer(this.layer_overlay);
|
||||||
|
|
||||||
this.conversation = new MissionConversationDisplay(this);
|
this.conversation = new MissionConversationDisplay(this);
|
||||||
this.conversation.moveToLayer(this.layer_overlay);
|
this.conversation.moveToLayer(this.layer_overlay);
|
||||||
|
|
||||||
this.gameui.audio.startMusic("spring-thaw");
|
this.audio.startMusic("spring-thaw");
|
||||||
|
|
||||||
// Inputs
|
// Inputs
|
||||||
this.inputs.bind(" ", "Conversation step", () => this.conversation.forward());
|
this.inputs.bind(" ", "Conversation step", () => this.conversation.forward());
|
||||||
|
@ -138,14 +138,8 @@ module TK.SpaceTac.UI {
|
||||||
|
|
||||||
this.setZoom(2, 0);
|
this.setZoom(2, 0);
|
||||||
|
|
||||||
// Add a shader background
|
// Add a background
|
||||||
builder.shader("map-background", { width: this.getWidth(), height: this.getHeight() }, 0, 0, () => {
|
//builder.image("map-background");
|
||||||
let scale = this.layer_universe.scale.x;
|
|
||||||
return {
|
|
||||||
offset: { x: (920 - this.layer_universe.x) / scale, y: -(540 - this.layer_universe.y) / scale },
|
|
||||||
scale: scale
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trigger an auto-save any time we go back to the map
|
// Trigger an auto-save any time we go back to the map
|
||||||
this.autoSave();
|
this.autoSave();
|
||||||
|
@ -195,8 +189,10 @@ module TK.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.starlinks.forEach(linkgraphics => {
|
this.starlinks.forEach(linkgraphics => {
|
||||||
let link = <StarLink>linkgraphics.data.link;
|
let link = linkgraphics.data.get("link");
|
||||||
linkgraphics.visible = this.player.hasVisitedSystem(link.first) || this.player.hasVisitedSystem(link.second);
|
if (link instanceof StarLink) {
|
||||||
|
linkgraphics.visible = this.player.hasVisitedSystem(link.first) || this.player.hasVisitedSystem(link.second);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.starsystems.forEach(system => system.updateInfo(this.zoom, system.starsystem == current_star));
|
this.starsystems.forEach(system => system.updateInfo(this.zoom, system.starsystem == current_star));
|
||||||
|
@ -226,16 +222,15 @@ module TK.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Set the camera to center on a target, and to display a given span in height
|
* Set the camera to center on a target, and to display a given span in height
|
||||||
*/
|
*/
|
||||||
setCamera(x: number, y: number, span: number, duration = 500, easing = Phaser.Easing.Cubic.InOut) {
|
setCamera(x: number, y: number, span: number, duration = 500, easing = "Cubic.easeInOut") {
|
||||||
let scale = 1000 / span;
|
let scale = 1000 / span;
|
||||||
let dest_x = 920 - x * scale;
|
let dest_x = 920 - x * scale;
|
||||||
let dest_y = 540 - y * scale;
|
let dest_y = 540 - y * scale;
|
||||||
if (duration) {
|
if (duration) {
|
||||||
this.tweens.create(this.layer_universe.position).to({ x: dest_x, y: dest_y }, duration, easing).start();
|
this.animations.addAnimation(this.layer_universe, { x: dest_x, y: dest_y, scaleX: scale, scaleY: scale }, duration, easing);
|
||||||
this.tweens.create(this.layer_universe.scale).to({ x: scale, y: scale }, duration, easing).start();
|
|
||||||
} else {
|
} else {
|
||||||
this.layer_universe.position.set(dest_x, dest_y);
|
this.layer_universe.setPosition(dest_x, dest_y);
|
||||||
this.layer_universe.scale.set(scale);
|
this.layer_universe.setScale(scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +252,7 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
setLinksAlpha(alpha: number, duration = 500) {
|
setLinksAlpha(alpha: number, duration = 500) {
|
||||||
if (duration) {
|
if (duration) {
|
||||||
this.game.add.tween(this.starlinks_group).to({ alpha: alpha }, duration * Math.abs(this.starlinks_group.alpha - alpha)).start();
|
this.animations.addAnimation(this.starlinks_group, { alpha: alpha }, duration * Math.abs(this.starlinks_group.alpha - alpha));
|
||||||
} else {
|
} else {
|
||||||
this.starlinks_group.alpha = alpha;
|
this.starlinks_group.alpha = alpha;
|
||||||
}
|
}
|
||||||
|
@ -297,7 +292,7 @@ module TK.SpaceTac.UI {
|
||||||
let dest_star = dest_location.star;
|
let dest_star = dest_location.star;
|
||||||
this.player_fleet.moveToLocation(dest_location, 3, duration => {
|
this.player_fleet.moveToLocation(dest_location, 3, duration => {
|
||||||
this.timer.schedule(duration / 2, () => this.updateInfo(dest_star, false));
|
this.timer.schedule(duration / 2, () => this.updateInfo(dest_star, false));
|
||||||
this.setCamera(dest_star.x, dest_star.y, dest_star.radius * 2, duration, Phaser.Easing.Cubic.Out);
|
this.setCamera(dest_star.x, dest_star.y, dest_star.radius * 2, duration, "Cubic.Out");
|
||||||
}, () => {
|
}, () => {
|
||||||
this.setInteractionEnabled(true);
|
this.setInteractionEnabled(true);
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
@ -348,12 +343,12 @@ module TK.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
setInteractionEnabled(enabled: boolean) {
|
setInteractionEnabled(enabled: boolean) {
|
||||||
this.interactive = enabled && !this.session.spectator;
|
this.interactive = enabled && !this.session.spectator;
|
||||||
this.actions.setVisible(enabled && this.zoom == 2, 300);
|
this.animations.setVisible(this.actions.container, enabled && this.zoom == 2, 300);
|
||||||
this.missions.setVisible(enabled && this.zoom == 2, 300);
|
this.missions.setVisible(enabled && this.zoom == 2, 300);
|
||||||
this.animations.setVisible(this.zoom_in, enabled && this.zoom < 2, 300);
|
this.animations.setVisible(this.zoom_in, enabled && this.zoom < 2, 300);
|
||||||
this.animations.setVisible(this.zoom_out, enabled && this.zoom > 0, 300);
|
this.animations.setVisible(this.zoom_out, enabled && this.zoom > 0, 300);
|
||||||
this.animations.setVisible(this.button_options, enabled, 300);
|
this.animations.setVisible(this.button_options, enabled, 300);
|
||||||
this.animations.setVisible(this.character_sheet, enabled, 300);
|
//this.animations.setVisible(this.character_sheet, enabled, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,12 @@
|
||||||
/// <reference path="MainMenu.ts" />
|
/// <reference path="MainMenu.ts" />
|
||||||
|
|
||||||
module TK.SpaceTac.UI.Specs {
|
module TK.SpaceTac.UI.Specs {
|
||||||
testing("LoadDialog", test => {
|
testing("InputInviteCode", test => {
|
||||||
let testgame = setupEmptyView(test);
|
let testgame = setupEmptyView(test);
|
||||||
|
|
||||||
test.acase("joins remote sessions as spectator", async check => {
|
test.acase("joins remote sessions as spectator", async check => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let view = <MainMenu>testgame.ui.state.getCurrentState();
|
let view = testgame.view;
|
||||||
|
|
||||||
let session = new GameSession();
|
let session = new GameSession();
|
||||||
check.equals(session.primary, true);
|
check.equals(session.primary, true);
|
||||||
check.equals(session.spectator, false);
|
check.equals(session.spectator, false);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue