Added RepelEffect and use it in Gravit Shield action
10
.vscode/settings.json
vendored
|
@ -2,5 +2,13 @@
|
||||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
120
|
120
|
||||||
]
|
],
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.svn": true,
|
||||||
|
"**/.hg": true,
|
||||||
|
"**/CVS": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"node_modules": true
|
||||||
|
}
|
||||||
}
|
}
|
4
TODO.md
|
@ -37,6 +37,7 @@ Battle
|
||||||
* Add a voluntary retreat option
|
* Add a voluntary retreat option
|
||||||
* Remove dead ships from ship list and play order
|
* Remove dead ships from ship list and play order
|
||||||
* Add quick animation of playing ship indicator, on ship change
|
* 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 effects description instead of attribute changes
|
||||||
* Display radius and power usage hints for area effects on action icon hover + add confirmation ?
|
* 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)
|
* 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 permanent effects and actions to ship models
|
||||||
* Add critical hit/miss
|
* Add critical hit/miss
|
||||||
* Add damage over time effect (tricky to make intuitive)
|
* 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
|
* Move distance should increase with maneuvrability
|
||||||
* Chance to hit should increase with precision
|
* Chance to hit should increase with precision
|
||||||
* Add actions with cost dependent of distance (like current move actions)
|
* Add actions with cost dependent of distance (like current move actions)
|
||||||
* Add "cone" targetting
|
* Add "cone" targetting
|
||||||
* Add disc targetting (for some jump move actions)
|
* Add disc targetting (for some jump move actions)
|
||||||
* Add "chain" effects
|
* 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
|
* Add hull points to drones and make them take area damage
|
||||||
* "Shield Transfer" has no quality offsets
|
* "Shield Transfer" has no quality offsets
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 40 KiB |
|
@ -16,13 +16,25 @@
|
||||||
version="1.1"
|
version="1.1"
|
||||||
inkscape:version="0.92.1 r15371"
|
inkscape:version="0.92.1 r15371"
|
||||||
sodipodi:docname="actions.svg"
|
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-xdpi="90"
|
||||||
inkscape:export-ydpi="90"
|
inkscape:export-ydpi="90"
|
||||||
viewBox="0 0 256 256"
|
viewBox="0 0 256 256"
|
||||||
enable-background="new">
|
enable-background="new">
|
||||||
<defs
|
<defs
|
||||||
id="defs4">
|
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
|
<linearGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
id="linearGradient5473">
|
id="linearGradient5473">
|
||||||
|
@ -63,23 +75,11 @@
|
||||||
offset="1"
|
offset="1"
|
||||||
id="stop4864" />
|
id="stop4864" />
|
||||||
</linearGradient>
|
</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
|
<linearGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
id="linearGradient5038">
|
id="linearGradient5038">
|
||||||
<stop
|
<stop
|
||||||
style="stop-color:#4f5c64;stop-opacity:1;"
|
style="stop-color:#8d9aa2;stop-opacity:1"
|
||||||
offset="0"
|
offset="0"
|
||||||
id="stop5034" />
|
id="stop5034" />
|
||||||
<stop
|
<stop
|
||||||
|
@ -756,22 +756,22 @@
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
style="color-interpolation-filters:sRGB"
|
style="color-interpolation-filters:sRGB"
|
||||||
id="filter5260"
|
id="filter5260"
|
||||||
x="-0.1224"
|
x="-0.5"
|
||||||
width="1.2448"
|
width="2"
|
||||||
y="-0.1224"
|
y="-0.5"
|
||||||
height="1.2448">
|
height="2">
|
||||||
|
<feGaussianBlur
|
||||||
|
stdDeviation="1.4199999999999999"
|
||||||
|
id="feGaussianBlur5264" />
|
||||||
<feBlend
|
<feBlend
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
mode="hard-light"
|
mode="hard-light"
|
||||||
in2="BackgroundImage"
|
in2="BackgroundImage"
|
||||||
id="feBlend5262" />
|
id="feBlend5262" />
|
||||||
<feGaussianBlur
|
|
||||||
stdDeviation="1.4194335"
|
|
||||||
id="feGaussianBlur5264" />
|
|
||||||
</filter>
|
</filter>
|
||||||
<radialGradient
|
<radialGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
xlink:href="#linearGradient5046"
|
xlink:href="#linearGradient5049"
|
||||||
id="radialGradient5289"
|
id="radialGradient5289"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
cx="187.13762"
|
cx="187.13762"
|
||||||
|
@ -779,26 +779,6 @@
|
||||||
fx="187.13762"
|
fx="187.13762"
|
||||||
fy="49.451832"
|
fy="49.451832"
|
||||||
r="13.916015" />
|
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
|
<radialGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
xlink:href="#linearGradient4866"
|
xlink:href="#linearGradient4866"
|
||||||
|
@ -1082,10 +1062,10 @@
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="2.56"
|
inkscape:zoom="2.56"
|
||||||
inkscape:cx="158.76788"
|
inkscape:cx="130.38994"
|
||||||
inkscape:cy="151.25188"
|
inkscape:cy="132.56959"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="g5519"
|
inkscape:current-layer="g5022"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
inkscape:window-width="1920"
|
inkscape:window-width="1920"
|
||||||
inkscape:window-height="1030"
|
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" />
|
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>
|
||||||
<g
|
<g
|
||||||
style="display:none"
|
style="display:inline"
|
||||||
inkscape:label="GravitShield"
|
inkscape:label="GravitShield"
|
||||||
id="g5022"
|
id="g5022"
|
||||||
inkscape:groupmode="layer">
|
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)" />
|
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>
|
||||||
<g
|
<g
|
||||||
id="g4856">
|
id="g5114">
|
||||||
<use
|
<use
|
||||||
transform="translate(0,-98.321274)"
|
transform="translate(0,-98.321274)"
|
||||||
height="100%"
|
height="100%"
|
||||||
|
@ -2253,34 +2233,30 @@
|
||||||
xlink:href="#g5267"
|
xlink:href="#g5267"
|
||||||
y="0"
|
y="0"
|
||||||
x="0" />
|
x="0" />
|
||||||
<g
|
<use
|
||||||
id="g5297"
|
transform="rotate(-180,128,128)"
|
||||||
transform="translate(-59.137619,176.86944)">
|
x="0"
|
||||||
<circle
|
y="0"
|
||||||
r="13.916015"
|
xlink:href="#use5291"
|
||||||
cy="49.451832"
|
id="use5073"
|
||||||
cx="187.13762"
|
width="100%"
|
||||||
id="circle5295"
|
height="100%" />
|
||||||
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)" />
|
<use
|
||||||
</g>
|
x="0"
|
||||||
<g
|
y="0"
|
||||||
id="g5303"
|
xlink:href="#use5293"
|
||||||
transform="translate(-157.45889,78.548168)">
|
id="use5075"
|
||||||
<circle
|
width="100%"
|
||||||
r="13.916015"
|
height="100%"
|
||||||
cy="49.451832"
|
transform="rotate(-180,128,128)" />
|
||||||
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>
|
|
||||||
</g>
|
</g>
|
||||||
<use
|
<use
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
xlink:href="#g4856"
|
xlink:href="#g5114"
|
||||||
id="use4858"
|
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
|
id="use5116"
|
||||||
transform="rotate(45,128,128)" />
|
transform="rotate(45,128,128)" />
|
||||||
</g>
|
</g>
|
||||||
<g
|
<g
|
||||||
|
@ -2579,7 +2555,7 @@
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="g4919"
|
id="g4919"
|
||||||
inkscape:label="VoidhawkEngine"
|
inkscape:label="VoidhawkEngine"
|
||||||
style="display:inline">
|
style="display:none">
|
||||||
<g
|
<g
|
||||||
id="g5449"
|
id="g5449"
|
||||||
transform="translate(1.6322846,-21.706596)">
|
transform="translate(1.6322846,-21.706596)">
|
||||||
|
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 123 KiB |
|
@ -170,7 +170,8 @@
|
||||||
y1="143.13321"
|
y1="143.13321"
|
||||||
x2="259.57538"
|
x2="259.57538"
|
||||||
y2="95.291969"
|
y2="95.291969"
|
||||||
gradientUnits="userSpaceOnUse" />
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(6.0768709,2.3920799)" />
|
||||||
<radialGradient
|
<radialGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
xlink:href="#linearGradient5286"
|
xlink:href="#linearGradient5286"
|
||||||
|
@ -180,7 +181,7 @@
|
||||||
fx="201.79222"
|
fx="201.79222"
|
||||||
fy="102.01662"
|
fy="102.01662"
|
||||||
r="136.00291"
|
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" />
|
gradientUnits="userSpaceOnUse" />
|
||||||
<filter
|
<filter
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
|
@ -289,11 +290,11 @@
|
||||||
borderopacity="1.0"
|
borderopacity="1.0"
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="3.959798"
|
inkscape:zoom="0.70000001"
|
||||||
inkscape:cx="1838.5705"
|
inkscape:cx="921.73937"
|
||||||
inkscape:cy="1007.2957"
|
inkscape:cy="523.01763"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer5"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
showguides="true"
|
showguides="true"
|
||||||
inkscape:lockguides="false"
|
inkscape:lockguides="false"
|
||||||
|
@ -413,18 +414,27 @@
|
||||||
id="use4617"
|
id="use4617"
|
||||||
transform="matrix(3.0721311,0,0,3.0721311,-6.6877028,-512.5613)"
|
transform="matrix(3.0721311,0,0,3.0721311,-6.6877028,-512.5613)"
|
||||||
width="100%"
|
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
|
<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)"
|
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"
|
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
|
<text
|
||||||
xml:space="preserve"
|
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"
|
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"
|
x="216.48831"
|
||||||
y="198.11024"
|
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"
|
sodipodi:role="line"
|
||||||
id="tspan4523"
|
id="tspan4523"
|
||||||
x="216.48831"
|
x="216.48831"
|
||||||
|
@ -433,35 +443,47 @@
|
||||||
<text
|
<text
|
||||||
xml:space="preserve"
|
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"
|
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"
|
x="122.79533"
|
||||||
y="132.02577"
|
y="134.41782"
|
||||||
id="text4529"><tspan
|
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"
|
sodipodi:role="line"
|
||||||
id="tspan4527"
|
id="tspan4527"
|
||||||
x="116.71846"
|
x="122.79533"
|
||||||
y="132.02577"
|
y="134.41782"
|
||||||
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>
|
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
|
<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"
|
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"
|
id="path6915"
|
||||||
cx="299.03043"
|
cx="299.03043"
|
||||||
cy="143.76476"
|
cy="143.76476"
|
||||||
rx="75.166008"
|
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
|
<text
|
||||||
xml:space="preserve"
|
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"
|
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"
|
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"
|
sodipodi:role="line"
|
||||||
id="tspan4548"
|
id="tspan4548"
|
||||||
x="230.68631"
|
x="227.21475"
|
||||||
y="146.65189"
|
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
|
<g
|
||||||
id="g7596"
|
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
|
<circle
|
||||||
r="3.2072344"
|
r="3.2072344"
|
||||||
cy="160.57437"
|
cy="160.57437"
|
||||||
|
@ -490,7 +512,10 @@
|
||||||
id="use7598"
|
id="use7598"
|
||||||
transform="matrix(-1,0,0,1,510.43903,0)"
|
transform="matrix(-1,0,0,1,510.43903,0)"
|
||||||
width="100%"
|
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
|
<use
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
|
@ -498,7 +523,10 @@
|
||||||
id="use7600"
|
id="use7600"
|
||||||
transform="translate(75.629323)"
|
transform="translate(75.629323)"
|
||||||
width="100%"
|
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
|
<use
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
|
@ -506,13 +534,19 @@
|
||||||
id="use7603"
|
id="use7603"
|
||||||
transform="translate(0,29.108755)"
|
transform="translate(0,29.108755)"
|
||||||
width="100%"
|
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
|
<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"
|
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"
|
d="M 254,189.71274 V 160.73956"
|
||||||
id="path7605"
|
id="path7605"
|
||||||
inkscape:connector-curvature="0"
|
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>
|
||||||
<g
|
<g
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
|
@ -520,41 +554,38 @@
|
||||||
inkscape:label="Texts"
|
inkscape:label="Texts"
|
||||||
style="display:inline">
|
style="display:inline">
|
||||||
<text
|
<text
|
||||||
transform="translate(0,-11.249983)"
|
|
||||||
xml:space="preserve"
|
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"
|
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"
|
x="48.484272"
|
||||||
y="208.91302"
|
y="197.66304"
|
||||||
id="text4554"><tspan
|
id="text4554"><tspan
|
||||||
sodipodi:role="line"
|
sodipodi:role="line"
|
||||||
id="tspan4552"
|
id="tspan4552"
|
||||||
x="46.876923"
|
x="48.484272"
|
||||||
y="208.91302"
|
y="197.66304"
|
||||||
style="font-size:14.11111069px;fill:#529aee;fill-opacity:1;stroke-width:0.26458332px">New game</tspan></text>
|
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
|
<text
|
||||||
transform="translate(0,-11.249983)"
|
|
||||||
xml:space="preserve"
|
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"
|
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"
|
x="215.93364"
|
||||||
y="209.34711"
|
y="198.09712"
|
||||||
id="text4554-6"><tspan
|
id="text4554-6"><tspan
|
||||||
sodipodi:role="line"
|
sodipodi:role="line"
|
||||||
id="tspan4552-7"
|
id="tspan4552-7"
|
||||||
x="214.49164"
|
x="215.93364"
|
||||||
y="209.34711"
|
y="198.09712"
|
||||||
style="font-size:14.11111069px;fill:#529aee;fill-opacity:1;stroke-width:0.26458332px">Load game</tspan></text>
|
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
|
<text
|
||||||
transform="translate(0,-11.249983)"
|
|
||||||
xml:space="preserve"
|
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"
|
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"
|
x="383.20383"
|
||||||
y="209.34711"
|
y="198.09712"
|
||||||
id="text4554-2"><tspan
|
id="text4554-2"><tspan
|
||||||
sodipodi:role="line"
|
sodipodi:role="line"
|
||||||
id="tspan4552-74"
|
id="tspan4552-74"
|
||||||
x="380.26669"
|
x="383.20383"
|
||||||
y="209.34711"
|
y="198.09712"
|
||||||
style="font-size:14.11111069px;fill:#4b8ad4;fill-opacity:1;stroke-width:0.26458332px">Quick battle</tspan></text>
|
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>
|
||||||
<g
|
<g
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
|
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 213 KiB |
|
@ -7,6 +7,9 @@ module TS.SpaceTac {
|
||||||
// Battle outcome, if *ended* is true
|
// Battle outcome, if *ended* is true
|
||||||
outcome: BattleOutcome
|
outcome: BattleOutcome
|
||||||
|
|
||||||
|
// Battle cheats
|
||||||
|
cheats: BattleCheats
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
stats: BattleStats
|
stats: BattleStats
|
||||||
|
|
||||||
|
@ -32,6 +35,8 @@ module TS.SpaceTac {
|
||||||
// Size of the battle area
|
// Size of the battle area
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
|
border = 50
|
||||||
|
ship_separation = 100
|
||||||
|
|
||||||
// Timer to use for scheduled things
|
// Timer to use for scheduled things
|
||||||
timer = Timer.global
|
timer = Timer.global
|
||||||
|
@ -51,6 +56,7 @@ module TS.SpaceTac {
|
||||||
|
|
||||||
this.log = new BattleLog();
|
this.log = new BattleLog();
|
||||||
this.stats = new BattleStats();
|
this.stats = new BattleStats();
|
||||||
|
this.cheats = new BattleCheats(this, fleet1.player);
|
||||||
|
|
||||||
this.fleets.forEach((fleet: Fleet) => {
|
this.fleets.forEach((fleet: Fleet) => {
|
||||||
fleet.setBattle(this);
|
fleet.setBattle(this);
|
||||||
|
|
33
src/core/BattleCheats.spec.ts
Normal 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
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,13 @@ module TS.SpaceTac {
|
||||||
this.duration = base_duration;
|
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
|
* Get a textual description of this drone
|
||||||
*/
|
*/
|
||||||
|
|
43
src/core/ExclusionAreas.spec.ts
Normal 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)]);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
96
src/core/ExclusionAreas.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -112,6 +112,7 @@ module TS.SpaceTac.Specs {
|
||||||
let ship1 = battle.fleets[0].addShip();
|
let ship1 = battle.fleets[0].addShip();
|
||||||
let moveaction = <MoveAction>nn(simulator.findBestEngine()).action;
|
let moveaction = <MoveAction>nn(simulator.findBestEngine()).action;
|
||||||
moveaction.safety_distance = 30;
|
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(350, 200), 100)).toBe(ApproachSimulationError.NO_MOVE_NEEDED);
|
||||||
expect(simulator.getApproach(moveaction, Target.newFromLocation(400, 200), 100)).toBe(ApproachSimulationError.NO_MOVE_NEEDED);
|
expect(simulator.getApproach(moveaction, Target.newFromLocation(400, 200), 100)).toBe(ApproachSimulationError.NO_MOVE_NEEDED);
|
||||||
|
|
|
@ -68,7 +68,8 @@ module TS.SpaceTac {
|
||||||
* Check that a move action can reach a given destination
|
* Check that a move action can reach a given destination
|
||||||
*/
|
*/
|
||||||
canMoveTo(action: MoveAction, target: Target): boolean {
|
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);
|
result.move_location = Target.newFromShip(this.ship);
|
||||||
if (action instanceof MoveAction) {
|
if (action instanceof MoveAction) {
|
||||||
let corrected_target = action.applyReachableRange(this.ship, target, move_margin);
|
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) {
|
if (corrected_target) {
|
||||||
result.need_move = target.getDistanceTo(this.ship.location) > 0;
|
result.need_move = target.getDistanceTo(this.ship.location) > 0;
|
||||||
move_target = corrected_target;
|
move_target = corrected_target;
|
||||||
|
|
|
@ -15,7 +15,8 @@ module TS.SpaceTac.Specs {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("moves and computes facing angle", function () {
|
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.setArenaFacingAngle(0);
|
||||||
ship.setArenaPosition(50, 50);
|
ship.setArenaPosition(50, 50);
|
||||||
|
|
||||||
|
@ -23,43 +24,49 @@ module TS.SpaceTac.Specs {
|
||||||
expect(ship.arena_y).toEqual(50);
|
expect(ship.arena_y).toEqual(50);
|
||||||
expect(ship.arena_angle).toEqual(0);
|
expect(ship.arena_angle).toEqual(0);
|
||||||
|
|
||||||
ship.moveTo(51, 50);
|
ship.moveTo(51, 50, engine);
|
||||||
expect(ship.arena_x).toEqual(51);
|
expect(ship.arena_x).toEqual(51);
|
||||||
expect(ship.arena_y).toEqual(50);
|
expect(ship.arena_y).toEqual(50);
|
||||||
expect(ship.arena_angle).toEqual(0);
|
expect(ship.arena_angle).toEqual(0);
|
||||||
|
|
||||||
ship.moveTo(50, 50);
|
ship.moveTo(50, 50, engine);
|
||||||
expect(ship.arena_angle).toBeCloseTo(3.14159265, 0.00001);
|
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);
|
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);
|
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_x).toEqual(52);
|
||||||
expect(ship.arena_y).toEqual(52);
|
expect(ship.arena_y).toEqual(52);
|
||||||
expect(ship.arena_angle).toEqual(0);
|
expect(ship.arena_angle).toEqual(0);
|
||||||
|
|
||||||
ship.moveTo(52, 50);
|
ship.moveTo(52, 50, engine);
|
||||||
expect(ship.arena_angle).toBeCloseTo(-1.5707963, 0.00001);
|
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);
|
expect(ship.arena_angle).toBeCloseTo(3.14159265, 0.00001);
|
||||||
|
|
||||||
let battle = new Battle();
|
let battle = new Battle();
|
||||||
battle.fleets[0].addShip(ship);
|
battle.fleets[0].addShip(ship);
|
||||||
expect(battle.log.events).toEqual([]);
|
expect(battle.log.events).toEqual([]);
|
||||||
|
|
||||||
ship.moveTo(70, 50);
|
ship.moveTo(70, 50, engine);
|
||||||
expect(battle.log.events).toEqual([new MoveEvent(ship, new ArenaLocationAngle(50, 50, Math.PI), new ArenaLocationAngle(70, 50, 0))]);
|
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);
|
ship.rotate(2.1);
|
||||||
expect(battle.log.events).toEqual([
|
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))
|
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 () {
|
it("applies equipment cooldown", function () {
|
||||||
|
|
|
@ -405,20 +405,25 @@ module TS.SpaceTac {
|
||||||
/**
|
/**
|
||||||
* Rotate the ship in place to face a direction
|
* 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) {
|
if (angle != this.arena_angle) {
|
||||||
let start = copy(this.location);
|
let start = copy(this.location);
|
||||||
this.setArenaFacingAngle(angle);
|
this.setArenaFacingAngle(angle);
|
||||||
|
|
||||||
if (log) {
|
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
|
* Move the ship to another location
|
||||||
moveTo(x: number, y: number, log: boolean = true): void {
|
*
|
||||||
|
* 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 dx = x - this.arena_x;
|
||||||
let dy = y - this.arena_y;
|
let dy = y - this.arena_y;
|
||||||
if (dx != 0 || dy != 0) {
|
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_impacted_ships = area_effects.map(action => action.getAffectedShips(this));
|
||||||
let old_area_effects = this.getActiveEffects().area;
|
let old_area_effects = this.getActiveEffects().area;
|
||||||
|
|
||||||
let angle = Math.atan2(dy, dx);
|
if (engine) {
|
||||||
this.setArenaFacingAngle(angle);
|
let angle = Math.atan2(dy, dx);
|
||||||
|
this.setArenaFacingAngle(angle);
|
||||||
|
}
|
||||||
|
|
||||||
this.setArenaPosition(x, y);
|
this.setArenaPosition(x, y);
|
||||||
|
|
||||||
if (log) {
|
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));
|
let new_impacted_ships = area_effects.map(action => action.getAffectedShips(this));
|
||||||
|
|
|
@ -4,12 +4,15 @@ module TS.SpaceTac {
|
||||||
describe("FireWeaponAction", function () {
|
describe("FireWeaponAction", function () {
|
||||||
it("constructs correctly", function () {
|
it("constructs correctly", function () {
|
||||||
let equipment = new Equipment(SlotType.Weapon, "testweapon");
|
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.code).toEqual("fire-testweapon");
|
||||||
expect(action.name).toEqual("Fire");
|
expect(action.name).toEqual("Fire");
|
||||||
expect(action.equipment).toBe(equipment);
|
expect(action.equipment).toBe(equipment);
|
||||||
expect(action.needs_target).toBe(true);
|
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 () {
|
it("applies effects to alive ships in blast radius", function () {
|
||||||
|
|
|
@ -21,7 +21,7 @@ module TS.SpaceTac {
|
||||||
equipment: Equipment;
|
equipment: Equipment;
|
||||||
|
|
||||||
constructor(equipment: Equipment, power = 1, range = 0, blast = 0, effects: BaseEffect[] = [], name = "Fire") {
|
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.power = power;
|
||||||
this.range = range;
|
this.range = range;
|
||||||
|
@ -80,9 +80,14 @@ module TS.SpaceTac {
|
||||||
return result;
|
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
|
// 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
|
// Fire event
|
||||||
ship.addBattleEvent(new FireEvent(ship, this.equipment, target));
|
ship.addBattleEvent(new FireEvent(ship, this.equipment, target));
|
||||||
|
|
|
@ -83,27 +83,27 @@ module TS.SpaceTac {
|
||||||
var ship = battle.fleets[0].ships[0];
|
var ship = battle.fleets[0].ships[0];
|
||||||
var enemy = battle.fleets[1].ships[0];
|
var enemy = battle.fleets[1].ships[0];
|
||||||
TestTools.setShipAP(ship, 100);
|
TestTools.setShipAP(ship, 100);
|
||||||
ship.setArenaPosition(5, 5);
|
ship.setArenaPosition(500, 500);
|
||||||
enemy.setArenaPosition(10, 5);
|
enemy.setArenaPosition(1000, 500);
|
||||||
|
|
||||||
var action = new MoveAction(new Equipment());
|
var action = new MoveAction(new Equipment());
|
||||||
action.distance_per_power = 10;
|
action.distance_per_power = 1000;
|
||||||
action.safety_distance = 2;
|
action.safety_distance = 200;
|
||||||
|
|
||||||
var result = action.checkLocationTarget(ship, Target.newFromLocation(7, 5));
|
var result = action.checkLocationTarget(ship, Target.newFromLocation(700, 500));
|
||||||
expect(result).toEqual(Target.newFromLocation(7, 5));
|
expect(result).toEqual(Target.newFromLocation(700, 500));
|
||||||
|
|
||||||
result = action.checkLocationTarget(ship, Target.newFromLocation(8, 5));
|
result = action.checkLocationTarget(ship, Target.newFromLocation(800, 500));
|
||||||
expect(result).toEqual(Target.newFromLocation(8, 5));
|
expect(result).toEqual(Target.newFromLocation(800, 500));
|
||||||
|
|
||||||
result = action.checkLocationTarget(ship, Target.newFromLocation(9, 5));
|
result = action.checkLocationTarget(ship, Target.newFromLocation(900, 500));
|
||||||
expect(result).toEqual(Target.newFromLocation(8, 5));
|
expect(result).toEqual(Target.newFromLocation(800, 500));
|
||||||
|
|
||||||
result = action.checkLocationTarget(ship, Target.newFromLocation(10, 5));
|
result = action.checkLocationTarget(ship, Target.newFromLocation(1000, 500));
|
||||||
expect(result).toEqual(Target.newFromLocation(8, 5));
|
expect(result).toEqual(Target.newFromLocation(800, 500));
|
||||||
|
|
||||||
result = action.checkLocationTarget(ship, Target.newFromLocation(12, 5));
|
result = action.checkLocationTarget(ship, Target.newFromLocation(1200, 500));
|
||||||
expect(result).toEqual(Target.newFromLocation(12, 5));
|
expect(result).toEqual(Target.newFromLocation(1200, 500));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exclusion radius is applied correctly over two ships", function () {
|
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 enemy1 = battle.fleets[1].ships[0];
|
||||||
var enemy2 = battle.fleets[1].ships[1];
|
var enemy2 = battle.fleets[1].ships[1];
|
||||||
TestTools.setShipAP(ship, 100);
|
TestTools.setShipAP(ship, 100);
|
||||||
enemy1.setArenaPosition(0, 80);
|
enemy1.setArenaPosition(0, 800);
|
||||||
enemy2.setArenaPosition(0, 100);
|
enemy2.setArenaPosition(0, 1000);
|
||||||
|
|
||||||
var action = new MoveAction(new Equipment());
|
var action = new MoveAction(new Equipment());
|
||||||
action.distance_per_power = 1000;
|
action.distance_per_power = 1000;
|
||||||
action.safety_distance = 15;
|
action.safety_distance = 150;
|
||||||
|
|
||||||
var result = action.checkLocationTarget(ship, Target.newFromLocation(0, 110));
|
var result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1100));
|
||||||
expect(result).toEqual(Target.newFromLocation(0, 65));
|
expect(result).toEqual(Target.newFromLocation(0, 650));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exclusion radius does not make the ship go back", function () {
|
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 enemy1 = battle.fleets[1].ships[0];
|
||||||
var enemy2 = battle.fleets[1].ships[1];
|
var enemy2 = battle.fleets[1].ships[1];
|
||||||
TestTools.setShipAP(ship, 100);
|
TestTools.setShipAP(ship, 100);
|
||||||
enemy1.setArenaPosition(0, 50);
|
enemy1.setArenaPosition(0, 500);
|
||||||
enemy2.setArenaPosition(0, 80);
|
enemy2.setArenaPosition(0, 800);
|
||||||
|
|
||||||
var action = new MoveAction(new Equipment());
|
var action = new MoveAction(new Equipment());
|
||||||
action.distance_per_power = 1000;
|
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();
|
expect(result).toBeNull();
|
||||||
result = action.checkLocationTarget(ship, Target.newFromLocation(0, 140));
|
result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1400));
|
||||||
expect(result).toEqual(Target.newFromLocation(0, 140));
|
expect(result).toEqual(Target.newFromLocation(0, 1400));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ module TS.SpaceTac {
|
||||||
// Distance allowed for each power point
|
// Distance allowed for each power point
|
||||||
distance_per_power: number
|
distance_per_power: number
|
||||||
|
|
||||||
// Safety distance from other ships and arena borders
|
// Safety distance from other ships
|
||||||
safety_distance: number
|
safety_distance: number
|
||||||
|
|
||||||
// Equipment cannot be null (engine)
|
// Equipment cannot be null (engine)
|
||||||
|
@ -54,31 +54,21 @@ module TS.SpaceTac {
|
||||||
return this.distance_per_power;
|
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)
|
* Apply exclusion areas (neer arena borders, or other ships)
|
||||||
*/
|
*/
|
||||||
applyExclusion(ship: Ship, target: Target, margin = 0.1): Target {
|
applyExclusion(ship: Ship, target: Target): Target {
|
||||||
let battle = ship.getBattle();
|
let exclusion = this.getExclusionAreas(ship);
|
||||||
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);
|
|
||||||
|
|
||||||
// Apply collision prevention
|
let destination = exclusion.stopBefore(new ArenaLocation(target.x, target.y), ship.location);
|
||||||
let ships = imaterialize(ifilter(battle.iships(true), s => s !== ship));
|
target = Target.newFromLocation(destination.x, destination.y);
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +88,7 @@ module TS.SpaceTac {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected customApply(ship: Ship, target: Target) {
|
protected customApply(ship: Ship, target: Target) {
|
||||||
ship.moveTo(target.x, target.y);
|
ship.moveTo(target.x, target.y, this.equipment);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEffectsDescription(): string {
|
getEffectsDescription(): string {
|
||||||
|
|
40
src/core/effects/RepelEffect.spec.ts
Normal 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));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
31
src/core/effects/RepelEffect.ts
Normal 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`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,33 +30,33 @@ module TS.SpaceTac.Equipments {
|
||||||
let equipment = template.generate(1);
|
let equipment = template.generate(1);
|
||||||
expect(equipment.requirements).toEqual({ "skill_gravity": 2 });
|
expect(equipment.requirements).toEqual({ "skill_gravity": 2 });
|
||||||
expect(equipment.effects).toEqual([
|
expect(equipment.effects).toEqual([
|
||||||
new AttributeEffect("shield_capacity", 160),
|
new AttributeEffect("shield_capacity", 80),
|
||||||
new AttributeEffect("precision", -1),
|
|
||||||
]);
|
]);
|
||||||
|
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 300, [new RepelEffect(100)]));
|
||||||
expect(equipment.price).toEqual(140);
|
expect(equipment.price).toEqual(140);
|
||||||
|
|
||||||
equipment = template.generate(2);
|
equipment = template.generate(2);
|
||||||
expect(equipment.requirements).toEqual({ "skill_gravity": 5 });
|
expect(equipment.requirements).toEqual({ "skill_gravity": 5 });
|
||||||
expect(equipment.effects).toEqual([
|
expect(equipment.effects).toEqual([
|
||||||
new AttributeEffect("shield_capacity", 190),
|
new AttributeEffect("shield_capacity", 110),
|
||||||
new AttributeEffect("precision", -2),
|
|
||||||
]);
|
]);
|
||||||
|
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 310, [new RepelEffect(105)]));
|
||||||
expect(equipment.price).toEqual(320);
|
expect(equipment.price).toEqual(320);
|
||||||
|
|
||||||
equipment = template.generate(3);
|
equipment = template.generate(3);
|
||||||
expect(equipment.requirements).toEqual({ "skill_gravity": 8 });
|
expect(equipment.requirements).toEqual({ "skill_gravity": 8 });
|
||||||
expect(equipment.effects).toEqual([
|
expect(equipment.effects).toEqual([
|
||||||
new AttributeEffect("shield_capacity", 220),
|
new AttributeEffect("shield_capacity", 140),
|
||||||
new AttributeEffect("precision", -3),
|
|
||||||
]);
|
]);
|
||||||
|
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 320, [new RepelEffect(110)]));
|
||||||
expect(equipment.price).toEqual(680);
|
expect(equipment.price).toEqual(680);
|
||||||
|
|
||||||
equipment = template.generate(10);
|
equipment = template.generate(10);
|
||||||
expect(equipment.requirements).toEqual({ "skill_gravity": 29 });
|
expect(equipment.requirements).toEqual({ "skill_gravity": 29 });
|
||||||
expect(equipment.effects).toEqual([
|
expect(equipment.effects).toEqual([
|
||||||
new AttributeEffect("shield_capacity", 430),
|
new AttributeEffect("shield_capacity", 350),
|
||||||
new AttributeEffect("precision", -10),
|
|
||||||
]);
|
]);
|
||||||
|
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 390, [new RepelEffect(145)]));
|
||||||
expect(equipment.price).toEqual(8240);
|
expect(equipment.price).toEqual(8240);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ module TS.SpaceTac.Equipments {
|
||||||
equipment = template.generate(2);
|
equipment = template.generate(2);
|
||||||
expect(equipment.requirements).toEqual({ "skill_antimatter": 4 });
|
expect(equipment.requirements).toEqual({ "skill_antimatter": 4 });
|
||||||
expect(equipment.effects).toEqual([
|
expect(equipment.effects).toEqual([
|
||||||
new AttributeEffect("shield_capacity", 165),
|
new AttributeEffect("shield_capacity", 175),
|
||||||
new AttributeEffect("power_generation", -1),
|
new AttributeEffect("power_generation", -1),
|
||||||
]);
|
]);
|
||||||
expect(equipment.price).toEqual(460);
|
expect(equipment.price).toEqual(460);
|
||||||
|
@ -82,7 +82,7 @@ module TS.SpaceTac.Equipments {
|
||||||
equipment = template.generate(3);
|
equipment = template.generate(3);
|
||||||
expect(equipment.requirements).toEqual({ "skill_antimatter": 6 });
|
expect(equipment.requirements).toEqual({ "skill_antimatter": 6 });
|
||||||
expect(equipment.effects).toEqual([
|
expect(equipment.effects).toEqual([
|
||||||
new AttributeEffect("shield_capacity", 200),
|
new AttributeEffect("shield_capacity", 220),
|
||||||
new AttributeEffect("power_generation", -1),
|
new AttributeEffect("power_generation", -1),
|
||||||
]);
|
]);
|
||||||
expect(equipment.price).toEqual(780);
|
expect(equipment.price).toEqual(780);
|
||||||
|
@ -90,7 +90,7 @@ module TS.SpaceTac.Equipments {
|
||||||
equipment = template.generate(10);
|
equipment = template.generate(10);
|
||||||
expect(equipment.requirements).toEqual({ "skill_antimatter": 20 });
|
expect(equipment.requirements).toEqual({ "skill_antimatter": 20 });
|
||||||
expect(equipment.effects).toEqual([
|
expect(equipment.effects).toEqual([
|
||||||
new AttributeEffect("shield_capacity", 445),
|
new AttributeEffect("shield_capacity", 535),
|
||||||
new AttributeEffect("power_generation", -3),
|
new AttributeEffect("power_generation", -3),
|
||||||
]);
|
]);
|
||||||
expect(equipment.price).toEqual(7500);
|
expect(equipment.price).toEqual(7500);
|
||||||
|
|
|
@ -12,11 +12,13 @@ module TS.SpaceTac.Equipments {
|
||||||
|
|
||||||
export class GravitShield extends LootTemplate {
|
export class GravitShield extends LootTemplate {
|
||||||
constructor() {
|
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.setSkillsRequirements({ "skill_gravity": istep(2, irepeat(3)) });
|
||||||
this.addAttributeEffect("shield_capacity", istep(160, irepeat(30)));
|
this.addAttributeEffect("shield_capacity", istep(80, irepeat(30)));
|
||||||
this.addAttributeEffect("precision", istep(-1, irepeat(-1)));
|
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);
|
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.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)));
|
this.addAttributeEffect("power_generation", istep(-0.2, irepeat(-0.3)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,15 +11,19 @@ module TS.SpaceTac {
|
||||||
// New location
|
// New location
|
||||||
end: ArenaLocationAngle
|
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));
|
super("move", ship, Target.newFromLocation(end.x, end.y));
|
||||||
|
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
|
this.engine = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
getReverse(): BaseBattleEvent {
|
getReverse(): BaseBattleEvent {
|
||||||
return new MoveEvent(this.ship, this.end, this.start);
|
return new MoveEvent(this.ship, this.end, this.start, this.engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -177,7 +177,7 @@ module TS.SpaceTac.UI {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (event instanceof MoveEvent && !event.initial) {
|
} else if (event instanceof MoveEvent && !event.initial) {
|
||||||
this.moveTo(event.start.x, event.start.y, event.start.angle, false);
|
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;
|
return duration;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -248,9 +248,10 @@ module TS.SpaceTac.UI {
|
||||||
*
|
*
|
||||||
* Return the duration of animation
|
* 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) {
|
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;
|
return duration;
|
||||||
} else {
|
} else {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
|
|
|
@ -118,22 +118,8 @@ module TS.SpaceTac.UI {
|
||||||
|
|
||||||
// Key mapping
|
// Key mapping
|
||||||
this.inputs.bind("t", "Show tactical view", () => this.toggle_tactical_mode.switch(3000));
|
this.inputs.bind("t", "Show tactical view", () => this.toggle_tactical_mode.switch(3000));
|
||||||
this.inputs.bindCheat("w", "Win current battle", () => {
|
this.inputs.bindCheat("w", "Win current battle", () => this.battle.cheats.win());
|
||||||
iforeach(this.battle.iships(), ship => {
|
this.inputs.bindCheat("x", "Lose current battle", () => this.battle.cheats.lose());
|
||||||
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("a", "Use AI to play", () => this.playAI());
|
this.inputs.bindCheat("a", "Use AI to play", () => this.playAI());
|
||||||
|
|
||||||
// Start processing the log
|
// Start processing the log
|
||||||
|
|
|
@ -57,21 +57,17 @@ module TS.SpaceTac.UI {
|
||||||
this.info.drawCircle(location.x, location.y, radius * 2);
|
this.info.drawCircle(location.x, location.y, radius * 2);
|
||||||
|
|
||||||
if (action instanceof MoveAction) {
|
if (action instanceof MoveAction) {
|
||||||
let safety = action.safety_distance / 2;
|
let exclusions = action.getExclusionAreas(ship);
|
||||||
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 battle = ship.getBattle();
|
this.info.beginFill(nocolor);
|
||||||
if (battle) {
|
this.info.drawRect(0, 0, this.width, exclusions.hard_border);
|
||||||
iforeach(battle.iships(true), s => {
|
this.info.drawRect(0, this.height - exclusions.hard_border, this.width, exclusions.hard_border);
|
||||||
if (s !== ship) {
|
this.info.drawRect(0, exclusions.hard_border, exclusions.hard_border, this.height - exclusions.hard_border * 2);
|
||||||
this.info.drawCircle(s.arena_x, s.arena_y, safety * 4);
|
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;
|
this.info.visible = true;
|
||||||
|
|
|
@ -174,6 +174,23 @@ module TS.SpaceTac.UI {
|
||||||
return duration;
|
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.
|
* Make an object move toward a location in space, with a ship-like animation.
|
||||||
*
|
*
|
||||||
|
|