feat(totalannihilation): add Megatank unit with multi-barrel weapons and rebalance combat
- Introduce Megatank: medium commander with Twin 150mm Cannon and Side Rocket Pods - Implement multi-barrel weapon system (barrels + barrelSpacing) in TALogic and TARules - Lower DEFENCE_MIN_SKILL from 4 to 3; make first Laser Tower proactive, not reactive - Rebalance unit triangle: Tank now counters Rocket Tank; rockets weak vs vehicle armour - Re-seed campaign missions m05 (67890) and m06 (80808) with updated maps and briefings - Skirmish default theme uses rules default (tropics) instead of hardcoded grasslands - Add intro video before main menu - Update sprite docs and verification tests for new balance
This commit is contained in:
parent
09a62f12cc
commit
045327d38a
Binary file not shown.
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 287 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 258 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -598,7 +598,7 @@
|
||||||
"id": "m05",
|
"id": "m05",
|
||||||
"name": "Tropic Vice",
|
"name": "Tropic Vice",
|
||||||
"theme": "tropics",
|
"theme": "tropics",
|
||||||
"seed": 71644,
|
"seed": 67890,
|
||||||
"playerArmy": "arm",
|
"playerArmy": "arm",
|
||||||
"playerCommander": "vance",
|
"playerCommander": "vance",
|
||||||
"enemies": [
|
"enemies": [
|
||||||
|
|
@ -628,7 +628,7 @@
|
||||||
{
|
{
|
||||||
"speaker": "ethel",
|
"speaker": "ethel",
|
||||||
"mood": "idle",
|
"mood": "idle",
|
||||||
"text": "Cy-Bro starts ahead and pushes early. Rocket tanks out-range everything CORE fields."
|
"text": "Cy-Bro starts ahead and pushes early. Rockets crack their base open — armour holds the line getting there."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"victoryLine": {
|
"victoryLine": {
|
||||||
|
|
@ -646,102 +646,102 @@
|
||||||
"h": 96,
|
"h": 96,
|
||||||
"theme": "tropics",
|
"theme": "tropics",
|
||||||
"rows": [
|
"rows": [
|
||||||
"~~r....^^^^^^^^^^^^...........rr~~~...r...^^^^^^^^^^^^...r...~~~rr...........^^^^^^^^^^^^....r~~",
|
"...............rr.rr.............rr....rrrr~~~....~~~rrrr....rr.............rr.rr...............",
|
||||||
"~~.....^^^^^^^^^^^^...........r.~~~........^^^....^^^........~~~.r...........^^^^^^^^^^^^.....~~",
|
"r.............rr..rr...................rrr~~~~~..~~~~~rrr...................rr..rr.............r",
|
||||||
"~~....rr^^^^^^^^^^^............r~~~~.r....................r.~~~~r............^^^^^^^^^^^rr....~~",
|
"..........rrrrrr...rrr...................r~~~~~..~~~~~r...................rrr...rrrrrr..........",
|
||||||
"~~~......^^^...................~~~~~~~rr................rr~~~~~~~...................^^^......~~~",
|
".........rrrrr.......rr..................~~~~~~~~~~~~~~..................rr.......rrrrr.........",
|
||||||
"~~~~.....rr....................~~~~~~~~~................~~~~~~~~~....................rr.....~~~~",
|
".......rr^^^^^............................~~~~~~~~~~~~............................^^^^^rr.......",
|
||||||
"~~~~......rr.....................~~~~~~rr..............rr~~~~~~.....................rr......~~~~",
|
"...r.^^^^^^^^^^..................................................................^^^^^^^^^^.r...",
|
||||||
"~~~~......rr..............rr.....~~.....rr............rr.....~~.....rr..............rr......~~~~",
|
"...r^^^^^^^^^^^^................................................................^^^^^^^^^^^^r...",
|
||||||
"~~~..................rrrrrrr.....~......rr............rr......~.....rrrrrrr..................~~~",
|
"..r^^^^^^^^^^^^^^^^...rr.........^r..........................r^.........rr...^^^^^^^^^^^^^^^^r..",
|
||||||
"~~~.................rrrrrrrr.....~r..........................r~.....rrrrrrrr.................~~~",
|
"..^^^^^^^^^^^^^^^^^^^^^rrrr...r^^^^..........................^^^^r...rrrr^^^^^^^^^^^^^^^^^^^^^..",
|
||||||
"~~~~................rrrr.............rr..................rr.............rrrr................~~~~",
|
".^^^^^^^^^^^^^^^^^^^^^^r.rr...^^^^^..........................^^^^^...rr.r^^^^^^^^^^^^^^^^^^^^^^.",
|
||||||
"~~~~~...............rrrr.............rr..................rr.............rrrr...............~~~~~",
|
"^^^^^^^^^^^^^^^^^^^^^^^^..r...^^^^^^^.r..................r.^^^^^^^...r..^^^^^^^^^^^^^^^^^^^^^^^^",
|
||||||
"~~~~~~................rrr...........rr....................rr...........rrr................~~~~~~",
|
"^^^^^^^^^^^^^^^^^^^^^^^^^^^....^^^^^^^^^................^^^^^^^^^....^^^^^^^^^^^^^^^^^^^^^^^^^^^",
|
||||||
"~~~~~~r...............rr................................................rr...............r~~~~~~",
|
"...^^^^..^.........^^^^^^^^.....^^^^^^^^^..............^^^^^^^^^.....^^^^^^^^.........^..^^^^...",
|
||||||
"~~~~~.rr................................................................................rr.~~~~~",
|
"...rrr.............^^^^^^^^.....^^^^^^^^^..............^^^^^^^^^.....^^^^^^^^.............rrr...",
|
||||||
"~~~...rr................................................................................rr...~~~",
|
"...........^^^.....^^^^^^^^....^^^^^^^^^................^^^^^^^^^....^^^^^^^^.....^^^...........",
|
||||||
"~~rr........................................................................................rr~~",
|
"..........^^^^^....^^^^^^^.....^^^^^^^....................^^^^^^^.....^^^^^^^....^^^^^..........",
|
||||||
"~~rr....................rr..............rr............rr..............rr....................rr~~",
|
"..........^^^^^.....^^^^rr.....^^^^^^...rr............rr...^^^^^^.....rr^^^^.....^^^^^..........",
|
||||||
".................rr....rrr..............rr............rr..............rrr....rr.................",
|
"...........^^^^....r^^^rrr.....^^^^^^..rrrr..........rrrr..^^^^^^.....rrr^^^r....^^^^...........",
|
||||||
".................rr..~~.....r....r...........rrrrrr...........r....r.....~~..rr.................",
|
"...........^^^^r...rr........r^^^^^^^^^rrrrr........rrrrr^^^^^^^^^r........rr...r^^^^...........",
|
||||||
".................r..~~~~...~~~~~~~~~.......^^^^..^^^^.......~~~~~~~~~...~~~~..r.................",
|
"...........^^^^.r............^^^^^^^^^^^^^r..........r^^^^^^^^^^^^^............r.^^^^...........",
|
||||||
"..................r.~~~~..~~~~~~~~~~~......^^^^..^^^^......~~~~~~~~~~~..~~~~.r..................",
|
"...........^^^^^rr..........^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^..........rr^^^^^...........",
|
||||||
"..................r~~~~~~~~~~~~~~~~~~.....^^^^^^^^^^^^.....~~~~~~~~~~~~~~~~~~r..................",
|
"............^^..rr..........^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^..........rr..^^............",
|
||||||
".................r.~~~~~~~~~~~~~~~~~~.....^^^^^^^^^^^^.....~~~~~~~~~~~~~~~~~~.r.................",
|
"....rrr.....................^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.....................rrr....",
|
||||||
"................rrr~~~~~~~~~~~~~~~~~~......^^^^..^^^^......~~~~~~~~~~~~~~~~~~rrr................",
|
"....rrrr......................^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^......................rrrr....",
|
||||||
"..............rrrr~~~~~~~.~~~~~~~~~~~...rr.^^^^..^^^^.rr...~~~~~~~~~~~.~~~~~~~rrrr..............",
|
"......r...........................^^^^^^^^^^^^^^^^^^^^^^^^^^^^...........................r......",
|
||||||
"..............rrr.~~~~~~...~~~~~~~~~~...rrr^^^^..^^^^rrr...~~~~~~~~~~...~~~~~~.rrr..............",
|
"...................................^^^^^^^^^^^^^^^^^^^^^^^^^^...................................",
|
||||||
"..............r............................^^^^..^^^^............................r..............",
|
"rrr.................................^^^^^^^^^^^^^^^^^^^^^^^^.................................rrr",
|
||||||
"..............r...........................^^^^^^^^^^^^...........................r..............",
|
"..r..................................^......rrr..rrr......^..................................r..",
|
||||||
"............rrrrrr.......................^^^^^^^^^^^^^^.......................rrrrrr............",
|
"........rrr..........................................................................rrr........",
|
||||||
"........rr.rrrrrrr......................^^^^^^^^^^^^^^^^......................rrrrrrr.rr........",
|
"........rrr..........................................................................rrr........",
|
||||||
"........rrrrr..rrr....................^^^^^^^^^^^^^^^^^^^^....................rrr..rrrrr........",
|
"........rrr......rr...........rr................................rr...........rr......rrr........",
|
||||||
"...........rr...rr...................^^^^^^^^^^^^^^^^^^^^^^...................rr...rr...........",
|
"r.........r.....rr............rr...rr......................rr...rr............rr.....r.........r",
|
||||||
"...........rr....r...........^......^^^^^^^^^^^^^^^^^^^^^^^^......^...........r....rr...........",
|
"................rr...........~..rrrrr......................rrrrr..~...........rr................",
|
||||||
"...........................r^^^...r^^^^^^^^^^^^^^^^^^^^^^^^^^r...^^^r...........................",
|
"..................rrrr.......~~~.r.~~~~~~~............~~~~~~~.r.~~~.......rrrr..................",
|
||||||
"r...........................^^^....^^^^^^^^^^^^^^^^^^^^^^^^^^....^^^...........................r",
|
"..................rrrr.......~~~...~~~~~~~~r........r~~~~~~~~...~~~.......rrrr..................",
|
||||||
"...................................r^^^^^^^^^^^^^^^^^^^^^^^^r...................................",
|
"................rrrrrr.......~~......~~~~~~~~......~~~~~~~~......~~.......rrrrrr................",
|
||||||
"......................................^^^^^^^^^^^^^^^^^^^^......................................",
|
"................rrrrr.................r..~~~~~....~~~~~..r.................rrrrr................",
|
||||||
".................................rrr.....^^^^^^..^^^^^^.....rrr.................................",
|
".................rrr......................~~~~....~~~~......................rrr.................",
|
||||||
".........MM.....................rrrrr.....^^^^....^^^^.....rrrrr.....................MM.........",
|
"..................rr....~.................~~~~~rr~~~~~.................~....rr..................",
|
||||||
".........MM.....^^...............rrrrr....................rrrrr...............^^.....MM.........",
|
"............rr....rr...r~.................~~~~~..~~~~~.................~r...rr....rr............",
|
||||||
"................^^...............rrrrr....................rrrrr...............^^................",
|
"~r..........~~....rr...r~.................~~~~~..~~~~~.................~r...rr....~~..........r~",
|
||||||
"...............^^^...............rrrrr....................rrrrr...............^^^...............",
|
"~~............~~~~rr...~~........^r.......~~~~~~~~~~~~.......r^........~~...rr~~~~............~~",
|
||||||
"................^^^...............r..........................r...............^^^................",
|
"~~..............~~rr..r~~.......^^^.......~~~~~~~~~~~~.......^^^.......~~r..rr~~..............~~",
|
||||||
"~~...............^^..........................................................^^...............~~",
|
"~~~..............~~.MMr~~.......^^^^......~~~~~~~~~~~~......^^^^.......~~rMM.~~..............~~~",
|
||||||
"~~~.MM...........^^..........................................................^^...........MM.~~~",
|
"~~~r.............~~.MM.~~......^^^^^....rr~~~~~~~~~~~~rr....^^^^^......~~.MM.~~.............r~~~",
|
||||||
"~~~.MM.......MM...^...rr................................................rr...^...MM.......MM.~~~",
|
"~~~...............~....~~......^^^^^....rr~~~~~~~~~~~~rr....^^^^^......~~....~...............~~~",
|
||||||
"~~...........MM.......rr................................................rr.......MM...........~~",
|
"~~~..MM...........~....~~......^^^^^....rr.~~~~..~~~~.rr....^^^^^......~~....~...........MM..~~~",
|
||||||
"~.....................rr................................................rr.....................~",
|
"~~r..MM...........r............^^^^^.......~~~....~~~.......^^^^^............r...........MM..r~~",
|
||||||
"r..............................................................................................r",
|
"~~..............................^^^^........rr....rr........^^^^..............................~~",
|
||||||
"....MM............rr........................................................rr............MM....",
|
"~~...................rr.........^^^^........................^^^^.........rr...................~~",
|
||||||
"....MM............rr........................................................rr............MM....",
|
"~.............MM....rrr.........^^^^........................^^^^.........rrr....MM.............~",
|
||||||
"..................rr~~~~................................................~~~~rr..................",
|
"..............MM....rrr.........^^^..........................^^^.........rrr....MM..............",
|
||||||
"..................r.~~~~~~~..r..............^^....^^..............r..~~~~~~~.r..................",
|
"................................^^^..........................^^^................................",
|
||||||
"...................~~~~~~~~rr..............^^^....^^^..............rr~~~~~~~~...................",
|
"................................^^^..........................^^^................................",
|
||||||
"...................~~~~~~~~~...............^^^^..^^^^...............~~~~~~~~~...................",
|
".................................^^r........................r^^.................................",
|
||||||
"rr.................~~~~~~~~~..............^^^^^..^^^^^..............~~~~~~~~~.................rr",
|
"...................................rr......................rr...................................",
|
||||||
"rrr................~~~~~~~~~..............^^^^^..^^^^^..............~~~~~~~~~................rrr",
|
"..........MM.......................rr......................rr.......................MM..........",
|
||||||
"..r................~~~~~~~~~................^^....^^................~~~~~~~~~................r..",
|
"..........MM........................................................................MM..........",
|
||||||
"...................~~~~~~~~..........................................~~~~~~~~...................",
|
".....rr..................................................................................rr.....",
|
||||||
"..................r~~~~~~~~.r......................................r.~~~~~~~~r..................",
|
".....rr..................................................................................rr.....",
|
||||||
"...................r~~~~~~.rrr.rr......rrrr..........rrrr......rr.rrr.~~~~~~r...................",
|
".....rr..................................................................................rr.....",
|
||||||
"...................r~~~~rrrrrrrrr.....rrrr............rrrr.....rrrrrrrrr~~~~r...................",
|
".....rr..................................................................................rr.....",
|
||||||
"....................rr..rrrrrr.rrr....rrrr............rrrr....rrr.rrrrrr..rr....................",
|
"....^rr.................~~~..........................................~~~.................rr^....",
|
||||||
".............r..........rrrrr....r....rr................rr....r....rrrrr..........r.............",
|
"..^^^^^................~~~~~~~~....rr......................rr....~~~~~~~~................^^^^^..",
|
||||||
"...........rrrrr.........rr..........rr..................rr..........rr.........rrrrr...........",
|
".^^^^^^^r.............~~~~~~~~~~...~~~~~~r.....rr.....r~~~~~~...~~~~~~~~~~.............r^^^^^^^.",
|
||||||
"..........~~~.rr.........r...........rr.....~~....~~.....rr...........r.........rr.~~~..........",
|
"^^^^^^^^^.............~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.............^^^^^^^^^",
|
||||||
"........r~~~~~rr.........rr..........rr....~~~~~~~~~~....rr..........rr.........rr~~~~~r........",
|
"^^^^^^^^^.............~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.............^^^^^^^^^",
|
||||||
"........r~~~~~rrr.......rrr..........rrr...~~~~~~~~~~...rrr..........rrr.......rrr~~~~~r........",
|
"^^^^^^^^^............~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~............^^^^^^^^^",
|
||||||
".........~~~~~..rr......rrr...........rr...r~~~rr~~~r...rr...........rrr......rr..~~~~~.........",
|
"^^^^^^^^^.............~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.............^^^^^^^^^",
|
||||||
".........~~~~~..rr......rrr..........................................rrr......rr..~~~~~.........",
|
"^^^^^^^^^....rr........~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~........rr....^^^^^^^^^",
|
||||||
"........~~~~~~..rr............................................................rr..~~~~~~........",
|
"..^^^^^^r....r.........r~~~~~~r....rr~~~~~~~~~~~~~~~~~~~~~~rr....r~~~~~~r.........r....r^^^^^^..",
|
||||||
"........~~~~~~..rr............................................................rr..~~~~~~........",
|
"...^^^^......r...............rr....r....~~~~~~~~~~~~~~~~....r....rr...............r......^^^^...",
|
||||||
"........~~~~~~..rr............................................................rr..~~~~~~........",
|
"....^^r......rr..............rr....rr.....~~~~~~~~~~~~.....rr....rr..............rr......r^^....",
|
||||||
"........~~~~~~~.......................rrrr............rrrr.......................~~~~~~~........",
|
".....rr.......rr.............rr.....rr....~~~~~~~~~~~~....rr.....rr.............rr.......rr.....",
|
||||||
".......~~~~~~~~......r..........r^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^r..........r......~~~~~~~~.......",
|
".....rr......................rr......r.....~~~~..~~~~.....r......rr......................rr.....",
|
||||||
".......~~~~~~~~.....rrrr........^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^........rrrr.....~~~~~~~~.......",
|
".....rr......................rr.^^^........~~~~rr~~~~........^^^.rr......................rr.....",
|
||||||
".......~~~~~~~~....rr.rr.......^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.......rr.rr....~~~~~~~~.......",
|
".....rr......................rr^^^^^.......~~~~rr~~~~.......^^^^^rr......................rr.....",
|
||||||
".......~~~~~~~~.....~~.r.......^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.......r.~~.....~~~~~~~~.......",
|
".....rr......................r^^^^^^.......~~~~..~~~~.......^^^^^^r......................rr.....",
|
||||||
".......~~~~~~~~.....~~..r......^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^......r..~~.....~~~~~~~~.......",
|
"..............................^^^^^^.......~~~~..~~~~.......^^^^^^..............................",
|
||||||
".......~~~~~~~~.....~~..rrr....^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^....rrr..~~.....~~~~~~~~.......",
|
"..............................^^^^^^.......~~~....~~~.......^^^^^^..............................",
|
||||||
"......r~~~~~~~~.....~~~..rrr....^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^....rrr..~~~.....~~~~~~~~r......",
|
".....................^r.......^^^^^^r.......~~....~~.......r^^^^^^.......r^.....................",
|
||||||
"......rr~~~~~~~.....~~~..rrr.....^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.....rrr..~~~.....~~~~~~~rr......",
|
".....................^r........^^^^^r.......~~....~~.......r^^^^^........r^.....................",
|
||||||
"......rr~~~~~~~.....~~~~............^^^^^^^^^^^^^^^^^^^^^^^^............~~~~.....~~~~~~~rr......",
|
"...............................^^^^rr......................rr^^^^...............................",
|
||||||
"......rr.~~~~~r.....~~~~...............rrrr..........rrrr...............~~~~.....r~~~~~.rr......",
|
"................................^^^..........................^^^................................",
|
||||||
"^.........~~~~r...r~~~~~~..............rrrrrrrr..rrrrrrrr..............~~~~~~r...r~~~~.........^",
|
"................................^^^..........................^^^................................",
|
||||||
"^^............rr..r~~~~~~~................rrrrrrrrrrrr................~~~~~~~r..rr............^^",
|
"...............................rr.......rrr..........rrr.......rr...............................",
|
||||||
"^^^...........rr..r~~~~~~~................rrr..rr..rrr................~~~~~~~r..rr...........^^^",
|
"r..............................rr.......rrr..........rrr.......rr..............................r",
|
||||||
"^^^.....rr........r~~~~~~~...............rrrr......rrrr...............~~~~~~~r........rr.....^^^",
|
"r..............................rr.........r..........r.........rr..............................r",
|
||||||
"^^^^....rr.........~~~~~~~~...............rr........rr...............~~~~~~~~.........rr....^^^^",
|
".....rr........................r..........rr........rr..........r........................rr.....",
|
||||||
"^^^^...............~~~~~~~~..........................................~~~~~~~~...............^^^^",
|
".....rr......................rr...........rrr......rrr...........rr......................rr.....",
|
||||||
"^^^^...............~~~~~~~~..........................................~~~~~~~~...............^^^^",
|
"...........rr................rr............rrrr..rrrr............rr................rr...........",
|
||||||
"^^^^...............~~~~~~~~..........................................~~~~~~~~...............^^^^",
|
"...........rr................rr............rrrr..rrrr............rr................rr...........",
|
||||||
"^^^................~~~~~~~~..........................................~~~~~~~~................^^^",
|
"...........rr.....rr.........rr...........rrrr....rrrr...........rr.........rr.....rr...........",
|
||||||
"^^^................~~~~~~~~...........r....r........r....r...........~~~~~~~~................^^^",
|
"..................rr........................................................rr..................",
|
||||||
"^^^.................~~~~~~...........rrr..rrr......rrr..rrr...........~~~~~~.................^^^",
|
"..................rrr......................................................rrr..................",
|
||||||
"^^.....r......rr.r..~~~~~~............r....r........r....r............~~~~~~..r.rr......r.....^^"
|
"..................r.rr..........r..............................r..........rr.r.................."
|
||||||
],
|
],
|
||||||
"starts": [
|
"starts": [
|
||||||
{
|
{
|
||||||
|
|
@ -787,7 +787,7 @@
|
||||||
"id": "m06",
|
"id": "m06",
|
||||||
"name": "Annihilation",
|
"name": "Annihilation",
|
||||||
"theme": "snowfields",
|
"theme": "snowfields",
|
||||||
"seed": 63541,
|
"seed": 80808,
|
||||||
"playerArmy": "arm",
|
"playerArmy": "arm",
|
||||||
"playerCommander": "vance",
|
"playerCommander": "vance",
|
||||||
"enemies": [
|
"enemies": [
|
||||||
|
|
@ -817,7 +817,7 @@
|
||||||
{
|
{
|
||||||
"speaker": "ethel",
|
"speaker": "ethel",
|
||||||
"mood": "idle",
|
"mood": "idle",
|
||||||
"text": "KLAXON at full strength with a base already standing. Nothing clever left — out-build it and end it."
|
"text": "KLAXON at full strength with a base already standing, dug in behind its own defences. Nothing clever left — out-build it and end it."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"victoryLine": {
|
"victoryLine": {
|
||||||
|
|
@ -835,102 +835,102 @@
|
||||||
"h": 96,
|
"h": 96,
|
||||||
"theme": "snowfields",
|
"theme": "snowfields",
|
||||||
"rows": [
|
"rows": [
|
||||||
"................^^^...^^^^^.............^^^^^^.......r...rr....r.........r....r...........rrr...",
|
"~~~r....r...rrrrrr..........r.~~~~~~...............^^^^^^^^^^^^^^..................r....r..^^^^^",
|
||||||
"..............MM.^^..r^^^^^..............^^^^^............................................rrr...",
|
"~~~r..........r....MM.........~~~~~~...............^^^^^^^^^^^^^...........................^^^^^",
|
||||||
"..............MM......^^^^^^...............rr..............................................rr...",
|
"~~~...............rMM.........~~~~~r................^^^^^^^^^^^............................^^^^^",
|
||||||
"......................^^^^^^..............r...............................................rrr...",
|
"~~~...............rr..............r...................^^^^^^............................rr.^^^^^",
|
||||||
"....................................................................rrr...................rrr...",
|
"rr................rrrr....................................r..........rr.................^^^^^^^^",
|
||||||
"........................rr..........................................rrrr.....................r..",
|
"...................rrrr.r............................................rr................^^^^^^^^^",
|
||||||
"........................rr.........................................^..rr.......rr............rr.",
|
"...................rrrrrrrr............................................................^^^^^^^^^",
|
||||||
"................................................................r^^^^^..........rr...........rr.",
|
"....................r...rr..................r...........................................^^^^^^rr",
|
||||||
".......................~~~.......................................^^^^^^.......................rr",
|
"r.....MM...................................r^^................rr........................^^^^^^..",
|
||||||
"......................~~~~~.....................................^^^^^^^^........................",
|
"......MM.............^^^..................^^^^^............rrrrrrrrr...................^^^^^^^^.",
|
||||||
"......................~~~~~......................................^^^^^^^........................",
|
"..MM................^^^^^^.............rr^^^^^^^...........rrrrrrrrr.................r^^^^^^^^^^",
|
||||||
"......................~~~~.....rr..............................rrrr...........................rr",
|
"..MM...............^^^^^^^...rr........r^^^^^^^^^............r....^^r................^^^^^^^^^^^",
|
||||||
".......................~~......rr...........................rrrrr.......................rr....rr",
|
"...................^^^^^^^..rrr..rr....r^^^^^^^^^.................^^^................^^^^^^^^^^^",
|
||||||
"...............................r...rr.................rr....rrrr.........................r....rr",
|
"..................r^^^^^^^rrrrrr.rr....r^^^^^^^^^..................^........rr.......^^^^^^^^^^^",
|
||||||
"................................rrrrr................rrr.....rrr................................",
|
"..................r^^^^^^...rrrr..........^^^^^^rr.....rrrrrrr..............rrrrr.....^^^^^^^^^.",
|
||||||
"...............MM................rrrr..............rrrr.......rrr...............................",
|
"..................^^^^^^....rrrrr................rrrrr~~~~~rrr..............rrrrr........r...r..",
|
||||||
"...............MM...............rrr...............rrrrr...rr..rr................................",
|
".................^^^^^^r...rrr.rr..~.............rrr~~~~~~~~~.............................rrr...",
|
||||||
"......r..........................rr..r............rrrr....rrrrrr................rrr............r",
|
"~..............^^^^^.^.....rr....~~~~.rrr........rrr~~~~~~~~~..............................r....",
|
||||||
"r....MM.............................rrr.........................................rrr.............",
|
"~~.........r^^^^^^..............~~~~..rrr.............~~~~~~....................................",
|
||||||
".....MM..............................rr...........................~~~~~~~~~~~~~~~~~.............",
|
"~~MM.......^^^^^...............~~~~........................rrrrrr...............................",
|
||||||
".......................^^^^..rr......rr...r~~~..................~~~~~~~~~~~~~~~~~~~~............",
|
"~~MM.......^^^^^..............~~~~~........................rrrrrr...............................",
|
||||||
".........MM...........^^^^^.rrr......rr...~~~~~................~~~~~~~~~~~~~~~~~~~~~~~..........",
|
"~~~.......r^^^^^.............~~~~~~.........................rrrr................................",
|
||||||
".........MM.........^^^^^^^rrrr..........~~~~~~~...............~~~~~~~~~~~~~~~~~~~~~~~~..rr....r",
|
"~~........^^^^^^^.............~~~~~.............................................................",
|
||||||
"^^................r^^^^^rr...rr..........~~~~~~~...............~~~~~~~~~~~~~~~~~~~~~~~~.rrrrrrrr",
|
"..........^^^^^^^^..............~~~.............................................rrr.....r^......",
|
||||||
"^^^...............^^^^^..................~~~~~~~.......rr......~~~~~~~~~~~~~~~~~~~~~~~~~.rrrr...",
|
".........r^^^^^^^^................r.............................................rrr....^^^^.....",
|
||||||
"^^^................^^^^...................~~~~~........rr.......~~~~~~~~~~~~~~~~~~~~~~~~~~......",
|
".........r^^^^^^^^rrrr.....r....................................................rrrr...^^^^^....",
|
||||||
"r..................^^^^.........................................~~~~~~~~~~~~~~~~~~~~~~~~~~~~....",
|
"r..........^^^^^^^rrrr....rrr......................................................rr..^^^^^....",
|
||||||
"r..................rr^............................................~~~~~~~~~~~~~~~~~~~~~~~~~~~~..",
|
"r.............rrr....rrrrr.rr......................................................rrrrr^^^.....",
|
||||||
".......................................................................~~~~~~~~~~~~~~~~~~~~~~~~.",
|
".rrrr........rrrr.......^^..r.......................................................rr..........",
|
||||||
"...rr..................rr.................................................~~~~~~~~~~~~~~~~~~~~~~",
|
"..rrrr.......rr........^^^^^^.........................r...r...............................rrr...",
|
||||||
"r..rr..................rrr.......................................................~~~~~~~~~~~~~~~",
|
"^^.rrr............rr..r^^^^^^r.......................^^^^^^^............................^^^^^^^^",
|
||||||
"r..rr..................rrrr........................................................~~~~~~~~~~~~~",
|
"^^.rrrr...........rrrr.^^^^^.......................^^^^^^^^^^^^.r.......................^^^^^^^^",
|
||||||
"...rr....................rr..........rr....................r........................~~~~~~~~~~~~",
|
"^^rrrrr........rrrr...^^^^^.....^^^^^.............^^^^^^^^^^^^^^^rr....................^^^^^^^^^",
|
||||||
"..........................rr.........rr...................rrr.......................~~~~~~~~~~~~",
|
"^^rrr..........rrr^...^^^^.....r^^^^^.............^^^^^^^^^^^^^^^^^....................^^^^^^^^^",
|
||||||
"..........................rr......................rr.......r........................r~~~~~~~~~~~",
|
"^^.rr..........rrr....^^^r.....r^^^^...............^^^^^^^^^^^^^........................^^^^^^^^",
|
||||||
".........................rrrr...rr................rr.................................r~~~~~~~~~~",
|
"^..rr..rr...............r.......^^..................^^^^^^^^^..............rrr..................",
|
||||||
".........................rrrr...rr..r.............rr.................................rrr~~~~~~..",
|
"....r.rrr...............r...^^^^.....................r^^^^................rrrr~.................",
|
||||||
".........................r.r.....rrrrrr.............................................rrrrrrrrr...",
|
".....rrrr..............rrr..^^^^.....................rr...................rrrr~.................",
|
||||||
".................................rr..rr................^^...........................rr.rr.......",
|
"r......rr...............r...^^^^.....................rr.........................................",
|
||||||
"................................rr...................^^^^^..rr..................................",
|
".r...........................^^^^^^...r...........rrr........................................rr.",
|
||||||
".....rrr............~~..........rr..............^^^^^^^^^^..rr..................................",
|
".rr..........................^^^^^^^rrrr..........rrr.......................................rrr.",
|
||||||
".....rrr.......rr...~~..........rr........^^^^^^^^^^^^^^^^..rr..................................",
|
"..r...................~~~....^^^^^^^^^^r.r..........r.......................................rrr.",
|
||||||
".......rr......rr..~~~..........rr.......^^^^^^^^^^^^^^^^^..rr..................................",
|
"~~~....rrr..........~~~~~~...^^^^^^^^^^^^^^^^^^.............................................rrrr",
|
||||||
"........rr.........~~~..................^^^^^^^^^^^^^^^^^^..rrrr..............^.................",
|
"~~~~...rrrr........~~~~~~~....^^^^^^^^^^^^^^^^^^..........rr..................................~~",
|
||||||
"........rr.........~~~~.................^^^^^^^^^^^^^^^^^^^.rrrr.............^^^................",
|
"~~~~...rrrr........~~~~~~~....^^^^^^^^^^^^^^^^^^^.........rr......~~~........................~~~",
|
||||||
"...................~~~~................^^^^^^^^^^^^^^^^^^^..rrr..............^^^rrr......rrrrrrr",
|
"~~~~...rr..........~~~~~~~....^^^^^^^^^^^^^^^^^^^..........r......~~~........................~~~",
|
||||||
"...................~~~..................^^^^^^^^^^^^^^^^^^..rr...............^^^rrr......rrrrrrr",
|
"~~~~...rr..........~~~~~~~....^^^^^^^^^^^^^^^^^^.r........rr......~~..........................~~",
|
||||||
"....................~~..................^^^^^^^^^^^^^^^^......................^................r",
|
"~~~~...rr..........~~~~~~~....^^^^^^^^^^^^^^^^^..rrrrr....rr..r...~~.....r....................~~",
|
||||||
"r................^......................^^^^^^^^^^^^^^^^..................~~....................",
|
"~~....................r.....~~...r..rr....rrrrr..^^^^^^^^^^^^^^^^^....~~~~~~~..........rr...~~~~",
|
||||||
"rrrrrrr......rrr^^^...............rr..^^^^^^^^^^^^^^^^^^..................~~~...................",
|
"~~..........................~~......rr........r.^^^^^^^^^^^^^^^^^^....~~~~~~~..........rr...~~~~",
|
||||||
"rrrrrrr......rrr^^^..............rrr..^^^^^^^^^^^^^^^^^^^................~~~~...................",
|
"~~~........................~~~......r..........^^^^^^^^^^^^^^^^^^^....~~~~~~~..........rr...~~~~",
|
||||||
"................^^^.............rrrr.^^^^^^^^^^^^^^^^^^^.................~~~~.........rr........",
|
"~~~........................~~~......rr.........^^^^^^^^^^^^^^^^^^^....~~~~~~~........rrrr...~~~~",
|
||||||
".................^..............rrrr..^^^^^^^^^^^^^^^^^^..................~~~.........rr........",
|
"~~..................................rr..........^^^^^^^^^^^^^^^^^^....~~~~~~~........rrrr...~~~~",
|
||||||
"..................................rr..^^^^^^^^^^^^^^^^^.......rr..........~~~..rr......rr.......",
|
"rrrr.............................................^^^^^^^^^^^^^^^^^^...~~~~~~..........rrr....~~~",
|
||||||
"..................................rr..^^^^^^^^^^^^^^^^........rr..........~~...rr.......rrr.....",
|
".rrr.......................................r..........r.r^^^^^^^^^^....~~~...................r..",
|
||||||
"..................................rr..^^^^^^^^^^..............rr..........~~............rrr.....",
|
".rrr.......................................rrr..........rrrr^^^^^^^..........................rr.",
|
||||||
"..................................rr..^^^^^...................rr................................",
|
".rr........................................rrr...........r...^^^^^^...........................r.",
|
||||||
".......rr.rr...........................^^................rr..rr.................................",
|
".........................................rr.....................^^^^...r...............rr......r",
|
||||||
"...rrrrrrrrr.............................................rrrrrr.....r.r.........................",
|
".................~rrrr...................rr.....................^^^^..rrr..............rrrr.....",
|
||||||
"..~~~~~~rrr.................................rr.............r..rr...rrrr.........................",
|
".................~rrrr................^^^^r.....................^^^^...r...............rrr.r....",
|
||||||
"~~~~~~~~~~r.................................rr................rr...rrrr.........................",
|
"..................rrr..............^^^^^^^^^..................^^.......r...............rr..rr..^",
|
||||||
"~~~~~~~~~~~r........................r.......rr......................rr..........................",
|
"^^^^^^^^........................^^^^^^^^^^^^^...............^^^^r.....r^^^....rrr..........rr.^^",
|
||||||
"~~~~~~~~~~~~.......................rrr...................rr.........rr..........................",
|
"^^^^^^^^^....................^^^^^^^^^^^^^^^^^.............^^^^^r.....^^^^...^rrr..........rrr^^",
|
||||||
"~~~~~~~~~~~~........................r....................rr..........rr....................rr...",
|
"^^^^^^^^^....................rr^^^^^^^^^^^^^^^.............^^^^^.....^^^^^...rrrr........rrrrr^^",
|
||||||
"~~~~~~~~~~~~~........................................................rrrr..................rr..r",
|
"^^^^^^^^.......................r.^^^^^^^^^^^^.......................^^^^^.rrrr...........rrrr.^^",
|
||||||
"~~~~~~~~~~~~~~~.......................................................rrr..................rr..r",
|
"^^^^^^^^............................^^^^^^^.......................r^^^^^^r..rr............rrr.^^",
|
||||||
"~~~~~~~~~~~~~~~~~~~~~~.................................................rr..................rr...",
|
"...rrr...............................r...r.........................^^^^^^........rr.......rrrr..",
|
||||||
".~~~~~~~~~~~~~~~~~~~~~~~~.......................................................................",
|
"..........rr.......................................................r..^^.......rrrr........rrrr.",
|
||||||
"..~~~~~~~~~~~~~~~~~~~~~~~~~~~~............................................^rr..................r",
|
".....^^^rrrrr......................................................rr.rrrrr....rrr.............r",
|
||||||
"....~~~~~~~~~~~~~~~~~~~~~~~~~~~~.........................................^^^^..................r",
|
"....^^^^^..rr......................................................rrr....rrrr^^^^^^^..........r",
|
||||||
"......~~~~~~~~~~~~~~~~~~~~~~~~~~.......rr........~~~~~...................^^^^................^^^",
|
"....^^^^^...rrrr....................................................r.....rrrr^^^^^^^^r.........",
|
||||||
"...rrrr.~~~~~~~~~~~~~~~~~~~~~~~~~......rr.......~~~~~~~..................^^^^^...............^^^",
|
".....^^^^....rrr.............................................r................^^^^^^^^r.........",
|
||||||
"rrrrrrrr.~~~~~~~~~~~~~~~~~~~~~~~~...............~~~~~~~..........rr...rr^^^^^r................^^",
|
"......^r.....rrr.............................................~~~..............^^^^^^^^..........",
|
||||||
"r....rr..~~~~~~~~~~~~~~~~~~~~~~~~...............~~~~~~~..........rrrr^^^^^^^.........MM.........",
|
".............................................................~~~~~.............^^^^^^^........~~",
|
||||||
"..........~~~~~~~~~~~~~~~~~~~~~~~................~~~~~...rr......rrr.^^^^^...........MM.........",
|
"................................rrrr.........................~~~~~~.............^^^^^r.......~~~",
|
||||||
"............~~~~~~~~~~~~~~~~~~~~..................~~~r...rr......rr..^^^^.......................",
|
"...............................rrrrrr........................~~~~~..............^^^^^.......MM~~",
|
||||||
".............~~~~~~~~~~~~~~~~~...........................rr..............................MM.....",
|
"...............................rrrrrr........................~~~~...............^^^^^.......MM~~",
|
||||||
".............rrr.........................................rrr.............................MM....r",
|
"....................................~~~~~~.............rrr..~~~~..............^^^^^^r.........~~",
|
||||||
"r............rrr................rrrrrr....rrrr............r..rr..........................r......",
|
"....r..............................~~~~~~~~~rrr........rrr.~~~~....rr.....^.^^^^^..............~",
|
||||||
"................................rr..rr...rrrrr...............rrr...............MM...............",
|
"...rrr.............................~~~~~~~~~rrr.............~..rr.rrr...r^^^^^^.................",
|
||||||
"...............................rrr.......rrrr..............rrrr................MM...............",
|
"..r...r........rrrrr..............rrr~~~~~rrrrr................rrrrr....^^^^^^..................",
|
||||||
"................................rrr.....rrr................rrrrr................................",
|
".^^^^^^^^^.....rrrrr..............rrrrrrr.....rr^^^^^^..........rrrr...^^^^^^r..................",
|
||||||
"rr....r.........................rrrr....rr.................rr...r...............................",
|
"^^^^^^^^^^^.......rr........^..................^^^^^^^^^r....rr.rrrrrr^^^^^^^r..................",
|
||||||
"rr....rr.......................rrrrr...........................rr......~~.......................",
|
"^^^^^^^^^^^................^^^.................^^^^^^^^^r....rr..rrr..^^^^^^^...................",
|
||||||
"rr...........................rrrr..............................rr.....~~~~......................",
|
"^^^^^^^^^^^................r^^....r............^^^^^^^^^r........rr...^^^^^^^...............MM..",
|
||||||
"........................^^^^^^^......................................~~~~~......................",
|
"^^^^^^^^^^r.................rrrrrrrrr...........^^^^^^^rr.............^^^^^^................MM..",
|
||||||
"........................^^^^^^^^.....................................~~~~~......................",
|
".^^^^^^^^...................rrrrrrrrr............^^^^^..................^^^.............MM......",
|
||||||
"rr.......................^^^^^^.......................................~~~.......................",
|
"..^^^^^^........................rr................^^r...................................MM.....r",
|
||||||
".rr...........rr..........^^^^^r................................................................",
|
"rr^^^^^^...........................................r..................rr...r....................",
|
||||||
".rr............rr.......rr..^.........................................rr........................",
|
"^^^^^^^^^............................................................rrrrrrrr...................",
|
||||||
"..r.....................rrrr..........................................rr........................",
|
"^^^^^^^^^................rr............................................r.rrrr...................",
|
||||||
"...rrr...................rrr....................................................................",
|
"^^^^^^^^.................rr..........r....................................rrrr................rr",
|
||||||
"...rrr...............................................r..............^^^^^^......................",
|
"^^^^^.rr............................^^^^^^...................r..............rr...............~~~",
|
||||||
"...rr..............................................rr...............^^^^^^......MM..............",
|
"^^^^^............................^^^^^^^^^^^................r~~~~~.........MMr...............~~~",
|
||||||
"...rrr............................................^^^^^..............^^^^^r..^^.MM..............",
|
"^^^^^...........................^^^^^^^^^^^^^...............~~~~~~.........MM....r..........r~~~",
|
||||||
"...rrr...........r....r.........r....rr...r.......^^^^^^.............^^^^^...^^^................"
|
"^^^^^..r....r..................^^^^^^^^^^^^^^...............~~~~~~.r..........rrrrrr...r....r~~~"
|
||||||
],
|
],
|
||||||
"starts": [
|
"starts": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -402,9 +402,9 @@
|
||||||
],
|
],
|
||||||
"armorMul": {
|
"armorMul": {
|
||||||
"infantry": 0.3,
|
"infantry": 0.3,
|
||||||
"light": 0.6,
|
"light": 0.35,
|
||||||
"medium": 1.0,
|
"medium": 0.3,
|
||||||
"heavy": 1.4,
|
"heavy": 0.3,
|
||||||
"structure": 1.5,
|
"structure": 1.5,
|
||||||
"fortification": 1.6,
|
"fortification": 1.6,
|
||||||
"air": 2.6
|
"air": 2.6
|
||||||
|
|
@ -418,6 +418,78 @@
|
||||||
"sound": "sfx-ta-rocket-1",
|
"sound": "sfx-ta-rocket-1",
|
||||||
"impactSound": "sfx-ta-rocket-2"
|
"impactSound": "sfx-ta-rocket-2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "megatankgun",
|
||||||
|
"name": "Twin 150mm Cannon",
|
||||||
|
"kind": "ballistic",
|
||||||
|
"damage": 145,
|
||||||
|
"reload": 2.0,
|
||||||
|
"burst": 1,
|
||||||
|
"range": 380,
|
||||||
|
"speed": 700,
|
||||||
|
"spread": 0.015,
|
||||||
|
"leadTarget": true,
|
||||||
|
"aoe": 44,
|
||||||
|
"aoeFalloff": 0.15,
|
||||||
|
"targets": [
|
||||||
|
"ground"
|
||||||
|
],
|
||||||
|
"barrels": 2,
|
||||||
|
"barrelSpacing": 9,
|
||||||
|
"armorMul": {
|
||||||
|
"infantry": 1.3,
|
||||||
|
"light": 1.15,
|
||||||
|
"medium": 1.0,
|
||||||
|
"heavy": 0.8,
|
||||||
|
"structure": 1.2,
|
||||||
|
"fortification": 1.1,
|
||||||
|
"air": 1.0
|
||||||
|
},
|
||||||
|
"fx": {
|
||||||
|
"style": "shell",
|
||||||
|
"color": "#ffcf6a",
|
||||||
|
"width": 4,
|
||||||
|
"muzzle": 22
|
||||||
|
},
|
||||||
|
"sound": "sfx-battle-tank"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "megatankrockets",
|
||||||
|
"name": "Side Rocket Pods",
|
||||||
|
"kind": "guided",
|
||||||
|
"damage": 55,
|
||||||
|
"reload": 1.32,
|
||||||
|
"burst": 1,
|
||||||
|
"range": 400,
|
||||||
|
"minRange": 90,
|
||||||
|
"speed": 330,
|
||||||
|
"turnRate": 2.1,
|
||||||
|
"aoe": 42,
|
||||||
|
"aoeFalloff": 0.15,
|
||||||
|
"targets": [
|
||||||
|
"ground",
|
||||||
|
"air"
|
||||||
|
],
|
||||||
|
"barrels": 2,
|
||||||
|
"barrelSpacing": 20,
|
||||||
|
"armorMul": {
|
||||||
|
"infantry": 0.3,
|
||||||
|
"light": 0.6,
|
||||||
|
"medium": 1.0,
|
||||||
|
"heavy": 1.3,
|
||||||
|
"structure": 1.4,
|
||||||
|
"fortification": 1.5,
|
||||||
|
"air": 2.4
|
||||||
|
},
|
||||||
|
"fx": {
|
||||||
|
"style": "rocket",
|
||||||
|
"color": "#ff5c3c",
|
||||||
|
"width": 2,
|
||||||
|
"trail": true
|
||||||
|
},
|
||||||
|
"sound": "sfx-ta-rocket-1",
|
||||||
|
"impactSound": "sfx-ta-rocket-2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "shoulderrocket",
|
"id": "shoulderrocket",
|
||||||
"name": "Shoulder Rocket",
|
"name": "Shoulder Rocket",
|
||||||
|
|
@ -878,6 +950,38 @@
|
||||||
"spritePx": 56,
|
"spritePx": 56,
|
||||||
"moveSound": "sfx-engine-heavy"
|
"moveSound": "sfx-engine-heavy"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "megatank",
|
||||||
|
"name": "Megatank",
|
||||||
|
"role": "combat",
|
||||||
|
"size": "medium",
|
||||||
|
"radius": 33,
|
||||||
|
"hp": 3200,
|
||||||
|
"speed": 65,
|
||||||
|
"turnRate": 1.6,
|
||||||
|
"moveClass": "tread",
|
||||||
|
"armorClass": "heavy",
|
||||||
|
"sight": 320,
|
||||||
|
"cost": {
|
||||||
|
"energy": 3200,
|
||||||
|
"mass": 1000
|
||||||
|
},
|
||||||
|
"buildTime": 80,
|
||||||
|
"builtBy": [
|
||||||
|
"advancedvehicleplant"
|
||||||
|
],
|
||||||
|
"weapons": [
|
||||||
|
"megatankgun",
|
||||||
|
"megatankrockets"
|
||||||
|
],
|
||||||
|
"sheetSlot": "unitSheet",
|
||||||
|
"frame": 15,
|
||||||
|
"turretFrame": 16,
|
||||||
|
"procShape": "tank",
|
||||||
|
"icon": 32,
|
||||||
|
"spritePx": 66,
|
||||||
|
"moveSound": "sfx-engine-heavy"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "constructor",
|
"id": "constructor",
|
||||||
"name": "Construction Vehicle",
|
"name": "Construction Vehicle",
|
||||||
|
|
@ -1254,7 +1358,8 @@
|
||||||
"builds": [
|
"builds": [
|
||||||
"jeep",
|
"jeep",
|
||||||
"tank",
|
"tank",
|
||||||
"rockettank"
|
"rockettank",
|
||||||
|
"megatank"
|
||||||
],
|
],
|
||||||
"spawnOffset": {
|
"spawnOffset": {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
|
|
@ -1451,7 +1556,7 @@
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"size": "medium",
|
"size": "medium",
|
||||||
"symmetry": "mirror-x",
|
"symmetry": "mirror-x",
|
||||||
"theme": "grasslands",
|
"theme": "tropics",
|
||||||
"aiSkill": 3,
|
"aiSkill": 3,
|
||||||
"playerArmy": "arm"
|
"playerArmy": "arm"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -302,16 +302,19 @@ function chooseBuilding(ctx, mine) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static defence is a skill-4-and-up behaviour, like the Airfield is skill 3 and up.
|
* Static defence is a skill-3-and-up behaviour, like the Airfield.
|
||||||
*
|
*
|
||||||
* These structures hit hard for their cost — a Laser Tower puts out 130 dps behind 1700 hp for
|
* These structures hit hard for their cost — a Laser Tower puts out 130 dps behind 1700 hp for
|
||||||
* 60% of a Tank's price — and the ground roster has no dedicated siege answer to them yet, so
|
* 60% of a Tank's price — and the ground roster has no dedicated siege answer to them yet, so
|
||||||
* two equally-matched AIs both digging in grind rather than resolve. Reserving them for the
|
* two equally-matched AIs both digging in grind rather than resolve. Reserving them for skill 3
|
||||||
* stronger opponents puts that pressure where a player goes looking for it and leaves the low
|
* and up puts that pressure where a player goes looking for it once they're past the beginner
|
||||||
* end of the ladder playing the open game it was tuned for. Drop this to 3 (or 1) once there is
|
* tier, and leaves skill 1-2 playing the open game they were tuned for: `urgentDefence` and
|
||||||
* a unit whose job is cracking fortifications.
|
* `steadyDefence` are still called for every skill, but their own `ctx.skill < DEFENCE_MIN_SKILL`
|
||||||
|
* guard keeps returning null for skill 1-2 exactly as before, so lowering this constant alone
|
||||||
|
* touches only skill 3 and up. Drop it further (to 1) once there is a unit whose job is cracking
|
||||||
|
* fortifications.
|
||||||
*/
|
*/
|
||||||
const DEFENCE_MIN_SKILL = 4;
|
const DEFENCE_MIN_SKILL = 3;
|
||||||
|
|
||||||
/** Has anything hostile been inside the base perimeter recently? */
|
/** Has anything hostile been inside the base perimeter recently? */
|
||||||
function underThreat(ctx) {
|
function underThreat(ctx) {
|
||||||
|
|
@ -325,9 +328,18 @@ function enemyAirSeen(ctx) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The one structure the base needs RIGHT NOW, ahead of any economic consideration. Kept
|
* The one structure the base needs RIGHT NOW, ahead of any economic consideration. Kept
|
||||||
* deliberately tiny — one tower against a raid, one launcher against aircraft. Everything
|
* deliberately tiny — one tower, one launcher against aircraft. Everything beyond the first of
|
||||||
* beyond the first of each is a steady-state decision (see steadyDefence), because an AI that
|
* each is a steady-state decision (see steadyDefence), because an AI that answers pressure by
|
||||||
* answers pressure by building walls stops building an army and loses slowly instead of fast.
|
* building walls stops building an army and loses slowly instead of fast.
|
||||||
|
*
|
||||||
|
* The first Laser Tower is PROACTIVE, not a raid reaction: it goes up right after the opening
|
||||||
|
* economy (2 energygen/1 massgen/1 barracks/3 energygen/2 massgen/1 vehicleplant), the moment
|
||||||
|
* this function is first reachable, regardless of `underThreat`. A purely reactive first tower
|
||||||
|
* — the previous behaviour — never exists until the base has already been hit once, which is no
|
||||||
|
* help at all against a single decisive rush that goes straight for the Commander: the raid
|
||||||
|
* that would have triggered it IS the loss. Everything past this first one stays reactive
|
||||||
|
* (steadyDefence), which is what keeps two skill-3+ AIs from both digging in and turning a
|
||||||
|
* mirror match into a stalemate — see the soak notes on that.
|
||||||
*/
|
*/
|
||||||
function urgentDefence(ctx, mine) {
|
function urgentDefence(ctx, mine) {
|
||||||
const { rules } = ctx;
|
const { rules } = ctx;
|
||||||
|
|
@ -338,7 +350,7 @@ function urgentDefence(ctx, mine) {
|
||||||
// Anti-air first: a Laser Tower cannot shoot at aircraft at all, so a base being bombed gets
|
// Anti-air first: a Laser Tower cannot shoot at aircraft at all, so a base being bombed gets
|
||||||
// nothing whatsoever from one.
|
// nothing whatsoever from one.
|
||||||
if (aa && enemyAirSeen(ctx) && n('missilelauncher') < 1) return aa;
|
if (aa && enemyAirSeen(ctx) && n('missilelauncher') < 1) return aa;
|
||||||
if (tower && underThreat(ctx) && n('lasertower') + n('missilelauncher') < 1) return tower;
|
if (tower && n('lasertower') + n('missilelauncher') < 1) return tower;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -532,9 +544,11 @@ function applyCounters(ctx, weights) {
|
||||||
const seen = infantryish + armour;
|
const seen = infantryish + armour;
|
||||||
if (seen) {
|
if (seen) {
|
||||||
const armourShare = armour / seen;
|
const armourShare = armour / seen;
|
||||||
// Tanks shred infantry; rockets and snipers answer armour and buildings.
|
// The Rocket Tank's rocketpod is weak against vehicle armour (weak vs light/medium/heavy,
|
||||||
weights.tank *= 1 + (1 - armourShare) * 0.8;
|
// strong vs structure/fortification/air) — Tanks are now the answer to an armour-heavy
|
||||||
weights.rockettank *= 1 + armourShare * 1.0 + (structures > 2 ? 0.3 : 0);
|
// enemy, not Rocket Tanks. Rocket Tanks still lean into a base full of structures.
|
||||||
|
weights.tank *= 1 + (1 - armourShare) * 0.8 + armourShare * 0.4;
|
||||||
|
weights.rockettank *= 1 + (structures > 2 ? 0.5 : 0);
|
||||||
weights.sniper *= 1 + (1 - armourShare) * 0.5;
|
weights.sniper *= 1 + (1 - armourShare) * 0.5;
|
||||||
weights.rockettrooper *= 1 + armourShare * 0.6;
|
weights.rockettrooper *= 1 + armourShare * 0.6;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Total Annihilation — the full-screen studio intro that plays once, ahead of the main menu,
|
||||||
|
// every time the scene starts fresh (see TotalAnnihilationGame.create — this scene is never
|
||||||
|
// resumed in place, only entered, so "fresh" is every time).
|
||||||
|
//
|
||||||
|
// The clip carries its own audio track, so it plays unmuted — autoplay-with-sound is safe here
|
||||||
|
// specifically because reaching this scene already required a user gesture (clicking the
|
||||||
|
// game's icon in the arcade), which is what the browser's autoplay gate actually checks for.
|
||||||
|
// The game's own soundtrack is paused for the duration so the two don't fight over the same
|
||||||
|
// speakers, then resumed once the clip ends or is skipped.
|
||||||
|
|
||||||
|
import * as Phaser from 'phaser';
|
||||||
|
import { GAME_HEIGHT, GAME_WIDTH } from '../../config.js';
|
||||||
|
import { FONT } from './TAHud.js';
|
||||||
|
|
||||||
|
const INTRO_KEY = 'ta-mov-intro';
|
||||||
|
const INTRO_PATH = 'assets/videos/ta/ta_intro.mp4';
|
||||||
|
// Source resolution, used until the real texture decodes (a Video's own width/height report a
|
||||||
|
// square placeholder before its first frame).
|
||||||
|
const INTRO_SRC_W = 864;
|
||||||
|
const INTRO_SRC_H = 480;
|
||||||
|
|
||||||
|
// How long "Press any key to skip" stays up after the first press before it quietly stands
|
||||||
|
// down (rather than skipping on its own) and waits for the next one.
|
||||||
|
const SKIP_HOLD_MS = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the intro clip full-screen, then call `onDone`. The first key press or click surfaces a
|
||||||
|
* skip prompt; a second one inside SKIP_HOLD_MS jumps straight to `onDone`. A load failure or
|
||||||
|
* playback error also falls straight through to `onDone` — a missing intro is never allowed to
|
||||||
|
* strand the player in front of a black screen.
|
||||||
|
*/
|
||||||
|
export function playIntroVideo(scene, onDone) {
|
||||||
|
let done = false;
|
||||||
|
let video = null;
|
||||||
|
let skipArmed = false;
|
||||||
|
let skipTimer = null;
|
||||||
|
let skipText = null;
|
||||||
|
|
||||||
|
scene.music?.pause();
|
||||||
|
let musicResumed = false;
|
||||||
|
const resumeMusic = () => {
|
||||||
|
if (musicResumed) return;
|
||||||
|
musicResumed = true;
|
||||||
|
scene.music?.resume();
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = scene.add.container(0, 0).setDepth(1000);
|
||||||
|
root.add(scene.add.rectangle(0, 0, GAME_WIDTH, GAME_HEIGHT, 0x000000, 1).setOrigin(0, 0));
|
||||||
|
|
||||||
|
const clearPrompt = () => {
|
||||||
|
skipTimer?.remove(false);
|
||||||
|
skipTimer = null;
|
||||||
|
skipText?.destroy();
|
||||||
|
skipText = null;
|
||||||
|
skipArmed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
scene.input.keyboard?.off('keydown', onInput);
|
||||||
|
scene.input.off('pointerdown', onInput);
|
||||||
|
clearPrompt();
|
||||||
|
video?.destroy();
|
||||||
|
root.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
const finish = () => {
|
||||||
|
if (done) return;
|
||||||
|
done = true;
|
||||||
|
cleanup();
|
||||||
|
resumeMusic();
|
||||||
|
onDone();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Belt-and-braces: if the scene tears down mid-clip (e.g. a hot reload), drop our own
|
||||||
|
// listeners and the video element without firing onDone a second time behind whatever else
|
||||||
|
// shutdown is already doing.
|
||||||
|
scene.events.once('shutdown', () => { if (!done) { done = true; cleanup(); } });
|
||||||
|
|
||||||
|
const armSkipPrompt = () => {
|
||||||
|
skipArmed = true;
|
||||||
|
skipText = scene.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 96, 'Press any key to skip', {
|
||||||
|
fontFamily: FONT, fontSize: '28px', color: '#e8f4ff',
|
||||||
|
}).setOrigin(0.5).setAlpha(0);
|
||||||
|
root.add(skipText);
|
||||||
|
scene.tweens.add({ targets: skipText, alpha: 1, duration: 200 });
|
||||||
|
skipTimer = scene.time.delayedCall(SKIP_HOLD_MS, clearPrompt);
|
||||||
|
};
|
||||||
|
|
||||||
|
function onInput() {
|
||||||
|
if (done) return;
|
||||||
|
if (skipArmed) { finish(); return; }
|
||||||
|
armSkipPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.input.keyboard?.on('keydown', onInput);
|
||||||
|
scene.input.on('pointerdown', onInput);
|
||||||
|
|
||||||
|
const startVideo = () => {
|
||||||
|
video = scene.add.video(GAME_WIDTH / 2, GAME_HEIGHT / 2, INTRO_KEY);
|
||||||
|
video.setLoop(false);
|
||||||
|
video.setMute(false);
|
||||||
|
video.setVolume(1);
|
||||||
|
|
||||||
|
// Cover, not contain: this is meant to fill the screen edge-to-edge, and the clip's own
|
||||||
|
// aspect (864x480) is within a hair of the game's (1920x1080) anyway, so the crop is
|
||||||
|
// imperceptible.
|
||||||
|
const fit = () => {
|
||||||
|
const real = !!video.videoTexture;
|
||||||
|
const w = real ? video.width : INTRO_SRC_W;
|
||||||
|
const h = real ? video.height : INTRO_SRC_H;
|
||||||
|
video.setScale(Math.max(GAME_WIDTH / w, GAME_HEIGHT / h));
|
||||||
|
};
|
||||||
|
fit();
|
||||||
|
video.on('created', fit);
|
||||||
|
video.on('playing', fit);
|
||||||
|
|
||||||
|
video.once('complete', finish);
|
||||||
|
video.once('error', finish);
|
||||||
|
// A browser that refuses to autoplay sound fires 'locked' and keeps retrying muted from
|
||||||
|
// preUpdate.
|
||||||
|
video.once('locked', () => { if (video?.scene) video.setMute(true); });
|
||||||
|
|
||||||
|
video.play(false);
|
||||||
|
root.add(video);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deliberately NOT in the asset manifest / preloaded ahead of time — this clip is loaded
|
||||||
|
// on demand, right here, the moment the game itself starts.
|
||||||
|
if (scene.cache.video?.exists(INTRO_KEY)) {
|
||||||
|
startVideo();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.load.video(INTRO_KEY, INTRO_PATH, false);
|
||||||
|
scene.load.once(`filecomplete-video-${INTRO_KEY}`, () => { if (!done) startVideo(); });
|
||||||
|
scene.load.once(Phaser.Loader.Events.FILE_LOAD_ERROR, (file) => {
|
||||||
|
if (file?.key === INTRO_KEY) finish();
|
||||||
|
});
|
||||||
|
if (!scene.load.isLoading()) scene.load.start();
|
||||||
|
}
|
||||||
|
|
@ -1644,59 +1644,67 @@ function acquireTarget(state, rules, e, def, hash, near) {
|
||||||
function fireWeapon(state, rules, e, w, target, facing, aimPt) {
|
function fireWeapon(state, rules, e, w, target, facing, aimPt) {
|
||||||
const aim = aimPt ?? aimPointOn(rules, e, target);
|
const aim = aimPt ?? aimPointOn(rules, e, target);
|
||||||
const muzzle = (w.fx?.muzzle ?? e.radius);
|
const muzzle = (w.fx?.muzzle ?? e.radius);
|
||||||
const ox = e.x + Math.cos(facing) * muzzle;
|
const barrels = w.barrels ?? 1;
|
||||||
const oy = e.y + Math.sin(facing) * muzzle;
|
// Perpendicular to the firing line, so twin-barrel weapons (the Megatank's cannon and its
|
||||||
|
// side-mounted rockets) spawn each shot from its own barrel tip instead of the hull centre.
|
||||||
|
const perpX = -Math.sin(facing), perpY = Math.cos(facing);
|
||||||
|
|
||||||
state.events.push({
|
for (let bi = 0; bi < barrels; bi++) {
|
||||||
t: 'weaponFired', id: e.id, army: e.army, weapon: w.id,
|
const lateral = barrels > 1 ? (bi - (barrels - 1) / 2) * (w.barrelSpacing / (barrels - 1)) : 0;
|
||||||
x: ox, y: oy, heading: facing, style: w.fx.style, sound: w.sound,
|
const ox = e.x + Math.cos(facing) * muzzle + perpX * lateral;
|
||||||
});
|
const oy = e.y + Math.sin(facing) * muzzle + perpY * lateral;
|
||||||
|
|
||||||
if (w.kind === 'hitscan' || w.kind === 'beam') {
|
|
||||||
let hit = true;
|
|
||||||
if (w.spread > 0) {
|
|
||||||
// Spread becomes a miss chance that grows with range — cheap, and it means massed
|
|
||||||
// infantry still trade meaningfully at the edge of their envelope.
|
|
||||||
const d = Math.hypot(aim.x - e.x, aim.y - e.y);
|
|
||||||
const missChance = Math.min(0.6, w.spread * (d / w.range) * 3);
|
|
||||||
hit = rngNext(state) >= missChance;
|
|
||||||
}
|
|
||||||
const ex = hit ? aim.x : aim.x + rngRange(state, -24, 24);
|
|
||||||
const ey = hit ? aim.y : aim.y + rngRange(state, -24, 24);
|
|
||||||
state.events.push({
|
state.events.push({
|
||||||
t: 'shot', style: w.fx.style, x1: ox, y1: oy, x2: ex, y2: ey,
|
t: 'weaponFired', id: e.id, army: e.army, weapon: w.id,
|
||||||
color: w.fx.color, width: w.fx.width, lifeMs: w.fx.lifeMs ?? 80, army: e.army,
|
x: ox, y: oy, heading: facing, style: w.fx.style, sound: w.sound,
|
||||||
});
|
});
|
||||||
if (hit) {
|
|
||||||
applyDamage(state, rules, target, w.damage * (w.armorMul?.[rules.defById[target.defId].armorClass] ?? 1), e);
|
if (w.kind === 'hitscan' || w.kind === 'beam') {
|
||||||
if (w.aoe) applyAoe(state, rules, aim.x, aim.y, w, e);
|
let hit = true;
|
||||||
state.events.push({ t: 'impact', x: ex, y: ey, radius: w.aoe ?? 8, style: w.fx.style, sound: w.impactSound });
|
if (w.spread > 0) {
|
||||||
|
// Spread becomes a miss chance that grows with range — cheap, and it means massed
|
||||||
|
// infantry still trade meaningfully at the edge of their envelope.
|
||||||
|
const d = Math.hypot(aim.x - e.x, aim.y - e.y);
|
||||||
|
const missChance = Math.min(0.6, w.spread * (d / w.range) * 3);
|
||||||
|
hit = rngNext(state) >= missChance;
|
||||||
|
}
|
||||||
|
const ex = hit ? aim.x : aim.x + rngRange(state, -24, 24);
|
||||||
|
const ey = hit ? aim.y : aim.y + rngRange(state, -24, 24);
|
||||||
|
state.events.push({
|
||||||
|
t: 'shot', style: w.fx.style, x1: ox, y1: oy, x2: ex, y2: ey,
|
||||||
|
color: w.fx.color, width: w.fx.width, lifeMs: w.fx.lifeMs ?? 80, army: e.army,
|
||||||
|
});
|
||||||
|
if (hit) {
|
||||||
|
applyDamage(state, rules, target, w.damage * (w.armorMul?.[rules.defById[target.defId].armorClass] ?? 1), e);
|
||||||
|
if (w.aoe) applyAoe(state, rules, aim.x, aim.y, w, e);
|
||||||
|
state.events.push({ t: 'impact', x: ex, y: ey, radius: w.aoe ?? 8, style: w.fx.style, sound: w.impactSound });
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Travelling ordnance: leading matters, and a strafing target can genuinely be missed.
|
// Travelling ordnance: leading matters, and a strafing target can genuinely be missed.
|
||||||
let aimX = aim.x, aimY = aim.y;
|
let aimX = aim.x, aimY = aim.y;
|
||||||
if (w.leadTarget) {
|
if (w.leadTarget) {
|
||||||
const d = Math.hypot(target.x - ox, target.y - oy);
|
const d = Math.hypot(target.x - ox, target.y - oy);
|
||||||
const flight = d / w.speed;
|
const flight = d / w.speed;
|
||||||
aimX += (target.vx ?? 0) * flight;
|
aimX += (target.vx ?? 0) * flight;
|
||||||
aimY += (target.vy ?? 0) * flight;
|
aimY += (target.vy ?? 0) * flight;
|
||||||
}
|
}
|
||||||
let ang = Math.atan2(aimY - oy, aimX - ox);
|
let ang = Math.atan2(aimY - oy, aimX - ox);
|
||||||
if (w.spread > 0) ang += rngRange(state, -w.spread, w.spread);
|
if (w.spread > 0) ang += rngRange(state, -w.spread, w.spread);
|
||||||
|
|
||||||
if (state.projectiles.length >= rules.constants.projectileCap) return;
|
if (state.projectiles.length >= rules.constants.projectileCap) break;
|
||||||
state.projectiles.push({
|
state.projectiles.push({
|
||||||
id: state.nextProjectileId++,
|
id: state.nextProjectileId++,
|
||||||
weapon: w.id, army: e.army, ownerId: e.id,
|
weapon: w.id, army: e.army, ownerId: e.id,
|
||||||
x: ox, y: oy, px: ox, py: oy,
|
x: ox, y: oy, px: ox, py: oy,
|
||||||
vx: Math.cos(ang) * w.speed, vy: Math.sin(ang) * w.speed,
|
vx: Math.cos(ang) * w.speed, vy: Math.sin(ang) * w.speed,
|
||||||
targetId: w.kind === 'guided' ? target.id : 0,
|
targetId: w.kind === 'guided' ? target.id : 0,
|
||||||
aimX, aimY,
|
aimX, aimY,
|
||||||
ttl: Math.ceil(((w.range * 1.4) / w.speed) * rules.constants.tickHz),
|
ttl: Math.ceil(((w.range * 1.4) / w.speed) * rules.constants.tickHz),
|
||||||
trail: [],
|
trail: [],
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stepProjectiles(state, rules) {
|
function stepProjectiles(state, rules) {
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,12 @@ export function compileRules(json) {
|
||||||
// The D-Gun is the reason this flag exists — left on auto-fire, a defending Commander
|
// The D-Gun is the reason this flag exists — left on auto-fire, a defending Commander
|
||||||
// deletes one attacker every reload for free, which makes assaulting any base suicide.
|
// deletes one attacker every reload for free, which makes assaulting any base suicide.
|
||||||
w.manual = w.manual === true;
|
w.manual = w.manual === true;
|
||||||
|
// Multi-barrel weapons (the Megatank's twin cannon and side rockets) fire every barrel
|
||||||
|
// simultaneously on one reload, each barrel's shot spawned `barrelSpacing` px apart,
|
||||||
|
// perpendicular to the firing line, split evenly either side of the hull centreline.
|
||||||
|
w.barrels = w.barrels ?? 1;
|
||||||
|
if (!(Number.isInteger(w.barrels) && w.barrels >= 1)) fail(`weapon "${w.id}" barrels must be a positive integer`);
|
||||||
|
if (w.barrels > 1 && !(w.barrelSpacing > 0)) fail(`weapon "${w.id}" has barrels > 1 but no positive barrelSpacing`);
|
||||||
}
|
}
|
||||||
const weaponById = indexById(weapons);
|
const weaponById = indexById(weapons);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,12 @@ export function skirmishSetup(scene, { onBack, onStart }) {
|
||||||
const skills = [1, 2, 3, 4, 5].map((n) => ({ label: `SKILL ${n}`, value: n }));
|
const skills = [1, 2, 3, 4, 5].map((n) => ({ label: `SKILL ${n}`, value: n }));
|
||||||
const victories = (rules.victoryModes ?? []).map((v) => ({ label: v.label, value: v.id, desc: v.desc }));
|
const victories = (rules.victoryModes ?? []).map((v) => ({ label: v.label, value: v.id, desc: v.desc }));
|
||||||
const victoryStart = Math.max(0, victories.findIndex((v) => v.value === rules.constants.victoryDefault));
|
const victoryStart = Math.max(0, victories.findIndex((v) => v.value === rules.constants.victoryDefault));
|
||||||
|
const themeStart = Math.max(0, themes.findIndex((t) => t.value === sk.defaults?.theme));
|
||||||
|
|
||||||
const cfg = {
|
const cfg = {
|
||||||
seed: (Math.random() * 0xffffffff) >>> 0,
|
seed: (Math.random() * 0xffffffff) >>> 0,
|
||||||
size: sizes[0].value,
|
size: sizes[0].value,
|
||||||
theme: themes[0]?.value ?? 'grasslands',
|
theme: themes[themeStart]?.value ?? 'grasslands',
|
||||||
symmetry: syms[0].value,
|
symmetry: syms[0].value,
|
||||||
victory: victories[victoryStart]?.value ?? rules.constants.victoryDefault,
|
victory: victories[victoryStart]?.value ?? rules.constants.victoryDefault,
|
||||||
armies: [
|
armies: [
|
||||||
|
|
@ -155,7 +156,7 @@ export function skirmishSetup(scene, { onBack, onStart }) {
|
||||||
|
|
||||||
const left = 300, right = GAME_WIDTH - 700;
|
const left = 300, right = GAME_WIDTH - 700;
|
||||||
cycler(scene, objs, left, 300, 'MAP SIZE', sizes, 0, (v) => { cfg.size = v; });
|
cycler(scene, objs, left, 300, 'MAP SIZE', sizes, 0, (v) => { cfg.size = v; });
|
||||||
cycler(scene, objs, left, 400, 'THEME', themes, 0, (v) => { cfg.theme = v; });
|
cycler(scene, objs, left, 400, 'THEME', themes, themeStart, (v) => { cfg.theme = v; });
|
||||||
cycler(scene, objs, left, 500, 'SYMMETRY', syms, 0, (v) => { cfg.symmetry = v; });
|
cycler(scene, objs, left, 500, 'SYMMETRY', syms, 0, (v) => { cfg.symmetry = v; });
|
||||||
cycler(scene, objs, left, 600, 'OPPONENT SKILL', skills, 2, (v) => { cfg.armies[1].skill = v; });
|
cycler(scene, objs, left, 600, 'OPPONENT SKILL', skills, 2, (v) => { cfg.armies[1].skill = v; });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import TAWorldView, { DEPTHS } from './TAWorldView.js';
|
||||||
import TAFx from './TAFx.js';
|
import TAFx from './TAFx.js';
|
||||||
import TAHud from './TAHud.js';
|
import TAHud from './TAHud.js';
|
||||||
import * as Screens from './TAScreens.js';
|
import * as Screens from './TAScreens.js';
|
||||||
|
import { playIntroVideo } from './TAIntroVideo.js';
|
||||||
|
|
||||||
const SAVE_KEY = 'totalannihilation-save';
|
const SAVE_KEY = 'totalannihilation-save';
|
||||||
const SETTINGS_KEY = 'totalannihilation-settings';
|
const SETTINGS_KEY = 'totalannihilation-settings';
|
||||||
|
|
@ -93,7 +94,7 @@ export default class TotalAnnihilationGame extends Phaser.Scene {
|
||||||
.catch(() => { /* portraits are optional */ });
|
.catch(() => { /* portraits are optional */ });
|
||||||
|
|
||||||
this.events.once('shutdown', () => this._teardown());
|
this.events.once('shutdown', () => this._teardown());
|
||||||
this.showMenu();
|
playIntroVideo(this, () => this.showMenu());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,8 @@ let the scale do the work.
|
||||||
| 12 | Fighter | plan-view airframe, swept wings, no tracks or wheels | 46 |
|
| 12 | Fighter | plan-view airframe, swept wings, no tracks or wheels | 46 |
|
||||||
| 13 | Bomber | plan-view airframe, long straight wing, wing-mounted engines | 60 |
|
| 13 | Bomber | plan-view airframe, long straight wing, wing-mounted engines | 60 |
|
||||||
| 14 | Hover Constructor | skirted hull over a plenum, nanolathe crane, no tracks | 52 |
|
| 14 | Hover Constructor | skirted hull over a plenum, nanolathe crane, no tracks | 52 |
|
||||||
|
| 15 | Megatank | hull + tracks, wider than the Tank | 66 |
|
||||||
|
| 16 | Megatank turret | twin cannon barrels + side rocket pods | 66 |
|
||||||
|
|
||||||
### Units that leave the ground
|
### Units that leave the ground
|
||||||
|
|
||||||
|
|
@ -115,7 +117,8 @@ The Hover Constructor uses the same machinery at a much lower height — it shou
|
||||||
independently, so the unit aims while it drives. Rules:
|
independently, so the unit aims while it drives. Rules:
|
||||||
|
|
||||||
- The turret's **pivot is the centre of the cell** — draw the turret so its rotation point
|
- The turret's **pivot is the centre of the cell** — draw the turret so its rotation point
|
||||||
sits at (32, 32), with the barrel extending to the **right**.
|
sits at the cell centre (64, 64 for the current 128×128 unit cells), with the barrel
|
||||||
|
extending to the **right**.
|
||||||
- Leave the rest of the turret cell transparent. The hull shows through it.
|
- Leave the rest of the turret cell transparent. The hull shows through it.
|
||||||
- A unit with no turret (infantry, sniper, rocket trooper, construction vehicle, and all three
|
- A unit with no turret (infantry, sniper, rocket trooper, construction vehicle, and all three
|
||||||
Airfield units) simply has no turret frame; its whole sprite rotates to face its target. The
|
Airfield units) simply has no turret frame; its whole sprite rotates to face its target. The
|
||||||
|
|
@ -123,9 +126,15 @@ independently, so the unit aims while it drives. Rules:
|
||||||
they just rotate to face wherever they're moving. Aircraft have fixed forward guns, so they
|
they just rotate to face wherever they're moving. Aircraft have fixed forward guns, so they
|
||||||
aim by pointing the whole airframe, which is why they have no turret either.
|
aim by pointing the whole airframe, which is why they have no turret either.
|
||||||
|
|
||||||
Frames 15–63 are free. Add a unit by appending a definition to `units[]` in
|
Frames 17–63 are free. Add a unit by appending a definition to `units[]` in
|
||||||
`data/totalannihilation-rules.json` with its `frame` (and optional `turretFrame`) — no code.
|
`data/totalannihilation-rules.json` with its `frame` (and optional `turretFrame`) — no code.
|
||||||
|
|
||||||
|
A weapon may declare `barrels` (default 1) and, when `barrels > 1`, a `barrelSpacing` in world
|
||||||
|
px: every barrel fires at once on the same reload, each shot spawned that many px apart,
|
||||||
|
perpendicular to the firing line and centred on the hull. This is how the Megatank's twin
|
||||||
|
cannon and side rocket pods line up with the two barrels/pods painted into its turret frame —
|
||||||
|
measure the offset between them in the source PNG and scale by `spritePx / frameWidth`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Structure sheets (128×128 cells, 6 per row)
|
## Structure sheets (128×128 cells, 6 per row)
|
||||||
|
|
|
||||||
|
|
@ -72,11 +72,14 @@ const MISSIONS = [
|
||||||
defeat: 'KLAXON out-produced us and it showed.',
|
defeat: 'KLAXON out-produced us and it showed.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Re-seeded twice as combat balance moved under it, both times measured over an 8-run
|
// Re-seeded three times as combat balance moved under it, each time measured over an
|
||||||
// sweep: 55930 -> 40411 when armed aircraft stopped being able to hover, then 40411 (which
|
// 8-run sweep: 55930 -> 40411 when armed aircraft stopped being able to hover, then 40411
|
||||||
// fell to 4/8) -> 71644 when the AI learned to build static defences. Cy-Bro is skill 4
|
// (which fell to 4/8) -> 71644 when the AI learned to build static defences, then 71644
|
||||||
// here, which is exactly the level at which defences switch on.
|
// (which fell to 6/8, and lost both of verify §13's own fixed sample runs — seed and
|
||||||
id: 'm05', name: 'Tropic Vice', seed: 71644, size: 'medium', theme: 'tropics', symmetry: 'mirror-x',
|
// seed+101 — even though the full 8-run rate was technically still at the bar) -> 67890
|
||||||
|
// (8/8) when rocketpod's armorMul was rebalanced weak-vs-vehicle/unchanged-vs-structure.
|
||||||
|
// Cy-Bro is skill 4 here, which is exactly the level at which defences switch on.
|
||||||
|
id: 'm05', name: 'Tropic Vice', seed: 67890, size: 'medium', theme: 'tropics', symmetry: 'mirror-x',
|
||||||
enemy: { commander: 'cybro', skill: 4, aggression: 0.7 },
|
enemy: { commander: 'cybro', skill: 4, aggression: 0.7 },
|
||||||
startResources: [{ mass: 2600, energy: 4200 }, { mass: 1500, energy: 2600 }],
|
startResources: [{ mass: 2600, energy: 4200 }, { mass: 1500, energy: 2600 }],
|
||||||
prebuilt: [
|
prebuilt: [
|
||||||
|
|
@ -84,12 +87,17 @@ const MISSIONS = [
|
||||||
{ id: 'barracks', dx: 4, dy: 3 },
|
{ id: 'barracks', dx: 4, dy: 3 },
|
||||||
{ enemy: true, id: 'energygen', dx: 3, dy: 2 },
|
{ enemy: true, id: 'energygen', dx: 3, dy: 2 },
|
||||||
],
|
],
|
||||||
briefing: 'Cy-Bro starts ahead and pushes early. Rocket tanks out-range everything CORE fields.',
|
briefing: 'Cy-Bro starts ahead and pushes early. Rockets crack their base open — armour holds the line getting there.',
|
||||||
victory: 'Cy-Bro is scrap on the sand.',
|
victory: 'Cy-Bro is scrap on the sand.',
|
||||||
defeat: 'The push came faster than the plant did.',
|
defeat: 'The push came faster than the plant did.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'm06', name: 'Annihilation', seed: 63541, size: 'medium', theme: 'snowfields', symmetry: 'rotational',
|
// Re-seeded once, 8-run sweep: 63541 (fell to 0/8) -> 80808 (8/8) when static defence
|
||||||
|
// became a skill-3+ behaviour with a PROACTIVE first tower (TAAI.urgentDefence no longer
|
||||||
|
// waits for a raid to build the first one). KLAXON here is skill 5, well past that gate,
|
||||||
|
// so it now stands up a tower before the player-bot's rush can land — 90123 and 56789 also
|
||||||
|
// scored 8/8 in the same sweep if this needs revisiting again.
|
||||||
|
id: 'm06', name: 'Annihilation', seed: 80808, size: 'medium', theme: 'snowfields', symmetry: 'rotational',
|
||||||
enemy: { commander: 'klaxon', skill: 5, aggression: 0.85 },
|
enemy: { commander: 'klaxon', skill: 5, aggression: 0.85 },
|
||||||
startResources: [{ mass: 3200, energy: 5200 }, { mass: 1800, energy: 3000 }],
|
startResources: [{ mass: 3200, energy: 5200 }, { mass: 1800, energy: 3000 }],
|
||||||
prebuilt: [
|
prebuilt: [
|
||||||
|
|
@ -97,7 +105,7 @@ const MISSIONS = [
|
||||||
{ id: 'barracks', dx: 4, dy: 3 }, { id: 'vehicleplant', dx: -4, dy: 3 },
|
{ id: 'barracks', dx: 4, dy: 3 }, { id: 'vehicleplant', dx: -4, dy: 3 },
|
||||||
{ enemy: true, id: 'energygen', dx: 3, dy: -3 }, { enemy: true, id: 'barracks', dx: 4, dy: 3 },
|
{ enemy: true, id: 'energygen', dx: 3, dy: -3 }, { enemy: true, id: 'barracks', dx: 4, dy: 3 },
|
||||||
],
|
],
|
||||||
briefing: 'KLAXON at full strength with a base already standing. Nothing clever left — out-build it and end it.',
|
briefing: 'KLAXON at full strength with a base already standing, dug in behind its own defences. Nothing clever left — out-build it and end it.',
|
||||||
victory: 'Total annihilation. There is nothing left to fight.',
|
victory: 'Total annihilation. There is nothing left to fight.',
|
||||||
defeat: 'KLAXON was simply better prepared.',
|
defeat: 'KLAXON was simply better prepared.',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1078,11 +1078,13 @@ section('6. Combat');
|
||||||
const rvs = equal('rockettank', 'sniper');
|
const rvs = equal('rockettank', 'sniper');
|
||||||
check('rocket tanks beat equal-cost snipers', rvs.fracA > rvs.fracB,
|
check('rocket tanks beat equal-cost snipers', rvs.fracA > rvs.fracB,
|
||||||
`${rvs.a} (${(rvs.fracA * 100) | 0}%) v ${rvs.b} (${(rvs.fracB * 100) | 0}%)`);
|
`${rvs.a} (${(rvs.fracA * 100) | 0}%) v ${rvs.b} (${(rvs.fracB * 100) | 0}%)`);
|
||||||
// Rockets out-range and out-damage armour; that is what makes the roster a triangle
|
// Rockets are the answer to buildings and air, not to armour — a straight fight against a
|
||||||
// rather than a ladder where one unit is simply the answer.
|
// Tank is one the Rocket Tank should lose, which is what makes the roster a triangle
|
||||||
|
// (Tank counters Rocket Tank, Rocket Tank counters Sniper/structures) rather than a ladder
|
||||||
|
// where one unit is simply the answer.
|
||||||
const rvt = equal('rockettank', 'tank');
|
const rvt = equal('rockettank', 'tank');
|
||||||
check('rocket tanks beat equal-cost tanks', rvt.fracA > rvt.fracB,
|
check('tanks beat equal-cost rocket tanks', rvt.fracB > rvt.fracA,
|
||||||
`${rvt.a} (${(rvt.fracA * 100) | 0}%) v ${rvt.b} (${(rvt.fracB * 100) | 0}%)`);
|
`${rvt.b} tanks (${(rvt.fracB * 100) | 0}%) v ${rvt.a} rocket tanks (${(rvt.fracA * 100) | 0}%)`);
|
||||||
|
|
||||||
// Engagements must take long enough for reinforcement and composition to matter. When
|
// Engagements must take long enough for reinforcement and composition to matter. When
|
||||||
// fights resolved in 2-4 seconds the whole AI skill ladder collapsed to a coin flip.
|
// fights resolved in 2-4 seconds the whole AI skill ladder collapsed to a coin flip.
|
||||||
|
|
@ -1098,7 +1100,11 @@ section('6. Combat');
|
||||||
check('rifle is strong vs infantry, weak vs medium',
|
check('rifle is strong vs infantry, weak vs medium',
|
||||||
armorMul(rifle, 'infantry') > armorMul(rifle, 'medium') * 3);
|
armorMul(rifle, 'infantry') > armorMul(rifle, 'medium') * 3);
|
||||||
const rocket = combatRules.weaponById.rocketpod;
|
const rocket = combatRules.weaponById.rocketpod;
|
||||||
check('rockets favour armour over infantry', armorMul(rocket, 'heavy') > armorMul(rocket, 'infantry') * 3);
|
check('rockets favour fortifications over vehicle armour',
|
||||||
|
armorMul(rocket, 'fortification') > armorMul(rocket, 'heavy') * 3);
|
||||||
|
check('rockets are weak against vehicle armour',
|
||||||
|
armorMul(rocket, 'light') < 1 && armorMul(rocket, 'medium') < 1 && armorMul(rocket, 'heavy') < 1,
|
||||||
|
`light ${armorMul(rocket, 'light')}, medium ${armorMul(rocket, 'medium')}, heavy ${armorMul(rocket, 'heavy')}`);
|
||||||
|
|
||||||
// Commander death explosion.
|
// Commander death explosion.
|
||||||
const dth = combatRules.unitById.commander.deathExplosion;
|
const dth = combatRules.unitById.commander.deathExplosion;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue