From 389e0c154b4e5401d8ad7fb1371106c465079bb9 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Fri, 3 Feb 2023 10:56:03 +0100 Subject: Clean up play.js. Use 75dpi coordinates in data. Common is_action function. Common on_action click function. Common on_tooltip function. Use cache for action elements too. Add storm/battle marker to locale. --- data.js | 106 +-- play.js | 1946 +++++++++++++++++++++++------------------------------- tools/gendata.js | 8 +- 3 files changed, 880 insertions(+), 1180 deletions(-) diff --git a/data.js b/data.js index a226d25..c35ec91 100644 --- a/data.js +++ b/data.js @@ -6,59 +6,59 @@ strongholds:[0,1,7,8,9,10,11,12,13,24,25,26,27,32,33,34,35,36], steppe_warriors:[18,19,22,23], summer_crusaders:[2,13], locales:[ -{"name":"Reval","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Danish Estonia","ways":[[5,25],[3,32]],"box":{"x":601,"y":3564,"w":206,"h":91},"adjacent":[3,5],"adjacent_by_trackway":[3,5],"adjacent_by_waterway":[],"trackways":[[3,32],[5,25]],"waterways":[]}, -{"name":"Wesenberg","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Danish Estonia","ways":[[5,26],[17,30],[6,31]],"box":{"x":1448,"y":3625,"w":304,"h":60},"adjacent":[5,6,17],"adjacent_by_trackway":[5,6,17],"adjacent_by_waterway":[],"trackways":[[5,26],[6,31],[17,30]],"waterways":[]}, -{"name":"Narwia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[7,2],[38,2],[49,2],[46,17],[6,27],[33,28]],"box":{"x":2371,"y":3549,"w":123,"h":31},"adjacent":[6,7,33,38,46,49],"adjacent_by_trackway":[6,33],"adjacent_by_waterway":[7,38,46,49],"trackways":[[6,27],[33,28]],"waterways":[[7,2],[38,2],[49,2],[46,17]]}, -{"name":"Warbola","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,32],[4,33],[8,35]],"box":{"x":292,"y":3797,"w":142,"h":31},"adjacent":[0,4,8],"adjacent_by_trackway":[0,4,8],"adjacent_by_waterway":[],"trackways":[[0,32],[4,33],[8,35]],"waterways":[]}, -{"name":"Harrien","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[3,33],[17,34]],"box":{"x":567,"y":3983,"w":200,"h":100},"adjacent":[3,17],"adjacent_by_trackway":[3,17],"adjacent_by_waterway":[],"trackways":[[3,33],[17,34]],"waterways":[]}, -{"name":"Revala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,25],[1,26]],"box":{"x":1030,"y":3410,"w":200,"h":100},"adjacent":[0,1],"adjacent_by_trackway":[0,1],"adjacent_by_waterway":[],"trackways":[[0,25],[1,26]],"waterways":[]}, -{"name":"Wierland","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[2,27],[23,29],[1,31]],"box":{"x":1999,"y":3680,"w":200,"h":100},"adjacent":[1,2,23],"adjacent_by_trackway":[1,2,23],"adjacent_by_waterway":[],"trackways":[[1,31],[2,27],[23,29]],"waterways":[]}, -{"name":"Dorpat","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[12,0,3],[22,0],[2,2],[38,2],[49,2],[11,3],[23,36]],"box":{"x":1625,"y":4589,"w":253,"h":91},"adjacent":[2,11,12,22,23,38,49],"adjacent_by_trackway":[12,22,23],"adjacent_by_waterway":[2,11,12,38,49],"trackways":[[12,0],[22,0],[23,36]],"waterways":[[12,3],[2,2],[38,2],[49,2],[11,3]]}, -{"name":"Leal","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[3,35],[15,37]],"box":{"x":108,"y":4266,"w":205,"h":91},"adjacent":[3,15],"adjacent_by_trackway":[3,15],"adjacent_by_waterway":[],"trackways":[[3,35],[15,37]],"waterways":[]}, -{"name":"Riga","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[13,8]],"box":{"x":273,"y":6231,"w":205,"h":91},"adjacent":[13],"adjacent_by_trackway":[],"adjacent_by_waterway":[13],"trackways":[],"waterways":[[13,8]]}, -{"name":"Adsel","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[13,9],[18,44],[21,44],[14,45]],"box":{"x":1504,"y":5612,"w":185,"h":60},"adjacent":[13,14,18,21],"adjacent_by_trackway":[14,18,21],"adjacent_by_waterway":[13],"trackways":[[14,45],[18,44],[21,44]],"waterways":[[13,9]]}, -{"name":"Fellin","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,3],[12,3],[15,7],[17,7],[20,38]],"box":{"x":1013,"y":4583,"w":184,"h":61},"adjacent":[7,12,15,17,20],"adjacent_by_trackway":[20],"adjacent_by_waterway":[7,12,15,17],"trackways":[[20,38]],"waterways":[[7,3],[12,3],[15,7],[17,7]]}, -{"name":"Odenpäh","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,0,3],[22,0],[11,3],[14,46]],"box":{"x":1378,"y":5103,"w":250,"h":61},"adjacent":[7,11,14,22],"adjacent_by_trackway":[7,14,22],"adjacent_by_waterway":[7,11],"trackways":[[7,0],[14,46],[22,0]],"waterways":[[7,3],[11,3]]}, -{"name":"Wenden","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[9,8],[10,9],[19,40],[21,41]],"box":{"x":909,"y":5759,"w":232,"h":60},"adjacent":[9,10,19,21],"adjacent_by_trackway":[19,21],"adjacent_by_waterway":[9,10],"trackways":[[19,40],[21,41]],"waterways":[[9,8],[10,9]]}, -{"name":"Kirrumpäh","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[10,45],[12,46],[32,47]],"box":{"x":1877,"y":5389,"w":175,"h":30},"adjacent":[10,12,32],"adjacent_by_trackway":[10,12,32],"adjacent_by_waterway":[],"trackways":[[10,45],[12,46],[32,47]],"waterways":[]}, -{"name":"Pernau","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[17,7],[8,37]],"box":{"x":517,"y":4580,"w":118,"h":30},"adjacent":[8,11,17],"adjacent_by_trackway":[8],"adjacent_by_waterway":[11,17],"trackways":[[8,37]],"waterways":[[11,7],[17,7]]}, -{"name":"Rositten","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[21,42],[18,43]],"box":{"x":2046,"y":6307,"w":146,"h":30},"adjacent":[18,21],"adjacent_by_trackway":[18,21],"adjacent_by_waterway":[],"trackways":[[18,43],[21,42]],"waterways":[]}, -{"name":"Jerwen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[15,7],[1,30],[4,34]],"box":{"x":1064,"y":3946,"w":200,"h":100},"adjacent":[1,4,11,15],"adjacent_by_trackway":[1,4],"adjacent_by_waterway":[11,15],"trackways":[[1,30],[4,34]],"waterways":[[11,7],[15,7]]}, -{"name":"Lettgallia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[16,43],[10,44],[21,44],[39,50],[32,51]],"box":{"x":2048,"y":5777,"w":200,"h":100},"adjacent":[10,16,21,32,39],"adjacent_by_trackway":[10,16,21,32,39],"adjacent_by_waterway":[],"trackways":[[10,44],[16,43],[21,44],[32,51],[39,50]],"waterways":[]}, -{"name":"Metsepole","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[20,39],[13,40]],"box":{"x":509,"y":5226,"w":200,"h":100},"adjacent":[13,20],"adjacent_by_trackway":[13,20],"adjacent_by_waterway":[],"trackways":[[13,40],[20,39]],"waterways":[]}, -{"name":"Sackala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,38],[19,39]],"box":{"x":617,"y":4769,"w":200,"h":100},"adjacent":[11,19],"adjacent_by_trackway":[11,19],"adjacent_by_waterway":[],"trackways":[[11,38],[19,39]],"waterways":[]}, -{"name":"Tolowa","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[13,41],[16,42],[10,44],[18,44]],"box":{"x":1541,"y":5933,"w":200,"h":100},"adjacent":[10,13,16,18],"adjacent_by_trackway":[10,13,16,18],"adjacent_by_waterway":[],"trackways":[[10,44],[13,41],[16,42],[18,44]],"waterways":[]}, -{"name":"Ugaunia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[7,0],[12,0],[49,48],[32,49]],"box":{"x":1957,"y":4940,"w":200,"h":100},"adjacent":[7,12,32,49],"adjacent_by_trackway":[7,12,32,49],"adjacent_by_waterway":[],"trackways":[[7,0],[12,0],[32,49],[49,48]],"waterways":[]}, -{"name":"Waiga","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[6,29],[7,36]],"box":{"x":1535,"y":4113,"w":200,"h":100},"adjacent":[6,7],"adjacent_by_trackway":[6,7],"adjacent_by_waterway":[],"trackways":[[6,29],[7,36]],"waterways":[]}, -{"name":"Novgorod","type":"novgorod","stronghold":3,"walls":3,"vp":3,"region":"Novgorodan Rus","ways":[[27,6],[47,6],[31,23],[41,62],[40,63]],"box":{"x":4318,"y":4315,"w":333,"h":112},"adjacent":[27,31,40,41,47],"adjacent_by_trackway":[40,41],"adjacent_by_waterway":[27,31,47],"trackways":[[40,63],[41,62]],"waterways":[[27,6],[47,6],[31,23]]}, -{"name":"Ladoga","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[31,22],[30,24],[44,59]],"box":{"x":4619,"y":2817,"w":238,"h":90},"adjacent":[30,31,44],"adjacent_by_trackway":[44],"adjacent_by_waterway":[30,31],"trackways":[[44,59]],"waterways":[[31,22],[30,24]]}, -{"name":"Pskov","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[49,4],[39,10],[32,52],[52,68],[37,69]],"box":{"x":2680,"y":5263,"w":205,"h":91},"adjacent":[32,37,39,49,52],"adjacent_by_trackway":[32,37,52],"adjacent_by_waterway":[39,49],"trackways":[[32,52],[37,69],[52,68]],"waterways":[[49,4],[39,10]]}, -{"name":"Rusa","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[24,6],[47,6],[28,13]],"box":{"x":4329,"y":5166,"w":205,"h":92},"adjacent":[24,28,47],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,28,47],"trackways":[],"waterways":[[24,6],[47,6],[28,13]]}, -{"name":"Lovat","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[27,13],[36,14]],"box":{"x":4243,"y":5581,"w":187,"h":63},"adjacent":[27,36],"adjacent_by_trackway":[],"adjacent_by_waterway":[27,36],"trackways":[],"waterways":[[27,13],[36,14]]}, -{"name":"Luga","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[33,18]],"box":{"x":2667,"y":3295,"w":148,"h":62},"adjacent":[33],"adjacent_by_trackway":[],"adjacent_by_waterway":[33],"trackways":[],"waterways":[[33,18]]}, -{"name":"Neva","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,24],[51,55],[45,57],[44,58]],"box":{"x":3924,"y":2934,"w":148,"h":62},"adjacent":[25,44,45,51],"adjacent_by_trackway":[44,45,51],"adjacent_by_waterway":[25],"trackways":[[44,58],[45,57],[51,55]],"waterways":[[25,24]]}, -{"name":"Volkhov","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,22],[24,23]],"box":{"x":4591,"y":3783,"w":231,"h":63},"adjacent":[24,25],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,25],"trackways":[],"waterways":[[25,22],[24,23]]}, -{"name":"Izborsk","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[14,47],[22,49],[18,51],[26,52]],"box":{"x":2240,"y":5431,"w":241,"h":62},"adjacent":[14,18,22,26],"adjacent_by_trackway":[14,18,22,26],"adjacent_by_waterway":[],"trackways":[[14,47],[18,51],[22,49],[26,52]],"waterways":[]}, -{"name":"Kaibolovo","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[29,18],[42,19],[2,28],[34,53]],"box":{"x":2904,"y":3522,"w":285,"h":62},"adjacent":[2,29,34,42],"adjacent_by_trackway":[2,34],"adjacent_by_waterway":[29,42],"trackways":[[2,28],[34,53]],"waterways":[[29,18],[42,19]]}, -{"name":"Koporye","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[33,53],[51,54]],"box":{"x":3133,"y":3160,"w":241,"h":62},"adjacent":[33,51],"adjacent_by_trackway":[33,51],"adjacent_by_waterway":[],"trackways":[[33,53],[51,54]],"waterways":[]}, -{"name":"Porkhov","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[47,16],[37,70],[48,71]],"box":{"x":3515,"y":5467,"w":241,"h":63},"adjacent":[37,47,48],"adjacent_by_trackway":[37,48],"adjacent_by_waterway":[47],"trackways":[[37,70],[48,71]],"waterways":[[47,16]]}, -{"name":"Velikiye Luki","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[28,14],[50,72]],"box":{"x":3706,"y":6347,"w":351,"h":61},"adjacent":[28,50],"adjacent_by_trackway":[50],"adjacent_by_waterway":[28],"trackways":[[50,72]],"waterways":[[28,14]]}, -{"name":"Dubrovno","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[47,15],[26,69],[35,70]],"box":{"x":3153,"y":5214,"w":161,"h":31},"adjacent":[26,35,47],"adjacent_by_trackway":[26,35],"adjacent_by_waterway":[47],"trackways":[[26,69],[35,70]],"waterways":[[47,15]]}, -{"name":"Gdov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,1,2],[2,2],[7,2],[46,65]],"box":{"x":2427,"y":4149,"w":88,"h":30},"adjacent":[2,7,46,49],"adjacent_by_trackway":[46],"adjacent_by_waterway":[2,7,49],"trackways":[[46,65]],"waterways":[[49,1],[2,2],[7,2]]}, -{"name":"Ostrov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[26,10],[50,11],[18,50]],"box":{"x":2746,"y":5717,"w":115,"h":30},"adjacent":[18,26,50],"adjacent_by_trackway":[18],"adjacent_by_waterway":[26,50],"trackways":[[18,50]],"waterways":[[26,10],[50,11]]}, -{"name":"Sablia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,20],[24,63],[47,64]],"box":{"x":3788,"y":4541,"w":104,"h":31},"adjacent":[24,42,47],"adjacent_by_trackway":[24,47],"adjacent_by_waterway":[42],"trackways":[[24,63],[47,64]],"waterways":[[42,20]]}, -{"name":"Tesovo","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,21],[43,61],[24,62]],"box":{"x":3936,"y":4102,"w":121,"h":32},"adjacent":[24,42,43],"adjacent_by_trackway":[24,43],"adjacent_by_waterway":[42],"trackways":[[24,62],[43,61]],"waterways":[[42,21]]}, -{"name":"Zheltsy","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[33,19],[40,20],[41,21],[46,66]],"box":{"x":3501,"y":4176,"w":128,"h":30},"adjacent":[33,40,41,46],"adjacent_by_trackway":[46],"adjacent_by_waterway":[33,40,41],"trackways":[[46,66]],"waterways":[[33,19],[40,20],[41,21]]}, -{"name":"Ingria","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[51,56],[44,60],[41,61]],"box":{"x":3820,"y":3639,"w":200,"h":100},"adjacent":[41,44,51],"adjacent_by_trackway":[41,44,51],"adjacent_by_waterway":[],"trackways":[[41,61],[44,60],[51,56]],"waterways":[]}, -{"name":"Izhora","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,58],[25,59],[43,60]],"box":{"x":4074,"y":3323,"w":200,"h":100},"adjacent":[25,30,43],"adjacent_by_trackway":[25,30,43],"adjacent_by_waterway":[],"trackways":[[25,59],[30,58],[43,60]],"waterways":[]}, -{"name":"Karelia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,57]],"box":{"x":3833,"y":2408,"w":200,"h":100},"adjacent":[30],"adjacent_by_trackway":[30],"adjacent_by_waterway":[],"trackways":[[30,57]],"waterways":[]}, -{"name":"Plyussa River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[2,17],[38,65],[42,66],[52,67]],"box":{"x":2829,"y":4234,"w":200,"h":100},"adjacent":[2,38,42,52],"adjacent_by_trackway":[38,42,52],"adjacent_by_waterway":[2],"trackways":[[38,65],[42,66],[52,67]],"waterways":[[2,17]]}, -{"name":"Shelon River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[24,6],[27,6],[37,15],[35,16],[40,64]],"box":{"x":3654,"y":4864,"w":200,"h":100},"adjacent":[24,27,35,37,40],"adjacent_by_trackway":[40],"adjacent_by_waterway":[24,27,35,37],"trackways":[[40,64]],"waterways":[[24,6],[27,6],[37,15],[35,16]]}, -{"name":"Sorot River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[50,12],[35,71]],"box":{"x":3299,"y":5781,"w":200,"h":100},"adjacent":[35,50],"adjacent_by_trackway":[35],"adjacent_by_waterway":[50],"trackways":[[35,71]],"waterways":[[50,12]]}, -{"name":"Uzmen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[38,1,2],[2,2],[7,2],[26,4],[52,5],[22,48]],"box":{"x":2112,"y":4692,"w":200,"h":100},"adjacent":[2,7,22,26,38,52],"adjacent_by_trackway":[22],"adjacent_by_waterway":[2,7,26,38,52],"trackways":[[22,48]],"waterways":[[38,1],[2,2],[7,2],[26,4],[52,5]]}, -{"name":"Velikaya River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[39,11],[48,12],[36,72]],"box":{"x":3029,"y":6090,"w":200,"h":100},"adjacent":[36,39,48],"adjacent_by_trackway":[36],"adjacent_by_waterway":[39,48],"trackways":[[36,72]],"waterways":[[39,11],[48,12]]}, -{"name":"Vod","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[34,54],[30,55],[43,56]],"box":{"x":3488,"y":3345,"w":200,"h":100},"adjacent":[30,34,43],"adjacent_by_trackway":[30,34,43],"adjacent_by_waterway":[],"trackways":[[30,55],[34,54],[43,56]],"waterways":[]}, -{"name":"Zhelcha River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,5],[46,67],[26,68]],"box":{"x":2782,"y":4586,"w":200,"h":100},"adjacent":[26,46,49],"adjacent_by_trackway":[26,46],"adjacent_by_waterway":[49],"trackways":[[26,68],[46,67]],"waterways":[[49,5]]}, +{"name":"Reval","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Danish Estonia","ways":[[5,25],[3,32]],"box":{"x":150,"y":891,"w":52,"h":23},"adjacent":[3,5],"adjacent_by_trackway":[3,5],"adjacent_by_waterway":[],"trackways":[[3,32],[5,25]],"waterways":[]}, +{"name":"Wesenberg","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Danish Estonia","ways":[[5,26],[17,30],[6,31]],"box":{"x":362,"y":906,"w":76,"h":15},"adjacent":[5,6,17],"adjacent_by_trackway":[5,6,17],"adjacent_by_waterway":[],"trackways":[[5,26],[6,31],[17,30]],"waterways":[]}, +{"name":"Narwia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[7,2],[38,2],[49,2],[46,17],[6,27],[33,28]],"box":{"x":592,"y":887,"w":31,"h":8},"adjacent":[6,7,33,38,46,49],"adjacent_by_trackway":[6,33],"adjacent_by_waterway":[7,38,46,49],"trackways":[[6,27],[33,28]],"waterways":[[7,2],[38,2],[49,2],[46,17]]}, +{"name":"Warbola","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,32],[4,33],[8,35]],"box":{"x":73,"y":949,"w":36,"h":8},"adjacent":[0,4,8],"adjacent_by_trackway":[0,4,8],"adjacent_by_waterway":[],"trackways":[[0,32],[4,33],[8,35]],"waterways":[]}, +{"name":"Harrien","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[3,33],[17,34]],"box":{"x":141,"y":995,"w":50,"h":25},"adjacent":[3,17],"adjacent_by_trackway":[3,17],"adjacent_by_waterway":[],"trackways":[[3,33],[17,34]],"waterways":[]}, +{"name":"Revala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,25],[1,26]],"box":{"x":257,"y":852,"w":50,"h":25},"adjacent":[0,1],"adjacent_by_trackway":[0,1],"adjacent_by_waterway":[],"trackways":[[0,25],[1,26]],"waterways":[]}, +{"name":"Wierland","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[2,27],[23,29],[1,31]],"box":{"x":499,"y":920,"w":50,"h":25},"adjacent":[1,2,23],"adjacent_by_trackway":[1,2,23],"adjacent_by_waterway":[],"trackways":[[1,31],[2,27],[23,29]],"waterways":[]}, +{"name":"Dorpat","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[12,0,3],[22,0],[2,2],[38,2],[49,2],[11,3],[23,36]],"box":{"x":406,"y":1147,"w":64,"h":23},"adjacent":[2,11,12,22,23,38,49],"adjacent_by_trackway":[12,22,23],"adjacent_by_waterway":[2,11,12,38,49],"trackways":[[12,0],[22,0],[23,36]],"waterways":[[12,3],[2,2],[38,2],[49,2],[11,3]]}, +{"name":"Leal","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[3,35],[15,37]],"box":{"x":27,"y":1066,"w":52,"h":23},"adjacent":[3,15],"adjacent_by_trackway":[3,15],"adjacent_by_waterway":[],"trackways":[[3,35],[15,37]],"waterways":[]}, +{"name":"Riga","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[13,8]],"box":{"x":68,"y":1557,"w":52,"h":23},"adjacent":[13],"adjacent_by_trackway":[],"adjacent_by_waterway":[13],"trackways":[],"waterways":[[13,8]]}, +{"name":"Adsel","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[13,9],[18,44],[21,44],[14,45]],"box":{"x":376,"y":1403,"w":47,"h":15},"adjacent":[13,14,18,21],"adjacent_by_trackway":[14,18,21],"adjacent_by_waterway":[13],"trackways":[[14,45],[18,44],[21,44]],"waterways":[[13,9]]}, +{"name":"Fellin","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,3],[12,3],[15,7],[17,7],[20,38]],"box":{"x":253,"y":1145,"w":46,"h":16},"adjacent":[7,12,15,17,20],"adjacent_by_trackway":[20],"adjacent_by_waterway":[7,12,15,17],"trackways":[[20,38]],"waterways":[[7,3],[12,3],[15,7],[17,7]]}, +{"name":"Odenpäh","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,0,3],[22,0],[11,3],[14,46]],"box":{"x":344,"y":1275,"w":63,"h":16},"adjacent":[7,11,14,22],"adjacent_by_trackway":[7,14,22],"adjacent_by_waterway":[7,11],"trackways":[[7,0],[14,46],[22,0]],"waterways":[[7,3],[11,3]]}, +{"name":"Wenden","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[9,8],[10,9],[19,40],[21,41]],"box":{"x":227,"y":1439,"w":58,"h":15},"adjacent":[9,10,19,21],"adjacent_by_trackway":[19,21],"adjacent_by_waterway":[9,10],"trackways":[[19,40],[21,41]],"waterways":[[9,8],[10,9]]}, +{"name":"Kirrumpäh","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[10,45],[12,46],[32,47]],"box":{"x":469,"y":1347,"w":44,"h":8},"adjacent":[10,12,32],"adjacent_by_trackway":[10,12,32],"adjacent_by_waterway":[],"trackways":[[10,45],[12,46],[32,47]],"waterways":[]}, +{"name":"Pernau","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[17,7],[8,37]],"box":{"x":129,"y":1145,"w":30,"h":8},"adjacent":[8,11,17],"adjacent_by_trackway":[8],"adjacent_by_waterway":[11,17],"trackways":[[8,37]],"waterways":[[11,7],[17,7]]}, +{"name":"Rositten","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[21,42],[18,43]],"box":{"x":511,"y":1576,"w":37,"h":8},"adjacent":[18,21],"adjacent_by_trackway":[18,21],"adjacent_by_waterway":[],"trackways":[[18,43],[21,42]],"waterways":[]}, +{"name":"Jerwen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[15,7],[1,30],[4,34]],"box":{"x":266,"y":986,"w":50,"h":25},"adjacent":[1,4,11,15],"adjacent_by_trackway":[1,4],"adjacent_by_waterway":[11,15],"trackways":[[1,30],[4,34]],"waterways":[[11,7],[15,7]]}, +{"name":"Lettgallia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[16,43],[10,44],[21,44],[39,50],[32,51]],"box":{"x":512,"y":1444,"w":50,"h":25},"adjacent":[10,16,21,32,39],"adjacent_by_trackway":[10,16,21,32,39],"adjacent_by_waterway":[],"trackways":[[10,44],[16,43],[21,44],[32,51],[39,50]],"waterways":[]}, +{"name":"Metsepole","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[20,39],[13,40]],"box":{"x":127,"y":1306,"w":50,"h":25},"adjacent":[13,20],"adjacent_by_trackway":[13,20],"adjacent_by_waterway":[],"trackways":[[13,40],[20,39]],"waterways":[]}, +{"name":"Sackala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,38],[19,39]],"box":{"x":154,"y":1192,"w":50,"h":25},"adjacent":[11,19],"adjacent_by_trackway":[11,19],"adjacent_by_waterway":[],"trackways":[[11,38],[19,39]],"waterways":[]}, +{"name":"Tolowa","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[13,41],[16,42],[10,44],[18,44]],"box":{"x":385,"y":1483,"w":50,"h":25},"adjacent":[10,13,16,18],"adjacent_by_trackway":[10,13,16,18],"adjacent_by_waterway":[],"trackways":[[10,44],[13,41],[16,42],[18,44]],"waterways":[]}, +{"name":"Ugaunia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[7,0],[12,0],[49,48],[32,49]],"box":{"x":489,"y":1235,"w":50,"h":25},"adjacent":[7,12,32,49],"adjacent_by_trackway":[7,12,32,49],"adjacent_by_waterway":[],"trackways":[[7,0],[12,0],[32,49],[49,48]],"waterways":[]}, +{"name":"Waiga","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[6,29],[7,36]],"box":{"x":383,"y":1028,"w":50,"h":25},"adjacent":[6,7],"adjacent_by_trackway":[6,7],"adjacent_by_waterway":[],"trackways":[[6,29],[7,36]],"waterways":[]}, +{"name":"Novgorod","type":"novgorod","stronghold":3,"walls":3,"vp":3,"region":"Novgorodan Rus","ways":[[27,6],[47,6],[31,23],[41,62],[40,63]],"box":{"x":1079,"y":1078,"w":84,"h":28},"adjacent":[27,31,40,41,47],"adjacent_by_trackway":[40,41],"adjacent_by_waterway":[27,31,47],"trackways":[[40,63],[41,62]],"waterways":[[27,6],[47,6],[31,23]]}, +{"name":"Ladoga","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[31,22],[30,24],[44,59]],"box":{"x":1154,"y":704,"w":60,"h":23},"adjacent":[30,31,44],"adjacent_by_trackway":[44],"adjacent_by_waterway":[30,31],"trackways":[[44,59]],"waterways":[[31,22],[30,24]]}, +{"name":"Pskov","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[49,4],[39,10],[32,52],[52,68],[37,69]],"box":{"x":670,"y":1315,"w":52,"h":23},"adjacent":[32,37,39,49,52],"adjacent_by_trackway":[32,37,52],"adjacent_by_waterway":[39,49],"trackways":[[32,52],[37,69],[52,68]],"waterways":[[49,4],[39,10]]}, +{"name":"Rusa","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[24,6],[47,6],[28,13]],"box":{"x":1082,"y":1291,"w":52,"h":23},"adjacent":[24,28,47],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,28,47],"trackways":[],"waterways":[[24,6],[47,6],[28,13]]}, +{"name":"Lovat","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[27,13],[36,14]],"box":{"x":1060,"y":1395,"w":47,"h":16},"adjacent":[27,36],"adjacent_by_trackway":[],"adjacent_by_waterway":[27,36],"trackways":[],"waterways":[[27,13],[36,14]]}, +{"name":"Luga","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[33,18]],"box":{"x":666,"y":823,"w":37,"h":16},"adjacent":[33],"adjacent_by_trackway":[],"adjacent_by_waterway":[33],"trackways":[],"waterways":[[33,18]]}, +{"name":"Neva","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,24],[51,55],[45,57],[44,58]],"box":{"x":981,"y":733,"w":37,"h":16},"adjacent":[25,44,45,51],"adjacent_by_trackway":[44,45,51],"adjacent_by_waterway":[25],"trackways":[[44,58],[45,57],[51,55]],"waterways":[[25,24]]}, +{"name":"Volkhov","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,22],[24,23]],"box":{"x":1147,"y":945,"w":58,"h":16},"adjacent":[24,25],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,25],"trackways":[],"waterways":[[25,22],[24,23]]}, +{"name":"Izborsk","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[14,47],[22,49],[18,51],[26,52]],"box":{"x":560,"y":1357,"w":61,"h":16},"adjacent":[14,18,22,26],"adjacent_by_trackway":[14,18,22,26],"adjacent_by_waterway":[],"trackways":[[14,47],[18,51],[22,49],[26,52]],"waterways":[]}, +{"name":"Kaibolovo","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[29,18],[42,19],[2,28],[34,53]],"box":{"x":726,"y":880,"w":72,"h":16},"adjacent":[2,29,34,42],"adjacent_by_trackway":[2,34],"adjacent_by_waterway":[29,42],"trackways":[[2,28],[34,53]],"waterways":[[29,18],[42,19]]}, +{"name":"Koporye","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[33,53],[51,54]],"box":{"x":783,"y":790,"w":61,"h":16},"adjacent":[33,51],"adjacent_by_trackway":[33,51],"adjacent_by_waterway":[],"trackways":[[33,53],[51,54]],"waterways":[]}, +{"name":"Porkhov","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[47,16],[37,70],[48,71]],"box":{"x":878,"y":1366,"w":61,"h":16},"adjacent":[37,47,48],"adjacent_by_trackway":[37,48],"adjacent_by_waterway":[47],"trackways":[[37,70],[48,71]],"waterways":[[47,16]]}, +{"name":"Velikiye Luki","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[28,14],[50,72]],"box":{"x":926,"y":1586,"w":88,"h":16},"adjacent":[28,50],"adjacent_by_trackway":[50],"adjacent_by_waterway":[28],"trackways":[[50,72]],"waterways":[[28,14]]}, +{"name":"Dubrovno","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[47,15],[26,69],[35,70]],"box":{"x":788,"y":1303,"w":41,"h":8},"adjacent":[26,35,47],"adjacent_by_trackway":[26,35],"adjacent_by_waterway":[47],"trackways":[[26,69],[35,70]],"waterways":[[47,15]]}, +{"name":"Gdov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,1,2],[2,2],[7,2],[46,65]],"box":{"x":606,"y":1037,"w":22,"h":8},"adjacent":[2,7,46,49],"adjacent_by_trackway":[46],"adjacent_by_waterway":[2,7,49],"trackways":[[46,65]],"waterways":[[49,1],[2,2],[7,2]]}, +{"name":"Ostrov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[26,10],[50,11],[18,50]],"box":{"x":686,"y":1429,"w":29,"h":8},"adjacent":[18,26,50],"adjacent_by_trackway":[18],"adjacent_by_waterway":[26,50],"trackways":[[18,50]],"waterways":[[26,10],[50,11]]}, +{"name":"Sablia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,20],[24,63],[47,64]],"box":{"x":947,"y":1135,"w":26,"h":8},"adjacent":[24,42,47],"adjacent_by_trackway":[24,47],"adjacent_by_waterway":[42],"trackways":[[24,63],[47,64]],"waterways":[[42,20]]}, +{"name":"Tesovo","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,21],[43,61],[24,62]],"box":{"x":984,"y":1025,"w":31,"h":8},"adjacent":[24,42,43],"adjacent_by_trackway":[24,43],"adjacent_by_waterway":[42],"trackways":[[24,62],[43,61]],"waterways":[[42,21]]}, +{"name":"Zheltsy","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[33,19],[40,20],[41,21],[46,66]],"box":{"x":875,"y":1044,"w":32,"h":8},"adjacent":[33,40,41,46],"adjacent_by_trackway":[46],"adjacent_by_waterway":[33,40,41],"trackways":[[46,66]],"waterways":[[33,19],[40,20],[41,21]]}, +{"name":"Ingria","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[51,56],[44,60],[41,61]],"box":{"x":955,"y":909,"w":50,"h":25},"adjacent":[41,44,51],"adjacent_by_trackway":[41,44,51],"adjacent_by_waterway":[],"trackways":[[41,61],[44,60],[51,56]],"waterways":[]}, +{"name":"Izhora","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,58],[25,59],[43,60]],"box":{"x":1018,"y":830,"w":50,"h":25},"adjacent":[25,30,43],"adjacent_by_trackway":[25,30,43],"adjacent_by_waterway":[],"trackways":[[25,59],[30,58],[43,60]],"waterways":[]}, +{"name":"Karelia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,57]],"box":{"x":958,"y":602,"w":50,"h":25},"adjacent":[30],"adjacent_by_trackway":[30],"adjacent_by_waterway":[],"trackways":[[30,57]],"waterways":[]}, +{"name":"Plyussa River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[2,17],[38,65],[42,66],[52,67]],"box":{"x":707,"y":1058,"w":50,"h":25},"adjacent":[2,38,42,52],"adjacent_by_trackway":[38,42,52],"adjacent_by_waterway":[2],"trackways":[[38,65],[42,66],[52,67]],"waterways":[[2,17]]}, +{"name":"Shelon River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[24,6],[27,6],[37,15],[35,16],[40,64]],"box":{"x":913,"y":1216,"w":50,"h":25},"adjacent":[24,27,35,37,40],"adjacent_by_trackway":[40],"adjacent_by_waterway":[24,27,35,37],"trackways":[[40,64]],"waterways":[[24,6],[27,6],[37,15],[35,16]]}, +{"name":"Sorot River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[50,12],[35,71]],"box":{"x":824,"y":1445,"w":50,"h":25},"adjacent":[35,50],"adjacent_by_trackway":[35],"adjacent_by_waterway":[50],"trackways":[[35,71]],"waterways":[[50,12]]}, +{"name":"Uzmen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[38,1,2],[2,2],[7,2],[26,4],[52,5],[22,48]],"box":{"x":528,"y":1173,"w":50,"h":25},"adjacent":[2,7,22,26,38,52],"adjacent_by_trackway":[22],"adjacent_by_waterway":[2,7,26,38,52],"trackways":[[22,48]],"waterways":[[38,1],[2,2],[7,2],[26,4],[52,5]]}, +{"name":"Velikaya River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[39,11],[48,12],[36,72]],"box":{"x":757,"y":1522,"w":50,"h":25},"adjacent":[36,39,48],"adjacent_by_trackway":[36],"adjacent_by_waterway":[39,48],"trackways":[[36,72]],"waterways":[[39,11],[48,12]]}, +{"name":"Vod","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[34,54],[30,55],[43,56]],"box":{"x":872,"y":836,"w":50,"h":25},"adjacent":[30,34,43],"adjacent_by_trackway":[30,34,43],"adjacent_by_waterway":[],"trackways":[[30,55],[34,54],[43,56]],"waterways":[]}, +{"name":"Zhelcha River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,5],[46,67],[26,68]],"box":{"x":695,"y":1146,"w":50,"h":25},"adjacent":[26,46,49],"adjacent_by_trackway":[26,46],"adjacent_by_waterway":[49],"trackways":[[26,68],[46,67]],"waterways":[[49,5]]}, ], ways:[ {"type":"trackway","locales":[7,12,22],"name":"Crossroads"}, diff --git a/play.js b/play.js index 29011f8..2983490 100644 --- a/play.js +++ b/play.js @@ -1,85 +1,174 @@ "use strict" // TODO: show strikers and targets highlighting on battle mat? -// TODO: battle.where marker on map? -function find_lord(name) { - return data.lords.findIndex((x) => x.name === name) +function toggle_pieces() { + document.getElementById("pieces").classList.toggle("hide") +} + +// === COMMON LIBRARY === + +function map_has(map, key) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else + return true + } + return false +} + +function map_get(map, key, missing) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else + return map[(m<<1)+1] + } + return missing +} + +function set_has(set, item) { + let a = 0 + let b = set.length - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = set[m] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else + return true + } + return false } +// === CONSTANTS (matching those in rules.js) === + +function find_lord(name) { return data.lords.findIndex((x) => x.name === name) } +function find_card(name) { return data.cards.findIndex((x) => x.name === name) } + const LORD_ANDREAS = find_lord("Andreas") const LORD_HERMANN = find_lord("Hermann") const LORD_ALEKSANDR = find_lord("Aleksandr") const LORD_ANDREY = find_lord("Andrey") -function find_card(name) { - return data.cards.findIndex((x) => x.name === name) -} +const first_p1_lord = 0 +const last_p1_lord = 5 +const first_p2_lord = 6 +const last_p2_lord = 11 + +const first_p1_card = 0 +const last_p1_card = 20 +const first_p2_card = 21 +const last_p2_card = 41 + +const first_p1_locale = 0 +const last_p1_locale = 23 +const first_p2_locale = 24 +const last_p2_locale = 52 const R1 = find_card("R1") const T4 = find_card("T4") const T10 = find_card("T10") const T14 = find_card("T14") - const EVENT_RUSSIAN_BRIDGE = R1 const EVENT_TEUTONIC_BRIDGE = T4 const EVENT_TEUTONIC_FIELD_ORGAN = T10 - const AOW_TEUTONIC_TREBUCHETS = T14 -const A1 = 0 // attackers -const A2 = 1 -const A3 = 2 -const D1 = 3 // defenders -const D2 = 4 -const D3 = 5 -const SA1 = 6 // relief sally: attackers -const SA2 = 7 -const SA3 = 8 -const RG1 = 9 // relief sally: rearguard -const RG2 = 10 -const RG3 = 11 - -const MAP_DPI = 75 - -const VASSAL_UNAVAILABLE = 0 +const A1 = 0, A2 = 1, A3 = 2, D1 = 3, D2 = 4, D3 = 5, SA1 = 6, SA2 = 7, SA3 = 8, RG1 = 9, RG2 = 10, RG3 = 11 + +const KNIGHTS = 0, SERGEANTS = 1, LIGHT_HORSE = 2, ASIATIC_HORSE = 3, MEN_AT_ARMS = 4, MILITIA = 5, SERFS = 6 +const force_type_count = 7 +const force_action_name = [ "knights", "sergeants", "light_horse", "asiatic_horse", "men_at_arms", "militia", "serfs" ] +const routed_force_action_name = [ "routed_knights", "routed_sergeants", "routed_light_horse", "routed_asiatic_horse", "routed_men_at_arms", "routed_militia", "routed_serfs" ] + +const COIN = 1 +const asset_type_count = 7 +const asset_action_name = [ "prov", "coin", "loot", "cart", "sled", "boat", "ship" ] +const asset_type_x3 = [ 1, 1, 1, 0, 0, 0, 0 ] + +const VECHE = 100 +const SUMMER = 0, EARLY_WINTER = 1, LATE_WINTER = 2, RASPUTITSA = 3 +const SEASONS = [ null, + SUMMER, SUMMER, EARLY_WINTER, EARLY_WINTER, LATE_WINTER, LATE_WINTER, RASPUTITSA, RASPUTITSA, + SUMMER, SUMMER, EARLY_WINTER, EARLY_WINTER, LATE_WINTER, LATE_WINTER, RASPUTITSA, RASPUTITSA, + null ] + const VASSAL_READY = 1 const VASSAL_MUSTERED = 2 - const NOWHERE = -1 const CALENDAR = 100 - const LEGATE_INDISPOSED = -2 const LEGATE_ARRIVED = -1 const GARRISON = 100 -const round = Math.round -const floor = Math.floor -const ceil = Math.ceil +// === ACTIONS === -const first_p1_lord = 0 -const last_p1_lord = 5 -const first_p2_lord = 6 -const last_p2_lord = 11 +function is_action(action, arg) { + if (arg === undefined) + return !!(view.actions && view.actions[action] === 1) + return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg)) +} -const first_p1_card = 0 -const last_p1_card = 20 -const first_p2_card = 21 -const last_p2_card = 41 +function on_action(evt) { + if (evt.button === 0) { + if (evt.target.my_id === undefined) { + send_action(evt.target.my_action) + if (evt.target.my_action_2) + send_action(evt.target.my_action_2) + } else { + send_action(evt.target.my_action, evt.target.my_id) + if (evt.target.my_action_2) + send_action(evt.target.my_action_2, evt.target.my_id) + } + } +} + +function register_action(elt, action, id, action_2) { + elt.my_id = id + elt.my_action = action + elt.my_action_2 = action_2 + elt.onmousedown = on_action +} -// unit types -const KNIGHTS = 0 -const SERGEANTS = 1 -const LIGHT_HORSE = 2 -const ASIATIC_HORSE = 3 -const MEN_AT_ARMS = 4 -const MILITIA = 5 -const SERFS = 6 +// === TOOLTIPS === -const force_action_name = [ "knights", "sergeants", "light_horse", "asiatic_horse", "men_at_arms", "militia", "serfs" ] -const routed_force_action_name = [ "routed_knights", "routed_sergeants", "routed_light_horse", "routed_asiatic_horse", "routed_men_at_arms", "routed_militia", "routed_serfs" ] +function register_tooltip(elt, focus, blur) { + if (typeof focus === "function") + elt.onmouseenter = focus + else + elt.onmouseenter = () => on_focus(focus) + if (blur) + elt.onmouseleave = blur + else + elt.onmouseleave = on_blur +} + +function on_focus(text) { + document.getElementById("status").textContent = text +} -function make_locale_tip(loc, id) { +function on_blur() { + document.getElementById("status").textContent = "" +} + +function get_locale_tip(id) { + let loc = data.locales[id] let tip = loc.name if (loc.name !== "Novgorod") { if (loc.type === "traderoute") @@ -87,7 +176,6 @@ function make_locale_tip(loc, id) { else tip += " - " + loc.type[0].toUpperCase() + loc.type.substring(1) } - // if (loc.stronghold) tip += ` [${loc.stronghold}]` if (data.seaports.includes(id)) tip += " - Seaport" let list = [] @@ -99,75 +187,46 @@ function make_locale_tip(loc, id) { if (data.lords[lord].seats.includes(id)) list.push(data.lords[lord].name) } + if (loc.name === "Pskov") + list.push("Yaroslav") if (list.length > 0) tip += " - " + list.join(", ") return tip } -const locale_tip = data.locales.map(make_locale_tip) - -// asset types -const PROV = 0 -const COIN = 1 -const LOOT = 2 -const CART = 3 -const SLED = 4 -const BOAT = 5 -const SHIP = 6 - -const VECHE = 100 - -const on_click_asset = [ - (evt) => evt.button === 0 && send_action('prov', evt.target.my_id), - (evt) => evt.button === 0 && send_action('coin', evt.target.my_id), - (evt) => evt.button === 0 && send_action('loot', evt.target.my_id), - (evt) => evt.button === 0 && send_action('cart', evt.target.my_id), - (evt) => evt.button === 0 && send_action('sled', evt.target.my_id), - (evt) => evt.button === 0 && send_action('boat', evt.target.my_id), - (evt) => evt.button === 0 && send_action('ship', evt.target.my_id), -] - -const on_click_force = [ - (evt) => evt.button === 0 && send_action('knights', evt.target.my_id), - (evt) => evt.button === 0 && send_action('sergeants', evt.target.my_id), - (evt) => evt.button === 0 && send_action('light_horse', evt.target.my_id), - (evt) => evt.button === 0 && send_action('asiatic_horse', evt.target.my_id), - (evt) => evt.button === 0 && send_action('men_at_arms', evt.target.my_id), - (evt) => evt.button === 0 && send_action('militia', evt.target.my_id), - (evt) => evt.button === 0 && send_action('serfs', evt.target.my_id), -] - -const on_click_routed_force = [ - (evt) => evt.button === 0 && send_action('routed_knights', evt.target.my_id), - (evt) => evt.button === 0 && send_action('routed_sergeants', evt.target.my_id), - (evt) => evt.button === 0 && send_action('routed_light_horse', evt.target.my_id), - (evt) => evt.button === 0 && send_action('routed_asiatic_horse', evt.target.my_id), - (evt) => evt.button === 0 && send_action('routed_men_at_arms', evt.target.my_id), - (evt) => evt.button === 0 && send_action('routed_militia', evt.target.my_id), - (evt) => evt.button === 0 && send_action('routed_serfs', evt.target.my_id), -] - -function on_click_veche_coin(evt) { - if (evt.button === 0) - send_action('veche_coin') +function on_focus_cylinder(evt) { + let lord = evt.target.my_id + let info = data.lords[lord] + let loc = view.pieces.locale[lord] + let tip = info.name + if (loc >= CALENDAR) { + if (lord !== LORD_ALEKSANDR) + tip += ` - ${info.fealty} Fealty` + tip += ` - ${info.service} Service` + } + on_focus(tip) } -function on_click_veche(evt) { - if (evt.button === 0) - send_action('veche') +function on_focus_lord_service_marker(evt) { + let lord = evt.target.my_id + let info = data.lords[lord] + on_focus(`${info.full_name} - ${info.title}`) + if (expand_calendar !== view.pieces.service[lord]) { + expand_calendar = view.pieces.service[lord] + layout_calendar() + } } -const SUMMER = 0 -const EARLY_WINTER = 1 -const LATE_WINTER = 2 -const RASPUTITSA = 3 +function on_blur_lord_service_marker(evt) { + let id = evt.target.my_id + on_blur(evt) + if (expand_calendar === view.pieces.service[id]) { + expand_calendar = -1 + layout_calendar() + } +} -const SEASONS = [ - null, - SUMMER, SUMMER, EARLY_WINTER, EARLY_WINTER, LATE_WINTER, LATE_WINTER, RASPUTITSA, RASPUTITSA, - SUMMER, SUMMER, EARLY_WINTER, EARLY_WINTER, LATE_WINTER, LATE_WINTER, RASPUTITSA, RASPUTITSA, - null -] +// === GAME STATE === function current_season() { return SEASONS[view.turn >> 1] @@ -182,68 +241,6 @@ function max_plan_length() { } } -function map_has(map, key) { - let a = 0 - let b = (map.length >> 1) - 1 - while (a <= b) { - let m = (a + b) >> 1 - let x = map[m<<1] - if (key < x) - b = m - 1 - else if (key > x) - a = m + 1 - else - return true - } - return false -} - -function map_get(map, key, missing) { - let a = 0 - let b = (map.length >> 1) - 1 - while (a <= b) { - let m = (a + b) >> 1 - let x = map[m<<1] - if (key < x) - b = m - 1 - else if (key > x) - a = m + 1 - else - return map[(m<<1)+1] - } - return missing -} - -function set_has(set, item) { - let a = 0 - let b = set.length - 1 - while (a <= b) { - let m = (a + b) >> 1 - let x = set[m] - if (item < x) - b = m - 1 - else if (item > x) - a = m + 1 - else - return true - } - return false -} - -function pack1_get(word, n) { - return (word >>> n) & 1 -} - -function pack2_get(word, n) { - n = n << 1 - return (word >>> n) & 3 -} - -function pack4_get(word, n) { - n = n << 2 - return (word >>> n) & 15 -} - function is_p1_lord(lord) { return lord >= first_p1_lord && lord <= last_p1_lord } @@ -255,7 +252,7 @@ function is_p2_lord(lord) { function is_lord_besieged(lord) { let besieged = pack1_get(view.pieces.besieged, lord) // show sallying lords as not besieged - if (view.battle && view.battle.reserves.includes(lord)) + if (view.battle && view.battle.array && view.battle.reserves.includes(lord)) return false return besieged } @@ -289,14 +286,6 @@ function is_lord_ambushed(lord) { return false } -function is_teutonic_lord(lord) { - return lord >= first_p1_lord && lord <= last_p1_lord -} - -function is_russian_lord(lord) { - return lord >= first_p2_lord && lord <= last_p2_lord -} - function get_lord_moved(lord) { return pack2_get(view.pieces.moved, lord) } @@ -317,276 +306,217 @@ function count_lord_all_forces(lord) { ) } -function is_veche_action() { - return !!(view.actions && view.actions.veche === 1) +function is_p1_locale(loc) { + return loc >= first_p1_locale && loc <= last_p1_locale } -function is_garrison_action() { - return !!(view.actions && view.actions.garrison === 1) +function is_p2_locale(loc) { + return loc >= first_p2_locale && loc <= last_p2_locale } -function is_calendar_action(turn) { - return !!(view.actions && view.actions.calendar && set_has(view.actions.calendar, turn)) +function count_vp1() { + let vp = view.pieces.elr1 << 1 + vp += view.pieces.castles1.length << 1 + for (let loc of view.pieces.conquered) + if (is_p2_locale(loc)) + vp += data.locales[loc].vp << 1 + for (let loc of view.pieces.ravaged) + if (is_p2_locale(loc)) + vp += 1 + return vp } -function is_lord_action(lord) { - return !!(view.actions && view.actions.lord && set_has(view.actions.lord, lord)) +function count_vp2() { + let vp = view.pieces.elr2 << 1 + vp += view.pieces.veche_vp << 1 + vp += view.pieces.castles2.length << 1 + for (let loc of view.pieces.conquered) + if (is_p1_locale(loc)) + vp += data.locales[loc].vp << 1 + for (let loc of view.pieces.ravaged) + if (is_p1_locale(loc)) + vp += 1 + return vp } -function is_battle_array_action(ix) { - return !!(view.actions && view.actions.array && set_has(view.actions.array, ix)) +function get_lord_locale(lord) { + return view.pieces.locale[lord] } -function is_routed_force_action(lord, action) { - return !!(view.actions && view.actions[action] && set_has(view.actions[action], lord)) +function is_lord_on_map(lord) { + let loc = get_lord_locale(lord) + return loc !== NOWHERE && loc < CALENDAR } -function is_force_action(lord, action) { - return !!(view.actions && view.actions[action] && set_has(view.actions[action], lord)) +function is_vassal_ready(vassal) { + return view.pieces.vassals[vassal] === VASSAL_READY } -function is_asset_action(lord, action) { - return !!(view.actions && view.actions[action] && set_has(view.actions[action], lord)) +function is_vassal_mustered(vassal) { + return view.pieces.vassals[vassal] === VASSAL_MUSTERED } -function is_plan_action(lord) { - return !!(view.actions && view.actions.plan && set_has(view.actions.plan, lord)) +function is_legate_selected() { + return player === "Teutons" && !!view.pieces.legate_selected } -function is_service_action(lord) { - return !!(view.actions && view.actions.service && set_has(view.actions.service, lord)) +function is_levy_phase() { + return (view.turn & 1) === 0 } -function is_service_bad_action(lord) { - return !!(view.actions && view.actions.service_bad && set_has(view.actions.service_bad, lord)) -} - -function is_vassal_action(vassal) { - return !!(view.actions && view.actions.vassal && set_has(view.actions.vassal, vassal)) -} - -function is_locale_action(locale) { - return !!(view.actions && view.actions.locale && set_has(view.actions.locale, locale)) -} - -function is_laden_march_action(locale) { - return !!(view.actions && view.actions.laden_march && set_has(view.actions.laden_march, locale)) -} - -function is_way_action(way) { - return !!(view.actions && view.actions.way && set_has(view.actions.way, way)) -} - -function is_card_action(c) { - return !!(view.actions && view.actions.card && set_has(view.actions.card, c)) -} - -function is_legate_action() { - return !!(view.actions && view.actions.legate) +function is_upper_lord(lord) { + return map_has(view.pieces.lieutenants, lord) } -function is_legate_selected() { - return player === "Teutons" && !!view.pieces.legate_selected +function is_lower_lord(lord) { + for (let i = 1; i < view.pieces.lieutenants.length; i += 2) + if (view.pieces.lieutenants[i] === lord) + return true + return false } -function is_levy_phase() { - return (view.turn & 1) === 0 +function get_lower_lord(upper) { + return map_get(view.pieces.lieutenants, upper, -1) } -const force_type_count = 7 -const force_type_name = [ "knights", "sergeants", "light_horse", "asiatic_horse", "men_at_arms", "militia", "serfs" ] -const force_type_tip = [ "knights", "sergeants", "light horse", "asiatic horse", "men-at-arms", "militia", "serfs" ] - -const asset_type_count = 7 -const asset_type_name = [ "prov", "coin", "loot", "cart", "sled", "boat", "ship" ] -const asset_type_x3 = [ 1, 1, 1, 0, 0, 0, 0 ] - -const first_p1_locale = 0 -const last_p1_locale = 23 -const first_p2_locale = 24 -const last_p2_locale = 52 - -let used_cache = {} -let unused_cache = {} - -function get_cached_element(className) { - if (!(className in unused_cache)) { - unused_cache[className] = [] - used_cache[className] = [] - } - if (unused_cache[className].length > 0) { - let elt = unused_cache[className].pop() - used_cache[className].push(elt) - return elt +function is_lord_in_battle(lord) { + if (view.battle && view.battle.array) { + for (let i = 0; i < 12; ++i) + if (view.battle.array[i] === lord) + return true + if (view.battle.reserves.includes(lord)) + return true } - let elt = document.createElement("div") - elt.className = className - used_cache[className].push(elt) - return elt + return false } -function restart_cache() { - for (let k in used_cache) { - let u = used_cache[k] - let uu = unused_cache[k] - while (u.length > 0) - uu.push(u.pop()) - } +function is_lord_command(ix) { + return view.command === ix } -function is_attacking_lord(lord) { - if (view.battle.attacker === "Teutons") - return lord < 6 - else - return lord >= 6 +function is_lord_selected(ix) { + if (view.who >= 0) + return ix === view.who + if (view.group) + return view.group.includes(ix) + return false } -function is_p1_locale(loc) { - return loc >= first_p1_locale && loc <= last_p1_locale +function is_town_locale(loc) { + return data.locales[loc].type === "town" } -function is_p2_locale(loc) { - return loc >= first_p2_locale && loc <= last_p2_locale +function has_castle_marker(loc) { + return ( + set_has(view.pieces.castles1, loc) || + set_has(view.pieces.castles2, loc) + ) } -function count_vp1() { - let vp = view.pieces.elr1 << 1 - vp += view.pieces.castles1.length << 1 - for (let loc of view.pieces.conquered) - if (is_p2_locale(loc)) - vp += data.locales[loc].vp << 1 - for (let loc of view.pieces.ravaged) - if (is_p2_locale(loc)) - vp += 1 - return vp +function is_castle(loc) { + return data.locales[loc].type === "castle" || has_castle_marker(loc) } -function count_vp2() { - let vp = view.pieces.elr2 << 1 - vp += view.pieces.veche_vp << 1 - vp += view.pieces.castles2.length << 1 - for (let loc of view.pieces.conquered) - if (is_p1_locale(loc)) - vp += data.locales[loc].vp << 1 - for (let loc of view.pieces.ravaged) - if (is_p1_locale(loc)) - vp += 1 - return vp +function is_bishopric(loc) { + return data.locales[loc].type === "bishopric" } -function get_lord_locale(lord) { - return view.pieces.locale[lord] +function has_walls(loc) { + return set_has(view.pieces.walls, loc) } -function is_lord_on_map(lord) { - let loc = get_lord_locale(lord) - return loc !== NOWHERE && loc < CALENDAR +function lord_has_unrouted_units(lord) { + return view.pieces.forces[lord] !== 0 } -function is_marshal(lord) { - if (lord === LORD_HERMANN) return !is_lord_on_map(LORD_ANDREAS) - if (lord === LORD_ANDREY) return !is_lord_on_map(LORD_ALEKSANDR) +function get_lord_capability(lord, n) { + return view.pieces.capabilities[(lord << 1) + n] } -function has_global_capability(cap) { - for (let c of view.capabilities) - if (data.cards[c].capability === cap) - return true +function lord_has_capability_card(lord, c) { + let name = data.cards[c].capability + let c1 = get_lord_capability(lord, 0) + if (c1 >= 0 && data.cards[c1].capability === name) + return true + let c2 = get_lord_capability(lord, 1) + if (c2 >= 0 && data.cards[c2].capability === name) + return true return false } -function is_vassal_ready(vassal) { - return view.pieces.vassals[vassal] === VASSAL_READY -} - -function is_vassal_mustered(vassal) { - return view.pieces.vassals[vassal] === VASSAL_MUSTERED -} - -function for_each_teutonic_card(fn) { - for (let i = 0; i < 21; ++i) - fn(i) -} - -function for_each_russian_card(fn) { - for (let i = 21; i < 42; ++i) - fn(i) -} - -function is_upper_lord(lord) { - return map_has(view.pieces.lieutenants, lord) +function lord_has_capability(lord, card_or_list) { + if (Array.isArray(card_or_list)) { + for (let card of card_or_list) + if (lord_has_capability_card(lord, card)) + return true + return false + } + return lord_has_capability_card(lord, card_or_list) } -function is_lower_lord(lord) { - for (let i = 1; i < view.pieces.lieutenants.length; i += 2) - if (view.pieces.lieutenants[i] === lord) - return true +function attacker_has_trebuchets() { + if (view.battle.attacker === "Teutons") { + for (let lord = first_p1_lord; lord <= last_p1_lord; ++lord) { + if (get_lord_locale(lord) === view.battle.where && lord_has_unrouted_units(lord)) { + if (lord_has_capability(lord, AOW_TEUTONIC_TREBUCHETS)) + return true + } + } + } return false } -function get_lower_lord(upper) { - return map_get(view.pieces.lieutenants, upper, -1) -} - -function for_each_friendly_card(fn) { - if (player === "Teutons") - for_each_teutonic_card(fn) - else - for_each_russian_card(fn) +function count_siege_markers(loc) { + return map_get(view.pieces.sieges, loc, 0) } -function for_each_enemy_card(fn) { - if (player !== "Teutons") - for_each_teutonic_card(fn) - else - for_each_russian_card(fn) -} +// === BUILD UI === const original_boxes = { - "way crossroads": [1500,4717,462,149], - "way wirz": [1295,4526,175,350], - "way peipus-east": [2232,4197,220,480], - "way peipus-north": [2053,3830,361,228], - "calendar summer box1": [40,168,598,924], - "calendar summer box2": [650,168,598,924], - "calendar winter box3": [1313,168,598,924], - "calendar winter box4": [1922,168,598,924], - "calendar winter box5": [2587,168,598,924], - "calendar winter box6": [3196,168,598,924], - "calendar rasputitsa box7": [3860,168,598,924], - "calendar rasputitsa box8": [4470,168,598,924], - "calendar summer box9": [40,1120,598,924], - "calendar summer box10": [650,1120,598,924], - "calendar winter box11": [1313,1120,598,924], - "calendar winter box12": [1922,1120,598,924], - "calendar winter box13": [2587,1120,598,924], - "calendar winter box14": [3196,1120,598,924], - "calendar rasputitsa box15": [3860,1120,598,924], - "calendar rasputitsa box16": [4470,1120,598,924], - "calendar box0": [6,62,1265,89], - "calendar box17": [3827,2056,1265,86], + "way crossroads": [ 375, 1179, 116, 37 ], + "way wirz": [ 324, 1132, 44, 88 ], + "way peipus-east": [ 558, 1049, 55, 120 ], + "way peipus-north": [ 513, 958, 90, 57 ], + "calendar summer box1": [ 10, 42, 150, 231 ], + "calendar summer box2": [ 163, 42, 150, 231 ], + "calendar winter box3": [ 328, 42, 150, 231 ], + "calendar winter box4": [ 481, 42, 150, 231 ], + "calendar winter box5": [ 647, 42, 150, 231 ], + "calendar winter box6": [ 799, 42, 150, 231 ], + "calendar rasputitsa box7": [ 965, 42, 150, 231 ], + "calendar rasputitsa box8": [ 1118, 42, 150, 231 ], + "calendar summer box9": [ 10, 280, 150, 231 ], + "calendar summer box10": [ 163, 280, 150, 231 ], + "calendar winter box11": [ 328, 280, 150, 231 ], + "calendar winter box12": [ 481, 280, 150, 231 ], + "calendar winter box13": [ 647, 280, 150, 231 ], + "calendar winter box14": [ 799, 280, 150, 231 ], + "calendar rasputitsa box15": [ 965, 280, 150, 231 ], + "calendar rasputitsa box16": [ 1118, 280, 150, 231 ], + "calendar box0": [ 2, 16, 316, 22 ], + "calendar box17": [ 957, 514, 316, 22 ], } const calendar_xy = [ - [40,8], - [40,168], - [650,168], - [1313,168], - [1922,168], - [2587,168], - [3196,168], - [3860,168], - [4470,168], - [40,1120], - [650,1120], - [1313,1120], - [1922,1120], - [2587,1120], - [3196,1120], - [3860,1120], - [4470,1120], - [4462,2068], -].map(([x,y])=>[x/4|0,y/4|0]) + [ 10, 2 ], + [ 10, 42 ], + [ 162, 42 ], + [ 328, 42 ], + [ 480, 42 ], + [ 646, 42 ], + [ 799, 42 ], + [ 965, 42 ], + [ 1117, 42 ], + [ 10, 280 ], + [ 162, 280 ], + [ 328, 280 ], + [ 480, 280 ], + [ 646, 280 ], + [ 799, 280 ], + [ 965, 280 ], + [ 1117, 280 ], + [ 1115, 517 ], +] const locale_xy = [] @@ -597,7 +527,6 @@ const ui = { locale_name: [], locale_markers: [], lord_cylinder: [], - battle_cylinder: [], lord_service: [], lord_mat: [], lord_buttons: [], @@ -694,150 +623,278 @@ function clean_name(name) { return name.toLowerCase().replaceAll("&", "and").replaceAll(" ", "_") } -const extra_size_OLD = { - town: [ 45, 32 ], - castle: [ 45, 32 ], - fort: [ 54, 32 ], - traderoute: [ 54, 32 ], - bishopric: [ 63, 45 ], - city: [ 100, 54 ], - novgorod: [ 117, 72 ], -} - -function toggle_pieces() { - document.getElementById("pieces").classList.toggle("hide") -} - -function on_click_locale(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('locale', id) - send_action('laden_march', id) - } +function build_div(parent, className) { + let e = document.createElement("div") + e.className = className + if (parent) + parent.appendChild(e) + return e } -function on_focus_locale(evt) { - let id = evt.target.my_id - document.getElementById("status").textContent = locale_tip[id] +function build_lord_mat(lord, ix, side, name) { + let mat = build_div(null, `mat ${side} ${name}`) + let bg = build_div(mat, "background") + ui.forces[ix] = build_div(bg, "forces") + ui.routed[ix] = build_div(bg, "routed") + ui.assets[ix] = build_div(bg, "assets") + ui.ready_vassals[ix] = build_div(bg, "ready_vassals") + ui.mustered_vassals[ix] = build_div(bg, "mustered_vassals") + ui.lord_buttons[ix] = build_div(bg, "shield") + ui.lord_capabilities[ix] = build_div(mat, "capabilities") + ui.lord_events[ix] = build_div(mat, "events") + ui.lord_moved1[ix] = build_div(mat, "marker square moved_fought one hide") + ui.lord_moved2[ix] = build_div(mat, "marker square moved_fought two hide") + ui.lord_feed_x2[ix] = build_div(mat, "marker small feed_x2") + ui.lord_mat[ix] = mat + register_action(ui.lord_buttons[ix], "lord", ix) } -function on_click_way(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('way', id) - } +function build_card(side, c) { + let card = ui.cards[c] = document.createElement("div") + card.className = `card ${side} aow_${c}` + register_action(card, "card", c) } -function on_click_cylinder(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('lord', id) +function build_plan() { + let elt + for (let i = 0; i < 6; ++i) { + elt = document.createElement("div") + elt.className = "hide" + ui.plan_cards.push(elt) + ui.plan.appendChild(elt) } -} - -function on_click_card(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('card', id) + for (let lord = 0; lord < 12; ++lord) { + let side = lord < 6 ? "teutonic" : "russian" + elt = document.createElement("div") + elt.className = `card ${side} cc_lord_${lord}` + register_action(elt, "plan", lord) + ui.plan_action_cards.push(elt) + ui.plan_actions.appendChild(elt) } -} -function on_click_plan(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('plan', id) - } -} + ui.plan_action_pass_p1 = elt = document.createElement("div") + elt.className = `card teutonic cc_pass` + register_action(elt, "plan", -1) + ui.plan_actions.appendChild(elt) -function on_focus_cylinder(evt) { - let lord = evt.target.my_id - let info = data.lords[lord] - let loc = view.pieces.locale[lord] - let tip = info.name - if (loc >= CALENDAR) { - if (lord !== LORD_ALEKSANDR) - tip += ` - ${info.fealty} Fealty` - tip += ` - ${info.service} Service` - } else { - /* - if (view.turn & 1) - tip += ` - ${info.command} Command` - else - tip += ` - ${info.lordship} Lordship` - */ - } - document.getElementById("status").textContent = tip + ui.plan_action_pass_p2 = elt = document.createElement("div") + elt.className = `card russian cc_pass` + register_action(elt, "plan", -1) + ui.plan_actions.appendChild(elt) } -function on_click_lord_service_marker(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('service', id) - send_action('service_bad', id) - } +function build_way(name, sel) { + let way = data.ways.findIndex(w => w.name === name) + ui.ways[way] = document.querySelector(sel) + register_action(ui.ways[way], "way", way) } -function on_click_calendar(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('calendar', evt.target.my_id) - } +const locale_size = { + region: [ 88, 56 ], + town: [ 80, 72 ], + traderoute: [ 90, 54 ], + fort: [ 96, 54 ], + castle: [ 96, 56 ], + city: [ 126, 80 ], + bishopric: [ 106, 72 ], + novgorod: [ 144, 86 ], } -function on_focus_legate(evt) { - document.getElementById("status").textContent = "William of Modena" -} +function build_map() { + for (let i = 0; i < data.locales.length; ++i) + locale_layout[i] = [] -function on_focus_lord_service_marker(evt) { - let lord = evt.target.my_id - let info = data.lords[lord] - document.getElementById("status").textContent = `${info.full_name} - ${info.title}` - if (expand_calendar !== view.pieces.service[lord]) { - expand_calendar = view.pieces.service[lord] - layout_calendar() - } -} + data.locales.forEach((locale, ix) => { + let region = clean_name(locale.region) + let { x, y, w, h } = locale.box + let xc = Math.round(x + w / 2) + let yc = Math.round(y + h / 2) + let e -function on_blur_lord_service_marker(evt) { - let id = evt.target.my_id - on_blur(evt) - if (expand_calendar === view.pieces.service[id]) { - expand_calendar = -1 - layout_calendar() - } -} + switch (locale.type) { + case "town": + locale_xy[ix] = [ xc, y - 24 ] + w = locale_size.town[0] + h = locale_size.town[1] + x = xc - w/2 + y = y - h + 16 + break + case "region": + xc += 2 + yc -= 3 + locale_xy[ix] = [ xc, yc - 24 ] + w = locale_size.region[0] + h = locale_size.region[1] + x = xc - w/2 + y = yc - h/2 + break + default: + locale_xy[ix] = [ xc, y - 36 ] + break + } -function on_click_vassal_service_marker(evt) { - if (evt.button === 0) { - let id = evt.target.my_id - send_action('vassal', id) + // Main Area + e = ui.locale[ix] = document.createElement("div") + e.className = "locale " + locale.type + " " + region + if (locale.type !== "region" && locale.type !== "town") { + let ew = locale_size[locale.type][0] + let eh = locale_size[locale.type][1] + e.style.top = (y - eh) + "px" + e.style.left = (xc - ew/2) + "px" + e.style.width = (ew) + "px" + e.style.height = (eh) + "px" + } else { + e.style.left = x + "px" + e.style.top = y + "px" + e.style.width = w + "px" + e.style.height = h + "px" + } + register_action(e, "locale", ix, "laden_march") + register_tooltip(e, get_locale_tip(ix)) + document.getElementById("locales").appendChild(e) + + // Name Plate + if (locale.type !== 'region' && locale.type !== 'town') { + e = ui.locale_name[ix] = document.createElement("div") + e.className = "locale_name " + locale.type + " " + region + e.style.left = x + "px" + e.style.top = y + "px" + e.style.width = w + "px" + e.style.height = h + "px" + register_action(e, "locale", ix, "laden_march") + register_tooltip(e, get_locale_tip(ix)) + document.getElementById("locales").appendChild(e) + } + + // Locale Markers + e = ui.locale_markers[ix] = document.createElement("div") + e.className = "locale_markers " + locale.type + " " + region + x = locale_xy[ix][0] - 196/2 + y = locale_xy[ix][1] + 36 + e.style.top = y + "px" + e.style.left = x + "px" + e.style.width = 196 + "px" + document.getElementById("pieces").appendChild(e) + }) + + data.lords.forEach((lord, ix) => { + let e = ui.lord_cylinder[ix] = document.createElement("div") + e.className = "cylinder lord " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide" + register_action(e, "lord", ix) + register_tooltip(e, on_focus_cylinder) + document.getElementById("pieces").appendChild(e) + + e = ui.lord_service[ix] = document.createElement("div") + e.className = "service_marker lord image" + lord.image + " " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide" + register_action(e, "service", ix, "service_bad") + register_tooltip(e, on_focus_lord_service_marker, on_blur_lord_service_marker) + document.getElementById("pieces").appendChild(e) + + build_lord_mat(lord, ix, clean_name(lord.side), clean_name(lord.name)) + }) + + data.vassals.forEach((vassal, ix) => { + let lord = data.lords[vassal.lord] + let e = ui.vassal_service[ix] = document.createElement("div") + e.className = "service_marker vassal image" + vassal.image + " " + clean_name(lord.side) + " " + clean_name(vassal.name) + " hide" + register_action(e, "vassal", ix) + register_tooltip(e, data.vassals[ix].name) + document.getElementById("pieces").appendChild(e) + }) + + register_action(ui.legate, "legate") + register_tooltip(ui.legate, "William of Modena") + + register_action(ui.veche, "veche") + + for (let name in original_boxes) { + let x = original_boxes[name][0] + let y = original_boxes[name][1] + let w = original_boxes[name][2] - 8 + let h = original_boxes[name][3] - 8 + let e = ui.boxes[name] = document.createElement("div") + e.className = "box " + name + e.style.left = x + "px" + e.style.top = y + "px" + e.style.width = w + "px" + e.style.height = h + "px" + document.getElementById("boxes").appendChild(e) } -} -function on_focus_vassal_service_marker(evt) { - let id = evt.target.my_id - let vassal = data.vassals[id] - let lord = data.lords[vassal.lord] - document.getElementById("status").textContent = `${lord.name} / ${vassal.name}` -} + ui.calendar = [ + document.querySelector(".calendar.box0"), + document.querySelector(".calendar.box1"), + document.querySelector(".calendar.box2"), + document.querySelector(".calendar.box3"), + document.querySelector(".calendar.box4"), + document.querySelector(".calendar.box5"), + document.querySelector(".calendar.box6"), + document.querySelector(".calendar.box7"), + document.querySelector(".calendar.box8"), + document.querySelector(".calendar.box9"), + document.querySelector(".calendar.box10"), + document.querySelector(".calendar.box11"), + document.querySelector(".calendar.box12"), + document.querySelector(".calendar.box13"), + document.querySelector(".calendar.box14"), + document.querySelector(".calendar.box15"), + document.querySelector(".calendar.box16"), + document.querySelector(".calendar.box17") + ] -function on_click_legate(evt) { - if (evt.button === 0) - send_action('legate') -} + for (let i = 0; i <= 17; ++i) + register_action(ui.calendar[i], "calendar", i) + + build_way("Crossroads", ".way.crossroads") + build_way("Peipus E", ".way.peipus-east") + build_way("Peipus W", ".way.peipus-north") + build_way("Wirz", ".way.wirz") + + build_plan() + + register_action(ui.garrison, "garrison") + for (let i = 0; i < 12; ++i) + register_action(ui.battle_grid_array[i], "array", i) -function on_click_array(evt) { - if (evt.button === 0) - send_action('array', evt.target.my_id) + for (let c = first_p1_card; c <= last_p1_card; ++c) + build_card("teutonic", c) + for (let c = first_p2_card; c <= last_p2_card; ++c) + build_card("russian", c) } -function on_click_garrison(evt) { - if (evt.button === 0) - send_action('garrison') +// === UPDATE UI === + +let used_cache = {} +let unused_cache = {} + +function get_cached_element(className, action, id) { + let key = className + if (action !== undefined) + key += "/" + action + "/" + id + if (!(key in unused_cache)) { + unused_cache[key] = [] + used_cache[key] = [] + } + if (unused_cache[key].length > 0) { + let elt = unused_cache[key].pop() + used_cache[key].push(elt) + return elt + } + let elt = document.createElement("div") + elt.className = className + used_cache[key].push(elt) + if (action !== undefined) + register_action(elt, action, id) + return elt } -function on_blur(evt) { - document.getElementById("status").textContent = "" +function restart_cache() { + for (let k in used_cache) { + let u = used_cache[k] + let uu = unused_cache[k] + while (u.length > 0) + uu.push(u.pop()) + } } function update_current_card_display() { @@ -864,215 +921,80 @@ function update_current_card_display() { } } -function on_focus_card_tip(c) { - if (c <= first_p1_card) - ui.command.className = `card teutonic aow_${c}` - else - ui.command.className = `card russian aow_${c}` +function layout_locale_item(loc, e, is_upper) { + locale_layout[loc].push([e, is_upper]) + e.classList.toggle("lieutenant", is_upper) } -function on_blur_card_tip(c) { - update_current_card_display() -} +function layout_locale_cylinders(loc) { + let [xc, yc] = locale_xy[loc] -function sub_card_capability(match, p1) { - let x = p1 | 0 - return `${data.cards[x].capability}` -} + let n = 0 + for (let [e,is_upper] of locale_layout[loc]) + if (!is_upper) + ++n -function sub_card_event(match, p1) { - let x = p1 | 0 - return `${data.cards[x].event}` -} + let wrap = 3 + switch (data.locales[loc].type) { + case "region": wrap = 2; break + case "town": wrap = 2; break + case "novgorod": wrap = 4; break + } -function on_focus_locale_tip(loc) { - ui.locale[loc].classList.add("tip") - if (ui.locale_name[loc]) - ui.locale_name[loc].classList.add("tip") + let m = Math.floor((n-1) / wrap) + let i = 0 + let k = 0 + for (let [e,is_upper] of locale_layout[loc]) { + let nn = n + if (nn > wrap) + nn = wrap + let x = xc + (i - (nn-1)/2) * 44 + k * 22 + let y = yc + (k * 32) - m * 32 + let z = 1 + if (is_upper) { + y -= 18 + z = 2 + } + if (e === ui.legate) { + y -= 16 + z = 3 + } + e.style.top = (y - 23) + "px" + e.style.left = (x - 23) + "px" + e.style.zIndex = z + if (!is_upper) + ++i + if (i >= wrap) { + i = 0 + ++k + } + } } -function on_blur_locale_tip(loc) { - ui.locale[loc].classList.remove("tip") - if (ui.locale_name[loc]) - ui.locale_name[loc].classList.remove("tip") -} - -function on_click_locale_tip(loc) { - ui.locale[loc].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) -} - -function on_focus_way_tip(way) { - ui.ways[way].classList.add("tip") -} - -function on_blur_way_tip(way) { - ui.ways[way].classList.remove("tip") -} - -function on_click_way_tip(way) { - ui.ways[way].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) -} - -function on_click_lord_tip(lord) { - ui.lord_mat[lord].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) -} - -function sub_locale_name(match, p1) { - let x = p1 | 0 - let n = data.locales[x].name - return `${n}` -} - -function sub_lord_name(match, p1) { - let x = p1 | 0 - let n = data.lords[x].name - return `${n}` -} - -function sub_way_name(match, p1) { - let x = p1 | 0 - let n = data.ways[x].name - return `${n}` -} - -function on_log(text) { - let p = document.createElement("div") - - if (text.match(/^>>/)) { - text = text.substring(2) - p.className = "ii" - } - - if (text.match(/^>/)) { - text = text.substring(1) - p.className = "i" - } - - text = text.replace(/&/g, "&") - text = text.replace(//g, ">") - - text = text.replace(/C(\d+)/g, sub_card_capability) - text = text.replace(/E(\d+)/g, sub_card_event) - text = text.replace(/L(\d+)/g, sub_lord_name) - text = text.replace(/%(\d+)/g, sub_locale_name) - text = text.replace(/W(\d+)/g, sub_way_name) - - if (text.match(/^\.h1/)) { - text = text.substring(4) - p.className = "h1" - } - else if (text.match(/^\.h2t/)) { - text = text.substring(5) - p.className = "h2 teutonic" - } - else if (text.match(/^\.h2r/)) { - text = text.substring(5) - p.className = "h2 russian" - } - else if (text.match(/^\.h2/)) { - text = text.substring(4) - p.className = "h2" - } - else if (text.match(/^\.h3t/)) { - text = text.substring(5) - p.className = "h3 teutonic" - } - else if (text.match(/^\.h3r/)) { - text = text.substring(5) - p.className = "h3 russian" - } - else if (text.match(/^\.h3/)) { - text = text.substring(4) - p.className = "h3" - } - else if (text.match(/^\.h4/)) { - text = text.substring(4) - p.className = "h4" - } - else if (text.match(/^\.h5/)) { - text = text.substring(4) - p.className = "h5" - } - - p.innerHTML = text - return p -} - -function layout_locale_item(loc, e, is_upper) { - locale_layout[loc].push([e, is_upper]) - e.classList.toggle("lieutenant", is_upper) -} - -function layout_locale_cylinders(loc) { - let [xc, yc] = locale_xy[loc] - - let n = 0 - for (let [e,is_upper] of locale_layout[loc]) - if (!is_upper) - ++n - - let wrap = 3 - switch (data.locales[loc].type) { - case "region": wrap = 2; break - case "town": wrap = 2; break - case "novgorod": wrap = 4; break - } - - let m = Math.floor((n-1) / wrap) - let i = 0 - let k = 0 - for (let [e,is_upper] of locale_layout[loc]) { - let nn = n - if (nn > wrap) - nn = wrap - let x = xc + (i - (nn-1)/2) * 44 + k * 22 - let y = yc + (k * 32) - m * 32 - let z = 1 - if (is_upper) { - y -= 18 - z = 2 - } - if (e === ui.legate) { - y -= 16 - z = 3 - } - e.style.top = (y - 23) + "px" - e.style.left = (x - 23) + "px" - e.style.zIndex = z - if (!is_upper) - ++i - if (i >= wrap) { - i = 0 - ++k - } - } -} - -function layout_calendar() { - for (let loc = 0; loc < 18; ++loc) { - let [cx, cy] = calendar_xy[loc] - let list = calendar_layout_service[loc] - for (let i = 0; i < list.length; ++i) { - let e = list[i] - let x = cx, y = cy, z = 60 - i - let d = 46 - 24 - if (loc === expand_calendar) - d = 46 - if (loc === 0) { - x += -6 + 46 * i - z = 1 - } else if (loc === 17) { - x += 60 - 46 * i - z = 60 - i - } else { - x += (146 - 94 - 2) - y += (227 - 46 - 2) - i * d - } - e.style.top = y + "px" - e.style.left = x + "px" - e.style.zIndex = z - } +function layout_calendar() { + for (let loc = 0; loc < 18; ++loc) { + let [cx, cy] = calendar_xy[loc] + let list = calendar_layout_service[loc] + for (let i = 0; i < list.length; ++i) { + let e = list[i] + let x = cx, y = cy, z = 60 - i + let d = 46 - 24 + if (loc === expand_calendar) + d = 46 + if (loc === 0) { + x += -6 + 46 * i + z = 1 + } else if (loc === 17) { + x += 60 - 46 * i + z = 60 - i + } else { + x += (146 - 94 - 2) + y += (227 - 46 - 2) - i * d + } + e.style.top = y + "px" + e.style.left = x + "px" + e.style.zIndex = z + } list = calendar_layout_cylinder[loc] for (let i = 0; i < list.length; ++i) { @@ -1105,38 +1027,39 @@ function layout_calendar() { } function add_force(parent, type, lord, routed) { - // TODO: reuse pool of elements? + let elt if (routed) { - if (is_routed_force_action(lord, routed_force_action_name[type])) - build_div(parent, "action unit " + force_type_name[type], lord, on_click_routed_force[type]) + if (is_action(routed_force_action_name[type], lord)) + elt = get_cached_element("action unit " + force_action_name[type], routed_force_action_name[type], lord) else - build_div(parent, "unit " + force_type_name[type], lord, on_click_routed_force[type]) + elt = get_cached_element("unit " + force_action_name[type], routed_force_action_name[type], lord) } else { - if (is_force_action(lord, force_action_name[type])) - build_div(parent, "action unit " + force_type_name[type], lord, on_click_force[type]) + if (is_action(force_action_name[type], lord)) + elt = get_cached_element("action unit " + force_action_name[type], force_action_name[type], lord) else - build_div(parent, "unit " + force_type_name[type], lord, on_click_force[type]) + elt = get_cached_element("unit " + force_action_name[type], force_action_name[type], lord) } + parent.appendChild(elt) } function add_asset(parent, type, n, lord) { - // TODO: reuse pool of elements? + let elt if (lord === VECHE) { - if (view.actions && view.actions.veche_coin) - build_div(parent, "action asset " + asset_type_name[type] + " x"+n, VECHE, on_click_veche_coin) + if (is_action("veche_coin")) + elt = get_cached_element("action asset " + asset_action_name[type] + " x"+n, "veche_coin", undefined) else - build_div(parent, "asset " + asset_type_name[type] + " x"+n) + elt = get_cached_element("asset " + asset_action_name[type] + " x"+n) } else { - if (is_asset_action(lord, asset_type_name[type])) - build_div(parent, "action asset " + asset_type_name[type] + " x"+n, lord, on_click_asset[type]) + if (is_action(asset_action_name[type], lord)) + elt = get_cached_element("action asset " + asset_action_name[type] + " x"+n, asset_action_name[type], lord) else - build_div(parent, "asset " + asset_type_name[type] + " x"+n) + elt = get_cached_element("asset " + asset_action_name[type] + " x"+n) } + parent.appendChild(elt) } function add_veche_vp(parent) { - // TODO: reuse pool of elements? - build_div(parent, "marker square conquered russian") + parent.appendChild(get_cached_element("marker square conquered russian")) } function update_forces(parent, forces, lord_ix, routed) { @@ -1188,7 +1111,7 @@ function update_vassals(ready_parent, mustered_parent, lord_ix) { else { e.classList.add("hide") } - e.classList.toggle("action", is_vassal_action(v)) + e.classList.toggle("action", is_action("vassal", v)) } } @@ -1216,18 +1139,6 @@ function update_lord_mat(ix) { ui.lord_moved2[ix].classList.toggle("hide", is_levy_phase() || (m !== 2)) } -function is_lord_command(ix) { - return view.command === ix -} - -function is_lord_selected(ix) { - if (view.who >= 0) - return ix === view.who - if (view.group) - return view.group.includes(ix) - return false -} - function update_lord(ix) { let locale = view.pieces.locale[ix] let service = view.pieces.service[ix] @@ -1265,10 +1176,10 @@ function update_lord(ix) { ui.lord_service[ix].classList.add("hide") } ui.lord_cylinder[ix].classList.toggle("besieged", is_lord_besieged(ix)) - ui.lord_buttons[ix].classList.toggle("action", is_lord_action(ix)) - ui.lord_cylinder[ix].classList.toggle("action", is_lord_action(ix)) - ui.lord_service[ix].classList.toggle("action", is_service_action(ix) || is_service_bad_action(ix)) - ui.lord_service[ix].classList.toggle("bad", is_service_bad_action(ix)) + ui.lord_buttons[ix].classList.toggle("action", is_action("lord", ix)) + ui.lord_cylinder[ix].classList.toggle("action", is_action("lord", ix)) + ui.lord_service[ix].classList.toggle("action", is_action("service", ix) || is_action("service_bad", ix)) + ui.lord_service[ix].classList.toggle("bad", is_action("service_bad", ix)) if (ix === LORD_HERMANN) ui.lord_cylinder[ix].classList.toggle("marshal", !is_lord_on_map(LORD_ANDREAS)) @@ -1291,7 +1202,7 @@ function update_legate() { ui.legate.classList.add("hide") } else { ui.legate.classList.remove("hide") - ui.legate.classList.toggle("action", is_legate_action()) + ui.legate.classList.toggle("action", is_action("legate")) ui.legate.classList.toggle("selected", is_legate_selected()) if (view.pieces.legate === LEGATE_ARRIVED) { ui.legate.style.top = "1356px" @@ -1305,7 +1216,7 @@ function update_legate() { function update_smerdi() { ui.smerdi.replaceChildren() for (let i = 0; i < view.pieces.smerdi; ++i) - build_div(ui.smerdi, "unit serfs") + ui.smerdi.appendChild(get_cached_element("unit serfs")) } function update_veche() { @@ -1333,10 +1244,6 @@ function update_veche() { add_veche_vp(ui.veche) } -function is_town_locale(loc) { - return data.locales[loc].type === "town" -} - function update_castle(elt, loc) { if (loc === undefined) { elt.classList.toggle("hide", true) @@ -1357,16 +1264,22 @@ function update_castle(elt, loc) { function update_locale(loc) { layout_locale_cylinders(loc) - ui.locale[loc].classList.toggle("action", is_locale_action(loc) || is_laden_march_action(loc)) - ui.locale[loc].classList.toggle("laden", is_laden_march_action(loc)) + ui.locale[loc].classList.toggle("action", is_action("locale", loc) || is_action("laden_march", loc)) + ui.locale[loc].classList.toggle("laden", is_action("laden_march", loc)) ui.locale[loc].classList.toggle("supply_path", !!(view.supply && view.supply[0] === loc)) ui.locale[loc].classList.toggle("supply_source", !!(view.supply && view.supply[1] === loc)) if (ui.locale_name[loc]) { - ui.locale_name[loc].classList.toggle("action", is_locale_action(loc) || is_laden_march_action(loc)) + ui.locale_name[loc].classList.toggle("action", is_action("locale", loc) || is_action("laden_march", loc)) } ui.locale_markers[loc].replaceChildren() + if (view.battle && view.battle.where === loc) + if (view.battle.storm) + ui.locale_markers[loc].appendChild(get_cached_element("marker circle storm")) + else + ui.locale_markers[loc].appendChild(get_cached_element("marker circle battle")) + if (set_has(view.pieces.ravaged, loc)) { let cn if (is_p1_locale(loc)) @@ -1386,11 +1299,8 @@ function update_locale(loc) { ui.locale_markers[loc].appendChild(get_cached_element(cn)) } - // TODO: max 4 walls - reuse elements - if (set_has(view.pieces.walls, loc)) { - let cn = "marker square walls" - ui.locale_markers[loc].appendChild(get_cached_element(cn)) - } + if (set_has(view.pieces.walls, loc)) + ui.locale_markers[loc].appendChild(get_cached_element("marker square walls")) let sieges = map_get(view.pieces.sieges, loc) if (sieges > 0) { @@ -1436,7 +1346,7 @@ function update_plan() { if (is_planning) { ui.plan_actions.classList.remove("hide") for (let lord = 0; lord < 12; ++lord) { - if (is_plan_action(lord)) { + if (is_action("plan", lord)) { ui.plan_action_cards[lord].classList.add("action") ui.plan_action_cards[lord].classList.remove("disabled") } else { @@ -1444,7 +1354,7 @@ function update_plan() { ui.plan_action_cards[lord].classList.add("disabled") } } - if (is_plan_action(-1)) { + if (is_action("plan", -1)) { ui.plan_action_pass_p1.classList.add("action") ui.plan_action_pass_p1.classList.remove("disabled") ui.plan_action_pass_p2.classList.add("action") @@ -1467,17 +1377,14 @@ function update_cards() { for (let c = 0; c < 42; ++c) { let elt = ui.cards[c] elt.classList.toggle("selected", c === view.what) - elt.classList.toggle("action", is_card_action(c)) + elt.classList.toggle("action", is_action("card", c)) } if (view.arts_of_war) { ui.arts_of_war_panel.classList.remove("hide") ui.arts_of_war.replaceChildren() - for (let c of view.arts_of_war) { - let elt = ui.cards[c] - console.log("showin'", c, ui.cards[c]) + for (let c of view.arts_of_war) ui.arts_of_war.appendChild(ui.cards[c]) - } } else { ui.arts_of_war_panel.classList.add("hide") } @@ -1503,19 +1410,16 @@ function update_cards() { } ui.capabilities1.replaceChildren() - for_each_teutonic_card(c => { - if (view.capabilities.includes(c)) - ui.capabilities1.appendChild(ui.cards[c]) - }) + for (let i = first_p1_card; i <= last_p1_card; ++i) + if (view.capabilities.includes(i)) + ui.capabilities1.appendChild(ui.cards[i]) ui.capabilities2.replaceChildren() - for_each_russian_card(c => { - if (view.capabilities.includes(c)) - ui.capabilities2.appendChild(ui.cards[c]) - }) + for (let i = first_p2_card; i <= last_p2_card; ++i) + if (view.capabilities.includes(i)) + ui.capabilities2.appendChild(ui.cards[i]) for (let ix = 0; ix < data.lords.length; ++ix) { - let side = ix < 6 ? "teutonic" : "russian" ui.lord_capabilities[ix].replaceChildren() ui.lord_events[ix].replaceChildren() if (view.reveal & (1 << ix)) { @@ -1535,107 +1439,38 @@ function update_cards() { } } -function has_castle_marker(loc) { - return ( - set_has(view.pieces.castles1, loc) || - set_has(view.pieces.castles2, loc) - ) -} - -function is_castle(loc) { - return data.locales[loc].type === "castle" || has_castle_marker(loc) -} +function update_battle() { + let array = view.battle.array -function is_bishopric(loc) { - return data.locales[loc].type === "bishopric" -} + // Pursuit marker points "up" towards the conceding side + if (view.battle.conceded === "Russians") { + if (view.battle.attacker === "Russians") + ui.pursuit.className = "marker rectangle pursuit teutonic" + else + ui.pursuit.className = "marker rectangle pursuit teutonic rotate" + } else if (view.battle.conceded === "Teutons") { + if (view.battle.attacker === "Teutons") + ui.pursuit.className = "marker rectangle pursuit russian" + else + ui.pursuit.className = "marker rectangle pursuit russian rotate" + } else { + ui.pursuit.className = "hide" + } -function has_walls(loc) { - return set_has(view.pieces.walls, loc) -} + for (let i = 0; i < array.length; ++i) { + let lord = array[i] + ui.battle_grid_array[i].replaceChildren() + if (lord >= 0) + ui.battle_grid_array[i].appendChild(ui.lord_mat[lord]) + ui.battle_grid_array[i].classList.toggle("action", is_action("array", i)) + } -function lord_has_unrouted_units(lord) { - return view.pieces.forces[lord] !== 0 -} + ui.reserves.replaceChildren() + for (let lord of view.battle.reserves) + ui.reserves.appendChild(ui.lord_mat[lord]) -function get_lord_capability(lord, n) { - return view.pieces.capabilities[(lord << 1) + n] -} - -function lord_has_capability_card(lord, c) { - let name = data.cards[c].capability - let c1 = get_lord_capability(lord, 0) - if (c1 >= 0 && data.cards[c1].capability === name) - return true - let c2 = get_lord_capability(lord, 1) - if (c2 >= 0 && data.cards[c2].capability === name) - return true - return false -} - -function lord_has_capability(lord, card_or_list) { - if (Array.isArray(card_or_list)) { - for (let card of card_or_list) - if (lord_has_capability_card(lord, card)) - return true - return false - } - return lord_has_capability_card(lord, card_or_list) -} - -function attacker_has_trebuchets() { - if (view.battle.attacker === "Teutons") { - for (let lord = first_p1_lord; lord <= last_p1_lord; ++lord) { - if (get_lord_locale(lord) === view.battle.where && lord_has_unrouted_units(lord)) { - if (lord_has_capability(lord, AOW_TEUTONIC_TREBUCHETS)) - return true - } - } - } - return false -} - -function count_siege_markers(loc) { - return map_get(view.pieces.sieges, loc, 0) -} - -function update_battle() { - let array = view.battle.array - - // Pursuit marker points "up" towards the conceding side - if (view.battle.conceded === "Russians") { - if (view.battle.attacker === "Russians") - ui.pursuit.className = "marker rectangle pursuit teutonic" - else - ui.pursuit.className = "marker rectangle pursuit teutonic rotate" - } else if (view.battle.conceded === "Teutons") { - if (view.battle.attacker === "Teutons") - ui.pursuit.className = "marker rectangle pursuit russian" - else - ui.pursuit.className = "marker rectangle pursuit russian rotate" - } else { - ui.pursuit.className = "hide" - } - - for (let i = 0; i < array.length; ++i) { - let lord = array[i] - ui.battle_grid_array[i].replaceChildren() - if (lord >= 0) - ui.battle_grid_array[i].appendChild(ui.lord_mat[lord]) - ui.battle_grid_array[i].classList.toggle("action", is_battle_array_action(i)) - } - - ui.reserves.replaceChildren() - for (let lord of view.battle.reserves) - ui.reserves.appendChild(ui.lord_mat[lord]) - - for (let lord = 0; lord < 12; ++lord) { - ui.battle_cylinder[lord].classList.toggle("action", is_lord_action(lord)) - ui.battle_cylinder[lord].classList.toggle("selected", is_lord_selected(lord)) - } - - ui.garrison.classList.toggle("hide", !view.battle.storm) - ui.garrison.classList.toggle("action", is_garrison_action()) + ui.garrison.classList.toggle("hide", !view.battle.storm) + ui.garrison.classList.toggle("action", is_action("garrison")) ui.garrison.replaceChildren() if (view.battle.garrison) { @@ -1703,17 +1538,6 @@ function update_battle() { att_ui.appendChild(get_cached_element("marker square russian siege")) } -function is_lord_in_battle(lord) { - if (view.battle && view.battle.array) { - for (let i = 0; i < 12; ++i) - if (view.battle.array[i] === lord) - return true - if (view.battle.reserves.includes(lord)) - return true - } - return false -} - function update_court() { let tcourt_hdr = (player === "Russians") ? ui.court2_header : ui.court1_header let rcourt_hdr = (player === "Russians") ? ui.court1_header : ui.court2_header @@ -1753,7 +1577,7 @@ function on_update() { } for (let way = 0; way < ui.ways.length; ++way) { - if (is_way_action(way)) + if (is_action("way", way)) ui.ways[way].classList.add("action") else ui.ways[way].classList.remove("action") @@ -1817,7 +1641,7 @@ function on_update() { update_plan() update_cards() - ui.veche.classList.toggle("action", is_veche_action()) + ui.veche.classList.toggle("action", is_action("veche")) if (view.battle && view.battle.array) { ui.reserves_panel.classList.remove("hide") @@ -1846,7 +1670,7 @@ function on_update() { update_court() for (let i = 0; i <= 17; ++i) { - ui.calendar[i].classList.toggle("action", is_calendar_action(i)) + ui.calendar[i].classList.toggle("action", is_action("calendar", i)) if (i >= 1 && i <= 16) ui.calendar[i].classList.toggle("end", i > view.end) } @@ -1927,280 +1751,156 @@ function on_update() { action_button("undo", "Undo") } -function build_div(parent, className, id, onclick) { - let e = document.createElement("div") - e.className = className - if (onclick) { - e.my_id = id - e.addEventListener("mousedown", onclick) - } - if (parent) - parent.appendChild(e) - return e -} +// === LOG === -function build_lord_mat(lord, ix, side, name) { - let mat = build_div(null, `mat ${side} ${name}`) - let bg = build_div(mat, "background") - ui.forces[ix] = build_div(bg, "forces") - ui.routed[ix] = build_div(bg, "routed") - ui.assets[ix] = build_div(bg, "assets") - ui.ready_vassals[ix] = build_div(bg, "ready_vassals") - ui.mustered_vassals[ix] = build_div(bg, "mustered_vassals") - ui.lord_buttons[ix] = build_div(bg, "shield", ix, on_click_cylinder) - ui.lord_capabilities[ix] = build_div(mat, "capabilities") - ui.lord_events[ix] = build_div(mat, "events") - ui.lord_moved1[ix] = build_div(mat, "marker square moved_fought one hide") - ui.lord_moved2[ix] = build_div(mat, "marker square moved_fought two hide") - ui.lord_feed_x2[ix] = build_div(mat, "marker small feed_x2") - ui.lord_mat[ix] = mat +function on_focus_card_tip(c) { + if (c <= first_p1_card) + ui.command.className = `card teutonic aow_${c}` + else + ui.command.className = `card russian aow_${c}` } -function build_card(side, c) { - let card = ui.cards[c] = document.createElement("div") - card.className = `card ${side} aow_${c}` - card.my_id = c - card.addEventListener("mousedown", on_click_card) +function on_blur_card_tip() { + update_current_card_display() } -function build_plan() { - let elt - for (let i = 0; i < 6; ++i) { - elt = document.createElement("div") - elt.className = "hide" - ui.plan_cards.push(elt) - ui.plan.appendChild(elt) - } - for (let lord = 0; lord < 12; ++lord) { - let side = lord < 6 ? "teutonic" : "russian" - elt = document.createElement("div") - elt.className = `card ${side} cc_lord_${lord}` - elt.my_id = lord - elt.addEventListener("mousedown", on_click_plan) - ui.plan_action_cards.push(elt) - ui.plan_actions.appendChild(elt) - } +function sub_card_capability(match, p1) { + let x = p1 | 0 + return `${data.cards[x].capability}` +} - ui.plan_action_pass_p1 = elt = document.createElement("div") - elt.className = `card teutonic cc_pass` - elt.my_id = -1 - elt.addEventListener("mousedown", on_click_plan) - ui.plan_actions.appendChild(elt) +function sub_card_event(match, p1) { + let x = p1 | 0 + return `${data.cards[x].event}` +} - ui.plan_action_pass_p2 = elt = document.createElement("div") - elt.className = `card russian cc_pass` - elt.my_id = -1 - elt.addEventListener("mousedown", on_click_plan) - ui.plan_actions.appendChild(elt) +function on_focus_locale_tip(loc) { + ui.locale[loc].classList.add("tip") + if (ui.locale_name[loc]) + ui.locale_name[loc].classList.add("tip") } -function build_way(name, sel) { - let way = data.ways.findIndex(w => w.name === name) - ui.ways[way] = document.querySelector(sel) - ui.ways[way].my_id = way - ui.ways[way].addEventListener("mousedown", on_click_way) +function on_blur_locale_tip(loc) { + ui.locale[loc].classList.remove("tip") + if (ui.locale_name[loc]) + ui.locale_name[loc].classList.remove("tip") } -const locale_size = { - region: [ 88, 56 ], - town: [ 80, 72 ], - traderoute: [ 90, 54 ], - fort: [ 96, 54 ], - castle: [ 96, 56 ], - city: [ 126, 80 ], - bishopric: [ 106, 72 ], - novgorod: [ 144, 86 ], +function on_click_locale_tip(loc) { + ui.locale[loc].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) } -function build_map() { - for (let i = 0; i < data.locales.length; ++i) - locale_layout[i] = [] +function on_focus_way_tip(way) { + ui.ways[way].classList.add("tip") +} - data.locales.forEach((locale, ix) => { - let region = clean_name(locale.region) - let x = floor(locale.box.x * MAP_DPI / 300) - let y = floor(locale.box.y * MAP_DPI / 300) - let w = ceil((locale.box.x+locale.box.w) * MAP_DPI / 300) - x - let h = ceil((locale.box.y+locale.box.h) * MAP_DPI / 300) - y - let xc = round(x + w / 2) - let yc = round(y + h / 2) - let e +function on_blur_way_tip(way) { + ui.ways[way].classList.remove("tip") +} - switch (locale.type) { - case "town": - locale_xy[ix] = [ xc, y - 24 ] - w = locale_size.town[0] - h = locale_size.town[1] - x = xc - w/2 - y = y - h + 16 - break - case "region": - xc += 2 - yc -= 3 - locale_xy[ix] = [ xc, yc - 24 ] - w = locale_size.region[0] - h = locale_size.region[1] - x = xc - w/2 - y = yc - h/2 - break - default: - locale_xy[ix] = [ xc, y - 36 ] - break - } +function on_click_way_tip(way) { + ui.ways[way].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) +} - // Main Area - e = ui.locale[ix] = document.createElement("div") - e.className = "locale " + locale.type + " " + region - e.my_id = ix - if (locale.type !== "region" && locale.type !== "town") { - let ew = locale_size[locale.type][0] - let eh = locale_size[locale.type][1] - e.style.top = (y - eh) + "px" - e.style.left = (xc - ew/2) + "px" - e.style.width = (ew) + "px" - e.style.height = (eh) + "px" - } else { - e.style.left = x + "px" - e.style.top = y + "px" - e.style.width = w + "px" - e.style.height = h + "px" - } - e.addEventListener("mousedown", on_click_locale) - e.addEventListener("mouseenter", on_focus_locale) - e.addEventListener("mouseleave", on_blur) - document.getElementById("locales").appendChild(e) +function on_click_lord_tip(lord) { + ui.lord_mat[lord].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) +} - // Name Plate - if (locale.type !== 'region' && locale.type !== 'town') { - e = ui.locale_name[ix] = document.createElement("div") - e.className = "locale_name " + locale.type + " " + region - e.style.left = x + "px" - e.style.top = y + "px" - e.style.width = w + "px" - e.style.height = h + "px" - e.my_id = ix - e.addEventListener("mousedown", on_click_locale) - e.addEventListener("mouseenter", on_focus_locale) - e.addEventListener("mouseleave", on_blur) - document.getElementById("locales").appendChild(e) - } +function sub_locale_name(match, p1) { + let x = p1 | 0 + let n = data.locales[x].name + return `${n}` +} - // Locale Markers - e = ui.locale_markers[ix] = document.createElement("div") - e.className = "locale_markers " + locale.type + " " + region - x = locale_xy[ix][0] - 196/2 - y = locale_xy[ix][1] + 36 - e.style.top = y + "px" - e.style.left = x + "px" - e.style.width = 196 + "px" - document.getElementById("pieces").appendChild(e) - }) +function sub_lord_name(match, p1) { + let x = p1 | 0 + let n = data.lords[x].name + return `${n}` +} - let x = 160 - let y = 2740 - data.lords.forEach((lord, ix) => { - let e = ui.lord_cylinder[ix] = document.createElement("div") - e.className = "cylinder lord " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide" - e.my_id = ix - e.addEventListener("mousedown", on_click_cylinder) - e.addEventListener("mouseenter", on_focus_cylinder) - e.addEventListener("mouseleave", on_blur) - document.getElementById("pieces").appendChild(e) +function sub_way_name(match, p1) { + let x = p1 | 0 + let n = data.ways[x].name + return `${n}` +} - e = ui.battle_cylinder[ix] = document.createElement("div") - e.className = "cylinder lord " + clean_name(lord.side) + " " + clean_name(lord.name) - e.my_id = ix - e.addEventListener("mousedown", on_click_cylinder) - e.addEventListener("mouseenter", on_focus_cylinder) - e.addEventListener("mouseleave", on_blur) - e = ui.lord_service[ix] = document.createElement("div") - e.className = "service_marker lord image" + lord.image + " " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide" - e.my_id = ix - e.addEventListener("mousedown", on_click_lord_service_marker) - e.addEventListener("mouseenter", on_focus_lord_service_marker) - e.addEventListener("mouseleave", on_blur_lord_service_marker) - document.getElementById("pieces").appendChild(e) +function on_log(text) { + let p = document.createElement("div") - build_lord_mat(lord, ix, clean_name(lord.side), clean_name(lord.name)) + if (text.match(/^>>/)) { + text = text.substring(2) + p.className = "ii" + } - x += 70 - }) + if (text.match(/^>/)) { + text = text.substring(1) + p.className = "i" + } - data.vassals.forEach((vassal, ix) => { - let lord = data.lords[vassal.lord] - let e = ui.vassal_service[ix] = document.createElement("div") - e.className = "service_marker vassal image" + vassal.image + " " + clean_name(lord.side) + " " + clean_name(vassal.name) + " hide" - e.my_id = ix - e.addEventListener("mousedown", on_click_vassal_service_marker) - e.addEventListener("mouseenter", on_focus_vassal_service_marker) - e.addEventListener("mouseleave", on_blur) - document.getElementById("pieces").appendChild(e) - }) + text = text.replace(/&/g, "&") + text = text.replace(//g, ">") - document.getElementById("legate").addEventListener("mouseenter", on_focus_legate) - document.getElementById("legate").addEventListener("mouseleave", on_blur) - document.getElementById("legate").addEventListener("mousedown", on_click_legate) - ui.veche.addEventListener("mousedown", on_click_veche) + text = text.replace(/C(\d+)/g, sub_card_capability) + text = text.replace(/E(\d+)/g, sub_card_event) + text = text.replace(/L(\d+)/g, sub_lord_name) + text = text.replace(/%(\d+)/g, sub_locale_name) + text = text.replace(/W(\d+)/g, sub_way_name) - for (let name in original_boxes) { - let x = round(original_boxes[name][0] * MAP_DPI / 300) - let y = round(original_boxes[name][1] * MAP_DPI / 300) - let w = round(original_boxes[name][2] * MAP_DPI / 300) - 8 - let h = round(original_boxes[name][3] * MAP_DPI / 300) - 8 - let e = ui.boxes[name] = document.createElement("div") - e.className = "box " + name - e.style.left = x + "px" - e.style.top = y + "px" - e.style.width = w + "px" - e.style.height = h + "px" - document.getElementById("boxes").appendChild(e) + if (text.match(/^\.h1/)) { + text = text.substring(4) + p.className = "h1" } - - ui.calendar = [ - document.querySelector(".calendar.box0"), - document.querySelector(".calendar.box1"), - document.querySelector(".calendar.box2"), - document.querySelector(".calendar.box3"), - document.querySelector(".calendar.box4"), - document.querySelector(".calendar.box5"), - document.querySelector(".calendar.box6"), - document.querySelector(".calendar.box7"), - document.querySelector(".calendar.box8"), - document.querySelector(".calendar.box9"), - document.querySelector(".calendar.box10"), - document.querySelector(".calendar.box11"), - document.querySelector(".calendar.box12"), - document.querySelector(".calendar.box13"), - document.querySelector(".calendar.box14"), - document.querySelector(".calendar.box15"), - document.querySelector(".calendar.box16"), - document.querySelector(".calendar.box17") - ] - - for (let i = 0; i <= 17; ++i) { - ui.calendar[i].my_id = i - ui.calendar[i].addEventListener("mousedown", on_click_calendar) + else if (text.match(/^\.h2t/)) { + text = text.substring(5) + p.className = "h2 teutonic" + } + else if (text.match(/^\.h2r/)) { + text = text.substring(5) + p.className = "h2 russian" + } + else if (text.match(/^\.h2/)) { + text = text.substring(4) + p.className = "h2" + } + else if (text.match(/^\.h3t/)) { + text = text.substring(5) + p.className = "h3 teutonic" + } + else if (text.match(/^\.h3r/)) { + text = text.substring(5) + p.className = "h3 russian" + } + else if (text.match(/^\.h3/)) { + text = text.substring(4) + p.className = "h3" + } + else if (text.match(/^\.h4/)) { + text = text.substring(4) + p.className = "h4" + } + else if (text.match(/^\.h5/)) { + text = text.substring(4) + p.className = "h5" } - build_way("Crossroads", ".way.crossroads") - build_way("Peipus E", ".way.peipus-east") - build_way("Peipus W", ".way.peipus-north") - build_way("Wirz", ".way.wirz") - - build_plan() + p.innerHTML = text + return p +} - ui.garrison.addEventListener("mousedown", on_click_garrison) +function pack1_get(word, n) { + return (word >>> n) & 1 +} - for (let i = 0; i < 12; ++i) { - ui.battle_grid_array[i].my_id = i - ui.battle_grid_array[i].addEventListener("mousedown", on_click_array) - } +function pack2_get(word, n) { + n = n << 1 + return (word >>> n) & 3 +} - for (let c = 0; c < 21; ++c) - build_card("teutonic", c) - for (let c = 21; c < 42; ++c) - build_card("russian", c) +function pack4_get(word, n) { + n = n << 2 + return (word >>> n) & 15 } build_map() diff --git a/tools/gendata.js b/tools/gendata.js index 05ac486..9505548 100644 --- a/tools/gendata.js +++ b/tools/gendata.js @@ -123,10 +123,10 @@ let strongholds = [] 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) + x = Math.floor(x * 75 / 300) + y = Math.floor(y * 75 / 300) + w = Math.ceil(w * 75 / 300) + h = Math.ceil(h * 75 / 300) locmap[name] = locales.length let vp = vp_map[type] let walls = wall_map[type] -- cgit v1.2.3