Added laser effect
This commit is contained in:
parent
1936cfef8d
commit
e34372891e
2
TODO.md
2
TODO.md
|
@ -77,8 +77,8 @@ Ships models and equipments
|
|||
Artificial Intelligence
|
||||
-----------------------
|
||||
|
||||
* Work on a simple representation of battle state, simulating effects on it, evaluating it, and only reevaluating parts that changed
|
||||
* Use a first batch of producers, and only if no "good" move has been found, go on with some infinite producers
|
||||
* Evaluate buffs/debuffs
|
||||
* Abandon fight if the AI judges there is no hope of victory
|
||||
* Add combination of random small move and actual maneuver, as producer
|
||||
* New duel page with producers/evaluators tweaking
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
|
@ -15,7 +15,7 @@
|
|||
viewBox="0 0 128 128"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.0 r15299"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="weaponeffects.svg"
|
||||
inkscape:export-filename="/tmp/image.png"
|
||||
inkscape:export-xdpi="96.000008"
|
||||
|
@ -198,6 +198,19 @@
|
|||
r="30.446428"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0997067,0,0,1.0997067,-4.1425418,-8.3253595)" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter5826"
|
||||
x="-0.047145694"
|
||||
width="1.0942914"
|
||||
y="-0.98874992"
|
||||
height="2.9775">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="7.6811109"
|
||||
id="feGaussianBlur5828" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
|
@ -206,11 +219,11 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="7.9195959"
|
||||
inkscape:cx="61.820614"
|
||||
inkscape:cy="63.238329"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="60.541466"
|
||||
inkscape:cy="49.925567"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer5"
|
||||
inkscape:current-layer="layer7"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:measure-start="35.2291,59.599"
|
||||
|
@ -406,7 +419,8 @@
|
|||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="Stasis">
|
||||
inkscape:label="Stasis"
|
||||
style="display:none">
|
||||
<circle
|
||||
style="fill:url(#radialGradient4556);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.10143852;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4548"
|
||||
|
@ -432,4 +446,20 @@
|
|||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer7"
|
||||
inkscape:label="Laser">
|
||||
<rect
|
||||
transform="matrix(1.0666667,-1.4605933e-5,6.2967195e-5,0.24742509,-63.824421,36.000686)"
|
||||
y="117.72057"
|
||||
x="-62.353645"
|
||||
height="18.644417"
|
||||
width="391.01483"
|
||||
id="rect5544"
|
||||
style="display:inline;fill:#f1a581;fill-opacity:1;fill-rule:evenodd;stroke:#ff2300;stroke-width:7.31692886;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5826)"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/battle/effects/laser.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 88 KiB |
|
@ -36,7 +36,7 @@
|
|||
"typescript": "^2.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"jasmine-core": "2.8.0",
|
||||
"jasmine-core": "2.5.2",
|
||||
"parse": "^1.9.2",
|
||||
"phaser": "^2.6.2",
|
||||
"phaser-plugin-scene-graph": "^1.0.4"
|
||||
|
|
|
@ -11,5 +11,10 @@ module TK.SpaceTac.Specs {
|
|||
expect(angularDistance(0.5, -0.5)).toBe(-1.0);
|
||||
expect(angularDistance(0.5, -0.3 - Math.PI * 4)).toBeCloseTo(-0.8, 0.000001);
|
||||
})
|
||||
|
||||
it("converts between degrees and radians", function () {
|
||||
expect(degrees(Math.PI / 2)).toBeCloseTo(90, 0.000001);
|
||||
expect(radians(45)).toBeCloseTo(Math.PI / 4, 0.000001);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -81,4 +81,18 @@ module TK.SpaceTac {
|
|||
let dist = arenaDistance(loc1, loc2);
|
||||
return border_inclusive ? (dist <= loc2.radius) : (dist < loc2.radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert radians angle to degrees
|
||||
*/
|
||||
export function degrees(angle: number): number {
|
||||
return angle * 180 / Math.PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert degrees angle to radians
|
||||
*/
|
||||
export function radians(angle: number): number {
|
||||
return angle * Math.PI / 180;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ module TK.SpaceTac.UI {
|
|||
this.loadImage("battle/actionbar/actions-background.png");
|
||||
this.loadSheet("battle/actionbar/button-menu.png", 79, 132);
|
||||
this.loadImage("battle/arena/background.png");
|
||||
this.loadImage("battle/arena/blast.png");
|
||||
this.loadImage("battle/shiplist/background.png");
|
||||
this.loadImage("battle/shiplist/item-background.png");
|
||||
this.loadImage("battle/shiplist/damage.png");
|
||||
|
|
|
@ -141,17 +141,6 @@ module TK.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
|
||||
*/
|
||||
|
|
|
@ -133,6 +133,10 @@ module TK.SpaceTac.UI {
|
|||
this.battleview.log_processor.registerForShip(ship, event => this.processShipLogEvent(event));
|
||||
}
|
||||
|
||||
jasmineToString(): string {
|
||||
return `ArenaShip ${this.ship.jasmineToString()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a battle log event
|
||||
*/
|
||||
|
|
|
@ -161,8 +161,8 @@ module TK.SpaceTac.UI {
|
|||
}
|
||||
|
||||
if (radius) {
|
||||
area.lineStyle(2, 0x90481e, 0.6);
|
||||
area.beginFill(0x90481e, 0.2);
|
||||
area.lineStyle(2, color, 0.6);
|
||||
area.beginFill(color, 0.2);
|
||||
if (angle) {
|
||||
area.arc(0, 0, radius, angle, -angle, true);
|
||||
} else {
|
||||
|
|
|
@ -33,31 +33,45 @@ module TK.SpaceTac.UI.Specs {
|
|||
let battleview = testgame.battleview;
|
||||
battleview.timer = new Timer();
|
||||
|
||||
let ship = nn(battleview.battle.playing_ship);
|
||||
let effect = new WeaponEffect(battleview.arena, new Ship(), Target.newFromShip(ship), new Equipment());
|
||||
effect.gunEffect();
|
||||
|
||||
let layer = battleview.arena.layer_weapon_effects;
|
||||
expect(layer.children.length).toBe(1);
|
||||
expect(layer.children[0] instanceof Phaser.Particles.Arcade.Emitter).toBe(true);
|
||||
});
|
||||
|
||||
it("displays shield and hull effect on impacted ships", function () {
|
||||
let battleview = testgame.battleview;
|
||||
battleview.timer = new Timer();
|
||||
|
||||
let ship = nn(battleview.battle.playing_ship);
|
||||
let sprite = nn(battleview.arena.findShipSprite(ship));
|
||||
ship.setArenaPosition(50, 30);
|
||||
sprite.position.set(50, 30);
|
||||
sprite.hull_bar.setValue(10, 10);
|
||||
sprite.shield_bar.setValue(0, 10);
|
||||
let effect = new WeaponEffect(battleview.arena, new Ship(), Target.newFromShip(ship), new Equipment());
|
||||
|
||||
let weapon = new Equipment();
|
||||
weapon.action = new TriggerAction(weapon, [new DamageEffect()], 1, 500);
|
||||
spyOn(weapon.action, "getImpactedShips").and.returnValue([ship]);
|
||||
|
||||
let effect = new WeaponEffect(battleview.arena, new Ship(), Target.newFromShip(ship), weapon);
|
||||
spyOn(effect, "getEffectForWeapon").and.returnValue(() => 100);
|
||||
|
||||
let mock_shield_impact = spyOn(effect, "shieldImpactEffect").and.stub();
|
||||
let mock_hull_impact = spyOn(effect, "hullImpactEffect").and.stub();
|
||||
|
||||
effect.gunEffect();
|
||||
|
||||
let layer = battleview.arena.layer_weapon_effects;
|
||||
expect(layer.children.length).toBe(1);
|
||||
|
||||
expect(layer.children[0] instanceof Phaser.Particles.Arcade.Emitter).toBe(true);
|
||||
effect.start();
|
||||
expect(mock_shield_impact).toHaveBeenCalledTimes(0);
|
||||
expect(mock_hull_impact).toHaveBeenCalledTimes(1);
|
||||
expect(mock_hull_impact).toHaveBeenCalledWith(jasmine.objectContaining({ x: 0, 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 }), 40, 400);
|
||||
|
||||
sprite.shield_bar.setValue(10, 10);
|
||||
effect.gunEffect();
|
||||
effect.start();
|
||||
expect(mock_shield_impact).toHaveBeenCalledTimes(1);
|
||||
expect(mock_shield_impact).toHaveBeenCalledWith(jasmine.objectContaining({ x: 0, 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 }), 40, 800, false);
|
||||
expect(mock_hull_impact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -68,7 +82,7 @@ module TK.SpaceTac.UI.Specs {
|
|||
let effect = new WeaponEffect(battleview.arena, new Ship(), Target.newFromLocation(50, 50), new Equipment());
|
||||
|
||||
effect.gunEffect();
|
||||
checkEmitters("gun effect started", 2);
|
||||
checkEmitters("gun effect started", 1);
|
||||
fastForward(6000);
|
||||
checkEmitters("gun effect ended", 0);
|
||||
|
||||
|
@ -77,5 +91,31 @@ module TK.SpaceTac.UI.Specs {
|
|||
fastForward(8500);
|
||||
checkEmitters("hull effect ended", 0);
|
||||
});
|
||||
|
||||
it("adds a laser effect", function () {
|
||||
let battleview = testgame.battleview;
|
||||
battleview.timer = new Timer();
|
||||
|
||||
let effect = new WeaponEffect(battleview.arena, new Ship(), Target.newFromLocation(31, 49), new Equipment());
|
||||
|
||||
let result = effect.angularLaser({ x: 20, y: 30 }, 300, Math.PI / 4, -Math.PI / 2, 5);
|
||||
expect(result).toBe(200);
|
||||
|
||||
let layer = battleview.arena.layer_weapon_effects;
|
||||
expect(layer.children.length).toBe(1);
|
||||
expect(layer.children[0] instanceof Phaser.Image).toBe(true, "is image");
|
||||
let image = <Phaser.Image>layer.children[0];
|
||||
expect(image.name).toEqual("battle-effects-laser");
|
||||
expect(image.width).toBe(300);
|
||||
expect(image.x).toEqual(20);
|
||||
expect(image.y).toEqual(30);
|
||||
expect(image.rotation).toBeCloseTo(Math.PI / 4, 0.000001);
|
||||
|
||||
let values = battleview.animations.simulate(image, "rotation", 4, result);
|
||||
expect(values[0]).toBeCloseTo(Math.PI / 4, 0.000001);
|
||||
expect(values[1]).toBeCloseTo(0, 0.000001);
|
||||
expect(values[2]).toBeCloseTo(-Math.PI / 4, 0.000001);
|
||||
expect(values[3]).toBeCloseTo(-Math.PI / 2, 0.000001);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -35,9 +35,6 @@ module TK.SpaceTac.UI {
|
|||
// Weapon used
|
||||
private weapon: Equipment
|
||||
|
||||
// Effect in use
|
||||
private effect: Function
|
||||
|
||||
constructor(arena: Arena, ship: Ship, target: Target, weapon: Equipment) {
|
||||
this.ui = arena.game;
|
||||
this.arena = arena;
|
||||
|
@ -47,7 +44,6 @@ module TK.SpaceTac.UI {
|
|||
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);
|
||||
|
@ -59,11 +55,45 @@ module TK.SpaceTac.UI {
|
|||
* Returns the duration of the effect.
|
||||
*/
|
||||
start(): number {
|
||||
if (this.effect) {
|
||||
return this.effect();
|
||||
} else {
|
||||
return 0;
|
||||
// Fire effect
|
||||
let effect = this.getEffectForWeapon(this.weapon.code, this.weapon.action);
|
||||
let duration = effect();
|
||||
|
||||
// Damage effect
|
||||
let action = this.weapon.action;
|
||||
if (action instanceof TriggerAction && any(action.effects, effect => effect instanceof DamageEffect)) {
|
||||
let ships = action.getImpactedShips(this.ship, this.target, this.source);
|
||||
let source = action.blast ? this.target : this.source;
|
||||
let damage_duration = this.damageEffect(source, ships, duration * 0.4, this.weapon.code == "gatlinggun");
|
||||
duration = Math.max(duration, damage_duration);
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a damage effect on ships impacted by a weapon
|
||||
*/
|
||||
damageEffect(source: IArenaLocation, ships: Ship[], base_delay = 0, shield_flares = false): number {
|
||||
let duration = 0;
|
||||
|
||||
// TODO For each ship, delay should depend on fire effect animation
|
||||
let delay = base_delay;
|
||||
|
||||
ships.forEach(ship => {
|
||||
let sprite = this.arena.findShipSprite(ship);
|
||||
if (sprite) {
|
||||
if (sprite.getValue("shield") > 0) {
|
||||
this.shieldImpactEffect(source, sprite, delay, 800, shield_flares);
|
||||
duration = Math.max(duration, delay + 800);
|
||||
} else {
|
||||
this.hullImpactEffect(source, sprite, delay, 400);
|
||||
duration = Math.max(duration, delay + 400);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,12 +115,17 @@ module TK.SpaceTac.UI {
|
|||
/**
|
||||
* Get the function that will be called to start the visual effect
|
||||
*/
|
||||
getEffectForWeapon(weapon: string): Function {
|
||||
getEffectForWeapon(weapon: string, action: BaseAction | null): () => number {
|
||||
switch (weapon) {
|
||||
case "gatlinggun":
|
||||
return this.gunEffect.bind(this);
|
||||
return () => this.gunEffect();
|
||||
case "prokhorovlaser":
|
||||
let trigger = <TriggerAction>nn(action);
|
||||
let angle = arenaAngle(this.source, this.target);
|
||||
let dangle = radians(trigger.angle) * 0.5;
|
||||
return () => this.angularLaser(this.source, trigger.range, angle - dangle, angle + dangle);
|
||||
default:
|
||||
return this.defaultEffect.bind(this);
|
||||
return () => this.defaultEffect();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,22 +220,28 @@ module TK.SpaceTac.UI {
|
|||
});
|
||||
tween.start();
|
||||
|
||||
if (blast_radius > 0 && this.weapon.action instanceof TriggerAction) {
|
||||
if (any(this.weapon.action.effects, effect => effect instanceof DamageEffect)) {
|
||||
let ships = this.arena.getShipsInCircle(new ArenaCircleArea(this.destination.x, this.destination.y, blast_radius));
|
||||
ships.forEach(sprite => {
|
||||
if (sprite.getValue("shield") > 0) {
|
||||
this.shieldImpactEffect(this.target, sprite, 1200, 800);
|
||||
} else {
|
||||
this.hullImpactEffect(this.target, sprite, 1200, 400);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return projectile_duration + (blast_radius ? 1500 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Laser effect, scanning from one angle to the other
|
||||
*/
|
||||
angularLaser(source: IArenaLocation, radius: number, start_angle: number, end_angle: number, speed = 1): number {
|
||||
let duration = 1000 / speed;
|
||||
|
||||
let laser = this.view.newImage("battle-effects-laser", source.x, source.y);
|
||||
laser.anchor.set(0, 0.5);
|
||||
laser.rotation = start_angle;
|
||||
laser.scale.set(radius / laser.width);
|
||||
this.layer.add(laser);
|
||||
|
||||
let tween = this.view.tweens.create(laser).to({ rotation: end_angle }, duration);
|
||||
tween.onComplete.addOnce(() => laser.destroy());
|
||||
tween.start();
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submachine gun effect (quick chain of small bullets)
|
||||
*/
|
||||
|
@ -230,12 +271,6 @@ module TK.SpaceTac.UI {
|
|||
this.layer.add(emitter);
|
||||
this.timer.schedule(5000, () => emitter.destroy());
|
||||
|
||||
if (has_shield) {
|
||||
this.shieldImpactEffect(this.source, this.target, 100, 800, true);
|
||||
} else {
|
||||
this.hullImpactEffect(this.source, this.target, 100, 800);
|
||||
}
|
||||
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ module TK.SpaceTac.UI {
|
|||
simulate(obj: any, property: string, points = 5, duration = 1000): number[] {
|
||||
let tween = first(this.tweens.getAll().concat((<any>this.tweens)._add), tween => tween.target === obj && !tween.pendingDelete);
|
||||
if (tween) {
|
||||
return [obj[property]].concat(tween.generateData(points - 1).map(data => data[property]));
|
||||
return [obj[property]].concat(tween.generateData(1000 * (points - 1) / duration).map(data => data[property]));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -1315,7 +1315,11 @@ istanbul@0.4.5, istanbul@^0.4.0:
|
|||
which "^1.1.1"
|
||||
wordwrap "^1.0.0"
|
||||
|
||||
jasmine-core@2.8.0, jasmine-core@~2.8.0:
|
||||
jasmine-core@2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297"
|
||||
|
||||
jasmine-core@~2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
|
||||
|
||||
|
|
Loading…
Reference in New Issue