Compare commits
No commits in common. 'master' and 'v1.3a' have entirely different histories.
@ -1 +0,0 @@ |
||||
logging.yaml |
@ -1,5 +1,4 @@ |
||||
[Debug] |
||||
|
||||
; NOTE: These need to be mapped in if you want to run the test suite against a container. |
||||
TEST_VASSAL_ENGINES = /test-data/vassal/ |
||||
TEST_VASL_MODS = /test-data/vasl-mods/ |
||||
TEST_VASL_MODS = /test-data/vasl-vmods/ |
||||
TEST_VASL_EXTNS_DIR = /test-data/vasl-extensions/ |
||||
|
@ -0,0 +1,31 @@ |
||||
version: 1 |
||||
|
||||
formatters: |
||||
standard: |
||||
format: "%(asctime)s.%(msecs)03d | %(message)s" |
||||
datefmt: "%H:%M:%S" |
||||
|
||||
handlers: |
||||
console: |
||||
class: "logging.StreamHandler" |
||||
formatter: "standard" |
||||
stream: "ext://sys.stdout" |
||||
file: |
||||
class: "logging.FileHandler" |
||||
formatter: "standard" |
||||
filename: "/tmp/vasl-templates.log" |
||||
mode: "w" |
||||
|
||||
loggers: |
||||
werkzeug: |
||||
level: "WARNING" |
||||
handlers: [ "console" ] |
||||
vasl_mod: |
||||
level: "WARNING" |
||||
handlers: [ "console", "file" ] |
||||
update_vsav: |
||||
level: "WARNING" |
||||
handlers: [ "console", "file" ] |
||||
control_tests: |
||||
level: "DEBUG" |
||||
handlers: [ "console", "file" ] |
@ -1,5 +1,6 @@ |
||||
[Site Config] |
||||
|
||||
FLASK_HOST = 0.0.0.0 |
||||
IS_CONTAINER = 1 |
||||
|
||||
WEBDRIVER_PATH = /usr/bin/geckodriver |
||||
|
@ -1,15 +1,8 @@ |
||||
#!/bin/sh |
||||
|
||||
# set up the display (so we can run VASSAL and a webdriver) |
||||
# set up the display (so we can run a webdriver) |
||||
export ENV=10 |
||||
export DISPLAY=:10.0 |
||||
Xvfb :10 -ac 1>/tmp/xvfb.log 2>/tmp/xvfb.err & |
||||
|
||||
# run the webapp server |
||||
# IMPORTANT! This script runs as PID 1, which is the only process that will receive signals, |
||||
# so we must replace it with the Python webserver process if it is to receive e.g. SIGTERM, |
||||
# which we must handle if "docker stop" and scaling down in Kubernetes is to work (otherwise |
||||
# things timeout and we get SIGKILL'ed). |
||||
exec python3 /app/vasl_templates/webapp/run_server.py \ |
||||
--addr 0.0.0.0 \ |
||||
--force-init-delay 30 |
||||
python /app/vasl_templates/webapp/run_server.py |
||||
|
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 902 KiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.4 KiB |
@ -1,107 +0,0 @@ |
||||
#!/usr/bin/env python3 |
||||
""" Freeze the vasl-templates loader program. |
||||
|
||||
This script is called by the main freeze script. |
||||
""" |
||||
|
||||
import sys |
||||
import os |
||||
import shutil |
||||
import tempfile |
||||
import getopt |
||||
|
||||
from PyInstaller.__main__ import run as run_pyinstaller |
||||
from PIL import Image |
||||
|
||||
APP_ICON = os.path.join( |
||||
os.path.abspath( os.path.dirname( __file__ ) ), |
||||
"../vasl_templates/webapp/static/images/app.ico" |
||||
) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def main( args ): |
||||
"""Main processing.""" |
||||
|
||||
# parse the command-line options |
||||
output_fname = "./loader" |
||||
work_dir = os.path.join( tempfile.gettempdir(), "freeze-loader" ) |
||||
cleanup = True |
||||
opts,args = getopt.getopt( args, "o:w:", ["output=","work=","no-clean"] ) |
||||
for opt, val in opts: |
||||
if opt in ["-o","--output"]: |
||||
output_fname = val.strip() |
||||
elif opt in ["-w","--work"]: |
||||
work_dir = val.strip() |
||||
elif opt in ["--no-clean"]: |
||||
cleanup = False |
||||
else: |
||||
raise RuntimeError( "Unknown argument: {}".format( opt ) ) |
||||
|
||||
# freeze the loader program |
||||
freeze_loader( output_fname, work_dir, cleanup ) |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def freeze_loader( output_fname, work_dir, cleanup ): |
||||
"""Freeze the loader program.""" |
||||
|
||||
with tempfile.TemporaryDirectory() as dist_dir: |
||||
|
||||
# initialize |
||||
base_dir = os.path.abspath( os.path.dirname( __file__ ) ) |
||||
assets_dir = os.path.join( base_dir, "assets" ) |
||||
|
||||
# convert the app icon to an image |
||||
if not os.path.isdir( work_dir ): |
||||
os.makedirs( work_dir ) |
||||
app_icon_fname = os.path.join( work_dir, "app-icon.png" ) |
||||
_convert_app_icon( app_icon_fname ) |
||||
|
||||
# initialize |
||||
app_name = "loader" |
||||
args = [ |
||||
"--distpath", dist_dir, |
||||
"--workpath", work_dir, |
||||
"--specpath", work_dir, |
||||
"--onefile", |
||||
"--name", app_name, |
||||
] |
||||
args.extend( [ |
||||
"--add-data", app_icon_fname + os.pathsep + "assets/", |
||||
"--add-data", os.path.join(assets_dir,"loading.gif") + os.pathsep + "assets/" |
||||
] ) |
||||
if sys.platform == "win32": |
||||
args.append( "--noconsole" ) |
||||
args.extend( [ "--icon", APP_ICON ] ) |
||||
args.append( os.path.join( base_dir, "main.py" ) ) |
||||
|
||||
# freeze the program |
||||
run_pyinstaller( args ) |
||||
|
||||
# save the generated artifact |
||||
fname = app_name+".exe" if sys.platform == "win32" else app_name |
||||
shutil.move( |
||||
os.path.join( dist_dir, fname ), |
||||
output_fname |
||||
) |
||||
|
||||
# clean up |
||||
if cleanup: |
||||
shutil.rmtree( work_dir ) |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def _convert_app_icon( save_fname ): |
||||
"""Convert the app icon to an image.""" |
||||
# NOTE: Tkinter's PhotoImage doesn't handle .ico files, so we convert the app icon |
||||
# to an image, then insert it into the PyInstaller-generated executable (so that |
||||
# we don't have to bundle Pillow into the release). |
||||
img = Image.open( APP_ICON ) |
||||
img = img.convert( "RGBA" ).resize( (48, 48) ) |
||||
img.save( save_fname, "png" ) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
if __name__ == "__main__": |
||||
main( sys.argv[1:] ) |
@ -1,207 +0,0 @@ |
||||
""" Load the main vasl-templates program. |
||||
|
||||
vasl-templates can be slow to start (especially on Windows), since it has to unpack the PyInstaller-generated EXE, |
||||
then startup Qt. We want to show a splash screen while all this happening, but we can't just put it in vasl-templates, |
||||
since it would only happen *after* all the slow stuff has finished :-/ So, we have this stub program that shows |
||||
a splash screen, launches the main vasl-templates program, and waits for it to finish starting up. |
||||
""" |
||||
|
||||
import sys |
||||
import os |
||||
import subprocess |
||||
import threading |
||||
import itertools |
||||
import urllib.request |
||||
from urllib.error import URLError |
||||
import time |
||||
import configparser |
||||
|
||||
# NOTE: It's important that this program start up quickly (otherwise it becomes pointless), |
||||
# so we use tkinter, instead of PyQt (and also avoid bundling a 2nd copy of PyQt :-/). |
||||
import tkinter |
||||
import tkinter.messagebox |
||||
|
||||
if getattr( sys, "frozen", False ): |
||||
BASE_DIR = sys._MEIPASS #pylint: disable=no-member,protected-access |
||||
else: |
||||
BASE_DIR = os.path.abspath( os.path.dirname( __file__ ) ) |
||||
|
||||
STARTUP_TIMEOUT = 60 # how to long to wait for vasl-templates to start (seconds) |
||||
|
||||
main_window = None |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def main( args ): |
||||
"""Load the main vasl-templates program.""" |
||||
|
||||
# initialize Tkinter |
||||
global main_window |
||||
main_window = tkinter.Tk() |
||||
main_window.option_add( "*Dialog.msg.font", "Helvetica 12" ) |
||||
|
||||
# load the app icon |
||||
# NOTE: This image file doesn't exist in source control, but is created dynamically from |
||||
# the main app icon by the freeze script, and inserted into the PyInstaller-generated executable. |
||||
# We do things this way so that we don't have to bundle Pillow into the release. |
||||
app_icon = tkinter.PhotoImage( |
||||
file = make_asset_path( "app-icon.png" ) |
||||
) |
||||
|
||||
# locate the main vasl-templates executable |
||||
fname = os.path.join( os.path.dirname( sys.executable ), "vasl-templates-main" ) |
||||
if sys.platform == "win32": |
||||
fname += ".exe" |
||||
if not os.path.isfile( fname ): |
||||
show_error_msg( "Can't find the main vasl-templates program.", withdraw=True ) |
||||
return -1 |
||||
|
||||
# launch the main vasl-templates program |
||||
try: |
||||
proc = subprocess.Popen( itertools.chain( [fname], args ) ) #pylint: disable=consider-using-with |
||||
except Exception as ex: #pylint: disable=broad-except |
||||
show_error_msg( "Can't start vasl-templates:\n\n{}".format( ex ), withdraw=True ) |
||||
return -2 |
||||
|
||||
# get the webapp port number |
||||
port = 5010 |
||||
fname = os.path.join( os.path.dirname( fname ), "config/app.cfg" ) |
||||
if os.path.isfile( fname ): |
||||
config_parser = configparser.ConfigParser() |
||||
config_parser.optionxform = str # preserve case for the keys :-/ |
||||
config_parser.read( fname ) |
||||
args = dict( config_parser.items( "System" ) ) |
||||
port = args.get( "FLASK_PORT_NO", port ) |
||||
|
||||
# create the splash window |
||||
create_window( app_icon ) |
||||
|
||||
# start a background thread to check on the main vasl-templates process |
||||
threading.Thread( |
||||
target = check_startup, |
||||
args = ( proc, port ) |
||||
).start() |
||||
|
||||
# run the main loop |
||||
main_window.mainloop() |
||||
|
||||
return 0 |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def create_window( app_icon ): |
||||
"""Create the splash window.""" |
||||
|
||||
# create the splash window |
||||
main_window.geometry( "275x64" ) |
||||
main_window.title( "vasl-templates loader" ) |
||||
main_window.overrideredirect( 1 ) # nb: "-type splash" doesn't work on Windows :-/ |
||||
main_window.eval( "tk::PlaceWindow . center" ) |
||||
main_window.wm_attributes( "-topmost", 1 ) |
||||
main_window.tk.call( "wm", "iconphoto", main_window._w, app_icon ) #pylint: disable=protected-access |
||||
main_window.protocol( "WM_DELETE_WINDOW", lambda: None ) |
||||
|
||||
# add the app icon |
||||
label = tkinter.Label( main_window, image=app_icon ) |
||||
label.grid( row=0, column=0, rowspan=2, padx=8, pady=8 ) |
||||
|
||||
# add the caption |
||||
label = tkinter.Label( main_window, text="Loading vasl-templates...", font=("Helvetica",12) ) |
||||
label.grid( row=0, column=1, padx=5, pady=(8,0) ) |
||||
|
||||
# add the "loading" image (we have to animate it ourself :-/) |
||||
anim_label = tkinter.Label( main_window ) |
||||
anim_label.grid( row=1, column=1, sticky=tkinter.N, padx=0, pady=0 ) |
||||
fname = make_asset_path( "loading.gif" ) |
||||
nframes = 13 |
||||
frames = [ |
||||
tkinter.PhotoImage( file=fname, format="gif -index {}".format( i ) ) |
||||
for i in range(nframes) |
||||
] |
||||
frame_index = 0 |
||||
def next_frame(): |
||||
nonlocal frame_index |
||||
frame = frames[ frame_index ] |
||||
frame_index = ( frame_index + 1 ) % nframes |
||||
anim_label.configure( image=frame ) |
||||
main_window.after( 75, next_frame ) |
||||
main_window.after( 0, next_frame ) |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def check_startup( proc, port ): |
||||
"""Check the startup of the main vasl-templates process.""" |
||||
|
||||
def do_check(): |
||||
|
||||
# check if we've waited for too long |
||||
if time.time() - start_time > STARTUP_TIMEOUT: |
||||
# yup - give up |
||||
raise RuntimeError( "Couldn't start vasl-templates." ) |
||||
|
||||
# check if the main vasl-templates process has gone away |
||||
if proc.poll() is not None: |
||||
raise RuntimeError( "The vasl-templates program ended unexpectedly." ) |
||||
|
||||
# check if the webapp is responding |
||||
url = "http://localhost:{}/ping".format( port ) |
||||
try: |
||||
with urllib.request.urlopen( url ) as resp: |
||||
_ = resp.read() |
||||
except URLError: |
||||
# no response - the webapp is probably still starting up |
||||
return False |
||||
except Exception as ex: #pylint: disable=broad-except |
||||
raise RuntimeError( "Couldn't communicate with vasl-templates:\n\n{}".format( ex ) ) from ex |
||||
|
||||
# the main vasl-templates program has started up and is responsive - our job is done! |
||||
if sys.platform == "win32": |
||||
# FUDGE! There is a short amount of time between the webapp server starting and |
||||
# the main window appearing. We delay here for a bit, to try to synchronize |
||||
# our window fading out with the main vasl-templates window appearing. |
||||
time.sleep( 1 ) |
||||
return True |
||||
|
||||
def on_done( msg ): |
||||
if msg: |
||||
show_error_msg( msg, withdraw=True ) |
||||
fade_out( main_window, main_window.quit ) |
||||
|
||||
# run the main loop |
||||
start_time = time.time() |
||||
while True: |
||||
try: |
||||
if do_check(): |
||||
on_done( None ) |
||||
break |
||||
except Exception as ex: #pylint: disable=broad-except |
||||
on_done( str(ex) ) |
||||
return |
||||
time.sleep( 0.25 ) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def fade_out( target, on_done ): |
||||
"""Fade out the target window.""" |
||||
alpha = target.attributes( "-alpha" ) |
||||
if alpha > 0: |
||||
alpha -= 0.1 |
||||
target.attributes( "-alpha", alpha ) |
||||
target.after( 50, lambda: fade_out( target, on_done ) ) |
||||
else: |
||||
on_done() |
||||
|
||||
def make_asset_path( fname ): |
||||
"""Generate the path to an asset file.""" |
||||
return os.path.join( BASE_DIR, "assets", fname ) |
||||
|
||||
def show_error_msg( error_msg, withdraw=False ): |
||||
"""Show an error dialog.""" |
||||
if withdraw: |
||||
main_window.withdraw() |
||||
tkinter.messagebox.showinfo( "vasl-templates loader error", error_msg ) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
if __name__ == "__main__": |
||||
sys.exit( main( sys.argv[1:] ) ) |
@ -1,7 +1,6 @@ |
||||
pytest==7.4.2 |
||||
grpcio-tools==1.58.0 |
||||
tabulate==0.9.0 |
||||
lxml==4.9.3 |
||||
pylint==2.17.5 |
||||
pytest-pylint==0.19.0 |
||||
pyinstaller==5.13.2 |
||||
pytest==3.6.0 |
||||
tabulate==0.8.2 |
||||
lxml==4.2.4 |
||||
pylint==1.9.2 |
||||
pytest-pylint==0.9.0 |
||||
pyinstaller==3.6 |
||||
|
@ -1,10 +1,7 @@ |
||||
# python 3.11.4 |
||||
# python 3.6.8 |
||||
|
||||
flask==2.3.3 |
||||
pyyaml==6.0.1 |
||||
# NOTE: Pillow 9.5.0 is the last version that provides 32-bit wheels. |
||||
pillow==9.5.0 |
||||
selenium==4.12.0 |
||||
waitress==2.1.2 |
||||
appdirs==1.4.4 |
||||
click==8.1.7 |
||||
flask==1.0.2 |
||||
pyyaml==5.3.1 |
||||
pillow==7.0.0 |
||||
selenium==3.12.0 |
||||
click==6.7 |
||||
|
@ -1,83 +0,0 @@ |
||||
#!/usr/bin/env python3 |
||||
""" Prepare the piece info for a VASL module. |
||||
|
||||
The main program used to identify 5/8" counters by reading a module's buildFile and checking the height |
||||
attribute of the PieceSlot nodes, but it turns out this is the wrong thing to do (this field actually |
||||
controls the size of the piece's entry in the counter palette): |
||||
https://github.com/vasl-developers/vasl/issues/1195 |
||||
|
||||
For each version of VASL supported, run vassal-shim (getPieceInfo command) to analyze the module's |
||||
buildFile and get the correct counter sizes. Then pass the output into this script, to generate |
||||
the final data file that should be saved in the $/data/vasl-$VERSION/ directory, where it will |
||||
be read by the main program. |
||||
|
||||
NOTE: Introducing this process opens the possibility of also extracting the image file paths |
||||
within the .vmod file, instead of the current messy parsing of the PieceSlot CDATA... :-/ |
||||
""" |
||||
|
||||
import sys |
||||
import os |
||||
import json |
||||
|
||||
import xml.etree.ElementTree as ET |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
# initialize |
||||
report = {} |
||||
|
||||
# figure out which GPID's we're interested in |
||||
gpids = set() |
||||
def get_gpids( vo_type ): |
||||
"""Get the GPID's from our data files.""" |
||||
dname = os.path.join( os.path.dirname(__file__), "../webapp/data", vo_type ) |
||||
for root,_,fnames in os.walk( dname ): |
||||
for fname in fnames: |
||||
if os.path.splitext( fname )[1] != ".json": |
||||
continue |
||||
fname = os.path.join( root, fname ) |
||||
with open( fname, "r", encoding="utf-8" ) as fp: |
||||
entries = json.load( fp ) |
||||
for entry in entries: |
||||
entry_gpid = entry.get( "gpid" ) |
||||
if not entry_gpid: |
||||
continue |
||||
if isinstance( entry_gpid, list ): |
||||
gpids.update( str(g) for g in entry_gpid ) |
||||
else: |
||||
gpids.add( str( entry_gpid ) ) |
||||
get_gpids( "vehicles" ) |
||||
get_gpids( "ordnance" ) |
||||
|
||||
# parse the piece info generated by vassal-shim |
||||
doc = ET.parse( sys.stdin ) |
||||
for piece_info in doc.getroot(): |
||||
gpid = piece_info.attrib["gpid"] |
||||
if gpid not in gpids: |
||||
continue |
||||
info = {} |
||||
# check if the next piece is small |
||||
# FUDGE! We used to check for <= 48, but what we get is GamePiece.boundingBox(), which is |
||||
# the click zone for the counter, not the actual size of the counter's image :-/ |
||||
if int( piece_info.attrib["height"] ) <= 55: |
||||
info["is_small"] = True |
||||
if info: |
||||
report[ gpid ] = info |
||||
|
||||
# FUDGE! These are from extensions - it's not worth trying to figure these out programtically. |
||||
report[ "adf:1948" ] = { "is_small": True } # BFP Blood & Jungle: Dutch Brandt 47mm Mortar |
||||
report[ "adf:75" ] = { "is_small": True } # BFP Blood & Jungle: Indonesian Type 89 Heavy Grenade Launcher |
||||
report[ "adf:77" ] = { "is_small": True } # BFP Blood & Jungle: Indonesian Type 97 Automatic Gun |
||||
report[ "adf:76" ] = { "is_small": True } # BFP Blood & Jungle: Indonesian Year-11 Flat-Trajectory INF Gun |
||||
report[ "adf:1407" ] = { "is_small": True } # BFP Poland In Flames: German 2cm Tankbusche S-18 |
||||
report[ "08d:75" ] = { "is_small": True } # Fight For Seoul: American M20(L) 75mm Recoilless Rifle |
||||
|
||||
# output the final report |
||||
print( "{" ) |
||||
lines = [] |
||||
for gpid, piece_info in report.items(): |
||||
lines.append( "\"{}\": {}".format( |
||||
gpid, json.dumps( piece_info ) |
||||
) ) |
||||
print( ",\n".join( lines ) ) |
||||
print( "}" ) |
@ -1,10 +0,0 @@ |
||||
[Debug] |
||||
|
||||
; Set this if you want to run the test suite (allows the webapp server to be controlled using gRPC). |
||||
; CONTROL_TESTS_PORT = -1 |
||||
|
||||
; Set this to a directory containing the VASSAL releases to run the test suite with. |
||||
; TEST_VASSAL_ENGINES = ... |
||||
|
||||
; Set this to a directory containing the VASL modules (.vmod files) to run the test suite with. |
||||
; TEST_VASL_MODS = ... |
@ -1,5 +0,0 @@ |
||||
<html> <!-- vasl-templates:id {{SNIPPET_ID}} --> |
||||
|
||||
<img src="{{IMAGES_BASE_URL}}/compass/{{COMPASS}}.png"> |
||||
|
||||
</html> |
@ -1,41 +0,0 @@ |
||||
<html> <!-- vasl-templates:id {{SNIPPET_ID}} --> |
||||
|
||||
<!-- vasl-templates:name Booby Traps --> |
||||
<!-- vasl-templates:description Data chart for Booby Traps. --> |
||||
|
||||
<!-- player = {{PLAYER_DROPLIST:|Player}} |
||||
<!-- boards = {{BOARDS*:/8|Board(s)}} --> |
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<style> |
||||
.header { |
||||
background: {{PLAYER_COLORS[PLAYER_DROPLIST][0]}} ; |
||||
border-bottom: 1px solid {{PLAYER_COLORS[PLAYER_DROPLIST][2]}} ; |
||||
padding: 2px 5px ; |
||||
font-size: 105% ; font-weight: bold ; |
||||
} |
||||
.header .level { font-size: 90% ; font-style: italic ; } |
||||
{{CSS:common}} |
||||
</style> |
||||
</head> |
||||
|
||||
<table> |
||||
|
||||
<tr> |
||||
<td class="header"> |
||||
<img src="{{PLAYER_FLAGS[PLAYER_DROPLIST]}}?prefh={{PLAYER_FLAG_SIZE_LARGE}}" width="{{PLAYER_FLAG_SIZE_LARGE}}" height="{{PLAYER_FLAG_SIZE_LARGE}}"> Booby Traps <span class="level">(Level {{LEVEL:A::B::C/3|Level}})</span> |
||||
|
||||
<tr> |
||||
<td style="padding:2px 5px;"> |
||||
<b> {%if BOARDS%} Boards: {{BOARDS}} {%else%} Entire map {%endif%} </b> |
||||
<ul> |
||||
<li> Original TC |
||||
{% if LEVEL == "A" %} ≥ 11 |
||||
{% elif LEVEL == "B" %} 11 |
||||
{% elif LEVEL == "C" %} 12 |
||||
{%else%} ??? {%endif%} |
||||
<li> Search Casualties |
||||
</ul> |
||||
|
||||
</table> |
@ -1,87 +0,0 @@ |
||||
<html> <!-- vasl-templates:id {{SNIPPET_ID}} --> |
||||
|
||||
<!-- vasl-templates:name Kampfgruppe Scherer --> |
||||
<!-- vasl-templates:description Data charts for Grenade Bundles and Molotov Cocktails. --> |
||||
<!-- {{TYPE:Grenade Bundles::Molotov Cocktails/10|Data chart}} --> |
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<style> {{CSS:common}} </style> |
||||
</head> |
||||
|
||||
<table> |
||||
|
||||
<tr> |
||||
<td colspan="2" style=" |
||||
background: {{PLAYER_COLORS["german"][0]}} ; |
||||
border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; |
||||
padding: 2px 5px ; |
||||
font-size: 105% ; font-weight: bold ; |
||||
"> |
||||
{# Some versions of Java require <img> tags to have the width and height specified!?! #} |
||||
{%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}?prefh={{PLAYER_FLAG_SIZE_LARGE}}" width="{{PLAYER_FLAG_SIZE_LARGE}}" height="{{PLAYER_FLAG_SIZE_LARGE}}"> {%endif%}{{TYPE}} |
||||
|
||||
{% if TYPE == "Grenade Bundles" %} |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
CC Attack -2 DRM |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
<b>ATMM check</b>: dr ≤ 3 (△) |
||||
<table style="margin-left:10px;"> |
||||
<tr> |
||||
<td style="width:20px;"> +1 |
||||
<td> HS/crew |
||||
<tr> |
||||
<td> +2 |
||||
<td> SMC |
||||
<tr> |
||||
<td> +1 |
||||
<td> CX |
||||
<tr> |
||||
<td> +1 |
||||
<td> vs. non-armored vehicle |
||||
</table> |
||||
original 6 = pinned (CCV reduced by 1) |
||||
|
||||
{% elif TYPE == "Molotov Cocktails" %} |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
Against AFV only. |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
<b>MOL check</b>: dr ≤ 3 (△) |
||||
<table style="margin-left:10px;"> |
||||
<tr> |
||||
<td style="width:20px;"> +1 |
||||
<td> HS/crew |
||||
<tr> |
||||
<td> +2 |
||||
<td> SMC |
||||
<tr> |
||||
<td> +1 |
||||
<td> CX |
||||
</table> |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
<b>IFT DR original colored dr</b>: |
||||
<ul> |
||||
<li> 1 = Flame in target Location |
||||
<li> 6 = thrower breaks, Flame in their Location |
||||
</ul> |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
<b>Kindling Attempt</b>: +2 DRM |
||||
|
||||
{%endif%} |
||||
|
||||
</table> |
||||
|
||||
</html> |
||||
|
@ -0,0 +1,49 @@ |
||||
<html> <!-- vasl-templates:id {{SNIPPET_ID}} --> |
||||
|
||||
<!-- vasl-templates:name KGS Grenade Bundles --> |
||||
<!-- vasl-templates:description Data chart for Grenade Bundles in <i>Kampfgruppe Scherer</i>. --> |
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<style> {{CSS:common}} </style> |
||||
</head> |
||||
|
||||
<table> |
||||
|
||||
<tr> |
||||
<td colspan="2" style=" |
||||
background: {{PLAYER_COLORS["german"][0]}} ; |
||||
border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; |
||||
padding: 2px 5px ; |
||||
font-size: 105% ; font-weight: bold ; |
||||
"> |
||||
{# Some versions of Java require <img> tags to have the width and height specified!?! #} |
||||
{%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}" {{PLAYER_FLAG_SIZE}}> {%endif%}Grenade Bundles |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
-2 CC Attack DRM |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
ATMM check: dr ≤ 3 (△) |
||||
<table style="margin-left:10px;"> |
||||
<tr> |
||||
<td style="width:20px;"> +1 |
||||
<td> HS/crew |
||||
<tr> |
||||
<td> +2 |
||||
<td> SMC |
||||
<tr> |
||||
<td> +1 |
||||
<td> CX |
||||
<tr> |
||||
<td> +1 |
||||
<td> vs. non-armored vehicle |
||||
</table> |
||||
original 6 = pinned (CCV reduced by 1) |
||||
|
||||
</table> |
||||
|
||||
</html> |
||||
|
@ -0,0 +1,57 @@ |
||||
<html> <!-- vasl-templates:id {{SNIPPET_ID}} --> |
||||
|
||||
<!-- vasl-templates:name KGS Molotov Cocktails --> |
||||
<!-- vasl-templates:description Data chart for Molotov Cocktails in <i>Kampfgruppe Scherer</i>. --> |
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<style> {{CSS:common}} </style> |
||||
</head> |
||||
|
||||
<table> |
||||
|
||||
<tr> |
||||
<td colspan="2" style=" |
||||
background: {{PLAYER_COLORS["german"][0]}} ; |
||||
border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; |
||||
padding: 2px 5px ; |
||||
font-size: 105% ; font-weight: bold ; |
||||
"> |
||||
{# Some versions of Java require <img> tags to have the width and height specified!?! #} |
||||
{%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}" {{PLAYER_FLAG_SIZE}}> {%endif%}Molotov Cocktails |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
vs. AFV only |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
MOL check: dr ≤ 3 (△) |
||||
<table style="margin-left:10px;"> |
||||
<tr> |
||||
<td style="width:20px;"> +1 |
||||
<td> HS/crew |
||||
<tr> |
||||
<td> +2 |
||||
<td> SMC |
||||
<tr> |
||||
<td> +1 |
||||
<td> CX |
||||
</table> |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
IFT DR original colored dr: |
||||
<ul> |
||||
<li> 1 = Flame in target Location |
||||
<li> 6 = thrower breaks, Flame in their Location |
||||
</ul> |
||||
|
||||
<tr> |
||||
<td style="padding:3px 5px 0 5px;"> |
||||
Kindling Attempt: +2 DRM |
||||
|
||||
</table> |
||||
|
||||
</html> |
||||
|
@ -1,2 +1,2 @@ |
||||
{# Some versions of Java require <img> tags to have the width and height specified!?! #} |
||||
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?prefh={{PLAYER_FLAG_SIZE}}" width="{{PLAYER_FLAG_SIZE}}" height="{{PLAYER_FLAG_SIZE}}"> {%endif%} |
||||
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}" {{PLAYER_FLAG_SIZE}}> {%endif%} |
||||
|
@ -1,2 +0,0 @@ |
||||
{# Some versions of Java require <img> tags to have the width and height specified!?! #} |
||||
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?prefh={{PLAYER_FLAG_SIZE_LARGE}}" width="{{PLAYER_FLAG_SIZE_LARGE}}" height="{{PLAYER_FLAG_SIZE_LARGE}}"> {%endif%} |
@ -1,116 +0,0 @@ |
||||
<html> <!-- vasl-templates:id {{SNIPPET_ID}} --> |
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<style> {{CSS:common}} </style> |
||||
|
||||
<style> |
||||
|
||||
td { |
||||
width: 50px ; min-width: 50px ; |
||||
height: {%if TURN_TRACK_PREVIEW_MODE%} 50px {%else%} 43px {%endif%} ; |
||||
padding: 2px ; |
||||
border: 1px solid black ; |
||||
} |
||||
{% set RESET_TD = "min-width: unset ; height: unset ; padding: 0 ; border: none" %} |
||||
td.turn-no { |
||||
{{RESET_TD}} ; width: unset ; |
||||
text-align: center ; vertical-align: center ; font-size: 18px ; font-weight: bold ; |
||||
} |
||||
td.no-reinforce { {{RESET_TD}} ; width: 13px ; } |
||||
{# NOTE: We do the reinforcement flags as CSS backgrounds, since VASSAL is incredibly slow downloading normal images. #} |
||||
td.reinforce1 { {{RESET_TD}} ; width: 13px ; background: url("{{TURN_TRACK_FLAG_1}}") top left no-repeat ; vertical-align: top ; } |
||||
td.reinforce2 { {{RESET_TD}} ; width: 13px ; background: url("{{TURN_TRACK_FLAG_2}}") bottom right no-repeat ; vertical-align: bottom ; } |
||||
td.half-turn { |
||||
background: url("{{TURN_TRACK_HALF_TURN_IMAGE_URL}}") bottom right no-repeat ; |
||||
background-size: contain ; {# nb: doesn't work in VASSAL, the image file needs to be the correct size :-/ #} |
||||
} |
||||
|
||||
{% if TURN_TRACK_PREVIEW_MODE %} |
||||
body { margin: 0 ; } |
||||
body ::selection {} |
||||
body ::moz-selection {} |
||||
body { user-select: none ; } |
||||
.reinforce1, .reinforce2 { opacity: 0 ; } |
||||
.flag-click { width: 13px ; height: 13px ; cursor: pointer ; } |
||||
.shading-click { cursor: pointer ; } |
||||
{%endif%} |
||||
|
||||
</style> |
||||
|
||||
</head> |
||||
|
||||
{% if TURN_TRACK_PREVIEW_MODE %} |
||||
<script> |
||||
// notify the parent window of clicks |
||||
function onFlagClick( turnNo, playerNo ) { |
||||
window.parent.postMessage( { |
||||
type: "FlagClick", |
||||
turnNo: turnNo, uiPlayerNo: playerNo |
||||
}, "*" ) ; |
||||
} |
||||
function onShadingClick( turnNo ) { |
||||
window.parent.postMessage( { |
||||
type: "ShadingClick", |
||||
turnNo: turnNo |
||||
}, "*" ) ; |
||||
} |
||||
</script> |
||||
{%endif%} |
||||
|
||||
<table class="turn-track"> |
||||
|
||||
{% for row in TURN_TRACK_SQUARES %} |
||||
<tr> |
||||
|
||||
{% for turnSquare in row %} |
||||
<td id="turn-square-{{turnSquare[0]}}" |
||||
{%if turnSquare[0] == TURN_TRACK_HALF_TURN%} class="half-turn" {%endif%} |
||||
{% if turnSquare[3] %} style="background-color:{{turnSquare[3]}};" {%endif%} |
||||
> |
||||
|
||||
<table style="width:100%;height:100%;"> <tr> |
||||
|
||||
<td id="flag-{{turnSquare[0]}}_1" width="100%" |
||||
class = {% if turnSquare[1] %} "reinforce1" {%else%} "no-reinforce" {%endif%} |
||||
> |
||||
{% if TURN_TRACK_PREVIEW_MODE %} |
||||
<div class="flag-click" |
||||
onclick = "onFlagClick( {{turnSquare[0]}}, 1 )" |
||||
> </div> |
||||
{%endif%} |
||||
</td> |
||||
|
||||
<td class="turn-no"> |
||||
{% if TURN_TRACK_PREVIEW_MODE %} |
||||
<div class="shading-click" |
||||
onclick = "onShadingClick( {{turnSquare[0]}} )" |
||||
> |
||||
{{turnSquare[0]}} |
||||
</div> |
||||
{%else%} |
||||
{{turnSquare[0]}} |
||||
{%endif%} |
||||
</td> |
||||
|
||||
<td id="flag-{{turnSquare[0]}}_2" width="100%" |
||||
class = {% if turnSquare[2] and turnSquare[0] != TURN_TRACK_HALF_TURN %} "reinforce2" {%else%} "no-reinforce" {%endif%} |
||||
> |
||||
{% if TURN_TRACK_PREVIEW_MODE and turnSquare[0] != TURN_TRACK_HALF_TURN %} |
||||
<div class="flag-click" |
||||
onclick = "onFlagClick( {{turnSquare[0]}}, 2 )" |
||||
> </div> |
||||
{%endif%} |
||||
</td> |
||||
|
||||
</tr> </table> |
||||
|
||||
</td> |
||||
{%endfor%} |
||||
|
||||
</tr> |
||||
{%endfor%} |
||||
|
||||
</table> |
||||
|
||||
</html> |