Added vehicle/ordnance snippets.

master
Pacman Ghost 6 years ago
parent 0f80578ce3
commit 956e57462a
  1. 29
      conftest.py
  2. 1
      setup.py
  3. 1
      vasl_templates/webapp/__init__.py
  4. 4
      vasl_templates/webapp/data/default-template-pack/baz.j2
  5. 6
      vasl_templates/webapp/data/default-template-pack/mol-p.j2
  6. 40
      vasl_templates/webapp/data/default-template-pack/ordnance.j2
  7. 6
      vasl_templates/webapp/data/default-template-pack/pf.j2
  8. 4
      vasl_templates/webapp/data/default-template-pack/piat.j2
  9. 4
      vasl_templates/webapp/data/default-template-pack/psk.j2
  10. 42
      vasl_templates/webapp/data/default-template-pack/vehicles.j2
  11. 175
      vasl_templates/webapp/data/ordnance/german.json
  12. 139
      vasl_templates/webapp/data/ordnance/russian.json
  13. 679
      vasl_templates/webapp/data/vehicles/german.json
  14. 489
      vasl_templates/webapp/data/vehicles/russian.json
  15. 7
      vasl_templates/webapp/main.py
  16. 13
      vasl_templates/webapp/static/css/main.css
  17. 42
      vasl_templates/webapp/static/css/tabs-ob.css
  18. 9
      vasl_templates/webapp/static/css/tabs-scenario.css
  19. 75
      vasl_templates/webapp/static/main.js
  20. 318
      vasl_templates/webapp/static/snippets.js
  21. 61
      vasl_templates/webapp/static/utils.js
  22. 149
      vasl_templates/webapp/static/vehicles_ordnance.js
  23. 39
      vasl_templates/webapp/templates/index.html
  24. 98
      vasl_templates/webapp/templates/vo-report.html
  25. 2
      vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_setup.j2
  26. 4
      vasl_templates/webapp/tests/fixtures/data/default-template-pack/ordnance.j2
  27. 5
      vasl_templates/webapp/tests/fixtures/data/default-template-pack/vehicles.j2
  28. 17
      vasl_templates/webapp/tests/fixtures/data/ordnance/german.json
  29. 17
      vasl_templates/webapp/tests/fixtures/data/ordnance/russian.json
  30. 19
      vasl_templates/webapp/tests/fixtures/data/vehicles/german.json
  31. 26
      vasl_templates/webapp/tests/fixtures/data/vehicles/russian.json
  32. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/ordnance.j2
  33. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/vehicles.j2
  34. 1
      vasl_templates/webapp/tests/fixtures/template-packs/new-default/ordnance.j2
  35. 1
      vasl_templates/webapp/tests/fixtures/template-packs/new-default/vehicles.j2
  36. 34
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/german/1940.txt
  37. 34
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/german/1941.txt
  38. 34
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/german/1942.txt
  39. 34
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/german/1943.txt
  40. 34
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/german/1944.txt
  41. 34
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/german/1945.txt
  42. 33
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/russian/1940.txt
  43. 33
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/russian/1941.txt
  44. 33
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/russian/1942.txt
  45. 33
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/russian/1943.txt
  46. 33
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/russian/1944.txt
  47. 33
      vasl_templates/webapp/tests/fixtures/vo-reports/ordnance/russian/1945.txt
  48. 124
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/german/1940.txt
  49. 124
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/german/1941.txt
  50. 124
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/german/1942.txt
  51. 124
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/german/1943.txt
  52. 124
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/german/1944.txt
  53. 124
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/german/1945.txt
  54. 89
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt
  55. 89
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt
  56. 89
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt
  57. 89
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt
  58. 89
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt
  59. 89
      vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt
  60. 12
      vasl_templates/webapp/tests/test_ob_setup.py
  61. 48
      vasl_templates/webapp/tests/test_scenario_persistence.py
  62. 9
      vasl_templates/webapp/tests/test_ssr.py
  63. 6
      vasl_templates/webapp/tests/test_template_packs.py
  64. 242
      vasl_templates/webapp/tests/test_vehicles_ordnance.py
  65. 94
      vasl_templates/webapp/tests/test_vo_reports.py
  66. 34
      vasl_templates/webapp/tests/utils.py
  67. 60
      vasl_templates/webapp/vehicles_ordnance.py

@ -5,6 +5,7 @@ import threading
import logging
import tempfile
import urllib.request
from urllib.error import URLError
import pytest
from flask import url_for
@ -17,6 +18,17 @@ FLASK_WEBAPP_PORT = 5001
# ---------------------------------------------------------------------
def pytest_addoption( parser ):
"""Configure pytest options."""
# add an option to control checking of vehicle/ordnance reports
# NOTE: This file needs to be in the project root for this to work :-/
parser.addoption(
"--vo-reports", action="store_true", dest="check_vo_reports", default=False,
help="Check the vehicle/ordnance reports."
)
# ---------------------------------------------------------------------
@pytest.fixture
def webapp():
"""Launch the webapp."""
@ -32,7 +44,7 @@ def webapp():
# configure the webapp to use our test data
# NOTE: Can't seem to change constants.DATA_DIR (probably some pytest funkiness :-/)
app.config["DATA_DIR"] = os.path.join( os.path.split(__file__)[0], "fixtures/data" )
app.config["DATA_DIR"] = os.path.join( os.path.split(__file__)[0], "vasl_templates/webapp/tests/fixtures/data" )
# start the webapp server (in a background thread)
logging.disable( logging.CRITICAL )
@ -40,6 +52,21 @@ def webapp():
target = lambda: app.run( host="0.0.0.0", port=FLASK_WEBAPP_PORT, use_reloader=False )
)
thread.start()
# wait for the server to start up
def is_ready():
"""Try to connect to the webapp server."""
try:
resp = urllib.request.urlopen( app.url_for("ping") ).read()
assert resp == b"pong"
return True
except URLError:
return False
except Exception as ex: #pylint: disable=broad-except
assert False, "Unexpected exception: {}".format(ex)
utils.wait_for( 5, is_ready )
# return the server to the caller
yield app
# shutdown the webapp server

@ -22,6 +22,7 @@ setup(
extras_require = {
"dev": [
"pytest==3.6.0",
"tabulate==0.8.2",
"selenium==3.12.0",
"pylint==1.9.2",
"pytest-pylint==0.9.0",

@ -41,6 +41,7 @@ if os.path.isfile( _fname ):
# load the application
import vasl_templates.webapp.main #pylint: disable=cyclic-import
import vasl_templates.webapp.vehicles_ordnance #pylint: disable=cyclic-import
import vasl_templates.webapp.snippets #pylint: disable=cyclic-import
# initialize the application

@ -74,6 +74,6 @@ td.r { text-align: right ; }
<td colspan="2" class="r"> 8-{{BAZ_RANGE}}
{%endif%}
</table>
</table>
</html>
</html>

@ -49,7 +49,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
<tr>
<td colspan="2" class="r"> 4-4
</table>
<tr>
<td colspan="2">
IFT DR original colored dr:
@ -57,6 +57,6 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
<li> 1 = Flame in target Location
<li> 6 = thrower breaks, Flame in their Location
</ul>
</table>
</html>
</html>

@ -0,0 +1,40 @@
<html>
<head>
<style>
td { margin: 0 ; padding: 0 ; }
.note { font-size: 90% ; font-style: italic ; color: #808080 ; }
</style>
</head>
<table style="
{%if ORDNANCE_WIDTH%} width: {{ORDNANCE_WIDTH}} ; {%endif%}
">
<tr>
<td colspan="2" style="
background: #{{OB_COLOR}} ;
border-bottom: 1px solid #{{OB_COLOR_2}} ;
padding: 2px 5px ;
font-weight: bold ;
">
{{PLAYER_NAME}} Ordnance
{%for ord in ORDNANCE%}
<tr style="border-bottom:1px dotted #e0e0e0;">
<td valign="top" style="padding:2px 5px;">
<b> {{ord.name}} </b>
<div class="note">
{%if ord.notes%}
{{ord.note_number}}, {{ord.notes | join(", ")}}
{%else%}
{{ord.note_number}}
{%endif%}
</div>
<td valign="top" style="padding:2px 5px;">
{%for cap in ord.capabilities%} <div> {{cap}} </div> {%endfor%}
{%endfor%}
</table>
</html>

@ -37,7 +37,7 @@ td.r { text-align: right ; }
<td> +1 <td> non-AFV target
</table>
original 6 = pinned
<td valign="top" style="padding:0 3px;">
<table>
<tr>
@ -57,7 +57,7 @@ td.r { text-align: right ; }
<td style="padding:0 3px;text-align:right;">
<b>TK#:</b> 31 <br>
16-3
</table>
</html>
</html>

@ -46,6 +46,6 @@ td.r { text-align: right ; }
<tr>
<td colspan="2" class="r"> 8-3
</table>
</table>
</html>
</html>

@ -48,6 +48,6 @@ td.r { text-align: right ; }
<tr>
<td colspan="2" class="r"> 12-4
</table>
</table>
</html>
</html>

@ -0,0 +1,42 @@
<html>
<head>
<style>
td { margin: 0 ; padding: 0 ; }
.note { font-size: 90% ; font-style: italic ; color: #808080 ; }
</style>
</head>
<table style="
{%if VEHICLES_WIDTH%} width: {{VEHICLES_WIDTH}} ; {%endif%}
">
<tr>
<td colspan="2" style="
background: #{{OB_COLOR}} ;
border-bottom: 1px solid #{{OB_COLOR_2}} ;
padding: 2px 5px ;
font-weight: bold ;
">
{{PLAYER_NAME}} Vehicles
{%for veh in VEHICLES%}
<tr style="border-bottom:1px dotted #e0e0e0;">
<td valign="top" style="padding:2px 5px;">
<b> {{veh.name}} </b>
<div class="note">
{%if veh.notes%}
{{veh.note_number}}, {{veh.notes | join(", ")}}
{%else%}
{{veh.note_number}}
{%endif%}
</div>
<td valign="top" style="padding:2px 5px;">
{% if veh.no_radio %} <div> {{veh.no_radio}} </div> {%endif%}
{%for cap in veh.capabilities%} <div> {{cap}} </div> {%endfor%}
{%if veh.crew_survival%} <div> {{veh.crew_survival}} </div> {%endif%}
{%endfor%}
</table>
</html>

@ -0,0 +1,175 @@
[
{ "name": "5cm leGrW",
"capabilities_other": [ "5PP" ],
"note_number": "1",
"notes": [ "N" ]
},
{ "name": "8cm GrW 34",
"capabilities": [ "NT", "QSU", "IR" ],
"capabilities2": { "s": 8 },
"note_number": "2\u2020",
"notes": [ "N" ]
},
{ "name": "10cm NbW 35",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "s": 10 },
"note_number": "3",
"notes": [ "O" ]
},
{ "name": "12cm GrW 42",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "s": 8 },
"note_number": "4\u2020"
},
{ "name": "2.8cm sPzB 41",
"capabilities": [ "NT", "QSU" ],
"capabilities_other": [ "uses APCR TK Table\u2020" ],
"note_number": "5\u2020",
"notes": [ "A\u2020", "N", "P" ]
},
{ "name": "3.7cm PaK 35/36",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [4,1], [5,2], [4,3], [3,4] ], "H": "6[9]\u2020" },
"note_number": "6",
"notes": [ "B\u2020", "N", "P" ]
},
{ "name": "4.2cm lePaK 41",
"capabilities": [ "NT", "QSU" ],
"capabilities_other": [ "uses APCR TK Table\u2020" ],
"note_number": "7",
"notes": [ "A\u2020", "N" ]
},
{ "name": "5cm PaK 38",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [5,1], [6,2], [5,3], [4,4] ] },
"note_number": "8\u2020",
"notes": [ "N" ]
},
{ "name": "7.5cm PaK 97/38",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "H": 6, "s": 7 },
"note_number": "9",
"notes": [ "N" ]
},
{ "name": "7.5cm PaK 40",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [5,2], [4,3], [3,4] ], "s": 7 },
"note_number": "10\u2020",
"notes": [ "N" ]
},
{ "name": "7.62cm PaK 36r",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [5,2], [4,3], [3,4] ], "s": 6 },
"note_number": "11\u2020",
"notes": [ "N" ]
},
{ "name": "8.8cm PaK 43",
"capabilities": [ "T" ],
"capabilities2": { "LF": [ "88\u2020", "1 ROF", "B11", "<b>M4</b>", "-1" ] },
"note_number": "12",
"notes": [ "C\u2020" ]
},
{ "name": "8.8cm PaK 43/41",
"capabilities": [ "NT" ],
"note_number": "13"
},
{ "name": "12.8cm K 81/1",
"capabilities": [ "NT", "NM" ],
"note_number": "14"
},
{ "name": "7.5cm leIG 18",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "H": 7 },
"note_number": "15",
"notes": [ "N", "O" ]
},
{ "name": "15cm sIG 33",
"capabilities": [ "NT" ],
"capabilities2": { "H": 7, "s": 8 },
"note_number": "16",
"notes": [ "N" ]
},
{ "name": "7.5cm LG 40",
"capabilities": [ "T", "QSU" ],
"capabilities2": { "H": 6 },
"note_number": "17",
"notes": [ "N" ]
},
{ "name": "10.5cm LG 42",
"capabilities": [ "T", "QSU" ],
"capabilities2": { "H": 6, "s": 6 },
"note_number": "18\u2020",
"notes": [ "N" ]
},
{ "name": "7.5cm leFK 16nA",
"capabilities": [ "NT", "QSU", "h-d" ],
"capabilities2": { "H": 5, "s": 8 },
"note_number": "19"
},
{ "name": "10.5cm leFH 18",
"capabilities": [ "NT" ],
"capabilities2": { "H": 6, "s": 8 },
"note_number": "20",
"notes": [ "N" ]
},
{ "name": "s 10cm K 18",
"capabilities": [ "NT", "NM" ],
"capabilities2": { "H": 6, "s": 8 },
"note_number": "21",
"notes": [ "N" ]
},
{ "name": "15cm sFH 18",
"capabilities": [ "NT", "NM" ],
"capabilities2": { "H": 6, "s": 8 },
"note_number": "22",
"notes": [ "N" ]
},
{ "name": "15cm K 18",
"capabilities": [ "NT", "RFNM" ],
"capabilities2": { "AP": 6 },
"note_number": "23",
"notes": [ "N" ]
},
{ "name": "17cm K 18",
"capabilities": [ "NT", "RFNM", "no IF" ],
"capabilities2": { "AP": 6 },
"note_number": "24",
"notes": [ "N" ]
},
{ "name": "2cm FlaK 30",
"capabilities": [ "T" ],
"capabilities2": { "LF": [ "20\u2020", "2 ROF", "B10", "NT" ] },
"note_number": "25",
"notes": [ "C\u2020", "N" ]
},
{ "name": "2cm FlaK 38",
"capabilities": [ "T" ],
"capabilities2": { "LF": [ "20\u2020", "2 ROF", "B11", "NT" ] },
"note_number": "26",
"notes": [ "C\u2020", "N" ]
},
{ "name": "2cm FlaKvierling 38",
"capabilities": [ "T" ],
"capabilities_other": [ "4TK DR\u2020" ],
"note_number": "27\u2020",
"notes": [ "N" ]
},
{ "name": "3.7cm FlaK 36 o. 37",
"capabilities": [ "T" ],
"capabilities2": { "H": "5[9]\u2020" },
"note_number": "28",
"notes": [ "B\u2020", "N" ]
},
{ "name": "3.7cm FlaK 43",
"capabilities": [ "T" ],
"note_number": "29"
},
{ "name": "8.8cm FlaK 18 o. 36",
"capabilities": [ "T" ],
"capabilities2": { "A": [ [5,2], [4,3], [3,4] ], "LF": [ "88L\u2020", "1 ROF", "B11" ] },
"note_number": "30",
"notes": [ "C\u2020", "N" ]
}
]

@ -0,0 +1,139 @@
[
{ "name": "50mm RM obr. 40",
"capabilities_other": [ "4PP" ],
"note_number": "1"
},
{ "name": "37mm PM-39",
"capabilities_other": [ "1PP", "Air Bursts & Acq. NA" ],
"note_number": "1.1",
"notes": [ "A" ]
},
{ "name": "82mm BM obr. 37",
"capabilities": [ "NT", "QSU", "IR" ],
"capabilities2": { "s": 8 },
"note_number": "2\u2020"
},
{ "name": "107mm GVPM obr. 38",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "s": 8 },
"note_number": "3",
"notes": [ "A" ]
},
{ "name": "120mm PM obr. 38",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "s": 8 },
"note_number": "4"
},
{ "name": "160mm PM obr. 43",
"capabilities": [ "NT", "no IF" ],
"note_number": "5"
},
{ "name": "37mm PTP obr. 30",
"capabilities": [ "NT", "QSU" ],
"note_number": "6"
},
{ "name": "45mm PTP obr. 32",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [4,2], [5,3], [6,4], [7,5] ] },
"note_number": "7"
},
{ "name": "45mm PTP obr. 42",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [4,2], [5,3], [6,4], [7,5] ] },
"note_number": "8"
},
{ "name": "57mm PTP obr. 43",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [4,3], [5,4], [6,5] ] },
"note_number": "9\u2020"
},
{ "name": "100mm PTP obr. 44",
"capabilities": [ "NT" ],
"note_number": "10"
},
{ "name": "37mm PP obr. 15R",
"capabilities": [ "NT", "QSU", "h-d" ],
"note_number": "11"
},
{ "name": "76.2mm PP obr. 27",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "H": 6, "s": 8 },
"note_number": "12",
"notes": [ "A" ]
},
{ "name": "76.2mm P obr. 00/02P",
"capabilities": [ "NT", "QSU", "h-d" ],
"capabilities2": { "s": 8 },
"note_number": "13"
},
{ "name": "76.2mm P obr. 02/30",
"capabilities": [ "NT", "QSU", "h-d" ],
"capabilities2": { "s": 8 },
"note_number": "14"
},
{ "name": "76.2mm P obr. 39",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [4,2], [5,3], [6,4], [7,5] ], "s": 8 },
"note_number": "15\u2020"
},
{ "name": "76.2mm P obr. 36",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [4,2] ], "s": 8 },
"note_number": "16\u2020"
},
{ "name": "85mm P obr. 44",
"capabilities": [ "NT", "QSU" ],
"capabilities2": { "A": [ [5,4], [6,5] ], "s": 8 },
"note_number": "17"
},
{ "name": "107mm P obr. 10/30",
"capabilities": [ "NT", "h-d" ],
"capabilities2": { "s": 8 },
"note_number": "18"
},
{ "name": "122mm G obr. 10/30",
"capabilities": [ "NT", "h-d" ],
"capabilities2": { "s": 8 },
"note_number": "19"
},
{ "name": "122mm G obr. 38",
"capabilities": [ "NT" ],
"capabilities2": { "H": 6, "s": 8 },
"note_number": "20"
},
{ "name": "122mm P obr. 31",
"capabilities": [ "NT" ],
"capabilities2": { "s": 8 },
"note_number": "21"
},
{ "name": "152mm G obr. 38",
"capabilities": [ "NT" ],
"note_number": "22"
},
{ "name": "152mm GP obr. 37",
"capabilities": [ "NT" ],
"note_number": "23"
},
{ "name": "203mm B-4 obr. 1931",
"capabilities": [ "NT", "RFNM", "no IF" ],
"note_number": "23.1"
},
{ "name": "25mm ZP obr. 40",
"capabilities": [ "T" ],
"note_number": "24"
},
{ "name": "37mm ZP obr. 39",
"capabilities": [ "T" ],
"note_number": "25\u2020"
},
{ "name": "76.2mm ZP obr. 38",
"capabilities": [ "T" ],
"note_number": "26\u2020"
},
{ "name": "85mm ZP obr. 39",
"capabilities": [ "T" ],
"note_number": "27"
}
]

