From eb75658a106289a8c3e4a400f98b881df7efaede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Wed, 8 Feb 2017 19:54:02 +0100 Subject: [PATCH] Added drone display --- README.md | 44 +++++++++++++++-- TODO | 8 ++- src/game/Drone.spec.ts | 20 +++++--- src/game/Drone.ts | 29 ++++++----- src/game/actions/DeployDroneAction.spec.ts | 2 + src/game/actions/DeployDroneAction.ts | 2 +- src/game/equipments/RepairDrone.spec.ts | 1 + src/game/equipments/RepairDrone.ts | 1 + src/view/battle/Arena.ts | 31 +++++++++++- src/view/battle/ArenaDrone.ts | 33 +++++++++++++ src/view/battle/LogProcessor.ts | 57 ++++++++++++---------- 11 files changed, 174 insertions(+), 54 deletions(-) create mode 100644 src/view/battle/ArenaDrone.ts diff --git a/README.md b/README.md index a4ca03b..c8e78f1 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,40 @@ After making changes to sources, you need to recompile: npm run build -## Attributes +## Ships + +### In-combat values (HSP) + +In combat, a ship's vitals are represented by the HSP system (Hull-Shield-Power): -* **Initiative** - Ability to play before other ships in the play order * **Hull** - Amout of damage that a ship can receive before having to shut down all its systems * **Shield** - Amount of damage that the shield equipments may absorb to protect the Hull * **Power** - Available action points (some actions require more power than others) + +These values will be changed by various effects (usage of equipments, sustained damage...). + +### Attributes + +Attributes represent a ship's ability to use its HSP system: + +* **Initiative** - Ability to play before other ships in the play order +* **Hull capacity** - Maximal Hull value (when the battle starts) +* **Shield capacity** - Maximal Shield value (when the battle starts) +* **Power capacity** - Maximal Power value +* **Initial power** - Power immediately available at the start of battle * **Power recovery** - Power generated at the end of a ship's turn -## Skills +These attributes are the sum of all currently applied effects (being permanent by an equipped item, +or a temporary effect caused by a weapon or a drone). + +For example, a ship that equips a power generator with "power recovery +3", but has a sticky effect +of "power recovery -1" from a previous weapon hit, will have an effective power recovery of 2. + +Attributes may also be upgraded permanently during level up. + +### Skills + +Skills represent a ship's ability to use equipments: * **Materials** - Usage of physical materials such as bullets, shells... * **Electronics** - Components of computers and communication @@ -38,6 +63,16 @@ After making changes to sources, you need to recompile: * **Gravity** - Interaction with gravitational forces * **Time** - Manipulation of time +Each equipment has minimal skill requirements to be used. For example, a weapon may require "materials >= 2" +and "energy >= 3" to be equipped. A ship that does not meet these requirements will not be able to use +the equipment. + +As for attributes, skill values are controlled by equipments, effects and level up. + +If an equipped item has a requirement of "time >= 2", that the ship has time skill of exactly 2, and that a +temporary effect of "time -1" is active, the requirement is no longer fulfilled and the equipped item +is then temporarily disabled (no more effects and cannot be used), until the "time -1" effect is lifted. + ## Drones Drones are static objects, deployed by ships, that apply effects in a circular zone around themselves. @@ -50,6 +85,9 @@ Drone effects are applied : Drones are fully autonomous, and once deployed, are not controlled by their owner ship. +They are small and cannot be the direct target of weapons. They are not affected by area effects, +except for area damage and area effects specifically designed for drones. + A drone lasts for a given number of turns, counting down each time its owner's turn starts. When reaching the number of turns, the drone is destroyed (before the owner's turn is started). For example, a drone with 1-turn duration will destroy just before the next turn of its owner. diff --git a/TODO b/TODO index 4582eb4..00ec993 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,10 @@ * Restore serialization -* Drones: add sprite, radius and tooltip +* Drones: add tooltip +* Drones: add hull points and take area damage +* Organize arena objects and information in layers * Allow to cancel last moves +* Add more visual effects to weapons, hits and explosions +* Add keyboard shortcuts for actions * Effect should be random in a range (eg. "damage target 50-75") * Add an overload/cooling system * Add auto-move to attack @@ -19,4 +23,4 @@ * Add a victory screen, with loot display * Add retreat from battle * Map : restore ability to jump to other systems -* Map : add stores +* Map : add stores and shipyards diff --git a/src/game/Drone.spec.ts b/src/game/Drone.spec.ts index d76af8d..553baf1 100644 --- a/src/game/Drone.spec.ts +++ b/src/game/Drone.spec.ts @@ -110,14 +110,18 @@ module TS.SpaceTac.Game { let [drone, effect] = newTestDrone(0, 0, 5, owner); drone.duration = 2; - let result = drone.onTurnStart(other); - expect(result).toBe(true); - result = drone.onTurnStart(owner); - expect(result).toBe(true); - result = drone.onTurnStart(other); - expect(result).toBe(true); - result = drone.onTurnStart(owner); - expect(result).toBe(false); + let battle = new Battle(); + spyOn(owner, "getBattle").and.returnValue(battle); + let removeDrone = spyOn(battle, "removeDrone").and.callThrough(); + + drone.onTurnStart(other); + expect(removeDrone).not.toHaveBeenCalled(); + drone.onTurnStart(owner); + expect(removeDrone).not.toHaveBeenCalled(); + drone.onTurnStart(other); + expect(removeDrone).not.toHaveBeenCalled(); + drone.onTurnStart(owner); + expect(removeDrone).toHaveBeenCalledWith(drone); }); }); } diff --git a/src/game/Drone.ts b/src/game/Drone.ts index 456fc09..512d97d 100644 --- a/src/game/Drone.ts +++ b/src/game/Drone.ts @@ -3,6 +3,9 @@ module TS.SpaceTac.Game { * Drones are static objects that apply effects in a circular zone around themselves. */ export class Drone { + // Code of the drone + code: string; + // Ship that deployed the drone owner: Ship; @@ -23,8 +26,9 @@ module TS.SpaceTac.Game { // Ships starting their turn the radius inside_at_start: Ship[] = []; - constructor(owner: Ship) { + constructor(owner: Ship, code = "drone") { this.owner = owner; + this.code = code; } /** @@ -56,16 +60,20 @@ module TS.SpaceTac.Game { /** * Called when a ship turn starts - * - * Returns false if the drone should be destroyed */ - onTurnStart(ship: Ship): boolean { + onTurnStart(ship: Ship) { if (ship == this.owner) { - if (this.duration <= 1) { - return false; - } else { - this.duration--; + this.duration--; + } + + if (this.duration <= 0) { + if (this.owner) { + let battle = this.owner.getBattle(); + if (battle) { + battle.removeDrone(this); + } } + return; } if (ship.isInCircle(this.x, this.y, this.radius)) { @@ -74,14 +82,13 @@ module TS.SpaceTac.Game { } else { remove(this.inside_at_start, ship); } - return true; } /** * Called when a ship turn ends */ onTurnEnd(ship: Ship) { - if (ship.isInCircle(this.x, this.y, this.radius) && contains(this.inside_at_start, ship)) { + if (this.duration > 0 && ship.isInCircle(this.x, this.y, this.radius) && contains(this.inside_at_start, ship)) { this.singleApply(ship); } } @@ -90,7 +97,7 @@ module TS.SpaceTac.Game { * Called after a ship moved */ onShipMove(ship: Ship) { - if (ship.isInCircle(this.x, this.y, this.radius)) { + if (this.duration > 0 && ship.isInCircle(this.x, this.y, this.radius)) { if (add(this.inside, ship)) { this.singleApply(ship); } diff --git a/src/game/actions/DeployDroneAction.spec.ts b/src/game/actions/DeployDroneAction.spec.ts index 5db2e01..c9804e4 100644 --- a/src/game/actions/DeployDroneAction.spec.ts +++ b/src/game/actions/DeployDroneAction.spec.ts @@ -35,6 +35,7 @@ module TS.SpaceTac.Game { battle.playing_ship = ship; TestTools.setShipAP(ship, 3); let equipment = new Equipment(); + equipment.code = "testdrone"; equipment.distance = 8; equipment.ap_usage = 2; equipment.duration = 2; @@ -48,6 +49,7 @@ module TS.SpaceTac.Game { expect(battle.drones.length).toBe(1); let drone = battle.drones[0]; + expect(drone.code).toEqual("testdrone"); expect(drone.duration).toEqual(2); expect(drone.owner).toBe(ship); expect(drone.x).toEqual(5); diff --git a/src/game/actions/DeployDroneAction.ts b/src/game/actions/DeployDroneAction.ts index 0b985c1..c8bf626 100644 --- a/src/game/actions/DeployDroneAction.ts +++ b/src/game/actions/DeployDroneAction.ts @@ -16,7 +16,7 @@ module TS.SpaceTac.Game { } protected customApply(battle: Battle, ship: Ship, target: Target): boolean { - let drone = new Drone(ship); + let drone = new Drone(ship, this.equipment.code); drone.x = target.x; drone.y = target.y; drone.radius = this.equipment.blast; diff --git a/src/game/equipments/RepairDrone.spec.ts b/src/game/equipments/RepairDrone.spec.ts index f006562..bf9930e 100644 --- a/src/game/equipments/RepairDrone.spec.ts +++ b/src/game/equipments/RepairDrone.spec.ts @@ -15,6 +15,7 @@ module TS.SpaceTac.Game.Equipments { expect(battle.drones.length).toBe(1); let drone = battle.drones[0]; + expect(drone.duration).toBe(1); ship.setAttribute("hull_capacity", 100); ship.setValue("hull", 85); drone.singleApply(ship); diff --git a/src/game/equipments/RepairDrone.ts b/src/game/equipments/RepairDrone.ts index 186ecdb..bf011e9 100644 --- a/src/game/equipments/RepairDrone.ts +++ b/src/game/equipments/RepairDrone.ts @@ -10,6 +10,7 @@ module TS.SpaceTac.Game.Equipments { this.min_level = new IntegerRange(1, 4); + this.setLifetime(1, 1); this.setDeployDistance(50, 100); this.setEffectRadius(40, 80); this.setPowerConsumption(4, 5); diff --git a/src/view/battle/Arena.ts b/src/view/battle/Arena.ts index dccff4e..7c4a920 100644 --- a/src/view/battle/Arena.ts +++ b/src/view/battle/Arena.ts @@ -15,7 +15,10 @@ module TS.SpaceTac.View { private battleview: BattleView; // List of ship sprites - private ship_sprites: ArenaShip[]; + private ship_sprites: ArenaShip[] = []; + + // List of drone sprites + private drone_sprites: ArenaDrone[] = []; // Currently hovered ship private hovered: ArenaShip; @@ -27,7 +30,6 @@ module TS.SpaceTac.View { super(battleview.game); this.battleview = battleview; - this.ship_sprites = []; this.playing = null; this.hovered = null; this.range_hint = null; @@ -129,5 +131,30 @@ module TS.SpaceTac.View { this.battleview.gameui.audio.playOnce("battle-ship-change"); } + + // Spawn a new drone + addDrone(drone: Game.Drone): void { + if (!any(this.drone_sprites, sprite => sprite.drone == drone)) { + let sprite = new ArenaDrone(this.battleview, drone); + this.addChild(sprite); + this.drone_sprites.push(sprite); + + sprite.position.set(drone.owner.arena_x, drone.owner.arena_y); + this.game.tweens.create(sprite.position).to({ x: drone.x, y: drone.y }, 1800, Phaser.Easing.Sinusoidal.InOut, true, 200); + } else { + console.error("Drone added twice to arena", drone); + } + } + + // Remove a destroyed drone + removeDrone(drone: Game.Drone): void { + let sprite = first(this.drone_sprites, sprite => sprite.drone == drone); + if (sprite) { + remove(this.drone_sprites, sprite); + sprite.destroy(); + } else { + console.error("Drone not found in arena for removal", drone); + } + } } } diff --git a/src/view/battle/ArenaDrone.ts b/src/view/battle/ArenaDrone.ts new file mode 100644 index 0000000..ed243d2 --- /dev/null +++ b/src/view/battle/ArenaDrone.ts @@ -0,0 +1,33 @@ +module TS.SpaceTac.View { + /** + * Drone sprite in the arena + */ + export class ArenaDrone extends Phaser.Group { + // Link to displayed drone + drone: Game.Drone; + + // Sprite + sprite: Phaser.Button; + + // Radius + radius: Phaser.Graphics; + + constructor(battleview: BattleView, drone: Game.Drone) { + super(battleview.game); + + this.drone = drone; + + this.radius = new Phaser.Graphics(this.game, 0, 0); + this.radius.lineStyle(3, 0xe9f2f9, 0.5); + this.radius.beginFill(0xe9f2f9, 0.0); + this.radius.drawCircle(0, 0, drone.radius * 2); + this.radius.endFill(); + this.addChild(this.radius); + + this.sprite = new Phaser.Button(this.game, 0, 0, `battle-actions-deploy-${drone.code}`); + this.sprite.anchor.set(0.5, 0.5); + this.sprite.scale.set(0.1, 0.1); + this.addChild(this.sprite); + } + } +} diff --git a/src/view/battle/LogProcessor.ts b/src/view/battle/LogProcessor.ts index 48865bb..7273104 100644 --- a/src/view/battle/LogProcessor.ts +++ b/src/view/battle/LogProcessor.ts @@ -30,33 +30,26 @@ module TS.SpaceTac.View { processBattleEvent(event: Game.BaseLogEvent) { console.log("Battle event", event); - switch (event.code) { - case "ship_change": - this.processShipChangeEvent(event); - break; - case "damage": - this.processDamageEvent(event); - break; - case "move": - this.processMoveEvent(event); - break; - case "value": - this.processValueChangedEvent(event); - break; - case "death": - this.processDeathEvent(event); - break; - case "fire": - this.processFireEvent(event); - break; - case "endbattle": - this.processEndBattleEvent(event); - break; - case "effectadd": - case "effectduration": - case "effectdel": - this.processEffectEvent(event); - break; + if (event instanceof Game.ShipChangeEvent) { + this.processShipChangeEvent(event); + } else if (event instanceof Game.MoveEvent) { + this.processMoveEvent(event); + } else if (event instanceof Game.ValueChangeEvent) { + this.processValueChangedEvent(event); + } else if (event instanceof Game.DeathEvent) { + this.processDeathEvent(event); + } else if (event instanceof Game.FireEvent) { + this.processFireEvent(event); + } else if (event instanceof Game.DamageEvent) { + this.processDamageEvent(event); + } else if (event instanceof Game.EndBattleEvent) { + this.processEndBattleEvent(event); + } else if (event instanceof Game.DroneDeployedEvent) { + this.processDroneDeployedEvent(event); + } else if (event instanceof Game.DroneDestroyedEvent) { + this.processDroneDestroyedEvent(event); + } else if (event.code == "effectadd" || event.code == "effectduration" || event.code == "effectdel") { + this.processEffectEvent(event); } } @@ -150,5 +143,15 @@ module TS.SpaceTac.View { item.updateEffects(); } } + + // New drone deployed + private processDroneDeployedEvent(event: Game.DroneDeployedEvent): void { + this.view.arena.addDrone(event.drone); + } + + // Drone destroyed + private processDroneDestroyedEvent(event: Game.DroneDestroyedEvent): void { + this.view.arena.removeDrone(event.drone); + } } }