diff --git a/TODO b/TODO index ebd37ac..323b70b 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,4 @@ -* UI: Use a common component class, and a layer abstraction +* UI: use a common component class, and a layer abstraction * Character sheet: add initial character creation * Character sheet: disable interaction during battle (except for loot screen) * Character sheet: improve eye-catching for shop and loot section @@ -16,22 +16,20 @@ * Equipment: add area effects, without the need of activation/stickyness (eg. damage reduction, attribute modifier...) * Menu: end appear animation when a button is clicked * Menu: allow to delete cloud saves -* Arena: hide dead drones from tactical view * Arena: add power indicator in ship hover information * Arena: display effects description instead of attribute changes -* Arena: Organize objects and information in layers -* Arena: Add auto-move to attack -* Actions: Separate overheat and cooldown display -* Actions: Show power usage/recovery in power bar, on action hover -* Actions: Fix targetting not resetting when using keyboard shortcuts +* Arena: add auto-move to attack +* Actions: separate overheat and cooldown display +* Actions: show power usage/recovery in power bar, on action hover +* Actions: fix targetting not resetting when using keyboard shortcuts * Suspend AI operation when the game is paused (window not focused) * Add permanent effects to ship models to ease balancing * Find incentives to move from starting position -* Outcome: Add battle statistics and/or honors -* Outcome: Disable the loot button if there is no loot +* Outcome: add battle statistics and/or honors +* 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) -* 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 +* 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 * Drones: add hull points and take area damage * Drones: find a way to avoid arena cluttering * Add a battle log display diff --git a/src/ui/battle/Arena.ts b/src/ui/battle/Arena.ts index 3f7dec6..b3560bd 100644 --- a/src/ui/battle/Arena.ts +++ b/src/ui/battle/Arena.ts @@ -1,32 +1,43 @@ module TS.SpaceTac.UI { - // Graphical representation of a battle - // This is the area in the BattleView that will display ships with their real positions + /** + * Graphical representation of a battle + * + * This is the area in the BattleView that will display ships with their real positions + */ export class Arena extends Phaser.Group { // Link to battleview - battleview: BattleView; + battleview: BattleView - // Arena background - background: Phaser.Button; + // Boundaries of the arena + boundaries: IBounded = { x: 112, y: 132, width: 1808, height: 948 } // Hint for weapon or move range - range_hint: RangeHint; + range_hint: RangeHint + + // Input capture + private mouse_capture: Phaser.Button // Input callback to receive mouse move events - private input_callback: any; + private input_callback: any = null // List of ship sprites - private ship_sprites: ArenaShip[] = []; + private ship_sprites: ArenaShip[] = [] // List of drone sprites - private drone_sprites: ArenaDrone[] = []; + private drone_sprites: ArenaDrone[] = [] // Currently hovered ship - private hovered: ArenaShip | null; + private hovered: ArenaShip | null // Currently playing ship - private playing: ArenaShip | null; + private playing: ArenaShip | null // Layer for particles - layer_weapon_effects: Phaser.Group; + layer_garbage: Phaser.Group + layer_hints: Phaser.Group + layer_drones: Phaser.Group + layer_ships: Phaser.Group + layer_weapon_effects: Phaser.Group + layer_targetting: Phaser.Group // Create a graphical arena for ship sprites to fight in a 2D space constructor(battleview: BattleView) { @@ -37,14 +48,20 @@ module TS.SpaceTac.UI { this.hovered = null; this.range_hint = new RangeHint(this); - var offset_x = 112; - var offset_y = 132; + this.position.set(this.boundaries.x, this.boundaries.y); + + this.init(); + } + + /** + * Setup the mouse capture for targetting events + */ + setupMouseCapture() { + let battleview = this.battleview; var background = new Phaser.Button(battleview.game, 0, 0, "battle-arena-background"); - var expected_width = battleview.getWidth() - offset_x; - var expected_height = battleview.getHeight() - offset_y; - background.scale.set(expected_width / background.width, expected_height / background.height); - this.background = background; + background.scale.set(this.boundaries.width / background.width, this.boundaries.height / background.height); + this.mouse_capture = background; // Capture clicks on background background.onInputUp.add(() => { @@ -59,30 +76,43 @@ module TS.SpaceTac.UI { } }, null); - this.position.set(offset_x, offset_y); - - this.add(this.background); - this.add(this.range_hint); - - this.init(); + this.add(this.mouse_capture); } destroy() { - this.game.input.deleteMoveCallback(this.input_callback); + if (this.input_callback) { + this.game.input.deleteMoveCallback(this.input_callback); + this.input_callback = null; + } super.destroy(); } - // Initialize state (create sprites) + /** + * Initialize state (create sprites) + */ init(): void { - // Add ship sprites - this.battleview.battle.play_order.forEach(ship => { - var sprite = new ArenaShip(this, ship); - this.add(sprite); + this.setupMouseCapture(); + + this.layer_garbage = this.add(new Phaser.Group(this.game)); + this.layer_hints = this.add(new Phaser.Group(this.game)); + this.layer_drones = this.add(new Phaser.Group(this.game)); + this.layer_ships = this.add(new Phaser.Group(this.game)); + this.layer_weapon_effects = this.add(new Phaser.Group(this.game)); + this.layer_targetting = this.add(new Phaser.Group(this.game)); + + this.layer_hints.add(this.range_hint); + this.addShipSprites(); + } + + /** + * Add the sprites for all ships + */ + addShipSprites() { + iforeach(this.battleview.battle.iships(), ship => { + let sprite = new ArenaShip(this, ship); + this.layer_ships.add(sprite); this.ship_sprites.push(sprite); }); - - this.layer_weapon_effects = new Phaser.Group(this.game); - this.add(this.layer_weapon_effects); } // Get the current MainUI instance @@ -126,7 +156,7 @@ module TS.SpaceTac.UI { var arena_ship = this.findShipSprite(ship); if (arena_ship) { arena_ship.setHovered(true); - this.bringToTop(arena_ship); + this.layer_ships.bringToTop(arena_ship); } this.hovered = arena_ship; } else { @@ -144,7 +174,7 @@ module TS.SpaceTac.UI { if (ship) { var arena_ship = this.findShipSprite(ship); if (arena_ship) { - this.bringToTop(arena_ship); + this.layer_ships.bringToTop(arena_ship); arena_ship.setPlaying(true); } this.playing = arena_ship; @@ -167,7 +197,7 @@ module TS.SpaceTac.UI { if (!this.findDrone(drone)) { let sprite = new ArenaDrone(this.battleview, drone); let angle = Math.atan2(drone.y - drone.owner.arena_y, drone.x - drone.owner.arena_x); - this.add(sprite); + this.layer_drones.add(sprite); this.drone_sprites.push(sprite); if (animate) { @@ -197,6 +227,7 @@ module TS.SpaceTac.UI { if (sprite) { remove(this.drone_sprites, sprite); sprite.setDestroyed(); + this.layer_garbage.add(sprite); } else { console.error("Drone not found in arena for removal", drone); } @@ -214,6 +245,7 @@ module TS.SpaceTac.UI { */ setTacticalMode(active: boolean): void { this.ship_sprites.forEach(sprite => sprite.setHovered(active)); + this.battleview.animations.setVisible(this.layer_garbage, !active, 200); if (this.battleview.background) { this.battleview.animations.setVisible(this.battleview.background, !active, 200); } diff --git a/src/ui/battle/ArenaShip.ts b/src/ui/battle/ArenaShip.ts index 729c90f..28b10e1 100644 --- a/src/ui/battle/ArenaShip.ts +++ b/src/ui/battle/ArenaShip.ts @@ -2,6 +2,7 @@ module TS.SpaceTac.UI { // Ship sprite in the arena (BattleView) export class ArenaShip extends Phaser.Group { // Link to the view + arena: Arena battleview: BattleView // Link to displayed ship @@ -35,6 +36,7 @@ module TS.SpaceTac.UI { // Create a ship sprite usable in the Arena constructor(parent: Arena, ship: Ship) { super(parent.game); + this.arena = parent; this.battleview = parent.battleview; this.ship = ship; diff --git a/src/ui/battle/RangeHint.ts b/src/ui/battle/RangeHint.ts index 4fb6d22..b95277a 100644 --- a/src/ui/battle/RangeHint.ts +++ b/src/ui/battle/RangeHint.ts @@ -11,7 +11,7 @@ module TS.SpaceTac.UI { primary: Phaser.Circle | null; constructor(parent: Arena) { - super(parent.game, parent); + super(parent.game); this.parent = parent; diff --git a/src/ui/battle/Targetting.spec.ts b/src/ui/battle/Targetting.spec.ts index dd3cc43..2fcdccf 100644 --- a/src/ui/battle/Targetting.spec.ts +++ b/src/ui/battle/Targetting.spec.ts @@ -28,8 +28,6 @@ module TS.SpaceTac.UI.Specs { let source = new Phaser.Group(battleview.game, battleview.arena); source.position.set(0, 0); - let init_children = battleview.arena.children.length; - let targetting = new Targetting(battleview); targetting.setSource(source); @@ -38,12 +36,12 @@ module TS.SpaceTac.UI.Specs { targetting.updateApIndicators(); expect(targetting.ap_indicators.length).toBe(0); - expect(battleview.arena.children.length).toBe(init_children + 3); + expect(battleview.arena.layer_targetting.children.length).toBe(3); targetting.setApIndicatorsInterval(Math.sqrt(5) * 20); expect(targetting.ap_indicators.length).toBe(5); - expect(battleview.arena.children.length).toBe(init_children + 3 + 5); + expect(battleview.arena.layer_targetting.children.length).toBe(3 + 5); expect(targetting.ap_indicators[0].position.x).toBe(0); expect(targetting.ap_indicators[0].position.y).toBe(0); expect(targetting.ap_indicators[1].position.x).toBeCloseTo(40); @@ -57,15 +55,15 @@ module TS.SpaceTac.UI.Specs { targetting.setApIndicatorsInterval(1000); expect(targetting.ap_indicators.length).toBe(1); - expect(battleview.arena.children.length).toBe(init_children + 3 + 1); + expect(battleview.arena.layer_targetting.children.length).toBe(3 + 1); targetting.setApIndicatorsInterval(1); expect(targetting.ap_indicators.length).toBe(224); - expect(battleview.arena.children.length).toBe(init_children + 3 + 224); + expect(battleview.arena.layer_targetting.children.length).toBe(3 + 224); targetting.destroy(); - expect(battleview.arena.children.length).toBe(init_children); + expect(battleview.arena.layer_targetting.children.length).toBe(0); }); }); } diff --git a/src/ui/battle/Targetting.ts b/src/ui/battle/Targetting.ts index bf3d90b..92b522d 100644 --- a/src/ui/battle/Targetting.ts +++ b/src/ui/battle/Targetting.ts @@ -41,13 +41,13 @@ module TS.SpaceTac.UI { this.blast = new Phaser.Image(battleview.game, 0, 0, "battle-arena-blast"); this.blast.anchor.set(0.5, 0.5); this.blast.visible = false; - battleview.arena.add(this.blast); + battleview.arena.layer_targetting.add(this.blast); this.line_initial = new Phaser.Graphics(battleview.game, 0, 0); this.line_initial.visible = false; - battleview.arena.add(this.line_initial); + battleview.arena.layer_targetting.add(this.line_initial); this.line_corrected = new Phaser.Graphics(battleview.game, 0, 0); this.line_corrected.visible = false; - battleview.arena.add(this.line_corrected); + battleview.arena.layer_targetting.add(this.line_corrected); } this.source = null; @@ -138,7 +138,7 @@ module TS.SpaceTac.UI { while (this.ap_indicators.length < count) { let indicator = new Phaser.Image(this.battleview.game, 0, 0, "battle-arena-ap-indicator"); indicator.anchor.set(0.5, 0.5); - this.battleview.arena.addChild(indicator); + this.battleview.arena.layer_targetting.add(indicator); this.ap_indicators.push(indicator); } while (this.ap_indicators.length > count) {