2017-09-24 22:23:22 +00:00
|
|
|
module TK.SpaceTac.UI {
|
2017-05-17 21:55:39 +00:00
|
|
|
/**
|
|
|
|
* Graphical representation of a battle
|
|
|
|
*
|
|
|
|
* This is the area in the BattleView that will display ships with their real positions
|
|
|
|
*/
|
2017-09-19 15:09:06 +00:00
|
|
|
export class Arena {
|
2017-02-12 22:18:36 +00:00
|
|
|
// Link to battleview
|
2017-09-19 15:09:06 +00:00
|
|
|
view: BattleView
|
2017-02-12 22:18:36 +00:00
|
|
|
|
2017-05-17 21:55:39 +00:00
|
|
|
// Boundaries of the arena
|
2017-09-28 23:18:46 +00:00
|
|
|
boundaries: IBounded = { x: 0, y: 0, width: 1808, height: 948 }
|
2015-01-06 00:00:00 +00:00
|
|
|
|
2015-03-03 00:00:00 +00:00
|
|
|
// Hint for weapon or move range
|
2017-05-17 21:55:39 +00:00
|
|
|
range_hint: RangeHint
|
|
|
|
|
|
|
|
// Input capture
|
2018-05-15 14:57:45 +00:00
|
|
|
private mouse_capture?: UIImage
|
2014-12-31 00:00:00 +00:00
|
|
|
|
2015-01-06 00:00:00 +00:00
|
|
|
// List of ship sprites
|
2017-05-17 21:55:39 +00:00
|
|
|
private ship_sprites: ArenaShip[] = []
|
2017-02-08 18:54:02 +00:00
|
|
|
|
|
|
|
// List of drone sprites
|
2017-05-17 21:55:39 +00:00
|
|
|
private drone_sprites: ArenaDrone[] = []
|
2015-01-06 00:00:00 +00:00
|
|
|
|
2015-01-23 00:00:00 +00:00
|
|
|
// Currently hovered ship
|
2017-05-17 21:55:39 +00:00
|
|
|
private hovered: ArenaShip | null
|
2015-01-23 00:00:00 +00:00
|
|
|
// Currently playing ship
|
2017-05-17 21:55:39 +00:00
|
|
|
private playing: ArenaShip | null
|
2015-01-23 00:00:00 +00:00
|
|
|
|
2017-02-13 19:31:45 +00:00
|
|
|
// Layer for particles
|
2018-05-15 14:57:45 +00:00
|
|
|
container: UIContainer
|
|
|
|
layer_garbage: UIContainer
|
|
|
|
layer_hints: UIContainer
|
|
|
|
layer_drones: UIContainer
|
|
|
|
layer_ships: UIContainer
|
|
|
|
layer_weapon_effects: UIContainer
|
|
|
|
layer_targetting: UIContainer
|
2017-02-13 19:31:45 +00:00
|
|
|
|
2017-09-19 15:09:06 +00:00
|
|
|
// Callbacks to receive cursor events
|
|
|
|
callbacks_hover: ((location: ArenaLocation | null, ship: Ship | null) => void)[] = []
|
|
|
|
callbacks_click: (() => void)[] = []
|
2017-01-08 22:04:07 +00:00
|
|
|
|
2017-09-19 15:09:06 +00:00
|
|
|
// Create a graphical arena for ship sprites to fight in a 2D space
|
2018-05-15 14:57:45 +00:00
|
|
|
constructor(view: BattleView, container?: UIContainer) {
|
2017-09-19 15:09:06 +00:00
|
|
|
this.view = view;
|
2015-01-23 00:00:00 +00:00
|
|
|
this.playing = null;
|
2015-01-23 00:00:00 +00:00
|
|
|
this.hovered = null;
|
2017-03-09 17:11:00 +00:00
|
|
|
this.range_hint = new RangeHint(this);
|
2015-01-06 00:00:00 +00:00
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
let builder = new UIBuilder(view, container);
|
|
|
|
if (!container) {
|
|
|
|
container = builder.container("arena");
|
|
|
|
builder = builder.in(container);
|
|
|
|
}
|
|
|
|
this.container = container;
|
|
|
|
container.setPosition(this.boundaries.x, this.boundaries.y);
|
2017-09-19 15:09:06 +00:00
|
|
|
|
|
|
|
this.setupMouseCapture();
|
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
this.layer_garbage = builder.container("garbage");
|
|
|
|
this.layer_hints = builder.container("hints");
|
|
|
|
this.layer_drones = builder.container("drones");
|
|
|
|
this.layer_ships = builder.container("ships");
|
|
|
|
this.layer_weapon_effects = builder.container("effects");
|
|
|
|
this.layer_targetting = builder.container("targetting");
|
2017-09-19 15:09:06 +00:00
|
|
|
|
|
|
|
this.range_hint.setLayer(this.layer_hints);
|
|
|
|
this.addShipSprites();
|
2018-06-10 22:58:42 +00:00
|
|
|
view.battle.drones.list().forEach(drone => this.addDrone(drone, 0));
|
2017-05-17 21:55:39 +00:00
|
|
|
|
2018-02-08 15:16:03 +00:00
|
|
|
view.log_processor.register(diff => this.checkDroneDeployed(diff));
|
|
|
|
view.log_processor.register(diff => this.checkDroneRecalled(diff));
|
2017-11-14 00:07:06 +00:00
|
|
|
view.log_processor.watchForShipChange(ship => {
|
2017-12-04 18:12:06 +00:00
|
|
|
return {
|
|
|
|
foreground: async () => {
|
|
|
|
await this.setShipPlaying(ship)
|
|
|
|
}
|
|
|
|
}
|
2017-11-14 00:07:06 +00:00
|
|
|
});
|
2017-09-19 15:09:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move to a specific layer
|
|
|
|
*/
|
2018-05-15 14:57:45 +00:00
|
|
|
moveToLayer(layer: UIContainer): void {
|
2017-09-19 15:09:06 +00:00
|
|
|
layer.add(this.container);
|
2017-05-17 21:55:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Setup the mouse capture for targetting events
|
|
|
|
*/
|
|
|
|
setupMouseCapture() {
|
2017-09-19 15:09:06 +00:00
|
|
|
let view = this.view;
|
2015-04-26 19:34:55 +00:00
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
let background = new UIBuilder(view, this.container).image("battle-arena-background");
|
|
|
|
background.setName("mouse-capture");
|
|
|
|
background.setScale(this.boundaries.width / background.width, this.boundaries.height / background.height)
|
2014-12-31 00:00:00 +00:00
|
|
|
|
2015-01-02 00:00:00 +00:00
|
|
|
// Capture clicks on background
|
2018-05-15 14:57:45 +00:00
|
|
|
background.setInteractive();
|
2018-06-11 17:10:06 +00:00
|
|
|
background.on("pointerup", (pointer: Phaser.Input.Pointer) => {
|
|
|
|
if (pointer.buttons == 1) {
|
|
|
|
this.callbacks_click.forEach(callback => callback());
|
|
|
|
}
|
2015-01-02 00:00:00 +00:00
|
|
|
});
|
2018-05-15 14:57:45 +00:00
|
|
|
background.on("pointerout", () => {
|
2017-09-19 15:09:06 +00:00
|
|
|
this.callbacks_hover.forEach(callback => callback(null, null));
|
2017-06-21 23:32:18 +00:00
|
|
|
});
|
2015-01-02 00:00:00 +00:00
|
|
|
|
2014-12-31 00:00:00 +00:00
|
|
|
// Watch mouse move to capture hovering over background
|
2018-05-15 14:57:45 +00:00
|
|
|
view.input.on("pointermove", (pointer: Phaser.Input.Pointer) => {
|
2018-04-16 18:12:26 +00:00
|
|
|
if (this.view.dialogs_opened.length > 0 || this.view.character_sheet.isOpened() || this.view.layer_overlay.length > 0) {
|
2017-12-07 00:05:06 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
let location = new ArenaLocation(pointer.x, pointer.y);
|
|
|
|
let ship = this.getShip(location);
|
|
|
|
this.callbacks_hover.forEach(callback => callback(location, ship));
|
2014-12-31 00:00:00 +00:00
|
|
|
}, null);
|
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
this.mouse_capture = background;
|
2014-12-31 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-19 15:09:06 +00:00
|
|
|
/**
|
|
|
|
* Get the ship under a cursor location
|
|
|
|
*/
|
|
|
|
getShip(location: ArenaLocation): Ship | null {
|
|
|
|
let nearest = minBy(this.ship_sprites, sprite => arenaDistance(location, sprite.ship.location));
|
|
|
|
if (nearest && arenaDistance(location, nearest) < 50) {
|
|
|
|
return nearest.ship;
|
|
|
|
} else {
|
|
|
|
return null;
|
2017-05-17 21:55:39 +00:00
|
|
|
}
|
2014-12-31 00:00:00 +00:00
|
|
|
}
|
2015-01-06 00:00:00 +00:00
|
|
|
|
2017-05-17 21:55:39 +00:00
|
|
|
/**
|
|
|
|
* Add the sprites for all ships
|
|
|
|
*/
|
|
|
|
addShipSprites() {
|
2017-09-19 15:09:06 +00:00
|
|
|
iforeach(this.view.battle.iships(), ship => {
|
2017-05-17 21:55:39 +00:00
|
|
|
let sprite = new ArenaShip(this, ship);
|
2017-12-20 18:57:28 +00:00
|
|
|
(ship.alive ? this.layer_ships : this.layer_garbage).add(sprite);
|
2015-03-06 00:00:00 +00:00
|
|
|
this.ship_sprites.push(sprite);
|
2015-01-06 00:00:00 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-09-19 15:09:06 +00:00
|
|
|
/**
|
|
|
|
* Get the current MainUI instance
|
|
|
|
*/
|
|
|
|
get game(): MainUI {
|
|
|
|
return this.view.gameui;
|
2015-04-22 21:53:13 +00:00
|
|
|
}
|
|
|
|
|
2017-05-09 20:41:35 +00:00
|
|
|
/**
|
|
|
|
* Get the current battle displayed
|
|
|
|
*/
|
|
|
|
getBattle(): Battle {
|
2017-09-19 15:09:06 +00:00
|
|
|
return this.view.battle;
|
2017-05-09 20:41:35 +00:00
|
|
|
}
|
|
|
|
|
2015-02-18 00:00:00 +00:00
|
|
|
// Remove a ship sprite
|
2017-02-09 00:00:35 +00:00
|
|
|
markAsDead(ship: Ship): void {
|
2015-02-18 00:00:00 +00:00
|
|
|
var sprite = this.findShipSprite(ship);
|
|
|
|
if (sprite) {
|
2017-02-15 22:34:27 +00:00
|
|
|
sprite.setDead(true);
|
2017-05-17 23:24:42 +00:00
|
|
|
this.layer_garbage.add(sprite);
|
2015-02-18 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-06 00:00:00 +00:00
|
|
|
// Find the sprite for a ship
|
2017-11-14 00:07:06 +00:00
|
|
|
findShipSprite(ship: Ship | RObjectId | null): ArenaShip | null {
|
|
|
|
return first(this.ship_sprites, sprite => sprite.ship.is(ship));
|
2015-01-06 00:00:00 +00:00
|
|
|
}
|
2015-01-21 00:00:00 +00:00
|
|
|
|
|
|
|
// Set the hovered state on a ship sprite
|
2017-03-09 17:11:00 +00:00
|
|
|
setShipHovered(ship: Ship | null): void {
|
2015-01-23 00:00:00 +00:00
|
|
|
if (this.hovered) {
|
2017-05-23 16:42:55 +00:00
|
|
|
this.hovered.setHovered(false, false);
|
2015-01-23 00:00:00 +00:00
|
|
|
}
|
2017-03-09 17:11:00 +00:00
|
|
|
|
|
|
|
if (ship) {
|
|
|
|
var arena_ship = this.findShipSprite(ship);
|
|
|
|
if (arena_ship) {
|
2017-05-23 16:42:55 +00:00
|
|
|
arena_ship.setHovered(true, false);
|
2017-05-17 21:55:39 +00:00
|
|
|
this.layer_ships.bringToTop(arena_ship);
|
2017-03-09 17:11:00 +00:00
|
|
|
}
|
|
|
|
this.hovered = arena_ship;
|
|
|
|
} else {
|
|
|
|
this.hovered = null;
|
2015-01-21 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|
2015-01-23 00:00:00 +00:00
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
/**
|
|
|
|
* Set the playing state on a ship sprite
|
|
|
|
*/
|
|
|
|
async setShipPlaying(ship: Ship | null, animate = true): Promise<void> {
|
2015-01-23 00:00:00 +00:00
|
|
|
if (this.playing) {
|
|
|
|
this.playing.setPlaying(false);
|
2017-03-09 17:11:00 +00:00
|
|
|
this.playing = null;
|
2015-01-23 00:00:00 +00:00
|
|
|
}
|
2017-03-09 17:11:00 +00:00
|
|
|
|
|
|
|
if (ship) {
|
2017-12-04 18:12:06 +00:00
|
|
|
let arena_ship = this.findShipSprite(ship);
|
2017-03-09 17:11:00 +00:00
|
|
|
if (arena_ship) {
|
2017-05-17 21:55:39 +00:00
|
|
|
this.layer_ships.bringToTop(arena_ship);
|
2017-12-04 18:12:06 +00:00
|
|
|
await arena_ship.setPlaying(true, animate);
|
2017-03-09 17:11:00 +00:00
|
|
|
}
|
2017-12-04 18:12:06 +00:00
|
|
|
|
2017-03-09 17:11:00 +00:00
|
|
|
this.playing = arena_ship;
|
2015-01-23 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-08 18:54:02 +00:00
|
|
|
|
2017-02-15 21:15:31 +00:00
|
|
|
/**
|
|
|
|
* Find an ArenaDrone displaying a Drone.
|
|
|
|
*/
|
2017-11-14 00:07:06 +00:00
|
|
|
findDrone(drone: Drone | RObjectId | null): ArenaDrone | null {
|
|
|
|
return first(this.drone_sprites, sprite => sprite.drone.is(drone));
|
2017-02-15 21:15:31 +00:00
|
|
|
}
|
|
|
|
|
2017-02-14 00:30:50 +00:00
|
|
|
/**
|
|
|
|
* Spawn a new drone
|
|
|
|
*/
|
2018-06-10 22:58:42 +00:00
|
|
|
async addDrone(drone: Drone, speed = 1): Promise<void> {
|
2017-02-15 21:15:31 +00:00
|
|
|
if (!this.findDrone(drone)) {
|
2017-09-19 15:09:06 +00:00
|
|
|
let sprite = new ArenaDrone(this.view, drone);
|
2017-11-14 00:07:06 +00:00
|
|
|
let owner = this.view.battle.getShip(drone.owner) || new Ship();
|
|
|
|
let angle = Math.atan2(drone.y - owner.arena_y, drone.x - owner.arena_x);
|
2017-05-17 21:55:39 +00:00
|
|
|
this.layer_drones.add(sprite);
|
2017-02-08 18:54:02 +00:00
|
|
|
this.drone_sprites.push(sprite);
|
|
|
|
|
2018-06-10 22:58:42 +00:00
|
|
|
if (speed) {
|
2018-05-15 14:57:45 +00:00
|
|
|
sprite.radius.setAlpha(0);
|
|
|
|
sprite.setPosition(owner.arena_x, owner.arena_y);
|
|
|
|
sprite.sprite.setRotation(owner.arena_angle);
|
2018-06-10 22:58:42 +00:00
|
|
|
await this.view.animations.moveInSpace(sprite, drone.x, drone.y, angle, sprite.sprite, speed);
|
|
|
|
await this.view.animations.addAnimation(sprite.radius, { alpha: 1 }, 500 / speed, "Cubic.easeIn");
|
2017-02-15 21:15:31 +00:00
|
|
|
} else {
|
2018-05-15 14:57:45 +00:00
|
|
|
sprite.setPosition(drone.x, drone.y);
|
|
|
|
sprite.setRotation(angle);
|
2017-02-15 21:15:31 +00:00
|
|
|
}
|
2017-02-14 00:30:50 +00:00
|
|
|
|
2017-02-08 18:54:02 +00:00
|
|
|
} else {
|
|
|
|
console.error("Drone added twice to arena", drone);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-10 23:13:56 +00:00
|
|
|
/**
|
|
|
|
* Remove a destroyed drone
|
|
|
|
*/
|
2018-06-10 22:58:42 +00:00
|
|
|
async removeDrone(drone: Drone, speed = 1): Promise<void> {
|
2017-02-15 21:15:31 +00:00
|
|
|
let sprite = this.findDrone(drone);
|
2017-02-08 18:54:02 +00:00
|
|
|
if (sprite) {
|
|
|
|
remove(this.drone_sprites, sprite);
|
2018-06-11 17:10:06 +00:00
|
|
|
return sprite.setDestroyed(speed);
|
2017-02-08 18:54:02 +00:00
|
|
|
} else {
|
|
|
|
console.error("Drone not found in arena for removal", drone);
|
|
|
|
}
|
|
|
|
}
|
2017-05-09 18:17:49 +00:00
|
|
|
|
2017-05-15 18:30:44 +00:00
|
|
|
/**
|
2017-05-16 23:31:23 +00:00
|
|
|
* Switch the tactical mode (shows information on all ships, and fades background)
|
2017-05-15 18:30:44 +00:00
|
|
|
*/
|
2017-05-16 23:31:23 +00:00
|
|
|
setTacticalMode(active: boolean): void {
|
2017-05-23 16:42:55 +00:00
|
|
|
this.ship_sprites.forEach(sprite => sprite.setHovered(active, true));
|
2017-05-21 16:39:02 +00:00
|
|
|
this.drone_sprites.forEach(drone => drone.setTacticalMode(active));
|
2017-09-19 15:09:06 +00:00
|
|
|
this.view.animations.setVisible(this.layer_garbage, !active, 200);
|
|
|
|
if (this.view.background) {
|
|
|
|
this.view.animations.setVisible(this.view.background, !active, 200);
|
2017-05-15 18:30:44 +00:00
|
|
|
}
|
|
|
|
}
|
2017-05-16 23:12:05 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the boundaries of the arena on display
|
|
|
|
*/
|
|
|
|
getBoundaries(): IBounded {
|
2017-05-30 22:40:28 +00:00
|
|
|
return this.boundaries;
|
2017-05-16 23:12:05 +00:00
|
|
|
}
|
2018-02-08 15:16:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a new drone as been deployed
|
|
|
|
*/
|
|
|
|
private checkDroneDeployed(diff: BaseBattleDiff): LogProcessorDelegate {
|
|
|
|
if (diff instanceof DroneDeployedDiff) {
|
|
|
|
return {
|
2018-06-10 22:58:42 +00:00
|
|
|
foreground: async (speed: number) => {
|
|
|
|
if (speed) {
|
2018-02-08 15:16:03 +00:00
|
|
|
this.view.gameui.audio.playOnce("battle-drone-deploy");
|
|
|
|
}
|
2018-06-10 22:58:42 +00:00
|
|
|
await this.addDrone(diff.drone, speed);
|
2018-02-08 15:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a drone as been recalled
|
|
|
|
*/
|
|
|
|
private checkDroneRecalled(diff: BaseBattleDiff): LogProcessorDelegate {
|
|
|
|
if (diff instanceof DroneRecalledDiff) {
|
|
|
|
return {
|
2018-06-10 22:58:42 +00:00
|
|
|
foreground: async (speed: number) => {
|
|
|
|
if (speed) {
|
2018-02-08 15:16:03 +00:00
|
|
|
this.view.gameui.audio.playOnce("battle-drone-destroy");
|
|
|
|
}
|
2018-06-10 22:58:42 +00:00
|
|
|
await this.removeDrone(diff.drone, speed);
|
2018-02-08 15:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
2014-12-31 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|