@ -0,0 +1,679 @@
[
{ "name": "PzKpfw IB",
"CS#": 2,
"capabilities2": { "sD": 6 },
"note_number": "1\u2020",
"notes": [ "N" ]
},
{ "name": "PzKpfw IIA",
"CS#": 3,
"capabilities2": { "sD": 6 },
"note_number": "2"
},
{ "name": "PzKpfw IIF",
"CS#": 3,
"capabilities2": { "sD": 7 },
"note_number": "3",
"notes": [ "N" ]
},
{ "name": "PzKpfw II(Fl)",
"CS#": "2:brewup",
"capabilities2": { "sD": "7\u2020" },
"note_number": "4\u2020"
},
{ "name": "PzKpfw IIL",
"CS#": 4,
"capabilities2": { "sD": 7 },
"note_number": "5"
},
{ "name": "PzKpfw 35t",
"CS#": 4,
"capabilities2": { "sD": 5 },
"note_number": "6\u2020"
},
{ "name": "PzKpfw 38(t)A",
"CS#": 4,
"capabilities2": { "A": [ [4,1], [5,2] ], "sD": 6 },
"note_number": "7\u2020",
"notes": [ "E" ]
},
{ "name": "PzKpfw 38(t)E",
"CS#": 4,
"capabilities2": { "A": [ [4,1], [5,2] ], "sD": 7 },
"note_number": "8\u2020",
"notes": [ "E" ]
},
{ "name": "Aufklaerer 38(t)",
"CS#": 5,
"note_number": "9",
"notes": [ "A\u2020", "E" ]
},
{ "name": "FT-17 730m(f)",
"CS#": 2,
"note_number": "9.1\u2020",
"notes": [ "a", "c", "f" ]
},
{ "name": "FT-17 730(f)",
"CS#": 2,
"capabilities2": { "AP": 9 },
"note_number": "9.1\u2020",
"notes": [ "a", "c", "f" ]
},
{ "name": "38H 735(f)",
"CS#": 3,
"capabilities2": { "AP": 10 },
"note_number": "9.2",
"notes": [ "a", "b\u2020", "f" ]
},
{ "name": "35-S 739(f)",
"CS#": 4,
"note_number": "9.3",
"notes": [ "a", "b\u2020<sup>2</sup>", "d\u2020<sup>1</sup>", "f" ]
},
{ "name": "PzKpfw IIID",
"CS#": 5,
"capabilities2": { "sD": 6 },
"note_number": "10\u2020"
},
{ "name": "PzKpfw IIIF",
"CS#": 5,
"capabilities2": { "A": [ [4,1] ], "sD": 7 },
"note_number": "11\u2020"
},
{ "name": "PzKpfw IIIG",
"CS#": 5,
"capabilities2": { "A": [ [4,1] ], "sD": 7 },
"note_number": "12",
"notes": [ "N" ]
},
{ "name": "PzKpfw IIIH",
"CS#": 6,
"capabilities2": { "A": [ [4,1] ], "sD": 7 },
"note_number": "13\u2020",
"notes": [ "N" ]
},
{ "name": "PzKpfw IIIJ",
"CS#": 6,
"capabilities2": { "A": [ [6,2] ], "sD": 7 },
"note_number": "14\u2020",
"notes": [ "N", "S" ]
},
{ "name": "PzKpfw IIIL",
"CS#": 5,
"capabilities2": { "A": [ [6,2] ], "sD": 7 },
"note_number": "15\u2020",
"notes": [ "B\u2020", "N", "S" ]
},
{ "name": "PzKpfw IIIN",
"CS#": 5,
"capabilities2": { "H": 9, "s": 9, "sD": 7 },
"note_number": "16",
"notes": [ "B\u2020", "N", "S" ]
},
{ "name": "PzKpfw III(Fl)",
"CS#": 3,
"capabilities2": { "sD": 7 },
"note_number": "17"
},
{ "name": "PzKpfw IVA",
"CS#": 5,
"capabilities2": { "AP": 7, "s": 9, "sD": 6 },
"note_number": "18\u2020"
},
{ "name": "PzKpfw IVC",
"CS#": 5,
"capabilities2": { "AP": 7, "s": 9, "sD": 6 },
"note_number": "19\u2020"
},
{ "name": "PzKpfw IVD",
"CS#": 5,
"capabilities2": { "H": 6, "AP": 7, "s": 9, "sD": 7 },
"note_number": "20",
"notes": [ "N", "R\u2020" ]
},
{ "name": "PzKpfw IVE",
"CS#": 6,
"capabilities2": { "H": 6, "AP": 7, "s": 9, "sD": 7 },
"note_number": "21",
"notes": [ "N", "R\u2020" ]
},
{ "name": "PzKpfw IVF<sub>1</sub>",
"CS#": 6,
"capabilities2": { "H": 6, "AP": 7, "s": 9, "sD": 7 },
"note_number": "22",
"notes": [ "N", "R\u2020" ]
},
{ "name": "PzKpfw IVF<sub>2</sub>",
"CS#": 6,
"capabilities2": { "A": [ [5,2] ], "s": 8, "sD": 6 },
"note_number": "23\u2020",
"notes": [ "N", "S" ]
},
{ "name": "PzKpfw IVH",
"CS#": 6,
"capabilities2": { "A": [ [5,2] ], "s": 8, "sD": 5 },
"note_number": "24\u2020",
"notes": [ "B\u2020", "S" ]
},
{ "name": "PzKpfw IVJ",
"CS#": 6,
"capabilities2": { "A": [ [3,4] ], "s": 8, "sN": 9 },
"note_number": "25",
"notes": [ "S" ]
},
{ "name": "PzKpfw VD",
"CS#": "5:brewup",
"capabilities2": { "sD": 5 },
"note_number": "26",
"notes": [ "F\u2020" ]
},
{ "name": "PzKpfw VG",
"CS#": 6,
"capabilities2": { "sN": [ [7,4] ] },
"note_number": "27\u2020",
"notes": [ "H\u2020" ]
},
{ "name": "PzKpfw M15/42(i)",
"CS#": 4,
"note_number": "28",
"notes": [ "I" ]
},
{ "name": "PzKpfw P26/40(i)",
"CS#": 4,
"capabilities2": { "s": 8 },
"note_number": "29",
"notes": [ "I" ]
},
{ "name": "PzKpfw VIE",
"CS#": 6,
"capabilities2": { "A": [ [6,2] ], "sD": 7, "M": "7\u2020" },
"note_number": "30\u2020",
"notes": [ "K", "N" ]
},
{ "name": "PzKpfw VIE (L)",
"CS#": 6,
"capabilities2": { "A": [ [4,4] ], "sN": [ [7,4] ] },
"note_number": "31",
"notes": [ "K" ]
},
{ "name": "PzKpfw VIB",
"CS#": 6,
"capabilities2": { "sN": 9 },
"note_number": "32\u2020",
"notes": [ "K" ]
},
{ "name": "StuG IIIB",
"CS#": 4,
"capabilities2": { "H": 6, "AP": 7, "s": 9, "sD": 7 },
"note_number": "33\u2020"
},
{ "name": "StuG IIIG",
"CS#": 4,
"capabilities2": { "A": [ [6,2] ], "s": 8, "sD": 7 },
"note_number": "34\u2020",
"notes": [ "N", "P\u2020", "S" ]
},
{ "name": "StuG IIIG (L)",
"CS#": 4,
"capabilities2": { "A": [ [4,4] ], "s": 8, "sN": 9 },
"note_number": "35",
"notes": [ "O\u2020", "Q\u2020", "S" ]
},
{ "name": "StuH 42",
"CS#": 4,
"capabilities2": { "H": 9, "s": 9, "sD": 7 },
"note_number": "36\u2020",
"notes": [ "N", "P\u2020", "S" ]
},
{ "name": "StuH 42 (L)",
"CS#": 4,
"capabilities2": { "H": 9, "s": 9, "sN": 9 },
"note_number": "35",
"notes": [ "O\u2020", "Q\u2020", "S" ]
},
{ "name": "StuPz IV",
"CS#": 6,
"no_if": true,
"capabilities2": { "H": 7, "s": 9 },
"note_number": "37\u2020",
"notes": [ "S" ]
},
{ "name": "Sturmtiger",
"CS#": 5,
"no_if": true,
"capabilities2": { "sN": 9 },
"note_number": "37.1\u2020",
"notes": [ "K" ]
},
{ "name": "PzJg Tiger",
"CS#": 7,
"note_number": "38"
},
{ "name": "StuG 75/18(i)",
"CS#": 3,
"capabilities2": { "H": 7, "s": 9 },
"note_number": "39",
"notes": [ "I", "P\u2020" ]
},
{ "name": "StuG 75/34(i)",
"CS#": 3,
"capabilities2": { "s": 9 },
"note_number": "40",
"notes": [ "I", "P\u2020" ]
},
{ "name": "StuG 105/25(i)",
"CS#": 3,
"no_if": true,
"capabilities2": { "H": 7, "s": 9 },
"note_number": "41",
"notes": [ "I", "P\u2020" ]
},
{ "name": "StuG 75/46(i)",
"CS#": 3,
"capabilities2": { "s": 9 },
"note_number": "42",
"notes": [ "I", "P\u2020" ]
},
{ "name": "PzJg I",
"CS#": 3,
"capabilities2": { "A": [ [5,1] ], "HE": 7 },
"note_number": "43",
"notes": [ "N" ]
},
{ "name": "PzJg 35R(f)",
"CS#": 3,
"capabilities2": { "A": [ [5,1] ], "HE": 7 },
"note_number": "44",
"notes": [ "a" ]
},
{ "name": "Pz 35R 731(f)",
"CS#": 2,
"note_number": "44.1\u2020",
"notes": [ "a" ]
},
{ "name": "Marder I",
"CS#": "4:brewup",
"capabilities2": { "A": [ [6,2] ], "HE": 7, "s": 7 },
"note_number": "45\u2020",
"notes": [ "a" ]
},
{ "name": "GSW 39H(f) Pak",
"CS#": 3,
"capabilities2": { "A": [ [4,4] ], "HE": 9 },
"note_number": "45.1",
"notes": [ "a", "e" ]
},
{ "name": "Marder II",
"CS#": "4:brewup",
"capabilities2": { "A": [ [6,2] ], "HE": 7, "s": 7 },
"note_number": "46",
"notes": [ "M\u2020", "N" ]
},
{ "name": "Marder III(t)H",
"CS#": "4:brewup",
"capabilities2": { "A": [ [6,2] ], "HE": 7, "s": 7, "sD": 6 },
"note_number": "47",
"notes": [ "E", "G", "N" ]
},
{ "name": "Marder III(t)M",
"CS#": "4:brewup",
"capabilities2": { "A": [ [5,3] ], "HE": 7, "s": 7, "sD": 5 },
"note_number": "48",
"notes": [ "E" ]
},
{ "name": "PzJg III/IV",
"CS#": 5,
"capabilities2": { "HE": 7 },
"note_number": "49",
"notes": [ "M\u2020" ]
},
{ "name": "JgdPz 38(t)",
"CS#": 4,
"capabilities2": { "A": [ [4,4] ], "HE": 7 },
"note_number": "50",
"notes": [ "E", "O\u2020" ]
},
{ "name": "JgdPz 38(t) (Fl)",
"CS#": 4,
"note_number": "51",
"notes": [ "E", "O\u2020" ]
},
{ "name": "JgdPz IV",
"CS#": 4,
"capabilities2": { "A": [ [4,4] ], "HE": 7, "s": 7, "sN": [ [7,4] ] },
"note_number": "52",
"notes": [ "S" ]
},
{ "name": "JgdPz IV (L)",
"CS#": 4,
"capabilities2": { "A": [ [4,4] ], "HE": 7, "s": 7, "sN": [ [8,4] ] },
"note_number": "52",
"notes": [ "S" ]
},
{ "name": "StuIG 33B",
"CS#": 5,
"no_if": true,
"capabilities2": { "H": 7, "s": 9 },
"note_number": "53\u2020"
},
{ "name": "JgdPz IV/70",
"CS#": 4,
"capabilities2": { "HE": 7, "sN": 9 },
"note_number": "54\u2020",
"notes": [ "S" ]
},
{ "name": "JgdPz V",
"CS#": 6,
"capabilities2": { "HE": 7, "sN": 9 },
"note_number": "55",
"notes": [ "H" ]
},
{ "name": "JgdPz VI",
"CS#": 7,
"capabilities2": { "HE": 7, "sN": 9 },
"note_number": "56"
},
{ "name": "SPW 250/1",
"CS#": 4,
"note_number": "57",
"notes": [ "N" ]
},
{ "name": "SPW 250/sMG",
"CS#": "4\u2020",
"note_number": "58\u2020",
"notes": [ "J\u2020", "N" ]
},
{ "name": "SPW 250/7",
"CS#": "4:brewup",
"capabilities": [ "IR" ],
"capabilities2": { "s": 8 },
"note_number": "59",
"notes": [ "J\u2020", "N" ]
},
{ "name": "SPW 250/8",
"CS#": "4:brewup",
"capabilities2": { "H": 6, "s": 8 },
"note_number": "60",
"notes": [ "Q\u2020" ]
},
{ "name": "SPW 250/9",
"CS#": 4,
"note_number": "61\u2020",
"notes": [ "A\u2020" ]
},
{ "name": "SPW 250/10",
"CS#": 4,
"capabilities2": { "A": [ [4,1] ] },
"note_number": "62",
"notes": [ "J\u2020", "N" ]
},
{ "name": "SPW 251/1",
"CS#": 5,
"note_number": "63",
"notes": [ "C\u2020", "N" ]
},
{ "name": "SPW 251/sMG",
"CS#": "5\u2020",
"note_number": "58\u2020",
"notes": [ "C\u2020", "J\u2020", "N" ]
},
{ "name": "SPW 251/2",
"CS#": "4:brewup",
"capabilities": [ "IR" ],
"capabilities2": { "s": 8 },
"note_number": "59",
"notes": [ "C\u2020", "J\u2020", "N" ]
},
{ "name": "SPW 251/9",
"CS#": "4:brewup",
"capabilities2": { "H": 6, "s": 9 },
"note_number": "64",
"notes": [ "C\u2020", "N", "Q\u2020" ]
},
{ "name": "SPW 251/10",
"CS#": 4,
"capabilities2": { "A": [ [4,1] ] },
"note_number": "65",
"notes": [ "C\u2020", "J\u2020", "N" ]
},
{ "name": "SPW 251/16",
"CS#": "4:brewup",
"note_number": "66\u2020"
},
{ "name": "SPW 251/21",
"CS#": 4,
"note_number": "66.1\u2020"
},
{ "name": "SPW 251/22",
"CS#": "4:brewup",
"capabilities2": { "HE": 7, "s": 7 },
"note_number": "67",
"notes": [ "J\u2020" ]
},
{ "name": "SPW S307(f)",
"CS#": 4,
"capabilities2": { "A": [ [4,4] ], "HE": 9 },
"note_number": "67.1",
"notes": [ "a", "e" ]
},
{ "name": "mSPW S307(f)",
"CS#": 4,
"no_if": true,
"capabilities": [ "IR" ],
"capabilities2": { "s": 5 },
"note_number": "67.2",
"notes": [ "a", "e" ]
},
{ "name": "Kfz 13",
"CS#": 2,
"note_number": "68\u2020"
},
{ "name": "PSW 221",
"CS#": 2,
"capabilities2": { "sD": 6 },
"note_number": "69\u2020",
"notes": [ "A\u2020", "N" ]
},
{ "name": "PSW 222",
"CS#": 3,
"capabilities2": { "sD": 6 },
"note_number": "70\u2020",
"notes": [ "A\u2020", "N" ]
},
{ "name": "PSW 222 (L)",
"CS#": 3,
"capabilities2": { "sD": 6 },
"note_number": "70\u2020",
"notes": [ "A\u2020", "N" ]
},
{ "name": "PSW 231 (6 rad)",
"CS#": 4,
"note_number": "71\u2020"
},
{ "name": "PSW 231 (8 rad)",
"CS#": 4,
"capabilities2": { "sD": 6 },
"note_number": "72\u2020",
"notes": [ "N" ]
},
{ "name": "PSW 232 (8 rad)",
"CS#": 4,
"capabilities2": { "sD": 6 },
"note_number": "72",
"notes": [ "N" ]
},
{ "name": "PSW 233",
"CS#": 5,
"capabilities2": { "H": 8, "s": 9, "sD": 7 },
"note_number": "73",
"notes": [ "N" ]
},
{ "name": "PSW 234/1",
"CS#": 5,
"note_number": "74",
"notes": [ "A\u2020" ]
},
{ "name": "PSW 234/2",
"CS#": 4,
"capabilities2": { "A": [ [5,3] ], "sD": 7 },
"note_number": "75"
},
{ "name": "PSW 234/3",
"CS#": 5,
"capabilities2": { "H": 8, "s": 9 },
"note_number": "76",
"notes": [ "Q\u2020" ]
},
{ "name": "PSW 234/4",
"CS#": "5:brewup",
"capabilities2": { "HE": 7, "s": 7 },
"note_number": "77\u2020"
},
{ "name": "sIG IB",
"CS#": "4:brewup",
"no_if": true,
"capabilities2": { "H": 6, "s": 9 },
"note_number": "78",
"notes": [ "G" ]
},
{ "name": "sIG II",
"CS#": "4:brewup",
"no_if": true,
"capabilities2": { "H": 6, "s": 9 },
"note_number": "79",
"notes": [ "N" ]
},
{ "name": "sIG 38(t)M",
"CS#": "4:brewup",
"no_if": true,
"capabilities2": { "H": 6, "s": 9 },
"note_number": "80",
"notes": [ "E", "N" ]
},
{ "name": "PzA II",
"CS#": 4,
"capabilities2": { "H": 5, "s": 8 },
"note_number": "81",
"notes": [ "M\u2020" ]
},
{ "name": "PzA LrS(f)",
"CS#": 4,
"no_if": true,
"capabilities2": { "H": 4, "s": 8 },
"note_number": "82",
"notes": [ "a", "N" ]
},
{ "name": "GSW 39H(f)",
"CS#": 4,
"capabilities2": { "H": 4, "s": 7 },
"note_number": "82.1",
"notes": [ "a", "e" ]
},
{ "name": "PzA III/IV",
"CS#": 6,
"no_if": true,
"capabilities2": { "H": 5, "s": 8 },
"note_number": "83"
},
{ "name": "FlaKPz 38(t)",
"CS#": 4,
"note_number": "84",
"notes": [ "A\u2020", "E" ]
},
{ "name": "37 FlaK/Pz IV",
"CS#": 5,
"note_number": "85\u2020",
"notes": [ "A\u2020" ]
},
{ "name": "(Moebelwagen)",
"CS#": 6,
"note_number": "85\u2020",
"notes": [ "A\u2020" ]
},
{ "name": "FlaKPz IV/20",
"CS#": 6,
"note_number": "86",
"notes": [ "A\u2020" ]
},
{ "name": "FlaKPz IV/37",
"CS#": 6,
"note_number": "87",
"notes": [ "A\u2020" ]
},
{ "name": "SdKfz 10/4",
"CS#": 5,
"note_number": "88",
"notes": [ "A\u2020", "D\u2020", "N" ]
},
{ "name": "SdKfz 10/5",
"CS#": 4,
"note_number": "88.1\u2020",
"notes": [ "A\u2020<sup>2</sup>", "D\u2020<sup>1</sup>" ]
},
{ "name": "SdKfz 6/2",
"CS#": 5,
"note_number": "89",
"notes": [ "A\u2020", "D\u2020", "N" ]
},
{ "name": "SdKfz 7/1",
"CS#": 4,
"note_number": "90",
"notes": [ "A\u2020", "D\u2020", "N" ]
},
{ "name": "Kfz 4",
"CS#": 3,
"note_number": "91\u2020",
"notes": [ "N" ]
},
{ "name": "2cm FlaK LKW",
"CS#": 5,
"note_number": "92",
"notes": [ "A\u2020" ]
},
{ "name": "3.7cm FlaK LKW",
"CS#": 5,
"note_number": "92",
"notes": [ "A\u2020" ]
},
{ "name": "Goliath",
"note_number": "93\u2020",
"notes": [ "L\u2020" ]
},
{ "name": "Kfz 1",
"cs#": "2\u2020",
"note_number": "94\u2020",
"notes": [ "L\u2020", "N" ]
},
{ "name": "Kfz 1/20",
"cs#": 2,
"note_number": "95\u2020",
"notes": [ "L\u2020" ]
},
{ "name": "Opel 6700 (Blitz)",
"cs#": 6,
"note_number": "96",
"notes": [ "N" ]
},
{ "name": "Buessing-NAG 4500",
"cs#": 7,
"note_number": "96",
"notes": [ "N" ]
},
{ "name": "SdKfz 2",
"cs#": 2,
"note_number": "97\u2020",
"notes": [ "L\u2020", "N" ]
},
{ "name": "SdKfz 7",
"cs#": 6,
"note_number": "98",
"notes": [ "N" ]
},
{ "name": "SdKfz 11",
"cs#": 5,
"note_number": "99",
"notes": [ "N" ]
}
]

@ -0,0 +1,489 @@
[
{ "name": "T-37",
"no_radio": "\u00ae",
"CS#": 2,
"note_number": "1",
"notes": [ "H\u2020", "M" ]
},
{ "name": "T-27",
"no_radio": "\u00ae",
"CS#": 2,
"note_number": "1.1\u2020",
"notes": [ "M", "Q" ]
},
{ "name": "T-40",
"no_radio": "\u00ae",
"CS#": 2,
"note_number": "2\u2020",
"notes": [ "H\u2020" ]
},
{ "name": "T-50",
"no_radio": "\u00ae",
"CS#": 4,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "3"
},
{ "name": "T-60 M40",
"no_radio": "\u00ae",
"CS#": 2,
"note_number": "4"
},
{ "name": "T-60 M42",
"no_radio": "\u00ae\u2020",
"CS#": 2,
"note_number": "4",
"notes": [ "C\u2020" ]
},
{ "name": "T-70",
"no_radio": "\u00ae\u2020",
"CS#": 2,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "5",
"notes": [ "C\u2020" ]
},
{ "name": "T-26 M33",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "6",
"notes": [ "D", "I\u2020", "M", "P" ]
},
{ "name": "T-26 M37/39",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "6",
"notes": [ "D", "M", "P" ]
},
{ "name": "T-26 M31",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "6.1\u2020",
"notes": [ "M", "P" ]
},
{ "name": "T-26 M32",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "6.2\u2020",
"notes": [ "M", "P" ]
},
{ "name": "OT-26",
"no_radio": "\u00ae",
"CS#": "3:brewup",
"note_number": "6.3\u2020",
"notes": [ "M", "P" ]
},
{ "name": "ST-26 Bridgelayer",
"CS#": 3,
"note_number": "6.4\u2020",
"notes": [ "M" ]
},
{ "name": "OT-133",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "7\u2020",
"notes": [ "M", "P" ]
},
{ "name": "BT-2A",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "7.1\u2020",
"notes": [ "M" ]
},
{ "name": "BT-5 M34",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "8\u2020",
"notes": [ "M", "P" ]
},
{ "name": "BT-7 M37",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "9",
"notes": [ "D", "I\u2020", "M", "P" ]
},
{ "name": "BT-7A",
"CS#": 3,
"capabilities2": { "AP": 7, "s": 9 },
"note_number": "10",
"notes": [ "M", "P" ]
},
{ "name": "T-28 M34",
"no_radio": "\u00ae",
"CS#": 5,
"capabilities2": { "s": 8 },
"note_number": "11\u2020",
"notes": [ "M" ]
},
{ "name": "T-28 M34(L)",
"no_radio": "\u00ae",
"CS#": 5,
"capabilities2": { "s": 8 },
"note_number": "11.1\u2020",
"notes": [ "M" ]
},
{ "name": "T-28E M40",
"no_radio": "\u00ae",
"CS#": 6,
"capabilities2": { "s": 8 },
"note_number": "12\u2020",
"notes": [ "M" ]
},
{ "name": "T-28E M40(L)",
"no_radio": "\u00ae",
"CS#": 6,
"capabilities2": { "s": 8 },
"note_number": "12.1\u2020",
"notes": [ "M" ]
},
{ "name": "T-34 M40",
"no_radio": "\u00ae",
"CS#": 5,
"note_number": "13",
"notes": [ "M" ]
},
{ "name": "T-34 M41",
"no_radio": "\u00ae\u2020",
"CS#": 5,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "14\u2020",
"notes": [ "C\u2020", "M" ]
},
{ "name": "OT-34",
"no_radio": "\u00ae\u2020",
"CS#": 4,
"no_if": true,
"capabilities2": { "A": [ [2,2] ], "AP": 7 },
"note_number": "15\u2020",
"notes": [ "C\u2020", "M" ]
},
{ "name": "T-34 M43",
"CS#": 5,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "16\u2020"
},
{ "name": "T-43",
"no_radio": "\u00ae\u2020",
"CS#": 5,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "17",
"notes": [ "C\u2020", "M" ]
},
{ "name": "T-34/85",
"CS#": 6,
"capabilities2": { "A": [ [5,4] ], "sD": "6\u2020" },
"note_number": "18\u2020",
"notes": [ "J\u2020" ]
},
{ "name": "M4/76(a)",
"CS#": 6,
"capabilities2": { "A": [ [3,4] ], "s": 8, "sM": 8 },
"note_number": "19\u2020",
"notes": [ "N" ]
},
{ "name": "T-44",
"CS#": 5,
"capabilities2": { "A": [ [6,5] ] },
"note_number": "20",
"notes": [ "B\u2020", "M" ]
},
{ "name": "T-35",
"no_radio": "\u00ae",
"CS#": 8,
"capabilities2": { "s": 9 },
"note_number": "21\u2020",
"notes": [ "M" ]
},
{ "name": "SMK",
"CS#": 7,
"note_number": "21.1",
"notes": [ "M", "T\u2020" ]
},
{ "name": "T-100",
"CS#": 7,
"note_number": "22.1",
"notes": [ "M", "T\u2020" ]
},
{ "name": "KV-1 M39/40",
"CS#": 6,
"note_number": "22\u2020",
"notes": [ "M" ]
},
{ "name": "KV-1E",
"CS#": 6,
"note_number": "23",
"notes": [ "D", "M" ]
},
{ "name": "KV-1 M41",
"CS#": 6,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "23",
"notes": [ "D", "M" ]
},
{ "name": "KV-1 M42",
"CS#": 6,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "23",
"notes": [ "D", "M" ]
},
{ "name": "KV-2",
"CS#": 7,
"no_if": true,
"capabilities2": { "AP": 9 },
"note_number": "24\u2020",
"notes": [ "M" ]
},
{ "name": "KV-8",
"CS#": 6,
"capabilities2": { "A": [ [4,2] ], "AP": 7 },
"note_number": "15\u2020",
"notes": [ "D", "M" ]
},
{ "name": "KV-1S",
"CS#": 6,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "25\u2020"
},
{ "name": "KV-85",
"CS#": 5,
"capabilities2": { "A": [ [4,3], [5,4] ] },
"note_number": "26",
"notes": [ "B\u2020" ]
},
{ "name": "IS-2",
"CS#": 6,
"no_if": true,
"note_number": "27",
"notes": [ "B\u2020" ]
},
{ "name": "IS-2m",
"CS#": 6,
"no_if": true,
"note_number": "28",
"notes": [ "B\u2020", "G\u2020" ]
},
{ "name": "IS-3",
"CS#": 6,
"no_if": true,
"capabilities2": { "sD": "6\u2020" },
"note_number": "29",
"notes": [ "G\u2020", "J\u2020" ]
},
{ "name": "SU-76M",
"CS#": "4:brewup",
"capabilities2": { "A": [ [5,3] ] },
"note_number": "30\u2020"
},
{ "name": "SU-76i(g)",
"CS#": 5,
"capabilities2": { "A": [ [5,3] ] },
"note_number": "31",
"notes": [ "K" ]
},
{ "name": "SU-122",
"CS#": 5,
"no_if": true,
"capabilities2": { "H": 9, "s": 8 },
"note_number": "32"
},
{ "name": "SU-152",
"CS#": 6,
"no_if": true,
"note_number": "33\u2020"
},
{ "name": "ISU-122",
"CS#": 6,
"no_if": true,
"capabilities2": { "s": 8 },
"note_number": "34",
"notes": [ "G\u2020" ]
},
{ "name": "ISU-152",
"CS#": 6,
"no_if": true,
"note_number": "34",
"notes": [ "G\u2020" ]
},
{ "name": "SU-85",
"CS#": 5,
"capabilities2": { "A": [ [5,3] ], "HE": 7, "s": 7 },
"note_number": "35"
},
{ "name": "SU-100",
"CS#": 5,
"capabilities2": { "HE": 7 },
"note_number": "36"
},
{ "name": "SU-57(a)",
"CS#": 4,
"capabilities2": { "A": [ [5,3] ], "HE": 7 },
"note_number": "37",
"notes": [ "K" ]
},
{ "name": "BA-20",
"no_radio": "\u00ae",
"CS#": 3,
"note_number": "38"
},
{ "name": "BA-6",
"no_radio": "\u00ae",
"CS#": 4,
"capabilities2": { "A": [ [4,2] ] },
"note_number": "39"
},
{ "name": "BA-64B",
"CS#": 2,
"note_number": "40\u2020",
"notes": [ "A\u2020" ]
},
{ "name": "ZSU-37",
"CS#": 4,
"note_number": "41\u2020",
"notes": [ "A\u2020" ]
},
{ "name": "SU-12",
"CS#": 4,
"capabilities2": { "AP": 7, "s": 8 },
"note_number": "42",
"notes": [ "F\u2020" ]
},
{ "name": "GAZ-4M-AA",
"CS#": 4,
"note_number": "43\u2020",
"notes": [ "A\u2020" ]
},
{ "name": "ZIS-42-AA",
"CS#": 4,
"note_number": "44",
"notes": [ "A\u2020", "F\u2020" ]
},
{ "name": "LANO AT",
"no_radio": "\u00ae",
"CS#": 5,
"capabilities2": { "A": [ [4,"2+"] ] },
"note_number": "44.1"
},
{ "name": "LANO AA",
"no_radio": "\u00ae",
"CS#": 5,
"note_number": "44.2\u2020",
"notes": [ "A\u2020<sup>1</sup>" ]
},
{ "name": "IAG-10-AA",
"CS#": 4,
"capabilities2": { "AP": 7 },
"note_number": "45",
"notes": [ "A\u2020", "F\u2020" ]
},
{ "name": "GAZ-67B",
"CS#": 2,
"note_number": "46",
"notes": [ "L\u2020" ]
},
{ "name": "Komsomolet",
"CS#": "2\u2020",
"note_number": "46.1\u2020"
},
{ "name": "STZ-3",
"cs#": 4,
"note_number": "46.2"
},
{ "name": "STZ-5",
"cs#": 7,
"note_number": "46.3"
},
{ "name": "GAZ-MM",
"cs#": 6,
"note_number": "47"
},
{ "name": "ZIS-5",
"cs#": 7,
"note_number": "47"
},
{ "name": "IAG-6",
"cs#": 7,
"note_number": "47"
},
{ "name": "NKL-6",
"CS#": "2\u2020",
"note_number": "47.1\u2020",
"notes": [ "S\u2020<sup>1</sup>" ]
},
{ "name": "NKL-16",
"cs#": 2,
"note_number": "47.1\u2020",
"notes": [ "S\u2020<sup>1</sup>" ]
},
{ "name": "NKL-26",
"CS#": 2,
"note_number": "47.2\u2020",
"notes": [ "Q", "S\u2020<sup>1</sup>" ]
},
{ "name": "RF-8-GAZ-98",
"CS#": 3,
"note_number": "47.3\u2020",
"notes": [ "S\u2020<sup>1</sup>" ]
},
{ "name": "Stuart III(a)",
"CS#": 4,
"capabilities2": { "C": 6 },
"note_number": "48",
"notes": [ "N", "O", "LL" ]
},
{ "name": "Lee(a)",
"CS#": 7,
"capabilities2": { "C": "4\u2020", "s": "8\u2020" },
"note_number": "49\u2020",
"notes": [ "B\u2020<sup>1</sup>", "N", "O", "LL" ]
},
{ "name": "Sherman III(a)",
"CS#": "5:brewup",
"capabilities2": { "s": 8, "WP": [ [6,"J4+"], "\u2020" ] },
"note_number": "50\u2020",
"notes": [ "N", "O", "R\u2020<sup>1</sup>", "LL" ]
},
{ "name": "Sherman III(L)(a)",
"CS#": "6:brewup",
"capabilities2": { "s": 5, "WP": 7, "sM": 8 },
"note_number": "50.1",
"notes": [ "N", "O", "R\u2020", "LL" ]
},
{ "name": "Matilda II(b)",
"CS#": 5,
"capabilities2": { "sD": 6 },
"note_number": "51\u2020",
"notes": [ "M\u2020<sup>1</sup>", "N", "LL" ]
},
{ "name": "Valentine V(b)",
"CS#": 4,
"capabilities2": { "sM": 8 },
"note_number": "52.1",
"notes": [ "N", "LL" ]
},
{ "name": "Valentine VIII(b)",
"CS#": 4,
"capabilities2": { "HE": 7, "sD": 6 },
"note_number": "52.2",
"notes": [ "N", "LL" ]
},
{ "name": "Churchill III(b)",
"CS#": 7,
"capabilities2": { "D": [ [6,"J4"], [7,5], "\u2020" ], "HE": [ [7,"F3"], [8,"4+"], "\u2020" ], "sD": [ [6,"4+"] ], "sM": "8\u2020" },
"note_number": "53\u2020",
"notes": [ "N", "LL" ]
},
{ "name": "M3A1 Scout Car(a)",
"CS#": 4,
"note_number": "54\u2020",
"notes": [ "N", "LL" ]
},
{ "name": "Jeep GPA(a)",
"cs#": 2,
"note_number": "59",
"notes": [ "H\u2020", "L\u2020<sup>1</sup>" ]
},
{ "name": "DUKW(a)",
"cs#": 7,
"note_number": "60\u2020"
}
]

@ -13,6 +13,13 @@ def main():
# ---------------------------------------------------------------------
@app.route( "/ping" )
def ping():
"""Let the caller know we're alive."""
return "pong"
# ---------------------------------------------------------------------
@app.route( "/shutdown" )
def shutdown():
"""Shutdown the webapp (for testing porpoises)."""

@ -83,6 +83,15 @@ input[type="text"] { margin-bottom: 0.25em ; }
/* -------------------------------------------------------------------- */
.sortable { font-size: 80% ; }
.sortable { list-style-type: none ; margin: 0 ; padding: 0 ; }
.sortable li {
margin-bottom: 2px ; padding: 5px ;
border: 1px dotted #333 ; background: #eee ;
}
.sortable li.highlighted { background: #48c8ff ; }
.sortable li:hover { cursor: pointer ; }
.ui-dialog-titlebar { padding: 0.2em 0.5em 0.2em 0.5em !important ; }
.ui-dialog-titlebar-close { margin-top: -10px !important ; }
@ -98,6 +107,10 @@ input[type="text"] { margin-bottom: 0.25em ; }
.ui-dialog.edit-ssr textarea { resize: none ; width: calc(100% - 4px) ; height: calc(100% - 1.25em) ; }
.ui-dialog.edit-ssr button { margin: 0 0 0 5px ; padding: 0.1em 0.2em ; }
#select-vo { overflow: hidden ; }
#select-vo .header { height: 2em ; }
#select-vo select { width: 100% ; top: 2em ; height: calc(100% - 2em) ; }
.growl-title { display: none ; }
.growl ul { margin-left: 1em ; }
.growl .pre { font-family: "Courier New"; }

@ -1,10 +1,12 @@
/* -------------------------------------------------------------------- */
.panel-obsetup {
height: 100% ;
display: grid ; display: -ms-grid ;
grid-template-rows: 1fr 2.5em ; -ms-grid-rows: 1fr 2.5em ;
grid-template-columns: 1fr ; -ms-grid-columns: 1fr ;
}
/* FUDGE! IE hackamathon follows (nb: <label> doesn't work, we use <div> for labels instead :-/) */
/* FUDGE! IE hackamathon follows... */
.panel-obsetup .footer { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
.panel-obsetup textarea { width: 100% ; height: 100% ; resize: none ; }
@ -12,3 +14,41 @@
.panel-obsetup div.snippet-control { float: left ; margin: 0.25em 0.25em 0 0 ; }
.panel-obsetup .footer .r { display: block-inline ; float: right ; }
.panel-obsetup .footer .r div.snippet-control { float: none ; margin-right: 0 ; }
/* -------------------------------------------------------------------- */
.panel-vehicles {
height: 100% ;
display: grid ; display: -ms-grid ;
grid-template-rows: 1fr 2em ; -ms-grid-rows: 1fr 2em ;
grid-template-columns: 1fr ; -ms-grid-columns: 1fr ;
}
/* FUDGE! IE hackamathon follows... */
.panel-vehicles .content { -ms-grid-row: 1 ; -ms-grid-column: 1 ; }
.panel-vehicles .footer { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
.panel-vehicles .footer { text-align: right ; font-size: 75% ; }
.panel-vehicles .footer .l { float: left ; }
.vehicle-trash { margin-left: 5px ; height: 2em ; }
.vehicle-hint { width:100% ; height: calc(100% - 1.5em) ; font-size: 80% ; font-style: italic ; }
.vehicle-hint p { margin-bottom: 1em ; }
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
.panel-ordnance {
height: 100% ;
display: grid ; display: -ms-grid ;
grid-template-rows: 1fr 2em ; -ms-grid-rows: 1fr 2em ;
grid-template-columns: 1fr ; -ms-grid-columns: 1fr ;
}
/* FUDGE! IE hackamathon follows... */
.panel-ordnance .content { -ms-grid-row: 1 ; -ms-grid-column: 1 ; }
.panel-ordnance .footer { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
.panel-ordnance .footer { text-align: right ; font-size: 75% ; }
.panel-ordnance .footer .l { float: left ; }
.ordnance-trash { margin-left: 5px ; height: 2em ; }
.ordnance-hint { width:100% ; height: calc(100% - 1.5em) ; font-size: 80% ; font-style: italic ; }
.ordnance-hint p { margin-bottom: 1em ; }

@ -70,15 +70,6 @@
#panel-ssr .content { -ms-grid-row: 1 ; -ms-grid-column: 1 ; }
#panel-ssr .footer { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
#panel-ssr #ssr-sortable { width:100% ; height: calc(100% - 1.5em) ; font-size: 80% ; }
#panel-ssr #ssr-sortable { list-style-type: none ; margin: 0 ; padding: 0 ; }
#panel-ssr #ssr-sortable li {
margin-bottom: 2px ; padding: 5px ;
border: 1px dotted #333 ; background: #eee ;
}
#panel-ssr #ssr-sortable li.highlighted { background: #48c8ff ; }
#panel-ssr #ssr-sortable li:hover { cursor: pointer ; }
#panel-ssr .footer { text-align: right ; font-size: 75% ; }
#panel-ssr .footer .l { float: left ; }
#panel-ssr #ssr-trash { margin-left: 5px ; height: 2em ; }

@ -1,6 +1,7 @@
var gTemplatePack = {} ;
var gDefaultNationalities = {} ;
var gValidTemplateIds = [] ;
var gVehicleOrdnanceListings = {} ;
var _NATIONALITY_SPECIFIC_BUTTONS = {
"russian": [ "mol", "mol-p" ],
@ -81,7 +82,6 @@ $(document).ready( function () {
// initialize
$("#ssr-sortable").sortable( { connectWith: "#ssr-trash", cursor: "move" } ) ;
init_ssr( $("#ssr-sortable li") ) ;
$("#add-ssr").click( add_ssr ) ;
$("#ssr-trash").sortable( {
receive: function( evt, ui ) { ui.item.remove() ; update_ssr_hint() ; }
@ -93,6 +93,45 @@ $(document).ready( function () {
}
} ) ;
// initialize vehicle controls (1)
$("#vehicle-sortable_1").sortable( { connectWith: "#vehicle-trash_1", cursor: "move" } ) ;
$("#add-vehicle_1").click( function() { add_vo( "vehicle", 1 ) ; } ) ;
$("#vehicle-trash_1").sortable( {
receive: function( evt, ui ) { ui.item.remove() ; update_vo_hint("vehicle",1) ; }
} ) ;
// initialize vehicle controls (2)
$("#vehicle-sortable_2").sortable( { connectWith: "#vehicle-trash_2", cursor: "move" } ) ;
$("#add-vehicle_2").click( function() { add_vo( "vehicle", 2 ) ; } ) ;
$("#vehicle-trash_2").sortable( {
receive: function( evt, ui ) { ui.item.remove() ; update_vo_hint("vehicle",2) ; }
} ) ;
// initialize ordnance controls (1)
$("#ordnance-sortable_1").sortable( { connectWith: "#ordnance-trash_1", cursor: "move" } ) ;
$("#add-ordnance_1").click( function() { add_vo( "ordnance", 1 ) ; } ) ;
$("#ordnance-trash_1").sortable( {
receive: function( evt, ui ) { ui.item.remove() ; update_vo_hint("ordnance",1) ; }
} ) ;
// initialize ordnance controls (2)
$("#ordnance-sortable_2").sortable( { connectWith: "#ordnance-trash_2", cursor: "move" } ) ;
$("#add-ordnance_2").click( function() { add_vo( "ordnance", 2 ) ; } ) ;
$("#ordnance-trash_2").sortable( {
receive: function( evt, ui ) { ui.item.remove() ; update_vo_hint("ordnance",2) ; }
} ) ;
// handle ENTER and double-clicks in the "select vehicle/ordnance" dialog
function auto_select_vo( evt ) {
if ( $("#select-vo select").val() ) {
$(".ui-dialog.select-vo button:contains('OK')").click() ;
evt.preventDefault() ;
}
}
$("#select-vo").keydown( function(evt) {
if ( evt.keyCode == 13 )
auto_select_vo( evt ) ;
} ) ;
$("#select-vo").dblclick( function(evt) { auto_select_vo(evt) ; } ) ;
// load the ELR's and SAN's
var buf = [] ;
for ( var i=0 ; i <= 5 ; ++i ) // nb: A19.1: ELR is 0-5
@ -111,6 +150,18 @@ $(document).ready( function () {
$("select[name='PLAYER_1']").change( function() { on_player_change($(this)) ; } ) ;
$("select[name='PLAYER_2']").change( function() { on_player_change($(this)) ; } ) ;
// get the vehicle/ordnance listings
$.getJSON( gVehicleListingsUrl, function(data) {
gVehicleOrdnanceListings.vehicle = data ;
} ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the vehicle listings:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ;
} ) ;
$.getJSON( gOrdnanceListingsUrl, function(data) {
gVehicleOrdnanceListings.ordnance = data ;
} ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the ordnance listings:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ;
} ) ;
// get the template pack
$.getJSON( gGetTemplatePackUrl, function(data) {
if ( "error" in data )
@ -157,7 +208,15 @@ $(document).ready( function () {
// replace all the "generate" buttons with "generate/edit" button/droplist's
$("input[type='button'].generate").each( function() {
var template_id = $(this).attr( "data-id" ) ;
var template_id2 = (template_id.substring(0,9) === "ob_setup_") ? "ob_setup" : template_id ;
var template_id2 ;
if ( template_id.substring(0,9) === "ob_setup_" )
template_id2 = "ob_setup" ;
else if ( template_id.substring(0,9) == "vehicles_" )
template_id2 = "vehicles" ;
else if ( template_id.substring(0,9) == "ordnance_" )
template_id2 = "ordnance" ;
else
template_id2 = template_id ;
var buf = [ "<div class='snippet-control' data-id='" + template_id + "'>",
$(this).prop( "outerHTML" ),
"<select data-id='" + template_id2 + "'>",
@ -197,6 +256,10 @@ $(document).ready( function () {
var template_id = $(this).attr( "data-id" ) ;
if ( template_id.substring(0,9) === "ob_setup_" )
template_id = "ob_setup" ;
else if ( template_id.substring(0,9) === "vehicles_" )
template_id = "vehicles" ;
else if ( template_id.substring(0,9) === "ordnance_" )
template_id = "ordnance" ;
$( "<a href='#' class='edit-template-link' data-id='" + template_id + "'" +
" onclick='edit_template(\"" + template_id + "\")'" +
"></a>"
@ -288,4 +351,12 @@ function on_player_change( $select )
$elem.css( "display", nat == player_nat ? "block" : "none" ) ;
}
}
// reset the OB params
$("textarea[name='OB_SETUP_"+player_id+"']").val( "" ) ;
$("input[name='OB_SETUP_WIDTH_"+player_id+"']").val( "" ) ;
delete_all_vo( "vehicle", player_id ) ;
$("input[name='VEHICLES_WIDTH_"+player_id+"']").val( "" ) ;
delete_all_vo( "ordnance", player_id ) ;
$("input[name='ORDNANCE_WIDTH_"+player_id+"']").val( "" ) ;
}

@ -23,54 +23,75 @@ function generate_snippet( $btn )
storeMsgForTestSuite( "_last-warning_", "" ) ;
storeMsgForTestSuite( "_last-error_", "" ) ;
// extract the scenario date components
var params = {} ;
var scenario_date = $("input[name='SCENARIO_DATE']").datepicker( "getDate" ) ;
if ( scenario_date ) {
params.SCENARIO_DAY_OF_MONTH = scenario_date.getDate() ;
var postfix ;
if ( params.SCENARIO_DAY_OF_MONTH in _DAY_OF_MONTH_POSTFIXES )
postfix = _DAY_OF_MONTH_POSTFIXES[ params.SCENARIO_DAY_OF_MONTH ] ;
else
postfix = _DAY_OF_MONTH_POSTFIXES[ params.SCENARIO_DAY_OF_MONTH % 10 ] ;
params.SCENARIO_DAY_OF_MONTH_POSTFIX = params.SCENARIO_DAY_OF_MONTH + postfix ;
params.SCENARIO_MONTH = 1 + scenario_date.getMonth() ;
params.SCENARIO_MONTH_NAME = _MONTH_NAMES[scenario_date.getMonth()] ;
params.SCENARIO_YEAR = scenario_date.getFullYear() ;
}
// unload the template parameters
var template_id = $btn.data( "id" ) ;
var params = unload_params() ;
unload_params( params, true ) ;
// set player-specific parameters
// NOTE: We used to delete the player-specific parameters (e.g. OB_SETUP_1/2)
// and just return a generic player-independent one (e.g. OB_SETUP), but now,
// we just leave them in place, in case a user-defined template wants them both.
if ( template_id === "ob_setup_1" ) {
template_id = "ob_setup" ;
params.OB_SETUP = params.OB_SETUP_1 ;
delete params.OB_SETUP_1 ;
params.OB_SETUP_WIDTH = params.OB_SETUP_WIDTH_1 ;
delete params.OB_SETUP_WIDTH_1 ;
}
else if ( template_id === "ob_setup_2" ) {
} else if ( template_id === "ob_setup_2" ) {
template_id = "ob_setup" ;
params.OB_SETUP = params.OB_SETUP_2 ;
delete params.OB_SETUP_2 ;
params.OB_SETUP_WIDTH = params.OB_SETUP_WIDTH_2 ;
delete params.OB_SETUP_WIDTH_2 ;
}
var nationalities = gTemplatePack.nationalities ;
var curr_tab = $("#tabs .ui-tabs-active a").attr( "href" ) ;
if ( curr_tab === "#tabs-ob1" ) {
params.PLAYER_NAME = nationalities[params.PLAYER_1].display_name ;
params.OB_COLOR = nationalities[params.PLAYER_1].ob_colors[0] ;
params.OB_COLOR_2 = nationalities[params.PLAYER_1].ob_colors[1] ;
} if ( curr_tab === "#tabs-ob2" ) {
} else if ( curr_tab === "#tabs-ob2" ) {
params.PLAYER_NAME = nationalities[params.PLAYER_2].display_name ;
params.OB_COLOR = nationalities[params.PLAYER_2].ob_colors[0] ;
params.OB_COLOR_2 = nationalities[params.PLAYER_2].ob_colors[1] ;
}
// set player-specific parameters
if ( template_id == "vehicles_1" ) {
template_id = "vehicles" ;
params.VEHICLES = params.VEHICLES_1 ;
params.VEHICLES_WIDTH = params.VEHICLES_WIDTH_1 ;
} else if ( template_id == "vehicles_2" ) {
template_id = "vehicles" ;
params.VEHICLES = params.VEHICLES_2 ;
params.VEHICLES_WIDTH = params.VEHICLES_WIDTH_2 ;
}
if ( template_id == "ordnance_1" ) {
template_id = "ordnance" ;
params.ORDNANCE = params.ORDNANCE_1 ;
params.ORDNANCE_WIDTH = params.ORDNANCE_WIDTH_1 ;
} else if ( template_id == "ordnance_2" ) {
template_id = "ordnance" ;
params.ORDNANCE = params.ORDNANCE_2 ;
params.ORDNANCE_WIDTH = params.ORDNANCE_WIDTH_2 ;
}
// include the player display names
params.PLAYER_1_NAME = nationalities[params.PLAYER_1].display_name ;
params.PLAYER_2_NAME = nationalities[params.PLAYER_2].display_name ;
// extract the scenario date components
var scenario_date = $("input[name='SCENARIO_DATE']").datepicker( "getDate" ) ;
var postfix ;
if ( scenario_date ) {
params.SCENARIO_DAY_OF_MONTH = scenario_date.getDate() ;
if ( params.SCENARIO_DAY_OF_MONTH in _DAY_OF_MONTH_POSTFIXES )
postfix = _DAY_OF_MONTH_POSTFIXES[ params.SCENARIO_DAY_OF_MONTH ] ;
else
postfix = _DAY_OF_MONTH_POSTFIXES[ params.SCENARIO_DAY_OF_MONTH % 10 ] ;
params.SCENARIO_DAY_OF_MONTH_POSTFIX = params.SCENARIO_DAY_OF_MONTH + postfix ;
params.SCENARIO_MONTH = 1 + scenario_date.getMonth() ;
params.SCENARIO_MONTH_NAME = _MONTH_NAMES[scenario_date.getMonth()] ;
params.SCENARIO_YEAR = scenario_date.getFullYear() ;
}
// generate PF parameters
if ( params.SCENARIO_YEAR < 1944 || (params.SCENARIO_YEAR == 1944 && params.SCENARIO_MONTH < 6) )
params.PF_RANGE = 1 ;
@ -115,13 +136,8 @@ function generate_snippet( $btn )
if ( ! (param_id in params && params[param_id].length > 0) )
missing_params.push( _MANDATORY_PARAMS[template_id][param_id] ) ;
}
if ( missing_params.length > 0 ) {
var buf = [ "Missing parameters:<ul>" ] ;
for ( var i=0 ; i < missing_params.length ; ++i )
buf.push( "<li> <span class='pre'>" + escapeHTML(missing_params[i]) + "</span>" ) ;
buf.push( "</ul>" ) ;
showWarningMsg( buf.join("") ) ;
}
if ( missing_params.length > 0 )
showWarningMsg( makeBulletListMsg( "Missing parameters:", missing_params, li_class="pre" ) ) ;
}
// check for date-specific parameters
@ -163,7 +179,12 @@ function generate_snippet( $btn )
// template parameter would have to be piped through the "safe" filter :-/ We never render
// any of the generated HTML, so any risk exists only when the user pastes the HTML snippet
// into a VASL scenario, which uses an ancient HTML engine (with probably no Javascript)...
val = func( params, {"autoEscape":false} ) ;
val = func( params, {
autoEscape: false,
filters: {
join: function(val,sep) { return val.join(sep) ; }
} ,
} ) ;
val = val.trim() ;
}
catch( ex ) {
@ -182,10 +203,9 @@ function generate_snippet( $btn )
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function unload_params()
function unload_params( params, check_date_capabilities )
{
// collect all the template parameters
var params = {} ;
add_param = function($elem) { params[ $elem.attr("name") ] = $elem.val() ; } ;
$("input[type='text'].param").each( function() { add_param($(this)) ; } ) ;
$("textarea.param").each( function() { add_param($(this)) ; } ) ;
@ -197,11 +217,160 @@ function unload_params()
params.SSR.push( $(this).text() ) ;
} ) ;
// collect the vehicles/ordnance
function get_vo( vo_type, player_id, paramName )
{
var objs = [] ;
$("#"+vo_type+"-sortable_"+player_id+" li").each( function() {
var entry = find_vo( $(this).data("vo-key") ) ;
var obj = {
name: entry.name,
note_number: entry.note_number,
notes: entry.notes
} ;
if ( entry.no_radio )
obj.no_radio = entry.no_radio ;
var capabilities = make_capabilities( entry, params.SCENARIO_YEAR, params.SCENARIO_MONTH, check_date_capabilities ) ;
if ( capabilities )
obj.capabilities = capabilities ;
var crew_survival = make_crew_survival( entry ) ;
if ( crew_survival )
obj.crew_survival = crew_survival ;
objs.push( obj ) ;
} ) ;
if ( objs.length > 0 )
params[paramName] = objs ;
}
get_vo( "vehicle", 1, "VEHICLES_1" ) ;
get_vo( "vehicle", 2, "VEHICLES_2" ) ;
get_vo( "ordnance", 1, "ORDNANCE_1" ) ;
get_vo( "ordnance", 2, "ORDNANCE_2" ) ;
return params ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function make_capabilities( entry, scenario_year, scenario_month, check_date_capabilities )
{
var capabilities = [] ;
// extract the static capabilities
var i ;
if ( "capabilities" in entry ) {
for ( i=0 ; i < entry.capabilities.length ; ++i )
capabilities.push( entry.capabilities[i] ) ;
}
// extract the variable capabilities
if ( "capabilities2" in entry ) {
var indeterminate_caps=[], unexpected_caps=[], invalid_caps=[] ;
for ( var key in entry.capabilities2 ) {
if ( entry.capabilities2[key] instanceof Array ) {
if ( key == "LF" )
capabilities.push( "LF [" + entry.capabilities2[key].join(", ") + "]" ) ;
else {
if ( ! scenario_year )
indeterminate_caps.push( key ) ;
if ( $.inArray( key, ["HE","A","D","sD","sN","WP"] ) === -1 ) {
unexpected_caps.push( key ) ;
continue ;
}
var cap = select_capability_by_date( entry.capabilities2[key], scenario_year, scenario_month ) ;
if ( cap == "<invalid>" ) {
invalid_caps.push( entry.name + ": " + key + " " + entry.capabilities2[key] ) ;
continue ;
}
if ( cap !== null )
capabilities.push( key + cap ) ;
}
}
else {
capabilities.push( key + entry.capabilities2[key] ) ;
}
}
// check if there were any capabilities not set
if ( check_date_capabilities && indeterminate_caps.length > 0 ) {
showErrorMsg( makeBulletListMsg(
"Can't determine capabilities without a scenario year:",
indeterminate_caps
) ) ;
}
// check if there were any unexpected capabilities
if ( unexpected_caps.length > 0 ) {
showErrorMsg( makeBulletListMsg(
"Internal error: unexpected date-based capabilities:",
unexpected_caps
) ) ;
}
// check if there were any invalid capabilities
if ( invalid_caps.length > 0 ) {
showErrorMsg( makeBulletListMsg(
"Internal error: invalid date-based capabilities:",
invalid_caps
) ) ;
}
}
// extract any other capabilities
if ( "capabilities_other" in entry ) {
for ( i=0 ; i < entry.capabilities_other.length ; ++i )
capabilities.push( entry.capabilities_other[i] ) ;
}
return capabilities.length > 0 ? capabilities : null ;
}
function select_capability_by_date( capabilities, scenario_year, scenario_month )
{
var MONTH_NAMES = { F: 2, J: 6, } ;
var val = null ;
for ( var i=0 ; i < capabilities.length ; ++i ) {
if ( capabilities[i] == "\u2020" )
continue ;
// remove any trailing "+" (why is it even there?)
var cap = capabilities[i][1].toString() ;
if ( cap.substring( cap.length-1 ) == "+" )
cap = cap.substring( 0, cap.length-1 ) ;
// parse the month/year the capability becomes available
var month = MONTH_NAMES[ cap.substring(0,1) ] ;
if ( month )
cap = cap.substring( 1 ) ;
if ( ! /^\d$/.test( cap ) )
return "<invalid>" ;
cap = parseInt( cap ) ;
// check if the capabilitity is available
if ( scenario_year > 1940 + cap )
val = capabilities[i][0] ;
else if ( scenario_year == 1940 + cap ) {
if( !month || scenario_month >= month )
val = capabilities[i][0] ;
}
}
return val ;
}
function make_crew_survival( entry )
{
// check if the vehicle has a crew survival field
var crew_survival = null ;
if ( "CS#" in entry )
crew_survival = "CS " + entry["CS#"] ;
else if ( "cs#" in entry )
crew_survival = "cs " + entry["cs#"] ;
if ( crew_survival === null )
return null ;
// check if the vehicle is subject to brew up
if ( crew_survival.substring(crew_survival.length-7) == ":brewup" )
crew_survival = crew_survival.substring(0,crew_survival.length-7) + " <small><i>(brew up)</i></small>" ;
return crew_survival ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function get_template( template_id )
{
// get the specified template
@ -216,6 +385,10 @@ function get_template( template_id )
function edit_template( template_id )
{
// get the specified template
if ( template_id.substring(0,9) == "ordnance_" )
template_id = "ordnance" ;
else if ( template_id.substring(0,9) == "vehicles_" )
template_id = "vehicles" ;
var template = get_template( template_id ) ;
if ( template === null )
return ;
@ -284,13 +457,22 @@ function on_load_scenario_file_selected()
function do_load_scenario( params )
{
// load the scenario parameters
// reset the scenario
on_new_scenario( false ) ;
// load the scenario parameters
var params_loaded = {} ;
var unknown_vo = [] ;
var set_param = function( $elem, key ) { $elem.val(params[key]) ; params_loaded[key]=true ; return $elem ; } ;
// FUDGE! We must set the players first, since changing these will reset the OB tabs.
if ( "PLAYER_1" in params )
set_param( $("select[name='PLAYER_1']"), "PLAYER_1" ).trigger( "change" ) ;
if ( "PLAYER_2" in params )
set_param( $("select[name='PLAYER_2']"), "PLAYER_2" ).trigger( "change" ) ;
var i ;
for ( var key in params ) {
if ( key === "SSR" ) {
for ( var i=0 ; i < params[key].length ; ++i ) {
for ( i=0 ; i < params[key].length ; ++i ) {
var $ssr = $( "<li></li>" ) ;
$ssr.text( params[key][i] ) ;
$("#ssr-sortable").append( $ssr ) ;
@ -300,6 +482,18 @@ function do_load_scenario( params )
params_loaded[key] = true ;
continue ;
}
if ( key === "VEHICLES_1" || key === "ORDNANCE_1" || key === "VEHICLES_2" || key === "ORDNANCE_2" ) {
var player_id = key.substring( key.length-1 ) ;
var nat = params[ "PLAYER_" + player_id ] ;
var vo_type = key.substring(0,9) === "VEHICLES_" ? "vehicle" : "ordnance" ;
for ( i=0 ; i < params[key].length ; ++i ) {
if ( ! do_add_vo( nat, vo_type, player_id, params[key][i] ) )
unknown_vo.push( params[key][i] ) ;
}
update_vo_hint( vo_type, player_id ) ;
params_loaded[key] = true ;
continue ;
}
//jshint loopfunc: true
var $elem = $("input[type='text'][name='"+key+"'].param").each( function() {
set_param( $(this), key ) ;
@ -308,7 +502,8 @@ function do_load_scenario( params )
set_param( $(this), key ) ;
} ) ;
$elem = $("select[name='"+key+"'].param").each( function() {
set_param( $(this), key ).trigger( "change" ) ;
if ( key !== "PLAYER_1" && key !== "PLAYER_2" )
set_param( $(this), key ).trigger( "change" ) ;
} ) ;
}
@ -316,10 +511,21 @@ function do_load_scenario( params )
var buf = [] ;
for ( key in params ) {
if ( ! (key in params_loaded) )
buf.push( "<li> <span class='pre'>" + escapeHTML(key) + " = <span class='pre'>'" + escapeHTML(params[key]) + "</span>'" ) ;
buf.push( key + " = " + params[key] ) ;
}
if ( buf.length > 0 ) {
showWarningMsg( makeBulletListMsg(
"Unknown keys in the scenario file:",
buf, li_class="pre"
) ) ;
}
// report any unknown vehicles/ordnance
if ( unknown_vo.length > 0 ) {
showWarningMsg( makeBulletListMsg(
"Unknown vehicles/ordnance:", unknown_vo
) ) ;
}
if ( buf.length > 0 )
showWarningMsg( "Unknown keys in the scenario file:<ul>" + buf.join("") + "</ul>" ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -327,7 +533,20 @@ function do_load_scenario( params )
function on_save_scenario()
{
// unload the template parameters
var params = unload_params() ;
function extract_vo_names( key ) { // nb: we only need to save the vehicle/ordnance name
if ( !(key in params) )
return ;
var names = [] ;
for ( var i=0 ; i < params[key].length ; ++i )
names.push( params[key][i].name ) ;
params[key] = names ;
}
var params = {};
unload_params( params, false ) ;
extract_vo_names( "VEHICLES_1" ) ;
extract_vo_names( "ORDNANCE_1" ) ;
extract_vo_names( "VEHICLES_2" ) ;
extract_vo_names( "ORDNANCE_2" ) ;
var data = JSON.stringify( params ) ;
// FOR TESTING PORPOISES! We can't control a file download from Selenium (since
@ -367,10 +586,16 @@ function on_new_scenario( verbose )
$("select[name='PLAYER_2_SAN']").val( 2 ) ;
// reset all the template parameters
$("#ssr-sortable li").each( function() {
$(this).remove() ;
} ) ;
$("#ssr-sortable li").each( function() { $(this).remove() ; } ) ;
update_ssr_hint() ;
// reset all the template parameters
delete_all_vo( "vehicle", 1 ) ;
delete_all_vo( "ordnance", 1 ) ;
delete_all_vo( "vehicle", 2 ) ;
delete_all_vo( "ordnance", 2 ) ;
// provide some feedback to the user
if ( verbose )
showInfoMsg( "The scenario was reset." ) ;
}
@ -483,10 +708,11 @@ function do_load_template_pack( fname, data )
for ( i=0 ; i < unknown_template_ids.length ; ++i )
buf.push( escapeHTML(unknown_template_ids[i]) + "<br>" ) ;
buf.push( "</div>" ) ;
buf.push( "Must be one of:<div class='pre'><ul>" ) ;
var buf2 = [] ;
for ( i=0 ; i < gValidTemplateIds.length ; ++i )
buf.push( "<li>" + escapeHTML(gValidTemplateIds[i]) + ".j2" ) ;
buf.push( "</ul></div>" ) ;
buf2.push( gValidTemplateIds[i] + ".j2" ) ;
buf.push( makeBulletListMsg( "Must be one of:<div class='pre'>", buf2 ) ) ;
buf.push( "</div>" ) ;
showErrorMsg( buf.join("") ) ;
ok = false ;
}

@ -33,6 +33,50 @@ function copyToClipboard( val )
// --------------------------------------------------------------------
// Connect a text box to a select box, and filter the available options.
jQuery.fn.filterByText = function( $textbox ) {
function compressSpaces( val ) { return val.replace( /\s/g, "" ).trim() ; }
return this.each( function() {
// initialize
var $select = $(this) ;
var $options = [] ;
$select.find( "option" ).each( function() {
$options.push( { value: $(this).val(), text: $(this).text() } ) ;
} ) ;
$select.data( "options", $options ) ;
$textbox.bind( "input", function() {
// prepare the value we will filter on
var val = $(this).val() ;
var adjustCase ;
if ( val !== val.toLowerCase() )
adjustCase = function(val) { return val ; } ; // nb: mixed-case => case-sensitive filtering
else
adjustCase = function(val) { return val.toLowerCase() ; } ;
val = compressSpaces( adjustCase( val ) ) ;
// filter the options
var $options = $select.empty().scrollTop(0).data( "options" ) ;
$.each( $options, function(i) {
var $opt = $options[i] ;
var optVal = compressSpaces( adjustCase( $opt.text ) ) ;
if ( optVal.indexOf( val ) !== -1 ) {
$select.append(
$("<option>").text( $opt.text ).val( $opt.value )
) ;
}
} ) ;
// auto-select if there's only one option
if ( $select.children().length === 1 )
$select.children().get(0).selected = true ;
} ) ;
} ) ;
} ;
// --------------------------------------------------------------------
function ask( title, msg, args )
{
// ask a question
@ -122,6 +166,23 @@ function storeMsgForTestSuite( id, msg )
$elem.html( msg ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function makeBulletListMsg( caption, items, li_class )
{
// generate a message
var buf = [] ;
for ( i=0 ; i < items.length ; ++i ) {
buf.push( "<li" ) ;
if ( li_class )
buf.push( " class='" + li_class + "'" ) ;
buf.push( ">" ) ;
buf.push( escapeHTML(items[i]) ) ;
buf.push( "</li>" ) ;
}
return caption + "<ul>" + buf.join("") + "</ul>" ;
}
// --------------------------------------------------------------------
function getUrlParam( param )

@ -0,0 +1,149 @@
function add_vo( vo_type, player_id )
{
// get the vehicles/ordnance already added
var vo_present = [];
$("#"+vo_type+"-sortable_"+player_id+" li").each( function() {
vo_present.push( $(this).text() ) ;
} );
// load the available vehicles/ordnance
var nat = $( "select[name='PLAYER_" + player_id + "']" ).val() ;
var entries = gVehicleOrdnanceListings[vo_type][nat] ;
if ( entries === undefined ) {
showErrorMsg( "There are no " + gTemplatePack.nationalities[nat].display_name + " " + vo_type + " listings." ) ;
return ;
}
var buf = [] ;
for ( var i=0 ; i < entries.length ; ++i ) {
if ( vo_present.indexOf( entries[i].name ) !== -1 )
continue ;
buf.push( "<option value='" + i + "'>" + escapeHTML(entries[i].name) + "</option>" ) ;
}
var $listbox = $( "#select-vo select" ) ;
$listbox.html( buf.join("") ) ;
$listbox.prop( "selectedIndex", 0 ).animate({ scrollTop: 0 }) ;
// let the user select a vehicle/ordnance
$("#select-vo").dialog( {
title: "Add " + vo_type,
dialogClass: "select-vo",
modal: true,
minWidth: 200,
minHeight: 300,
open: function() {
$("#select-vo input[type='text']").val( "" ) ;
$(this).height( $(this).height() ) ; // fudge: force the select to resize
$("#select-vo select").filterByText( $("#select-vo input[type='text']") ) ;
},
buttons: {
OK: function() {
// add the new vehicle/ordnance
var val = $listbox.val() ;
do_add_vo( nat, vo_type, player_id, entries[val].name ) ;
update_vo_hint( vo_type, player_id ) ;
$(this).dialog( "close" ) ;
},
Cancel: function() { $(this).dialog( "close" ) ; },
},
} ) ;
}
function do_add_vo( nat, vo_type, player_id, vo_name )
{
// find the specified vehicle/ordnance
var vo_key = make_vo_key( nat, vo_type, vo_name ) ;
if ( ! find_vo( vo_key ) )
return false ;
// add a new vehicle/ordnance entry
var $sortable = $( "#" + vo_type + "-sortable_" + player_id ) ;
var $elem = $( "<li></li>" ) ;
$elem.text( vo_name ) ;
$elem.data( "vo-key", vo_key ) ;
$sortable.append( $elem ) ;
init_vo( vo_type, player_id, $elem ) ;
return true ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function init_vo( vo_type, player_id, $elem )
{
// initialize vehicle/ordnance element(s)
$elem.click( function( evt ) {
if ( evt.ctrlKey )
delete_vo( vo_type, player_id, $(this) ) ;
} ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function update_vo_hint( vo_type, player_id )
{
// show/hide the vehicle/ordnance hint
if ( $("#"+vo_type+"-sortable_"+player_id+" li").length === 0 ) {
$("#"+vo_type+"-sortable_"+player_id).hide() ;
$("#"+vo_type+"-hint_"+player_id).show() ;
} else {
$("#"+vo_type+"-sortable_"+player_id).show() ;
$("#"+vo_type+"-hint_"+player_id).hide() ;
}
}
// --------------------------------------------------------------------
function delete_vo( vo_type, player_id, $elem )
{
// delete the vehicle/ordnance
$elem.addClass( "highlighted" ) ;
ask( "Delete this "+vo_type+"?", escapeHTML($elem.text()), {
"ok": function() {
$elem.remove() ;
update_vo_hint( vo_type, player_id ) ;
},
"close": function() { $elem.removeClass("highlighted") ; },
} ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function delete_all_vo( vo_type, player_id )
{
// delete all vehicles/ordnance
$("#"+vo_type+"-sortable_"+player_id+" li").each( function() {
$(this).remove() ;
} ) ;
update_vo_hint( vo_type, player_id ) ;
}
// --------------------------------------------------------------------
var gVehicleOrdnanceIndex = null ;
function find_vo( vo_key )
{
// check if we need to build the index
function build_vo_index( vo_type ) {
for ( var nat in gVehicleOrdnanceListings[vo_type] ) {
for ( var i=0 ; i < gVehicleOrdnanceListings[vo_type][nat].length ; ++i ) {
var entry = gVehicleOrdnanceListings[vo_type][nat][i] ;
gVehicleOrdnanceIndex[ make_vo_key(nat,vo_type,entry.name) ] = entry ;
}
}
}
if ( gVehicleOrdnanceIndex === null ) {
// yup - make it so
gVehicleOrdnanceIndex = {} ;
build_vo_index( "vehicle" ) ;
build_vo_index( "ordnance" ) ;
}
// find a vehicle/ordnance entry
return gVehicleOrdnanceIndex[ vo_key ] ;
}
function make_vo_key( nat, vo_type, name ) {
// generate a key use to identify each vehicle/ordnance
return nat + ":" + vo_type + ":" + name ;
}

@ -71,7 +71,7 @@
<div id="panel-ssr">
<div class="content">
<div id="ssr-hint"> <p>Click on the "+" below to add a new SSR, double-click on an SSR to edit it. <p>To re-order the SSR's, use the mouse to drag them around. <p>Ctrl-click on an SSR to delete it, or drag it into the trashcan below. </div>
<ul id="ssr-sortable" style="display:none;"></ul>
<ul id="ssr-sortable" class="sortable" style="display:none;"></ul>
</div>
<div class="footer">
<div class="l">
@ -107,11 +107,37 @@
</div>
</fieldset>
<fieldset class="r"> <legend>Vehicles</legend>
<div id="panel-obvehicles1">
<div id="panel-vehicles_1" class="panel-vehicles">
<div class="content">
<div id="vehicle-hint_1" class="vehicle-hint"> <p>Click on the "+" below to add a new Vehicle. <p>To re-order the Vehicles, use the mouse to drag them around. <p>Ctrl-click on an Vehicle to delete it, or drag it into the trashcan below. </div>
<ul id="vehicle-sortable_1" class="sortable" style="display:none;"></ul>
</div>
<div class="footer">
<div class="l">
<input type="button" id="add-vehicle_1" value="+">
<img id="vehicle-trash_1" class="vehicle-trash" src="{{url_for('static',filename='images/trash.png')}}">
</div>
<label for="VEHICLES_WIDTH_1">Width:</label>
<input type="text" class="param" name="VEHICLES_WIDTH_1" size="5">
<input type="button" class="generate" data-id="vehicles_1" value="Snippet">
</div>
</div>
</fieldset>
<fieldset class="bl"> <legend>Ordnance</legend>
<div id="panel-obordnance1">
<div id="panel-ordnance_1" class="panel-ordnance">
<div class="content">
<div id="ordnance-hint_1" class="ordnance-hint"> <p>Click on the "+" below to add a new Gun. <p>To re-order the Gun's, use the mouse to drag them around. <p>Ctrl-click on an Gun to delete it, or drag it into the trashcan below. </div>
<ul id="ordnance-sortable_1" class="sortable" style="display:none;"></ul>
</div>
<div class="footer">
<div class="l">
<input type="button" id="add-ordnance_1" value="+">
<img id="ordnance-trash_1" class="ordnance-trash" src="{{url_for('static',filename='images/trash.png')}}">
</div>
<label for="ORDNANCE_WIDTH_1">Width:</label>
<input type="text" class="param" name="ORDNANCE_WIDTH_1" size="5">
<input type="button" class="generate" data-id="ordnance_1" value="Snippet">
</div>
</div>
</fieldset>
</div>
@ -128,6 +154,10 @@
<div id="edit-template" style="display:none;"> <textarea></textarea> </div>
<div id="edit-ssr" style="display:none;"> <b>Enter the SSR content:</b> <textarea></textarea> </div>
<div id="select-vo" style="display:none;">
<div class="header"> <b>Filter by:</b> <input type="text" size=10> </div>
<select size=2></select> </div>
</div>
<div id="ask" style="display:none;"></div>
</div>
@ -144,10 +174,13 @@
<script>
gImagesBaseUrl = "{{url_for('static',filename='images')}}" ;
gGetTemplatePackUrl = "{{url_for('get_template_pack')}}" ;
gVehicleListingsUrl = "{{url_for('get_vehicle_listings')}}" ;
gOrdnanceListingsUrl = "{{url_for('get_ordnance_listings')}}" ;
</script>
<script src="{{url_for('static',filename='main.js')}}"></script>
<script src="{{url_for('static',filename='snippets.js')}}"></script>
<script src="{{url_for('static',filename='ssr.js')}}"></script>
<script src="{{url_for('static',filename='vehicles_ordnance.js')}}"></script>
<script src="{{url_for('static',filename='utils.js')}}"></script>
</html>

@ -0,0 +1,98 @@
<!doctype html> <!-- NOTE: For testing porpoises only! -->
<html lang="en">
<head>
<meta charset="utf-8">
<title> {{VO_TYPE}} listings: {{NATIONALITY}} </title>
<style>
table { border-collapse: collapse ; }
th { text-align: left ; padding: 0.2em 0.5em ; background: #eee ; border: 1px solid #ccc ; }
td { padding: 0.2em 0.5em ; }
.val { padding: 0.1em 0.25em ; background: #eee ; }
</style>
</head>
<body>
<div id="results" style="display:none;"></div>
</body>
<script src="{{url_for('static',filename='jquery/jquery-3.3.1.min.js')}}"></script>
<script src="{{url_for('static',filename='snippets.js')}}"></script>
<script src="{{url_for('static',filename='utils.js')}}"></script>
<script>
$(document).ready( function () {
// get the vehicle listings
var url ;
if ( "{{VO_TYPE}}" == "vehicle" )
url = "{{url_for( 'get_vehicle_listings', report=1 )}}"
else
url = "{{url_for( 'get_ordnance_listings', report=1 )}}" ;
$.getJSON( url, function(data) {
load_vo_listings( data ) ;
} ).fail( function( xhr, status, errorMsg ) {
alert( "Can't get the {{VO_TYPE}} listings:\n\n" + errorMsg ) ;
} ) ;
} ) ;
function load_vo_listings( objs )
{
// initialize
var nat = "{{NATIONALITY}}" ;
var year = {{YEAR}} ;
var $results = $("#results") ;
// check if there are any vehicles/ordnance for the specified nationality
if ( ! (nat in objs ) ) {
$results.text( "No listings for nationality: " ) ; + nat
return ;
}
// load the vehicles/ordnance
var buf = [] ;
buf.push( "<table>" ) ;
buf.push( "<tr>", "<th>Name" ) ;
if ( "{{VO_TYPE}}" === "vehicle" ) {
buf.push( "<th>Radio" ) ;
buf.push( "<th>Crew survival" ) ;
}
buf.push( "<th>Capabilities", "<th colspan='2'>Notes" ) ;
for ( var i=0 ; i < objs[nat].length ; ++i ) {
var obj = objs[nat][i] ;
buf.push( "<tr>" ) ;
buf.push( "<td>", fmtval(obj.name) ) ;
if ( "{{VO_TYPE}}" === "vehicle" ) {
buf.push( "<td>", fmtval(obj.no_radio) ) ;
buf.push( "<td>", fmtval(make_crew_survival(obj)) ) ;
}
var capabilities = make_capabilities( obj, year, true ) ;
buf.push( "<td>", listval(capabilities) ) ;
buf.push( "<td>", "<span class='val'>" + fmtval(obj.note_number) + "</span>" ) ;
buf.push( "<td>", listval(obj.notes) ) ;
}
buf.push( "</table>" ) ;
$results.html( buf.join("") ).show() ;
}
function fmtval( val )
{
// return the formatted value
if ( ! val )
return "<small><em>n/a</em></small>" ;
return val ;
}
function listval( vals )
{
// return the formatted list of values
if ( ! vals )
return "<small><em>n/a</em></small>" ;
var buf = [] ;
for ( var i=0 ; i < vals.length ; ++i )
buf.push( "<span class='val'>" + vals[i] + "</span>" ) ;
return buf.join( " " ) ;
}
</script>
</html>

@ -1 +1 @@
[{{OB_SETUP}}] (col=[{{OB_COLOR}}/{{OB_COLOR_2}}]){%if OB_SETUP_WIDTH%} (width=[{{OB_SETUP_WIDTH}}]){%endif%}
[{{PLAYER_NAME}}] [{{OB_SETUP}}] (col=[{{OB_COLOR}}/{{OB_COLOR_2}}]){%if OB_SETUP_WIDTH%} (width=[{{OB_SETUP_WIDTH}}]){%endif%}

@ -0,0 +1,4 @@
[{{PLAYER_NAME}}] ; width={{ORDNANCE_WIDTH}}{%for ord in ORDNANCE %}
[*] {{ord.name}}: #={{ord.note_number}}{%if ord.notes%}
- notes:{%for n in ord.notes%} "{{n}}"{%endfor%}{%endif%}{%if ord.capabilities%}
- capabilities:{%for c in ord.capabilities%} "{{c}}"{%endfor%}{%endif%}{%endfor%}

@ -0,0 +1,5 @@
[{{PLAYER_NAME}}] ; width={{VEHICLES_WIDTH}}{%for veh in VEHICLES %}
[*] {{veh.name}}: #={{veh.note_number}}{%if veh.notes%}
- notes:{%for n in veh.notes%} "{{n}}"{%endfor%}{%endif%}{%if veh.crew_survival%}
- {{veh.crew_survival}}{%endif%}{%if veh.capabilities%}
- capabilities:{%for c in veh.capabilities%} "{{c}}"{%endfor%}{%endif%}{%endfor%}

@ -0,0 +1,17 @@
[
{ "name": "a german ordnance",
"capabilities": [ "QSU", "IR" ],
"capabilities2": { "A": 1, "H": 2 },
"capabilities_other": [ "can do other stuff" ],
"note_number": "1",
"notes": [ "A", "B\u2020" ]
},
{ "name": "another german ordnance",
"capabilities": [ "QSU" ],
"note_number": "2"
},
{ "name": "name only"
}
]

@ -0,0 +1,17 @@
[
{ "name": "a russian ordnance",
"capabilities": [ "NM" ],
"capabilities2": { "s": 1, "sN": 2 },
"capabilities_other": [ "twerking" ],
"note_number": "1",
"notes": [ "X", "Y\u2020" ]
},
{ "name": "another russian ordnance",
"capabilities": [ "RFNM" ],
"note_number": "2"
},
{ "name": "name only"
}
]

@ -0,0 +1,19 @@
[
{ "name": "a german vehicle",
"capabilities": [ "QSU", "IR" ],
"capabilities2": { "A": 1, "H": 2 },
"capabilities_other": [ "can do other stuff" ],
"CS#": 5,
"note_number": "1",
"notes": [ "A", "B\u2020" ]
},
{ "name": "another german vehicle",
"capabilities": [ "QSU" ],
"cs#": "4:brewup",
"note_number": "2"
},
{ "name": "name only"
}
]

@ -0,0 +1,26 @@
[
{ "name": "a russian vehicle",
"capabilities": [ "NM" ],
"capabilities2": { "s": 1, "sN": 2 },
"capabilities_other": [ "twerking" ],
"CS#": 3,
"note_number": "1",
"notes": [ "X", "Y\u2020" ]
},
{ "name": "another russian vehicle",
"capabilities": [ "RFNM" ],
"cs#": 4,
"note_number": "2"
},
{ "name": "name only"
},
{ "name": "Churchill III(b)",
"CS#": 7,
"capabilities2": { "D": [ [6,"J4"], [7,5], "\u2020" ], "HE": [ [7,"F3"], [8,"4+"], "\u2020" ], "sD": [ [6,"4+"] ], "sM": "8\u2020" },
"note_number": "53\u2020",
"notes": [ "N", "LL" ]
}
]

@ -0,0 +1,34 @@
=== ordnance/german/1940 ===
Name Capabilities # Notes
------------------- ------------------------------------- --- -------
5cm leGrW 5PP 1 N
8cm GrW 34 NT QSU IR s8 2† N
10cm NbW 35 NT QSU s10 3 O
12cm GrW 42 NT QSU s8 4† n/a
2.8cm sPzB 41 NT QSU uses APCR TK Table† 5† A† N P
3.7cm PaK 35/36 NT QSU H6[9]† 6 B† N P
4.2cm lePaK 41 NT QSU uses APCR TK Table† 7 A† N
5cm PaK 38 NT QSU 8† N
7.5cm PaK 97/38 NT QSU H6 s7 9 N
7.5cm PaK 40 NT QSU s7 10† N
7.62cm PaK 36r NT QSU s6 11† N
8.8cm PaK 43 T LF [88†, 1 ROF, B11, <b>M4</b>, -1] 12 C†
8.8cm PaK 43/41 NT 13 n/a
12.8cm K 81/1 NT NM 14 n/a
7.5cm leIG 18 NT QSU H7 15 N O
15cm sIG 33 NT H7 s8 16 N
7.5cm LG 40 T QSU H6 17 N
10.5cm LG 42 T QSU H6 s6 18† N
7.5cm leFK 16nA NT QSU h-d H5 s8 19 n/a
10.5cm leFH 18 NT H6 s8 20 N
s 10cm K 18 NT NM H6 s8 21 N
15cm sFH 18 NT NM H6 s8 22 N
15cm K 18 NT RFNM AP6 23 N
17cm K 18 NT RFNM no IF AP6 24 N
2cm FlaK 30 T LF [20†, 2 ROF, B10, NT] 25 C† N
2cm FlaK 38 T LF [20†, 2 ROF, B11, NT] 26 C† N
2cm FlaKvierling 38 T 4TK DR† 27† N
3.7cm FlaK 36 o. 37 T H5[9]† 28 B† N
3.7cm FlaK 43 T 29 n/a
8.8cm FlaK 18 o. 36 T LF [88L†, 1 ROF, B11] 30 C† N

@ -0,0 +1,34 @@
=== ordnance/german/1941 ===
Name Capabilities # Notes
------------------- ------------------------------------- --- -------
5cm leGrW 5PP 1 N
8cm GrW 34 NT QSU IR s8 2† N
10cm NbW 35 NT QSU s10 3 O
12cm GrW 42 NT QSU s8 4† n/a
2.8cm sPzB 41 NT QSU uses APCR TK Table† 5† A† N P
3.7cm PaK 35/36 NT QSU A4 H6[9]† 6 B† N P
4.2cm lePaK 41 NT QSU uses APCR TK Table† 7 A† N
5cm PaK 38 NT QSU A5 8† N
7.5cm PaK 97/38 NT QSU H6 s7 9 N
7.5cm PaK 40 NT QSU s7 10† N
7.62cm PaK 36r NT QSU s6 11† N
8.8cm PaK 43 T LF [88†, 1 ROF, B11, <b>M4</b>, -1] 12 C†
8.8cm PaK 43/41 NT 13 n/a
12.8cm K 81/1 NT NM 14 n/a
7.5cm leIG 18 NT QSU H7 15 N O
15cm sIG 33 NT H7 s8 16 N
7.5cm LG 40 T QSU H6 17 N
10.5cm LG 42 T QSU H6 s6 18† N
7.5cm leFK 16nA NT QSU h-d H5 s8 19 n/a
10.5cm leFH 18 NT H6 s8 20 N
s 10cm K 18 NT NM H6 s8 21 N
15cm sFH 18 NT NM H6 s8 22 N
15cm K 18 NT RFNM AP6 23 N
17cm K 18 NT RFNM no IF AP6 24 N
2cm FlaK 30 T LF [20†, 2 ROF, B10, NT] 25 C† N
2cm FlaK 38 T LF [20†, 2 ROF, B11, NT] 26 C† N
2cm FlaKvierling 38 T 4TK DR† 27† N
3.7cm FlaK 36 o. 37 T H5[9]† 28 B† N
3.7cm FlaK 43 T 29 n/a
8.8cm FlaK 18 o. 36 T LF [88L†, 1 ROF, B11] 30 C† N

@ -0,0 +1,34 @@
=== ordnance/german/1942 ===
Name Capabilities # Notes
------------------- ------------------------------------- --- -------
5cm leGrW 5PP 1 N
8cm GrW 34 NT QSU IR s8 2† N
10cm NbW 35 NT QSU s10 3 O
12cm GrW 42 NT QSU s8 4† n/a
2.8cm sPzB 41 NT QSU uses APCR TK Table† 5† A† N P
3.7cm PaK 35/36 NT QSU A5 H6[9]† 6 B† N P
4.2cm lePaK 41 NT QSU uses APCR TK Table† 7 A† N
5cm PaK 38 NT QSU A6 8† N
7.5cm PaK 97/38 NT QSU H6 s7 9 N
7.5cm PaK 40 NT QSU A5 s7 10† N
7.62cm PaK 36r NT QSU A5 s6 11† N
8.8cm PaK 43 T LF [88†, 1 ROF, B11, <b>M4</b>, -1] 12 C†
8.8cm PaK 43/41 NT 13 n/a
12.8cm K 81/1 NT NM 14 n/a
7.5cm leIG 18 NT QSU H7 15 N O
15cm sIG 33 NT H7 s8 16 N
7.5cm LG 40 T QSU H6 17 N
10.5cm LG 42 T QSU H6 s6 18† N
7.5cm leFK 16nA NT QSU h-d H5 s8 19 n/a
10.5cm leFH 18 NT H6 s8 20 N
s 10cm K 18 NT NM H6 s8 21 N
15cm sFH 18 NT NM H6 s8 22 N
15cm K 18 NT RFNM AP6 23 N
17cm K 18 NT RFNM no IF AP6 24 N
2cm FlaK 30 T LF [20†, 2 ROF, B10, NT] 25 C† N
2cm FlaK 38 T LF [20†, 2 ROF, B11, NT] 26 C† N
2cm FlaKvierling 38 T 4TK DR† 27† N
3.7cm FlaK 36 o. 37 T H5[9]† 28 B† N
3.7cm FlaK 43 T 29 n/a
8.8cm FlaK 18 o. 36 T A5 LF [88L†, 1 ROF, B11] 30 C† N

@ -0,0 +1,34 @@
=== ordnance/german/1943 ===
Name Capabilities # Notes
------------------- ------------------------------------- --- -------
5cm leGrW 5PP 1 N
8cm GrW 34 NT QSU IR s8 2† N
10cm NbW 35 NT QSU s10 3 O
12cm GrW 42 NT QSU s8 4† n/a
2.8cm sPzB 41 NT QSU uses APCR TK Table† 5† A† N P
3.7cm PaK 35/36 NT QSU A4 H6[9]† 6 B† N P
4.2cm lePaK 41 NT QSU uses APCR TK Table† 7 A† N
5cm PaK 38 NT QSU A5 8† N
7.5cm PaK 97/38 NT QSU H6 s7 9 N
7.5cm PaK 40 NT QSU A4 s7 10† N
7.62cm PaK 36r NT QSU A4 s6 11† N
8.8cm PaK 43 T LF [88†, 1 ROF, B11, <b>M4</b>, -1] 12 C†
8.8cm PaK 43/41 NT 13 n/a
12.8cm K 81/1 NT NM 14 n/a
7.5cm leIG 18 NT QSU H7 15 N O
15cm sIG 33 NT H7 s8 16 N
7.5cm LG 40 T QSU H6 17 N
10.5cm LG 42 T QSU H6 s6 18† N
7.5cm leFK 16nA NT QSU h-d H5 s8 19 n/a
10.5cm leFH 18 NT H6 s8 20 N
s 10cm K 18 NT NM H6 s8 21 N
15cm sFH 18 NT NM H6 s8 22 N
15cm K 18 NT RFNM AP6 23 N
17cm K 18 NT RFNM no IF AP6 24 N
2cm FlaK 30 T LF [20†, 2 ROF, B10, NT] 25 C† N
2cm FlaK 38 T LF [20†, 2 ROF, B11, NT] 26 C† N
2cm FlaKvierling 38 T 4TK DR† 27† N
3.7cm FlaK 36 o. 37 T H5[9]† 28 B† N
3.7cm FlaK 43 T 29 n/a
8.8cm FlaK 18 o. 36 T A4 LF [88L†, 1 ROF, B11] 30 C† N

@ -0,0 +1,34 @@
=== ordnance/german/1944 ===
Name Capabilities # Notes
------------------- ------------------------------------- --- -------
5cm leGrW 5PP 1 N
8cm GrW 34 NT QSU IR s8 2† N
10cm NbW 35 NT QSU s10 3 O
12cm GrW 42 NT QSU s8 4† n/a
2.8cm sPzB 41 NT QSU uses APCR TK Table† 5† A† N P
3.7cm PaK 35/36 NT QSU A3 H6[9]† 6 B† N P
4.2cm lePaK 41 NT QSU uses APCR TK Table† 7 A† N
5cm PaK 38 NT QSU A4 8† N
7.5cm PaK 97/38 NT QSU H6 s7 9 N
7.5cm PaK 40 NT QSU A3 s7 10† N
7.62cm PaK 36r NT QSU A3 s6 11† N
8.8cm PaK 43 T LF [88†, 1 ROF, B11, <b>M4</b>, -1] 12 C†
8.8cm PaK 43/41 NT 13 n/a
12.8cm K 81/1 NT NM 14 n/a
7.5cm leIG 18 NT QSU H7 15 N O
15cm sIG 33 NT H7 s8 16 N
7.5cm LG 40 T QSU H6 17 N
10.5cm LG 42 T QSU H6 s6 18† N
7.5cm leFK 16nA NT QSU h-d H5 s8 19 n/a
10.5cm leFH 18 NT H6 s8 20 N
s 10cm K 18 NT NM H6 s8 21 N
15cm sFH 18 NT NM H6 s8 22 N
15cm K 18 NT RFNM AP6 23 N
17cm K 18 NT RFNM no IF AP6 24 N
2cm FlaK 30 T LF [20†, 2 ROF, B10, NT] 25 C† N
2cm FlaK 38 T LF [20†, 2 ROF, B11, NT] 26 C† N
2cm FlaKvierling 38 T 4TK DR† 27† N
3.7cm FlaK 36 o. 37 T H5[9]† 28 B† N
3.7cm FlaK 43 T 29 n/a
8.8cm FlaK 18 o. 36 T A3 LF [88L†, 1 ROF, B11] 30 C† N

@ -0,0 +1,34 @@
=== ordnance/german/1945 ===
Name Capabilities # Notes
------------------- ------------------------------------- --- -------
5cm leGrW 5PP 1 N
8cm GrW 34 NT QSU IR s8 2† N
10cm NbW 35 NT QSU s10 3 O
12cm GrW 42 NT QSU s8 4† n/a
2.8cm sPzB 41 NT QSU uses APCR TK Table† 5† A† N P
3.7cm PaK 35/36 NT QSU A3 H6[9]† 6 B† N P
4.2cm lePaK 41 NT QSU uses APCR TK Table† 7 A† N
5cm PaK 38 NT QSU A4 8† N
7.5cm PaK 97/38 NT QSU H6 s7 9 N
7.5cm PaK 40 NT QSU A3 s7 10† N
7.62cm PaK 36r NT QSU A3 s6 11† N
8.8cm PaK 43 T LF [88†, 1 ROF, B11, <b>M4</b>, -1] 12 C†
8.8cm PaK 43/41 NT 13 n/a
12.8cm K 81/1 NT NM 14 n/a
7.5cm leIG 18 NT QSU H7 15 N O
15cm sIG 33 NT H7 s8 16 N
7.5cm LG 40 T QSU H6 17 N
10.5cm LG 42 T QSU H6 s6 18† N
7.5cm leFK 16nA NT QSU h-d H5 s8 19 n/a
10.5cm leFH 18 NT H6 s8 20 N
s 10cm K 18 NT NM H6 s8 21 N
15cm sFH 18 NT NM H6 s8 22 N
15cm K 18 NT RFNM AP6 23 N
17cm K 18 NT RFNM no IF AP6 24 N
2cm FlaK 30 T LF [20†, 2 ROF, B10, NT] 25 C† N
2cm FlaK 38 T LF [20†, 2 ROF, B11, NT] 26 C† N
2cm FlaKvierling 38 T 4TK DR† 27† N
3.7cm FlaK 36 o. 37 T H5[9]† 28 B† N
3.7cm FlaK 43 T 29 n/a
8.8cm FlaK 18 o. 36 T A3 LF [88L†, 1 ROF, B11] 30 C† N

@ -0,0 +1,33 @@
=== ordnance/russian/1940 ===
Name Capabilities # Notes
-------------------- ---------------------------- ---- -------
50mm RM obr. 40 4PP 1 n/a
37mm PM-39 1PP Air Bursts &amp; Acq. NA 1.1 A
82mm BM obr. 37 NT QSU IR s8 2† n/a
107mm GVPM obr. 38 NT QSU s8 3 A
120mm PM obr. 38 NT QSU s8 4 n/a
160mm PM obr. 43 NT no IF 5 n/a
37mm PTP obr. 30 NT QSU 6 n/a
45mm PTP obr. 32 NT QSU 7 n/a
45mm PTP obr. 42 NT QSU 8 n/a
57mm PTP obr. 43 NT QSU 9† n/a
100mm PTP obr. 44 NT 10 n/a
37mm PP obr. 15R NT QSU h-d 11 n/a
76.2mm PP obr. 27 NT QSU H6 s8 12 A
76.2mm P obr. 00/02P NT QSU h-d s8 13 n/a
76.2mm P obr. 02/30 NT QSU h-d s8 14 n/a
76.2mm P obr. 39 NT QSU s8 15† n/a
76.2mm P obr. 36 NT QSU s8 16† n/a
85mm P obr. 44 NT QSU s8 17 n/a
107mm P obr. 10/30 NT h-d s8 18 n/a
122mm G obr. 10/30 NT h-d s8 19 n/a
122mm G obr. 38 NT H6 s8 20 n/a
122mm P obr. 31 NT s8 21 n/a
152mm G obr. 38 NT 22 n/a
152mm GP obr. 37 NT 23 n/a
203mm B-4 obr. 1931 NT RFNM no IF 23.1 n/a
25mm ZP obr. 40 T 24 n/a
37mm ZP obr. 39 T 25† n/a
76.2mm ZP obr. 38 T 26† n/a
85mm ZP obr. 39 T 27 n/a

@ -0,0 +1,33 @@
=== ordnance/russian/1941 ===
Name Capabilities # Notes
-------------------- ---------------------------- ---- -------
50mm RM obr. 40 4PP 1 n/a
37mm PM-39 1PP Air Bursts &amp; Acq. NA 1.1 A
82mm BM obr. 37 NT QSU IR s8 2† n/a
107mm GVPM obr. 38 NT QSU s8 3 A
120mm PM obr. 38 NT QSU s8 4 n/a
160mm PM obr. 43 NT no IF 5 n/a
37mm PTP obr. 30 NT QSU 6 n/a
45mm PTP obr. 32 NT QSU 7 n/a
45mm PTP obr. 42 NT QSU 8 n/a
57mm PTP obr. 43 NT QSU 9† n/a
100mm PTP obr. 44 NT 10 n/a
37mm PP obr. 15R NT QSU h-d 11 n/a
76.2mm PP obr. 27 NT QSU H6 s8 12 A
76.2mm P obr. 00/02P NT QSU h-d s8 13 n/a
76.2mm P obr. 02/30 NT QSU h-d s8 14 n/a
76.2mm P obr. 39 NT QSU s8 15† n/a
76.2mm P obr. 36 NT QSU s8 16† n/a
85mm P obr. 44 NT QSU s8 17 n/a
107mm P obr. 10/30 NT h-d s8 18 n/a
122mm G obr. 10/30 NT h-d s8 19 n/a
122mm G obr. 38 NT H6 s8 20 n/a
122mm P obr. 31 NT s8 21 n/a
152mm G obr. 38 NT 22 n/a
152mm GP obr. 37 NT 23 n/a
203mm B-4 obr. 1931 NT RFNM no IF 23.1 n/a
25mm ZP obr. 40 T 24 n/a
37mm ZP obr. 39 T 25† n/a
76.2mm ZP obr. 38 T 26† n/a
85mm ZP obr. 39 T 27 n/a

@ -0,0 +1,33 @@
=== ordnance/russian/1942 ===
Name Capabilities # Notes
-------------------- ---------------------------- ---- -------
50mm RM obr. 40 4PP 1 n/a
37mm PM-39 1PP Air Bursts &amp; Acq. NA 1.1 A
82mm BM obr. 37 NT QSU IR s8 2† n/a
107mm GVPM obr. 38 NT QSU s8 3 A
120mm PM obr. 38 NT QSU s8 4 n/a
160mm PM obr. 43 NT no IF 5 n/a
37mm PTP obr. 30 NT QSU 6 n/a
45mm PTP obr. 32 NT QSU A4 7 n/a
45mm PTP obr. 42 NT QSU A4 8 n/a
57mm PTP obr. 43 NT QSU 9† n/a
100mm PTP obr. 44 NT 10 n/a
37mm PP obr. 15R NT QSU h-d 11 n/a
76.2mm PP obr. 27 NT QSU H6 s8 12 A
76.2mm P obr. 00/02P NT QSU h-d s8 13 n/a
76.2mm P obr. 02/30 NT QSU h-d s8 14 n/a
76.2mm P obr. 39 NT QSU A4 s8 15† n/a
76.2mm P obr. 36 NT QSU A4 s8 16† n/a
85mm P obr. 44 NT QSU s8 17 n/a
107mm P obr. 10/30 NT h-d s8 18 n/a
122mm G obr. 10/30 NT h-d s8 19 n/a
122mm G obr. 38 NT H6 s8 20 n/a
122mm P obr. 31 NT s8 21 n/a
152mm G obr. 38 NT 22 n/a
152mm GP obr. 37 NT 23 n/a
203mm B-4 obr. 1931 NT RFNM no IF 23.1 n/a
25mm ZP obr. 40 T 24 n/a
37mm ZP obr. 39 T 25† n/a
76.2mm ZP obr. 38 T 26† n/a
85mm ZP obr. 39 T 27 n/a

@ -0,0 +1,33 @@
=== ordnance/russian/1943 ===
Name Capabilities # Notes
-------------------- ---------------------------- ---- -------
50mm RM obr. 40 4PP 1 n/a
37mm PM-39 1PP Air Bursts &amp; Acq. NA 1.1 A
82mm BM obr. 37 NT QSU IR s8 2† n/a
107mm GVPM obr. 38 NT QSU s8 3 A
120mm PM obr. 38 NT QSU s8 4 n/a
160mm PM obr. 43 NT no IF 5 n/a
37mm PTP obr. 30 NT QSU 6 n/a
45mm PTP obr. 32 NT QSU A5 7 n/a
45mm PTP obr. 42 NT QSU A5 8 n/a
57mm PTP obr. 43 NT QSU A4 9† n/a
100mm PTP obr. 44 NT 10 n/a
37mm PP obr. 15R NT QSU h-d 11 n/a
76.2mm PP obr. 27 NT QSU H6 s8 12 A
76.2mm P obr. 00/02P NT QSU h-d s8 13 n/a
76.2mm P obr. 02/30 NT QSU h-d s8 14 n/a
76.2mm P obr. 39 NT QSU A5 s8 15† n/a
76.2mm P obr. 36 NT QSU A4 s8 16† n/a
85mm P obr. 44 NT QSU s8 17 n/a
107mm P obr. 10/30 NT h-d s8 18 n/a
122mm G obr. 10/30 NT h-d s8 19 n/a
122mm G obr. 38 NT H6 s8 20 n/a
122mm P obr. 31 NT s8 21 n/a
152mm G obr. 38 NT 22 n/a
152mm GP obr. 37 NT 23 n/a
203mm B-4 obr. 1931 NT RFNM no IF 23.1 n/a
25mm ZP obr. 40 T 24 n/a
37mm ZP obr. 39 T 25† n/a
76.2mm ZP obr. 38 T 26† n/a
85mm ZP obr. 39 T 27 n/a

@ -0,0 +1,33 @@
=== ordnance/russian/1944 ===
Name Capabilities # Notes
-------------------- ---------------------------- ---- -------
50mm RM obr. 40 4PP 1 n/a
37mm PM-39 1PP Air Bursts &amp; Acq. NA 1.1 A
82mm BM obr. 37 NT QSU IR s8 2† n/a
107mm GVPM obr. 38 NT QSU s8 3 A
120mm PM obr. 38 NT QSU s8 4 n/a
160mm PM obr. 43 NT no IF 5 n/a
37mm PTP obr. 30 NT QSU 6 n/a
45mm PTP obr. 32 NT QSU A6 7 n/a
45mm PTP obr. 42 NT QSU A6 8 n/a
57mm PTP obr. 43 NT QSU A5 9† n/a
100mm PTP obr. 44 NT 10 n/a
37mm PP obr. 15R NT QSU h-d 11 n/a
76.2mm PP obr. 27 NT QSU H6 s8 12 A
76.2mm P obr. 00/02P NT QSU h-d s8 13 n/a
76.2mm P obr. 02/30 NT QSU h-d s8 14 n/a
76.2mm P obr. 39 NT QSU A6 s8 15† n/a
76.2mm P obr. 36 NT QSU A4 s8 16† n/a
85mm P obr. 44 NT QSU A5 s8 17 n/a
107mm P obr. 10/30 NT h-d s8 18 n/a
122mm G obr. 10/30 NT h-d s8 19 n/a
122mm G obr. 38 NT H6 s8 20 n/a
122mm P obr. 31 NT s8 21 n/a
152mm G obr. 38 NT 22 n/a
152mm GP obr. 37 NT 23 n/a
203mm B-4 obr. 1931 NT RFNM no IF 23.1 n/a
25mm ZP obr. 40 T 24 n/a
37mm ZP obr. 39 T 25† n/a
76.2mm ZP obr. 38 T 26† n/a
85mm ZP obr. 39 T 27 n/a

@ -0,0 +1,33 @@
=== ordnance/russian/1945 ===
Name Capabilities # Notes
-------------------- ---------------------------- ---- -------
50mm RM obr. 40 4PP 1 n/a
37mm PM-39 1PP Air Bursts &amp; Acq. NA 1.1 A
82mm BM obr. 37 NT QSU IR s8 2† n/a
107mm GVPM obr. 38 NT QSU s8 3 A
120mm PM obr. 38 NT QSU s8 4 n/a
160mm PM obr. 43 NT no IF 5 n/a
37mm PTP obr. 30 NT QSU 6 n/a
45mm PTP obr. 32 NT QSU A7 7 n/a
45mm PTP obr. 42 NT QSU A7 8 n/a
57mm PTP obr. 43 NT QSU A6 9† n/a
100mm PTP obr. 44 NT 10 n/a
37mm PP obr. 15R NT QSU h-d 11 n/a
76.2mm PP obr. 27 NT QSU H6 s8 12 A
76.2mm P obr. 00/02P NT QSU h-d s8 13 n/a
76.2mm P obr. 02/30 NT QSU h-d s8 14 n/a
76.2mm P obr. 39 NT QSU A7 s8 15† n/a
76.2mm P obr. 36 NT QSU A4 s8 16† n/a
85mm P obr. 44 NT QSU A6 s8 17 n/a
107mm P obr. 10/30 NT h-d s8 18 n/a
122mm G obr. 10/30 NT h-d s8 19 n/a
122mm G obr. 38 NT H6 s8 20 n/a
122mm P obr. 31 NT s8 21 n/a
152mm G obr. 38 NT 22 n/a
152mm GP obr. 37 NT 23 n/a
203mm B-4 obr. 1931 NT RFNM no IF 23.1 n/a
25mm ZP obr. 40 T 24 n/a
37mm ZP obr. 39 T 25† n/a
76.2mm ZP obr. 38 T 26† n/a
85mm ZP obr. 39 T 27 n/a

@ -0,0 +1,124 @@
=== vehicles/german/1940 ===
Name Radio Crew survival Capabilities # Notes
---------------------- ------- ------------------------------------ -------------- ----- ---------------------------------
PzKpfw IB n/a CS 2 sD6 1† N
PzKpfw IIA n/a CS 3 sD6 2 n/a
PzKpfw IIF n/a CS 3 sD7 3 N
PzKpfw II(Fl) n/a CS 2 <small><i>(brew up)</i></small> sD7† 4† n/a
PzKpfw IIL n/a CS 4 sD7 5 n/a
PzKpfw 35t n/a CS 4 sD5 6† n/a
PzKpfw 38(t)A n/a CS 4 sD6 7† E
PzKpfw 38(t)E n/a CS 4 sD7 8† E
Aufklaerer 38(t) n/a CS 5 n/a 9 A† E
FT-17 730m(f) n/a CS 2 n/a 9.1† a c f
FT-17 730(f) n/a CS 2 AP9 9.1† a c f
38H 735(f) n/a CS 3 AP10 9.2 a b† f
35-S 739(f) n/a CS 4 n/a 9.3 a b†<sup>2</sup> d†<sup>1</sup> f
PzKpfw IIID n/a CS 5 sD6 10† n/a
PzKpfw IIIF n/a CS 5 sD7 11† n/a
PzKpfw IIIG n/a CS 5 sD7 12 N
PzKpfw IIIH n/a CS 6 sD7 13† N
PzKpfw IIIJ n/a CS 6 sD7 14† N S
PzKpfw IIIL n/a CS 5 sD7 15† B† N S
PzKpfw IIIN n/a CS 5 H9 s9 sD7 16 B† N S
PzKpfw III(Fl) n/a CS 3 sD7 17 n/a
PzKpfw IVA n/a CS 5 AP7 s9 sD6 18† n/a
PzKpfw IVC n/a CS 5 AP7 s9 sD6 19† n/a
PzKpfw IVD n/a CS 5 AP7 H6 s9 sD7 20 N R†
PzKpfw IVE n/a CS 6 AP7 H6 s9 sD7 21 N R†
PzKpfw IVF<sub>1</sub> n/a CS 6 AP7 H6 s9 sD7 22 N R†
PzKpfw IVF<sub>2</sub> n/a CS 6 s8 sD6 23† N S
PzKpfw IVH n/a CS 6 s8 sD5 24† B† S
PzKpfw IVJ n/a CS 6 s8 sN9 25 S
PzKpfw VD n/a CS 5 <small><i>(brew up)</i></small> sD5 26 F†
PzKpfw VG n/a CS 6 n/a 27† H†
PzKpfw M15/42(i) n/a CS 4 n/a 28 I
PzKpfw P26/40(i) n/a CS 4 s8 29 I
PzKpfw VIE n/a CS 6 M7† sD7 30† K N
PzKpfw VIE (L) n/a CS 6 n/a 31 K
PzKpfw VIB n/a CS 6 sN9 32† K
StuG IIIB n/a CS 4 AP7 H6 s9 sD7 33† n/a
StuG IIIG n/a CS 4 s8 sD7 34† N P† S
StuG IIIG (L) n/a CS 4 s8 sN9 35 O† Q† S
StuH 42 n/a CS 4 H9 s9 sD7 36† N P† S
StuH 42 (L) n/a CS 4 H9 s9 sN9 35 O† Q† S
StuPz IV n/a CS 6 H7 s9 37† S
Sturmtiger n/a CS 5 sN9 37.1† K
PzJg Tiger n/a CS 7 n/a 38 n/a
StuG 75/18(i) n/a CS 3 H7 s9 39 I P†
StuG 75/34(i) n/a CS 3 s9 40 I P†
StuG 105/25(i) n/a CS 3 H7 s9 41 I P†
StuG 75/46(i) n/a CS 3 s9 42 I P†
PzJg I n/a CS 3 HE7 43 N
PzJg 35R(f) n/a CS 3 HE7 44 a
Pz 35R 731(f) n/a CS 2 n/a 44.1† a
Marder I n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 45† a
GSW 39H(f) Pak n/a CS 3 HE9 45.1 a e
Marder II n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 46 M† N
Marder III(t)H n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 sD6 47 E G N
Marder III(t)M n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 sD5 48 E
PzJg III/IV n/a CS 5 HE7 49 M†
JgdPz 38(t) n/a CS 4 HE7 50 E O†
JgdPz 38(t) (Fl) n/a CS 4 n/a 51 E O†
JgdPz IV n/a CS 4 HE7 s7 52 S
JgdPz IV (L) n/a CS 4 HE7 s7 52 S
StuIG 33B n/a CS 5 H7 s9 53† n/a
JgdPz IV/70 n/a CS 4 HE7 sN9 54† S
JgdPz V n/a CS 6 HE7 sN9 55 H
JgdPz VI n/a CS 7 HE7 sN9 56 n/a
SPW 250/1 n/a CS 4 n/a 57 N
SPW 250/sMG n/a CS 4† n/a 58† J† N
SPW 250/7 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 J† N
SPW 250/8 n/a CS 4 <small><i>(brew up)</i></small> H6 s8 60 Q†
SPW 250/9 n/a CS 4 n/a 61† A†
SPW 250/10 n/a CS 4 n/a 62 J† N
SPW 251/1 n/a CS 5 n/a 63 C† N
SPW 251/sMG n/a CS 5† n/a 58† C† J† N
SPW 251/2 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 C† J† N
SPW 251/9 n/a CS 4 <small><i>(brew up)</i></small> H6 s9 64 C† N Q†
SPW 251/10 n/a CS 4 n/a 65 C† J† N
SPW 251/16 n/a CS 4 <small><i>(brew up)</i></small> n/a 66† n/a
SPW 251/21 n/a CS 4 n/a 66.1† n/a
SPW 251/22 n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 67 J†
SPW S307(f) n/a CS 4 HE9 67.1 a e
mSPW S307(f) n/a CS 4 IR s5 67.2 a e
Kfz 13 n/a CS 2 n/a 68† n/a
PSW 221 n/a CS 2 sD6 69† A† N
PSW 222 n/a CS 3 sD6 70† A† N
PSW 222 (L) n/a CS 3 sD6 70† A† N
PSW 231 (6 rad) n/a CS 4 n/a 71† n/a
PSW 231 (8 rad) n/a CS 4 sD6 72† N
PSW 232 (8 rad) n/a CS 4 sD6 72 N
PSW 233 n/a CS 5 H8 s9 sD7 73 N
PSW 234/1 n/a CS 5 n/a 74 A†
PSW 234/2 n/a CS 4 sD7 75 n/a
PSW 234/3 n/a CS 5 H8 s9 76 Q†
PSW 234/4 n/a CS 5 <small><i>(brew up)</i></small> HE7 s7 77† n/a
sIG IB n/a CS 4 <small><i>(brew up)</i></small> H6 s9 78 G
sIG II n/a CS 4 <small><i>(brew up)</i></small> H6 s9 79 N
sIG 38(t)M n/a CS 4 <small><i>(brew up)</i></small> H6 s9 80 E N
PzA II n/a CS 4 H5 s8 81 M†
PzA LrS(f) n/a CS 4 H4 s8 82 a N
GSW 39H(f) n/a CS 4 H4 s7 82.1 a e
PzA III/IV n/a CS 6 H5 s8 83 n/a
FlaKPz 38(t) n/a CS 4 n/a 84 A† E
37 FlaK/Pz IV n/a CS 5 n/a 85† A†
(Moebelwagen) n/a CS 6 n/a 85† A†
FlaKPz IV/20 n/a CS 6 n/a 86 A†
FlaKPz IV/37 n/a CS 6 n/a 87 A†
SdKfz 10/4 n/a CS 5 n/a 88 A† D† N
SdKfz 10/5 n/a CS 4 n/a 88.1† A†<sup>2</sup> D†<sup>1</sup>
SdKfz 6/2 n/a CS 5 n/a 89 A† D† N
SdKfz 7/1 n/a CS 4 n/a 90 A† D† N
Kfz 4 n/a CS 3 n/a 91† N
2cm FlaK LKW n/a CS 5 n/a 92 A†
3.7cm FlaK LKW n/a CS 5 n/a 92 A†
Goliath n/a n/a n/a 93† L†
Kfz 1 n/a cs 2† n/a 94† L† N
Kfz 1/20 n/a cs 2 n/a 95† L†
Opel 6700 (Blitz) n/a cs 6 n/a 96 N
Buessing-NAG 4500 n/a cs 7 n/a 96 N
SdKfz 2 n/a cs 2 n/a 97† L† N
SdKfz 7 n/a cs 6 n/a 98 N
SdKfz 11 n/a cs 5 n/a 99 N

@ -0,0 +1,124 @@
=== vehicles/german/1941 ===
Name Radio Crew survival Capabilities # Notes
---------------------- ------- ------------------------------------ -------------- ----- ---------------------------------
PzKpfw IB n/a CS 2 sD6 1† N
PzKpfw IIA n/a CS 3 sD6 2 n/a
PzKpfw IIF n/a CS 3 sD7 3 N
PzKpfw II(Fl) n/a CS 2 <small><i>(brew up)</i></small> sD7† 4† n/a
PzKpfw IIL n/a CS 4 sD7 5 n/a
PzKpfw 35t n/a CS 4 sD5 6† n/a
PzKpfw 38(t)A n/a CS 4 A4 sD6 7† E
PzKpfw 38(t)E n/a CS 4 A4 sD7 8† E
Aufklaerer 38(t) n/a CS 5 n/a 9 A† E
FT-17 730m(f) n/a CS 2 n/a 9.1† a c f
FT-17 730(f) n/a CS 2 AP9 9.1† a c f
38H 735(f) n/a CS 3 AP10 9.2 a b† f
35-S 739(f) n/a CS 4 n/a 9.3 a b†<sup>2</sup> d†<sup>1</sup> f
PzKpfw IIID n/a CS 5 sD6 10† n/a
PzKpfw IIIF n/a CS 5 A4 sD7 11† n/a
PzKpfw IIIG n/a CS 5 A4 sD7 12 N
PzKpfw IIIH n/a CS 6 A4 sD7 13† N
PzKpfw IIIJ n/a CS 6 sD7 14† N S
PzKpfw IIIL n/a CS 5 sD7 15† B† N S
PzKpfw IIIN n/a CS 5 H9 s9 sD7 16 B† N S
PzKpfw III(Fl) n/a CS 3 sD7 17 n/a
PzKpfw IVA n/a CS 5 AP7 s9 sD6 18† n/a
PzKpfw IVC n/a CS 5 AP7 s9 sD6 19† n/a
PzKpfw IVD n/a CS 5 AP7 H6 s9 sD7 20 N R†
PzKpfw IVE n/a CS 6 AP7 H6 s9 sD7 21 N R†
PzKpfw IVF<sub>1</sub> n/a CS 6 AP7 H6 s9 sD7 22 N R†
PzKpfw IVF<sub>2</sub> n/a CS 6 s8 sD6 23† N S
PzKpfw IVH n/a CS 6 s8 sD5 24† B† S
PzKpfw IVJ n/a CS 6 s8 sN9 25 S
PzKpfw VD n/a CS 5 <small><i>(brew up)</i></small> sD5 26 F†
PzKpfw VG n/a CS 6 n/a 27† H†
PzKpfw M15/42(i) n/a CS 4 n/a 28 I
PzKpfw P26/40(i) n/a CS 4 s8 29 I
PzKpfw VIE n/a CS 6 M7† sD7 30† K N
PzKpfw VIE (L) n/a CS 6 n/a 31 K
PzKpfw VIB n/a CS 6 sN9 32† K
StuG IIIB n/a CS 4 AP7 H6 s9 sD7 33† n/a
StuG IIIG n/a CS 4 s8 sD7 34† N P† S
StuG IIIG (L) n/a CS 4 s8 sN9 35 O† Q† S
StuH 42 n/a CS 4 H9 s9 sD7 36† N P† S
StuH 42 (L) n/a CS 4 H9 s9 sN9 35 O† Q† S
StuPz IV n/a CS 6 H7 s9 37† S
Sturmtiger n/a CS 5 sN9 37.1† K
PzJg Tiger n/a CS 7 n/a 38 n/a
StuG 75/18(i) n/a CS 3 H7 s9 39 I P†
StuG 75/34(i) n/a CS 3 s9 40 I P†
StuG 105/25(i) n/a CS 3 H7 s9 41 I P†
StuG 75/46(i) n/a CS 3 s9 42 I P†
PzJg I n/a CS 3 A5 HE7 43 N
PzJg 35R(f) n/a CS 3 A5 HE7 44 a
Pz 35R 731(f) n/a CS 2 n/a 44.1† a
Marder I n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 45† a
GSW 39H(f) Pak n/a CS 3 HE9 45.1 a e
Marder II n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 46 M† N
Marder III(t)H n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 sD6 47 E G N
Marder III(t)M n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 sD5 48 E
PzJg III/IV n/a CS 5 HE7 49 M†
JgdPz 38(t) n/a CS 4 HE7 50 E O†
JgdPz 38(t) (Fl) n/a CS 4 n/a 51 E O†
JgdPz IV n/a CS 4 HE7 s7 52 S
JgdPz IV (L) n/a CS 4 HE7 s7 52 S
StuIG 33B n/a CS 5 H7 s9 53† n/a
JgdPz IV/70 n/a CS 4 HE7 sN9 54† S
JgdPz V n/a CS 6 HE7 sN9 55 H
JgdPz VI n/a CS 7 HE7 sN9 56 n/a
SPW 250/1 n/a CS 4 n/a 57 N
SPW 250/sMG n/a CS 4† n/a 58† J† N
SPW 250/7 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 J† N
SPW 250/8 n/a CS 4 <small><i>(brew up)</i></small> H6 s8 60 Q†
SPW 250/9 n/a CS 4 n/a 61† A†
SPW 250/10 n/a CS 4 A4 62 J† N
SPW 251/1 n/a CS 5 n/a 63 C† N
SPW 251/sMG n/a CS 5† n/a 58† C† J† N
SPW 251/2 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 C† J† N
SPW 251/9 n/a CS 4 <small><i>(brew up)</i></small> H6 s9 64 C† N Q†
SPW 251/10 n/a CS 4 A4 65 C† J† N
SPW 251/16 n/a CS 4 <small><i>(brew up)</i></small> n/a 66† n/a
SPW 251/21 n/a CS 4 n/a 66.1† n/a
SPW 251/22 n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 67 J†
SPW S307(f) n/a CS 4 HE9 67.1 a e
mSPW S307(f) n/a CS 4 IR s5 67.2 a e
Kfz 13 n/a CS 2 n/a 68† n/a
PSW 221 n/a CS 2 sD6 69† A† N
PSW 222 n/a CS 3 sD6 70† A† N
PSW 222 (L) n/a CS 3 sD6 70† A† N
PSW 231 (6 rad) n/a CS 4 n/a 71† n/a
PSW 231 (8 rad) n/a CS 4 sD6 72† N
PSW 232 (8 rad) n/a CS 4 sD6 72 N
PSW 233 n/a CS 5 H8 s9 sD7 73 N
PSW 234/1 n/a CS 5 n/a 74 A†
PSW 234/2 n/a CS 4 sD7 75 n/a
PSW 234/3 n/a CS 5 H8 s9 76 Q†
PSW 234/4 n/a CS 5 <small><i>(brew up)</i></small> HE7 s7 77† n/a
sIG IB n/a CS 4 <small><i>(brew up)</i></small> H6 s9 78 G
sIG II n/a CS 4 <small><i>(brew up)</i></small> H6 s9 79 N
sIG 38(t)M n/a CS 4 <small><i>(brew up)</i></small> H6 s9 80 E N
PzA II n/a CS 4 H5 s8 81 M†
PzA LrS(f) n/a CS 4 H4 s8 82 a N
GSW 39H(f) n/a CS 4 H4 s7 82.1 a e
PzA III/IV n/a CS 6 H5 s8 83 n/a
FlaKPz 38(t) n/a CS 4 n/a 84 A† E
37 FlaK/Pz IV n/a CS 5 n/a 85† A†
(Moebelwagen) n/a CS 6 n/a 85† A†
FlaKPz IV/20 n/a CS 6 n/a 86 A†
FlaKPz IV/37 n/a CS 6 n/a 87 A†
SdKfz 10/4 n/a CS 5 n/a 88 A† D† N
SdKfz 10/5 n/a CS 4 n/a 88.1† A†<sup>2</sup> D†<sup>1</sup>
SdKfz 6/2 n/a CS 5 n/a 89 A† D† N
SdKfz 7/1 n/a CS 4 n/a 90 A† D† N
Kfz 4 n/a CS 3 n/a 91† N
2cm FlaK LKW n/a CS 5 n/a 92 A†
3.7cm FlaK LKW n/a CS 5 n/a 92 A†
Goliath n/a n/a n/a 93† L†
Kfz 1 n/a cs 2† n/a 94† L† N
Kfz 1/20 n/a cs 2 n/a 95† L†
Opel 6700 (Blitz) n/a cs 6 n/a 96 N
Buessing-NAG 4500 n/a cs 7 n/a 96 N
SdKfz 2 n/a cs 2 n/a 97† L† N
SdKfz 7 n/a cs 6 n/a 98 N
SdKfz 11 n/a cs 5 n/a 99 N

@ -0,0 +1,124 @@
=== vehicles/german/1942 ===
Name Radio Crew survival Capabilities # Notes
---------------------- ------- ------------------------------------ -------------- ----- ---------------------------------
PzKpfw IB n/a CS 2 sD6 1† N
PzKpfw IIA n/a CS 3 sD6 2 n/a
PzKpfw IIF n/a CS 3 sD7 3 N
PzKpfw II(Fl) n/a CS 2 <small><i>(brew up)</i></small> sD7† 4† n/a
PzKpfw IIL n/a CS 4 sD7 5 n/a
PzKpfw 35t n/a CS 4 sD5 6† n/a
PzKpfw 38(t)A n/a CS 4 A5 sD6 7† E
PzKpfw 38(t)E n/a CS 4 A5 sD7 8† E
Aufklaerer 38(t) n/a CS 5 n/a 9 A† E
FT-17 730m(f) n/a CS 2 n/a 9.1† a c f
FT-17 730(f) n/a CS 2 AP9 9.1† a c f
38H 735(f) n/a CS 3 AP10 9.2 a b† f
35-S 739(f) n/a CS 4 n/a 9.3 a b†<sup>2</sup> d†<sup>1</sup> f
PzKpfw IIID n/a CS 5 sD6 10† n/a
PzKpfw IIIF n/a CS 5 A4 sD7 11† n/a
PzKpfw IIIG n/a CS 5 A4 sD7 12 N
PzKpfw IIIH n/a CS 6 A4 sD7 13† N
PzKpfw IIIJ n/a CS 6 A6 sD7 14† N S
PzKpfw IIIL n/a CS 5 A6 sD7 15† B† N S
PzKpfw IIIN n/a CS 5 H9 s9 sD7 16 B† N S
PzKpfw III(Fl) n/a CS 3 sD7 17 n/a
PzKpfw IVA n/a CS 5 AP7 s9 sD6 18† n/a
PzKpfw IVC n/a CS 5 AP7 s9 sD6 19† n/a
PzKpfw IVD n/a CS 5 AP7 H6 s9 sD7 20 N R†
PzKpfw IVE n/a CS 6 AP7 H6 s9 sD7 21 N R†
PzKpfw IVF<sub>1</sub> n/a CS 6 AP7 H6 s9 sD7 22 N R†
PzKpfw IVF<sub>2</sub> n/a CS 6 A5 s8 sD6 23† N S
PzKpfw IVH n/a CS 6 A5 s8 sD5 24† B† S
PzKpfw IVJ n/a CS 6 s8 sN9 25 S
PzKpfw VD n/a CS 5 <small><i>(brew up)</i></small> sD5 26 F†
PzKpfw VG n/a CS 6 n/a 27† H†
PzKpfw M15/42(i) n/a CS 4 n/a 28 I
PzKpfw P26/40(i) n/a CS 4 s8 29 I
PzKpfw VIE n/a CS 6 A6 M7† sD7 30† K N
PzKpfw VIE (L) n/a CS 6 n/a 31 K
PzKpfw VIB n/a CS 6 sN9 32† K
StuG IIIB n/a CS 4 AP7 H6 s9 sD7 33† n/a
StuG IIIG n/a CS 4 A6 s8 sD7 34† N P† S
StuG IIIG (L) n/a CS 4 s8 sN9 35 O† Q† S
StuH 42 n/a CS 4 H9 s9 sD7 36† N P† S
StuH 42 (L) n/a CS 4 H9 s9 sN9 35 O† Q† S
StuPz IV n/a CS 6 H7 s9 37† S
Sturmtiger n/a CS 5 sN9 37.1† K
PzJg Tiger n/a CS 7 n/a 38 n/a
StuG 75/18(i) n/a CS 3 H7 s9 39 I P†
StuG 75/34(i) n/a CS 3 s9 40 I P†
StuG 105/25(i) n/a CS 3 H7 s9 41 I P†
StuG 75/46(i) n/a CS 3 s9 42 I P†
PzJg I n/a CS 3 A5 HE7 43 N
PzJg 35R(f) n/a CS 3 A5 HE7 44 a
Pz 35R 731(f) n/a CS 2 n/a 44.1† a
Marder I n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 45† a
GSW 39H(f) Pak n/a CS 3 HE9 45.1 a e
Marder II n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 46 M† N
Marder III(t)H n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 sD6 47 E G N
Marder III(t)M n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 sD5 48 E
PzJg III/IV n/a CS 5 HE7 49 M†
JgdPz 38(t) n/a CS 4 HE7 50 E O†
JgdPz 38(t) (Fl) n/a CS 4 n/a 51 E O†
JgdPz IV n/a CS 4 HE7 s7 52 S
JgdPz IV (L) n/a CS 4 HE7 s7 52 S
StuIG 33B n/a CS 5 H7 s9 53† n/a
JgdPz IV/70 n/a CS 4 HE7 sN9 54† S
JgdPz V n/a CS 6 HE7 sN9 55 H
JgdPz VI n/a CS 7 HE7 sN9 56 n/a
SPW 250/1 n/a CS 4 n/a 57 N
SPW 250/sMG n/a CS 4† n/a 58† J† N
SPW 250/7 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 J† N
SPW 250/8 n/a CS 4 <small><i>(brew up)</i></small> H6 s8 60 Q†
SPW 250/9 n/a CS 4 n/a 61† A†
SPW 250/10 n/a CS 4 A4 62 J† N
SPW 251/1 n/a CS 5 n/a 63 C† N
SPW 251/sMG n/a CS 5† n/a 58† C† J† N
SPW 251/2 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 C† J† N
SPW 251/9 n/a CS 4 <small><i>(brew up)</i></small> H6 s9 64 C† N Q†
SPW 251/10 n/a CS 4 A4 65 C† J† N
SPW 251/16 n/a CS 4 <small><i>(brew up)</i></small> n/a 66† n/a
SPW 251/21 n/a CS 4 n/a 66.1† n/a
SPW 251/22 n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 67 J†
SPW S307(f) n/a CS 4 HE9 67.1 a e
mSPW S307(f) n/a CS 4 IR s5 67.2 a e
Kfz 13 n/a CS 2 n/a 68† n/a
PSW 221 n/a CS 2 sD6 69† A† N
PSW 222 n/a CS 3 sD6 70† A† N
PSW 222 (L) n/a CS 3 sD6 70† A† N
PSW 231 (6 rad) n/a CS 4 n/a 71† n/a
PSW 231 (8 rad) n/a CS 4 sD6 72† N
PSW 232 (8 rad) n/a CS 4 sD6 72 N
PSW 233 n/a CS 5 H8 s9 sD7 73 N
PSW 234/1 n/a CS 5 n/a 74 A†
PSW 234/2 n/a CS 4 sD7 75 n/a
PSW 234/3 n/a CS 5 H8 s9 76 Q†
PSW 234/4 n/a CS 5 <small><i>(brew up)</i></small> HE7 s7 77† n/a
sIG IB n/a CS 4 <small><i>(brew up)</i></small> H6 s9 78 G
sIG II n/a CS 4 <small><i>(brew up)</i></small> H6 s9 79 N
sIG 38(t)M n/a CS 4 <small><i>(brew up)</i></small> H6 s9 80 E N
PzA II n/a CS 4 H5 s8 81 M†
PzA LrS(f) n/a CS 4 H4 s8 82 a N
GSW 39H(f) n/a CS 4 H4 s7 82.1 a e
PzA III/IV n/a CS 6 H5 s8 83 n/a
FlaKPz 38(t) n/a CS 4 n/a 84 A† E
37 FlaK/Pz IV n/a CS 5 n/a 85† A†
(Moebelwagen) n/a CS 6 n/a 85† A†
FlaKPz IV/20 n/a CS 6 n/a 86 A†
FlaKPz IV/37 n/a CS 6 n/a 87 A†
SdKfz 10/4 n/a CS 5 n/a 88 A† D† N
SdKfz 10/5 n/a CS 4 n/a 88.1† A†<sup>2</sup> D†<sup>1</sup>
SdKfz 6/2 n/a CS 5 n/a 89 A† D† N
SdKfz 7/1 n/a CS 4 n/a 90 A† D† N
Kfz 4 n/a CS 3 n/a 91† N
2cm FlaK LKW n/a CS 5 n/a 92 A†
3.7cm FlaK LKW n/a CS 5 n/a 92 A†
Goliath n/a n/a n/a 93† L†
Kfz 1 n/a cs 2† n/a 94† L† N
Kfz 1/20 n/a cs 2 n/a 95† L†
Opel 6700 (Blitz) n/a cs 6 n/a 96 N
Buessing-NAG 4500 n/a cs 7 n/a 96 N
SdKfz 2 n/a cs 2 n/a 97† L† N
SdKfz 7 n/a cs 6 n/a 98 N
SdKfz 11 n/a cs 5 n/a 99 N

@ -0,0 +1,124 @@
=== vehicles/german/1943 ===
Name Radio Crew survival Capabilities # Notes
---------------------- ------- ------------------------------------ -------------- ----- ---------------------------------
PzKpfw IB n/a CS 2 sD6 1† N
PzKpfw IIA n/a CS 3 sD6 2 n/a
PzKpfw IIF n/a CS 3 sD7 3 N
PzKpfw II(Fl) n/a CS 2 <small><i>(brew up)</i></small> sD7† 4† n/a
PzKpfw IIL n/a CS 4 sD7 5 n/a
PzKpfw 35t n/a CS 4 sD5 6† n/a
PzKpfw 38(t)A n/a CS 4 A5 sD6 7† E
PzKpfw 38(t)E n/a CS 4 A5 sD7 8† E
Aufklaerer 38(t) n/a CS 5 n/a 9 A† E
FT-17 730m(f) n/a CS 2 n/a 9.1† a c f
FT-17 730(f) n/a CS 2 AP9 9.1† a c f
38H 735(f) n/a CS 3 AP10 9.2 a b† f
35-S 739(f) n/a CS 4 n/a 9.3 a b†<sup>2</sup> d†<sup>1</sup> f
PzKpfw IIID n/a CS 5 sD6 10† n/a
PzKpfw IIIF n/a CS 5 A4 sD7 11† n/a
PzKpfw IIIG n/a CS 5 A4 sD7 12 N
PzKpfw IIIH n/a CS 6 A4 sD7 13† N
PzKpfw IIIJ n/a CS 6 A6 sD7 14† N S
PzKpfw IIIL n/a CS 5 A6 sD7 15† B† N S
PzKpfw IIIN n/a CS 5 H9 s9 sD7 16 B† N S
PzKpfw III(Fl) n/a CS 3 sD7 17 n/a
PzKpfw IVA n/a CS 5 AP7 s9 sD6 18† n/a
PzKpfw IVC n/a CS 5 AP7 s9 sD6 19† n/a
PzKpfw IVD n/a CS 5 AP7 H6 s9 sD7 20 N R†
PzKpfw IVE n/a CS 6 AP7 H6 s9 sD7 21 N R†
PzKpfw IVF<sub>1</sub> n/a CS 6 AP7 H6 s9 sD7 22 N R†
PzKpfw IVF<sub>2</sub> n/a CS 6 A5 s8 sD6 23† N S
PzKpfw IVH n/a CS 6 A5 s8 sD5 24† B† S
PzKpfw IVJ n/a CS 6 s8 sN9 25 S
PzKpfw VD n/a CS 5 <small><i>(brew up)</i></small> sD5 26 F†
PzKpfw VG n/a CS 6 n/a 27† H†
PzKpfw M15/42(i) n/a CS 4 n/a 28 I
PzKpfw P26/40(i) n/a CS 4 s8 29 I
PzKpfw VIE n/a CS 6 A6 M7† sD7 30† K N
PzKpfw VIE (L) n/a CS 6 n/a 31 K
PzKpfw VIB n/a CS 6 sN9 32† K
StuG IIIB n/a CS 4 AP7 H6 s9 sD7 33† n/a
StuG IIIG n/a CS 4 A6 s8 sD7 34† N P† S
StuG IIIG (L) n/a CS 4 s8 sN9 35 O† Q† S
StuH 42 n/a CS 4 H9 s9 sD7 36† N P† S
StuH 42 (L) n/a CS 4 H9 s9 sN9 35 O† Q† S
StuPz IV n/a CS 6 H7 s9 37† S
Sturmtiger n/a CS 5 sN9 37.1† K
PzJg Tiger n/a CS 7 n/a 38 n/a
StuG 75/18(i) n/a CS 3 H7 s9 39 I P†
StuG 75/34(i) n/a CS 3 s9 40 I P†
StuG 105/25(i) n/a CS 3 H7 s9 41 I P†
StuG 75/46(i) n/a CS 3 s9 42 I P†
PzJg I n/a CS 3 A5 HE7 43 N
PzJg 35R(f) n/a CS 3 A5 HE7 44 a
Pz 35R 731(f) n/a CS 2 n/a 44.1† a
Marder I n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 45† a
GSW 39H(f) Pak n/a CS 3 HE9 45.1 a e
Marder II n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 46 M† N
Marder III(t)H n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 sD6 47 E G N
Marder III(t)M n/a CS 4 <small><i>(brew up)</i></small> A5 HE7 s7 sD5 48 E
PzJg III/IV n/a CS 5 HE7 49 M†
JgdPz 38(t) n/a CS 4 HE7 50 E O†
JgdPz 38(t) (Fl) n/a CS 4 n/a 51 E O†
JgdPz IV n/a CS 4 HE7 s7 52 S
JgdPz IV (L) n/a CS 4 HE7 s7 52 S
StuIG 33B n/a CS 5 H7 s9 53† n/a
JgdPz IV/70 n/a CS 4 HE7 sN9 54† S
JgdPz V n/a CS 6 HE7 sN9 55 H
JgdPz VI n/a CS 7 HE7 sN9 56 n/a
SPW 250/1 n/a CS 4 n/a 57 N
SPW 250/sMG n/a CS 4† n/a 58† J† N
SPW 250/7 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 J† N
SPW 250/8 n/a CS 4 <small><i>(brew up)</i></small> H6 s8 60 Q†
SPW 250/9 n/a CS 4 n/a 61† A†
SPW 250/10 n/a CS 4 A4 62 J† N
SPW 251/1 n/a CS 5 n/a 63 C† N
SPW 251/sMG n/a CS 5† n/a 58† C† J† N
SPW 251/2 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 C† J† N
SPW 251/9 n/a CS 4 <small><i>(brew up)</i></small> H6 s9 64 C† N Q†
SPW 251/10 n/a CS 4 A4 65 C† J† N
SPW 251/16 n/a CS 4 <small><i>(brew up)</i></small> n/a 66† n/a
SPW 251/21 n/a CS 4 n/a 66.1† n/a
SPW 251/22 n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 67 J†
SPW S307(f) n/a CS 4 HE9 67.1 a e
mSPW S307(f) n/a CS 4 IR s5 67.2 a e
Kfz 13 n/a CS 2 n/a 68† n/a
PSW 221 n/a CS 2 sD6 69† A† N
PSW 222 n/a CS 3 sD6 70† A† N
PSW 222 (L) n/a CS 3 sD6 70† A† N
PSW 231 (6 rad) n/a CS 4 n/a 71† n/a
PSW 231 (8 rad) n/a CS 4 sD6 72† N
PSW 232 (8 rad) n/a CS 4 sD6 72 N
PSW 233 n/a CS 5 H8 s9 sD7 73 N
PSW 234/1 n/a CS 5 n/a 74 A†
PSW 234/2 n/a CS 4 A5 sD7 75 n/a
PSW 234/3 n/a CS 5 H8 s9 76 Q†
PSW 234/4 n/a CS 5 <small><i>(brew up)</i></small> HE7 s7 77† n/a
sIG IB n/a CS 4 <small><i>(brew up)</i></small> H6 s9 78 G
sIG II n/a CS 4 <small><i>(brew up)</i></small> H6 s9 79 N
sIG 38(t)M n/a CS 4 <small><i>(brew up)</i></small> H6 s9 80 E N
PzA II n/a CS 4 H5 s8 81 M†
PzA LrS(f) n/a CS 4 H4 s8 82 a N
GSW 39H(f) n/a CS 4 H4 s7 82.1 a e
PzA III/IV n/a CS 6 H5 s8 83 n/a
FlaKPz 38(t) n/a CS 4 n/a 84 A† E
37 FlaK/Pz IV n/a CS 5 n/a 85† A†
(Moebelwagen) n/a CS 6 n/a 85† A†
FlaKPz IV/20 n/a CS 6 n/a 86 A†
FlaKPz IV/37 n/a CS 6 n/a 87 A†
SdKfz 10/4 n/a CS 5 n/a 88 A† D† N
SdKfz 10/5 n/a CS 4 n/a 88.1† A†<sup>2</sup> D†<sup>1</sup>
SdKfz 6/2 n/a CS 5 n/a 89 A† D† N
SdKfz 7/1 n/a CS 4 n/a 90 A† D† N
Kfz 4 n/a CS 3 n/a 91† N
2cm FlaK LKW n/a CS 5 n/a 92 A†
3.7cm FlaK LKW n/a CS 5 n/a 92 A†
Goliath n/a n/a n/a 93† L†
Kfz 1 n/a cs 2† n/a 94† L† N
Kfz 1/20 n/a cs 2 n/a 95† L†
Opel 6700 (Blitz) n/a cs 6 n/a 96 N
Buessing-NAG 4500 n/a cs 7 n/a 96 N
SdKfz 2 n/a cs 2 n/a 97† L† N
SdKfz 7 n/a cs 6 n/a 98 N
SdKfz 11 n/a cs 5 n/a 99 N

@ -0,0 +1,124 @@
=== vehicles/german/1944 ===
Name Radio Crew survival Capabilities # Notes
---------------------- ------- ------------------------------------ -------------- ----- ---------------------------------
PzKpfw IB n/a CS 2 sD6 1† N
PzKpfw IIA n/a CS 3 sD6 2 n/a
PzKpfw IIF n/a CS 3 sD7 3 N
PzKpfw II(Fl) n/a CS 2 <small><i>(brew up)</i></small> sD7† 4† n/a
PzKpfw IIL n/a CS 4 sD7 5 n/a
PzKpfw 35t n/a CS 4 sD5 6† n/a
PzKpfw 38(t)A n/a CS 4 A5 sD6 7† E
PzKpfw 38(t)E n/a CS 4 A5 sD7 8† E
Aufklaerer 38(t) n/a CS 5 n/a 9 A† E
FT-17 730m(f) n/a CS 2 n/a 9.1† a c f
FT-17 730(f) n/a CS 2 AP9 9.1† a c f
38H 735(f) n/a CS 3 AP10 9.2 a b† f
35-S 739(f) n/a CS 4 n/a 9.3 a b†<sup>2</sup> d†<sup>1</sup> f
PzKpfw IIID n/a CS 5 sD6 10† n/a
PzKpfw IIIF n/a CS 5 A4 sD7 11† n/a
PzKpfw IIIG n/a CS 5 A4 sD7 12 N
PzKpfw IIIH n/a CS 6 A4 sD7 13† N
PzKpfw IIIJ n/a CS 6 A6 sD7 14† N S
PzKpfw IIIL n/a CS 5 A6 sD7 15† B† N S
PzKpfw IIIN n/a CS 5 H9 s9 sD7 16 B† N S
PzKpfw III(Fl) n/a CS 3 sD7 17 n/a
PzKpfw IVA n/a CS 5 AP7 s9 sD6 18† n/a
PzKpfw IVC n/a CS 5 AP7 s9 sD6 19† n/a
PzKpfw IVD n/a CS 5 AP7 H6 s9 sD7 20 N R†
PzKpfw IVE n/a CS 6 AP7 H6 s9 sD7 21 N R†
PzKpfw IVF<sub>1</sub> n/a CS 6 AP7 H6 s9 sD7 22 N R†
PzKpfw IVF<sub>2</sub> n/a CS 6 A5 s8 sD6 23† N S
PzKpfw IVH n/a CS 6 A5 s8 sD5 24† B† S
PzKpfw IVJ n/a CS 6 A3 s8 sN9 25 S
PzKpfw VD n/a CS 5 <small><i>(brew up)</i></small> sD5 26 F†
PzKpfw VG n/a CS 6 sN7 27† H†
PzKpfw M15/42(i) n/a CS 4 n/a 28 I
PzKpfw P26/40(i) n/a CS 4 s8 29 I
PzKpfw VIE n/a CS 6 A6 M7† sD7 30† K N
PzKpfw VIE (L) n/a CS 6 A4 sN7 31 K
PzKpfw VIB n/a CS 6 sN9 32† K
StuG IIIB n/a CS 4 AP7 H6 s9 sD7 33† n/a
StuG IIIG n/a CS 4 A6 s8 sD7 34† N P† S
StuG IIIG (L) n/a CS 4 A4 s8 sN9 35 O† Q† S
StuH 42 n/a CS 4 H9 s9 sD7 36† N P† S
StuH 42 (L) n/a CS 4 H9 s9 sN9 35 O† Q† S
StuPz IV n/a CS 6 H7 s9 37† S
Sturmtiger n/a CS 5 sN9 37.1† K
PzJg Tiger n/a CS 7 n/a 38 n/a
StuG 75/18(i) n/a CS 3 H7 s9 39 I P†
StuG 75/34(i) n/a CS 3 s9 40 I P†
StuG 105/25(i) n/a CS 3 H7 s9 41 I P†
StuG 75/46(i) n/a CS 3 s9 42 I P†
PzJg I n/a CS 3 A5 HE7 43 N
PzJg 35R(f) n/a CS 3 A5 HE7 44 a
Pz 35R 731(f) n/a CS 2 n/a 44.1† a
Marder I n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 45† a
GSW 39H(f) Pak n/a CS 3 A4 HE9 45.1 a e
Marder II n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 46 M† N
Marder III(t)H n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 sD6 47 E G N
Marder III(t)M n/a CS 4 <small><i>(brew up)</i></small> A5 HE7 s7 sD5 48 E
PzJg III/IV n/a CS 5 HE7 49 M†
JgdPz 38(t) n/a CS 4 A4 HE7 50 E O†
JgdPz 38(t) (Fl) n/a CS 4 n/a 51 E O†
JgdPz IV n/a CS 4 A4 HE7 s7 sN7 52 S
JgdPz IV (L) n/a CS 4 A4 HE7 s7 sN8 52 S
StuIG 33B n/a CS 5 H7 s9 53† n/a
JgdPz IV/70 n/a CS 4 HE7 sN9 54† S
JgdPz V n/a CS 6 HE7 sN9 55 H
JgdPz VI n/a CS 7 HE7 sN9 56 n/a
SPW 250/1 n/a CS 4 n/a 57 N
SPW 250/sMG n/a CS 4† n/a 58† J† N
SPW 250/7 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 J† N
SPW 250/8 n/a CS 4 <small><i>(brew up)</i></small> H6 s8 60 Q†
SPW 250/9 n/a CS 4 n/a 61† A†
SPW 250/10 n/a CS 4 A4 62 J† N
SPW 251/1 n/a CS 5 n/a 63 C† N
SPW 251/sMG n/a CS 5† n/a 58† C† J† N
SPW 251/2 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 C† J† N
SPW 251/9 n/a CS 4 <small><i>(brew up)</i></small> H6 s9 64 C† N Q†
SPW 251/10 n/a CS 4 A4 65 C† J† N
SPW 251/16 n/a CS 4 <small><i>(brew up)</i></small> n/a 66† n/a
SPW 251/21 n/a CS 4 n/a 66.1† n/a
SPW 251/22 n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 67 J†
SPW S307(f) n/a CS 4 A4 HE9 67.1 a e
mSPW S307(f) n/a CS 4 IR s5 67.2 a e
Kfz 13 n/a CS 2 n/a 68† n/a
PSW 221 n/a CS 2 sD6 69† A† N
PSW 222 n/a CS 3 sD6 70† A† N
PSW 222 (L) n/a CS 3 sD6 70† A† N
PSW 231 (6 rad) n/a CS 4 n/a 71† n/a
PSW 231 (8 rad) n/a CS 4 sD6 72† N
PSW 232 (8 rad) n/a CS 4 sD6 72 N
PSW 233 n/a CS 5 H8 s9 sD7 73 N
PSW 234/1 n/a CS 5 n/a 74 A†
PSW 234/2 n/a CS 4 A5 sD7 75 n/a
PSW 234/3 n/a CS 5 H8 s9 76 Q†
PSW 234/4 n/a CS 5 <small><i>(brew up)</i></small> HE7 s7 77† n/a
sIG IB n/a CS 4 <small><i>(brew up)</i></small> H6 s9 78 G
sIG II n/a CS 4 <small><i>(brew up)</i></small> H6 s9 79 N
sIG 38(t)M n/a CS 4 <small><i>(brew up)</i></small> H6 s9 80 E N
PzA II n/a CS 4 H5 s8 81 M†
PzA LrS(f) n/a CS 4 H4 s8 82 a N
GSW 39H(f) n/a CS 4 H4 s7 82.1 a e
PzA III/IV n/a CS 6 H5 s8 83 n/a
FlaKPz 38(t) n/a CS 4 n/a 84 A† E
37 FlaK/Pz IV n/a CS 5 n/a 85† A†
(Moebelwagen) n/a CS 6 n/a 85† A†
FlaKPz IV/20 n/a CS 6 n/a 86 A†
FlaKPz IV/37 n/a CS 6 n/a 87 A†
SdKfz 10/4 n/a CS 5 n/a 88 A† D† N
SdKfz 10/5 n/a CS 4 n/a 88.1† A†<sup>2</sup> D†<sup>1</sup>
SdKfz 6/2 n/a CS 5 n/a 89 A† D† N
SdKfz 7/1 n/a CS 4 n/a 90 A† D† N
Kfz 4 n/a CS 3 n/a 91† N
2cm FlaK LKW n/a CS 5 n/a 92 A†
3.7cm FlaK LKW n/a CS 5 n/a 92 A†
Goliath n/a n/a n/a 93† L†
Kfz 1 n/a cs 2† n/a 94† L† N
Kfz 1/20 n/a cs 2 n/a 95† L†
Opel 6700 (Blitz) n/a cs 6 n/a 96 N
Buessing-NAG 4500 n/a cs 7 n/a 96 N
SdKfz 2 n/a cs 2 n/a 97† L† N
SdKfz 7 n/a cs 6 n/a 98 N
SdKfz 11 n/a cs 5 n/a 99 N

@ -0,0 +1,124 @@
=== vehicles/german/1945 ===
Name Radio Crew survival Capabilities # Notes
---------------------- ------- ------------------------------------ -------------- ----- ---------------------------------
PzKpfw IB n/a CS 2 sD6 1† N
PzKpfw IIA n/a CS 3 sD6 2 n/a
PzKpfw IIF n/a CS 3 sD7 3 N
PzKpfw II(Fl) n/a CS 2 <small><i>(brew up)</i></small> sD7† 4† n/a
PzKpfw IIL n/a CS 4 sD7 5 n/a
PzKpfw 35t n/a CS 4 sD5 6† n/a
PzKpfw 38(t)A n/a CS 4 A5 sD6 7† E
PzKpfw 38(t)E n/a CS 4 A5 sD7 8† E
Aufklaerer 38(t) n/a CS 5 n/a 9 A† E
FT-17 730m(f) n/a CS 2 n/a 9.1† a c f
FT-17 730(f) n/a CS 2 AP9 9.1† a c f
38H 735(f) n/a CS 3 AP10 9.2 a b† f
35-S 739(f) n/a CS 4 n/a 9.3 a b†<sup>2</sup> d†<sup>1</sup> f
PzKpfw IIID n/a CS 5 sD6 10† n/a
PzKpfw IIIF n/a CS 5 A4 sD7 11† n/a
PzKpfw IIIG n/a CS 5 A4 sD7 12 N
PzKpfw IIIH n/a CS 6 A4 sD7 13† N
PzKpfw IIIJ n/a CS 6 A6 sD7 14† N S
PzKpfw IIIL n/a CS 5 A6 sD7 15† B† N S
PzKpfw IIIN n/a CS 5 H9 s9 sD7 16 B† N S
PzKpfw III(Fl) n/a CS 3 sD7 17 n/a
PzKpfw IVA n/a CS 5 AP7 s9 sD6 18† n/a
PzKpfw IVC n/a CS 5 AP7 s9 sD6 19† n/a
PzKpfw IVD n/a CS 5 AP7 H6 s9 sD7 20 N R†
PzKpfw IVE n/a CS 6 AP7 H6 s9 sD7 21 N R†
PzKpfw IVF<sub>1</sub> n/a CS 6 AP7 H6 s9 sD7 22 N R†
PzKpfw IVF<sub>2</sub> n/a CS 6 A5 s8 sD6 23† N S
PzKpfw IVH n/a CS 6 A5 s8 sD5 24† B† S
PzKpfw IVJ n/a CS 6 A3 s8 sN9 25 S
PzKpfw VD n/a CS 5 <small><i>(brew up)</i></small> sD5 26 F†
PzKpfw VG n/a CS 6 sN7 27† H†
PzKpfw M15/42(i) n/a CS 4 n/a 28 I
PzKpfw P26/40(i) n/a CS 4 s8 29 I
PzKpfw VIE n/a CS 6 A6 M7† sD7 30† K N
PzKpfw VIE (L) n/a CS 6 A4 sN7 31 K
PzKpfw VIB n/a CS 6 sN9 32† K
StuG IIIB n/a CS 4 AP7 H6 s9 sD7 33† n/a
StuG IIIG n/a CS 4 A6 s8 sD7 34† N P† S
StuG IIIG (L) n/a CS 4 A4 s8 sN9 35 O† Q† S
StuH 42 n/a CS 4 H9 s9 sD7 36† N P† S
StuH 42 (L) n/a CS 4 H9 s9 sN9 35 O† Q† S
StuPz IV n/a CS 6 H7 s9 37† S
Sturmtiger n/a CS 5 sN9 37.1† K
PzJg Tiger n/a CS 7 n/a 38 n/a
StuG 75/18(i) n/a CS 3 H7 s9 39 I P†
StuG 75/34(i) n/a CS 3 s9 40 I P†
StuG 105/25(i) n/a CS 3 H7 s9 41 I P†
StuG 75/46(i) n/a CS 3 s9 42 I P†
PzJg I n/a CS 3 A5 HE7 43 N
PzJg 35R(f) n/a CS 3 A5 HE7 44 a
Pz 35R 731(f) n/a CS 2 n/a 44.1† a
Marder I n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 45† a
GSW 39H(f) Pak n/a CS 3 A4 HE9 45.1 a e
Marder II n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 46 M† N
Marder III(t)H n/a CS 4 <small><i>(brew up)</i></small> A6 HE7 s7 sD6 47 E G N
Marder III(t)M n/a CS 4 <small><i>(brew up)</i></small> A5 HE7 s7 sD5 48 E
PzJg III/IV n/a CS 5 HE7 49 M†
JgdPz 38(t) n/a CS 4 A4 HE7 50 E O†
JgdPz 38(t) (Fl) n/a CS 4 n/a 51 E O†
JgdPz IV n/a CS 4 A4 HE7 s7 sN7 52 S
JgdPz IV (L) n/a CS 4 A4 HE7 s7 sN8 52 S
StuIG 33B n/a CS 5 H7 s9 53† n/a
JgdPz IV/70 n/a CS 4 HE7 sN9 54† S
JgdPz V n/a CS 6 HE7 sN9 55 H
JgdPz VI n/a CS 7 HE7 sN9 56 n/a
SPW 250/1 n/a CS 4 n/a 57 N
SPW 250/sMG n/a CS 4† n/a 58† J† N
SPW 250/7 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 J† N
SPW 250/8 n/a CS 4 <small><i>(brew up)</i></small> H6 s8 60 Q†
SPW 250/9 n/a CS 4 n/a 61† A†
SPW 250/10 n/a CS 4 A4 62 J† N
SPW 251/1 n/a CS 5 n/a 63 C† N
SPW 251/sMG n/a CS 5† n/a 58† C† J† N
SPW 251/2 n/a CS 4 <small><i>(brew up)</i></small> IR s8 59 C† J† N
SPW 251/9 n/a CS 4 <small><i>(brew up)</i></small> H6 s9 64 C† N Q†
SPW 251/10 n/a CS 4 A4 65 C† J† N
SPW 251/16 n/a CS 4 <small><i>(brew up)</i></small> n/a 66† n/a
SPW 251/21 n/a CS 4 n/a 66.1† n/a
SPW 251/22 n/a CS 4 <small><i>(brew up)</i></small> HE7 s7 67 J†
SPW S307(f) n/a CS 4 A4 HE9 67.1 a e
mSPW S307(f) n/a CS 4 IR s5 67.2 a e
Kfz 13 n/a CS 2 n/a 68† n/a
PSW 221 n/a CS 2 sD6 69† A† N
PSW 222 n/a CS 3 sD6 70† A† N
PSW 222 (L) n/a CS 3 sD6 70† A† N
PSW 231 (6 rad) n/a CS 4 n/a 71† n/a
PSW 231 (8 rad) n/a CS 4 sD6 72† N
PSW 232 (8 rad) n/a CS 4 sD6 72 N
PSW 233 n/a CS 5 H8 s9 sD7 73 N
PSW 234/1 n/a CS 5 n/a 74 A†
PSW 234/2 n/a CS 4 A5 sD7 75 n/a
PSW 234/3 n/a CS 5 H8 s9 76 Q†
PSW 234/4 n/a CS 5 <small><i>(brew up)</i></small> HE7 s7 77† n/a
sIG IB n/a CS 4 <small><i>(brew up)</i></small> H6 s9 78 G
sIG II n/a CS 4 <small><i>(brew up)</i></small> H6 s9 79 N
sIG 38(t)M n/a CS 4 <small><i>(brew up)</i></small> H6 s9 80 E N
PzA II n/a CS 4 H5 s8 81 M†
PzA LrS(f) n/a CS 4 H4 s8 82 a N
GSW 39H(f) n/a CS 4 H4 s7 82.1 a e
PzA III/IV n/a CS 6 H5 s8 83 n/a
FlaKPz 38(t) n/a CS 4 n/a 84 A† E
37 FlaK/Pz IV n/a CS 5 n/a 85† A†
(Moebelwagen) n/a CS 6 n/a 85† A†
FlaKPz IV/20 n/a CS 6 n/a 86 A†
FlaKPz IV/37 n/a CS 6 n/a 87 A†
SdKfz 10/4 n/a CS 5 n/a 88 A† D† N
SdKfz 10/5 n/a CS 4 n/a 88.1† A†<sup>2</sup> D†<sup>1</sup>
SdKfz 6/2 n/a CS 5 n/a 89 A† D† N
SdKfz 7/1 n/a CS 4 n/a 90 A† D† N
Kfz 4 n/a CS 3 n/a 91† N
2cm FlaK LKW n/a CS 5 n/a 92 A†
3.7cm FlaK LKW n/a CS 5 n/a 92 A†
Goliath n/a n/a n/a 93† L†
Kfz 1 n/a cs 2† n/a 94† L† N
Kfz 1/20 n/a cs 2 n/a 95† L†
Opel 6700 (Blitz) n/a cs 6 n/a 96 N
Buessing-NAG 4500 n/a cs 7 n/a 96 N
SdKfz 2 n/a cs 2 n/a 97† L† N
SdKfz 7 n/a cs 6 n/a 98 N
SdKfz 11 n/a cs 5 n/a 99 N

@ -0,0 +1,89 @@
=== vehicles/russian/1940 ===
Name Radio Crew survival Capabilities # Notes
----------------- ------- ------------------------------------ -------------- ----- ---------------------
T-37 ® CS 2 n/a 1 H† M
T-27 ® CS 2 n/a 1.1† M Q
T-40 ® CS 2 n/a 2† H†
T-50 ® CS 4 n/a 3 n/a
T-60 M40 ® CS 2 n/a 4 n/a
T-60 M42 ®† CS 2 n/a 4 C†
T-70 ®† CS 2 n/a 5 C†
T-26 M33 ® CS 3 n/a 6 D I† M P
T-26 M37/39 ® CS 3 n/a 6 D M P
T-26 M31 ® CS 3 n/a 6.1† M P
T-26 M32 ® CS 3 n/a 6.2† M P
OT-26 ® CS 3 <small><i>(brew up)</i></small> n/a 6.3† M P
ST-26 Bridgelayer n/a CS 3 n/a 6.4† M
OT-133 ® CS 3 n/a 7† M P
BT-2A ® CS 3 n/a 7.1† M
BT-5 M34 ® CS 3 n/a 8† M P
BT-7 M37 ® CS 3 n/a 9 D I† M P
BT-7A n/a CS 3 AP7 s9 10 M P
T-28 M34 ® CS 5 s8 11† M
T-28 M34(L) ® CS 5 s8 11.1† M
T-28E M40 ® CS 6 s8 12† M
T-28E M40(L) ® CS 6 s8 12.1† M
T-34 M40 ® CS 5 n/a 13 M
T-34 M41 ®† CS 5 n/a 14† C† M
OT-34 ®† CS 4 AP7 15† C† M
T-34 M43 n/a CS 5 n/a 16† n/a
T-43 ®† CS 5 n/a 17 C† M
T-34/85 n/a CS 6 sD6† 18† J†
M4/76(a) n/a CS 6 s8 sM8 19† N
T-44 n/a CS 5 n/a 20 B† M
T-35 ® CS 8 s9 21† M
SMK n/a CS 7 n/a 21.1 M T†
T-100 n/a CS 7 n/a 22.1 M T†
KV-1 M39/40 n/a CS 6 n/a 22† M
KV-1E n/a CS 6 n/a 23 D M
KV-1 M41 n/a CS 6 n/a 23 D M
KV-1 M42 n/a CS 6 n/a 23 D M
KV-2 n/a CS 7 AP9 24† M
KV-8 n/a CS 6 AP7 15† D M
KV-1S n/a CS 6 n/a 25† n/a
KV-85 n/a CS 5 n/a 26 B†
IS-2 n/a CS 6 n/a 27 B†
IS-2m n/a CS 6 n/a 28 B† G†
IS-3 n/a CS 6 sD6† 29 G† J†
SU-76M n/a CS 4 <small><i>(brew up)</i></small> n/a 30† n/a
SU-76i(g) n/a CS 5 n/a 31 K
SU-122 n/a CS 5 H9 s8 32 n/a
SU-152 n/a CS 6 n/a 33† n/a
ISU-122 n/a CS 6 s8 34 G†
ISU-152 n/a CS 6 n/a 34 G†
SU-85 n/a CS 5 HE7 s7 35 n/a
SU-100 n/a CS 5 HE7 36 n/a
SU-57(a) n/a CS 4 HE7 37 K
BA-20 ® CS 3 n/a 38 n/a
BA-6 ® CS 4 n/a 39 n/a
BA-64B n/a CS 2 n/a 40† A†
ZSU-37 n/a CS 4 n/a 41† A†
SU-12 n/a CS 4 AP7 s8 42 F†
GAZ-4M-AA n/a CS 4 n/a 43† A†
ZIS-42-AA n/a CS 4 n/a 44 A† F†
LANO AT ® CS 5 n/a 44.1 n/a
LANO AA ® CS 5 n/a 44.2† A†<sup>1</sup>
IAG-10-AA n/a CS 4 AP7 45 A† F†
GAZ-67B n/a CS 2 n/a 46 L†
Komsomolet n/a CS 2† n/a 46.1† n/a
STZ-3 n/a cs 4 n/a 46.2 n/a
STZ-5 n/a cs 7 n/a 46.3 n/a
GAZ-MM n/a cs 6 n/a 47 n/a
ZIS-5 n/a cs 7 n/a 47 n/a
IAG-6 n/a cs 7 n/a 47 n/a
NKL-6 n/a CS 2† n/a 47.1† S†<sup>1</sup>
NKL-16 n/a cs 2 n/a 47.1† S†<sup>1</sup>
NKL-26 n/a CS 2 n/a 47.2† Q S†<sup>1</sup>
RF-8-GAZ-98 n/a CS 3 n/a 47.3† S†<sup>1</sup>
Stuart III(a) n/a CS 4 C6 48 N O LL
Lee(a) n/a CS 7 C4† s8† 49† B†<sup>1</sup> N O LL
Sherman III(a) n/a CS 5 <small><i>(brew up)</i></small> s8 50† N O R†<sup>1</sup> LL
Sherman III(L)(a) n/a CS 6 <small><i>(brew up)</i></small> WP7 s5 sM8 50.1 N O R† LL
Matilda II(b) n/a CS 5 sD6 51† M†<sup>1</sup> N LL
Valentine V(b) n/a CS 4 sM8 52.1 N LL
Valentine VIII(b) n/a CS 4 HE7 sD6 52.2 N LL
Churchill III(b) n/a CS 7 sM8† 53† N LL
M3A1 Scout Car(a) n/a CS 4 n/a 54† N LL
Jeep GPA(a) n/a cs 2 n/a 59 H† L†<sup>1</sup>
DUKW(a) n/a cs 7 n/a 60† n/a

@ -0,0 +1,89 @@
=== vehicles/russian/1941 ===
Name Radio Crew survival Capabilities # Notes
----------------- ------- ------------------------------------ -------------- ----- ---------------------
T-37 ® CS 2 n/a 1 H† M
T-27 ® CS 2 n/a 1.1† M Q
T-40 ® CS 2 n/a 2† H†
T-50 ® CS 4 n/a 3 n/a
T-60 M40 ® CS 2 n/a 4 n/a
T-60 M42 ®† CS 2 n/a 4 C†
T-70 ®† CS 2 n/a 5 C†
T-26 M33 ® CS 3 n/a 6 D I† M P
T-26 M37/39 ® CS 3 n/a 6 D M P
T-26 M31 ® CS 3 n/a 6.1† M P
T-26 M32 ® CS 3 n/a 6.2† M P
OT-26 ® CS 3 <small><i>(brew up)</i></small> n/a 6.3† M P
ST-26 Bridgelayer n/a CS 3 n/a 6.4† M
OT-133 ® CS 3 n/a 7† M P
BT-2A ® CS 3 n/a 7.1† M
BT-5 M34 ® CS 3 n/a 8† M P
BT-7 M37 ® CS 3 n/a 9 D I† M P
BT-7A n/a CS 3 AP7 s9 10 M P
T-28 M34 ® CS 5 s8 11† M
T-28 M34(L) ® CS 5 s8 11.1† M
T-28E M40 ® CS 6 s8 12† M
T-28E M40(L) ® CS 6 s8 12.1† M
T-34 M40 ® CS 5 n/a 13 M
T-34 M41 ®† CS 5 n/a 14† C† M
OT-34 ®† CS 4 AP7 15† C† M
T-34 M43 n/a CS 5 n/a 16† n/a
T-43 ®† CS 5 n/a 17 C† M
T-34/85 n/a CS 6 sD6† 18† J†
M4/76(a) n/a CS 6 s8 sM8 19† N
T-44 n/a CS 5 n/a 20 B† M
T-35 ® CS 8 s9 21† M
SMK n/a CS 7 n/a 21.1 M T†
T-100 n/a CS 7 n/a 22.1 M T†
KV-1 M39/40 n/a CS 6 n/a 22† M
KV-1E n/a CS 6 n/a 23 D M
KV-1 M41 n/a CS 6 n/a 23 D M
KV-1 M42 n/a CS 6 n/a 23 D M
KV-2 n/a CS 7 AP9 24† M
KV-8 n/a CS 6 AP7 15† D M
KV-1S n/a CS 6 n/a 25† n/a
KV-85 n/a CS 5 n/a 26 B†
IS-2 n/a CS 6 n/a 27 B†
IS-2m n/a CS 6 n/a 28 B† G†
IS-3 n/a CS 6 sD6† 29 G† J†
SU-76M n/a CS 4 <small><i>(brew up)</i></small> n/a 30† n/a
SU-76i(g) n/a CS 5 n/a 31 K
SU-122 n/a CS 5 H9 s8 32 n/a
SU-152 n/a CS 6 n/a 33† n/a
ISU-122 n/a CS 6 s8 34 G†
ISU-152 n/a CS 6 n/a 34 G†
SU-85 n/a CS 5 HE7 s7 35 n/a
SU-100 n/a CS 5 HE7 36 n/a
SU-57(a) n/a CS 4 HE7 37 K
BA-20 ® CS 3 n/a 38 n/a
BA-6 ® CS 4 n/a 39 n/a
BA-64B n/a CS 2 n/a 40† A†
ZSU-37 n/a CS 4 n/a 41† A†
SU-12 n/a CS 4 AP7 s8 42 F†
GAZ-4M-AA n/a CS 4 n/a 43† A†
ZIS-42-AA n/a CS 4 n/a 44 A† F†
LANO AT ® CS 5 n/a 44.1 n/a
LANO AA ® CS 5 n/a 44.2† A†<sup>1</sup>
IAG-10-AA n/a CS 4 AP7 45 A† F†
GAZ-67B n/a CS 2 n/a 46 L†
Komsomolet n/a CS 2† n/a 46.1† n/a
STZ-3 n/a cs 4 n/a 46.2 n/a
STZ-5 n/a cs 7 n/a 46.3 n/a
GAZ-MM n/a cs 6 n/a 47 n/a
ZIS-5 n/a cs 7 n/a 47 n/a
IAG-6 n/a cs 7 n/a 47 n/a
NKL-6 n/a CS 2† n/a 47.1† S†<sup>1</sup>
NKL-16 n/a cs 2 n/a 47.1† S†<sup>1</sup>
NKL-26 n/a CS 2 n/a 47.2† Q S†<sup>1</sup>
RF-8-GAZ-98 n/a CS 3 n/a 47.3† S†<sup>1</sup>
Stuart III(a) n/a CS 4 C6 48 N O LL
Lee(a) n/a CS 7 C4† s8† 49† B†<sup>1</sup> N O LL
Sherman III(a) n/a CS 5 <small><i>(brew up)</i></small> s8 50† N O R†<sup>1</sup> LL
Sherman III(L)(a) n/a CS 6 <small><i>(brew up)</i></small> WP7 s5 sM8 50.1 N O R† LL
Matilda II(b) n/a CS 5 sD6 51† M†<sup>1</sup> N LL
Valentine V(b) n/a CS 4 sM8 52.1 N LL
Valentine VIII(b) n/a CS 4 HE7 sD6 52.2 N LL
Churchill III(b) n/a CS 7 sM8† 53† N LL
M3A1 Scout Car(a) n/a CS 4 n/a 54† N LL
Jeep GPA(a) n/a cs 2 n/a 59 H† L†<sup>1</sup>
DUKW(a) n/a cs 7 n/a 60† n/a

@ -0,0 +1,89 @@
=== vehicles/russian/1942 ===
Name Radio Crew survival Capabilities # Notes
----------------- ------- ------------------------------------ -------------- ----- ---------------------
T-37 ® CS 2 n/a 1 H† M
T-27 ® CS 2 n/a 1.1† M Q
T-40 ® CS 2 n/a 2† H†
T-50 ® CS 4 A4 3 n/a
T-60 M40 ® CS 2 n/a 4 n/a
T-60 M42 ®† CS 2 n/a 4 C†
T-70 ®† CS 2 A4 5 C†
T-26 M33 ® CS 3 n/a 6 D I† M P
T-26 M37/39 ® CS 3 n/a 6 D M P
T-26 M31 ® CS 3 n/a 6.1† M P
T-26 M32 ® CS 3 n/a 6.2† M P
OT-26 ® CS 3 <small><i>(brew up)</i></small> n/a 6.3† M P
ST-26 Bridgelayer n/a CS 3 n/a 6.4† M
OT-133 ® CS 3 n/a 7† M P
BT-2A ® CS 3 n/a 7.1† M
BT-5 M34 ® CS 3 n/a 8† M P
BT-7 M37 ® CS 3 n/a 9 D I† M P
BT-7A n/a CS 3 AP7 s9 10 M P
T-28 M34 ® CS 5 s8 11† M
T-28 M34(L) ® CS 5 s8 11.1† M
T-28E M40 ® CS 6 s8 12† M
T-28E M40(L) ® CS 6 s8 12.1† M
T-34 M40 ® CS 5 n/a 13 M
T-34 M41 ®† CS 5 A4 14† C† M
OT-34 ®† CS 4 A2 AP7 15† C† M
T-34 M43 n/a CS 5 A4 16† n/a
T-43 ®† CS 5 A4 17 C† M
T-34/85 n/a CS 6 sD6† 18† J†
M4/76(a) n/a CS 6 s8 sM8 19† N
T-44 n/a CS 5 n/a 20 B† M
T-35 ® CS 8 s9 21† M
SMK n/a CS 7 n/a 21.1 M T†
T-100 n/a CS 7 n/a 22.1 M T†
KV-1 M39/40 n/a CS 6 n/a 22† M
KV-1E n/a CS 6 n/a 23 D M
KV-1 M41 n/a CS 6 A4 23 D M
KV-1 M42 n/a CS 6 A4 23 D M
KV-2 n/a CS 7 AP9 24† M
KV-8 n/a CS 6 A4 AP7 15† D M
KV-1S n/a CS 6 A4 25† n/a
KV-85 n/a CS 5 n/a 26 B†
IS-2 n/a CS 6 n/a 27 B†
IS-2m n/a CS 6 n/a 28 B† G†
IS-3 n/a CS 6 sD6† 29 G† J†
SU-76M n/a CS 4 <small><i>(brew up)</i></small> n/a 30† n/a
SU-76i(g) n/a CS 5 n/a 31 K
SU-122 n/a CS 5 H9 s8 32 n/a
SU-152 n/a CS 6 n/a 33† n/a
ISU-122 n/a CS 6 s8 34 G†
ISU-152 n/a CS 6 n/a 34 G†
SU-85 n/a CS 5 HE7 s7 35 n/a
SU-100 n/a CS 5 HE7 36 n/a
SU-57(a) n/a CS 4 HE7 37 K
BA-20 ® CS 3 n/a 38 n/a
BA-6 ® CS 4 A4 39 n/a
BA-64B n/a CS 2 n/a 40† A†
ZSU-37 n/a CS 4 n/a 41† A†
SU-12 n/a CS 4 AP7 s8 42 F†
GAZ-4M-AA n/a CS 4 n/a 43† A†
ZIS-42-AA n/a CS 4 n/a 44 A† F†
LANO AT ® CS 5 A4 44.1 n/a
LANO AA ® CS 5 n/a 44.2† A†<sup>1</sup>
IAG-10-AA n/a CS 4 AP7 45 A† F†
GAZ-67B n/a CS 2 n/a 46 L†
Komsomolet n/a CS 2† n/a 46.1† n/a
STZ-3 n/a cs 4 n/a 46.2 n/a
STZ-5 n/a cs 7 n/a 46.3 n/a
GAZ-MM n/a cs 6 n/a 47 n/a
ZIS-5 n/a cs 7 n/a 47 n/a
IAG-6 n/a cs 7 n/a 47 n/a
NKL-6 n/a CS 2† n/a 47.1† S†<sup>1</sup>
NKL-16 n/a cs 2 n/a 47.1† S†<sup>1</sup>
NKL-26 n/a CS 2 n/a 47.2† Q S†<sup>1</sup>
RF-8-GAZ-98 n/a CS 3 n/a 47.3† S†<sup>1</sup>
Stuart III(a) n/a CS 4 C6 48 N O LL
Lee(a) n/a CS 7 C4† s8† 49† B†<sup>1</sup> N O LL
Sherman III(a) n/a CS 5 <small><i>(brew up)</i></small> s8 50† N O R†<sup>1</sup> LL
Sherman III(L)(a) n/a CS 6 <small><i>(brew up)</i></small> WP7 s5 sM8 50.1 N O R† LL
Matilda II(b) n/a CS 5 sD6 51† M†<sup>1</sup> N LL
Valentine V(b) n/a CS 4 sM8 52.1 N LL
Valentine VIII(b) n/a CS 4 HE7 sD6 52.2 N LL
Churchill III(b) n/a CS 7 sM8† 53† N LL
M3A1 Scout Car(a) n/a CS 4 n/a 54† N LL
Jeep GPA(a) n/a cs 2 n/a 59 H† L†<sup>1</sup>
DUKW(a) n/a cs 7 n/a 60† n/a

@ -0,0 +1,89 @@
=== vehicles/russian/1943 ===
Name Radio Crew survival Capabilities # Notes
----------------- ------- ------------------------------------ -------------- ----- ---------------------
T-37 ® CS 2 n/a 1 H† M
T-27 ® CS 2 n/a 1.1† M Q
T-40 ® CS 2 n/a 2† H†
T-50 ® CS 4 A4 3 n/a
T-60 M40 ® CS 2 n/a 4 n/a
T-60 M42 ®† CS 2 n/a 4 C†
T-70 ®† CS 2 A4 5 C†
T-26 M33 ® CS 3 n/a 6 D I† M P
T-26 M37/39 ® CS 3 n/a 6 D M P
T-26 M31 ® CS 3 n/a 6.1† M P
T-26 M32 ® CS 3 n/a 6.2† M P
OT-26 ® CS 3 <small><i>(brew up)</i></small> n/a 6.3† M P
ST-26 Bridgelayer n/a CS 3 n/a 6.4† M
OT-133 ® CS 3 n/a 7† M P
BT-2A ® CS 3 n/a 7.1† M
BT-5 M34 ® CS 3 n/a 8† M P
BT-7 M37 ® CS 3 n/a 9 D I† M P
BT-7A n/a CS 3 AP7 s9 10 M P
T-28 M34 ® CS 5 s8 11† M
T-28 M34(L) ® CS 5 s8 11.1† M
T-28E M40 ® CS 6 s8 12† M
T-28E M40(L) ® CS 6 s8 12.1† M
T-34 M40 ® CS 5 n/a 13 M
T-34 M41 ®† CS 5 A4 14† C† M
OT-34 ®† CS 4 A2 AP7 15† C† M
T-34 M43 n/a CS 5 A4 16† n/a
T-43 ®† CS 5 A4 17 C† M
T-34/85 n/a CS 6 sD6† 18† J†
M4/76(a) n/a CS 6 s8 sM8 19† N
T-44 n/a CS 5 n/a 20 B† M
T-35 ® CS 8 s9 21† M
SMK n/a CS 7 n/a 21.1 M T†
T-100 n/a CS 7 n/a 22.1 M T†
KV-1 M39/40 n/a CS 6 n/a 22† M
KV-1E n/a CS 6 n/a 23 D M
KV-1 M41 n/a CS 6 A4 23 D M
KV-1 M42 n/a CS 6 A4 23 D M
KV-2 n/a CS 7 AP9 24† M
KV-8 n/a CS 6 A4 AP7 15† D M
KV-1S n/a CS 6 A4 25† n/a
KV-85 n/a CS 5 A4 26 B†
IS-2 n/a CS 6 n/a 27 B†
IS-2m n/a CS 6 n/a 28 B† G†
IS-3 n/a CS 6 sD6† 29 G† J†
SU-76M n/a CS 4 <small><i>(brew up)</i></small> A5 30† n/a
SU-76i(g) n/a CS 5 A5 31 K
SU-122 n/a CS 5 H9 s8 32 n/a
SU-152 n/a CS 6 n/a 33† n/a
ISU-122 n/a CS 6 s8 34 G†
ISU-152 n/a CS 6 n/a 34 G†
SU-85 n/a CS 5 A5 HE7 s7 35 n/a
SU-100 n/a CS 5 HE7 36 n/a
SU-57(a) n/a CS 4 A5 HE7 37 K
BA-20 ® CS 3 n/a 38 n/a
BA-6 ® CS 4 A4 39 n/a
BA-64B n/a CS 2 n/a 40† A†
ZSU-37 n/a CS 4 n/a 41† A†
SU-12 n/a CS 4 AP7 s8 42 F†
GAZ-4M-AA n/a CS 4 n/a 43† A†
ZIS-42-AA n/a CS 4 n/a 44 A† F†
LANO AT ® CS 5 A4 44.1 n/a
LANO AA ® CS 5 n/a 44.2† A†<sup>1</sup>
IAG-10-AA n/a CS 4 AP7 45 A† F†
GAZ-67B n/a CS 2 n/a 46 L†
Komsomolet n/a CS 2† n/a 46.1† n/a
STZ-3 n/a cs 4 n/a 46.2 n/a
STZ-5 n/a cs 7 n/a 46.3 n/a
GAZ-MM n/a cs 6 n/a 47 n/a
ZIS-5 n/a cs 7 n/a 47 n/a
IAG-6 n/a cs 7 n/a 47 n/a
NKL-6 n/a CS 2† n/a 47.1† S†<sup>1</sup>
NKL-16 n/a cs 2 n/a 47.1† S†<sup>1</sup>
NKL-26 n/a CS 2 n/a 47.2† Q S†<sup>1</sup>
RF-8-GAZ-98 n/a CS 3 n/a 47.3† S†<sup>1</sup>
Stuart III(a) n/a CS 4 C6 48 N O LL
Lee(a) n/a CS 7 C4† s8† 49† B†<sup>1</sup> N O LL
Sherman III(a) n/a CS 5 <small><i>(brew up)</i></small> s8 50† N O R†<sup>1</sup> LL
Sherman III(L)(a) n/a CS 6 <small><i>(brew up)</i></small> WP7 s5 sM8 50.1 N O R† LL
Matilda II(b) n/a CS 5 sD6 51† M†<sup>1</sup> N LL
Valentine V(b) n/a CS 4 sM8 52.1 N LL
Valentine VIII(b) n/a CS 4 HE7 sD6 52.2 N LL
Churchill III(b) n/a CS 7 sM8† 53† N LL
M3A1 Scout Car(a) n/a CS 4 n/a 54† N LL
Jeep GPA(a) n/a cs 2 n/a 59 H† L†<sup>1</sup>
DUKW(a) n/a cs 7 n/a 60† n/a

@ -0,0 +1,89 @@
=== vehicles/russian/1944 ===
Name Radio Crew survival Capabilities # Notes
----------------- ------- ------------------------------------ -------------- ----- ---------------------
T-37 ® CS 2 n/a 1 H† M
T-27 ® CS 2 n/a 1.1† M Q
T-40 ® CS 2 n/a 2† H†
T-50 ® CS 4 A4 3 n/a
T-60 M40 ® CS 2 n/a 4 n/a
T-60 M42 ®† CS 2 n/a 4 C†
T-70 ®† CS 2 A4 5 C†
T-26 M33 ® CS 3 n/a 6 D I† M P
T-26 M37/39 ® CS 3 n/a 6 D M P
T-26 M31 ® CS 3 n/a 6.1† M P
T-26 M32 ® CS 3 n/a 6.2† M P
OT-26 ® CS 3 <small><i>(brew up)</i></small> n/a 6.3† M P
ST-26 Bridgelayer n/a CS 3 n/a 6.4† M
OT-133 ® CS 3 n/a 7† M P
BT-2A ® CS 3 n/a 7.1† M
BT-5 M34 ® CS 3 n/a 8† M P
BT-7 M37 ® CS 3 n/a 9 D I† M P
BT-7A n/a CS 3 AP7 s9 10 M P
T-28 M34 ® CS 5 s8 11† M
T-28 M34(L) ® CS 5 s8 11.1† M
T-28E M40 ® CS 6 s8 12† M
T-28E M40(L) ® CS 6 s8 12.1† M
T-34 M40 ® CS 5 n/a 13 M
T-34 M41 ®† CS 5 A4 14† C† M
OT-34 ®† CS 4 A2 AP7 15† C† M
T-34 M43 n/a CS 5 A4 16† n/a
T-43 ®† CS 5 A4 17 C† M
T-34/85 n/a CS 6 A5 sD6† 18† J†
M4/76(a) n/a CS 6 A3 s8 sM8 19† N
T-44 n/a CS 5 n/a 20 B† M
T-35 ® CS 8 s9 21† M
SMK n/a CS 7 n/a 21.1 M T†
T-100 n/a CS 7 n/a 22.1 M T†
KV-1 M39/40 n/a CS 6 n/a 22† M
KV-1E n/a CS 6 n/a 23 D M
KV-1 M41 n/a CS 6 A4 23 D M
KV-1 M42 n/a CS 6 A4 23 D M
KV-2 n/a CS 7 AP9 24† M
KV-8 n/a CS 6 A4 AP7 15† D M
KV-1S n/a CS 6 A4 25† n/a
KV-85 n/a CS 5 A5 26 B†
IS-2 n/a CS 6 n/a 27 B†
IS-2m n/a CS 6 n/a 28 B† G†
IS-3 n/a CS 6 sD6† 29 G† J†
SU-76M n/a CS 4 <small><i>(brew up)</i></small> A5 30† n/a
SU-76i(g) n/a CS 5 A5 31 K
SU-122 n/a CS 5 H9 s8 32 n/a
SU-152 n/a CS 6 n/a 33† n/a
ISU-122 n/a CS 6 s8 34 G†
ISU-152 n/a CS 6 n/a 34 G†
SU-85 n/a CS 5 A5 HE7 s7 35 n/a
SU-100 n/a CS 5 HE7 36 n/a
SU-57(a) n/a CS 4 A5 HE7 37 K
BA-20 ® CS 3 n/a 38 n/a
BA-6 ® CS 4 A4 39 n/a
BA-64B n/a CS 2 n/a 40† A†
ZSU-37 n/a CS 4 n/a 41† A†
SU-12 n/a CS 4 AP7 s8 42 F†
GAZ-4M-AA n/a CS 4 n/a 43† A†
ZIS-42-AA n/a CS 4 n/a 44 A† F†
LANO AT ® CS 5 A4 44.1 n/a
LANO AA ® CS 5 n/a 44.2† A†<sup>1</sup>
IAG-10-AA n/a CS 4 AP7 45 A† F†
GAZ-67B n/a CS 2 n/a 46 L†
Komsomolet n/a CS 2† n/a 46.1† n/a
STZ-3 n/a cs 4 n/a 46.2 n/a
STZ-5 n/a cs 7 n/a 46.3 n/a
GAZ-MM n/a cs 6 n/a 47 n/a
ZIS-5 n/a cs 7 n/a 47 n/a
IAG-6 n/a cs 7 n/a 47 n/a
NKL-6 n/a CS 2† n/a 47.1† S†<sup>1</sup>
NKL-16 n/a cs 2 n/a 47.1† S†<sup>1</sup>
NKL-26 n/a CS 2 n/a 47.2† Q S†<sup>1</sup>
RF-8-GAZ-98 n/a CS 3 n/a 47.3† S†<sup>1</sup>
Stuart III(a) n/a CS 4 C6 48 N O LL
Lee(a) n/a CS 7 C4† s8† 49† B†<sup>1</sup> N O LL
Sherman III(a) n/a CS 5 <small><i>(brew up)</i></small> s8 50† N O R†<sup>1</sup> LL
Sherman III(L)(a) n/a CS 6 <small><i>(brew up)</i></small> WP7 s5 sM8 50.1 N O R† LL
Matilda II(b) n/a CS 5 sD6 51† M†<sup>1</sup> N LL
Valentine V(b) n/a CS 4 sM8 52.1 N LL
Valentine VIII(b) n/a CS 4 HE7 sD6 52.2 N LL
Churchill III(b) n/a CS 7 HE8 sD6 sM8† 53† N LL
M3A1 Scout Car(a) n/a CS 4 n/a 54† N LL
Jeep GPA(a) n/a cs 2 n/a 59 H† L†<sup>1</sup>
DUKW(a) n/a cs 7 n/a 60† n/a

@ -0,0 +1,89 @@
=== vehicles/russian/1945 ===
Name Radio Crew survival Capabilities # Notes
----------------- ------- ------------------------------------ --------------- ----- ---------------------
T-37 ® CS 2 n/a 1 H† M
T-27 ® CS 2 n/a 1.1† M Q
T-40 ® CS 2 n/a 2† H†
T-50 ® CS 4 A4 3 n/a
T-60 M40 ® CS 2 n/a 4 n/a
T-60 M42 ®† CS 2 n/a 4 C†
T-70 ®† CS 2 A4 5 C†
T-26 M33 ® CS 3 n/a 6 D I† M P
T-26 M37/39 ® CS 3 n/a 6 D M P
T-26 M31 ® CS 3 n/a 6.1† M P
T-26 M32 ® CS 3 n/a 6.2† M P
OT-26 ® CS 3 <small><i>(brew up)</i></small> n/a 6.3† M P
ST-26 Bridgelayer n/a CS 3 n/a 6.4† M
OT-133 ® CS 3 n/a 7† M P
BT-2A ® CS 3 n/a 7.1† M
BT-5 M34 ® CS 3 n/a 8† M P
BT-7 M37 ® CS 3 n/a 9 D I† M P
BT-7A n/a CS 3 AP7 s9 10 M P
T-28 M34 ® CS 5 s8 11† M
T-28 M34(L) ® CS 5 s8 11.1† M
T-28E M40 ® CS 6 s8 12† M
T-28E M40(L) ® CS 6 s8 12.1† M
T-34 M40 ® CS 5 n/a 13 M
T-34 M41 ®† CS 5 A4 14† C† M
OT-34 ®† CS 4 A2 AP7 15† C† M
T-34 M43 n/a CS 5 A4 16† n/a
T-43 ®† CS 5 A4 17 C† M
T-34/85 n/a CS 6 A5 sD6† 18† J†
M4/76(a) n/a CS 6 A3 s8 sM8 19† N
T-44 n/a CS 5 A6 20 B† M
T-35 ® CS 8 s9 21† M
SMK n/a CS 7 n/a 21.1 M T†
T-100 n/a CS 7 n/a 22.1 M T†
KV-1 M39/40 n/a CS 6 n/a 22† M
KV-1E n/a CS 6 n/a 23 D M
KV-1 M41 n/a CS 6 A4 23 D M
KV-1 M42 n/a CS 6 A4 23 D M
KV-2 n/a CS 7 AP9 24† M
KV-8 n/a CS 6 A4 AP7 15† D M
KV-1S n/a CS 6 A4 25† n/a
KV-85 n/a CS 5 A5 26 B†
IS-2 n/a CS 6 n/a 27 B†
IS-2m n/a CS 6 n/a 28 B† G†
IS-3 n/a CS 6 sD6† 29 G† J†
SU-76M n/a CS 4 <small><i>(brew up)</i></small> A5 30† n/a
SU-76i(g) n/a CS 5 A5 31 K
SU-122 n/a CS 5 H9 s8 32 n/a
SU-152 n/a CS 6 n/a 33† n/a
ISU-122 n/a CS 6 s8 34 G†
ISU-152 n/a CS 6 n/a 34 G†
SU-85 n/a CS 5 A5 HE7 s7 35 n/a
SU-100 n/a CS 5 HE7 36 n/a
SU-57(a) n/a CS 4 A5 HE7 37 K
BA-20 ® CS 3 n/a 38 n/a
BA-6 ® CS 4 A4 39 n/a
BA-64B n/a CS 2 n/a 40† A†
ZSU-37 n/a CS 4 n/a 41† A†
SU-12 n/a CS 4 AP7 s8 42 F†
GAZ-4M-AA n/a CS 4 n/a 43† A†
ZIS-42-AA n/a CS 4 n/a 44 A† F†
LANO AT ® CS 5 A4 44.1 n/a
LANO AA ® CS 5 n/a 44.2† A†<sup>1</sup>
IAG-10-AA n/a CS 4 AP7 45 A† F†
GAZ-67B n/a CS 2 n/a 46 L†
Komsomolet n/a CS 2† n/a 46.1† n/a
STZ-3 n/a cs 4 n/a 46.2 n/a
STZ-5 n/a cs 7 n/a 46.3 n/a
GAZ-MM n/a cs 6 n/a 47 n/a
ZIS-5 n/a cs 7 n/a 47 n/a
IAG-6 n/a cs 7 n/a 47 n/a
NKL-6 n/a CS 2† n/a 47.1† S†<sup>1</sup>
NKL-16 n/a cs 2 n/a 47.1† S†<sup>1</sup>
NKL-26 n/a CS 2 n/a 47.2† Q S†<sup>1</sup>
RF-8-GAZ-98 n/a CS 3 n/a 47.3† S†<sup>1</sup>
Stuart III(a) n/a CS 4 C6 48 N O LL
Lee(a) n/a CS 7 C4† s8† 49† B†<sup>1</sup> N O LL
Sherman III(a) n/a CS 5 <small><i>(brew up)</i></small> WP6 s8 50† N O R†<sup>1</sup> LL
Sherman III(L)(a) n/a CS 6 <small><i>(brew up)</i></small> WP7 s5 sM8 50.1 N O R† LL
Matilda II(b) n/a CS 5 sD6 51† M†<sup>1</sup> N LL
Valentine V(b) n/a CS 4 sM8 52.1 N LL
Valentine VIII(b) n/a CS 4 HE7 sD6 52.2 N LL
Churchill III(b) n/a CS 7 D7 HE8 sD6 sM8† 53† N LL
M3A1 Scout Car(a) n/a CS 4 n/a 54† N LL
Jeep GPA(a) n/a cs 2 n/a 59 H† L†<sup>1</sup>
DUKW(a) n/a cs 7 n/a 60† n/a

@ -26,10 +26,10 @@ def test_ob_setup( webapp, webdriver ):
textarea2.send_keys( "setup <b>there</b>." )
btn2 = find_child( "input[type='button'][data-id='ob_setup_2']" )
btn2.click()
assert get_clipboard() == "[setup <b>there</b>.] (col=[OBCOL:russian/OBCOL2:russian])"
assert get_clipboard() == "[Russian] [setup <b>there</b>.] (col=[OBCOL:russian/OBCOL2:russian])"
select_tab( "ob1" )
btn1.click()
assert get_clipboard() == "[setup <i>here</i>.] (col=[OBCOL:german/OBCOL2:german])"
assert get_clipboard() == "[German] [setup <i>here</i>.] (col=[OBCOL:german/OBCOL2:german])"
# change the player nationalities and generate the OB SETUP snippets again
select_tab( "scenario" )
@ -43,22 +43,22 @@ def test_ob_setup( webapp, webdriver ):
sel.select_by_value( "french" )
select_tab( "ob1" )
btn1.click()
assert get_clipboard() == "[setup <i>here</i>.] (col=[OBCOL:british/OBCOL2:british])"
assert get_clipboard() == "[British] [] (col=[OBCOL:british/OBCOL2:british])"
select_tab( "ob2" )
btn2.click()
assert get_clipboard() == "[setup <b>there</b>.] (col=[OBCOL:french/OBCOL2:french])"
assert get_clipboard() == "[French] [] (col=[OBCOL:french/OBCOL2:french])"
# set the snippet widths and generate the snippets again
select_tab( "ob1" )
elem = find_child( "input[name='OB_SETUP_WIDTH_1']" )
elem.send_keys( "100px" )
btn1.click()
assert get_clipboard() == "[setup <i>here</i>.] (col=[OBCOL:british/OBCOL2:british]) (width=[100px])"
assert get_clipboard() == "[British] [] (col=[OBCOL:british/OBCOL2:british]) (width=[100px])"
select_tab( "ob2" )
elem = find_child( "input[name='OB_SETUP_WIDTH_2']" )
elem.send_keys( "200px" )
btn2.click()
assert get_clipboard() == "[setup <b>there</b>.] (col=[OBCOL:french/OBCOL2:french]) (width=[200px])"
assert get_clipboard() == "[French] [] (col=[OBCOL:french/OBCOL2:french]) (width=[200px])"
# ---------------------------------------------------------------------

@ -27,17 +27,25 @@ def test_scenario_persistence( webapp, webdriver ):
"scenario": {
"SCENARIO_NAME": "my test scenario", "SCENARIO_LOCATION": "right here", "SCENARIO_DATE": "12/31/1945",
"SCENARIO_WIDTH": "101",
"PLAYER_1": "british", "PLAYER_1_ELR": "1", "PLAYER_1_SAN": "2",
"PLAYER_2": "french", "PLAYER_2_ELR": "3", "PLAYER_2_SAN": "4",
"PLAYER_1": "russian", "PLAYER_1_ELR": "1", "PLAYER_1_SAN": "2",
"PLAYER_2": "german", "PLAYER_2_ELR": "3", "PLAYER_2_SAN": "4",
"VICTORY_CONDITIONS": "just do it!", "VICTORY_CONDITIONS_WIDTH": "102",
"SSR": [ "This is an SSR.", "This is another SSR." ],
"SSR_WIDTH": "103",
},
"ob1": {
"OB_SETUP_1": "Player 1's OB", "OB_SETUP_WIDTH_1": "201",
"VEHICLES_1": [ "a russian vehicle", "another russian vehicle" ],
"VEHICLES_WIDTH_1": "202",
"ORDNANCE_1": [ "a russian ordnance", "another russian ordnance" ],
"ORDNANCE_WIDTH_1": "203",
},
"ob2": {
"OB_SETUP_2": "Player 2's OB", "OB_SETUP_WIDTH_2": "301",
"VEHICLES_2": [ "a german vehicle" ],
"VEHICLES_WIDTH_2": "302",
"ORDNANCE_2": [ "a german ordnance" ],
"ORDNANCE_WIDTH_2": "303",
},
}
load_scenario_fields( scenario_params )
@ -69,6 +77,8 @@ def test_scenario_persistence( webapp, webdriver ):
for field,val in scenario_params[tab_id].items():
if field == "SSR":
continue # nb: this requires special handling, we do it below
if field in ("VEHICLES_1","ORDNANCE_1","VEHICLES_2","ORDNANCE_2"):
continue # nb: this requires special handling, we do it below
elem = next( c for c in ( \
find_child( "{}[name='{}']".format(elem_type,field) ) \
for elem_type in ["input","textarea","select"]
@ -79,6 +89,10 @@ def test_scenario_persistence( webapp, webdriver ):
assert elem.get_attribute("value") == val
ssrs = _get_ssrs()
assert ssrs == scenario_params["scenario"]["SSR"]
assert _get_vo("vehicle",1) == scenario_params["ob1"]["VEHICLES_1"]
assert _get_vo("ordnance",1) == scenario_params["ob1"]["ORDNANCE_1"]
assert _get_vo("vehicle",2) == scenario_params["ob2"]["VEHICLES_2"]
assert _get_vo("ordnance",2) == scenario_params["ob2"]["ORDNANCE_2"]
# ---------------------------------------------------------------------
@ -108,6 +122,28 @@ def test_loading_ssrs( webapp, webdriver ):
# ---------------------------------------------------------------------
def test_unknown_vo( webapp, webdriver ):
"""Test detection of unknown vehicles/ordnance."""
# initialize
webdriver.get( webapp.url_for( "main", scenario_persistence=1, store_msgs=1 ) )
_ = _save_scenario() # nb: force the "scenario-persistence" element to be created
# load a scenario that has unknown vehicles/ordnance
scenario_params = {
"VEHICLES_1": [ "unknown vehicle 1a", "unknown vehicle 1b" ],
"ORDNANCE_1": [ "unknown ordnance 1a", "unknown ordnance 1b" ],
"VEHICLES_2": [ "unknown vehicle 2" ],
"ORDNANCE_2": [ "unknown ordnance 2" ],
}
_load_scenario( scenario_params )
last_warning = get_stored_msg( "_last-warning_" )
assert last_warning.startswith( "Unknown vehicles/ordnance:" )
for vals in scenario_params.values():
assert all( v in last_warning for v in vals )
# ---------------------------------------------------------------------
def _load_scenario( scenario ):
"""Load a scenario into the UI."""
set_stored_msg( "scenario_persistence", json.dumps(scenario) )
@ -123,3 +159,11 @@ def _get_ssrs():
"""Get the SSR's from the UI."""
select_tab( "scenario" )
return [ c.text for c in find_children("#ssr-sortable li") ]
def _get_vo( vo_type, player_id ):
"""Get the vehicles/ordnance from the UI."""
select_tab( "ob{}".format( player_id ) )
return [
c.text
for c in find_children( "#{}-sortable_{} li".format( vo_type, player_id ) )
]

@ -4,7 +4,8 @@ import html
from selenium.webdriver.common.action_chains import ActionChains
from vasl_templates.webapp.tests.utils import get_clipboard, dismiss_notifications, find_child, find_children
from vasl_templates.webapp.tests.utils import find_child, find_children
from vasl_templates.webapp.tests.utils import get_clipboard, dismiss_notifications, click_dialog_button
# ---------------------------------------------------------------------
@ -83,8 +84,4 @@ def edit_ssr( webdriver, ssr_no, val ):
textarea = find_child( "#edit-ssr textarea" )
textarea.clear()
textarea.send_keys( val )
btn = next(
elem for elem in find_children(".ui-dialog.edit-ssr button")
if elem.text == "OK"
)
btn.click()
click_dialog_button( "OK" )

@ -7,6 +7,7 @@ import base64
from selenium.webdriver.support.ui import Select
from vasl_templates.webapp import snippets
from vasl_templates.webapp.tests.utils import select_menu_option, get_clipboard
from vasl_templates.webapp.tests.utils import get_stored_msg, set_stored_msg, dismiss_notifications, find_child
from vasl_templates.webapp.tests.utils import for_each_template
@ -88,13 +89,12 @@ def test_zip_files( webapp, webdriver ):
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def test_new_default_template_pack( webapp, webdriver ):
def test_new_default_template_pack( webapp, webdriver, monkeypatch ):
"""Test changing the default template pack."""
# configure a new default template pack
dname = os.path.join( os.path.split(__file__)[0], "fixtures/template-packs/new-default/" )
from vasl_templates.webapp import snippets
snippets.default_template_pack = dname
monkeypatch.setattr( snippets, "default_template_pack", dname )
# initialize
webdriver.get( webapp.url_for( "main" ) )

@ -0,0 +1,242 @@
""" Test generating vehicle/ordnance snippets. """
import re
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.action_chains import ActionChains
from vasl_templates.webapp.tests.utils import select_tab, set_template_params, find_child, find_children
from vasl_templates.webapp.tests.utils import get_clipboard, click_dialog_button, dismiss_notifications
# ---------------------------------------------------------------------
def test_crud( webapp, webdriver ):
"""Test basic create/read/update/delete of vehicles/ordnance."""
# initialize
webdriver.get( webapp.url_for( "main" ) )
# initialize
_expected = {
("vehicle",1): [], ("ordnance",1): [],
("vehicle",2): [], ("ordnance",2): []
}
_width = {
("vehicle",1): None, ("ordnance",1): None,
("vehicle",2): None, ("ordnance",2): None
}
def _add_vo( vo_type, player_id, name ):
"""Add a vehicle/ordnance."""
# check the hint
select_tab( "ob{}".format( player_id ) )
_check_hint( vo_type, player_id )
# add the vehicle/ordnance
add_vo( vo_type, player_id, name )
_expected[ (vo_type,player_id) ].append( name )
# check the snippet and hint
_check_snippet( vo_type, player_id )
_check_hint( vo_type, player_id )
def _delete_vo( vo_type, player_id, name, webdriver ):
"""Delete a vehicle/ordnance."""
# check the hint
select_tab( "ob{}".format( player_id ) )
_check_hint( vo_type, player_id )
# delete the vehicle/ordnance
delete_vo( vo_type, player_id, name, webdriver )
_expected[ (vo_type,player_id) ].remove( name )
# check the snippet and hint
_check_snippet( vo_type, player_id )
_check_hint( vo_type, player_id )
def _set_width( vo_type, player_id, width ):
"""Set the snippet width."""
select_tab( "ob{}".format( player_id ) )
vo_type2 = "vehicles" if vo_type == "vehicle" else vo_type
elem = find_child( "input[name='{}_WIDTH_{}']".format( vo_type2.upper(), player_id ) )
elem.clear()
if width is not None:
elem.send_keys( str(width) )
_width[ (vo_type,player_id) ] = width
def _check_snippet( vo_type, player_id ):
"""Check the generated vehicle/ordnance snippet."""
# check the snippet
select_tab( "ob{}".format( player_id ) )
dismiss_notifications()
vo_type2 = "vehicles" if vo_type == "vehicle" else vo_type
btn = find_child( "input[type='button'][data-id='{}_{}']".format( vo_type2, player_id ) )
btn.click()
buf = get_clipboard()
names = [
mo.group(1)
for mo in re.finditer( r"^\[\*\] (.*):" , buf, re.MULTILINE )
]
assert names == _expected[ (vo_type,player_id) ]
# check the snippet width
expected = _width[ (vo_type,player_id) ]
mo = re.search(
r"width={}$".format( expected if expected else "" ),
buf,
re.MULTILINE
)
assert mo
def _check_hint( vo_type, player_id ):
"""Check the hint visibility."""
hint = find_child( "#{}-hint_{}".format( vo_type, player_id ) )
expected = "none" if _expected[(vo_type,player_id)] else "block"
assert hint.value_of_css_property("display") == expected
def do_test( vo_type ):
"""Run the test."""
# add a German vehicle/ordnance
_add_vo( vo_type, 1, "a german {}".format(vo_type) )
# generate a Russian vehicle/ordnance snippet
_check_snippet( vo_type, 2 )
# add a Russian vehicle/ordnance
_add_vo( vo_type, 2, "another russian {}".format(vo_type) )
# go back and check the German snippet again
_check_snippet( vo_type, 1 )
# add another Russian vehicle/ordnance
_set_width( vo_type, 2, "200px" )
_add_vo( vo_type, 2, "name only" )
# delete the German vehicle/ordnance
_set_width( vo_type, 1, "100px" )
_delete_vo( vo_type, 1, "a german {}".format(vo_type), webdriver )
_set_width( vo_type, 1, None )
_check_snippet( vo_type, 1 )
# go back and check the Russian snippet again
_check_snippet( vo_type, 2 )
# delete the Russian vehicles/ordnance
_delete_vo( vo_type, 2, "another russian {}".format(vo_type), webdriver )
_set_width( vo_type, 2, None )
_delete_vo( vo_type, 2, "name only", webdriver )
# check the final state
assert not _expected[ (vo_type,1) ]
assert not _expected[ (vo_type,2) ]
# do the test
do_test( "vehicle" )
do_test( "ordnance" )
# ---------------------------------------------------------------------
def test_snippets( webapp, webdriver ):
"""Test vehicle/ordnance snippet generation in detail."""
# initialize
webdriver.get( webapp.url_for( "main" ) )
def do_test( vo_type ):
"""Run the test."""
vo_type2 = "vehicles" if vo_type == "vehicle" else vo_type
# test a full example
add_vo( vo_type, 1, "a german {}".format(vo_type) )
dismiss_notifications()
btn = find_child( "input[type='button'][data-id='{}_1']".format( vo_type2 ) )
btn.click()
expected = [
'[German] ; width=',
'[*] a german {}: #=1'.format( vo_type ),
'- notes: "A" "B†"',
'- capabilities: "QSU" "IR" "A1" "H2" "can do other stuff"'
]
if vo_type == "vehicle":
expected.insert( 3, "- CS 5" )
assert get_clipboard() == "\n".join(expected)
delete_vo( vo_type, 1, "a german {}".format(vo_type), webdriver )
# test a partial example
add_vo( vo_type, 1, "another german {}".format(vo_type) )
dismiss_notifications()
btn = find_child( "input[type='button'][data-id='{}_1']".format( vo_type2 ) )
btn.click()
expected = [
'[German] ; width=',
'[*] another german {}: #=2'.format( vo_type ),
'- capabilities: "QSU"'
]
if vo_type == "vehicle":
expected.insert( 2, '- cs 4 <small><i>(brew up)</i></small>' )
assert get_clipboard() == "\n".join(expected)
delete_vo( vo_type, 1, "another german {}".format(vo_type), webdriver )
# test a minimal example
add_vo( vo_type, 1, "name only" )
dismiss_notifications()
btn = find_child( "input[type='button'][data-id='{}_1']".format( vo_type2 ) )
btn.click()
assert get_clipboard() == \
'''[German] ; width=
[*] name only: #='''
# do the test
do_test( "vehicle" )
do_test( "ordnance" )
# ---------------------------------------------------------------------
def test_variable_capabilities( webapp, webdriver ):
"""Test date-based variable capabilities."""
# initialize
webdriver.get( webapp.url_for( "main" ) )
# add a vehicle
add_vo( "vehicle", 2, "Churchill III(b)" )
def do_test( month, year, expected ):
"""Set the date and check the vehicle snippet."""
dismiss_notifications()
select_tab( "scenario" )
set_template_params( { "SCENARIO_DATE": "{:02d}/01/{}".format(month,year) } )
select_tab( "ob2" )
submit = find_child( "input.generate[data-id='vehicles_2']" )
submit.click()
buf = get_clipboard()
mo = re.search( r"capabilities: (.*)$", buf )
print( mo.group(1) )
assert mo.group(1) == expected
do_test( 1, 1940, '"sM8\u2020"' )
do_test( 1, 1943, '"sM8\u2020"' )
do_test( 2, 1943, '"HE7" "sM8\u2020"' )
do_test( 12, 1943, '"HE7" "sM8\u2020"' )
do_test( 1, 1944, '"HE8" "sD6" "sM8\u2020"' )
do_test( 5, 1944, '"HE8" "sD6" "sM8\u2020"' )
do_test( 6, 1944, '"D6" "HE8" "sD6" "sM8\u2020"' )
do_test( 12, 1944, '"D6" "HE8" "sD6" "sM8\u2020"' )
do_test( 1, 1945, '"D7" "HE8" "sD6" "sM8\u2020"' )
do_test( 12, 1945, '"D7" "HE8" "sD6" "sM8\u2020"' )
do_test( 1, 1946, '"D7" "HE8" "sD6" "sM8\u2020"' )
# ---------------------------------------------------------------------
def add_vo( vo_type, player_id, name ):
"""Add a vehicle/ordnance."""
# add the vehicle/ordnance
select_tab( "ob{}".format( player_id ) )
elem = find_child( "#add-{}_{}".format( vo_type, player_id ) )
elem.click()
sel = Select( find_child( "#select-vo select" ) )
sel.select_by_visible_text( name )
click_dialog_button( "OK" )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def delete_vo( vo_type, player_id, name, webdriver ):
"""Delete a vehicle/ordnance."""
# delete the vehicle/ordnance
select_tab( "ob{}".format( player_id ) )
elems = [
c for c in find_children( "#{}-sortable_{} li".format( vo_type, player_id ) )
if c.text == name
]
assert len(elems) == 1
elem = elems[0]
trash = find_child( "#{}-trash_{}".format( vo_type, player_id ) )
ActionChains(webdriver).drag_and_drop( elem, trash ).perform()

@ -0,0 +1,94 @@
""" Check the vehicle/ordnance reports. """
import os
import io
import shutil
import tabulate
import pytest
from vasl_templates.webapp.tests.utils import find_child, find_children, wait_for
# ---------------------------------------------------------------------
# NOTE: Running these checks is fairly slow, and once done, don't provide a great deal of value
# in the day-to-day development process, so we make them optional.
@pytest.mark.skipif(
not pytest.config.option.check_vo_reports, #pylint: disable=no-member
reason = "--no-reports not specified"
)
def test_vo_reports( webapp, webdriver ):
"""Check the vehicle/ordnance reports."""
# initialize
check_dir = os.path.join( os.path.split(__file__)[0], "fixtures/vo-reports/" )
save_dir = None # nb: define this to save the generated reports
# initialize
if save_dir and os.path.isdir(save_dir):
shutil.rmtree( save_dir )
# check each vehicle/ordnance report
for nat in ["german","russian"]:
for vo_type in ["vehicles","ordnance"]:
for year in range(1940,1945+1):
# generate the next report
buf = io.StringIO()
results = get_vo_report( webapp, webdriver, nat, vo_type, year )
print( "=== {}/{}/{} ===".format( vo_type, nat, year ), file=buf )
print( "", file=buf )
# FUDGE! The last "notes" column spans 2 columns, so we add a dummy header
# to stop tabulate from getting confused :-/
assert results[0][-1] == "Notes"
results[0][-1] = "#"
results[0].append( "Notes" )
print(
tabulate.tabulate( results, headers="firstrow" ),
file = buf
)
report = buf.getvalue()
# check if we should save the report
fname = "{}/{}/{}.txt".format( vo_type, nat, year )
if save_dir:
fname2 = os.path.join( save_dir, fname )
os.makedirs( os.path.split(fname2)[0], exist_ok=True )
with open( os.path.join(save_dir,fname2), "w" ) as fp:
fp.write( report )
# check the report
fname = os.path.join( check_dir, fname )
assert open(fname,"r",encoding="utf-8").read() == report
# ---------------------------------------------------------------------
def get_vo_report( webapp, webdriver, nat, vo_type, year ):
"""Get a vehicle/ordnance report.
NOTE: We can't get the V/O report to return its results as, say, plain-text, for easy checking,
since it's all done in Javascript, asynchronously i.e. we need something that will wait until
the results are ready i.e. Selenium, not wget :-/
"""
# initialize
webdriver.get( webapp.url_for( "get_vo_report", nat=nat, vo_type=vo_type, year=year ) )
wait_for( 5, lambda: find_child("#results").is_displayed() )
# unload the results
def getval( cell ):
"""Get a table cell's value (cleaned up)."""
val = cell.get_attribute( "innerHTML" )
if val == "<small><em>n/a</em></small>":
return "n/a"
val = val.replace( '<span class="val">', "" ).replace( "</span>", "" )
return val
results = []
elem = find_child( "#results" )
for row in find_children( "tr", elem ):
if not results:
results.append( [ getval(c) for c in find_children("th",row) ] )
else:
results.append( [ getval(c) for c in find_children("td",row) ] )
return results

@ -13,8 +13,8 @@ from selenium.common.exceptions import NoSuchElementException, StaleElementRefer
# standard templates
_STD_TEMPLATES = {
"scenario": [ "scenario", "players", "victory_conditions", "ssr" ],
"ob1": [ "ob_setup_1" ],
"ob2": [ "ob_setup_2" ],
"ob1": [ "ob_setup_1", "vehicles_1", "ordnance_1" ],
"ob2": [ "ob_setup_2", "vehicles_2", "ordnance_2" ],
}
# nationality-specific templates
@ -45,9 +45,16 @@ def for_each_template( func ):
for tab_id,template_ids in _STD_TEMPLATES.items():
select_tab( tab_id )
for template_id in template_ids:
template_id2 = "ob_setup" if template_id.startswith("ob_setup_") else template_id
if template_id.startswith( "ob_setup_" ):
template_id2 = "ob_setup"
elif template_id.startswith( "vehicles_" ):
template_id2 = "vehicles"
elif template_id.startswith( "ordnance_" ):
template_id2 = "ordnance"
else:
template_id2 = template_id
func( template_id2, template_id )
if template_id != "ob_setup_2":
if template_id not in ("ob_setup_2","vehicles_2","ordnance_2"):
templates_to_test.remove( template_id2 )
# test the nationality-specific templates
@ -94,6 +101,15 @@ def set_template_params( params ):
add_ssr( _webdriver, ssr )
continue
# check for vehicles/ordnance (these require special handling)
if key in ("VEHICLES_1","ORDNANCE_1","VEHICLES_2","ORDNANCE_2"):
# add them in (nb: we don't consider any existing vehicles/ordnance)
vo_type = "vehicle" if key.startswith("VEHICLES_") else "ordnance"
from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo #pylint: disable=cyclic-import
for vo_name in val:
add_vo( vo_type, int(key[-1]), vo_name )
continue
# locate the next parameter
elem = next( c for c in ( \
find_child( "{}[name='{}']".format(elem_type,key) ) \
@ -168,6 +184,16 @@ def dismiss_notifications():
except StaleElementReferenceException:
pass # nb: the notification had already auto-closed
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def click_dialog_button( caption ):
"""Click a dialog button."""
btn = next(
elem for elem in find_children(".ui-dialog button")
if elem.text == caption
)
btn.click()
# ---------------------------------------------------------------------
def get_clipboard() :

@ -0,0 +1,60 @@
""" Main webapp handlers. """
import os
import json
import glob
from flask import request, render_template, jsonify, abort
from vasl_templates.webapp import app
from vasl_templates.webapp.config.constants import DATA_DIR
# ---------------------------------------------------------------------
@app.route( "/vehicles" )
def get_vehicle_listings():
"""Return the vehicle listings."""
return _do_get_listings( "vehicles" )
@app.route( "/ordnance" )
def get_ordnance_listings():
"""Return the ordnance listings."""
return _do_get_listings( "ordnance" )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _do_get_listings( listings_type ):
"""Load the vehicle/ordnance listings."""
# locate the data directory
if request.args.get( "report" ):
dname = DATA_DIR # nb: always use the real data for reports, not the test fixtures
else:
dname = app.config.get( "DATA_DIR", DATA_DIR )
dname = os.path.join( dname, listings_type )
if not os.path.isdir( dname ):
abort( 404 )
# load the listings
listings = {}
for fname in glob.glob( os.path.join( dname, "*.json" ) ):
nat = os.path.splitext( os.path.split(fname)[1] )[ 0 ]
with open( fname, "r" ) as fp:
listings[nat] = json.load( fp )
return jsonify( listings )
# ---------------------------------------------------------------------
@app.route( "/<vo_type>/<nat>/<int:year>" )
def get_vo_report( nat, vo_type, year ):
"""Get a vehicle/ordnance report."""
# generate the vehicle/ordnance report
if vo_type not in ("vehicles","ordnance"):
abort( 404 )
return render_template( "vo-report.html",
NATIONALITY = nat,
VO_TYPE = "vehicle" if vo_type == "vehicles" else vo_type,
YEAR = year
)
Loading…
Cancel
Save