1
0
Fork 0

Fixed weapon effect originating from wrong ship location

This commit is contained in:
Michaël Lemaire 2017-06-23 00:37:38 +02:00
parent c5c141edcb
commit 20377b49e7
12 changed files with 173 additions and 68 deletions

10
TODO
View file

@ -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

View 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);
});
});
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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})`;
}

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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
*

View file

@ -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;

View file

@ -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);
});
});

View file

@ -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;

View file

@ -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;
}