Added RepelEffect and use it in Gravit Shield action
10
.vscode/settings.json
vendored
|
@ -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
|
||||
}
|
||||
}
|
4
TODO.md
|
@ -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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 40 KiB |
|
@ -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 |
|
@ -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 |
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
|
||||
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);
|
||||
|
|
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current location of the drone
|
||||
*/
|
||||
get location(): ArenaLocation {
|
||||
return new ArenaLocation(this.x, this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
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);
|
||||
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);
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|