Fixed weapon effect originating from wrong ship location
This commit is contained in:
parent
c5c141edcb
commit
20377b49e7
10
TODO
10
TODO
|
@ -12,20 +12,16 @@
|
|||
* Loot: lucky finds should be proportional to cargo space
|
||||
* Ship models: Add permanent effects
|
||||
* Ship models: Add permanent actions
|
||||
* Equipment: no-target actions (direct activation, with confirmation dialog ?)
|
||||
* Equipment: toggle actions (AP usage ? time of effect ?)
|
||||
* Equipment: add critical hit/miss
|
||||
* Equipment: add damage over time effect
|
||||
* 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: display effects description instead of attribute changes
|
||||
* Arena: display radius for area effects (both on action hover, and while action is active)
|
||||
* Arena: fix effects originating from real ship location instead of current sprite (when AI fires then moves)
|
||||
* Arena: any displayed info should be based on a ship copy stored in ArenaShip, and in sync with current log index (not the game state ship)
|
||||
* Arena: add engine trail
|
||||
* Log processor: Cancel previous animations, and allow no animation mode
|
||||
* Fix capacity limit effect not refreshing associated value (for example, on "limit power capacity to 3", potential "power" value change is not broadcast)
|
||||
* Actions: show power usage/recovery in power bar, on action hover
|
||||
* Actions: fix targetting not resetting when using keyboard shortcuts
|
||||
* Actions: fix targetting not resetting on current cursor location when using keyboard shortcuts
|
||||
* Add actions with cost dependent of distance (like current move actions)
|
||||
* Find incentives to move from starting position
|
||||
* Outcome: disable the loot button if there is no loot
|
||||
|
|
8
src/core/ArenaLocation.spec.ts
Normal file
8
src/core/ArenaLocation.spec.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
module TS.SpaceTac.Specs {
|
||||
describe("ArenaLocation", () => {
|
||||
it("gets distance and angle between two locations", () => {
|
||||
expect(arenaDistance({ x: 0, y: 0 }, { x: 1, y: 1 })).toBeCloseTo(Math.sqrt(2), 8);
|
||||
expect(arenaAngle({ x: 0, y: 0 }, { x: 1, y: 1 })).toBeCloseTo(Math.PI / 4, 8);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
module TS.SpaceTac {
|
||||
/**
|
||||
* Location in the arena
|
||||
* Location in the arena (coordinates only)
|
||||
*/
|
||||
export class ArenaLocation {
|
||||
export interface IArenaLocation {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
export class ArenaLocation implements IArenaLocation {
|
||||
x: number
|
||||
y: number
|
||||
|
||||
|
@ -10,21 +14,17 @@ module TS.SpaceTac {
|
|||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance to another location
|
||||
*/
|
||||
getDistanceTo(other: ArenaLocation) {
|
||||
let dx = this.x - other.x;
|
||||
let dy = this.y - other.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Location in the arena, with a facing angle in radians
|
||||
*/
|
||||
export class ArenaLocationAngle extends ArenaLocation {
|
||||
export interface IArenaLocationAngle {
|
||||
x: number
|
||||
y: number
|
||||
angle: number
|
||||
}
|
||||
export class ArenaLocationAngle extends ArenaLocation implements IArenaLocationAngle {
|
||||
angle: number
|
||||
|
||||
constructor(x = 0, y = 0, angle = 0) {
|
||||
|
@ -32,4 +32,46 @@ module TS.SpaceTac {
|
|||
this.angle = angle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Circle area in the arena
|
||||
*/
|
||||
export interface IArenaCircleArea {
|
||||
x: number
|
||||
y: number
|
||||
radius: number
|
||||
}
|
||||
|
||||
export class ArenaCircleArea extends ArenaLocation implements IArenaCircleArea {
|
||||
radius: number
|
||||
|
||||
constructor(x = 0, y = 0, radius = 0) {
|
||||
super(x, y);
|
||||
this.radius = radius;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the normalized angle between two locations
|
||||
*/
|
||||
export function arenaAngle(loc1: IArenaLocation, loc2: IArenaLocation) {
|
||||
return Math.atan2(loc2.y - loc1.y, loc2.x - loc1.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the normalized distance between two locations
|
||||
*/
|
||||
export function arenaDistance(loc1: IArenaLocation, loc2: IArenaLocation) {
|
||||
let dx = loc2.x - loc1.x;
|
||||
let dy = loc2.y - loc1.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a location is inside an area
|
||||
*/
|
||||
export function arenaInside(loc1: IArenaLocation, loc2: IArenaCircleArea, border_inclusive = true) {
|
||||
let dist = arenaDistance(loc1, loc2);
|
||||
return border_inclusive ? (dist <= loc2.radius) : (dist < loc2.radius);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ module TS.SpaceTac {
|
|||
play_priority = 0;
|
||||
|
||||
// Create a new ship inside a fleet
|
||||
constructor(fleet: Fleet | null = null, name = "Ship", model = new ShipModel("default", "Default", 1, 0, false, 0)) {
|
||||
constructor(fleet: Fleet | null = null, name = "unnamed", model = new ShipModel("default", "Default", 1, 0, false, 0)) {
|
||||
this.fleet = fleet || new Fleet();
|
||||
this.name = name;
|
||||
this.alive = true;
|
||||
|
|
|
@ -48,7 +48,7 @@ module TS.SpaceTac {
|
|||
|
||||
jasmineToString() {
|
||||
if (this.ship) {
|
||||
return this.ship.jasmineToString();
|
||||
return `(${this.x},${this.y}) ${this.ship.jasmineToString()}`;
|
||||
} else {
|
||||
return `(${this.x},${this.y})`;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ module TS.SpaceTac {
|
|||
* Get the distance travelled
|
||||
*/
|
||||
getDistance(): number {
|
||||
return this.start.getDistanceTo(this.end);
|
||||
return arenaDistance(this.start, this.end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,17 @@ module TS.SpaceTac.UI {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all ship sprites in a circle area
|
||||
*/
|
||||
getShipsInCircle(area: ArenaCircleArea, alive_only = true, border_inclusive = true): ArenaShip[] {
|
||||
let base = this.ship_sprites;
|
||||
if (alive_only) {
|
||||
base = base.filter(ship => !ship.isDead());
|
||||
}
|
||||
return base.filter(ship => arenaInside(ship, area, border_inclusive));
|
||||
}
|
||||
|
||||
// Get the current MainUI instance
|
||||
getGame(): MainUI {
|
||||
return this.battleview.gameui;
|
||||
|
|
|
@ -204,6 +204,27 @@ module TS.SpaceTac.UI {
|
|||
this.battleview.animations.setVisible(this.stasis, dead, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the ship is dead
|
||||
*/
|
||||
isDead(): boolean {
|
||||
return this.stasis.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ship value
|
||||
*/
|
||||
getValue(value: keyof ShipValues): number {
|
||||
switch (value) {
|
||||
case "hull":
|
||||
return this.hull.getValue();
|
||||
case "shield":
|
||||
return this.shield.getValue();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the sprite to a location
|
||||
*
|
||||
|
|
|
@ -266,10 +266,7 @@ module TS.SpaceTac.UI {
|
|||
|
||||
// Weapon used
|
||||
private processFireEvent(event: FireEvent): number {
|
||||
var source = Target.newFromShip(event.ship);
|
||||
var destination = event.target;
|
||||
|
||||
var effect = new WeaponEffect(this.view.arena, source, destination, event.weapon);
|
||||
var effect = new WeaponEffect(this.view.arena, event.ship, event.target, event.weapon);
|
||||
let duration = effect.start();
|
||||
|
||||
return duration;
|
||||
|
|
|
@ -4,7 +4,7 @@ module TS.SpaceTac.UI.Specs {
|
|||
|
||||
it("displays shield hit effect", function () {
|
||||
let battleview = testgame.battleview;
|
||||
let effect = new WeaponEffect(battleview.arena, new Target(0, 0), new Target(0, 0), new Equipment());
|
||||
let effect = new WeaponEffect(battleview.arena, new Ship(), new Target(0, 0), new Equipment());
|
||||
effect.shieldImpactEffect({ x: 10, y: 10 }, { x: 20, y: 15 }, 1000, 3000, true);
|
||||
|
||||
let layer = battleview.arena.layer_weapon_effects;
|
||||
|
@ -19,10 +19,13 @@ module TS.SpaceTac.UI.Specs {
|
|||
|
||||
it("displays gatling gun effect", function () {
|
||||
let battleview = testgame.battleview;
|
||||
let ship = new Ship();
|
||||
let ship = nn(battleview.battle.playing_ship);
|
||||
let sprite = nn(battleview.arena.findShipSprite(ship));
|
||||
ship.setArenaPosition(50, 30);
|
||||
TestTools.setShipHP(ship, 10, 0);
|
||||
let effect = new WeaponEffect(battleview.arena, new Target(10, 0), Target.newFromShip(ship), new Equipment());
|
||||
sprite.position.set(50, 30);
|
||||
sprite.hull.setValue(10, 10);
|
||||
sprite.shield.setValue(0, 10);
|
||||
let effect = new WeaponEffect(battleview.arena, new Ship(), Target.newFromShip(ship), new Equipment());
|
||||
|
||||
let mock_shield_impact = spyOn(effect, "shieldImpactEffect").and.stub();
|
||||
let mock_hull_impact = spyOn(effect, "hullImpactEffect").and.stub();
|
||||
|
@ -35,12 +38,12 @@ module TS.SpaceTac.UI.Specs {
|
|||
expect(layer.children[0] instanceof Phaser.Particles.Arcade.Emitter).toBe(true);
|
||||
expect(mock_shield_impact).toHaveBeenCalledTimes(0);
|
||||
expect(mock_hull_impact).toHaveBeenCalledTimes(1);
|
||||
expect(mock_hull_impact).toHaveBeenCalledWith(jasmine.objectContaining({ x: 10, y: 0 }), jasmine.objectContaining({ x: 50, y: 30 }), 100, 800);
|
||||
expect(mock_hull_impact).toHaveBeenCalledWith(jasmine.objectContaining({ x: 0, y: 0 }), jasmine.objectContaining({ x: 50, y: 30 }), 100, 800);
|
||||
|
||||
TestTools.setShipHP(ship, 10, 10);
|
||||
sprite.shield.setValue(10, 10);
|
||||
effect.gunEffect();
|
||||
expect(mock_shield_impact).toHaveBeenCalledTimes(1);
|
||||
expect(mock_shield_impact).toHaveBeenCalledWith(jasmine.objectContaining({ x: 10, y: 0 }), jasmine.objectContaining({ x: 50, y: 30 }), 100, 800, true);
|
||||
expect(mock_shield_impact).toHaveBeenCalledWith(jasmine.objectContaining({ x: 0, y: 0 }), jasmine.objectContaining({ x: 50, y: 30 }), 100, 800, true);
|
||||
expect(mock_hull_impact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,44 +7,44 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual effects renderer for weapons.
|
||||
*/
|
||||
export class WeaponEffect {
|
||||
// Link to game
|
||||
private ui: MainUI;
|
||||
private ui: MainUI
|
||||
|
||||
// Link to arena
|
||||
private arena: Arena;
|
||||
private arena: Arena
|
||||
|
||||
// Display group in which to display the visual effects
|
||||
private layer: Phaser.Group;
|
||||
private layer: Phaser.Group
|
||||
|
||||
// Firing ship
|
||||
private source: Target;
|
||||
private ship: Ship
|
||||
private source: IArenaLocation
|
||||
|
||||
// Targetted ship
|
||||
private destination: Target;
|
||||
// Target (ship or space)
|
||||
private target: Target
|
||||
private destination: IArenaLocation
|
||||
|
||||
// Weapon used
|
||||
private weapon: Equipment;
|
||||
private weapon: Equipment
|
||||
|
||||
// Effect in use
|
||||
private effect: Function;
|
||||
private effect: Function
|
||||
|
||||
constructor(arena: Arena, source: Target, destination: Target, weapon: Equipment) {
|
||||
constructor(arena: Arena, ship: Ship, target: Target, weapon: Equipment) {
|
||||
this.ui = arena.getGame();
|
||||
this.arena = arena;
|
||||
this.layer = arena.layer_weapon_effects;
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
this.ship = ship;
|
||||
this.target = target;
|
||||
this.weapon = weapon;
|
||||
this.effect = this.getEffectForWeapon(weapon.code);
|
||||
|
||||
this.source = this.getCoords(Target.newFromShip(this.ship));
|
||||
this.destination = this.getCoords(this.target);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,6 +60,22 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of a target (as of current view, not as actual game state)
|
||||
*/
|
||||
getCoords(target: Target): IArenaLocation {
|
||||
if (target.ship) {
|
||||
let sprite = this.arena.findShipSprite(target.ship);
|
||||
if (sprite) {
|
||||
return sprite;
|
||||
} else {
|
||||
return target.ship.location;
|
||||
}
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the function that will be called to start the visual effect
|
||||
*/
|
||||
|
@ -75,7 +91,7 @@ module TS.SpaceTac.UI {
|
|||
/**
|
||||
* Add a shield impact effect on a ship
|
||||
*/
|
||||
shieldImpactEffect(from: Point, ship: Point, 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 effect = new Phaser.Image(this.ui, ship.x, ship.y, "battle-weapon-shield-impact");
|
||||
|
@ -108,7 +124,7 @@ module TS.SpaceTac.UI {
|
|||
/**
|
||||
* Add a hull impact effect on a ship
|
||||
*/
|
||||
hullImpactEffect(from: Point, ship: Point, 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 emitter = this.ui.add.emitter(ship.x + Math.cos(angle) * 10, ship.y + Math.sin(angle) * 10, 30);
|
||||
|
@ -132,10 +148,10 @@ module TS.SpaceTac.UI {
|
|||
|
||||
let missile = new Phaser.Image(this.ui, this.source.x, this.source.y, "battle-weapon-default");
|
||||
missile.anchor.set(0.5, 0.5);
|
||||
missile.rotation = this.source.getAngleTo(this.destination);
|
||||
missile.rotation = arenaAngle(this.source, this.destination);
|
||||
this.layer.addChild(missile);
|
||||
|
||||
let blast_radius = this.weapon.action.getBlastRadius(this.source.ship || new Ship());
|
||||
let blast_radius = this.weapon.action.getBlastRadius(this.ship);
|
||||
|
||||
let tween = this.ui.tweens.create(missile);
|
||||
tween.to({ x: this.destination.x, y: this.destination.y }, 1000);
|
||||
|
@ -159,12 +175,12 @@ module TS.SpaceTac.UI {
|
|||
tween.start();
|
||||
|
||||
if (blast_radius > 0) {
|
||||
let ships = this.arena.getBattle().collectShipsInCircle(this.destination, blast_radius, true);
|
||||
ships.forEach(ship => {
|
||||
if (ship.getValue("shield") > 0) {
|
||||
this.shieldImpactEffect(this.destination, { x: ship.arena_x, y: ship.arena_y }, 1200, 800);
|
||||
let ships = this.arena.getShipsInCircle(new ArenaCircleArea());
|
||||
ships.forEach(sprite => {
|
||||
if (sprite.getValue("shield") > 0) {
|
||||
this.shieldImpactEffect(this.target, sprite, 1200, 800);
|
||||
} else {
|
||||
this.hullImpactEffect(this.destination, { x: ship.arena_x, y: ship.arena_y }, 1200, 400);
|
||||
this.hullImpactEffect(this.target, sprite, 1200, 400);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -178,13 +194,13 @@ module TS.SpaceTac.UI {
|
|||
gunEffect(): number {
|
||||
this.ui.audio.playOnce("battle-weapon-bullets");
|
||||
|
||||
let has_shield = this.destination.ship && this.destination.ship.getValue("shield") > 0;
|
||||
let sprite = this.target.ship ? this.arena.findShipSprite(this.target.ship) : null;
|
||||
let has_shield = sprite && sprite.getValue("shield") > 0;
|
||||
|
||||
var dx = this.destination.x - this.source.x;
|
||||
var dy = this.destination.y - this.source.y;
|
||||
var angle = Math.atan2(dy, dx);
|
||||
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||
var emitter = new Phaser.Particles.Arcade.Emitter(this.ui, this.source.x + Math.cos(angle) * 35, this.source.y + Math.sin(angle) * 35, 10);
|
||||
var angle = arenaAngle(this.source, this.target);
|
||||
var distance = arenaDistance(this.source, this.target);
|
||||
var emitter = new Phaser.Particles.Arcade.Emitter(this.ui,
|
||||
this.source.x + Math.cos(angle) * 35, this.source.y + Math.sin(angle) * 35, 10);
|
||||
var speed = 2000;
|
||||
emitter.particleClass = BulletParticle;
|
||||
emitter.gravity = 0;
|
||||
|
@ -201,9 +217,9 @@ module TS.SpaceTac.UI {
|
|||
this.layer.addChild(emitter);
|
||||
|
||||
if (has_shield) {
|
||||
this.shieldImpactEffect(this.source, this.destination, 100, 800, true);
|
||||
this.shieldImpactEffect(this.source, this.target, 100, 800, true);
|
||||
} else {
|
||||
this.hullImpactEffect(this.source, this.destination, 100, 800);
|
||||
this.hullImpactEffect(this.source, this.target, 100, 800);
|
||||
}
|
||||
|
||||
return 1000;
|
||||
|
|
|
@ -69,7 +69,9 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
}
|
||||
|
||||
// Set current value
|
||||
/**
|
||||
* Set the current value, and maximal value
|
||||
*/
|
||||
setValue(current: number, maximal: number = -1) {
|
||||
this.current = current > 0 ? current : 0;
|
||||
if (maximal >= 0) {
|
||||
|
@ -84,7 +86,16 @@ module TS.SpaceTac.UI {
|
|||
this.update();
|
||||
}
|
||||
|
||||
// Get the proportional (in 0.0-1.0 range) value
|
||||
/**
|
||||
* Get current raw value
|
||||
*/
|
||||
getValue(): number {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the proportional (in 0.0-1.0 range) value
|
||||
*/
|
||||
getProportionalValue(): number {
|
||||
return this.proportional;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue