1
0
Fork 0

Added laser effect

This commit is contained in:
Michaël Lemaire 2017-10-03 22:56:25 +02:00
parent 1936cfef8d
commit e34372891e
15 changed files with 185 additions and 65 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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