1
0
Fork 0

Added RepelEffect and use it in Gravit Shield action

This commit is contained in:
Michaël Lemaire 2017-08-17 19:51:22 +02:00
parent 9f6c6bfa09
commit 0f413d334c
29 changed files with 590 additions and 240 deletions

10
.vscode/settings.json vendored
View file

@ -2,5 +2,13 @@
"typescript.tsdk": "./node_modules/typescript/lib",
"editor.rulers": [
120
]
],
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"node_modules": true
}
}

View file

@ -37,6 +37,7 @@ Battle
* Add a voluntary retreat option
* Remove dead ships from ship list and play order
* Add quick animation of playing ship indicator, on ship change
* Display a hint when a move-fire simulation failed (cannot enter exclusion area for example)
* Display effects description instead of attribute changes
* Display radius and power usage hints for area effects on action icon hover + add confirmation ?
* 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)
@ -57,14 +58,13 @@ Ships models and equipments
* Add permanent effects and actions to ship models
* Add critical hit/miss
* Add damage over time effect (tricky to make intuitive)
* Safety margin should only be applied on ships, not arena borders (which should be fixed)
* Move distance should increase with maneuvrability
* Chance to hit should increase with precision
* Add actions with cost dependent of distance (like current move actions)
* Add "cone" targetting
* Add disc targetting (for some jump move actions)
* Add "chain" effects
* Add "forced move" effects (like a gravity well)
* RepelEffect should apply on ships in a good order (distance decreasing)
* Add hull points to drones and make them take area damage
* "Shield Transfer" has no quality offsets

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -16,13 +16,25 @@
version="1.1"
inkscape:version="0.92.1 r15371"
sodipodi:docname="actions.svg"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/equipment/voidhawkengine.png"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/equipment/gravitshield.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
viewBox="0 0 256 256"
enable-background="new">
<defs
id="defs4">
<linearGradient
id="linearGradient5049"
inkscape:collect="always">
<stop
id="stop5045"
offset="0"
style="stop-color:#b5c3c6;stop-opacity:1" />
<stop
id="stop5047"
offset="1"
style="stop-color:#4d5959;stop-opacity:1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5473">
@ -63,23 +75,11 @@
offset="1"
id="stop4864" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5046">
<stop
style="stop-color:#6f8185;stop-opacity:1"
offset="0"
id="stop5042" />
<stop
style="stop-color:#222a2c;stop-opacity:1"
offset="1"
id="stop5044" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5038">
<stop
style="stop-color:#4f5c64;stop-opacity:1;"
style="stop-color:#8d9aa2;stop-opacity:1"
offset="0"
id="stop5034" />
<stop
@ -756,22 +756,22 @@
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter5260"
x="-0.1224"
width="1.2448"
y="-0.1224"
height="1.2448">
x="-0.5"
width="2"
y="-0.5"
height="2">
<feGaussianBlur
stdDeviation="1.4199999999999999"
id="feGaussianBlur5264" />
<feBlend
inkscape:collect="always"
mode="hard-light"
in2="BackgroundImage"
id="feBlend5262" />
<feGaussianBlur
stdDeviation="1.4194335"
id="feGaussianBlur5264" />
</filter>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5046"
xlink:href="#linearGradient5049"
id="radialGradient5289"
gradientUnits="userSpaceOnUse"
cx="187.13762"
@ -779,26 +779,6 @@
fx="187.13762"
fy="49.451832"
r="13.916015" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5046"
id="radialGradient5299"
gradientUnits="userSpaceOnUse"
cx="187.13762"
cy="49.451832"
fx="187.13762"
fy="49.451832"
r="13.916015" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5046"
id="radialGradient5305"
gradientUnits="userSpaceOnUse"
cx="187.13762"
cy="49.451832"
fx="187.13762"
fy="49.451832"
r="13.916015" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4866"
@ -1082,10 +1062,10 @@
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="2.56"
inkscape:cx="158.76788"
inkscape:cy="151.25188"
inkscape:cx="130.38994"
inkscape:cy="132.56959"
inkscape:document-units="px"
inkscape:current-layer="g5519"
inkscape:current-layer="g5022"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1030"
@ -2215,7 +2195,7 @@
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#38454d;stroke-width:8.99291325;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
style="display:none"
style="display:inline"
inkscape:label="GravitShield"
id="g5022"
inkscape:groupmode="layer">
@ -2236,7 +2216,7 @@
style="fill:url(#radialGradient5289);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.93749994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter5260)" />
</g>
<g
id="g4856">
id="g5114">
<use
transform="translate(0,-98.321274)"
height="100%"
@ -2253,34 +2233,30 @@
xlink:href="#g5267"
y="0"
x="0" />
<g
id="g5297"
transform="translate(-59.137619,176.86944)">
<circle
r="13.916015"
cy="49.451832"
cx="187.13762"
id="circle5295"
style="fill:url(#radialGradient5299);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.93749994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter5260)" />
</g>
<g
id="g5303"
transform="translate(-157.45889,78.548168)">
<circle
r="13.916015"
cy="49.451832"
cx="187.13762"
id="circle5301"
style="fill:url(#radialGradient5305);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.93749994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter5260)" />
</g>
<use
transform="rotate(-180,128,128)"
x="0"
y="0"
xlink:href="#use5291"
id="use5073"
width="100%"
height="100%" />
<use
x="0"
y="0"
xlink:href="#use5293"
id="use5075"
width="100%"
height="100%"
transform="rotate(-180,128,128)" />
</g>
<use
x="0"
y="0"
xlink:href="#g4856"
id="use4858"
xlink:href="#g5114"
width="100%"
height="100%"
id="use5116"
transform="rotate(45,128,128)" />
</g>
<g
@ -2579,7 +2555,7 @@
inkscape:groupmode="layer"
id="g4919"
inkscape:label="VoidhawkEngine"
style="display:inline">
style="display:none">
<g
id="g5449"
transform="translate(1.6322846,-21.706596)">

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View file

