diff --git a/TODO b/TODO
index 0106a4c..089ee2f 100644
--- a/TODO
+++ b/TODO
@@ -31,6 +31,7 @@
* Find incentives to move from starting position
* Outcome: disable the loot button if there is no loot
* Ensure that tweens and particle emitters get destroyed once animation is done (or view changes)
+* UI: add standard confirm dialog
* Controls: do not focus on ship while targetting for area effects (dissociate hover and target)
* Controls: fix hover being stuck when the cursor exits the window, or the item moves or is hidden
* Drones: add hull points and take area damage
diff --git a/graphics/ui/battle.svg b/graphics/ui/battle.svg
index 37badfd..4c89536 100644
--- a/graphics/ui/battle.svg
+++ b/graphics/ui/battle.svg
@@ -18,10 +18,11 @@
version="1.1"
inkscape:version="0.92.1 r15371"
sodipodi:docname="battle.svg"
- inkscape:export-filename="/tmp/whole.png"
+ inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/actionbar/background.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
- style="enable-background:new">
+ style="enable-background:new"
+ enable-background="new">
+
+
+
@@ -2060,7 +2071,6 @@
height="131.76981"
x="0"
y="-27.637838"
- transform="translate(0,27.637839)"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/actionbar.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
@@ -2069,7 +2079,8 @@
inkscape:tile-w="1920"
inkscape:tile-h="131.76981"
inkscape:tile-x0="0"
- inkscape:tile-y0="6.3635254e-07" />
+ inkscape:tile-y0="6.3635254e-07"
+ transform="translate(0,27.637839)" />
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
2
+ style="fill:#aaaaaa;fill-opacity:1;stroke-width:0.9375px">2
image/svg+xml
-
+
@@ -1346,6 +1346,118 @@
id="layer2"
inkscape:label="Buttons"
style="display:inline">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/graphics/ui/template.svg b/graphics/ui/template.svg
new file mode 100644
index 0000000..2743328
--- /dev/null
+++ b/graphics/ui/template.svg
@@ -0,0 +1,54 @@
+
+
+
+
diff --git a/out/assets/images/battle/action-active.png b/out/assets/images/battle/action-active.png
deleted file mode 100644
index 3cdcc2d..0000000
Binary files a/out/assets/images/battle/action-active.png and /dev/null differ
diff --git a/out/assets/images/battle/action-cooldown.png b/out/assets/images/battle/action-cooldown.png
deleted file mode 100644
index 7c72147..0000000
Binary files a/out/assets/images/battle/action-cooldown.png and /dev/null differ
diff --git a/out/assets/images/battle/action-inactive.png b/out/assets/images/battle/action-inactive.png
deleted file mode 100644
index 7d69743..0000000
Binary files a/out/assets/images/battle/action-inactive.png and /dev/null differ
diff --git a/out/assets/images/battle/action-selected.png b/out/assets/images/battle/action-selected.png
deleted file mode 100644
index 1cc233a..0000000
Binary files a/out/assets/images/battle/action-selected.png and /dev/null differ
diff --git a/out/assets/images/battle/actionbar.png b/out/assets/images/battle/actionbar.png
deleted file mode 100644
index dd9139e..0000000
Binary files a/out/assets/images/battle/actionbar.png and /dev/null differ
diff --git a/out/assets/images/battle/actions/endturn.png b/out/assets/images/battle/actionbar/action-endturn.png
similarity index 100%
rename from out/assets/images/battle/actions/endturn.png
rename to out/assets/images/battle/actionbar/action-endturn.png
diff --git a/out/assets/images/battle/actions/move.png b/out/assets/images/battle/actionbar/action-move.png
similarity index 100%
rename from out/assets/images/battle/actions/move.png
rename to out/assets/images/battle/actionbar/action-move.png
diff --git a/out/assets/images/battle/actionbar/background.png b/out/assets/images/battle/actionbar/background.png
new file mode 100644
index 0000000..3212f3a
Binary files /dev/null and b/out/assets/images/battle/actionbar/background.png differ
diff --git a/out/assets/images/battle/actionbar/button-menu.png b/out/assets/images/battle/actionbar/button-menu.png
new file mode 100644
index 0000000..7c74771
Binary files /dev/null and b/out/assets/images/battle/actionbar/button-menu.png differ
diff --git a/out/assets/images/battle/actionbar/icon.png b/out/assets/images/battle/actionbar/icon.png
new file mode 100644
index 0000000..03c9655
Binary files /dev/null and b/out/assets/images/battle/actionbar/icon.png differ
diff --git a/out/assets/images/battle/actionbar/power.png b/out/assets/images/battle/actionbar/power.png
new file mode 100644
index 0000000..25a1b05
Binary files /dev/null and b/out/assets/images/battle/actionbar/power.png differ
diff --git a/out/assets/images/battle/power-available.png b/out/assets/images/battle/power-available.png
deleted file mode 100644
index c42665d..0000000
Binary files a/out/assets/images/battle/power-available.png and /dev/null differ
diff --git a/out/assets/images/battle/power-used.png b/out/assets/images/battle/power-used.png
deleted file mode 100644
index 37f0d07..0000000
Binary files a/out/assets/images/battle/power-used.png and /dev/null differ
diff --git a/out/assets/images/battle/power-using.png b/out/assets/images/battle/power-using.png
deleted file mode 100644
index a4695ba..0000000
Binary files a/out/assets/images/battle/power-using.png and /dev/null differ
diff --git a/out/assets/images/common/dialog-close.png b/out/assets/images/common/dialog-close.png
new file mode 100644
index 0000000..65da8da
Binary files /dev/null and b/out/assets/images/common/dialog-close.png differ
diff --git a/out/assets/images/map/button-zoom.png b/out/assets/images/map/button-zoom.png
deleted file mode 100644
index 7006dc1..0000000
Binary files a/out/assets/images/map/button-zoom.png and /dev/null differ
diff --git a/out/assets/images/map/buttons.png b/out/assets/images/map/buttons.png
new file mode 100644
index 0000000..5a2a9a7
Binary files /dev/null and b/out/assets/images/map/buttons.png differ
diff --git a/out/assets/images/menu/button-fullscreen.png b/out/assets/images/menu/button-fullscreen.png
deleted file mode 100644
index f34d054..0000000
Binary files a/out/assets/images/menu/button-fullscreen.png and /dev/null differ
diff --git a/out/assets/images/options/background.png b/out/assets/images/options/background.png
new file mode 100644
index 0000000..96b721d
Binary files /dev/null and b/out/assets/images/options/background.png differ
diff --git a/out/assets/images/options/button.png b/out/assets/images/options/button.png
new file mode 100644
index 0000000..4d0c902
Binary files /dev/null and b/out/assets/images/options/button.png differ
diff --git a/out/assets/images/options/options.png b/out/assets/images/options/options.png
new file mode 100644
index 0000000..f21d954
Binary files /dev/null and b/out/assets/images/options/options.png differ
diff --git a/out/assets/images/options/toggle.png b/out/assets/images/options/toggle.png
new file mode 100644
index 0000000..48a7d00
Binary files /dev/null and b/out/assets/images/options/toggle.png differ
diff --git a/src/MainUI.ts b/src/MainUI.ts
index a4eb7c6..493513e 100644
--- a/src/MainUI.ts
+++ b/src/MainUI.ts
@@ -13,6 +13,9 @@ module TS.SpaceTac {
// Audio manager
audio: UI.Audio;
+ // Game options
+ options: UI.GameOptions;
+
// Storage used
storage: Storage;
@@ -25,7 +28,6 @@ module TS.SpaceTac {
this.headless = headless;
this.audio = new UI.Audio(this);
-
this.storage = localStorage;
this.session = new GameSession();
@@ -53,6 +55,9 @@ module TS.SpaceTac {
if (!this.headless) {
this.plugins.add((Phaser.Plugin).SceneGraph);
}
+
+ this.audio = new UI.Audio(this);
+ this.options = new UI.GameOptions(this);
}
/**
@@ -133,5 +138,27 @@ module TS.SpaceTac {
return null;
}
}
+
+ /**
+ * Check if the game is currently fullscreen
+ */
+ isFullscreen(): boolean {
+ return this.scale.isFullScreen;
+ }
+
+ /**
+ * Toggle fullscreen mode.
+ *
+ * Returns true if the result is fullscreen
+ */
+ toggleFullscreen(active: boolean | null = null): boolean {
+ if (active === false || (active !== true && this.isFullscreen())) {
+ this.scale.stopFullScreen();
+ return false;
+ } else {
+ this.scale.startFullScreen(true);
+ return true;
+ }
+ }
}
}
diff --git a/src/ui/BaseView.ts b/src/ui/BaseView.ts
index 4faf280..d795a09 100644
--- a/src/ui/BaseView.ts
+++ b/src/ui/BaseView.ts
@@ -4,26 +4,29 @@ module TS.SpaceTac.UI {
*/
export class BaseView extends Phaser.State {
// Link to the root UI
- gameui: MainUI;
+ gameui: MainUI
// Message notifications
- messages: Messages;
+ messages: Messages
// Input and key bindings
- inputs: InputManager;
+ inputs: InputManager
// Animations
- animations: Animations;
+ animations: Animations
// Timing
- timer: Timer;
+ timer: Timer
// Tooltip
- tooltip_layer: Phaser.Group;
- tooltip: Tooltip;
+ tooltip_layer: Phaser.Group
+ tooltip: Tooltip
// Layers
- layers: Phaser.Group;
+ layers: Phaser.Group
+
+ // Modal dialogs
+ dialogs_layer: Phaser.Group
// Get the size of display
getWidth(): number {
@@ -56,6 +59,7 @@ module TS.SpaceTac.UI {
// Layers
this.layers = this.add.group(undefined, "View layers");
+ this.dialogs_layer = this.add.group(undefined, "Dialogs layer");
this.tooltip_layer = this.add.group(undefined, "Tooltip layer");
this.tooltip = new Tooltip(this);
this.messages = new Messages(this);
@@ -80,6 +84,14 @@ module TS.SpaceTac.UI {
this.timer.cancelAll(true);
}
+ get audio() {
+ return this.gameui.audio;
+ }
+
+ get options() {
+ return this.gameui.options;
+ }
+
/**
* Go back to the router state
*/
@@ -123,20 +135,10 @@ module TS.SpaceTac.UI {
}
/**
- * Toggle fullscreen mode.
- *
- * Returns true if the result is fullscreen
+ * Open options dialog
*/
- toggleFullscreen(active: boolean | null = null): boolean {
- if (active === false || (active !== true && this.game.scale.isFullScreen)) {
- this.scale.stopFullScreen();
- this.setStorage("fullscreen", "false");
- return false;
- } else {
- this.scale.startFullScreen(true);
- this.setStorage("fullscreen", "true");
- return true;
- }
+ showOptions(): void {
+ let dialog = new OptionsDialog(this);
}
/**
diff --git a/src/ui/Preload.ts b/src/ui/Preload.ts
index 2a8434d..2848564 100644
--- a/src/ui/Preload.ts
+++ b/src/ui/Preload.ts
@@ -11,11 +11,6 @@ module TS.SpaceTac.UI {
this.load.setPreloadSprite(this.preloadBar);
// Load images
- this.loadImage("menu/title.png");
- this.loadImage("menu/button.png");
- this.loadImage("menu/button-hover.png");
- this.loadImage("menu/button-fullscreen.png");
- this.loadImage("menu/load-bg.png");
this.loadSheet("common/particles.png", 32);
this.loadImage("common/transparent.png");
this.loadImage("common/debug.png");
@@ -23,20 +18,22 @@ module TS.SpaceTac.UI {
this.loadImage("common/arrow.png");
this.loadImage("common/button-ok.png");
this.loadImage("common/button-cancel.png");
- this.loadImage("battle/shiplist/background.png");
- this.loadImage("battle/shiplist/item-background.png");
- this.loadImage("battle/shiplist/damage.png");
- this.loadImage("battle/shiplist/hover.png");
- this.loadImage("battle/shiplist/info-button.png");
+ this.loadSheet("common/dialog-close.png", 92, 82);
+ this.loadImage("menu/title.png");
+ this.loadImage("menu/button.png");
+ this.loadImage("menu/button-hover.png");
+ this.loadImage("menu/load-bg.png");
+ this.loadImage("options/background.png");
+ this.loadSheet("options/button.png", 497, 134);
+ this.loadSheet("options/options.png", 128, 128);
+ this.loadSheet("options/toggle.png", 149, 149);
this.loadImage("battle/background.jpg");
- this.loadImage("battle/actionbar.png");
- this.loadImage("battle/action-inactive.png");
- this.loadImage("battle/action-active.png");
- this.loadImage("battle/action-selected.png");
- this.loadImage("battle/action-cooldown.png");
- this.loadImage("battle/power-available.png");
- this.loadImage("battle/power-using.png");
- this.loadImage("battle/power-used.png");
+ this.loadImage("battle/actionbar/background.png");
+ this.loadSheet("battle/actionbar/icon.png", 88, 88);
+ this.loadSheet("battle/actionbar/power.png", 58, 21);
+ this.loadImage("battle/actionbar/action-move.png");
+ this.loadImage("battle/actionbar/action-endturn.png");
+ this.loadSheet("battle/actionbar/button-menu.png", 79, 132);
this.loadImage("battle/arena/background.png");
this.loadImage("battle/arena/ap-indicator.png");
this.loadImage("battle/arena/ship-normal-enemy.png");
@@ -53,8 +50,11 @@ module TS.SpaceTac.UI {
this.loadImage("battle/arena/stasis.png");
this.loadImage("battle/arena/target.png");
this.loadImage("battle/arena/blast.png");
- this.loadImage("battle/actions/move.png");
- this.loadImage("battle/actions/endturn.png");
+ this.loadImage("battle/shiplist/background.png");
+ this.loadImage("battle/shiplist/item-background.png");
+ this.loadImage("battle/shiplist/damage.png");
+ this.loadImage("battle/shiplist/hover.png");
+ this.loadImage("battle/shiplist/info-button.png");
this.loadImage("battle/weapon/default.png");
this.loadImage("battle/weapon/bullets.png");
this.loadImage("battle/weapon/hot.png");
@@ -74,7 +74,7 @@ module TS.SpaceTac.UI {
this.loadSheet("map/action.png", 323, 192);
this.loadImage("map/orbit.png");
this.loadImage("map/boundaries.png");
- this.loadSheet("map/button-zoom.png", 115, 191);
+ this.loadSheet("map/buttons.png", 115, 191);
this.loadImage("map/location-star.png");
this.loadImage("map/location-planet.png");
this.loadImage("map/location-warp.png");
diff --git a/src/ui/TestGame.ts b/src/ui/TestGame.ts
index ba83da2..79a6970 100644
--- a/src/ui/TestGame.ts
+++ b/src/ui/TestGame.ts
@@ -114,4 +114,18 @@ module TS.SpaceTac.UI.Specs {
let tnode = node;
expect(tnode.text).toEqual(content);
}
+
+ /**
+ * Check that a layer contains the given component at a given index
+ */
+ export function checkComponentInLayer(layer: Phaser.Group, index: number, component: UIComponent) {
+ if (index >= layer.children.length) {
+ fail(`Not enough children in group ${layer.name} for ${component} at index ${index}`);
+ } else {
+ let child = layer.children[index];
+ if (child !== (component).container) {
+ fail(`${component} is not at index ${index} in ${layer.name}`);
+ }
+ }
+ }
}
diff --git a/src/ui/battle/ActionBar.spec.ts b/src/ui/battle/ActionBar.spec.ts
index a36234f..c44a707 100644
--- a/src/ui/battle/ActionBar.spec.ts
+++ b/src/ui/battle/ActionBar.spec.ts
@@ -105,11 +105,11 @@ module TS.SpaceTac.UI.Specs {
bar.power.children.forEach((child, idx) => {
let img = child;
if (idx < available) {
- expect(img.name).toEqual("battle-power-available");
+ expect(img.data.frame).toEqual(0);
} else if (idx < available + using) {
- expect(img.name).toEqual("battle-power-using");
+ expect(img.data.frame).toEqual(2);
} else {
- expect(img.name).toEqual("battle-power-used");
+ expect(img.data.frame).toEqual(1);
}
});
}
diff --git a/src/ui/battle/ActionBar.ts b/src/ui/battle/ActionBar.ts
index 1887e51..a3a4951 100644
--- a/src/ui/battle/ActionBar.ts
+++ b/src/ui/battle/ActionBar.ts
@@ -33,21 +33,25 @@ module TS.SpaceTac.UI {
battleview.layer_borders.add(this);
// Background
- this.addChild(new Phaser.Image(this.game, 0, 0, "battle-actionbar", 0));
+ this.add(new Phaser.Image(this.game, 0, 0, "battle-actionbar-background", 0));
// Power bar
this.power = this.game.add.group();
- this.addChild(this.power);
+ this.add(this.power);
// Group for actions
this.actions = new Phaser.Group(this.game);
- this.addChild(this.actions);
+ this.add(this.actions);
// Waiting icon
this.icon_waiting = new Phaser.Image(this.game, this.width / 2, 50, "common-waiting", 0);
this.icon_waiting.anchor.set(0.5, 0.5);
this.icon_waiting.scale.set(0.5, 0.5);
- this.addChild(this.icon_waiting);
+ this.add(this.icon_waiting);
+
+ // Options button
+ let button = battleview.add.button(1841, 0, "battle-actionbar-button-menu", () => battleview.showOptions(), null, 1, 0, 0, 1, this);
+ battleview.tooltip.bindStaticText(button, "Game options");
// Key bindings
battleview.inputs.bind("Escape", "Cancel action", () => this.actionEnded());
@@ -133,7 +137,7 @@ module TS.SpaceTac.UI {
// Add an action icon
addAction(ship: Ship, action: BaseAction): ActionIcon {
- var icon = new ActionIcon(this, 192 + this.action_icons.length * 88, 8, ship, action, this.action_icons.length);
+ var icon = new ActionIcon(this, 170 + this.action_icons.length * 88, 8, ship, action, this.action_icons.length);
this.action_icons.push(icon);
return icon;
@@ -150,23 +154,23 @@ module TS.SpaceTac.UI {
range(current_power - power_capacity).forEach(i => this.power.removeChildAt(current_power - 1 - i));
//this.power.removeChildren(ship_power, current_power); // TODO bugged in phaser 2.6
} else if (power_capacity > current_power) {
- range(power_capacity - current_power).forEach(i => this.game.add.image(190 + (current_power + i) * 56, 104, "battle-power-used", 0, this.power));
+ range(power_capacity - current_power).forEach(i => this.game.add.image(192 + (current_power + i) * 56, 104, "battle-actionbar-power", 0, this.power));
}
let power_value = this.ship_power_value;
let remaining_power = power_value - selected_action;
this.power.children.forEach((obj, idx) => {
let img = obj;
- let key: string;
+ let frame: number;
if (idx < remaining_power) {
- key = "battle-power-available";
+ frame = 0;
} else if (idx < power_value) {
- key = "battle-power-using";
+ frame = 2;
} else {
- key = "battle-power-used"
+ frame = 1;
}
- img.name = key;
- img.loadTexture(key);
+ img.data.frame = frame;
+ img.frame = frame;
});
}
diff --git a/src/ui/battle/ActionIcon.ts b/src/ui/battle/ActionIcon.ts
index eb442a1..a00b1cf 100644
--- a/src/ui/battle/ActionIcon.ts
+++ b/src/ui/battle/ActionIcon.ts
@@ -40,7 +40,7 @@ module TS.SpaceTac.UI {
// Create an icon for a single ship action
constructor(bar: ActionBar, x: number, y: number, ship: Ship, action: BaseAction, position: number) {
- super(bar.game, x, y, "battle-action-inactive");
+ super(bar.game, x, y, "battle-actionbar-icon");
this.bar = bar;
this.battleview = bar.battleview;
@@ -51,27 +51,27 @@ module TS.SpaceTac.UI {
// Active layer
this.active = false;
- this.layer_active = new Phaser.Image(this.game, this.width / 2, this.height / 2, "battle-action-active", 0);
+ this.layer_active = new Phaser.Image(this.game, this.width / 2, this.height / 2, "battle-actionbar-icon", 1);
this.layer_active.anchor.set(0.5, 0.5);
this.layer_active.visible = false;
this.addChild(this.layer_active);
// Selected layer
this.selected = false;
- this.layer_selected = new Phaser.Image(this.game, this.width / 2, this.height / 2, "battle-action-selected", 0);
+ this.layer_selected = new Phaser.Image(this.game, this.width / 2, this.height / 2, "battle-actionbar-icon", 2);
this.layer_selected.anchor.set(0.5, 0.5);
this.layer_selected.visible = false;
this.addChild(this.layer_selected);
// Icon layer
- let icon = this.battleview.getImage(`battle-actions-${action.code}`, `equipment-${action.equipment ? action.equipment.code : "---"}`);
+ let icon = this.battleview.getImage(`battle-actionbar-action-${action.code}`, `equipment-${action.equipment ? action.equipment.code : "---"}`);
this.layer_icon = new Phaser.Image(this.game, this.width / 2, this.height / 2, icon, 0);
this.layer_icon.anchor.set(0.5, 0.5);
this.layer_icon.scale.set(0.25, 0.25);
this.addChild(this.layer_icon);
// Cooldown layer
- this.cooldown = new Phaser.Image(this.game, this.width / 2, this.height / 2, "battle-action-cooldown");
+ this.cooldown = new Phaser.Image(this.game, this.width / 2, this.height / 2, "battle-actionbar-icon", 3);
this.cooldown.anchor.set(0.5, 0.5);
this.cooldown_count = new Phaser.Text(this.game, 0, 0, "", { align: "center", font: "36pt Arial", fill: "#aaaaaa" });
this.cooldown_count.anchor.set(0.5, 0.5);
diff --git a/src/ui/battle/ActionTooltip.ts b/src/ui/battle/ActionTooltip.ts
index 778deb3..b0b3cd7 100644
--- a/src/ui/battle/ActionTooltip.ts
+++ b/src/ui/battle/ActionTooltip.ts
@@ -7,7 +7,7 @@ module TS.SpaceTac.UI {
* Fill the tooltip
*/
static fill(filler: TooltipFiller, ship: Ship, action: BaseAction, position: number) {
- let icon = filler.view.getImage(`equipment-${action.equipment ? action.equipment.code : "---"}`, `battle-actions-${action.code}`);
+ let icon = filler.view.getImage(`equipment-${action.equipment ? action.equipment.code : "---"}`, `battle-actionbar-action-${action.code}`);
filler.addImage(0, 0, icon, 0.5);
filler.addText(150, 0, action.equipment ? action.equipment.name : action.name, "#ffffff", 24);
diff --git a/src/ui/common/Audio.ts b/src/ui/common/Audio.ts
index c2813b2..573ba39 100644
--- a/src/ui/common/Audio.ts
+++ b/src/ui/common/Audio.ts
@@ -1,14 +1,13 @@
module TS.SpaceTac.UI {
// Utility functions for sounds
export class Audio {
-
- private game: MainUI;
-
- private music: Phaser.Sound | null;
+ private game: MainUI
+ private music: Phaser.Sound | null = null
+ private music_volume = 1
+ private music_playing_volume = 1
constructor(game: MainUI) {
this.game = game;
- this.music = null;
}
// Check if the sound system is up and running
@@ -31,7 +30,8 @@ module TS.SpaceTac.UI {
this.stopMusic();
}
if (!this.music) {
- this.music = this.game.sound.play(key, volume, true);
+ this.music_playing_volume = volume;
+ this.music = this.game.sound.play(key, volume * this.music_volume, true);
}
}
}
@@ -46,13 +46,41 @@ module TS.SpaceTac.UI {
}
}
- // Toggle the mute status of the sound system
- toggleMute(): void {
+ /**
+ * Get the main volume (0-1)
+ */
+ getMainVolume(): number {
if (this.isActive()) {
- if (this.game.sound.volume > 0) {
- this.game.sound.volume = 0;
- } else {
- this.game.sound.volume = 1;
+ return this.game.sound.volume;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Set the main volume (0-1)
+ */
+ setMainVolume(value: number) {
+ if (this.isActive()) {
+ this.game.sound.volume = clamp(value, 0, 1);
+ }
+ }
+
+ /**
+ * Get the music volume (0-1)
+ */
+ getMusicVolume(): number {
+ return this.music_volume;
+ }
+
+ /**
+ * Set the music volume (0-1)
+ */
+ setMusicVolume(value: number) {
+ this.music_volume = value;
+ if (this.isActive()) {
+ if (this.music) {
+ this.music.volume = value * this.music_playing_volume;
}
}
}
diff --git a/src/ui/common/InputManager.ts b/src/ui/common/InputManager.ts
index 1ce69f8..1bfd5c6 100644
--- a/src/ui/common/InputManager.ts
+++ b/src/ui/common/InputManager.ts
@@ -36,10 +36,10 @@ module TS.SpaceTac.UI {
this.game.state.start("router");
});
this.bind("m", "Toggle sound", () => {
- this.game.audio.toggleMute();
+ this.game.options.setNumberValue("mainvolume", this.game.options.getNumberValue("mainvolume") > 0 ? 0 : 1);
});
this.bind("f", "Toggle fullscreen", () => {
- view.toggleFullscreen();
+ this.game.options.setBooleanValue("fullscreen", !this.game.options.getBooleanValue("fullscreen"));
});
this.bind("+", "", () => {
if (this.cheats_allowed) {
diff --git a/src/ui/common/UIComponent.ts b/src/ui/common/UIComponent.ts
index e75a8c6..28d5068 100644
--- a/src/ui/common/UIComponent.ts
+++ b/src/ui/common/UIComponent.ts
@@ -1,6 +1,32 @@
module TS.SpaceTac.UI {
export type UIInternalComponent = Phaser.Group | Phaser.Image | Phaser.Button | Phaser.Sprite;
+ export type UIImageInfo = string | { key: string, frame?: number, frame1?: number, frame2?: number };
+ export type UITextInfo = { content: string, color: string, size: number, bold?: boolean };
+
+ function imageFromInfo(game: Phaser.Game, info: UIImageInfo): Phaser.Image {
+ if (typeof info === "string") {
+ 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 Arial`, 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, info);
+ } else {
+ return imageFromInfo(game, info);
+ }
+ }
+
/**
* Base class for UI components
*/
@@ -39,6 +65,14 @@ module TS.SpaceTac.UI {
return this.view.gameui;
}
+ jasmineToString(): string {
+ return this.toString();
+ }
+
+ toString(): string {
+ return `<${classname(this)}>`;
+ }
+
/**
* Move the a parent's layer
*/
@@ -46,6 +80,13 @@ module TS.SpaceTac.UI {
layer.add(this.container);
}
+ /**
+ * Destroy the component
+ */
+ destroy(children = true) {
+ this.container.destroy(children);
+ }
+
/**
* Create the internal phaser node
*/
@@ -143,7 +184,7 @@ module TS.SpaceTac.UI {
/**
* Add a button in the component, positioning its center.
*/
- addButton(x: number, y: number, on_click: Function, background: string, frame_normal = 0, frame_hover = frame_normal, tooltip = "", angle = 0) {
+ addButton(x: number, y: number, on_click: Function, background: string, frame_normal = 0, frame_hover = 1, tooltip = "", angle = 0) {
let button = new Phaser.Button(this.view.game, x, y, background, on_click, undefined, frame_hover, frame_normal);
button.anchor.set(0.5, 0.5);
button.angle = angle;
@@ -179,6 +220,40 @@ module TS.SpaceTac.UI {
this.addInternalChild(image);
}
+ /**
+ * Add a 2-states toggle button.
+ *
+ * *background* should have three frames (toggled, untoggled and hovered).
+ *
+ * Returns a function to force the state of the button.
+ */
+ addToggleButton(x: number, y: number, background: UIImageInfo, content: UIImageInfo | UITextInfo, on_change: (toggled: boolean) => void): (toggled: boolean) => void {
+ let toggled = false;
+ let toggle = (state: boolean, broadcast = false) => {
+ toggled = state;
+ if (typeof background !== "string") {
+ image.frame = (toggled ? background.frame : background.frame1) || background.frame || 0;
+ }
+ contentobj.alpha = toggled ? 1 : 0.5;
+ if (broadcast) {
+ on_change(toggled);
+ }
+ };
+
+ let button = new Phaser.Button(this.container.game, x, y, "common-transparent", () => toggle(!toggled, true));
+
+ let image = imageFromInfo(this.game, background);
+ let contentobj = autoFromInfo(this.game, content);
+
+ button.addChild(image);
+ button.addChild(contentobj);
+ this.addInternalChild(button);
+
+ toggle(toggled);
+
+ return toggle;
+ }
+
/**
* Set the keyboard focus on this component.
*/
diff --git a/src/ui/common/UIDialog.spec.ts b/src/ui/common/UIDialog.spec.ts
new file mode 100644
index 0000000..ee483a8
--- /dev/null
+++ b/src/ui/common/UIDialog.spec.ts
@@ -0,0 +1,31 @@
+module TS.SpaceTac.UI.Specs {
+ describe("UIDialog", () => {
+ let testgame = setupEmptyView();
+
+ it("sets up an overlay", function () {
+ let view = testgame.baseview;
+ expect(view.dialogs_layer.children.length).toBe(0);
+
+ let dialog1 = new UIDialog(view, 10, 10, "fake");
+ expect(view.dialogs_layer.children.length).toBe(2);
+ expect(view.dialogs_layer.children[0] instanceof Phaser.Button).toBe(true);
+ checkComponentInLayer(view.dialogs_layer, 1, dialog1);
+
+ let dialog2 = new UIDialog(view, 10, 10, "fake");
+ expect(view.dialogs_layer.children.length).toBe(3);
+ expect(view.dialogs_layer.children[0] instanceof Phaser.Button).toBe(true);
+ checkComponentInLayer(view.dialogs_layer, 1, dialog1);
+ checkComponentInLayer(view.dialogs_layer, 2, dialog2);
+
+ dialog1.close();
+
+ expect(view.dialogs_layer.children.length).toBe(2);
+ expect(view.dialogs_layer.children[0] instanceof Phaser.Button).toBe(true);
+ checkComponentInLayer(view.dialogs_layer, 1, dialog2);
+
+ dialog2.close();
+
+ expect(view.dialogs_layer.children.length).toBe(0);
+ });
+ });
+}
diff --git a/src/ui/common/UIDialog.ts b/src/ui/common/UIDialog.ts
new file mode 100644
index 0000000..b0dfc0e
--- /dev/null
+++ b/src/ui/common/UIDialog.ts
@@ -0,0 +1,48 @@
+///
+
+module TS.SpaceTac.UI {
+ /**
+ * Base class for modal dialogs
+ *
+ * When a modal dialog opens, an overlay is displayed behind it to prevent clicking through it
+ */
+ export class UIDialog extends UIComponent {
+ constructor(parent: BaseView, width: number, height: number, background: string) {
+ super(parent, width, height, background);
+
+ if (parent.dialogs_layer.children.length == 0) {
+ this.addOverlay(parent.dialogs_layer);
+ }
+
+ this.moveToLayer(parent.dialogs_layer);
+ this.setPositionInsideParent(0.5, 0.5);
+ }
+
+ /**
+ * Add a control-capturing overlay
+ */
+ addOverlay(layer: Phaser.Group): void {
+ let overlay = layer.game.add.button(0, 0, "common-transparent", () => null);
+ overlay.scale.set(this.view.getWidth() / overlay.width, this.view.getHeight() / overlay.height);
+ layer.add(overlay);
+ }
+
+ /**
+ * Add a close button
+ */
+ addCloseButton(key: string, x: number, y: number, frame = 0, frame_hover = 1): void {
+ this.addButton(x, y, () => this.close(), key, frame, frame_hover, "Close this dialog");
+ }
+
+ /**
+ * Close the dialog, removing the overlay if needed
+ */
+ close() {
+ this.destroy();
+
+ if (this.view.dialogs_layer.children.length == 1) {
+ this.view.dialogs_layer.removeAll(true);
+ }
+ }
+ }
+}
diff --git a/src/ui/map/UniverseMapView.ts b/src/ui/map/UniverseMapView.ts
index 970165d..a67caa2 100644
--- a/src/ui/map/UniverseMapView.ts
+++ b/src/ui/map/UniverseMapView.ts
@@ -88,14 +88,19 @@ module TS.SpaceTac.UI {
this.actions.setPosition(30, 30);
this.actions.moveToLayer(this.layer_overlay);
- this.zoom_in = new Phaser.Button(this.game, 1520, 100, "map-button-zoom", () => this.setZoom(this.zoom + 1), undefined, 1, 0);
+ this.zoom_in = new Phaser.Button(this.game, 1540, 172, "map-buttons", () => this.setZoom(this.zoom + 1), undefined, 3, 0);
this.zoom_in.anchor.set(0.5, 0.5);
this.layer_overlay.add(this.zoom_in);
this.tooltip.bindStaticText(this.zoom_in, "Zoom in");
- this.zoom_out = new Phaser.Button(this.game, 1520, 980, "map-button-zoom", () => this.setZoom(this.zoom - 1), undefined, 3, 2);
+ this.zoom_out = new Phaser.Button(this.game, 1540, 958, "map-buttons", () => this.setZoom(this.zoom - 1), undefined, 4, 1);
this.zoom_out.anchor.set(0.5, 0.5);
this.layer_overlay.add(this.zoom_out);
this.tooltip.bindStaticText(this.zoom_out, "Zoom out");
+ let options = new Phaser.Button(this.game, 1436, 69, "map-buttons", () => this.showOptions(), undefined, 5, 2);
+ options.angle = -90;
+ options.anchor.set(0.5, 0.5);
+ this.layer_overlay.add(options);
+ this.tooltip.bindStaticText(options, "Game options");
this.character_sheet = new CharacterSheet(this, this.getWidth() - 307);
this.character_sheet.show(this.player.fleet.ships[0], false);
diff --git a/src/ui/menu/MainMenu.ts b/src/ui/menu/MainMenu.ts
index f48313e..5bbcdf7 100644
--- a/src/ui/menu/MainMenu.ts
+++ b/src/ui/menu/MainMenu.ts
@@ -36,7 +36,8 @@ module TS.SpaceTac.UI {
this.button_quick_battle = this.addButton(1606, 674, "Quick Battle", "Play a single generated battle", () => this.onQuickBattle());
// Fullscreen button
- let button = new Phaser.Button(this.game, 1815, 15, "menu-button-fullscreen", () => this.toggleFullscreen());
+ let button = new Phaser.Button(this.game, this.getWidth(), 0, "options-options", () => this.options.toggleBoolean("fullscreen"), null, 2, 2);
+ button.anchor.set(1, 0);
this.tooltip.bindStaticText(button, "Toggle full-screen");
this.layer_title.add(button);
@@ -65,13 +66,7 @@ module TS.SpaceTac.UI {
}
addButton(x: number, y: number, caption: string, tooltip: string, callback: Function): Phaser.Button {
- var button = this.add.button(x - 20, y + 20, "menu-button", () => {
- let fullscreen = this.getStorage("fullscreen");
- if (fullscreen == "true") {
- this.toggleFullscreen(true);
- }
- callback();
- });
+ var button = this.add.button(x - 20, y + 20, "menu-button", callback);
button.anchor.set(0.5, 0);
button.input.useHandCursor = true;
diff --git a/src/ui/options/GameOptions.ts b/src/ui/options/GameOptions.ts
new file mode 100644
index 0000000..eaea14c
--- /dev/null
+++ b/src/ui/options/GameOptions.ts
@@ -0,0 +1,98 @@
+module TS.SpaceTac.UI {
+ class GameOption {
+ code: string
+ current: T
+ getter: () => T
+ setter: (value: T) => any
+
+ constructor(code: string, getter: () => T, setter: (value: T) => any) {
+ this.code = code;
+ this.getter = getter;
+ this.setter = setter;
+ this.current = getter();
+ }
+
+ set(value: T) {
+ this.setter(value);
+ this.current = this.getter();
+ }
+ }
+
+ /**
+ * Object to store and maintain game-wide options
+ *
+ * Options are kept on the browser storage when possible
+ */
+ export class GameOptions {
+ booleans: { [code: string]: GameOption }
+ numbers: { [code: string]: GameOption }
+
+ constructor(parent: MainUI) {
+ this.booleans = {
+ fullscreen: new GameOption("fullscreen", () => parent.isFullscreen(), value => parent.toggleFullscreen(value)),
+ }
+ this.numbers = {
+ mainvolume: new GameOption("mainvolume", () => parent.audio.getMainVolume(), value => parent.audio.setMainVolume(value)),
+ musicvolume: new GameOption("musicvolume", () => parent.audio.getMusicVolume(), value => parent.audio.setMusicVolume(value)),
+ }
+ }
+
+ /**
+ * Get the current value of a boolean option
+ */
+ getBooleanValue(code: string, default_value = false): boolean {
+ let option = this.booleans[code];
+ if (option) {
+ return option.current;
+ } else {
+ return default_value;
+ }
+ }
+
+ /**
+ * Set the current value of a boolean option
+ */
+ setBooleanValue(code: string, value: boolean): boolean {
+ let option = this.booleans[code];
+ if (option) {
+ option.set(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Toggle a boolean value between true and false
+ */
+ toggleBoolean(code: string): boolean {
+ this.setBooleanValue(code, !this.getBooleanValue(code));
+ return this.getBooleanValue(code);
+ }
+
+ /**
+ * Get the current value of a number option
+ */
+ getNumberValue(code: string, default_value = 0): number {
+ let option = this.numbers[code];
+ if (option) {
+ return option.current;
+ } else {
+ return default_value;
+ }
+ }
+
+ /**
+ * Set the current value of a number option
+ */
+ setNumberValue(code: string, value: number): boolean {
+ let option = this.numbers[code];
+ if (option) {
+ option.set(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/ui/options/OptionsDialog.ts b/src/ui/options/OptionsDialog.ts
new file mode 100644
index 0000000..f02d53e
--- /dev/null
+++ b/src/ui/options/OptionsDialog.ts
@@ -0,0 +1,38 @@
+///
+
+module TS.SpaceTac.UI {
+ /**
+ * Dialog to display game options
+ */
+ export class OptionsDialog extends UIDialog {
+ constructor(parent: BaseView) {
+ super(parent, 1453, 1080, "options-background");
+
+ this.addCloseButton("common-dialog-close", 1304, 131, 0, 1);
+
+ let toggle = this.addToggleButton(415, 381,
+ { key: "options-toggle", frame: 0, frame1: 1, frame2: 2 },
+ { key: "options-options", frame: 0 },
+ toggled => parent.options.setNumberValue("mainvolume", toggled ? 1 : 0));
+ toggle(parent.options.getNumberValue("mainvolume") > 0);
+
+ toggle = this.addToggleButton(this.width / 2, 381,
+ { key: "options-toggle", frame: 0, frame1: 1, frame2: 2 },
+ { key: "options-options", frame: 1 },
+ toggled => parent.options.setNumberValue("musicvolume", toggled ? 1 : 0));
+ toggle(parent.options.getNumberValue("musicvolume") > 0);
+
+ toggle = this.addToggleButton(this.width - 415, 381,
+ { key: "options-toggle", frame: 0, frame1: 1, frame2: 2 },
+ { key: "options-options", frame: 2 },
+ toggled => parent.options.setBooleanValue("fullscreen", toggled));
+ toggle(parent.options.getBooleanValue("fullscreen"));
+
+ this.addButton(this.width / 2, 600, () => null, "options-button");
+ this.addText(this.width / 2, 600, "Invite a friend", "#5398e9", 36, true, true);
+
+ this.addButton(this.width / 2, 800, () => parent.gameui.quitGame(), "options-button");
+ this.addText(this.width / 2, 800, "Quit to menu", "#5398e9", 36, true, true);
+ }
+ }
+}