arena: Added auto-approach to bring in range for action
1
TODO
|
@ -21,7 +21,6 @@
|
||||||
* Menu: allow to delete cloud saves
|
* Menu: allow to delete cloud saves
|
||||||
* Arena: display effects description instead of attribute changes
|
* Arena: display effects description instead of attribute changes
|
||||||
* Arena: display radius for area effects (both on action hover, and while action is active)
|
* Arena: display radius for area effects (both on action hover, and while action is active)
|
||||||
* Arena: add auto-move to attack
|
|
||||||
* Arena: fix effects originating from real ship location instead of current sprite (when AI fires then moves)
|
* Arena: fix effects originating from real ship location instead of current sprite (when AI fires then moves)
|
||||||
* Arena: add engine trail
|
* Arena: add engine trail
|
||||||
* Fix capacity limit effect not refreshing associated value (for example, on "limit power capacity to 3", potential "power" value change is not broadcast)
|
* Fix capacity limit effect not refreshing associated value (for example, on "limit power capacity to 3", potential "power" value change is not broadcast)
|
||||||
|
|
|
@ -235,18 +235,6 @@
|
||||||
offset="1"
|
offset="1"
|
||||||
id="stop10127" />
|
id="stop10127" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient9717">
|
|
||||||
<stop
|
|
||||||
style="stop-color:#d1d4b2;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop9713" />
|
|
||||||
<stop
|
|
||||||
style="stop-color:#d1d4b2;stop-opacity:0;"
|
|
||||||
offset="1"
|
|
||||||
id="stop9715" />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient
|
<linearGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
id="linearGradient9609">
|
id="linearGradient9609">
|
||||||
|
@ -710,16 +698,6 @@
|
||||||
x2="1512.2041"
|
x2="1512.2041"
|
||||||
y2="877.88531"
|
y2="877.88531"
|
||||||
gradientUnits="userSpaceOnUse" />
|
gradientUnits="userSpaceOnUse" />
|
||||||
<radialGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient9717"
|
|
||||||
id="radialGradient9719"
|
|
||||||
cx="825.83337"
|
|
||||||
cy="849.4455"
|
|
||||||
fx="825.83337"
|
|
||||||
fy="849.4455"
|
|
||||||
r="22.5"
|
|
||||||
gradientUnits="userSpaceOnUse" />
|
|
||||||
<radialGradient
|
<radialGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
xlink:href="#linearGradient10129"
|
xlink:href="#linearGradient10129"
|
||||||
|
@ -843,16 +821,6 @@
|
||||||
operator="atop"
|
operator="atop"
|
||||||
result="composite2" />
|
result="composite2" />
|
||||||
</filter>
|
</filter>
|
||||||
<radialGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient9717"
|
|
||||||
id="radialGradient5025"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
cx="825.83337"
|
|
||||||
cy="849.4455"
|
|
||||||
fx="825.83337"
|
|
||||||
fy="849.4455"
|
|
||||||
r="22.5" />
|
|
||||||
<filter
|
<filter
|
||||||
id="filter5041"
|
id="filter5041"
|
||||||
inkscape:label="Color impossible"
|
inkscape:label="Color impossible"
|
||||||
|
@ -1166,11 +1134,11 @@
|
||||||
borderopacity="1"
|
borderopacity="1"
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="0.5"
|
inkscape:zoom="4"
|
||||||
inkscape:cx="1132.2376"
|
inkscape:cx="358.32238"
|
||||||
inkscape:cy="630.86578"
|
inkscape:cy="992.57962"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer25"
|
inkscape:current-layer="layer8"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
units="px"
|
units="px"
|
||||||
showguides="false"
|
showguides="false"
|
||||||
|
@ -1181,7 +1149,7 @@
|
||||||
inkscape:object-nodes="true"
|
inkscape:object-nodes="true"
|
||||||
inkscape:snap-intersection-paths="false"
|
inkscape:snap-intersection-paths="false"
|
||||||
inkscape:object-paths="true"
|
inkscape:object-paths="true"
|
||||||
inkscape:snap-global="true"
|
inkscape:snap-global="false"
|
||||||
inkscape:showpageshadow="false"
|
inkscape:showpageshadow="false"
|
||||||
showborder="true"
|
showborder="true"
|
||||||
borderlayer="true" />
|
borderlayer="true" />
|
||||||
|
@ -1548,6 +1516,12 @@
|
||||||
cx="1551.4003"
|
cx="1551.4003"
|
||||||
cy="742.08289"
|
cy="742.08289"
|
||||||
r="31.144533" />
|
r="31.144533" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;fill-rule:evenodd;stroke:url(#linearGradient9611);stroke-width:5.89960623;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new"
|
||||||
|
d="M 732.44478,877.88531 H 1512.2041"
|
||||||
|
id="path9595"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
<use
|
<use
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
|
@ -1557,121 +1531,16 @@
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
style="display:inline;opacity:0.58499995;enable-background:new" />
|
style="display:inline;opacity:0.58499995;enable-background:new" />
|
||||||
<path
|
|
||||||
style="display:inline;fill:none;fill-rule:evenodd;stroke:url(#linearGradient9611);stroke-width:5.89960623;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new"
|
|
||||||
d="M 732.44478,877.88531 H 1512.2041"
|
|
||||||
id="path9595"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
sodipodi:nodetypes="cc" />
|
|
||||||
<path
|
|
||||||
sodipodi:nodetypes="ccccc"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="use9628"
|
|
||||||
d="m 1133.0389,877.88531 -23.5347,9.28279 3.6495,-9.28279 -3.6495,-9.28279 z"
|
|
||||||
inkscape:transform-center-x="-2.67976"
|
|
||||||
style="display:inline;opacity:1;fill:#362c20;fill-opacity:1;fill-rule:evenodd;stroke:#e09c47;stroke-width:2.43093753;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new" />
|
|
||||||
<path
|
<path
|
||||||
style="display:inline;opacity:1;fill:#391b13;fill-opacity:1;fill-rule:evenodd;stroke:#dc6441;stroke-width:2.33156252;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new"
|
style="display:inline;opacity:1;fill:#391b13;fill-opacity:1;fill-rule:evenodd;stroke:#dc6441;stroke-width:2.33156252;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new"
|
||||||
inkscape:transform-center-x="-3.93566"
|
inkscape:transform-center-x="-3.93566"
|
||||||
d="m 1517.8308,877.88531 -34.5651,13.6335 5.3601,-13.6335 -5.3601,-13.6335 z"
|
d="m 1517.8308,877.88531 -34.5651,13.6335 5.3601,-13.6335 -5.3601,-13.6335 z"
|
||||||
id="path10745"
|
id="path10745"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
sodipodi:nodetypes="ccccc" />
|
sodipodi:nodetypes="ccccc"
|
||||||
<g
|
inkscape:export-filename="/tmp/export.png"
|
||||||
style="display:inline;enable-background:new"
|
|
||||||
id="g9681"
|
|
||||||
transform="translate(-16.499158,28.439814)"
|
|
||||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/arena/ap-indicator.png"
|
|
||||||
inkscape:export-xdpi="90"
|
inkscape:export-xdpi="90"
|
||||||
inkscape:export-ydpi="90">
|
inkscape:export-ydpi="90" />
|
||||||
<circle
|
|
||||||
r="22.5"
|
|
||||||
cy="849.4455"
|
|
||||||
cx="825.83337"
|
|
||||||
id="path9677"
|
|
||||||
style="opacity:1;fill:url(#radialGradient9719);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.33156252;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
<use
|
|
||||||
height="100%"
|
|
||||||
width="100%"
|
|
||||||
transform="translate(828.83521,989.66854)"
|
|
||||||
id="use9656"
|
|
||||||
xlink:href="#g4573"
|
|
||||||
y="0"
|
|
||||||
x="0" />
|
|
||||||
</g>
|
|
||||||
<use
|
|
||||||
style="display:inline;enable-background:new"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
xlink:href="#g9681"
|
|
||||||
id="use9699"
|
|
||||||
transform="translate(121.99958)"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
<use
|
|
||||||
style="display:inline;enable-background:new"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
xlink:href="#g9681"
|
|
||||||
id="use9701"
|
|
||||||
transform="translate(243.99916)"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
<use
|
|
||||||
style="display:inline;enable-background:new"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
xlink:href="#g9681"
|
|
||||||
id="use9703"
|
|
||||||
transform="translate(370.64181)"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
<use
|
|
||||||
style="display:inline;enable-background:new"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
xlink:href="#g9681"
|
|
||||||
id="use9705"
|
|
||||||
transform="translate(436.68086)"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
<use
|
|
||||||
style="display:inline;enable-background:new"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
xlink:href="#g9681"
|
|
||||||
id="use9707"
|
|
||||||
transform="translate(502.7198)"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
<use
|
|
||||||
style="display:inline;enable-background:new"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
xlink:href="#g9681"
|
|
||||||
id="use9709"
|
|
||||||
transform="translate(568.75877)"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
<g
|
|
||||||
transform="translate(618.6426,28.439814)"
|
|
||||||
id="g5021"
|
|
||||||
style="display:inline;filter:url(#filter5041);enable-background:new">
|
|
||||||
<circle
|
|
||||||
style="opacity:1;fill:url(#radialGradient5025);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.33156252;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="circle5017"
|
|
||||||
cx="825.83337"
|
|
||||||
cy="849.4455"
|
|
||||||
r="22.5" />
|
|
||||||
<use
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
xlink:href="#g4573"
|
|
||||||
id="use5019"
|
|
||||||
transform="translate(828.83521,989.66854)"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
<g
|
<g
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
|
@ -2262,17 +2131,7 @@
|
||||||
inkscape:export-ydpi="90"
|
inkscape:export-ydpi="90"
|
||||||
transform="matrix(0.98149613,0,0,1.5780874,-2.5439867,-65.79016)" />
|
transform="matrix(0.98149613,0,0,1.5780874,-2.5439867,-65.79016)" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.98149613,0,0,1.5780874,52.760346,-65.79016)"
|
transform="matrix(0.98149613,0,0,1.5780874,160.43481,-65.79016)"
|
||||||
inkscape:export-ydpi="90"
|
|
||||||
inkscape:export-xdpi="90"
|
|
||||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/power-using.png"
|
|
||||||
sodipodi:nodetypes="ccccc"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path4925"
|
|
||||||
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
|
|
||||||
style="opacity:1;fill:#f8f0b5;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
<path
|
|
||||||
transform="matrix(0.98149613,0,0,1.5780874,108.06471,-65.79016)"
|
|
||||||
inkscape:export-ydpi="90"
|
inkscape:export-ydpi="90"
|
||||||
inkscape:export-xdpi="90"
|
inkscape:export-xdpi="90"
|
||||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/power-used.png"
|
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/power-used.png"
|
||||||
|
@ -2281,6 +2140,26 @@
|
||||||
id="path4927"
|
id="path4927"
|
||||||
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
|
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
|
||||||
style="opacity:1;fill:#6b6443;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5649)" />
|
style="opacity:1;fill:#6b6443;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5649)" />
|
||||||
|
<path
|
||||||
|
transform="matrix(0.98149613,0,0,1.5780874,51.782267,-65.79016)"
|
||||||
|
inkscape:export-ydpi="90"
|
||||||
|
inkscape:export-xdpi="90"
|
||||||
|
inkscape:export-filename="/tmp/export.png"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path5001"
|
||||||
|
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
|
||||||
|
style="opacity:1;fill:#e09c47;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5649)" />
|
||||||
|
<path
|
||||||
|
transform="matrix(0.98149613,0,0,1.5780874,106.10853,-65.79016)"
|
||||||
|
inkscape:export-ydpi="90"
|
||||||
|
inkscape:export-xdpi="90"
|
||||||
|
inkscape:export-filename="/tmp/export.png"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path5003"
|
||||||
|
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
|
||||||
|
style="opacity:1;fill:#dc6441;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5649)" />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g
|
<g
|
||||||
|
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.5 KiB |
BIN
out/assets/images/battle/arena/indicators.png
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 12 KiB |
|
@ -25,6 +25,8 @@ module TS.SpaceTac {
|
||||||
success = false
|
success = false
|
||||||
// Ideal successive parts to make the full move+fire
|
// Ideal successive parts to make the full move+fire
|
||||||
parts: MoveFirePart[] = []
|
parts: MoveFirePart[] = []
|
||||||
|
// Simulation complete (both move and fire are possible)
|
||||||
|
complete = false
|
||||||
|
|
||||||
need_move = false
|
need_move = false
|
||||||
can_move = false
|
can_move = false
|
||||||
|
@ -74,7 +76,8 @@ module TS.SpaceTac {
|
||||||
* Get an iterator for scanning a circle
|
* Get an iterator for scanning a circle
|
||||||
*/
|
*/
|
||||||
scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterator<Target> {
|
scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterator<Target> {
|
||||||
return ichainit(imap(istep(0, irepeat(nr ? 1 / (nr - 1) : 0, nr - 1)), r => {
|
let rcount = nr ? 1 / (nr - 1) : 0;
|
||||||
|
return ichainit(imap(istep(0, irepeat(rcount, nr - 1)), r => {
|
||||||
let angles = Math.max(1, Math.ceil(na * r));
|
let angles = Math.max(1, Math.ceil(na * r));
|
||||||
return imap(istep(0, irepeat(2 * Math.PI / angles, angles - 1)), a => {
|
return imap(istep(0, irepeat(2 * Math.PI / angles, angles - 1)), a => {
|
||||||
return new Target(x + r * radius * Math.cos(a), y + r * radius * Math.sin(a))
|
return new Target(x + r * radius * Math.cos(a), y + r * radius * Math.sin(a))
|
||||||
|
@ -125,8 +128,10 @@ module TS.SpaceTac {
|
||||||
|
|
||||||
// Move or approach needed ?
|
// Move or approach needed ?
|
||||||
let move_target: Target | null = null;
|
let move_target: Target | null = null;
|
||||||
|
result.move_location = Target.newFromShip(this.ship);
|
||||||
if (action instanceof MoveAction) {
|
if (action instanceof MoveAction) {
|
||||||
let corrected_target = action.applyExclusion(this.ship, target);
|
let corrected_target = action.applyReachableRange(this.ship, target, move_margin);
|
||||||
|
corrected_target = action.applyExclusion(this.ship, corrected_target, move_margin);
|
||||||
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;
|
||||||
|
@ -174,7 +179,9 @@ module TS.SpaceTac {
|
||||||
result.fire_location = target;
|
result.fire_location = target;
|
||||||
result.parts.push({ action: action, target: target, ap: result.total_fire_ap, possible: (!result.need_move || result.can_end_move) && result.can_fire });
|
result.parts.push({ action: action, target: target, ap: result.total_fire_ap, possible: (!result.need_move || result.can_end_move) && result.can_fire });
|
||||||
}
|
}
|
||||||
|
|
||||||
result.success = true;
|
result.success = true;
|
||||||
|
result.complete = (!result.need_move || result.can_end_move) && (!result.need_fire || result.can_fire);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ module TS.SpaceTac {
|
||||||
expect(result).toEqual(Target.newFromLocation(0, 2));
|
expect(result).toEqual(Target.newFromLocation(0, 2));
|
||||||
|
|
||||||
result = action.checkTarget(ship, Target.newFromLocation(0, 8));
|
result = action.checkTarget(ship, Target.newFromLocation(0, 8));
|
||||||
expect(result).toEqual(Target.newFromLocation(0, 3));
|
expect(result).toEqual(Target.newFromLocation(0, 2.9));
|
||||||
|
|
||||||
ship.values.power.set(0);
|
ship.values.power.set(0);
|
||||||
result = action.checkTarget(ship, Target.newFromLocation(0, 8));
|
result = action.checkTarget(ship, Target.newFromLocation(0, 8));
|
||||||
|
|
|
@ -57,7 +57,7 @@ module TS.SpaceTac {
|
||||||
/**
|
/**
|
||||||
* Apply exclusion areas (neer arena borders, or other ships)
|
* Apply exclusion areas (neer arena borders, or other ships)
|
||||||
*/
|
*/
|
||||||
applyExclusion(ship: Ship, target: Target): Target {
|
applyExclusion(ship: Ship, target: Target, margin = 0.1): Target {
|
||||||
let battle = ship.getBattle();
|
let battle = ship.getBattle();
|
||||||
if (battle) {
|
if (battle) {
|
||||||
// Keep out of arena borders
|
// Keep out of arena borders
|
||||||
|
@ -76,14 +76,18 @@ module TS.SpaceTac {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply reachable range, with remaining power
|
||||||
|
*/
|
||||||
|
applyReachableRange(ship: Ship, target: Target, margin = 0.1): Target {
|
||||||
|
let max_distance = this.getRangeRadius(ship);
|
||||||
|
max_distance = Math.max(0, max_distance - margin);
|
||||||
|
return target.constraintInRange(ship.arena_x, ship.arena_y, max_distance);
|
||||||
|
}
|
||||||
|
|
||||||
checkLocationTarget(ship: Ship, target: Target): Target {
|
checkLocationTarget(ship: Ship, target: Target): Target {
|
||||||
// Apply maximal distance
|
target = this.applyReachableRange(ship, target);
|
||||||
var max_distance = this.getRangeRadius(ship);
|
|
||||||
target = target.constraintInRange(ship.arena_x, ship.arena_y, max_distance);
|
|
||||||
|
|
||||||
// Apply exclusion areas
|
|
||||||
target = this.applyExclusion(ship, target);
|
target = this.applyExclusion(ship, target);
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ module TS.SpaceTac.Specs {
|
||||||
it("guesses area effects on final location", function () {
|
it("guesses area effects on final location", function () {
|
||||||
let battle = new Battle();
|
let battle = new Battle();
|
||||||
let ship = battle.fleets[0].addShip();
|
let ship = battle.fleets[0].addShip();
|
||||||
TestTools.addEngine(ship, 500);
|
let engine = TestTools.addEngine(ship, 500);
|
||||||
|
TestTools.setShipAP(ship, 10);
|
||||||
let drone = new Drone(ship);
|
let drone = new Drone(ship);
|
||||||
drone.effects = [new AttributeEffect("maneuvrability", 1)];
|
drone.effects = [new AttributeEffect("maneuvrability", 1)];
|
||||||
drone.x = 100;
|
drone.x = 100;
|
||||||
|
@ -50,11 +51,11 @@ module TS.SpaceTac.Specs {
|
||||||
drone.radius = 50;
|
drone.radius = 50;
|
||||||
battle.addDrone(drone);
|
battle.addDrone(drone);
|
||||||
|
|
||||||
let maneuver = new Maneuver(ship, new MoveAction(new Equipment()), Target.newFromLocation(40, 30));
|
let maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(40, 30));
|
||||||
expect(maneuver.getFinalLocation()).toEqual(jasmine.objectContaining({ x: 40, y: 30 }));
|
expect(maneuver.getFinalLocation()).toEqual(jasmine.objectContaining({ x: 40, y: 30 }));
|
||||||
expect(maneuver.effects).toEqual([]);
|
expect(maneuver.effects).toEqual([]);
|
||||||
|
|
||||||
maneuver = new Maneuver(ship, new MoveAction(new Equipment()), Target.newFromLocation(100, 30));
|
maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(100, 30));
|
||||||
expect(maneuver.getFinalLocation()).toEqual(jasmine.objectContaining({ x: 100, y: 30 }));
|
expect(maneuver.getFinalLocation()).toEqual(jasmine.objectContaining({ x: 100, y: 30 }));
|
||||||
expect(maneuver.effects).toEqual([[ship, new AttributeEffect("maneuvrability", 1)]]);
|
expect(maneuver.effects).toEqual([[ship, new AttributeEffect("maneuvrability", 1)]]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,6 +50,7 @@ module TS.SpaceTac.UI {
|
||||||
create() {
|
create() {
|
||||||
// Phaser config
|
// Phaser config
|
||||||
this.game.stage.backgroundColor = 0x000000;
|
this.game.stage.backgroundColor = 0x000000;
|
||||||
|
this.game.stage.disableVisibilityChange = this.gameui.headless;
|
||||||
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
|
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
|
||||||
this.input.maxPointers = 1;
|
this.input.maxPointers = 1;
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,10 @@ module TS.SpaceTac.UI {
|
||||||
this.loadImage("battle/actionbar/action-endturn.png");
|
this.loadImage("battle/actionbar/action-endturn.png");
|
||||||
this.loadSheet("battle/actionbar/button-menu.png", 79, 132);
|
this.loadSheet("battle/actionbar/button-menu.png", 79, 132);
|
||||||
this.loadImage("battle/arena/background.png");
|
this.loadImage("battle/arena/background.png");
|
||||||
this.loadImage("battle/arena/ap-indicator.png");
|
|
||||||
this.loadImage("battle/arena/blast.png");
|
this.loadImage("battle/arena/blast.png");
|
||||||
this.loadSheet("battle/arena/gauges.png", 19, 93);
|
this.loadSheet("battle/arena/gauges.png", 19, 93);
|
||||||
this.loadSheet("battle/arena/small-indicators.png", 10, 10);
|
this.loadSheet("battle/arena/small-indicators.png", 10, 10);
|
||||||
|
this.loadSheet("battle/arena/indicators.png", 64, 64);
|
||||||
this.loadSheet("battle/arena/ship-frames.png", 70, 70);
|
this.loadSheet("battle/arena/ship-frames.png", 70, 70);
|
||||||
this.loadImage("battle/shiplist/background.png");
|
this.loadImage("battle/shiplist/background.png");
|
||||||
this.loadImage("battle/shiplist/item-background.png");
|
this.loadImage("battle/shiplist/item-background.png");
|
||||||
|
|
|
@ -49,51 +49,45 @@ module TS.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
expect(bar.action_icons.length).toBe(4);
|
expect(bar.action_icons.length).toBe(4);
|
||||||
|
|
||||||
var checkFading = (fading: number[], available: number[]) => {
|
var checkFading = (fading: number[], available: number[], message: string) => {
|
||||||
fading.forEach((index: number) => {
|
fading.forEach((index: number) => {
|
||||||
var icon = bar.action_icons[index];
|
var icon = bar.action_icons[index];
|
||||||
expect(icon.fading || !icon.active).toBe(true);
|
expect(icon.fading || !icon.active).toBe(true, `${message} - ${index} should be fading`);
|
||||||
});
|
});
|
||||||
available.forEach((index: number) => {
|
available.forEach((index: number) => {
|
||||||
var icon = bar.action_icons[index];
|
var icon = bar.action_icons[index];
|
||||||
expect(icon.fading).toBe(false);
|
expect(icon.fading).toBe(false, `${message} - ${index} should be available`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Weapon 1 leaves all choices open
|
bar.updateSelectedActionPower(3, 0, bar.action_icons[1].action);
|
||||||
bar.action_icons[1].processClick();
|
checkFading([], [0, 1, 2, 3], "Weapon 1 leaves all choices open");
|
||||||
checkFading([], [0, 1, 2, 3]);
|
|
||||||
bar.actionEnded();
|
bar.actionEnded();
|
||||||
|
|
||||||
// Weapon 2 can't be fired twice
|
bar.updateSelectedActionPower(5, 0, bar.action_icons[2].action);
|
||||||
bar.action_icons[2].processClick();
|
checkFading([2], [0, 1, 3], "Weapon 2 can't be fired twice");
|
||||||
checkFading([2], [0, 1, 3]);
|
|
||||||
bar.actionEnded();
|
bar.actionEnded();
|
||||||
|
|
||||||
// Not enough AP for both weapons
|
|
||||||
ship.setValue("power", 7);
|
ship.setValue("power", 7);
|
||||||
bar.action_icons[2].processClick();
|
bar.updateSelectedActionPower(5, 0, bar.action_icons[2].action);
|
||||||
checkFading([1, 2], [0, 3]);
|
checkFading([1, 2], [0, 3], "Not enough AP for both weapons");
|
||||||
bar.actionEnded();
|
bar.actionEnded();
|
||||||
|
|
||||||
// Not enough AP to move
|
|
||||||
ship.setValue("power", 3);
|
ship.setValue("power", 3);
|
||||||
bar.action_icons[1].processClick();
|
bar.updateSelectedActionPower(3, 0, bar.action_icons[1].action);
|
||||||
checkFading([0, 1, 2], [3]);
|
checkFading([0, 1, 2], [3], "Not enough AP to move");
|
||||||
bar.actionEnded();
|
bar.actionEnded();
|
||||||
|
|
||||||
// Dynamic AP usage for move actions
|
// Dynamic AP usage for move actions
|
||||||
ship.setValue("power", 6);
|
ship.setValue("power", 6);
|
||||||
bar.action_icons[0].processClick();
|
bar.updateSelectedActionPower(2, 0, bar.action_icons[0].action);
|
||||||
checkFading([], [0, 1, 2, 3]);
|
checkFading([2], [0, 1, 3], "2 move power used");
|
||||||
bar.action_icons[0].processHover(Target.newFromLocation(2, 8));
|
bar.updateSelectedActionPower(4, 0, bar.action_icons[0].action);
|
||||||
checkFading([2], [0, 1, 3]);
|
checkFading([1, 2], [0, 3], "4 move power used");
|
||||||
bar.action_icons[0].processHover(Target.newFromLocation(3, 8));
|
bar.updateSelectedActionPower(6, 0, bar.action_icons[0].action);
|
||||||
checkFading([1, 2], [0, 3]);
|
checkFading([0, 1, 2], [3], "6 move power used");
|
||||||
bar.action_icons[0].processHover(Target.newFromLocation(4, 8));
|
bar.updateSelectedActionPower(8, 0, bar.action_icons[0].action);
|
||||||
checkFading([0, 1, 2], [3]);
|
checkFading([0, 1, 2], [3], "8 move power used");
|
||||||
bar.action_icons[0].processHover(Target.newFromLocation(5, 8));
|
|
||||||
checkFading([0, 1, 2], [3]);
|
|
||||||
bar.actionEnded();
|
bar.actionEnded();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ module TS.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Update the power indicator
|
* Update the power indicator
|
||||||
*/
|
*/
|
||||||
updatePower(selected_action = 0): void {
|
updatePower(move_power = 0, fire_power = 0): void {
|
||||||
let current_power = this.power.children.length;
|
let current_power = this.power.children.length;
|
||||||
let power_capacity = this.ship_power_capacity;
|
let power_capacity = this.ship_power_capacity;
|
||||||
|
|
||||||
|
@ -162,14 +162,16 @@ module TS.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
let power_value = this.ship_power_value;
|
let power_value = this.ship_power_value;
|
||||||
let remaining_power = power_value - selected_action;
|
let remaining_power = power_value - move_power - fire_power;
|
||||||
this.power.children.forEach((obj, idx) => {
|
this.power.children.forEach((obj, idx) => {
|
||||||
let img = <Phaser.Image>obj;
|
let img = <Phaser.Image>obj;
|
||||||
let frame: number;
|
let frame: number;
|
||||||
if (idx < remaining_power) {
|
if (idx < remaining_power) {
|
||||||
frame = 0;
|
frame = 0;
|
||||||
} else if (idx < power_value) {
|
} else if (idx < remaining_power + move_power) {
|
||||||
frame = 2;
|
frame = 2;
|
||||||
|
} else if (idx < power_value) {
|
||||||
|
frame = 3;
|
||||||
} else {
|
} else {
|
||||||
frame = 1;
|
frame = 1;
|
||||||
}
|
}
|
||||||
|
@ -179,23 +181,34 @@ module TS.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set current action power usage.
|
* Temporarily set current action power usage.
|
||||||
*
|
*
|
||||||
* When an action is selected, this will fade the icons not available after the action would be done.
|
* When an action is selected, this will fade the icons not available after the action would be done.
|
||||||
* This will also highlight power usage in the power bar.
|
* This will also highlight power usage in the power bar.
|
||||||
*
|
*
|
||||||
* *power_usage* is the consumption of currently selected action.
|
* *move_power* and *fire_power* is the consumption of currently selected action/target.
|
||||||
*/
|
*/
|
||||||
updateSelectedActionPower(power_usage: number, action: BaseAction): void {
|
updateSelectedActionPower(move_power: number, fire_power: number, action: BaseAction): void {
|
||||||
var remaining_ap = this.ship ? (this.ship.values.power.get() - power_usage) : 0;
|
var remaining_ap = this.ship ? (this.ship.getValue("power") - move_power - fire_power) : 0;
|
||||||
if (remaining_ap < 0) {
|
if (remaining_ap < 0) {
|
||||||
remaining_ap = 0;
|
remaining_ap = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.action_icons.forEach((icon: ActionIcon) => {
|
this.action_icons.forEach(icon => {
|
||||||
icon.updateFadingStatus(remaining_ap, action);
|
icon.updateFadingStatus(remaining_ap, action);
|
||||||
});
|
});
|
||||||
this.updatePower(power_usage);
|
this.updatePower(move_power, fire_power);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporarily set power status for a given move-fire simulation
|
||||||
|
*/
|
||||||
|
updateFromSimulation(action: BaseAction, simulation: MoveFireResult) {
|
||||||
|
if (simulation.complete) {
|
||||||
|
this.updateSelectedActionPower(simulation.total_move_ap, simulation.total_fire_ap, action);
|
||||||
|
} else {
|
||||||
|
this.updateSelectedActionPower(0, 0, action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,7 +40,7 @@ module TS.SpaceTac.UI {
|
||||||
|
|
||||||
// Create an icon for a single ship action
|
// Create an icon for a single ship action
|
||||||
constructor(bar: ActionBar, x: number, y: number, ship: Ship, action: BaseAction, position: number) {
|
constructor(bar: ActionBar, x: number, y: number, ship: Ship, action: BaseAction, position: number) {
|
||||||
super(bar.game, x, y, "battle-actionbar-icon");
|
super(bar.game, x, y, "battle-actionbar-icon", () => this.processClick());
|
||||||
|
|
||||||
this.bar = bar;
|
this.bar = bar;
|
||||||
this.battleview = bar.battleview;
|
this.battleview = bar.battleview;
|
||||||
|
@ -83,19 +83,6 @@ module TS.SpaceTac.UI {
|
||||||
ActionTooltip.fill(filler, this.ship, this.action, position);
|
ActionTooltip.fill(filler, this.ship, this.action, position);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
UITools.setHoverClick(this,
|
|
||||||
() => {
|
|
||||||
if (!this.bar.hasActionSelected()) {
|
|
||||||
this.battleview.arena.range_hint.update(this.ship, this.action);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (!this.bar.hasActionSelected()) {
|
|
||||||
this.battleview.arena.range_hint.clear();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => this.processClick()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
this.updateActiveStatus(true);
|
this.updateActiveStatus(true);
|
||||||
|
@ -120,13 +107,10 @@ module TS.SpaceTac.UI {
|
||||||
this.bar.actionStarted();
|
this.bar.actionStarted();
|
||||||
|
|
||||||
// Update range hint
|
// Update range hint
|
||||||
if (this.battleview.arena.range_hint) {
|
if (this.battleview.arena.range_hint && this.action instanceof MoveAction) {
|
||||||
this.battleview.arena.range_hint.update(this.ship, this.action);
|
this.battleview.arena.range_hint.update(this.ship, this.action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fading statuses
|
|
||||||
this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.ship, null), this.action);
|
|
||||||
|
|
||||||
// Set the selected state
|
// Set the selected state
|
||||||
this.setSelected(true);
|
this.setSelected(true);
|
||||||
|
|
||||||
|
@ -134,15 +118,7 @@ module TS.SpaceTac.UI {
|
||||||
let sprite = this.battleview.arena.findShipSprite(this.ship);
|
let sprite = this.battleview.arena.findShipSprite(this.ship);
|
||||||
if (sprite) {
|
if (sprite) {
|
||||||
// Switch to targetting mode (will apply action when a target is selected)
|
// Switch to targetting mode (will apply action when a target is selected)
|
||||||
this.targetting = this.battleview.enterTargettingMode();
|
this.targetting = this.battleview.enterTargettingMode(this.action);
|
||||||
if (this.targetting) {
|
|
||||||
this.targetting.setSource(sprite);
|
|
||||||
this.targetting.targetSelected.add(this.processSelection, this);
|
|
||||||
this.targetting.targetHovered.add(this.processHover, this);
|
|
||||||
if (this.action instanceof MoveAction) {
|
|
||||||
this.targetting.setApIndicatorsInterval(this.action.getDistanceByActionPoint(this.ship));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No target needed, apply action immediately
|
// No target needed, apply action immediately
|
||||||
|
@ -150,16 +126,6 @@ module TS.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when a target is hovered
|
|
||||||
// This will check the target against current action and adjust it if needed
|
|
||||||
processHover(target: Target): void {
|
|
||||||
let correct_target = this.action.checkTarget(this.ship, target);
|
|
||||||
if (this.targetting) {
|
|
||||||
this.targetting.setTarget(correct_target, false, this.action.getBlastRadius(this.ship));
|
|
||||||
}
|
|
||||||
this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.ship, correct_target), this.action);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when a target is selected
|
// Called when a target is selected
|
||||||
processSelection(target: Target | null): void {
|
processSelection(target: Target | null): void {
|
||||||
if (this.action.apply(this.ship, target)) {
|
if (this.action.apply(this.ship, target)) {
|
||||||
|
|
|
@ -67,6 +67,9 @@ module TS.SpaceTac.UI {
|
||||||
background.onInputUp.add(() => {
|
background.onInputUp.add(() => {
|
||||||
battleview.cursorClicked();
|
battleview.cursorClicked();
|
||||||
});
|
});
|
||||||
|
background.onInputOut.add(() => {
|
||||||
|
battleview.targetting.setTarget(null);
|
||||||
|
});
|
||||||
|
|
||||||
// Watch mouse move to capture hovering over background
|
// Watch mouse move to capture hovering over background
|
||||||
this.input_callback = this.game.input.addMoveCallback((pointer: Phaser.Pointer) => {
|
this.input_callback = this.game.input.addMoveCallback((pointer: Phaser.Pointer) => {
|
||||||
|
@ -234,13 +237,6 @@ module TS.SpaceTac.UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Highlight ships that would be the target of current action
|
|
||||||
*/
|
|
||||||
highlightTargets(ships: Ship[]): void {
|
|
||||||
this.ship_sprites.forEach(sprite => sprite.setTargetted(contains(ships, sprite.ship)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch the tactical mode (shows information on all ships, and fades background)
|
* Switch the tactical mode (shows information on all ships, and fades background)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,9 +17,6 @@ module TS.SpaceTac.UI {
|
||||||
// Statis effect
|
// Statis effect
|
||||||
stasis: Phaser.Image
|
stasis: Phaser.Image
|
||||||
|
|
||||||
// Target effect
|
|
||||||
target: Phaser.Image
|
|
||||||
|
|
||||||
// HSP display
|
// HSP display
|
||||||
hull: ValueBar
|
hull: ValueBar
|
||||||
toggle_hull: Toggle
|
toggle_hull: Toggle
|
||||||
|
@ -53,7 +50,7 @@ module TS.SpaceTac.UI {
|
||||||
this.sprite = new Phaser.Button(this.game, 0, 0, "ship-" + ship.model.code + "-sprite");
|
this.sprite = new Phaser.Button(this.game, 0, 0, "ship-" + ship.model.code + "-sprite");
|
||||||
this.sprite.rotation = ship.arena_angle;
|
this.sprite.rotation = ship.arena_angle;
|
||||||
this.sprite.anchor.set(0.5, 0.5);
|
this.sprite.anchor.set(0.5, 0.5);
|
||||||
this.sprite.scale.set(64 / this.sprite.width);
|
this.sprite.scale.set(0.25);
|
||||||
this.add(this.sprite);
|
this.add(this.sprite);
|
||||||
|
|
||||||
// Add stasis effect
|
// Add stasis effect
|
||||||
|
@ -62,12 +59,6 @@ module TS.SpaceTac.UI {
|
||||||
this.stasis.visible = false;
|
this.stasis.visible = false;
|
||||||
this.add(this.stasis);
|
this.add(this.stasis);
|
||||||
|
|
||||||
// Add target effect
|
|
||||||
this.target = new Phaser.Image(this.game, 0, 0, "battle-arena-ship-frames", 5);
|
|
||||||
this.target.anchor.set(0.5, 0.5);
|
|
||||||
this.target.visible = false;
|
|
||||||
this.add(this.target);
|
|
||||||
|
|
||||||
// Add playing effect
|
// Add playing effect
|
||||||
this.frame = new Phaser.Image(this.game, 0, 0, "battle-arena-ship-frames", this.enemy ? 0 : 1);
|
this.frame = new Phaser.Image(this.game, 0, 0, "battle-arena-ship-frames", this.enemy ? 0 : 1);
|
||||||
this.frame.anchor.set(0.5, 0.5);
|
this.frame.anchor.set(0.5, 0.5);
|
||||||
|
@ -202,15 +193,6 @@ module TS.SpaceTac.UI {
|
||||||
this.frame.frame = (playing ? 3 : 0) + (this.enemy ? 0 : 1);
|
this.frame.frame = (playing ? 3 : 0) + (this.enemy ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the ship as target of current action
|
|
||||||
*
|
|
||||||
* This will toggle the visibility of target indicator
|
|
||||||
*/
|
|
||||||
setTargetted(targetted: boolean): void {
|
|
||||||
this.target.visible = targetted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the dead effect (stasis)
|
* Activate the dead effect (stasis)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,34 +6,26 @@ module TS.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
it("forwards events in targetting mode", function () {
|
it("forwards events in targetting mode", function () {
|
||||||
let battleview = testgame.battleview;
|
let battleview = testgame.battleview;
|
||||||
expect(battleview.targetting).toBeNull();
|
expect(battleview.targetting.active).toBe(false);
|
||||||
battleview.setInteractionEnabled(true);
|
battleview.setInteractionEnabled(true);
|
||||||
|
|
||||||
|
spyOn(battleview.targetting, "validate").and.stub();
|
||||||
|
|
||||||
battleview.cursorInSpace(5, 5);
|
battleview.cursorInSpace(5, 5);
|
||||||
|
|
||||||
expect(battleview.targetting).toBeNull();
|
expect(battleview.targetting.active).toBe(false);
|
||||||
|
|
||||||
// Enter targetting mode
|
// Enter targetting mode
|
||||||
var result = nn(battleview.enterTargettingMode());
|
let weapon = TestTools.addWeapon(nn(battleview.battle.playing_ship), 10);
|
||||||
|
battleview.enterTargettingMode(weapon.action);
|
||||||
|
|
||||||
expect(battleview.targetting).toBeTruthy();
|
expect(battleview.targetting.active).toBe(true);
|
||||||
expect(result).toBe(nn(battleview.targetting));
|
|
||||||
|
|
||||||
// Collect targetting events
|
|
||||||
var hovered: (Target | null)[] = [];
|
|
||||||
var clicked: Target[] = [];
|
|
||||||
result.targetHovered.add((target: Target) => {
|
|
||||||
hovered.push(target);
|
|
||||||
});
|
|
||||||
result.targetSelected.add((target: Target) => {
|
|
||||||
clicked.push(target);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Forward selection in space
|
// Forward selection in space
|
||||||
battleview.cursorInSpace(8, 4);
|
battleview.cursorInSpace(8, 4);
|
||||||
|
|
||||||
expect(battleview.ship_hovered).toBeNull();
|
expect(battleview.ship_hovered).toBeNull();
|
||||||
expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromLocation(8, 4));
|
expect(battleview.targetting.target).toEqual(Target.newFromLocation(8, 4));
|
||||||
|
|
||||||
// Process a click on space
|
// Process a click on space
|
||||||
battleview.cursorClicked();
|
battleview.cursorClicked();
|
||||||
|
@ -42,19 +34,19 @@ module TS.SpaceTac.UI.Specs {
|
||||||
battleview.cursorOnShip(battleview.battle.play_order[0]);
|
battleview.cursorOnShip(battleview.battle.play_order[0]);
|
||||||
|
|
||||||
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
||||||
expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
expect(battleview.targetting.target).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
||||||
|
|
||||||
// Don't leave a ship we're not hovering
|
// Don't leave a ship we're not hovering
|
||||||
battleview.cursorOffShip(battleview.battle.play_order[1]);
|
battleview.cursorOffShip(battleview.battle.play_order[1]);
|
||||||
|
|
||||||
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
||||||
expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
expect(battleview.targetting.target).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
||||||
|
|
||||||
// Don't move in space while on ship
|
// Don't move in space while on ship
|
||||||
battleview.cursorInSpace(1, 3);
|
battleview.cursorInSpace(1, 3);
|
||||||
|
|
||||||
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
||||||
expect(nn(battleview.targetting).target_corrected).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
expect(battleview.targetting.target).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
||||||
|
|
||||||
// Process a click on ship
|
// Process a click on ship
|
||||||
battleview.cursorClicked();
|
battleview.cursorClicked();
|
||||||
|
@ -63,12 +55,12 @@ module TS.SpaceTac.UI.Specs {
|
||||||
battleview.cursorOffShip(battleview.battle.play_order[0]);
|
battleview.cursorOffShip(battleview.battle.play_order[0]);
|
||||||
|
|
||||||
expect(battleview.ship_hovered).toBeNull();
|
expect(battleview.ship_hovered).toBeNull();
|
||||||
expect(nn(battleview.targetting).target_corrected).toBeNull();
|
expect(battleview.targetting.target).toBeNull();
|
||||||
|
|
||||||
// Quit targetting
|
// Quit targetting
|
||||||
battleview.exitTargettingMode();
|
battleview.exitTargettingMode();
|
||||||
|
|
||||||
expect(battleview.targetting).toBeNull();
|
expect(battleview.targetting.active).toBe(false);
|
||||||
|
|
||||||
// Events process normally
|
// Events process normally
|
||||||
battleview.cursorInSpace(8, 4);
|
battleview.cursorInSpace(8, 4);
|
||||||
|
@ -78,17 +70,6 @@ module TS.SpaceTac.UI.Specs {
|
||||||
|
|
||||||
// Quit twice don't do anything
|
// Quit twice don't do anything
|
||||||
battleview.exitTargettingMode();
|
battleview.exitTargettingMode();
|
||||||
|
|
||||||
// Check collected targetting events
|
|
||||||
expect(hovered).toEqual([
|
|
||||||
Target.newFromLocation(8, 4),
|
|
||||||
Target.newFromShip(battleview.battle.play_order[0]),
|
|
||||||
null
|
|
||||||
]);
|
|
||||||
expect(clicked).toEqual([
|
|
||||||
Target.newFromLocation(8, 4),
|
|
||||||
Target.newFromShip(battleview.battle.play_order[0]),
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,56 +3,55 @@
|
||||||
module TS.SpaceTac.UI {
|
module TS.SpaceTac.UI {
|
||||||
// Interactive view of a Battle
|
// Interactive view of a Battle
|
||||||
export class BattleView extends BaseView {
|
export class BattleView extends BaseView {
|
||||||
|
|
||||||
// Displayed battle
|
// Displayed battle
|
||||||
battle: Battle;
|
battle: Battle
|
||||||
|
|
||||||
// Interacting player
|
// Interacting player
|
||||||
player: Player;
|
player: Player
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
layer_background: Phaser.Group;
|
layer_background: Phaser.Group
|
||||||
layer_arena: Phaser.Group;
|
layer_arena: Phaser.Group
|
||||||
layer_borders: Phaser.Group;
|
layer_borders: Phaser.Group
|
||||||
layer_overlay: Phaser.Group;
|
layer_overlay: Phaser.Group
|
||||||
layer_dialogs: Phaser.Group;
|
layer_dialogs: Phaser.Group
|
||||||
layer_sheets: Phaser.Group;
|
layer_sheets: Phaser.Group
|
||||||
|
|
||||||
// Battleground container
|
// Battleground container
|
||||||
arena: Arena;
|
arena: Arena
|
||||||
|
|
||||||
// Background image
|
// Background image
|
||||||
background: Phaser.Image | null;
|
background: Phaser.Image | null
|
||||||
|
|
||||||
// Targetting mode (null if we're not in this mode)
|
// Targetting mode (null if we're not in this mode)
|
||||||
targetting: Targetting | null;
|
targetting: Targetting
|
||||||
|
|
||||||
// Ship list
|
// Ship list
|
||||||
ship_list: ShipList;
|
ship_list: ShipList
|
||||||
|
|
||||||
// Action bar
|
// Action bar
|
||||||
action_bar: ActionBar;
|
action_bar: ActionBar
|
||||||
|
|
||||||
// Currently hovered ship
|
// Currently hovered ship
|
||||||
ship_hovered: Ship | null;
|
ship_hovered: Ship | null
|
||||||
|
|
||||||
// Ship tooltip
|
// Ship tooltip
|
||||||
ship_tooltip: ShipTooltip;
|
ship_tooltip: ShipTooltip
|
||||||
|
|
||||||
// Outcome dialog layer
|
// Outcome dialog layer
|
||||||
outcome_layer: Phaser.Group;
|
outcome_layer: Phaser.Group
|
||||||
|
|
||||||
// Character sheet
|
// Character sheet
|
||||||
character_sheet: CharacterSheet;
|
character_sheet: CharacterSheet
|
||||||
|
|
||||||
// Subscription to the battle log
|
// Subscription to the battle log
|
||||||
log_processor: LogProcessor;
|
log_processor: LogProcessor
|
||||||
|
|
||||||
// True if player interaction is allowed
|
// True if player interaction is allowed
|
||||||
interacting: boolean;
|
interacting: boolean
|
||||||
|
|
||||||
// Tactical mode toggle
|
// Tactical mode toggle
|
||||||
toggle_tactical_mode: Toggle;
|
toggle_tactical_mode: Toggle
|
||||||
|
|
||||||
// Init the view, binding it to a specific battle
|
// Init the view, binding it to a specific battle
|
||||||
init(player: Player, battle: Battle) {
|
init(player: Player, battle: Battle) {
|
||||||
|
@ -60,7 +59,6 @@ module TS.SpaceTac.UI {
|
||||||
|
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.battle = battle;
|
this.battle = battle;
|
||||||
this.targetting = null;
|
|
||||||
this.ship_hovered = null;
|
this.ship_hovered = null;
|
||||||
this.background = null;
|
this.background = null;
|
||||||
|
|
||||||
|
@ -104,6 +102,10 @@ module TS.SpaceTac.UI {
|
||||||
this.character_sheet = new CharacterSheet(this, -this.getWidth());
|
this.character_sheet = new CharacterSheet(this, -this.getWidth());
|
||||||
this.layer_sheets.add(this.character_sheet);
|
this.layer_sheets.add(this.character_sheet);
|
||||||
|
|
||||||
|
// Targetting info
|
||||||
|
this.targetting = new Targetting(this, this.action_bar);
|
||||||
|
this.targetting.moveToLayer(this.arena.layer_targetting);
|
||||||
|
|
||||||
// "Battle" animation
|
// "Battle" animation
|
||||||
this.displayFightMessage();
|
this.displayFightMessage();
|
||||||
|
|
||||||
|
@ -150,8 +152,6 @@ module TS.SpaceTac.UI {
|
||||||
|
|
||||||
// Leaving the view, we unbind the battle
|
// Leaving the view, we unbind the battle
|
||||||
shutdown() {
|
shutdown() {
|
||||||
this.exitTargettingMode();
|
|
||||||
|
|
||||||
this.log_processor.destroy();
|
this.log_processor.destroy();
|
||||||
|
|
||||||
super.shutdown();
|
super.shutdown();
|
||||||
|
@ -172,7 +172,7 @@ module TS.SpaceTac.UI {
|
||||||
|
|
||||||
// Method called when cursor starts hovering over a ship (or its icon)
|
// Method called when cursor starts hovering over a ship (or its icon)
|
||||||
cursorOnShip(ship: Ship): void {
|
cursorOnShip(ship: Ship): void {
|
||||||
if (!this.targetting || ship.alive) {
|
if (!this.targetting.active || ship.alive) {
|
||||||
this.setShipHovered(ship);
|
this.setShipHovered(ship);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,15 +187,15 @@ module TS.SpaceTac.UI {
|
||||||
// Method called when cursor moves in space
|
// Method called when cursor moves in space
|
||||||
cursorInSpace(x: number, y: number): void {
|
cursorInSpace(x: number, y: number): void {
|
||||||
if (!this.ship_hovered) {
|
if (!this.ship_hovered) {
|
||||||
if (this.targetting) {
|
if (this.targetting.active) {
|
||||||
this.targetting.setTargetSpace(x, y);
|
this.targetting.setTarget(Target.newFromLocation(x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method called when cursor has been clicked (in space or on a ship)
|
// Method called when cursor has been clicked (in space or on a ship)
|
||||||
cursorClicked(): void {
|
cursorClicked(): void {
|
||||||
if (this.targetting) {
|
if (this.targetting.active) {
|
||||||
this.targetting.validate();
|
this.targetting.validate();
|
||||||
} else if (this.ship_hovered && this.ship_hovered.getPlayer() == this.player && this.interacting) {
|
} else if (this.ship_hovered && this.ship_hovered.getPlayer() == this.player && this.interacting) {
|
||||||
this.character_sheet.show(this.ship_hovered);
|
this.character_sheet.show(this.ship_hovered);
|
||||||
|
@ -215,11 +215,11 @@ module TS.SpaceTac.UI {
|
||||||
this.ship_tooltip.hide();
|
this.ship_tooltip.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.targetting) {
|
if (this.targetting.active) {
|
||||||
if (ship) {
|
if (ship) {
|
||||||
this.targetting.setTargetShip(ship);
|
this.targetting.setTarget(Target.newFromShip(ship));
|
||||||
} else {
|
} else {
|
||||||
this.targetting.unsetTarget();
|
this.targetting.setTarget(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,25 +240,18 @@ module TS.SpaceTac.UI {
|
||||||
|
|
||||||
// Enter targetting mode
|
// Enter targetting mode
|
||||||
// While in this mode, the Targetting object will receive hover and click events, and handle them
|
// While in this mode, the Targetting object will receive hover and click events, and handle them
|
||||||
enterTargettingMode(): Targetting | null {
|
enterTargettingMode(action: BaseAction): Targetting | null {
|
||||||
if (!this.interacting) {
|
if (!this.interacting) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.targetting) {
|
this.targetting.setAction(action);
|
||||||
this.exitTargettingMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.targetting = new Targetting(this);
|
|
||||||
return this.targetting;
|
return this.targetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit targetting mode
|
// Exit targetting mode
|
||||||
exitTargettingMode(): void {
|
exitTargettingMode(): void {
|
||||||
if (this.targetting) {
|
this.targetting.setAction(null);
|
||||||
this.targetting.destroy();
|
|
||||||
}
|
|
||||||
this.targetting = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -43,7 +43,7 @@ module TS.SpaceTac.UI {
|
||||||
/**
|
/**
|
||||||
* Update displayed information
|
* Update displayed information
|
||||||
*/
|
*/
|
||||||
update(ship: Ship, action: BaseAction): void {
|
update(ship: Ship, action: BaseAction, location: ArenaLocation = ship.location): void {
|
||||||
let yescolor = 0x000000;
|
let yescolor = 0x000000;
|
||||||
let nocolor = 0x242022;
|
let nocolor = 0x242022;
|
||||||
this.info.clear();
|
this.info.clear();
|
||||||
|
@ -54,7 +54,7 @@ module TS.SpaceTac.UI {
|
||||||
this.info.drawRect(0, 0, this.width, this.height);
|
this.info.drawRect(0, 0, this.width, this.height);
|
||||||
|
|
||||||
this.info.beginFill(yescolor);
|
this.info.beginFill(yescolor);
|
||||||
this.info.drawCircle(ship.arena_x, ship.arena_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 safety = action.safety_distance / 2;
|
||||||
|
|
|
@ -2,68 +2,111 @@ module TS.SpaceTac.UI.Specs {
|
||||||
describe("Targetting", function () {
|
describe("Targetting", function () {
|
||||||
let testgame = setupBattleview();
|
let testgame = setupBattleview();
|
||||||
|
|
||||||
it("broadcasts hovering and selection events", function () {
|
it("draws simulation parts", function () {
|
||||||
var targetting = new Targetting(null);
|
let targetting = new Targetting(testgame.battleview, testgame.battleview.action_bar);
|
||||||
|
|
||||||
var hovered: Target[] = [];
|
let ship = nn(testgame.battleview.battle.playing_ship);
|
||||||
var selected: Target[] = [];
|
ship.setArenaPosition(10, 20);
|
||||||
targetting.targetHovered.add((target: Target) => {
|
let weapon = TestTools.addWeapon(ship);
|
||||||
hovered.push(target);
|
let engine = TestTools.addEngine(ship, 12);
|
||||||
});
|
targetting.setAction(weapon.action);
|
||||||
targetting.targetSelected.add((target: Target) => {
|
|
||||||
selected.push(target);
|
|
||||||
});
|
|
||||||
|
|
||||||
targetting.setTargetSpace(1, 2);
|
let drawvector = spyOn(targetting, "drawVector").and.stub();
|
||||||
expect(hovered).toEqual([Target.newFromLocation(1, 2)]);
|
|
||||||
expect(selected).toEqual([]);
|
|
||||||
|
|
||||||
targetting.validate();
|
let part = {
|
||||||
expect(hovered).toEqual([Target.newFromLocation(1, 2)]);
|
action: weapon.action,
|
||||||
expect(selected).toEqual([Target.newFromLocation(1, 2)]);
|
target: new Target(50, 30),
|
||||||
|
ap: 5,
|
||||||
|
possible: true
|
||||||
|
};
|
||||||
|
targetting.drawPart(part, true, null);
|
||||||
|
expect(drawvector).toHaveBeenCalledTimes(1);
|
||||||
|
expect(drawvector).toHaveBeenCalledWith(0xdc6441, 10, 20, 50, 30, 0);
|
||||||
|
|
||||||
|
targetting.drawPart(part, false, null);
|
||||||
|
expect(drawvector).toHaveBeenCalledTimes(2);
|
||||||
|
expect(drawvector).toHaveBeenCalledWith(0x8e8e8e, 10, 20, 50, 30, 0);
|
||||||
|
|
||||||
|
targetting.setAction(engine.action);
|
||||||
|
part.action = engine.action;
|
||||||
|
targetting.drawPart(part, true, null);
|
||||||
|
expect(drawvector).toHaveBeenCalledTimes(3);
|
||||||
|
expect(drawvector).toHaveBeenCalledWith(0xe09c47, 10, 20, 50, 30, 12);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays action point indicators", function () {
|
it("updates impact indicators on ships inside the blast radius", function () {
|
||||||
let battleview = testgame.battleview;
|
let targetting = new Targetting(testgame.battleview, testgame.battleview.action_bar);
|
||||||
let source = new Phaser.Group(battleview.game, battleview.arena);
|
let ship = nn(testgame.battleview.battle.playing_ship);
|
||||||
source.position.set(0, 0);
|
|
||||||
|
|
||||||
let targetting = new Targetting(battleview);
|
let collect = spyOn(testgame.battleview.battle, "collectShipsInCircle").and.returnValues(
|
||||||
|
[new Ship(), new Ship(), new Ship()],
|
||||||
|
[new Ship(), new Ship()],
|
||||||
|
[]);
|
||||||
|
targetting.updateImpactIndicators(ship, new Target(20, 10), 50);
|
||||||
|
|
||||||
targetting.setSource(source);
|
expect(collect).toHaveBeenCalledTimes(1);
|
||||||
targetting.setTargetSpace(200, 100);
|
expect(collect).toHaveBeenCalledWith(new Target(20, 10), 50, true);
|
||||||
|
expect(targetting.fire_impact.children.length).toBe(3);
|
||||||
|
expect(targetting.fire_impact.visible).toBe(true);
|
||||||
|
|
||||||
|
targetting.updateImpactIndicators(ship, new Target(20, 11), 50);
|
||||||
|
|
||||||
|
expect(collect).toHaveBeenCalledTimes(2);
|
||||||
|
expect(collect).toHaveBeenCalledWith(new Target(20, 11), 50, true);
|
||||||
|
expect(targetting.fire_impact.children.length).toBe(2);
|
||||||
|
expect(targetting.fire_impact.visible).toBe(true);
|
||||||
|
|
||||||
|
let target = Target.newFromShip(new Ship());
|
||||||
|
targetting.updateImpactIndicators(ship, target, 0);
|
||||||
|
|
||||||
|
expect(collect).toHaveBeenCalledTimes(2);
|
||||||
|
expect(targetting.fire_impact.children.length).toBe(1);
|
||||||
|
expect(targetting.fire_impact.visible).toBe(true);
|
||||||
|
|
||||||
|
targetting.updateImpactIndicators(ship, new Target(20, 12), 50);
|
||||||
|
|
||||||
|
expect(collect).toHaveBeenCalledTimes(3);
|
||||||
|
expect(collect).toHaveBeenCalledWith(new Target(20, 12), 50, true);
|
||||||
|
expect(targetting.fire_impact.visible).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates graphics from simulation", function () {
|
||||||
|
let targetting = new Targetting(testgame.battleview, testgame.battleview.action_bar);
|
||||||
|
let ship = nn(testgame.battleview.battle.playing_ship);
|
||||||
|
|
||||||
|
let engine = TestTools.addEngine(ship, 8000);
|
||||||
|
let weapon = TestTools.addWeapon(ship, 30, 5, 100, 50);
|
||||||
|
targetting.setAction(weapon.action);
|
||||||
|
targetting.setTarget(Target.newFromLocation(156, 65));
|
||||||
|
|
||||||
|
spyOn(targetting, "simulate").and.callFake(() => {
|
||||||
|
let result = new MoveFireResult();
|
||||||
|
result.success = true;
|
||||||
|
result.complete = true;
|
||||||
|
result.need_move = true;
|
||||||
|
result.move_location = Target.newFromLocation(80, 20);
|
||||||
|
result.can_move = true;
|
||||||
|
result.can_end_move = true;
|
||||||
|
result.need_fire = true;
|
||||||
|
result.can_fire = true;
|
||||||
|
result.parts = [
|
||||||
|
{ action: engine.action, target: Target.newFromLocation(80, 20), ap: 1, possible: true },
|
||||||
|
{ action: weapon.action, target: Target.newFromLocation(156, 65), ap: 5, possible: true }
|
||||||
|
]
|
||||||
|
targetting.simulation = result;
|
||||||
|
});
|
||||||
targetting.update();
|
targetting.update();
|
||||||
targetting.updateApIndicators();
|
|
||||||
|
|
||||||
expect(targetting.ap_indicators.length).toBe(0);
|
expect(targetting.container.visible).toBe(true);
|
||||||
expect(battleview.arena.layer_targetting.children.length).toBe(3);
|
expect(targetting.drawn_info.visible).toBe(true);
|
||||||
|
expect(targetting.fire_arrow.visible).toBe(true);
|
||||||
targetting.setApIndicatorsInterval(Math.sqrt(5) * 20);
|
expect(targetting.fire_arrow.position).toEqual(jasmine.objectContaining({ x: 156, y: 65 }));
|
||||||
|
expect(targetting.fire_arrow.rotation).toBeCloseTo(0.534594, 5);
|
||||||
expect(targetting.ap_indicators.length).toBe(5);
|
expect(targetting.fire_blast.visible).toBe(true);
|
||||||
expect(battleview.arena.layer_targetting.children.length).toBe(3 + 5);
|
expect(targetting.fire_blast.position).toEqual(jasmine.objectContaining({ x: 156, y: 65 }));
|
||||||
expect(targetting.ap_indicators[0].position.x).toBe(0);
|
expect(targetting.move_ghost.visible).toBe(true);
|
||||||
expect(targetting.ap_indicators[0].position.y).toBe(0);
|
expect(targetting.move_ghost.position).toEqual(jasmine.objectContaining({ x: 80, y: 20 }));
|
||||||
expect(targetting.ap_indicators[1].position.x).toBeCloseTo(40);
|
expect(targetting.move_ghost.rotation).toBeCloseTo(0.534594, 5);
|
||||||
expect(targetting.ap_indicators[1].position.y).toBeCloseTo(20);
|
|
||||||
expect(targetting.ap_indicators[2].position.x).toBeCloseTo(80);
|
|
||||||
expect(targetting.ap_indicators[2].position.y).toBeCloseTo(40);
|
|
||||||
expect(targetting.ap_indicators[3].position.x).toBeCloseTo(120);
|
|
||||||
expect(targetting.ap_indicators[3].position.y).toBeCloseTo(60);
|
|
||||||
expect(targetting.ap_indicators[4].position.x).toBeCloseTo(160);
|
|
||||||
expect(targetting.ap_indicators[4].position.y).toBeCloseTo(80);
|
|
||||||
|
|
||||||
targetting.setApIndicatorsInterval(1000);
|
|
||||||
expect(targetting.ap_indicators.length).toBe(1);
|
|
||||||
expect(battleview.arena.layer_targetting.children.length).toBe(3 + 1);
|
|
||||||
|
|
||||||
targetting.setApIndicatorsInterval(1);
|
|
||||||
expect(targetting.ap_indicators.length).toBe(224);
|
|
||||||
expect(battleview.arena.layer_targetting.children.length).toBe(3 + 224);
|
|
||||||
|
|
||||||
targetting.destroy();
|
|
||||||
|
|
||||||
expect(battleview.arena.layer_targetting.children.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,199 +1,265 @@
|
||||||
module TS.SpaceTac.UI {
|
module TS.SpaceTac.UI {
|
||||||
// Targetting system
|
/**
|
||||||
// Allows to pick a target for an action
|
* Targetting system on the arena
|
||||||
|
*
|
||||||
|
* This system handles choosing a target for currently selected action, and displays a visual aid.
|
||||||
|
*/
|
||||||
export class Targetting {
|
export class Targetting {
|
||||||
// Initial target (as pointed by the user)
|
// Container group
|
||||||
target_initial: Target | null;
|
container: Phaser.Group
|
||||||
line_initial: Phaser.Graphics;
|
|
||||||
|
|
||||||
// Corrected target (applying action rules)
|
// Current action
|
||||||
target_corrected: Target | null;
|
ship: Ship | null = null
|
||||||
line_corrected: Phaser.Graphics;
|
action: BaseAction | null = null
|
||||||
|
target: Target | null = null
|
||||||
|
simulation = new MoveFireResult()
|
||||||
|
|
||||||
// Circle for effect radius
|
// Movement projector
|
||||||
blast_radius: number;
|
drawn_info: Phaser.Graphics
|
||||||
blast: Phaser.Image;
|
move_ghost: Phaser.Image
|
||||||
|
|
||||||
// Signal to receive hovering events
|
// Fire projector
|
||||||
targetHovered: Phaser.Signal;
|
fire_arrow: Phaser.Image
|
||||||
|
fire_blast: Phaser.Image
|
||||||
|
fire_impact: Phaser.Group
|
||||||
|
|
||||||
// Signal to receive targetting events
|
// Collaborators to update
|
||||||
targetSelected: Phaser.Signal;
|
actionbar: ActionBar
|
||||||
|
|
||||||
// AP usage display
|
// Access to the parent view
|
||||||
ap_interval: number = 0;
|
view: BaseView
|
||||||
ap_indicators: Phaser.Image[] = [];
|
|
||||||
|
|
||||||
// Access to the parent battle view
|
constructor(view: BaseView, actionbar: ActionBar) {
|
||||||
private battleview: BattleView | null;
|
this.view = view;
|
||||||
|
this.actionbar = actionbar;
|
||||||
|
|
||||||
// Source of the targetting
|
this.container = view.add.group();
|
||||||
private source: PIXI.DisplayObject | null;
|
|
||||||
|
|
||||||
// Create a default targetting mode
|
|
||||||
constructor(battleview: BattleView | null) {
|
|
||||||
this.battleview = battleview;
|
|
||||||
this.targetHovered = new Phaser.Signal();
|
|
||||||
this.targetSelected = new Phaser.Signal();
|
|
||||||
|
|
||||||
// Visual effects
|
// Visual effects
|
||||||
if (battleview) {
|
this.drawn_info = new Phaser.Graphics(view.game, 0, 0);
|
||||||
this.blast = new Phaser.Image(battleview.game, 0, 0, "battle-arena-blast");
|
this.drawn_info.visible = false;
|
||||||
this.blast.anchor.set(0.5, 0.5);
|
this.move_ghost = new Phaser.Image(view.game, 0, 0, "common-transparent");
|
||||||
this.blast.visible = false;
|
this.move_ghost.anchor.set(0.5, 0.5);
|
||||||
battleview.arena.layer_targetting.add(this.blast);
|
this.move_ghost.alpha = 0.8;
|
||||||
this.line_initial = new Phaser.Graphics(battleview.game, 0, 0);
|
this.move_ghost.visible = false;
|
||||||
this.line_initial.visible = false;
|
this.fire_arrow = new Phaser.Image(view.game, 0, 0, "battle-arena-indicators", 0);
|
||||||
battleview.arena.layer_targetting.add(this.line_initial);
|
this.fire_arrow.anchor.set(1, 0.5);
|
||||||
this.line_corrected = new Phaser.Graphics(battleview.game, 0, 0);
|
this.fire_arrow.visible = false;
|
||||||
this.line_corrected.visible = false;
|
this.fire_impact = new Phaser.Group(view.game);
|
||||||
battleview.arena.layer_targetting.add(this.line_corrected);
|
this.fire_impact.visible = false;
|
||||||
}
|
this.fire_blast = new Phaser.Image(view.game, 0, 0, "battle-arena-blast");
|
||||||
|
this.fire_blast.anchor.set(0.5, 0.5);
|
||||||
|
this.fire_blast.visible = false;
|
||||||
|
|
||||||
this.source = null;
|
this.container.add(this.fire_impact);
|
||||||
this.target_initial = null;
|
this.container.add(this.fire_blast);
|
||||||
this.target_corrected = null;
|
this.container.add(this.drawn_info);
|
||||||
|
this.container.add(this.fire_arrow);
|
||||||
|
this.container.add(this.move_ghost);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
/**
|
||||||
destroy(): void {
|
* Move to a given view layer
|
||||||
this.targetHovered.dispose();
|
*/
|
||||||
this.targetSelected.dispose();
|
moveToLayer(layer: Phaser.Group): void {
|
||||||
if (this.line_initial) {
|
layer.add(this.container);
|
||||||
this.line_initial.destroy();
|
|
||||||
}
|
|
||||||
if (this.line_corrected) {
|
|
||||||
this.line_corrected.destroy();
|
|
||||||
}
|
|
||||||
if (this.blast) {
|
|
||||||
this.blast.destroy();
|
|
||||||
}
|
|
||||||
this.ap_indicators.forEach(indicator => indicator.destroy());
|
|
||||||
if (this.battleview) {
|
|
||||||
this.battleview.arena.highlightTargets([]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set AP indicators to display at fixed interval along the line
|
/**
|
||||||
setApIndicatorsInterval(interval: number) {
|
* Indicator that the targetting is currently active
|
||||||
this.ap_interval = interval;
|
*/
|
||||||
this.updateApIndicators();
|
get active(): boolean {
|
||||||
|
return (this.ship && this.action) ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update visual effects for current targetting
|
/**
|
||||||
update(): void {
|
* Draw a vector, with line and gradation
|
||||||
if (this.battleview) {
|
*/
|
||||||
if (this.source && this.target_initial) {
|
drawVector(color: number, x1: number, y1: number, x2: number, y2: number, gradation = 0) {
|
||||||
this.line_initial.clear();
|
let line = this.drawn_info;
|
||||||
this.line_initial.lineStyle(3, 0x666666);
|
line.lineStyle(6, color);
|
||||||
this.line_initial.moveTo(this.source.x, this.source.y);
|
line.moveTo(x1, y1);
|
||||||
this.line_initial.lineTo(this.target_initial.x, this.target_initial.y);
|
line.lineTo(x2, y2);
|
||||||
this.line_initial.visible = true;
|
line.visible = true;
|
||||||
} else {
|
|
||||||
this.line_initial.visible = false;
|
if (gradation) {
|
||||||
|
let dx = x2 - x1;
|
||||||
|
let dy = y2 - y1;
|
||||||
|
let dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
let angle = Math.atan2(dy, dx);
|
||||||
|
dx = Math.cos(angle);
|
||||||
|
dy = Math.sin(angle);
|
||||||
|
for (let d = gradation; d <= dist; d += gradation) {
|
||||||
|
line.moveTo(x1 + dx * d + dy * 10, y1 + dy * d - dx * 10);
|
||||||
|
line.lineTo(x1 + dx * d - dy * 10, y1 + dy * d + dx * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.source && this.target_corrected) {
|
|
||||||
this.line_corrected.clear();
|
|
||||||
this.line_corrected.lineStyle(6, this.ap_interval ? 0xe09c47 : 0xDC6441);
|
|
||||||
this.line_corrected.moveTo(this.source.x, this.source.y);
|
|
||||||
this.line_corrected.lineTo(this.target_corrected.x, this.target_corrected.y);
|
|
||||||
this.line_corrected.visible = true;
|
|
||||||
} else {
|
|
||||||
this.line_corrected.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.target_corrected && this.blast_radius) {
|
|
||||||
this.blast.position.set(this.target_corrected.x, this.target_corrected.y);
|
|
||||||
this.blast.scale.set(this.blast_radius * 2 / 365);
|
|
||||||
this.blast.visible = true;
|
|
||||||
|
|
||||||
let targets = this.battleview.battle.collectShipsInCircle(this.target_corrected, this.blast_radius, true);
|
|
||||||
this.battleview.arena.highlightTargets(targets);
|
|
||||||
} else {
|
|
||||||
this.blast.visible = false;
|
|
||||||
|
|
||||||
this.battleview.arena.highlightTargets(this.target_corrected && this.target_corrected.ship ? [this.target_corrected.ship] : []);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateApIndicators();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the AP indicators display
|
/**
|
||||||
updateApIndicators() {
|
* Draw a part of the simulation
|
||||||
if (!this.battleview || !this.source) {
|
*/
|
||||||
|
drawPart(part: MoveFirePart, enabled = true, previous: MoveFirePart | null = null): void {
|
||||||
|
if (!this.ship) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get indicator count
|
let move = part.action instanceof MoveAction;
|
||||||
let count = 0;
|
let color = (enabled && part.possible) ? (move ? 0xe09c47 : 0xdc6441) : 0x8e8e8e;
|
||||||
let distance = 0;
|
let src = previous ? previous.target : this.ship.location;
|
||||||
if (this.line_corrected.visible && this.ap_interval > 0 && this.target_corrected) {
|
let gradation = part.action instanceof MoveAction ? part.action.distance_per_power : 0;
|
||||||
distance = this.target_corrected.getDistanceTo(Target.newFromLocation(this.source.x, this.source.y)) - 0.00001;
|
this.drawVector(color, src.x, src.y, part.target.x, part.target.y, gradation);
|
||||||
count = Math.ceil(distance / this.ap_interval);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update impact indicators
|
||||||
|
*/
|
||||||
|
updateImpactIndicators(ship: Ship, target: Target, radius: number): void {
|
||||||
|
let ships: Ship[];
|
||||||
|
if (radius) {
|
||||||
|
let battle = ship.getBattle();
|
||||||
|
if (battle) {
|
||||||
|
ships = battle.collectShipsInCircle(target, radius, true);
|
||||||
|
} else {
|
||||||
|
ships = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ships = target.ship ? [target.ship] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust object count to match
|
if (ships.length) {
|
||||||
while (this.ap_indicators.length < count) {
|
this.fire_impact.removeAll(true);
|
||||||
let indicator = new Phaser.Image(this.battleview.game, 0, 0, "battle-arena-ap-indicator");
|
ships.forEach(iship => {
|
||||||
indicator.anchor.set(0.5, 0.5);
|
let frame = this.view.add.image(iship.arena_x, iship.arena_y, "battle-arena-ship-frames", 5, this.fire_impact);
|
||||||
this.battleview.arena.layer_targetting.add(indicator);
|
frame.anchor.set(0.5);
|
||||||
this.ap_indicators.push(indicator);
|
|
||||||
}
|
|
||||||
while (this.ap_indicators.length > count) {
|
|
||||||
this.ap_indicators[this.ap_indicators.length - 1].destroy();
|
|
||||||
this.ap_indicators.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spread indicators
|
|
||||||
if (count > 0 && distance > 0 && this.target_corrected) {
|
|
||||||
let source = this.source;
|
|
||||||
let dx = this.ap_interval * (this.target_corrected.x - source.x) / distance;
|
|
||||||
let dy = this.ap_interval * (this.target_corrected.y - source.y) / distance;
|
|
||||||
this.ap_indicators.forEach((indicator, index) => {
|
|
||||||
indicator.position.set(source.x + dx * index, source.y + dy * index);
|
|
||||||
});
|
});
|
||||||
|
this.fire_impact.visible = true;
|
||||||
|
} else {
|
||||||
|
this.fire_impact.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the source sprite for the targetting (for visual effects)
|
/**
|
||||||
setSource(sprite: PIXI.DisplayObject) {
|
* Update visual effects to show the simulation of current action/target
|
||||||
this.source = sprite;
|
*/
|
||||||
|
update(): void {
|
||||||
|
this.simulate();
|
||||||
|
if (this.ship && this.action && this.target) {
|
||||||
|
let simulation = this.simulation;
|
||||||
|
|
||||||
|
this.drawn_info.clear();
|
||||||
|
this.fire_arrow.visible = false;
|
||||||
|
this.move_ghost.visible = false;
|
||||||
|
|
||||||
|
if (simulation.success) {
|
||||||
|
let previous: MoveFirePart | null = null;
|
||||||
|
simulation.parts.forEach(part => {
|
||||||
|
this.drawPart(part, simulation.complete, previous);
|
||||||
|
previous = part;
|
||||||
|
});
|
||||||
|
this.fire_arrow.frame = simulation.complete ? 0 : 1;
|
||||||
|
|
||||||
|
let from = simulation.need_fire ? simulation.move_location : this.ship.location;
|
||||||
|
let angle = Math.atan2(this.target.y - from.y, this.target.x - from.x);
|
||||||
|
|
||||||
|
if (simulation.need_move) {
|
||||||
|
this.move_ghost.visible = true;
|
||||||
|
this.move_ghost.position.set(simulation.move_location.x, simulation.move_location.y);
|
||||||
|
this.move_ghost.rotation = angle;
|
||||||
|
} else {
|
||||||
|
this.move_ghost.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simulation.need_fire) {
|
||||||
|
let blast = this.action.getBlastRadius(this.ship);
|
||||||
|
if (blast) {
|
||||||
|
this.fire_blast.position.set(this.target.x, this.target.y);
|
||||||
|
this.fire_blast.scale.set(blast * 2 / 365);
|
||||||
|
this.fire_blast.alpha = simulation.can_fire ? 1 : 0.5;
|
||||||
|
this.fire_blast.visible = true;
|
||||||
|
} else {
|
||||||
|
this.fire_blast.visible = false;
|
||||||
|
}
|
||||||
|
this.updateImpactIndicators(this.ship, this.target, blast);
|
||||||
|
|
||||||
|
this.fire_arrow.position.set(this.target.x, this.target.y);
|
||||||
|
this.fire_arrow.rotation = angle;
|
||||||
|
this.fire_arrow.frame = simulation.complete ? 0 : 1;
|
||||||
|
this.fire_arrow.visible = true;
|
||||||
|
} else {
|
||||||
|
this.fire_blast.visible = false;
|
||||||
|
this.fire_impact.visible = false;
|
||||||
|
this.fire_arrow.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container.visible = true;
|
||||||
|
} else {
|
||||||
|
// TODO Display error
|
||||||
|
this.container.visible = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.container.visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a target from a target object
|
/**
|
||||||
setTarget(target: Target | null, dispatch: boolean = true, blast_radius: number = 0): void {
|
* Simulate current action
|
||||||
this.target_corrected = target;
|
*/
|
||||||
this.blast_radius = blast_radius;
|
simulate(): void {
|
||||||
if (dispatch) {
|
if (this.ship && this.action && this.target) {
|
||||||
this.target_initial = target ? copy(target) : null;
|
let simulator = new MoveFireSimulator(this.ship);
|
||||||
this.targetHovered.dispatch(this.target_corrected);
|
this.simulation = simulator.simulateAction(this.action, this.target, 1);
|
||||||
|
} else {
|
||||||
|
this.simulation = new MoveFireResult();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current targetting action, or null to stop targetting
|
||||||
|
*/
|
||||||
|
setAction(action: BaseAction | null): void {
|
||||||
|
if (action && action.equipment && action.equipment.attached_to && action.equipment.attached_to.ship) {
|
||||||
|
this.ship = action.equipment.attached_to.ship;
|
||||||
|
this.action = action;
|
||||||
|
|
||||||
|
this.move_ghost.loadTexture(`ship-${this.ship.model.code}-sprite`);
|
||||||
|
this.move_ghost.scale.set(0.25);
|
||||||
|
} else {
|
||||||
|
this.ship = null;
|
||||||
|
this.action = null;
|
||||||
|
}
|
||||||
|
this.target = null;
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set no target
|
/**
|
||||||
unsetTarget(dispatch: boolean = true): void {
|
* Set the target for current action
|
||||||
this.setTarget(null, dispatch);
|
*/
|
||||||
}
|
setTarget(target: Target | null): void {
|
||||||
|
this.target = target;
|
||||||
// Set the current target ship (when hovered)
|
this.update();
|
||||||
setTargetShip(ship: Ship, dispatch: boolean = true): void {
|
if (this.action) {
|
||||||
if (ship.alive) {
|
this.actionbar.updateFromSimulation(this.action, this.simulation);
|
||||||
this.setTarget(Target.newFromShip(ship), dispatch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current target in space (when hovered)
|
/**
|
||||||
setTargetSpace(x: number, y: number, dispatch: boolean = true): void {
|
* Validate the current target.
|
||||||
this.setTarget(Target.newFromLocation(x, y));
|
*
|
||||||
}
|
* This will make the needed approach and apply the action.
|
||||||
|
*/
|
||||||
// Validate the current target (when clicked)
|
|
||||||
// This will broadcast the targetSelected signal
|
|
||||||
validate(): void {
|
validate(): void {
|
||||||
this.targetSelected.dispatch(this.target_corrected);
|
this.simulate();
|
||||||
|
|
||||||
|
if (this.ship && this.simulation.complete) {
|
||||||
|
let ship = this.ship;
|
||||||
|
this.simulation.parts.forEach(part => {
|
||||||
|
if (part.possible) {
|
||||||
|
part.action.apply(ship, part.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.actionbar.actionEnded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|