summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-10-22 12:53:57 +0200
committerTor Andersson <tor@ccxvii.net>2022-11-19 00:05:34 +0100
commit719d61982953caf61c0df7776347e13105a2e8b2 (patch)
tree4e11a9d0c502264f4169de97914fca6ccaf9ef22 /tools
parent8d8d6db547653dff6ffb34fdf9336317da50121e (diff)
downloadnevsky-719d61982953caf61c0df7776347e13105a2e8b2.tar.gz
Add asset rendering scripts.
Diffstat (limited to 'tools')
-rw-r--r--tools/boxes.svg688
-rw-r--r--tools/build_cards1.sh86
-rw-r--r--tools/build_cards2.sh10
-rw-r--r--tools/build_counters1.sh117
-rw-r--r--tools/build_counters2.sh119
-rw-r--r--tools/build_counters3.sh5
-rw-r--r--tools/build_map.sh16
-rw-r--r--tools/build_mats.sh35
-rw-r--r--tools/build_screen.sh10
-rw-r--r--tools/build_stickers.sh37
-rw-r--r--tools/colors.mjs108
-rw-r--r--tools/genboxes.py65
-rw-r--r--tools/gencss.js5
-rw-r--r--tools/gencyl.js51
-rw-r--r--tools/gendata.js705
-rw-r--r--tools/scale.sh18
-rw-r--r--tools/svgo.config.js10
17 files changed, 2085 insertions, 0 deletions
diff --git a/tools/boxes.svg b/tools/boxes.svg
new file mode 100644
index 0000000..16b8e71
--- /dev/null
+++ b/tools/boxes.svg
@@ -0,0 +1,688 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="svg2"
+ width="5100"
+ height="6600"
+ viewBox="0 0 5100 6600"
+ sodipodi:docname="boxes.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="0.4218644"
+ inkscape:cx="2021.1195"
+ inkscape:cy="4535.2592"
+ inkscape:current-layer="svg2"
+ inkscape:document-rotation="0" />
+ <metadata
+ id="metadata8">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs6" />
+ <image
+ sodipodi:absref="/home/tor/src/rally/public/nevsky/tools/map300.png"
+ xlink:href="map300.png"
+ id="image12"
+ preserveAspectRatio="none"
+ height="6600"
+ width="5100"
+ style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+ sodipodi:insensitive="true"
+ x="0"
+ y="0" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+ id="rect838"
+ width="303.94965"
+ height="60.271549"
+ x="1448.1355"
+ y="3624.7134"
+ ry="0.381295"
+ inkscape:label="Wesenberg" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+ id="rect840"
+ width="184.05482"
+ height="60.919552"
+ x="1012.6255"
+ y="4583.2241"
+ ry="0.381295"
+ inkscape:label="Fellin" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+ id="rect844"
+ width="250.44016"
+ height="60.948803"
+ x="1377.8793"
+ y="5103.0913"
+ ry="0.61047864"
+ inkscape:label="Odenpäh" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+ id="rect848"
+ width="184.90872"
+ height="60.490585"
+ x="1503.5576"
+ y="5612.335"
+ ry="0.610479"
+ inkscape:label="Adsel" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+ id="rect850"
+ width="231.68872"
+ height="60.27153"
+ x="909.25671"
+ y="5759.166"
+ ry="0.610479"
+ inkscape:label="Wenden" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect854"
+ width="147.6003"
+ height="62.215714"
+ x="2666.8506"
+ y="3294.6785"
+ ry="0.610479"
+ inkscape:label="Luga" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect856"
+ width="284.58069"
+ height="62.094578"
+ x="2904.4648"
+ y="3521.9727"
+ ry="0.610479"
+ inkscape:label="Kaibolovo" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect858"
+ width="240.76186"
+ height="61.891674"
+ x="3132.8203"
+ y="3159.7158"
+ ry="0.610479"
+ inkscape:label="Koporye" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect860"
+ width="147.92438"
+ height="61.891689"
+ x="3924.1265"
+ y="2933.6978"
+ ry="0.610479"
+ inkscape:label="Neva" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect864"
+ width="230.96405"
+ height="63.011024"
+ x="4591.4414"
+ y="3783.0674"
+ ry="0.610479"
+ inkscape:label="Volkhov" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect870"
+ width="186.64714"
+ height="62.539753"
+ x="4242.9819"
+ y="5580.9438"
+ ry="0.610479"
+ inkscape:label="Lovat" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect872"
+ width="240.92387"
+ height="62.539753"
+ x="3514.5398"
+ y="5466.5576"
+ ry="0.610479"
+ inkscape:label="Porkhov" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+ id="rect876"
+ width="240.81668"
+ height="62.323635"
+ x="2239.7556"
+ y="5430.8633"
+ ry="0.610479"
+ inkscape:label="Izborsk" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect878"
+ width="351.25955"
+ height="60.595512"
+ x="3706.0474"
+ y="6347.2988"
+ ry="0.610479"
+ inkscape:label="Velikiye Luki" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect880"
+ width="121.21022"
+ height="32.307468"
+ x="3936.2415"
+ y="4101.6738"
+ ry="0.610479"
+ inkscape:label="Tesovo" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect886"
+ width="127.99587"
+ height="30.459776"
+ x="3500.9299"
+ y="4176.23"
+ ry="0.610479"
+ inkscape:label="Zheltsy" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect888"
+ width="104.25461"
+ height="31.390947"
+ x="3787.9937"
+ y="4540.689"
+ ry="0.610479"
+ inkscape:label="Sablia" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect890"
+ width="88.138931"
+ height="30.135736"
+ x="2427.0608"
+ y="4148.6865"
+ ry="0.610479"
+ inkscape:label="Gdov" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect892"
+ width="161.37201"
+ height="31.107857"
+ x="3152.9109"
+ y="5214.4546"
+ ry="0.610479"
+ inkscape:label="Dubrovno" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect894"
+ width="115.3583"
+ height="30.459776"
+ x="2745.9165"
+ y="5716.7168"
+ ry="0.610479"
+ inkscape:label="Ostrov" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect896"
+ width="145.81808"
+ height="30.459776"
+ x="2045.9896"
+ y="6307.4419"
+ ry="0.610479"
+ inkscape:label="Rositten" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect898"
+ width="174.98169"
+ height="30.135736"
+ x="1876.8407"
+ y="5389.436"
+ ry="0.610479"
+ inkscape:label="Kirrumpäh" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+ id="rect900"
+ width="118.43668"
+ height="29.811697"
+ x="516.68207"
+ y="4580.3081"
+ ry="0.610479"
+ inkscape:label="Pernau" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#efc300;stroke-opacity:1"
+ id="rect902"
+ width="123.13527"
+ height="30.783817"
+ x="2371.002"
+ y="3548.8879"
+ ry="0.610479"
+ inkscape:label="Narwia" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904"
+ cx="2099.4792"
+ cy="3729.7024"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Wierland" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-width:0.938948;stroke-opacity:1"
+ id="rect892-8"
+ width="141.99065"
+ height="31.168909"
+ x="292.25372"
+ y="3796.748"
+ ry="0.61167711"
+ inkscape:label="Warbola" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-3"
+ cx="1635.0789"
+ cy="4162.5376"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Waiga"
+ sodipodi:insensitive="true" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-6"
+ cx="1163.9856"
+ cy="3996.0444"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Jerwen" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7"
+ cx="667.22955"
+ cy="4033.4651"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Harrien" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-5"
+ cx="716.7218"
+ cy="4819.083"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Sackala" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-35"
+ cx="608.5719"
+ cy="5275.5122"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Metsepole" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-62"
+ cx="2148.3323"
+ cy="5827.2598"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Lettgallia" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-9"
+ cx="1640.578"
+ cy="5983.0688"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Tolowa" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-1"
+ cx="2056.6799"
+ cy="4989.5566"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Ugaunia" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-4"
+ cx="1130.2521"
+ cy="3460.1011"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Revala" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-3"
+ cx="3129.4712"
+ cy="6140.2524"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Velikaya River" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-1"
+ cx="3399.3877"
+ cy="5830.9258"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Sorot River" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-49"
+ cx="3754.0825"
+ cy="4914.4019"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Shelon River" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-2"
+ cx="2881.5515"
+ cy="4635.7783"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Zhelcha River" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-0"
+ cx="2928.7524"
+ cy="4283.8335"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Plyussa River" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-6"
+ cx="3919.5151"
+ cy="3689.009"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Ingria" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-8"
+ cx="3587.7727"
+ cy="3394.645"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Vod"
+ sodipodi:insensitive="true" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-9"
+ cx="4173.6377"
+ cy="3372.6101"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Izhora" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-26"
+ cx="3932.5515"
+ cy="2457.5205"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Karelia" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1468"
+ width="237.84526"
+ height="89.759171"
+ x="4618.8687"
+ y="2816.719"
+ ry="0.610479"
+ inkscape:label="Ladoga" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1470"
+ width="333.15622"
+ height="112.27441"
+ x="4318.2031"
+ y="4315.4531"
+ ry="0.610479"
+ inkscape:label="Novgorod" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1472"
+ width="205.44145"
+ height="91.54155"
+ x="4328.8525"
+ y="5165.6865"
+ ry="0.610479"
+ inkscape:label="Rusa" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1474"
+ width="205.44148"
+ height="91.217155"
+ x="272.84183"
+ y="6230.9683"
+ ry="0.610479"
+ inkscape:label="Riga"
+ sodipodi:insensitive="true" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1476"
+ width="205.07263"
+ height="90.965332"
+ x="2679.9163"
+ y="5263.1392"
+ ry="0.610479"
+ inkscape:label="Pskov" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1478"
+ width="252.50232"
+ height="90.965202"
+ x="1625.2262"
+ y="4588.5771"
+ ry="0.610479"
+ inkscape:label="Dorpat" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1480"
+ width="205.2198"
+ height="90.645508"
+ x="107.90538"
+ y="4265.9893"
+ ry="0.610479"
+ inkscape:label="Leal" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1482"
+ width="205.75963"
+ height="90.735939"
+ x="601.01062"
+ y="3563.9038"
+ ry="0.610479"
+ inkscape:label="Reval" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1506"
+ width="591.99841"
+ height="916.72931"
+ x="39.683407"
+ y="168.04262"
+ ry="0.610479"
+ inkscape:label="box1" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1508"
+ width="590.72522"
+ height="913.46924"
+ x="649.70056"
+ y="168.17685"
+ ry="0.610479"
+ inkscape:label="box2" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1510"
+ width="591.04926"
+ height="916.38562"
+ x="1313.0107"
+ y="167.20473"
+ ry="0.610479"
+ inkscape:label="box3" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1512"
+ width="589.75311"
+ height="916.38562"
+ x="1922.2063"
+ y="167.20473"
+ ry="0.610479"
+ inkscape:label="box4" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1514"
+ width="592.34546"
+ height="917.68176"
+ x="2587.1367"
+ y="167.20473"
+ ry="0.610479"
+ inkscape:label="box5" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1516"
+ width="588.45697"
+ height="916.38562"
+ x="3196.3323"
+ y="167.20473"
+ ry="0.610479"
+ inkscape:label="box6" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1518"
+ width="593.6416"
+ height="916.38562"
+ x="3858.6704"
+ y="167.20473"
+ ry="0.610479"
+ inkscape:label="box7" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1520"
+ width="591.04926"
+ height="917.68176"
+ x="4469.1621"
+ y="165.90857"
+ ry="0.610479"
+ inkscape:label="box8" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1522"
+ width="593.6416"
+ height="915.08942"
+ x="38.884819"
+ y="1118.5867"
+ ry="0.610479"
+ inkscape:label="box9" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1524"
+ width="217.2162"
+ height="215.38313"
+ x="172.3065"
+ y="183.30479"
+ ry="0.610479"
+ inkscape:label="Victory" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1526"
+ width="216.29967"
+ height="213.55009"
+ x="398.68796"
+ y="184.22133"
+ ry="0.610479"
+ inkscape:label="Turn" />
+ <rect
+ style="fill:none;stroke:#000000"
+ id="rect74"
+ width="843.68152"
+ height="627.99255"
+ x="4192.9326"
+ y="5846.7227"
+ ry="0.610479"
+ inkscape:label="Novgorod Veche" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path904-7-36"
+ cx="2212.0798"
+ cy="4741.5249"
+ rx="100.02692"
+ ry="50.425739"
+ inkscape:label="Uzmen" />
+ <rect
+ style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+ id="rect895"
+ width="461.87738"
+ height="148.64993"
+ x="1499.7745"
+ y="4716.9902"
+ ry="0"
+ inkscape:label="way-crossroads" />
+ <rect
+ style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+ id="rect897"
+ width="175.19495"
+ height="350.38925"
+ x="1295.3806"
+ y="4525.8687"
+ inkscape:label="way-wirz" />
+ <rect
+ style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+ id="rect899"
+ width="220.32043"
+ height="464.53174"
+ x="2232.4082"
+ y="4196.7144"
+ inkscape:label="way-peipus-east" />
+ <rect
+ style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+ id="rect901"
+ width="361.00775"
+ height="228.28438"
+ x="2065.1765"
+ y="3835.7068"
+ ry="2.6544683"
+ inkscape:label="way-peipus-north" />
+ <rect
+ style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+ id="rect903"
+ width="217.66652"
+ height="520.27612"
+ x="1988.1968"
+ y="4140.9712"
+ ry="0"
+ inkscape:label="way-peipus-west" />
+ <rect
+ style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+ id="rect77"
+ width="431.35098"
+ height="84.279167"
+ x="4397.7905"
+ y="5836.5122"
+ inkscape:label="veche-label-top" />
+ <rect
+ style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+ id="rect79"
+ width="362.33572"
+ height="60.389202"
+ x="4426.3257"
+ y="6428.459"
+ inkscape:label="veche-label-bottom" />
+</svg>
diff --git a/tools/build_cards1.sh b/tools/build_cards1.sh
new file mode 100644
index 0000000..3459345
--- /dev/null
+++ b/tools/build_cards1.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+# Extract, crop, and rename card images to create 300 dpi PNG versions.
+
+function extract_images {
+ echo "$1"
+ mutool extract -r "$1"
+ mkdir -p $2
+ mv image* $2
+ rm -f font*
+}
+
+extract_images "../HIRES/Nevsky_Arts of War Cards_42 Anverses.pdf" tmp/aow_front
+extract_images "../HIRES/Nevsky_Arts of War Cards_42 Backs.pdf" tmp/aow_back
+extract_images "../HIRES/Nevsky_Arts of War Cards_R1 and R2 Anverses_Corrected.pdf" tmp/aow_corr
+extract_images "../HIRES/Nevsky_Command Cards_42 Anverses.pdf" tmp/cc_front
+extract_images "../HIRES/Nevsky_Command Cards_42 Backs.pdf" tmp/cc_back
+
+# cards are 63.5 x 89.0 mm - 2.5 x 3.5 inches - 750 x 1050 pixels
+# rounded to multiple of 12 = 744 x 1044
+for IM in tmp/aow_*/*.png tmp/cc_*/*.png
+do
+ echo $IM
+ convert $IM -gravity Center -crop 744x1040+0+0 +repage ${IM/png/ppm}
+done
+
+mkdir -p cards300
+pnmtopng tmp/aow_back/image-0083.ppm >cards300/aow_russian_back.png
+pnmtopng tmp/aow_back/image-0102.ppm >cards300/aow_teutonic_back.png
+pnmtopng tmp/aow_corr/image-0004.ppm >cards300/aow_russian_02.png
+pnmtopng tmp/aow_corr/image-0015.ppm >cards300/aow_russian_01.png
+pnmtopng tmp/aow_front/image-0004.ppm >cards300/aow_teutonic_02.png
+pnmtopng tmp/aow_front/image-0008.ppm >cards300/aow_teutonic_03.png
+pnmtopng tmp/aow_front/image-0012.ppm >cards300/aow_teutonic_04.png
+pnmtopng tmp/aow_front/image-0016.ppm >cards300/aow_teutonic_05.png
+pnmtopng tmp/aow_front/image-0020.ppm >cards300/aow_teutonic_06.png
+pnmtopng tmp/aow_front/image-0024.ppm >cards300/aow_teutonic_07.png
+pnmtopng tmp/aow_front/image-0028.ppm >cards300/aow_teutonic_08.png
+pnmtopng tmp/aow_front/image-0032.ppm >cards300/aow_teutonic_09.png
+pnmtopng tmp/aow_front/image-0036.ppm >cards300/aow_teutonic_10.png
+pnmtopng tmp/aow_front/image-0040.ppm >cards300/aow_teutonic_11.png
+pnmtopng tmp/aow_front/image-0044.ppm >cards300/aow_teutonic_12.png
+pnmtopng tmp/aow_front/image-0048.ppm >cards300/aow_teutonic_13.png
+pnmtopng tmp/aow_front/image-0052.ppm >cards300/aow_teutonic_14.png
+pnmtopng tmp/aow_front/image-0056.ppm >cards300/aow_teutonic_15.png
+pnmtopng tmp/aow_front/image-0060.ppm >cards300/aow_teutonic_16.png
+pnmtopng tmp/aow_front/image-0064.ppm >cards300/aow_teutonic_17.png
+pnmtopng tmp/aow_front/image-0068.ppm >cards300/aow_teutonic_18.png
+# pnmtopng tmp/aow_front/image-0078.ppm >cards300/aow_russian_01.ppm # corrected
+# pnmtopng tmp/aow_front/image-0082.ppm >cards300/aow_russian_02.ppm # corrected
+pnmtopng tmp/aow_front/image-0086.ppm >cards300/aow_russian_03.png
+pnmtopng tmp/aow_front/image-0090.ppm >cards300/aow_russian_04.png
+pnmtopng tmp/aow_front/image-0094.ppm >cards300/aow_russian_05.png
+pnmtopng tmp/aow_front/image-0098.ppm >cards300/aow_russian_06.png
+pnmtopng tmp/aow_front/image-0102.ppm >cards300/aow_russian_07.png
+pnmtopng tmp/aow_front/image-0106.ppm >cards300/aow_russian_08.png
+pnmtopng tmp/aow_front/image-0110.ppm >cards300/aow_russian_09.png
+pnmtopng tmp/aow_front/image-0114.ppm >cards300/aow_russian_10.png
+pnmtopng tmp/aow_front/image-0118.ppm >cards300/aow_russian_11.png
+pnmtopng tmp/aow_front/image-0122.ppm >cards300/aow_russian_12.png
+pnmtopng tmp/aow_front/image-0126.ppm >cards300/aow_russian_13.png
+pnmtopng tmp/aow_front/image-0130.ppm >cards300/aow_russian_14.png
+pnmtopng tmp/aow_front/image-0134.ppm >cards300/aow_russian_15.png
+pnmtopng tmp/aow_front/image-0138.ppm >cards300/aow_russian_16.png
+pnmtopng tmp/aow_front/image-0142.ppm >cards300/aow_russian_17.png
+pnmtopng tmp/aow_front/image-0146.ppm >cards300/aow_russian_18.png
+pnmtopng tmp/aow_front/image-0153.ppm >cards300/aow_teutonic_none.png
+pnmtopng tmp/aow_front/image-0155.ppm >cards300/aow_russian_none.png
+pnmtopng tmp/aow_front/image-0175.ppm >cards300/aow_teutonic_01.png
+pnmtopng tmp/cc_back/image-0083.ppm >cards300/cc_russian_back.png
+pnmtopng tmp/cc_back/image-0102.ppm >cards300/cc_teutonic_back.png
+pnmtopng tmp/cc_front/image-0083.ppm >cards300/cc_teutonic_andreas.png
+pnmtopng tmp/cc_front/image-0085.ppm >cards300/cc_teutonic_pass.png
+pnmtopng tmp/cc_front/image-0087.ppm >cards300/cc_teutonic_heinrich.png
+pnmtopng tmp/cc_front/image-0089.ppm >cards300/cc_teutonic_rudolf.png
+pnmtopng tmp/cc_front/image-0091.ppm >cards300/cc_teutonic_knud_and_abel.png
+pnmtopng tmp/cc_front/image-0093.ppm >cards300/cc_teutonic_yaroslav.png
+pnmtopng tmp/cc_front/image-0095.ppm >cards300/cc_russian_aleksandr.png
+pnmtopng tmp/cc_front/image-0097.ppm >cards300/cc_russian_domash.png
+pnmtopng tmp/cc_front/image-0099.ppm >cards300/cc_russian_vladislav.png
+pnmtopng tmp/cc_front/image-0101.ppm >cards300/cc_russian_pass.png
+pnmtopng tmp/cc_front/image-0103.ppm >cards300/cc_russian_karelians.png
+pnmtopng tmp/cc_front/image-0105.ppm >cards300/cc_russian_andrey.png
+pnmtopng tmp/cc_front/image-0107.ppm >cards300/cc_russian_gavrilo.png
+pnmtopng tmp/cc_front/image-0126.ppm >cards300/cc_teutonic_hermann.png
+
+rm -rf tmp
diff --git a/tools/build_cards2.sh b/tools/build_cards2.sh
new file mode 100644
index 0000000..bcf4062
--- /dev/null
+++ b/tools/build_cards2.sh
@@ -0,0 +1,10 @@
+# 300dpi @ 744 x 1044
+
+mkdir -p cards75 cards150
+
+for IM in cards300/*.png
+do
+ echo $IM
+ convert $IM -colorspace RGB -resize 25% -colorspace sRGB ${IM/cards300/cards75}
+ convert $IM -colorspace RGB -resize 50% -colorspace sRGB ${IM/cards300/cards150}
+done
diff --git a/tools/build_counters1.sh b/tools/build_counters1.sh
new file mode 100644
index 0000000..a8573e2
--- /dev/null
+++ b/tools/build_counters1.sh
@@ -0,0 +1,117 @@
+# 3 counter sheet dies
+# 1 and 3 has square counters, only bottom row differs
+# 2 has rectangular tiles
+
+# circular (sheet 1 bottom) 210x210
+# large square 190x190 (188-190-ish)
+# rectangle 380x190 (188-190-ish)
+# small square 150x150
+
+mkdir -p tmp
+
+# large squares (top sheet 1 and 3)
+ROW=1
+for TOP in 340 586 833 1080 1326
+do
+ COL=1
+ RCOL=12
+ for LEFT in 304 551 797 1043 1290 1536 1874 2121 2367 2613 2860 3106
+ do
+ echo large square $ROW $COL
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_sq_large_1_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_sq_large_1_${ROW}_${RCOL}_b.ppm
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-3F-nf.ppm > tmp/cs_sq_large_3_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-3B-nf.ppm > tmp/cs_sq_large_3_${ROW}_${RCOL}_b.ppm
+ COL=$(expr $COL + 1)
+ RCOL=$(expr $RCOL - 1)
+ done
+ ROW=$(expr $ROW + 1)
+done
+
+# large squares (bottom sheet 2)
+ROW=1
+for TOP in 2075 2322
+do
+ COL=1
+ RCOL=12
+ for LEFT in 304 551 797 1043 1290 1536 1874 2121 2367 2613 2860 3106
+ do
+ echo large square $ROW $COL
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-2F-nf.ppm > tmp/cs_sq_large_2_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-2B-nf.ppm > tmp/cs_sq_large_2_${ROW}_${RCOL}_b.ppm
+ COL=$(expr $COL + 1)
+ RCOL=$(expr $RCOL - 1)
+ done
+ ROW=$(expr $ROW + 1)
+done
+
+# small squares (bottom sheet 1 and 3)
+ROW=1
+for TOP in 1595 1745 1970 2120 2345
+do
+ COL=1
+ RCOL=4
+ for LEFT in 305 1275 2175 2695
+ do
+ echo small square $ROW $COL
+ pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_sq_small_1_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_sq_small_1_${ROW}_${RCOL}_b.ppm
+ pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-3F-nf.ppm > tmp/cs_sq_small_3_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-3B-nf.ppm > tmp/cs_sq_small_3_${ROW}_${RCOL}_b.ppm
+ COL=$(expr $COL + 1)
+ RCOL=$(expr $RCOL - 1)
+ done
+ ROW=$(expr $ROW + 1)
+done
+
+# large rects (top sheet 2)
+ROW=1
+for TOP in 340 586 833 1080 1326 1572 1818
+do
+ COL=1
+ RCOL=7
+ TOP=$(expr $TOP + 1)
+ for LEFT in 304 739 1175 1611 2047 2483 2919
+ do
+ echo rectangle $ROW $COL
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-2F-nf.ppm > tmp/cs_rect_2_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-2B-nf.ppm > tmp/cs_rect_2_${ROW}_${RCOL}_b.ppm
+ COL=$(expr $COL + 1)
+ RCOL=$(expr $RCOL - 1)
+ done
+ ROW=$(expr $ROW + 1)
+done
+
+# circles (sheet 1)
+ROW=1
+for TOP in 2332
+do
+ COL=1
+ RCOL=6
+ for LEFT in 987 1253 1518 1874 2139 2404
+ do
+ echo circle $ROW $COL
+ pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 204 -height 204 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_circle_1_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 204 -height 204 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_circle_1_${ROW}_${RCOL}_b.ppm
+ COL=$(expr $COL + 1)
+ RCOL=$(expr $RCOL - 1)
+ done
+ ROW=$(expr $ROW + 1)
+done
+
+# rectangles (sheet 1)
+ROW=1
+for TOP in 2342
+do
+ COL=1
+ RCOL=2
+ for LEFT in 416 2806
+ do
+ echo rectangle $ROW $COL
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_rect_1_${ROW}_${COL}_a.ppm
+ pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_rect_1_${ROW}_${RCOL}_b.ppm
+ COL=$(expr $COL + 1)
+ RCOL=$(expr $RCOL - 1)
+ done
+ ROW=$(expr $ROW + 1)
+done
diff --git a/tools/build_counters2.sh b/tools/build_counters2.sh
new file mode 100644
index 0000000..ad7bc5d
--- /dev/null
+++ b/tools/build_counters2.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+
+# set -x
+
+mkdir -p counters300
+
+function marker {
+ pnmtopng tmp/${1}_a.ppm > counters300/$2.a.png
+ pnmtopng tmp/${1}_b.ppm > counters300/$2.b.png
+}
+
+function marker2 {
+ pnmtopng tmp/${1}_a.ppm > counters300/$2.png
+ pnmtopng tmp/${1}_b.ppm > counters300/$3.png
+}
+
+marker2 cs_circle_1_1_1 marker_battle marker_storm
+marker2 cs_circle_1_1_2 marker_levy marker_campaign
+marker2 cs_circle_1_1_3 marker_victory_teutonic marker_victory_half_teutonic
+marker2 cs_circle_1_1_4 marker_victory_russian marker_victory_half_russian
+marker2 cs_circle_1_1_5 marker_enemy_lords_removed_teutonic marker_pleskau_blue
+marker2 cs_circle_1_1_6 marker_enemy_lords_removed_russian makrer_pleskau_gray
+
+marker2 cs_sq_small_1_1_2 marker_ravaged_teutonic marker_ravaged_russian
+
+marker cs_sq_small_1_1_1 unit_men_at_arms
+marker cs_sq_small_3_1_1 unit_knights
+marker cs_sq_small_3_1_3 unit_asiatic_horse
+marker cs_sq_small_3_1_4 unit_militia
+marker cs_sq_small_3_3_1 unit_light_horse
+marker cs_sq_small_3_3_3 unit_serfs
+marker cs_sq_small_3_5_1 unit_sergeants
+
+marker2 cs_sq_large_1_1_7 marker_conquered_teutonic marker_conquered_russian
+marker2 cs_sq_large_1_4_11 marker_moved_fought marker_supply_source
+marker2 cs_sq_large_2_1_1 marker_siege_teutonic marker_siege_russian
+marker cs_sq_large_2_1_8 marker_walls
+
+marker2 cs_sq_large_2_1_10 marker_1_teutonic marker_1_russian
+marker2 cs_sq_large_2_1_11 marker_2_teutonic marker_2_russian
+marker2 cs_sq_large_2_1_12 marker_3_teutonic marker_3_russian
+marker2 cs_sq_large_2_2_10 marker_4_teutonic marker_4_russian
+marker2 cs_sq_large_2_2_11 marker_5_teutonic marker_5_russian
+marker2 cs_sq_large_2_2_12 marker_6_teutonic marker_6_russian
+
+marker2 cs_sq_large_1_1_2 asset_coin_x1 asset_coin_x2
+marker2 cs_sq_large_1_3_1 asset_coin_x3 asset_coin_x4
+
+marker2 cs_sq_large_1_3_3 asset_loot_x1 asset_loot_x2
+marker2 cs_sq_large_1_4_6 asset_loot_x3 asset_loot_x4
+
+marker2 cs_sq_large_1_5_1 asset_prov_x1 asset_prov_x2
+marker2 cs_sq_large_3_4_10 asset_prov_x3 asset_prov_x4
+
+marker2 cs_sq_large_3_1_1 asset_boat_x1 asset_ship_x1
+marker2 cs_sq_large_3_4_1 asset_boat_x2 asset_ship_x2
+marker2 cs_sq_large_3_4_2 asset_boat_x4 asset_ship_x4
+
+marker2 cs_sq_large_3_1_7 asset_sled_x1 asset_cart_x1
+marker2 cs_sq_large_3_4_4 asset_sled_x2 asset_cart_x2
+marker2 cs_sq_large_3_5_6 asset_sled_x4 asset_cart_x4
+
+marker cs_rect_1_1_1 marker_sea_trade_blocked
+
+marker cs_rect_2_1_1 lord_russian_aleksandr
+marker cs_rect_2_1_2 lord_russian_andrey
+marker cs_rect_2_1_3 vassal_russian_gavrilo_pskov
+marker cs_rect_2_1_4 vassal_russian_andrey_vladimir
+marker cs_rect_2_1_5 vassal_russian_domash_novgorod
+marker cs_rect_2_1_6 vassal_russian_andrey_kipchaqs
+marker cs_rect_2_1_7 vassal_teutonic_knud_and_abel_dietrich_von_kivel
+
+marker cs_rect_2_2_1 lord_russian_vladislav
+marker cs_rect_2_2_2 lord_russian_karelians
+marker cs_rect_2_2_3 vassal_russian_aleksandr_pereyaslavl
+marker cs_rect_2_2_4 vassal_russian_andrey_suzdal
+marker cs_rect_2_2_5 lord_teutonic_andreas
+marker cs_rect_2_2_6 vassal_teutonic_andreas_summer_crusaders
+marker cs_rect_2_2_7 vassal_teutonic_knud_and_abel_otto_von_luneburg
+
+marker cs_rect_2_3_1 lord_russian_domash
+marker cs_rect_2_3_2 vassal_russian_vladislav_vepsian_auxiliaries
+marker cs_rect_2_3_3 vassal_russian_aleksandr_rostov
+marker cs_rect_2_3_4 vassal_teutonic_hermann_ugaunian_auxiliaries
+marker cs_rect_2_3_5 lord_teutonic_hermann
+marker cs_rect_2_3_6 lord_teutonic_knud_and_abel
+marker cs_rect_2_3_7 vassal_teutonic_knud_and_abel_estonian_auxiliaries
+
+marker cs_rect_2_4_1 lord_russian_gavrilo
+marker cs_rect_2_4_2 vassal_russian_vladislav_ingrian_auxiliaries
+marker cs_rect_2_4_3 vassal_russian_aleksandr_yaroslavl
+marker cs_rect_2_4_4 vassal_teutonic_hermann_helmond_von_luneburg
+marker cs_rect_2_4_5 lord_teutonic_heinrich
+marker cs_rect_2_4_6 lord_teutonic_yaroslav
+marker2 cs_rect_2_4_7 marker_castle_teutonic marker_castle_russian
+
+marker cs_rect_2_5_1 vassal_russian_vladislav_vodian_auxiliaries
+marker cs_rect_2_5_2 vassal_russian_vladislav_izhoran_auxiliaries
+marker cs_rect_2_5_3 vassal_russian_aleksandr_mongols
+marker cs_rect_2_5_4 vassal_teutonic_hermann_johannes_von_dolen
+marker cs_rect_2_5_5 lord_teutonic_rudolf
+marker cs_rect_2_5_6 vassal_teutonic_rudolf_summer_crusaders
+#marker2 cs_rect_2_5_7 marker_castle_teutonic marker_castle_russian
+
+marker cs_rect_2_6_1 vassal_russian_gavrilo_borderland_russians
+marker cs_rect_2_6_2 vassal_russian_domash_novgorod
+#marker cs_rect_2_6_3 vassal_russian_aleksandr_mongols
+marker cs_rect_2_6_4 vassal_teutonic_heinrich_odward_von_lode
+marker cs_rect_2_6_5 vassal_teutonic_andreas_teutonic_vassals
+marker cs_rect_2_6_6 vassal_teutonic_rudolf_ex_sword_brethren
+marker cs_rect_2_6_7 vassal_teutonic_yaroslav_mstislavich_partisans
+
+marker cs_rect_2_7_1 vassal_russian_gavrilo_pskov_militia
+marker cs_rect_2_7_2 vassal_russian_domash_novgorod
+#marker cs_rect_2_7_3 vassal_russian_andrey_kipchaqs
+marker cs_rect_2_7_4 vassal_teutonic_heinrich_heinrich_von_lode
+marker cs_rect_2_7_5 vassal_teutonic_andreas_lettgallian_auxiliaries
+marker cs_rect_2_7_6 vassal_teutonic_rudolf_jerwen_teutonic_vassals
+marker2 cs_rect_2_7_7 marker_pursuit_teutonic marker_pursuit_russian
diff --git a/tools/build_counters3.sh b/tools/build_counters3.sh
new file mode 100644
index 0000000..142d802
--- /dev/null
+++ b/tools/build_counters3.sh
@@ -0,0 +1,5 @@
+mkdir -p service300
+montage -mode concatenate -tile 2x counters300/lord_teutonic_andreas.a.png counters300/lord_teutonic_andreas.b.png counters300/lord_teutonic_heinrich.a.png counters300/lord_teutonic_heinrich.b.png counters300/lord_teutonic_hermann.a.png counters300/lord_teutonic_hermann.b.png counters300/lord_teutonic_knud_and_abel.a.png counters300/lord_teutonic_knud_and_abel.b.png counters300/lord_teutonic_rudolf.a.png counters300/lord_teutonic_rudolf.b.png counters300/lord_teutonic_yaroslav.a.png counters300/lord_teutonic_yaroslav.b.png service300/service_lords_teutonic.png
+montage -mode concatenate -tile 2x counters300/vassal_teutonic_andreas_lettgallian_auxiliaries.a.png counters300/vassal_teutonic_andreas_lettgallian_auxiliaries.b.png counters300/vassal_teutonic_andreas_summer_crusaders.a.png counters300/vassal_teutonic_andreas_summer_crusaders.b.png counters300/vassal_teutonic_andreas_teutonic_vassals.a.png counters300/vassal_teutonic_andreas_teutonic_vassals.b.png counters300/vassal_teutonic_heinrich_heinrich_von_lode.a.png counters300/vassal_teutonic_heinrich_heinrich_von_lode.b.png counters300/vassal_teutonic_heinrich_odward_von_lode.a.png counters300/vassal_teutonic_heinrich_odward_von_lode.b.png counters300/vassal_teutonic_hermann_helmond_von_luneburg.a.png counters300/vassal_teutonic_hermann_helmond_von_luneburg.b.png counters300/vassal_teutonic_hermann_johannes_von_dolen.a.png counters300/vassal_teutonic_hermann_johannes_von_dolen.b.png counters300/vassal_teutonic_hermann_ugaunian_auxiliaries.a.png counters300/vassal_teutonic_hermann_ugaunian_auxiliaries.b.png counters300/vassal_teutonic_knud_and_abel_dietrich_von_kivel.a.png counters300/vassal_teutonic_knud_and_abel_dietrich_von_kivel.b.png counters300/vassal_teutonic_knud_and_abel_estonian_auxiliaries.a.png counters300/vassal_teutonic_knud_and_abel_estonian_auxiliaries.b.png counters300/vassal_teutonic_knud_and_abel_otto_von_luneburg.a.png counters300/vassal_teutonic_knud_and_abel_otto_von_luneburg.b.png counters300/vassal_teutonic_rudolf_ex_sword_brethren.a.png counters300/vassal_teutonic_rudolf_ex_sword_brethren.b.png counters300/vassal_teutonic_rudolf_jerwen_teutonic_vassals.a.png counters300/vassal_teutonic_rudolf_jerwen_teutonic_vassals.b.png counters300/vassal_teutonic_rudolf_summer_crusaders.a.png counters300/vassal_teutonic_rudolf_summer_crusaders.b.png counters300/vassal_teutonic_yaroslav_mstislavich_partisans.a.png counters300/vassal_teutonic_yaroslav_mstislavich_partisans.b.png service300/service_vassals_teutonic.png
+montage -mode concatenate -tile 2x counters300/lord_russian_aleksandr.a.png counters300/lord_russian_aleksandr.b.png counters300/lord_russian_andrey.a.png counters300/lord_russian_andrey.b.png counters300/lord_russian_domash.a.png counters300/lord_russian_domash.b.png counters300/lord_russian_gavrilo.a.png counters300/lord_russian_gavrilo.b.png counters300/lord_russian_karelians.a.png counters300/lord_russian_karelians.b.png counters300/lord_russian_vladislav.a.png counters300/lord_russian_vladislav.b.png service300/service_lords_russian.png
+montage -mode concatenate -tile 2x counters300/vassal_russian_aleksandr_mongols.a.png counters300/vassal_russian_aleksandr_mongols.b.png counters300/vassal_russian_aleksandr_pereyaslavl.a.png counters300/vassal_russian_aleksandr_pereyaslavl.b.png counters300/vassal_russian_aleksandr_rostov.a.png counters300/vassal_russian_aleksandr_rostov.b.png counters300/vassal_russian_aleksandr_yaroslavl.a.png counters300/vassal_russian_aleksandr_yaroslavl.b.png counters300/vassal_russian_andrey_kipchaqs.a.png counters300/vassal_russian_andrey_kipchaqs.b.png counters300/vassal_russian_andrey_suzdal.a.png counters300/vassal_russian_andrey_suzdal.b.png counters300/vassal_russian_andrey_vladimir.a.png counters300/vassal_russian_andrey_vladimir.b.png counters300/vassal_russian_domash_novgorod.a.png counters300/vassal_russian_domash_novgorod.b.png counters300/vassal_russian_gavrilo_borderland_russians.a.png counters300/vassal_russian_gavrilo_borderland_russians.b.png counters300/vassal_russian_gavrilo_pskov_militia.a.png counters300/vassal_russian_gavrilo_pskov_militia.b.png counters300/vassal_russian_gavrilo_pskov.a.png counters300/vassal_russian_gavrilo_pskov.b.png counters300/vassal_russian_vladislav_izhoran_auxiliaries.a.png counters300/vassal_russian_vladislav_izhoran_auxiliaries.b.png counters300/vassal_russian_vladislav_ingrian_auxiliaries.a.png counters300/vassal_russian_vladislav_ingrian_auxiliaries.b.png counters300/vassal_russian_vladislav_vepsian_auxiliaries.a.png counters300/vassal_russian_vladislav_vepsian_auxiliaries.b.png counters300/vassal_russian_vladislav_vodian_auxiliaries.a.png counters300/vassal_russian_vladislav_vodian_auxiliaries.b.png service300/service_vassals_russian.png
diff --git a/tools/build_map.sh b/tools/build_map.sh
new file mode 100644
index 0000000..1af18c4
--- /dev/null
+++ b/tools/build_map.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -x
+
+mutool extract -r "../HIRES/Nevsky - Game Board_CORRECTED 080919.pdf"
+mv image-0017.png map300_uncropped.png
+
+# ArtBox 63.5197 63.5197 1287.52 1647.52
+# 5100x6600
+convert map300_uncropped.png -gravity Center -crop 5100x6600+0+0 +repage map300.png
+rm map300_uncropped.png
+
+# convert map300.png -colorspace RGB -resize 33.3333333% -colorspace sRGB map100.png
+# convert map300.png -colorspace RGB -resize 66.6666667% -colorspace sRGB map200.png
+convert map300.png -colorspace RGB -resize 25% -colorspace sRGB map75.png
+convert map300.png -colorspace RGB -resize 50% -colorspace sRGB map150.png
diff --git a/tools/build_mats.sh b/tools/build_mats.sh
new file mode 100644
index 0000000..a9e348b
--- /dev/null
+++ b/tools/build_mats.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+set -x
+
+mkdir -p tmp/mat
+mkdir -p output300
+
+if [ ! -f ../HIRES/raw_mat_01.1200.png ]
+then
+ gs -sDEVICE=png16m -r1200 -o ../HIRES/raw_mat_%02d.1200.png "../HIRES/Lord Mats-FINAL-nf.pdf"
+fi
+
+for F in ../HIRES/raw_mat_*.1200.png
+do
+ convert -colorspace RGB -scale 25% -colorspace sRGB $F ${F/.1200.png/.png}
+done
+
+# crop: 150,150 to 1650,1650 = 1500x1500
+
+# ARGS="-gravity Center -crop 1500x1500+0+0 +repage -background #d0bf7d -flatten"
+ARGS="-gravity Center -crop 1440x1440+0+0 +repage -background #d0bf7d -flatten"
+
+convert ../HIRES/raw_mat_01.png $ARGS output300/mat_russian_aleksandr.png
+convert ../HIRES/raw_mat_02.png $ARGS output300/mat_russian_andrey.png
+convert ../HIRES/raw_mat_03.png $ARGS output300/mat_russian_domash.png
+convert ../HIRES/raw_mat_04.png $ARGS output300/mat_russian_gavrilo.png
+convert ../HIRES/raw_mat_05.png $ARGS output300/mat_russian_vladislav.png
+convert ../HIRES/raw_mat_06.png $ARGS output300/mat_russian_karelians.png
+convert ../HIRES/raw_mat_07.png $ARGS output300/mat_teutonic_yaroslav.png
+convert ../HIRES/raw_mat_08.png $ARGS output300/mat_teutonic_knud_and_abel.png
+convert ../HIRES/raw_mat_09.png $ARGS output300/mat_teutonic_rudolf.png
+convert ../HIRES/raw_mat_10.png $ARGS output300/mat_teutonic_heinrich.png
+convert ../HIRES/raw_mat_11.png $ARGS output300/mat_teutonic_hermann.png
+convert ../HIRES/raw_mat_12.png $ARGS output300/mat_teutonic_andreas.png
+convert ../HIRES/raw_mat_13.png $ARGS output300/mat_battle.png
diff --git a/tools/build_screen.sh b/tools/build_screen.sh
new file mode 100644
index 0000000..607ca7b
--- /dev/null
+++ b/tools/build_screen.sh
@@ -0,0 +1,10 @@
+mkdir -p tmp
+convert -size 2160x750 gradient:white-none tmp/gradient.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen1.png tmp/screen1.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen2.png tmp/screen2.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen3.png tmp/screen3.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen4.png tmp/screen4.png
+convert tmp/screen1.png gradient.png -compose Over -flatten screen1.png
+convert tmp/screen2.png gradient.png -compose Over -flatten screen2.png
+convert tmp/screen3.png gradient.png -compose Over -flatten screen3.png
+convert tmp/screen4.png gradient.png -compose Over -flatten screen4.png
diff --git a/tools/build_stickers.sh b/tools/build_stickers.sh
new file mode 100644
index 0000000..3e49e69
--- /dev/null
+++ b/tools/build_stickers.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+set -x
+
+#gs -sDEVICE=png16m -r1200 -o tmp/stickers1200.png ../HIRES/stickers3.pdf
+
+mkdir -p tmp
+
+# 672x672 stickers @1200
+#pngtopnm tmp/stickers1200.png > tmp/stickers1200.ppm
+pnmcut -top 1912 -height 672 tmp/stickers1200.ppm > tmp/row1x.ppm
+pnmcut -top 2856 -height 672 tmp/stickers1200.ppm > tmp/row2x.ppm
+
+I=1
+for LEFT in 405 641 877 1113 1349 1586 1822
+do
+ pnmcut -left $(expr 4 '*' $LEFT) -width 672 tmp/row1x.ppm | pnmtopng > tmp/lord_teutonic_$I.png
+ pnmcut -left $(expr 4 '*' $LEFT) -width 672 tmp/row2x.ppm | pnmtopng > tmp/lord_russian_$I.png
+ I=$(expr $I + 1)
+done
+
+# 150dpi -> 84x84
+# 75dpi -> 42x42
+# 3d -> 42x28
+for I in 1 2 3 4 5 6 7
+do
+ convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 84x56! -colorspace sRGB output150/lord_teutonic_${I}_3d.png
+ convert tmp/lord_russian_$I.png -colorspace RGB -geometry 84x56! -colorspace sRGB output150/lord_russian_${I}_3d.png
+ convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 84x84! -colorspace sRGB output150/lord_teutonic_${I}.png
+ convert tmp/lord_russian_$I.png -colorspace RGB -geometry 84x84! -colorspace sRGB output150/lord_russian_${I}.png
+
+ convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 42x28! -colorspace sRGB output75/lord_teutonic_${I}_3d.png
+ convert tmp/lord_russian_$I.png -colorspace RGB -geometry 42x28! -colorspace sRGB output75/lord_russian_${I}_3d.png
+ convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 42x42! -colorspace sRGB output75/lord_teutonic_${I}.png
+ convert tmp/lord_russian_$I.png -colorspace RGB -geometry 42x42! -colorspace sRGB output75/lord_russian_${I}.png
+done
+
diff --git a/tools/colors.mjs b/tools/colors.mjs
new file mode 100644
index 0000000..4af8545
--- /dev/null
+++ b/tools/colors.mjs
@@ -0,0 +1,108 @@
+import { formatHex, filterBrightness, parseHex, convertRgbToOklab, convertRgbToLrgb, interpolate } from 'culori'
+
+const yuv = true;
+const gamma = 2.2;
+
+const data = [
+[".mat .background", "d1c07e"],
+[".card.teutonic", "e1e6e8"],
+[".card.russian", "e1d6c1"],
+[".service_marker.teutonic.vassal", "777474"],
+[".service_marker.russian.vassal", "f0ead8"],
+[".asset.sled", "e5dcc1"],
+[".asset.boat", "adceed"],
+[".asset.cart.x1", "daba8b"],
+[".asset.cart.x2", "d1a973"],
+[".asset.cart.x4", "c4975b"],
+[".asset.coin.x1", "d2d5d4"],
+[".asset.coin.x2", "d2d5d4"],
+[".asset.coin.x3", "b3b5b4"],
+[".asset.coin.x4", "b3b5b4"],
+[".asset.prov.x1", "ffe293"],
+[".asset.prov.x2", "ffe293"],
+[".asset.prov.x3", "ffcd66"],
+[".asset.prov.x4", "ffcd66"],
+[".asset.ship.x1", "79b7e4"],
+[".asset.ship.x2", "79b7e4"],
+[".asset.ship.x4", "5da9dd"],
+[".asset.loot.x1", "f0b64f"],
+[".asset.loot.x2", "eda44c"],
+[".asset.loot.x3", "eb924a"],
+[".asset.loot.x4", "e1884a"],
+[".marker.battle", "d0bf7d"],
+[".marker.storm", "d0bf7d"],
+[".marker.pursuit", "c6ab7f"],
+[".marker.turn.campaign", "6a8aa8"],
+[".marker.turn.levy", "967348"],
+[".marker.teutonic.victory", "ffd400"],
+[".marker.teutonic.siege", "a39382"],
+[".marker.russian.conquered", "649655"],
+[".marker.russian.enemy_lords_removed", "ffd400"],
+[".marker.russian.victory", "2d8b47"],
+[".marker.walls", "e3dedc"],
+[".marker.russian.number", "c6992f"],
+[".marker.teutonic.number", "a02532"],
+
+/*
+[".unit", "ffd768"],
+[".marker.supply_source", "e6dcb9"],
+[".marker.moved_fought", "0072bc"],
+[".marker.pleskau_black", "324b5b"],
+[".marker.pleskau_white", "e3dedc"],
+*/
+].map(([sel,color])=>[ sel, parseHex(color) ])
+
+const colors = `knights_fill ffffff
+knights_stroke d1d3d4
+asiatic_horse_fill f7df93
+asiatic_horse_stroke 908357
+serfs_fill e39c43
+serfs_stroke b17b33
+militia_fill cc6a2c
+militia_stroke 773b0c
+men_at_arms_fill c0b6b3
+men_at_arms_stroke 716c6b
+die_1 662c91
+die_2 ee161c
+die_3 f7941d
+die_4 ffd400
+die_5 26903a`
+
+let css = []
+
+function brighten(color, n) {
+ return { mode: 'lrgb',
+ r: 1 - (1-color.r) * n,
+ g: 1 - (1-color.g) * n,
+ b: 1 - (1-color.b) * n,
+ }
+}
+
+function darken(color, n) {
+ return { mode: 'lrgb',
+ r: (color.r) * n,
+ g: (color.g) * n,
+ b: (color.b) * n,
+ }
+}
+
+let white = parseHex('#fff')
+let black = parseHex('#000')
+
+for (let [ sel, rgb ] of data) {
+ let base = formatHex(rgb)
+ //let hi = formatHex(brighten(convertRgbToLrgb(rgb), 0.8))
+ //let lo = formatHex(darken(convertRgbToLrgb(rgb), 0.8))
+ //let sh = formatHex(darken(convertRgbToLrgb(rgb), 0.125))
+ //let hi = formatHex(interpolate([rgb,white],'lrgb')(0.2))
+ //let lo = formatHex(interpolate([rgb,black],'lrgb')(0.2))
+ //let sh = formatHex(interpolate([rgb,black],'lrgb')(0.6))
+ let hic = convertRgbToOklab(rgb); hic.l = Math.min(1,hic.l+0.1)
+ let loc = convertRgbToOklab(rgb); loc.l = Math.max(0,loc.l-0.1)
+ let shc = convertRgbToOklab(rgb); shc.l = Math.max(0,shc.l-0.4)
+ let sh = formatHex(shc)
+ let hi = formatHex(hic)
+ let lo = formatHex(loc)
+ css.push(`${sel} { background-color: ${base}; border-color: ${hi} ${lo} ${lo} ${hi}; box-shadow: 0 0 0 1px ${sh}, 1px 2px 4px #0008; }`)
+}
+console.log(css.join("\n"))
diff --git a/tools/genboxes.py b/tools/genboxes.py
new file mode 100644
index 0000000..92488ed
--- /dev/null
+++ b/tools/genboxes.py
@@ -0,0 +1,65 @@
+mode = None
+
+list = []
+
+x = y = w = h = 0
+name = None
+
+def flush():
+ global x, y, w, h, name
+ if mode == 'rect':
+ list.append((x,y,w,h,'box',name))
+ if mode == 'circle':
+ x = cx - rx
+ y = cy - ry
+ w = rx * 2
+ h = ry * 2
+ list.append((x,y,w,h,'circle',name))
+ x = y = w = h = 0
+ name = None
+
+for line in open("boxes.svg").readlines():
+ line = line.strip()
+ if line == "<rect":
+ flush()
+ mode = 'rect'
+ x = y = w = h = 0
+ elif line == "<ellipse":
+ flush()
+ mode = 'circle'
+ cx = cy = rx = ry = 0
+ if line.startswith('x="'): x = round(float(line.split('"')[1]))
+ if line.startswith('y="'): y = round(float(line.split('"')[1]))
+ if line.startswith('width="'): w = round(float(line.split('"')[1]))
+ if line.startswith('height="'): h = round(float(line.split('"')[1]))
+ if line.startswith('cx="'): cx = round(float(line.split('"')[1]))
+ if line.startswith('cy="'): cy = round(float(line.split('"')[1]))
+ if line.startswith('rx="'): rx = round(float(line.split('"')[1]))
+ if line.startswith('ry="'): ry = round(float(line.split('"')[1]))
+ if line.startswith('inkscape:label="'): name = line.split('"')[1]
+flush()
+
+def print_list():
+ print("const boxes = {")
+ for (x,y,w,h,c,name) in list:
+ print(f'"{name}": [{x},{y},{w},{h}],')
+ print("}")
+
+def print_html():
+ print('<html><style>')
+ print('.box{position:absolute;background-color:#f008;border:2px solid blue;}')
+ print('.circle{position:absolute;background-color:#0f08;border-radius:50%;border:2px solid blue;}')
+ print('img{position:absolute;display:block}')
+ print('</style>')
+ print('<div style="position:relative;width:1275px;heigth:1650px;">')
+ print('<img src="map75.png">')
+ for (x,y,w,h,c,name) in list:
+ x = round(x/4) - 1
+ y = round(y/4) - 1
+ w = round(w/4) + 2
+ h = round(h/4) + 2
+ print(f'<div class="{c}" style="top:{y}px;left:{x}px;width:{w-4}px;height:{h-4}px">{name}</div>')
+ print('</div>')
+
+#print_html()
+print_list()
diff --git a/tools/gencss.js b/tools/gencss.js
new file mode 100644
index 0000000..4edc4be
--- /dev/null
+++ b/tools/gencss.js
@@ -0,0 +1,5 @@
+const h = 42
+for (let i = 0; i < 15; ++i)
+ console.log(`.service_marker.image${i}{background-position:0 -${i*h}px}`)
+for (let i = 0; i < 15; ++i)
+ console.log(`.service_marker.image${i}:hover{background-position:100% -${i*h}px}`)
diff --git a/tools/gencyl.js b/tools/gencyl.js
new file mode 100644
index 0000000..bae34b9
--- /dev/null
+++ b/tools/gencyl.js
@@ -0,0 +1,51 @@
+// physical cylinders are diameter 15mm x 10mm
+// at 75dpi => 44px x 29px
+// stickers: 42x28
+// image: 44x? - outline at 1 to 2 - start at 1.5
+
+const fs = require('fs')
+
+function print_lord(output, side, label) {
+ let image = fs.readFileSync(label).toString('base64')
+ let svg = []
+ let bd = '#222'
+ let f = 'url(#g)'
+ svg.push('<svg xmlns="http://www.w3.org/2000/svg" width="44" height="48">')
+ svg.push('<clipPath id="c"><ellipse cx="22" cy="15" rx="20.5" ry="13.5"/></clipPath>')
+
+ if (1) {
+ svg.push('<linearGradient id="g">')
+ if (side === 'russian') {
+ svg.push('<stop offset="0%" stop-color="#ddd"/>')
+ svg.push('<stop offset="40%" stop-color="#fff"/>')
+ svg.push('<stop offset="100%" stop-color="#ccc"/>')
+ bd = '#555'
+ } else {
+ svg.push('<stop offset="0%" stop-color="#444"/>')
+ svg.push('<stop offset="40%" stop-color="#666"/>')
+ svg.push('<stop offset="100%" stop-color="#333"/>')
+ bd = '#111'
+ }
+ svg.push('</linearGradient>')
+ } else {
+ if (side === 'russian') {
+ f = '#ddd'
+ bd = '#222'
+ } else {
+ f = '#555'
+ bd = '#222'
+ }
+ }
+
+ svg.push(`<path fill="${f}" stroke="${bd}" d="M1.5 15v18A20.5 13.5 0 0 0 22 46.5 20.5 13.5 0 0 0 42.5 33V15h-41z"/>`)
+ svg.push(`<image href="data:image/png;base64,${image}" clip-path="url(#c)" x="1" y="1" width="42" height="28"/>`)
+ svg.push(`<ellipse fill="none" stroke="${bd}" cx="22" cy="15" rx="20.5" ry="13.5"/>`)
+
+ svg.push('</svg>')
+ fs.writeFileSync(output, svg.join("\n") + "\n")
+}
+
+for (let i = 1; i <= 7; ++i) {
+ print_lord(`images/lord_teutonic_${i}.svg`, "teutonic", `tools/output150/lord_teutonic_${i}_3d.png`)
+ print_lord(`images/lord_russian_${i}.svg`, "russian", `tools/output150/lord_russian_${i}_3d.png`)
+}
diff --git a/tools/gendata.js b/tools/gendata.js
new file mode 100644
index 0000000..2968023
--- /dev/null
+++ b/tools/gendata.js
@@ -0,0 +1,705 @@
+// Run this script inside the "tools" directory to generate data.js and build_counters3.sh
+
+"use strict"
+
+const fs = require('fs')
+
+// :r !python3 genboxes.py
+const boxes = {
+"Wesenberg": [1448,3625,304,60],
+"Fellin": [1013,4583,184,61],
+"Odenpäh": [1378,5103,250,61],
+"Adsel": [1504,5612,185,60],
+"Wenden": [909,5759,232,60],
+"Luga": [2667,3295,148,62],
+"Kaibolovo": [2904,3522,285,62],
+"Koporye": [3133,3160,241,62],
+"Neva": [3924,2934,148,62],
+"Volkhov": [4591,3783,231,63],
+"Lovat": [4243,5581,187,63],
+"Porkhov": [3515,5467,241,63],
+"Izborsk": [2240,5431,241,62],
+"Velikiye Luki": [3706,6347,351,61],
+"Tesovo": [3936,4102,121,32],
+"Zheltsy": [3501,4176,128,30],
+"Sablia": [3788,4541,104,31],
+"Gdov": [2427,4149,88,30],
+"Dubrovno": [3153,5214,161,31],
+"Ostrov": [2746,5717,115,30],
+"Rositten": [2046,6307,146,30],
+"Kirrumpäh": [1877,5389,175,30],
+"Pernau": [517,4580,118,30],
+"Narwia": [2371,3549,123,31],
+"Wierland": [1999,3680,200,100],
+"Warbola": [292,3797,142,31],
+"Waiga": [1535,4113,200,100],
+"Jerwen": [1064,3946,200,100],
+"Harrien": [567,3983,200,100],
+"Sackala": [617,4769,200,100],
+"Metsepole": [509,5226,200,100],
+"Lettgallia": [2048,5777,200,100],
+"Tolowa": [1541,5933,200,100],
+"Ugaunia": [1957,4940,200,100],
+"Revala": [1030,3410,200,100],
+"Velikaya River": [3029,6090,200,100],
+"Sorot River": [3299,5781,200,100],
+"Shelon River": [3654,4864,200,100],
+"Zhelcha River": [2782,4586,200,100],
+"Plyussa River": [2829,4234,200,100],
+"Ingria": [3820,3639,200,100],
+"Vod": [3488,3345,200,100],
+"Izhora": [4074,3323,200,100],
+"Karelia": [3833,2408,200,100],
+"Ladoga": [4619,2817,238,90],
+"Novgorod": [4318,4315,333,112],
+"Rusa": [4329,5166,205,92],
+"Riga": [273,6231,205,91],
+"Pskov": [2680,5263,205,91],
+"Dorpat": [1625,4589,253,91],
+"Leal": [108,4266,205,91],
+"Reval": [601,3564,206,91],
+"box1": [40,168,592,917],
+"box2": [650,168,591,913],
+"box3": [1313,167,591,916],
+"box4": [1922,167,590,916],
+"box5": [2587,167,592,918],
+"box6": [3196,167,588,916],
+"box7": [3859,167,594,916],
+"box8": [4469,166,591,918],
+"box9": [39,1119,594,915],
+"Victory": [172,183,217,215],
+"Turn": [399,184,216,214],
+"Novgorod Veche": [4193,5847,844,628],
+"Uzmen": [2112,4692,200,100],
+"way-crossroads": [1500,4717,462,149],
+"way-wirz": [1295,4526,175,350],
+"way-peipus-east": [2232,4197,220,465],
+"way-peipus-north": [2065,3836,361,228],
+"way-peipus-west": [1988,4141,218,520],
+}
+
+let data = []
+function print(str) {
+ data.push(str)
+}
+
+var locmap = {}
+
+// 0=offmap, 1-N=map locales, 100-M=calendar boxes
+var locales = [null]
+var ways = []
+var waterways = []
+var trackways = []
+
+const scale = 1
+
+function defloc(region, stronghold, type, name) {
+ let [x, y, w, h] = boxes[name]
+ x = Math.round(x * scale)
+ y = Math.round(y * scale)
+ w = Math.round(w * scale)
+ h = Math.round(h * scale)
+ locmap[name] = locales.length
+ locales.push({ name, type, stronghold, region, ways: [], box: { x, y, w, h } })
+}
+
+function defway(type, list) {
+ let ix = ways.length
+ list = list.map(name=>locmap[name]).sort((a,b)=>a-b)
+ ways.push({type, locales: list})
+ for (let from of list) {
+ for (let to of list) {
+ if (from !== to) {
+ let old = locales[from].ways.find(w => w[0] === to)
+ if (old)
+ old.push(ix)
+ else
+ locales[from].ways.push([to, ix])
+ }
+ }
+ }
+ return ways[ix]
+}
+
+function waterway(locs) { return defway('waterway', locs.split(", ")) }
+function trackway(locs) { return defway('trackway', locs.split(", ")) }
+
+defloc("Danish Estonia", 3, "bishopric", "Reval")
+defloc("Danish Estonia", 2, "castle", "Wesenberg")
+defloc("Danish Estonia", 0, "town", "Narwia")
+defloc("Danish Estonia", 0, "town", "Warbola")
+defloc("Danish Estonia", 0, "region", "Harrien")
+defloc("Danish Estonia", 0, "region", "Revala")
+defloc("Danish Estonia", 0, "region", "Wierland")
+
+defloc("Crusader Livonia", 3, "bishopric", "Dorpat")
+defloc("Crusader Livonia", 3, "bishopric", "Leal")
+defloc("Crusader Livonia", 3, "bishopric", "Riga")
+defloc("Crusader Livonia", 2, "castle", "Adsel")
+defloc("Crusader Livonia", 2, "castle", "Fellin")
+defloc("Crusader Livonia", 2, "castle", "Odenpäh")
+defloc("Crusader Livonia", 2, "castle", "Wenden")
+defloc("Crusader Livonia", 0, "town", "Kirrumpäh")
+defloc("Crusader Livonia", 0, "town", "Pernau")
+defloc("Crusader Livonia", 0, "town", "Rositten")
+defloc("Crusader Livonia", 0, "region", "Jerwen")
+defloc("Crusader Livonia", 0, "region", "Lettgallia")
+defloc("Crusader Livonia", 0, "region", "Metsepole")
+defloc("Crusader Livonia", 0, "region", "Sackala")
+defloc("Crusader Livonia", 0, "region", "Tolowa")
+defloc("Crusader Livonia", 0, "region", "Ugaunia")
+defloc("Crusader Livonia", 0, "region", "Waiga")
+
+defloc("Novgorodan Rus", 3, "archbishopric", "Novgorod")
+defloc("Novgorodan Rus", 3, "city", "Ladoga")
+defloc("Novgorodan Rus", 3, "city", "Pskov")
+defloc("Novgorodan Rus", 3, "city", "Rusa")
+defloc("Novgorodan Rus", 0, "traderoute", "Lovat")
+defloc("Novgorodan Rus", 0, "traderoute", "Luga")
+defloc("Novgorodan Rus", 0, "traderoute", "Neva")
+defloc("Novgorodan Rus", 0, "traderoute", "Volkhov")
+defloc("Novgorodan Rus", 1, "fort", "Izborsk")
+defloc("Novgorodan Rus", 1, "fort", "Kaibolovo")
+defloc("Novgorodan Rus", 1, "fort", "Koporye")
+defloc("Novgorodan Rus", 1, "fort", "Porkhov")
+defloc("Novgorodan Rus", 1, "fort", "Velikiye Luki")
+defloc("Novgorodan Rus", 0, "town", "Dubrovno")
+defloc("Novgorodan Rus", 0, "town", "Gdov")
+defloc("Novgorodan Rus", 0, "town", "Ostrov")
+defloc("Novgorodan Rus", 0, "town", "Sablia")
+defloc("Novgorodan Rus", 0, "town", "Tesovo")
+defloc("Novgorodan Rus", 0, "town", "Zheltsy")
+defloc("Novgorodan Rus", 0, "region", "Ingria")
+defloc("Novgorodan Rus", 0, "region", "Izhora")
+defloc("Novgorodan Rus", 0, "region", "Karelia")
+defloc("Novgorodan Rus", 0, "region", "Plyussa River")
+defloc("Novgorodan Rus", 0, "region", "Shelon River")
+defloc("Novgorodan Rus", 0, "region", "Sorot River")
+defloc("Novgorodan Rus", 0, "region", "Uzmen")
+defloc("Novgorodan Rus", 0, "region", "Velikaya River")
+defloc("Novgorodan Rus", 0, "region", "Vod")
+defloc("Novgorodan Rus", 0, "region", "Zhelcha River")
+
+waterway("Dorpat, Narwia, Gdov, Uzmen").name = "Pleipat W"
+waterway("Gdov, Uzmen").name = "Pleipat E"
+waterway("Fellin, Dorpat, Odenpäh").name = "Wirz"
+trackway("Dorpat, Odenpäh, Ugaunia").name = "Crossroads"
+
+waterway("Uzmen, Pskov")
+waterway("Uzmen, Zhelcha River")
+waterway("Novgorod, Shelon River, Rusa")
+waterway("Pernau, Fellin, Jerwen")
+waterway("Riga, Wenden")
+waterway("Wenden, Adsel")
+waterway("Pskov, Ostrov")
+waterway("Ostrov, Velikaya River")
+waterway("Sorot River, Velikaya River")
+waterway("Rusa, Lovat")
+waterway("Lovat, Velikiye Luki")
+waterway("Shelon River, Dubrovno")
+waterway("Shelon River, Porkhov")
+waterway("Narwia, Plyussa River")
+waterway("Luga, Kaibolovo")
+waterway("Kaibolovo, Zheltsy")
+waterway("Zheltsy, Sablia")
+waterway("Zheltsy, Tesovo")
+waterway("Ladoga, Volkhov")
+waterway("Volkhov, Novgorod")
+waterway("Neva, Ladoga")
+
+trackway("Reval, Revala")
+trackway("Revala, Wesenberg")
+trackway("Wierland, Narwia")
+trackway("Narwia, Kaibolovo")
+trackway("Wierland, Waiga")
+trackway("Wesenberg, Jerwen")
+trackway("Reval, Warbola")
+trackway("Warbola, Harrien")
+trackway("Harrien, Jerwen")
+trackway("Warbola, Leal")
+
+trackway("Waiga, Dorpat")
+trackway("Leal, Pernau")
+trackway("Fellin, Sackala")
+trackway("Sackala, Metsepole")
+trackway("Metsepole, Wenden")
+trackway("Wenden, Tolowa")
+trackway("Tolowa, Rositten")
+trackway("Lettgallia, Rositten")
+trackway("Adsel, Tolowa, Lettgallia")
+trackway("Adsel, Kirrumpäh")
+trackway("Odenpäh, Kirrumpäh")
+trackway("Kirrumpäh, Izborsk")
+trackway("Ugaunia, Uzmen")
+trackway("Ugaunia, Izborsk")
+trackway("Lettgallia, Ostrov")
+trackway("Lettgallia, Izborsk")
+
+trackway("Izborsk, Pskov")
+trackway("Kaibolovo, Koporye")
+trackway("Koporye, Vod")
+trackway("Vod, Neva")
+trackway("Vod, Ingria")
+trackway("Karelia, Neva")
+trackway("Neva, Izhora")
+trackway("Izhora, Ladoga")
+trackway("Izhora, Ingria")
+trackway("Ingria, Tesovo")
+trackway("Tesovo, Novgorod")
+trackway("Novgorod, Sablia")
+trackway("Sablia, Shelon River")
+
+trackway("Gdov, Plyussa River")
+trackway("Plyussa River, Zheltsy")
+trackway("Plyussa River, Zhelcha River")
+trackway("Zhelcha River, Pskov")
+trackway("Pskov, Dubrovno")
+trackway("Dubrovno, Porkhov")
+trackway("Porkhov, Sorot River")
+trackway("Velikaya River, Velikiye Luki")
+
+let seaports = [
+ "Riga", "Pernau", "Leal", "Reval", "Narwia", "Luga", "Koporye", "Neva"
+].map(name => locmap[name]).sort((a,b)=>a-b)
+
+function dumplist(name, list) {
+ print(name + ":[")
+ for (let item of list)
+ print(JSON.stringify(item) + ",")
+ print("],")
+}
+
+function seats(list) {
+ return list.split(", ").map(name => locmap[name]).sort((a,b)=>a-b)
+}
+
+let lords = [
+
+ {
+ side: "Teutonic",
+ name: "Andreas",
+ full_name: "Andreas von Felben",
+ title: "Landmeister in Livonia",
+ seats: seats("Riga, Wenden"),
+ marshal: 2,
+ fealty: 2,
+ service: 4,
+ lordship: 3,
+ command: 3,
+ forces: {
+ knights: 1,
+ sergeants: 2,
+ light_horse: 0,
+ men_at_arms: 1,
+ militia: 0,
+ },
+ assets: {
+ transport: 2,
+ prov: 2,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Teutonic",
+ name: "Heinrich",
+ full_name: "Heinrich",
+ title: "Bishop of Ösel-Wiek",
+ seats: seats("Leal"),
+ marshal: 0,
+ fealty: 3,
+ service: 4,
+ lordship: 2,
+ command: 1,
+ forces: {
+ knights: 1,
+ sergeants: 1,
+ light_horse: 0,
+ men_at_arms: 1,
+ militia: 0,
+ },
+ assets: {
+ ship: 1,
+ coin: 2,
+ prov: 1,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Teutonic",
+ name: "Hermann",
+ full_name: "Hermann",
+ title: "Bishop of Dorpat",
+ seats: seats("Dorpat, Odenpäh"),
+ marshal: 1,
+ fealty: 4,
+ service: 4,
+ lordship: 3,
+ command: 3,
+ forces: {
+ knights: 1,
+ sergeants: 1,
+ light_horse: 0,
+ men_at_arms: 2,
+ militia: 0,
+ },
+ assets: {
+ transport: 1,
+ coin: 1,
+ prov: 1,
+ },
+ ships: 0,
+ },
+
+ {
+ side: "Teutonic",
+ name: "Knud & Abel",
+ full_name: "Knud & Abel",
+ title: "Princes of Denmark",
+ seats: seats("Reval, Wesenburg"),
+ marshal: 0,
+ fealty: 2,
+ service: 3,
+ lordship: 3,
+ command: 2,
+ forces: {
+ knights: 1,
+ sergeants: 1,
+ light_horse: 3,
+ men_at_arms: 0,
+ militia: 0,
+ },
+ assets: {
+ ship: 2,
+ prov: 2,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Teutonic",
+ name: "Rudolf",
+ full_name: "Rudolf von Kassel",
+ title: "Castellan of Wenden",
+ seats: seats("Wenden"),
+ marshal: 0,
+ fealty: 5,
+ service: 2,
+ lordship: 1,
+ command: 3,
+ forces: {
+ knights: 1,
+ sergeants: 1,
+ light_horse: 0,
+ men_at_arms: 1,
+ militia: 0,
+ },
+ assets: {
+ transport: 1,
+ prov: 1,
+ },
+ ships: 0,
+ },
+
+ {
+ side: "Teutonic",
+ name: "Yaroslav",
+ full_name: "Yaroslav",
+ title: "Exile of Pskov",
+ seats: seats("Odenpäh, Pskov"),
+ marshal: 0,
+ fealty: 4,
+ service: 2,
+ lordship: 1,
+ command: 2,
+ forces: {
+ knights: 1,
+ sergeants: 0,
+ light_horse: 1,
+ men_at_arms: 1,
+ militia: 0,
+ },
+ assets: {
+ transport: 1,
+ prov: 1,
+ },
+ ships: 0,
+ },
+
+ {
+ side: "Russian",
+ name: "Aleksandr",
+ full_name: "Aleksandr",
+ title: "Prince of Novgorod",
+ seats: seats("Novgorod, Rusa"),
+ marshal: 2,
+ fealty: 0,
+ service: 6,
+ lordship: 4,
+ command: 3,
+ forces: {
+ knights: 3,
+ sergeants: 0,
+ light_horse: 0,
+ men_at_arms: 2,
+ militia: 0,
+ },
+ assets: {
+ transport: 2,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Russian",
+ name: "Andrey",
+ full_name: "Andrey",
+ title: "Prince of Suzdal",
+ seats: seats("Novgorod, Rusa"),
+ marshal: 1,
+ fealty: 4,
+ service: 5,
+ lordship: 3,
+ command: 2,
+ forces: {
+ knights: 3,
+ sergeants: 0,
+ light_horse: 0,
+ men_at_arms: 2,
+ militia: 0,
+ },
+ assets: {
+ transport: 2,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Russian",
+ name: "Domash",
+ full_name: "Domash",
+ title: "Tysyatskiy of Novgorod",
+ seats: seats("Novgorod"),
+ marshal: 0,
+ fealty: 4,
+ service: 4,
+ lordship: 2,
+ command: 2,
+ forces: {
+ knights: 0,
+ sergeants: 1,
+ light_horse: 1,
+ men_at_arms: 2,
+ militia: 1,
+ },
+ assets: {
+ transport: 4,
+ prov: 4,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Russian",
+ name: "Gavrilo",
+ full_name: "Gavrilo",
+ title: "Voyevoda of Pskov",
+ seats: seats("Pskov"),
+ marshal: 0,
+ fealty: 3,
+ service: 4,
+ lordship: 3,
+ command: 2,
+ forces: {
+ knights: 1,
+ sergeants: 0,
+ light_horse: 1,
+ men_at_arms: 1,
+ militia: 1,
+ },
+ assets: {
+ transport: 2,
+ coin: 1,
+ prov: 2,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Russian",
+ name: "Karelians",
+ full_name: "Karelians",
+ title: "Tributaries of Novgorod",
+ seats: seats("Ladoga"),
+ marshal: 0,
+ fealty: 4,
+ service: 2,
+ lordship: 1,
+ command: 2,
+ forces: {
+ knights: 0,
+ sergeants: 0,
+ light_horse: 1,
+ men_at_arms: 0,
+ militia: 4,
+ },
+ assets: {
+ transport: 1,
+ },
+ ships: 1,
+ },
+
+ {
+ side: "Russian",
+ name: "Vladislav",
+ full_name: "Vladislav",
+ title: "Bailiff of Ladoga",
+ seats: seats("Ladoga"),
+ marshal: 0,
+ fealty: 5,
+ service: 3,
+ lordship: 2,
+ command: 3,
+ forces: {
+ knights: 0,
+ sergeants: 1,
+ light_horse: 1,
+ men_at_arms: 2,
+ militia: 0,
+ },
+ assets: {
+ transport: 1,
+ prov: 1,
+ },
+ ships: 1,
+ },
+
+]
+
+let vassals = []
+for (let lord of lords)
+ lord.vassals = []
+
+function vassal(lord, service, name, forces, capability) {
+ let lord_id = lords.findIndex(x => x.name === lord)
+ if (lord_id < 0) throw Error("no such lord", lord)
+ lords[lord_id].vassals.push(vassals.length)
+ vassals.push({ lord: lord_id, name, service, forces, capability })
+}
+
+vassal("Andreas", 1, "Lettgallian Auxiliaries", { light_horse: 1, militia: 1 })
+vassal("Andreas", 2, "Summer Crusaders", { knights: 3 }, "Crusade")
+vassal("Andreas", 3, "Teutonic Vassals", { knights: 1, men_at_arms: 2 })
+
+vassal("Heinrich", 2, "Heinrich von Lode", { knights: 1, men_at_arms: 1 })
+vassal("Heinrich", 2, "Odward von Lode", { knights: 1, men_at_arms: 1 })
+
+vassal("Hermann", 2, "Helmond von Lüneburg", { knights: 1, men_at_arms: 1 })
+vassal("Hermann", 2, "Johannes von Dolen", { knights: 1, men_at_arms: 1 })
+vassal("Hermann", 1, "Ugaunian Auxiliaries", { light_horse: 1, militia: 1 })
+
+vassal("Knud & Abel", 2, "Dietrich von Kivel", { knights: 1, men_at_arms: 1 })
+vassal("Knud & Abel", 1, "Estonian Auxiliaries", { light_horse: 1, militia: 1 })
+vassal("Knud & Abel", 2, "Otto von Lüneburg", { knights: 1, men_at_arms: 1 })
+
+vassal("Rudolf", 2, "Ex-Sword Brethren", { knights: 1, sergeants: 1 })
+vassal("Rudolf", 2, "Jerwen Teutonic Vassals", { knights: 1, men_at_arms: 1 })
+vassal("Rudolf", 2, "Summer Crusaders", { knights: 2 }, "Crusade")
+
+vassal("Yaroslav", 1, "Mstislavich Partisans", { militia: 2 })
+
+vassal("Aleksandr", 3, "Mongols", { asiatic_horse: 2 }, "Steppe Warriors")
+vassal("Aleksandr", 3, "Mongols", { asiatic_horse: 2 }, "Steppe Warriors")
+vassal("Aleksandr", 4, "Pereyaslavl", { men_at_arms: 1 })
+vassal("Aleksandr", 3, "Rostov", { men_at_arms: 1 })
+vassal("Aleksandr", 3, "Yaroslavl", { men_at_arms: 1 })
+
+vassal("Andrey", 3, "Kipchaqs", { asiatic_horse: 3 }, "Steppe Warriors")
+vassal("Andrey", 3, "Kipchaqs", { asiatic_horse: 3 }, "Steppe Warriors")
+vassal("Andrey", 4, "Suzdal", { men_at_arms: 1 })
+vassal("Andrey", 4, "Vladimir", { men_at_arms: 1 })
+
+vassal("Domash", 2, "Novgorod", { militia: 2 })
+vassal("Domash", 2, "Novgorod", { militia: 2 })
+vassal("Domash", 2, "Novgorod", { militia: 2 })
+
+vassal("Gavrilo", 1, "Borderland Russians", { light_horse: 1, militia: 1 })
+vassal("Gavrilo", 2, "Pskov Militia", { militia: 2 })
+vassal("Gavrilo", 4, "Pskov", { men_at_arms: 1 })
+
+vassal("Vladislav", 1, "Izhoran Auxiliaries", { militia: 1 })
+vassal("Vladislav", 1, "Ingrian Auxiliaries", { militia: 1 })
+vassal("Vladislav", 1, "Vepsian Auxiliaries", { militia: 1 })
+vassal("Vladislav", 1, "Vodian Auxiliaries", { militia: 1 })
+
+function to_path(name) {
+ return name
+ .toLowerCase()
+ .replace(/&/g, 'and')
+ .replace(/[ -]/g, '_')
+ .replace(/ü/g, 'u')
+ .replace(/ö/g, 'o')
+ .replace(/ä/g, 'a')
+}
+
+let lord_service = {Russian:[],Teutonic:[]}
+let vassal_service = {Russian:[],Teutonic:[]}
+
+let last_path, last_side, last_ix
+
+last_path = null
+last_side = null
+lords.forEach((lord,id) => {
+ let side = lord.side
+ let path = "counters300/lord_" + side.toLowerCase() + "_" + to_path(lord.name)
+ if (side !== last_side) {
+ last_ix = 0
+ last_side = side
+ }
+ lord.image = last_ix
+ if (path !== last_path) {
+ last_ix++
+ last_path = path
+ lord_service[side].push(path + ".a.png")
+ lord_service[side].push(path + ".b.png")
+ }
+})
+
+last_path = null
+last_side = null
+vassals.forEach((vassal,id) => {
+ let lord = lords[vassal.lord]
+ let side = lord.side
+ let path = "counters300/vassal_" + side.toLowerCase() + "_" + to_path(lord.name) + "_" + to_path(vassal.name)
+ if (side !== last_side) {
+ last_ix = 0
+ last_side = side
+ }
+ vassal.image = last_ix
+ if (path !== last_path) {
+ last_ix++
+ last_path = path
+ vassal_service[side].push(path + ".a.png")
+ vassal_service[side].push(path + ".b.png")
+ }
+})
+
+let script = []
+script.push("mkdir -p service300")
+script.push("montage -mode concatenate -tile 2x " + lord_service.Teutonic.join(" ") + " service300/service_lords_teutonic.png")
+script.push("montage -mode concatenate -tile 2x " + vassal_service.Teutonic.join(" ") + " service300/service_vassals_teutonic.png")
+script.push("montage -mode concatenate -tile 2x " + lord_service.Russian.join(" ") + " service300/service_lords_russian.png")
+script.push("montage -mode concatenate -tile 2x " + vassal_service.Russian.join(" ") + " service300/service_vassals_russian.png")
+
+print("const data = {")
+print("seaports:" + JSON.stringify(seaports) + ",")
+dumplist("locales", locales)
+dumplist("ways", ways)
+dumplist("lords", lords)
+dumplist("vassals", vassals)
+print("}")
+print("if (typeof module !== 'undefined') module.exports = data")
+
+fs.writeFileSync("build_counters3.sh", script.join("\n") + "\n")
+fs.writeFileSync("../data.js", data.join("\n") + "\n")
diff --git a/tools/scale.sh b/tools/scale.sh
new file mode 100644
index 0000000..a54d41e
--- /dev/null
+++ b/tools/scale.sh
@@ -0,0 +1,18 @@
+mkdir -p output75
+mkdir -p output150
+for F in output300/*.png
+do
+ echo $F
+
+ O=${F/300/75}
+ gm convert $F -colorspace RGB -resize 25% -colorspace sRGB $O.tmp
+ rm -f $O
+ zopflipng -m $O.tmp $O
+ rm -f $O.tmp
+
+ O=${F/300/150}
+ gm convert $F -colorspace RGB -resize 50% -colorspace sRGB $O.tmp
+ rm -f $O
+ zopflipng -m $O.tmp $O
+ rm -f $O.tmp
+done
diff --git a/tools/svgo.config.js b/tools/svgo.config.js
new file mode 100644
index 0000000..ef3339c
--- /dev/null
+++ b/tools/svgo.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+ js2svg: {
+ 'finalNewline': true,
+ },
+ plugins: [
+ 'preset-default',
+ 'convertStyleToAttrs',
+ 'sortAttrs',
+ ],
+}