@ -170,7 +170,8 @@
y1="143.13321"
x2="259.57538"
y2="95.291969"
gradientUnits="userSpaceOnUse" />
gradientUnits="userSpaceOnUse"
gradientTransform="translate(6.0768709,2.3920799)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5286"
@ -180,7 +181,7 @@
fx="201.79222"
fy="102.01662"
r="136.00291"
gradientTransform="matrix(1.9392397,0.081369,-0.00937077,0.22333045,-191.15776,76.134141)"
gradientTransform="matrix(1.9392397,0.081369,-0.00937077,0.22333045,-185.08089,78.526222)"
gradientUnits="userSpaceOnUse" />
<filter
inkscape:collect="always"
@ -289,11 +290,11 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="1838.5705"
inkscape:cy="1007.2957"
inkscape:zoom="0.70000001"
inkscape:cx="921.73937"
inkscape:cy="523.01763"
inkscape:document-units="px"
inkscape:current-layer="layer5"
inkscape:current-layer="layer1"
showgrid="false"
showguides="true"
inkscape:lockguides="false"
@ -413,18 +414,27 @@
id="use4617"
transform="matrix(3.0721311,0,0,3.0721311,-6.6877028,-512.5613)"
width="100%"
height="100%" />
height="100%"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="fill:none;fill-rule:evenodd;stroke:#283646;stroke-width:2.86500001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:15.75749969;stroke-opacity:1;marker-start:url(#Arrow1Send);marker-end:url(#Arrow2Sstart)"
d="M 213.28108,143.75099 H 384.60085"
d="M 208.51855,143.75099 H 379.83832"
id="path5290"
inkscape:connector-curvature="0" />
inkscape:connector-curvature="0"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:6.61458302px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="216.48831"
y="198.11024"
id="text4525"><tspan
id="text4525"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"><tspan
sodipodi:role="line"
id="tspan4523"
x="216.48831"
@ -433,35 +443,47 @@
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.79999924px;line-height:6.61458302px;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, ';letter-spacing:0px;word-spacing:0px;fill:url(#radialGradient5288);fill-opacity:1;stroke:url(#linearGradient5254);stroke-width:0.96499997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="116.71846"
y="132.02577"
id="text4529"><tspan
x="122.79533"
y="134.41782"
id="text4529"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"><tspan
sodipodi:role="line"
id="tspan4527"
x="116.71846"
y="132.02577"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.79999924px;line-height:6.61000013px;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, ';fill:url(#radialGradient5288);fill-opacity:1;stroke:url(#linearGradient5254);stroke-width:0.96499997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">SpaceTac</tspan></text>
x="122.79533"
y="134.41782"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:56.44444656px;line-height:6.61000013px;font-family:DAGGERSQUARE;-inkscape-font-specification:DAGGERSQUARE;fill:url(#radialGradient5288);fill-opacity:1;stroke:url(#linearGradient5254);stroke-width:0.96499997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">SpaceTac</tspan></text>
<ellipse
style="fill:#19232c;fill-opacity:1;stroke:#1a2e44;stroke-width:0.60000002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:59.55590439;stroke-opacity:1"
id="path6915"
cx="299.03043"
cy="143.76476"
rx="75.166008"
ry="3.60095" />
ry="3.60095"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:6.61458302px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#b2b2b2;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="230.68631"
x="227.21475"
y="146.65189"
id="text4550"><tspan
id="text4550"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"><tspan
sodipodi:role="line"
id="tspan4548"
x="230.68631"
x="227.21475"
y="146.65189"
style="fill:#b2b2b2;fill-opacity:1;stroke-width:0.26458332px">a tactical turn-based RPG</tspan></text>
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:DAGGERSQUARE;-inkscape-font-specification:DAGGERSQUARE;fill:#b2b2b2;fill-opacity:1;stroke-width:0.26458332px">a tactical turn-based RPG</tspan></text>
<g
id="g7596"
transform="translate(-1.0583333)">
transform="translate(-1.0583333)"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<circle
r="3.2072344"
cy="160.57437"
@ -490,7 +512,10 @@
id="use7598"
transform="matrix(-1,0,0,1,510.43903,0)"
width="100%"
height="100%" />
height="100%"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<use
x="0"
y="0"
@ -498,7 +523,10 @@
id="use7600"
transform="translate(75.629323)"
width="100%"
height="100%" />
height="100%"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<use
x="0"
y="0"
@ -506,13 +534,19 @@
id="use7603"
transform="translate(0,29.108755)"
width="100%"
height="100%" />
height="100%"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4e535a;stroke-width:2.56500006;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:5.13000018, 5.13000018;stroke-dashoffset:8.46450043;stroke-opacity:0.56470588"
d="M 254,189.71274 V 160.73956"
id="path7605"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
sodipodi:nodetypes="cc"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/menu/title1.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
</g>
<g
inkscape:groupmode="layer"
@ -520,41 +554,38 @@
inkscape:label="Texts"
style="display:inline">
<text
transform="translate(0,-11.249983)"
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:6.61458302px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#529aee;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="46.876923"
y="208.91302"
x="48.484272"
y="197.66304"
id="text4554"><tspan
sodipodi:role="line"
id="tspan4552"
x="46.876923"
y="208.91302"
style="font-size:14.11111069px;fill:#529aee;fill-opacity:1;stroke-width:0.26458332px">New game</tspan></text>
x="48.484272"
y="197.66304"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.11111069px;font-family:DAGGERSQUARE;-inkscape-font-specification:DAGGERSQUARE;fill:#529aee;fill-opacity:1;stroke-width:0.26458332px">New game</tspan></text>
<text
transform="translate(0,-11.249983)"
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#529aee;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="214.49164"
y="209.34711"
x="215.93364"
y="198.09712"
id="text4554-6"><tspan
sodipodi:role="line"
id="tspan4552-7"
x="214.49164"
y="209.34711"
style="font-size:14.11111069px;fill:#529aee;fill-opacity:1;stroke-width:0.26458332px">Load game</tspan></text>
x="215.93364"
y="198.09712"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.11111069px;font-family:DAGGERSQUARE;-inkscape-font-specification:DAGGERSQUARE;fill:#529aee;fill-opacity:1;stroke-width:0.26458332px">Load game</tspan></text>
<text
transform="translate(0,-11.249983)"
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4b8ad4;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="380.26669"
y="209.34711"
x="383.20383"
y="198.09712"
id="text4554-2"><tspan
sodipodi:role="line"
id="tspan4552-74"
x="380.26669"
y="209.34711"
style="font-size:14.11111069px;fill:#4b8ad4;fill-opacity:1;stroke-width:0.26458332px">Quick battle</tspan></text>
x="383.20383"
y="198.09712"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.11111069px;font-family:DAGGERSQUARE;-inkscape-font-specification:DAGGERSQUARE;fill:#4b8ad4;fill-opacity:1;stroke-width:0.26458332px">Quick battle</tspan></text>
</g>
<g
inkscape:groupmode="layer"

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 213 KiB

View file

@ -7,6 +7,9 @@ module TS.SpaceTac {
// Battle outcome, if *ended* is true
outcome: BattleOutcome
// Battle cheats
cheats: BattleCheats
// Statistics
stats: BattleStats
@ -32,6 +35,8 @@ module TS.SpaceTac {
// Size of the battle area
width: number
height: number
border = 50
ship_separation = 100
// Timer to use for scheduled things
timer = Timer.global
@ -51,6 +56,7 @@ module TS.SpaceTac {
this.log = new BattleLog();
this.stats = new BattleStats();
this.cheats = new BattleCheats(this, fleet1.player);
this.fleets.forEach((fleet: Fleet) => {
fleet.setBattle(this);

View file

@ -0,0 +1,33 @@
module TS.SpaceTac.Specs {
describe("BattleCheats", function () {
it("wins a battle", function () {
let battle = Battle.newQuickRandom();
battle.cheats.win();
expect(battle.ended).toBe(true, "ended");
expect(battle.outcome.winner).toBe(battle.fleets[0], "winner");
expect(battle.log.events.filter(event => event instanceof DeathEvent).map(event => event.ship)).toEqual(battle.fleets[1].ships, "all mark dead");
expect(any(battle.fleets[1].ships, ship => !ship.alive)).toBe(false, "all restored");
})
it("loses a battle", function () {
let battle = Battle.newQuickRandom();
battle.cheats.lose();
expect(battle.ended).toBe(true, "ended");
expect(battle.outcome.winner).toBe(battle.fleets[1], "winner");
expect(battle.log.events.filter(event => event instanceof DeathEvent).map(event => event.ship)).toEqual(battle.fleets[0].ships, "all mark dead");
expect(any(battle.fleets[0].ships, ship => !ship.alive)).toBe(false, "all restored");
})
it("adds an equipment", function () {
let battle = new Battle();
battle.playing_ship = new Ship();
battle.playing_ship.upgradeSkill("skill_materials");
expect(battle.playing_ship.listEquipment()).toEqual([]);
battle.cheats.equip("Iron Hull");
expect(battle.playing_ship.listEquipment()).toEqual([<any>jasmine.objectContaining({name: "Iron Hull", level: 1})]);
})
})
}

58
src/core/BattleCheats.ts Normal file
View file

@ -0,0 +1,58 @@
module TS.SpaceTac {
/**
* Cheat helpers for current battle
*
* May be used from the console to help development
*/
export class BattleCheats {
battle: Battle
player: Player
constructor(battle: Battle, player: Player) {
this.battle = battle;
this.player = player;
}
/**
* Make player win the current battle
*/
win(): void {
iforeach(this.battle.iships(), ship => {
if (ship.fleet.player != this.player) {
ship.setDead();
}
});
this.battle.endBattle(this.player.fleet);
}
/**
* Make player lose the current battle
*/
lose(): void {
iforeach(this.battle.iships(), ship => {
if (ship.fleet.player == this.player) {
ship.setDead();
}
});
this.battle.endBattle(first(this.battle.fleets, fleet => fleet.player != this.player));
}
/**
* Add an equipment to current playing ship
*/
equip(name: string): void {
let ship = this.battle.playing_ship;
if (ship) {
let generator = new LootGenerator();
generator.setTemplateFilter(template => template.name == name);
let equipment = generator.generateHighest(ship.skills);
if (equipment) {
let slot_type = nn(equipment.slot_type);
let slot = ship.getFreeSlot(slot_type) || ship.addSlot(slot_type);
slot.attach(equipment);
}
}
}
}
}

View file

@ -30,6 +30,13 @@ module TS.SpaceTac {
this.duration = base_duration;
}
/**
* Return the current location of the drone
*/
get location(): ArenaLocation {
return new ArenaLocation(this.x, this.y);
}
/**
* Get a textual description of this drone
*/

View file

@ -0,0 +1,43 @@
module TS.SpaceTac.Specs {
describe("ExclusionAreas", function () {
it("constructs from a ship or battle", function () {
let battle = new Battle();
battle.border = 17;
battle.ship_separation = 31;
let ship1 = battle.fleets[0].addShip();
ship1.setArenaPosition(12, 5);
let ship2 = battle.fleets[1].addShip();
ship2.setArenaPosition(43, 89);
let exclusion = ExclusionAreas.fromBattle(battle);
expect(exclusion.hard_border).toEqual(17);
expect(exclusion.effective_obstacle).toEqual(31);
expect(exclusion.obstacles).toEqual([new ArenaLocationAngle(12, 5), new ArenaLocationAngle(43, 89)]);
exclusion = ExclusionAreas.fromBattle(battle, [ship1], 120);
expect(exclusion.hard_border).toEqual(17);
expect(exclusion.effective_obstacle).toEqual(120);
expect(exclusion.obstacles).toEqual([new ArenaLocationAngle(43, 89)]);
exclusion = ExclusionAreas.fromBattle(battle, [ship2], 10);
expect(exclusion.hard_border).toEqual(17);
expect(exclusion.effective_obstacle).toEqual(31);
expect(exclusion.obstacles).toEqual([new ArenaLocationAngle(12, 5)]);
exclusion = ExclusionAreas.fromShip(ship1);
expect(exclusion.hard_border).toEqual(17);
expect(exclusion.effective_obstacle).toEqual(31);
expect(exclusion.obstacles).toEqual([new ArenaLocationAngle(43, 89)]);
exclusion = ExclusionAreas.fromShip(ship2, 99);
expect(exclusion.hard_border).toEqual(17);
expect(exclusion.effective_obstacle).toEqual(99);
expect(exclusion.obstacles).toEqual([new ArenaLocationAngle(12, 5)]);
exclusion = ExclusionAreas.fromShip(ship2, 10, false);
expect(exclusion.hard_border).toEqual(17);
expect(exclusion.effective_obstacle).toEqual(31);
expect(exclusion.obstacles).toEqual([new ArenaLocationAngle(12, 5), new ArenaLocationAngle(43, 89)]);
})
})
}

View file

@ -0,0 +1,96 @@
module TS.SpaceTac {
/**
* Helper for working with exclusion areas (areas where a ship cannot go)
*
* There are three types of exclusion:
* - Hard border exclusion, that prevents a ship from being too close to the battle edges
* - Hard obstacle exclusion, that prevents two ships from being too close to each other
* - Soft obstacle exclusion, usually associated with an engine, that prevents a ship from moving too close to others
*/
export class ExclusionAreas {
xmin: number
xmax: number
ymin: number
ymax: number
active: boolean
hard_border = 50
hard_obstacle = 100
effective_obstacle = this.hard_obstacle
obstacles: ArenaLocation[] = []
constructor(width: number, height: number) {
this.xmin = 0;
this.xmax = width - 1;
this.ymin = 0;
this.ymax = height - 1;
this.active = width > 0 && height > 0;
}
/**
* Build an exclusion helper from a battle.
*/
static fromBattle(battle: Battle, ignore_ships: Ship[] = [], soft_distance = 0): ExclusionAreas {
let result = new ExclusionAreas(battle.width, battle.height);
result.hard_border = battle.border;
result.hard_obstacle = battle.ship_separation;
let obstacles = imap(ifilter(battle.iships(), ship => !contains(ignore_ships, ship)), ship => ship.location);
result.configure(imaterialize(obstacles), soft_distance);
return result;
}
/**
* Build an exclusion helper for a ship.
*
* If *ignore_self* is True, the ship will itself not be included in exclusion areas.
*/
static fromShip(ship: Ship, soft_distance = 0, ignore_self = true): ExclusionAreas {
let battle = ship.getBattle();
if (battle) {
return ExclusionAreas.fromBattle(battle, ignore_self ? [ship] : [], soft_distance);
} else {
return new ExclusionAreas(0, 0);
}
}
/**
* Configure the areas for next check calls.
*/
configure(obstacles: ArenaLocation[], soft_distance: number) {
this.obstacles = obstacles;
this.effective_obstacle = Math.max(soft_distance, this.hard_obstacle);
}
/**
* Keep a location outside exclusion areas, when coming from a source.
*
* It will return the furthest location on the [source, location] segment, that is not inside an exclusion
* area.
*/
stopBefore(location: ArenaLocation, source: ArenaLocation): ArenaLocation {
if (!this.active) {
return location;
}
let target = Target.newFromLocation(location.x, location.y);
// Keep out of arena borders
target = target.keepInsideRectangle(this.xmin + this.hard_border, this.ymin + this.hard_border,
this.xmax - this.hard_border, this.ymax - this.hard_border,
source.x, source.y);
// Apply collision prevention
let obstacles = sorted(this.obstacles, (a, b) => cmp(arenaDistance(a, source), arenaDistance(b, source), true));
obstacles.forEach(s => {
let new_target = target.moveOutOfCircle(s.x, s.y, this.effective_obstacle, source.x, source.y);
if (target != new_target && arenaDistance(s, source) < this.effective_obstacle) {
// Already inside the nearest ship's exclusion area
target = Target.newFromLocation(source.x, source.y);
} else {
target = new_target;
}
});
return new ArenaLocation(target.x, target.y);
}
}
}

View file

@ -112,6 +112,7 @@ module TS.SpaceTac.Specs {
let ship1 = battle.fleets[0].addShip();
let moveaction = <MoveAction>nn(simulator.findBestEngine()).action;
moveaction.safety_distance = 30;
battle.ship_separation = 30;
expect(simulator.getApproach(moveaction, Target.newFromLocation(350, 200), 100)).toBe(ApproachSimulationError.NO_MOVE_NEEDED);
expect(simulator.getApproach(moveaction, Target.newFromLocation(400, 200), 100)).toBe(ApproachSimulationError.NO_MOVE_NEEDED);

View file

@ -68,7 +68,8 @@ module TS.SpaceTac {
* Check that a move action can reach a given destination
*/
canMoveTo(action: MoveAction, target: Target): boolean {
return action.checkLocationTarget(this.ship, target) == target;
let checked = action.checkLocationTarget(this.ship, target);
return checked != null && checked.x == target.x && checked.y == target.y;
}
/**
@ -133,7 +134,7 @@ module TS.SpaceTac {
result.move_location = Target.newFromShip(this.ship);
if (action instanceof MoveAction) {
let corrected_target = action.applyReachableRange(this.ship, target, move_margin);
corrected_target = action.applyExclusion(this.ship, corrected_target, move_margin);
corrected_target = action.applyExclusion(this.ship, corrected_target);
if (corrected_target) {
result.need_move = target.getDistanceTo(this.ship.location) > 0;
move_target = corrected_target;

View file

@ -15,7 +15,8 @@ module TS.SpaceTac.Specs {
});
it("moves and computes facing angle", function () {
var ship = new Ship(null, "Test");
let ship = new Ship(null, "Test");
let engine = TestTools.addEngine(ship, 50);
ship.setArenaFacingAngle(0);
ship.setArenaPosition(50, 50);
@ -23,43 +24,49 @@ module TS.SpaceTac.Specs {
expect(ship.arena_y).toEqual(50);
expect(ship.arena_angle).toEqual(0);
ship.moveTo(51, 50);
ship.moveTo(51, 50, engine);
expect(ship.arena_x).toEqual(51);
expect(ship.arena_y).toEqual(50);
expect(ship.arena_angle).toEqual(0);
ship.moveTo(50, 50);
ship.moveTo(50, 50, engine);
expect(ship.arena_angle).toBeCloseTo(3.14159265, 0.00001);
ship.moveTo(51, 51);
ship.moveTo(51, 51, engine);
expect(ship.arena_angle).toBeCloseTo(0.785398, 0.00001);
ship.moveTo(51, 52);
ship.moveTo(51, 52, engine);
expect(ship.arena_angle).toBeCloseTo(1.5707963, 0.00001);
ship.moveTo(52, 52);
ship.moveTo(52, 52, engine);
expect(ship.arena_x).toEqual(52);
expect(ship.arena_y).toEqual(52);
expect(ship.arena_angle).toEqual(0);
ship.moveTo(52, 50);
ship.moveTo(52, 50, engine);
expect(ship.arena_angle).toBeCloseTo(-1.5707963, 0.00001);
ship.moveTo(50, 50);
ship.moveTo(50, 50, engine);
expect(ship.arena_angle).toBeCloseTo(3.14159265, 0.00001);
let battle = new Battle();
battle.fleets[0].addShip(ship);
expect(battle.log.events).toEqual([]);
ship.moveTo(70, 50);
expect(battle.log.events).toEqual([new MoveEvent(ship, new ArenaLocationAngle(50, 50, Math.PI), new ArenaLocationAngle(70, 50, 0))]);
ship.moveTo(70, 50, engine);
expect(battle.log.events).toEqual([new MoveEvent(ship, new ArenaLocationAngle(50, 50, Math.PI), new ArenaLocationAngle(70, 50, 0), engine)]);
battle.log.clear();
ship.rotate(2.1);
expect(battle.log.events).toEqual([
new MoveEvent(ship, new ArenaLocationAngle(50, 50, Math.PI), new ArenaLocationAngle(70, 50, 0)),
new MoveEvent(ship, new ArenaLocationAngle(70, 50, 0), new ArenaLocationAngle(70, 50, 2.1))
]);
battle.log.clear();
ship.moveTo(0, 0, null);
expect(battle.log.events).toEqual([
new MoveEvent(ship, new ArenaLocationAngle(70, 50, 2.1), new ArenaLocationAngle(0, 0, 2.1))
]);
});
it("applies equipment cooldown", function () {

View file

@ -405,20 +405,25 @@ module TS.SpaceTac {
/**
* Rotate the ship in place to face a direction
*/
rotate(angle: number, log = true) {
rotate(angle: number, engine: Equipment | null = null, log = true) {
if (angle != this.arena_angle) {
let start = copy(this.location);
this.setArenaFacingAngle(angle);
if (log) {
this.addBattleEvent(new MoveEvent(this, start, copy(this.location)));
this.addBattleEvent(new MoveEvent(this, start, copy(this.location), engine));
}
}
}
// Move toward a location
// This does not check or consume action points
moveTo(x: number, y: number, log: boolean = true): void {
/**
* Move the ship to another location
*
* This does not check or consume action points, but will update area effects (for this ship and the others).
*
* If *engine* is specified, the facing angle will be updated to simulate an engine maneuver.
*/
moveTo(x: number, y: number, engine: Equipment | null = null, log = true): void {
let dx = x - this.arena_x;
let dy = y - this.arena_y;
if (dx != 0 || dy != 0) {
@ -428,12 +433,15 @@ module TS.SpaceTac {
let old_impacted_ships = area_effects.map(action => action.getAffectedShips(this));
let old_area_effects = this.getActiveEffects().area;
let angle = Math.atan2(dy, dx);
this.setArenaFacingAngle(angle);
if (engine) {
let angle = Math.atan2(dy, dx);
this.setArenaFacingAngle(angle);
}
this.setArenaPosition(x, y);
if (log) {
this.addBattleEvent(new MoveEvent(this, start, copy(this.location)));
this.addBattleEvent(new MoveEvent(this, start, copy(this.location), engine));
}
let new_impacted_ships = area_effects.map(action => action.getAffectedShips(this));

View file

@ -4,12 +4,15 @@ module TS.SpaceTac {
describe("FireWeaponAction", function () {
it("constructs correctly", function () {
let equipment = new Equipment(SlotType.Weapon, "testweapon");
let action = new FireWeaponAction(equipment);
let action = new FireWeaponAction(equipment, 4, 30, 10);
expect(action.code).toEqual("fire-testweapon");
expect(action.name).toEqual("Fire");
expect(action.equipment).toBe(equipment);
expect(action.needs_target).toBe(true);
action = new FireWeaponAction(equipment, 4, 0, 10);
expect(action.needs_target).toBe(false);
});
it("applies effects to alive ships in blast radius", function () {

View file

@ -21,7 +21,7 @@ module TS.SpaceTac {
equipment: Equipment;
constructor(equipment: Equipment, power = 1, range = 0, blast = 0, effects: BaseEffect[] = [], name = "Fire") {
super("fire-" + equipment.code, name, true, equipment);
super("fire-" + equipment.code, name, range > 0, equipment);
this.power = power;
this.range = range;
@ -80,9 +80,14 @@ module TS.SpaceTac {
return result;
}
protected customApply(ship: Ship, target: Target) {
protected customApply(ship: Ship, target: Target | null) {
if (!target) {
// Self-target
target = Target.newFromShip(ship);
}
// Face the target
ship.rotate(Target.newFromShip(ship).getAngleTo(target));
ship.rotate(Target.newFromShip(ship).getAngleTo(target), first(ship.listEquipment(SlotType.Engine), () => true));
// Fire event
ship.addBattleEvent(new FireEvent(ship, this.equipment, target));

View file

@ -83,27 +83,27 @@ module TS.SpaceTac {
var ship = battle.fleets[0].ships[0];
var enemy = battle.fleets[1].ships[0];
TestTools.setShipAP(ship, 100);
ship.setArenaPosition(5, 5);
enemy.setArenaPosition(10, 5);
ship.setArenaPosition(500, 500);
enemy.setArenaPosition(1000, 500);
var action = new MoveAction(new Equipment());
action.distance_per_power = 10;
action.safety_distance = 2;
action.distance_per_power = 1000;
action.safety_distance = 200;
var result = action.checkLocationTarget(ship, Target.newFromLocation(7, 5));
expect(result).toEqual(Target.newFromLocation(7, 5));
var result = action.checkLocationTarget(ship, Target.newFromLocation(700, 500));
expect(result).toEqual(Target.newFromLocation(700, 500));
result = action.checkLocationTarget(ship, Target.newFromLocation(8, 5));
expect(result).toEqual(Target.newFromLocation(8, 5));
result = action.checkLocationTarget(ship, Target.newFromLocation(800, 500));
expect(result).toEqual(Target.newFromLocation(800, 500));
result = action.checkLocationTarget(ship, Target.newFromLocation(9, 5));
expect(result).toEqual(Target.newFromLocation(8, 5));
result = action.checkLocationTarget(ship, Target.newFromLocation(900, 500));
expect(result).toEqual(Target.newFromLocation(800, 500));
result = action.checkLocationTarget(ship, Target.newFromLocation(10, 5));
expect(result).toEqual(Target.newFromLocation(8, 5));
result = action.checkLocationTarget(ship, Target.newFromLocation(1000, 500));
expect(result).toEqual(Target.newFromLocation(800, 500));
result = action.checkLocationTarget(ship, Target.newFromLocation(12, 5));
expect(result).toEqual(Target.newFromLocation(12, 5));
result = action.checkLocationTarget(ship, Target.newFromLocation(1200, 500));
expect(result).toEqual(Target.newFromLocation(1200, 500));
});
it("exclusion radius is applied correctly over two ships", function () {
@ -112,15 +112,15 @@ module TS.SpaceTac {
var enemy1 = battle.fleets[1].ships[0];
var enemy2 = battle.fleets[1].ships[1];
TestTools.setShipAP(ship, 100);
enemy1.setArenaPosition(0, 80);
enemy2.setArenaPosition(0, 100);
enemy1.setArenaPosition(0, 800);
enemy2.setArenaPosition(0, 1000);
var action = new MoveAction(new Equipment());
action.distance_per_power = 1000;
action.safety_distance = 15;
action.safety_distance = 150;
var result = action.checkLocationTarget(ship, Target.newFromLocation(0, 110));
expect(result).toEqual(Target.newFromLocation(0, 65));
var result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1100));
expect(result).toEqual(Target.newFromLocation(0, 650));
});
it("exclusion radius does not make the ship go back", function () {
@ -129,17 +129,17 @@ module TS.SpaceTac {
var enemy1 = battle.fleets[1].ships[0];
var enemy2 = battle.fleets[1].ships[1];
TestTools.setShipAP(ship, 100);
enemy1.setArenaPosition(0, 50);
enemy2.setArenaPosition(0, 80);
enemy1.setArenaPosition(0, 500);
enemy2.setArenaPosition(0, 800);
var action = new MoveAction(new Equipment());
action.distance_per_power = 1000;
action.safety_distance = 60;
action.safety_distance = 600;
let result = action.checkLocationTarget(ship, Target.newFromLocation(0, 100));
let result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1000));
expect(result).toBeNull();
result = action.checkLocationTarget(ship, Target.newFromLocation(0, 140));
expect(result).toEqual(Target.newFromLocation(0, 140));
result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1400));
expect(result).toEqual(Target.newFromLocation(0, 1400));
});
});
}

View file

@ -4,7 +4,7 @@ module TS.SpaceTac {
// Distance allowed for each power point
distance_per_power: number
// Safety distance from other ships and arena borders
// Safety distance from other ships
safety_distance: number
// Equipment cannot be null (engine)
@ -54,31 +54,21 @@ module TS.SpaceTac {
return this.distance_per_power;
}
/**
* Get an exclusion helper for this move action
*/
getExclusionAreas(ship: Ship): ExclusionAreas {
return ExclusionAreas.fromShip(ship, this.safety_distance);
}
/**
* Apply exclusion areas (neer arena borders, or other ships)
*/
applyExclusion(ship: Ship, target: Target, margin = 0.1): Target {
let battle = ship.getBattle();
if (battle) {
// Keep out of arena borders
let border = this.safety_distance * 0.5;
target = target.keepInsideRectangle(border, border,
battle.width - border, battle.height - border,
ship.arena_x, ship.arena_y);
applyExclusion(ship: Ship, target: Target): Target {
let exclusion = this.getExclusionAreas(ship);
// Apply collision prevention
let ships = imaterialize(ifilter(battle.iships(true), s => s !== ship));
ships = ships.sort((a, b) => cmp(a.getDistanceTo(ship), b.getDistanceTo(ship), true));
ships.forEach(s => {
let new_target = target.moveOutOfCircle(s.arena_x, s.arena_y, this.safety_distance, ship.arena_x, ship.arena_y);
if (target != new_target && s.getDistanceTo(ship) < this.safety_distance) {
// Already inside the nearest ship's exclusion area
target = Target.newFromLocation(ship.arena_x, ship.arena_y);
} else {
target = new_target;
}
});
}
let destination = exclusion.stopBefore(new ArenaLocation(target.x, target.y), ship.location);
target = Target.newFromLocation(destination.x, destination.y);
return target;
}
@ -98,7 +88,7 @@ module TS.SpaceTac {
}
protected customApply(ship: Ship, target: Target) {
ship.moveTo(target.x, target.y);
ship.moveTo(target.x, target.y, this.equipment);
}
getEffectsDescription(): string {

View file

@ -0,0 +1,40 @@
module TS.SpaceTac.Specs {
describe("RepelEffect", function () {
it("shows a textual description", function () {
expect(new RepelEffect(34).getDescription()).toEqual("repel ships 34km away");
})
it("repel other ships from a central point", function () {
let battle = new Battle();
let ship1a = battle.fleets[0].addShip();
ship1a.setArenaPosition(100, 100);
let ship1b = battle.fleets[0].addShip();
ship1b.setArenaPosition(250, 100);
let ship2a = battle.fleets[1].addShip();
ship2a.setArenaPosition(100, 280);
let effect = new RepelEffect(12);
effect.applyOnShip(ship1a, ship1a);
effect.applyOnShip(ship1b, ship1a);
effect.applyOnShip(ship2a, ship1a);
expect(ship1a.location).toEqual(new ArenaLocationAngle(100, 100));
expect(ship1b.location).toEqual(new ArenaLocationAngle(262, 100));
expect(ship2a.location).toEqual(new ArenaLocationAngle(100, 292));
})
it("does not push a ship inside a hard exclusion area", function () {
let battle = new Battle();
let ship1a = battle.fleets[0].addShip();
ship1a.setArenaPosition(100, 100);
let ship2a = battle.fleets[1].addShip();
ship2a.setArenaPosition(100, 200);
let ship2b = battle.fleets[1].addShip();
ship2b.setArenaPosition(100, 350);
let effect = new RepelEffect(85);
effect.applyOnShip(ship2a, ship1a);
expect(ship2a.location).toEqual(new ArenaLocationAngle(100, 250));
})
})
}

View file

@ -0,0 +1,31 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac {
/**
* Repel ships from a central point
*/
export class RepelEffect extends BaseEffect {
value: number;
constructor(value = 0) {
super("repel");
this.value = value;
}
applyOnShip(ship: Ship, source: Ship | Drone): boolean {
if (ship != source) {
let angle = arenaAngle(source.location, ship.location);
let destination = new ArenaLocation(ship.arena_x + Math.cos(angle) * this.value, ship.arena_y + Math.sin(angle) * this.value);
let exclusions = ExclusionAreas.fromShip(ship);
destination = exclusions.stopBefore(destination, ship.location);
ship.moveTo(destination.x, destination.y);
}
return true;
}
getDescription(): string {
return `repel ships ${this.value}km away`;
}
}
}

View file

@ -30,33 +30,33 @@ module TS.SpaceTac.Equipments {
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_gravity": 2 });
expect(equipment.effects).toEqual([
new AttributeEffect("shield_capacity", 160),
new AttributeEffect("precision", -1),
new AttributeEffect("shield_capacity", 80),
]);
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 300, [new RepelEffect(100)]));
expect(equipment.price).toEqual(140);
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_gravity": 5 });
expect(equipment.effects).toEqual([
new AttributeEffect("shield_capacity", 190),
new AttributeEffect("precision", -2),
new AttributeEffect("shield_capacity", 110),
]);
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 310, [new RepelEffect(105)]));
expect(equipment.price).toEqual(320);
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_gravity": 8 });
expect(equipment.effects).toEqual([
new AttributeEffect("shield_capacity", 220),
new AttributeEffect("precision", -3),
new AttributeEffect("shield_capacity", 140),
]);
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 320, [new RepelEffect(110)]));
expect(equipment.price).toEqual(680);
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_gravity": 29 });
expect(equipment.effects).toEqual([
new AttributeEffect("shield_capacity", 430),
new AttributeEffect("precision", -10),
new AttributeEffect("shield_capacity", 350),
]);
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 390, [new RepelEffect(145)]));
expect(equipment.price).toEqual(8240);
});
@ -74,7 +74,7 @@ module TS.SpaceTac.Equipments {
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_antimatter": 4 });
expect(equipment.effects).toEqual([
new AttributeEffect("shield_capacity", 165),
new AttributeEffect("shield_capacity", 175),
new AttributeEffect("power_generation", -1),
]);
expect(equipment.price).toEqual(460);
@ -82,7 +82,7 @@ module TS.SpaceTac.Equipments {
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_antimatter": 6 });
expect(equipment.effects).toEqual([
new AttributeEffect("shield_capacity", 200),
new AttributeEffect("shield_capacity", 220),
new AttributeEffect("power_generation", -1),
]);
expect(equipment.price).toEqual(780);
@ -90,7 +90,7 @@ module TS.SpaceTac.Equipments {
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_antimatter": 20 });
expect(equipment.effects).toEqual([
new AttributeEffect("shield_capacity", 445),
new AttributeEffect("shield_capacity", 535),
new AttributeEffect("power_generation", -3),
]);
expect(equipment.price).toEqual(7500);

View file

@ -12,11 +12,13 @@ module TS.SpaceTac.Equipments {
export class GravitShield extends LootTemplate {
constructor() {
super(SlotType.Shield, "Gravit Shield", "A shield with micro-gravity wells to help absorb damage", 140, 180);
super(SlotType.Shield, "Gravit Shield", "A shield able to repel damage and enemies using micro-gravity wells", 140, 180);
this.setSkillsRequirements({ "skill_gravity": istep(2, irepeat(3)) });
this.addAttributeEffect("shield_capacity", istep(160, irepeat(30)));
this.addAttributeEffect("precision", istep(-1, irepeat(-1)));
this.addAttributeEffect("shield_capacity", istep(80, irepeat(30)));
this.addFireAction(irepeat(2), 0, istep(300, irepeat(10)), [
new EffectTemplate(new RepelEffect(), { value: istep(100, irepeat(5)) })
]);
}
}
@ -25,7 +27,7 @@ module TS.SpaceTac.Equipments {
super(SlotType.Shield, "Inverter Shield", "An antimatter shield that tries to cancel inbound energy", 300, 160);
this.setSkillsRequirements({ "skill_antimatter": istep(2, irepeat(2)) });
this.addAttributeEffect("shield_capacity", istep(130, irepeat(35)));
this.addAttributeEffect("shield_capacity", istep(130, irepeat(45)));
this.addAttributeEffect("power_generation", istep(-0.2, irepeat(-0.3)));
}
}

View file

@ -11,15 +11,19 @@ module TS.SpaceTac {
// New location
end: ArenaLocationAngle
constructor(ship: Ship, start: ArenaLocationAngle, end: ArenaLocationAngle) {
// Engine used
engine: Equipment | null
constructor(ship: Ship, start: ArenaLocationAngle, end: ArenaLocationAngle, engine: Equipment | null = null) {
super("move", ship, Target.newFromLocation(end.x, end.y));
this.start = start;
this.end = end;
this.engine = engine;
}
getReverse(): BaseBattleEvent {
return new MoveEvent(this.ship, this.end, this.start);
return new MoveEvent(this.ship, this.end, this.start, this.engine);
}
/**

View file

@ -177,7 +177,7 @@ module TS.SpaceTac.UI {
return 0;
} else if (event instanceof MoveEvent && !event.initial) {
this.moveTo(event.start.x, event.start.y, event.start.angle, false);
let duration = this.moveTo(event.end.x, event.end.y, event.end.angle, true);
let duration = this.moveTo(event.end.x, event.end.y, event.end.angle, true, !!event.engine);
return duration;
} else {
return 0;
@ -248,9 +248,10 @@ module TS.SpaceTac.UI {
*
* Return the duration of animation
*/
moveTo(x: number, y: number, facing_angle: number, animate = true): number {
moveTo(x: number, y: number, facing_angle: number, animate = true, engine = true): number {
if (animate) {
let duration = Animations.moveInSpace(this, x, y, facing_angle, this.sprite);
let animation = engine ? Animations.moveInSpace : Animations.moveTo;
let duration = animation(this, x, y, facing_angle, this.sprite);
return duration;
} else {
this.x = x;

View file

@ -118,22 +118,8 @@ module TS.SpaceTac.UI {
// Key mapping
this.inputs.bind("t", "Show tactical view", () => this.toggle_tactical_mode.switch(3000));
this.inputs.bindCheat("w", "Win current battle", () => {
iforeach(this.battle.iships(), ship => {
if (ship.fleet.player != this.player) {
ship.setDead();
}
});
this.battle.endBattle(this.player.fleet);
});
this.inputs.bindCheat("x", "Lose current battle", () => {
iforeach(this.battle.iships(), ship => {
if (ship.fleet.player == this.player) {
ship.setDead();
}
});
this.battle.endBattle(first(this.battle.fleets, fleet => fleet.player != this.player));
});
this.inputs.bindCheat("w", "Win current battle", () => this.battle.cheats.win());
this.inputs.bindCheat("x", "Lose current battle", () => this.battle.cheats.lose());
this.inputs.bindCheat("a", "Use AI to play", () => this.playAI());
// Start processing the log

View file

@ -57,21 +57,17 @@ module TS.SpaceTac.UI {
this.info.drawCircle(location.x, location.y, radius * 2);
if (action instanceof MoveAction) {
let safety = action.safety_distance / 2;
this.info.beginFill(nocolor);
this.info.drawRect(0, 0, this.width, safety);
this.info.drawRect(0, this.height - safety, this.width, safety);
this.info.drawRect(0, safety, safety, this.height - safety * 2);
this.info.drawRect(this.width - safety, safety, safety, this.height - safety * 2);
let exclusions = action.getExclusionAreas(ship);
let battle = ship.getBattle();
if (battle) {
iforeach(battle.iships(true), s => {
if (s !== ship) {
this.info.drawCircle(s.arena_x, s.arena_y, safety * 4);
}
});
}
this.info.beginFill(nocolor);
this.info.drawRect(0, 0, this.width, exclusions.hard_border);
this.info.drawRect(0, this.height - exclusions.hard_border, this.width, exclusions.hard_border);
this.info.drawRect(0, exclusions.hard_border, exclusions.hard_border, this.height - exclusions.hard_border * 2);
this.info.drawRect(this.width - exclusions.hard_border, exclusions.hard_border, exclusions.hard_border, this.height - exclusions.hard_border * 2);
exclusions.obstacles.forEach(obstacle => {
this.info.drawCircle(obstacle.x, obstacle.y, exclusions.effective_obstacle * 2);
});
}
this.info.visible = true;

View file

@ -174,6 +174,23 @@ module TS.SpaceTac.UI {
return duration;
}
/**
* Move an object linearly to another position
*
* Returns the animation duration.
*/
static moveTo(obj: PhaserGraphics, x: number, y: number, angle: number, rotated_obj = obj, ease = true): number {
let tween_rot = obj.game.tweens.create(rotated_obj);
let duration_rot = Animations.rotationTween(tween_rot, angle, 0.5);
let tween_pos = obj.game.tweens.create(obj);
let duration_pos = arenaDistance(obj, { x: x, y: y }) * 2;
tween_pos.to({ x: x, y: y }, duration_pos, ease ? Phaser.Easing.Quadratic.InOut : undefined);
tween_rot.start();
tween_pos.start();
return Math.max(duration_rot, duration_pos);
}
/**
* Make an object move toward a location in space, with a ship-like animation.
*