diff --git a/package.json b/package.json index 5d0f7f3..b1fc088 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "generate:version-info": "bun run src/scripts/generateVersionInfo.ts", "import:arma": "bun run src/tools/import/importArmaLibrary.ts", "icons:arma": "bun run src/tools/import/extractArmaIcons.ts", + "markers:arma": "bun run src/tools/arma/extractMarkers.ts", "version:bump": "bun run src/scripts/bumpVersion.ts", "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload", diff --git a/public/arma/markers/a3/ui_f/data/igui/cfg/islandmap/iconplayer_ca.png b/public/arma/markers/a3/ui_f/data/igui/cfg/islandmap/iconplayer_ca.png new file mode 100644 index 0000000..ac00e4e Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/igui/cfg/islandmap/iconplayer_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectable_ca.png b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectable_ca.png new file mode 100644 index 0000000..2fee9d9 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectable_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedenemy_ca.png b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedenemy_ca.png new file mode 100644 index 0000000..c8aef7a Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedenemy_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedfriendly_ca.png b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedfriendly_ca.png new file mode 100644 index 0000000..2fee9d9 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedfriendly_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedmission_ca.png b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedmission_ca.png new file mode 100644 index 0000000..96da088 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedmission_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/groupicons/waypoint.png b/public/arma/markers/a3/ui_f/data/map/groupicons/waypoint.png new file mode 100644 index 0000000..8c9c270 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/groupicons/waypoint.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/bunker_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/bunker_ca.png new file mode 100644 index 0000000..a44bb13 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/bunker_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/bush_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/bush_ca.png new file mode 100644 index 0000000..56a9ec4 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/bush_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/busstop_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/busstop_ca.png new file mode 100644 index 0000000..27b67cc Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/busstop_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/chapel_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/chapel_ca.png new file mode 100644 index 0000000..4ea1032 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/chapel_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/church_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/church_ca.png new file mode 100644 index 0000000..9121393 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/church_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/cross_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/cross_ca.png new file mode 100644 index 0000000..402a789 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/cross_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/fountain_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/fountain_ca.png new file mode 100644 index 0000000..c557975 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/fountain_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/fuelstation_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/fuelstation_ca.png new file mode 100644 index 0000000..8f3473b Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/fuelstation_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/hospital_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/hospital_ca.png new file mode 100644 index 0000000..74fd769 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/hospital_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/lighthouse_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/lighthouse_ca.png new file mode 100644 index 0000000..161904e Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/lighthouse_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/power_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/power_ca.png new file mode 100644 index 0000000..9021f30 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/power_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/powersolar_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/powersolar_ca.png new file mode 100644 index 0000000..34b3c66 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/powersolar_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/powerwave_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/powerwave_ca.png new file mode 100644 index 0000000..6552c75 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/powerwave_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/powerwind_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/powerwind_ca.png new file mode 100644 index 0000000..f694c30 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/powerwind_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/quay_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/quay_ca.png new file mode 100644 index 0000000..860cb1f Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/quay_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/rock_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/rock_ca.png new file mode 100644 index 0000000..669feba Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/rock_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/ruin_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/ruin_ca.png new file mode 100644 index 0000000..b162584 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/ruin_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/smalltree_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/smalltree_ca.png new file mode 100644 index 0000000..7a5f9b2 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/smalltree_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/stack_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/stack_ca.png new file mode 100644 index 0000000..a3a315f Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/stack_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/tourism_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/tourism_ca.png new file mode 100644 index 0000000..c3e0da0 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/tourism_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/transmitter_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/transmitter_ca.png new file mode 100644 index 0000000..bd46d04 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/transmitter_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/tree_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/tree_ca.png new file mode 100644 index 0000000..cd6250c Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/tree_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/viewtower_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/viewtower_ca.png new file mode 100644 index 0000000..3cbd515 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/viewtower_ca.png differ diff --git a/public/arma/markers/a3/ui_f/data/map/mapcontrol/watertower_ca.png b/public/arma/markers/a3/ui_f/data/map/mapcontrol/watertower_ca.png new file mode 100644 index 0000000..41d3a32 Binary files /dev/null and b/public/arma/markers/a3/ui_f/data/map/mapcontrol/watertower_ca.png differ diff --git a/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/kia_ca.png b/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/kia_ca.png new file mode 100644 index 0000000..2e64ef6 Binary files /dev/null and b/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/kia_ca.png differ diff --git a/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefield_ca.png b/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefield_ca.png new file mode 100644 index 0000000..bdc93fe Binary files /dev/null and b/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefield_ca.png differ diff --git a/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefieldap_ca.png b/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefieldap_ca.png new file mode 100644 index 0000000..a050b30 Binary files /dev/null and b/public/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefieldap_ca.png differ diff --git a/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/astra_ca.png b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/astra_ca.png new file mode 100644 index 0000000..ec0a485 Binary files /dev/null and b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/astra_ca.png differ diff --git a/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/ldf_ca.png b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/ldf_ca.png new file mode 100644 index 0000000..9ad8340 Binary files /dev/null and b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/ldf_ca.png differ diff --git a/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonia_ca.png b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonia_ca.png new file mode 100644 index 0000000..3be8597 Binary files /dev/null and b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonia_ca.png differ diff --git a/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonialooters_ca.png b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonialooters_ca.png new file mode 100644 index 0000000..8c50eb6 Binary files /dev/null and b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonialooters_ca.png differ diff --git a/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/russia_ca.png b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/russia_ca.png new file mode 100644 index 0000000..e5a16b8 Binary files /dev/null and b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/russia_ca.png differ diff --git a/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/spetsnaz_ca.png b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/spetsnaz_ca.png new file mode 100644 index 0000000..7fea00c Binary files /dev/null and b/public/arma/markers/a3/ui_f_enoch/data/cfgmarkers/spetsnaz_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/b_ordnance_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/b_ordnance_ca.png new file mode 100644 index 0000000..59c2e8c Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/b_ordnance_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/civildefense.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/civildefense.png new file mode 100644 index 0000000..19b0103 Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/civildefense.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/culturalproperty_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/culturalproperty_ca.png new file mode 100644 index 0000000..3643bec Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/culturalproperty_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/dangerousforces_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/dangerousforces_ca.png new file mode 100644 index 0000000..d195c59 Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/dangerousforces_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/idap_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/idap_ca.png new file mode 100644 index 0000000..c9d5861 Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/idap_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/n_ordnance_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/n_ordnance_ca.png new file mode 100644 index 0000000..76e3cce Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/n_ordnance_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/o_ordnance_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/o_ordnance_ca.png new file mode 100644 index 0000000..b58a53b Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/o_ordnance_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/redcrystal_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/redcrystal_ca.png new file mode 100644 index 0000000..e45a982 Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/redcrystal_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/safetyzone_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/safetyzone_ca.png new file mode 100644 index 0000000..6a0fcf3 Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/safetyzone_ca.png differ diff --git a/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/white_ca.png b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/white_ca.png new file mode 100644 index 0000000..47542ec Binary files /dev/null and b/public/arma/markers/a3/ui_f_orange/data/cfgmarkers/white_ca.png differ diff --git a/public/arma/markers/flags/aaf_ca.png b/public/arma/markers/flags/aaf_ca.png new file mode 100644 index 0000000..27a46b7 Binary files /dev/null and b/public/arma/markers/flags/aaf_ca.png differ diff --git a/public/arma/markers/flags/altis_ca.png b/public/arma/markers/flags/altis_ca.png new file mode 100644 index 0000000..66b988e Binary files /dev/null and b/public/arma/markers/flags/altis_ca.png differ diff --git a/public/arma/markers/flags/altiscolonial_ca.png b/public/arma/markers/flags/altiscolonial_ca.png new file mode 100644 index 0000000..92d6101 Binary files /dev/null and b/public/arma/markers/flags/altiscolonial_ca.png differ diff --git a/public/arma/markers/flags/belgium_ca.png b/public/arma/markers/flags/belgium_ca.png new file mode 100644 index 0000000..bcc7dfe Binary files /dev/null and b/public/arma/markers/flags/belgium_ca.png differ diff --git a/public/arma/markers/flags/canada_ca.png b/public/arma/markers/flags/canada_ca.png new file mode 100644 index 0000000..abdf5b8 Binary files /dev/null and b/public/arma/markers/flags/canada_ca.png differ diff --git a/public/arma/markers/flags/croatia_ca.png b/public/arma/markers/flags/croatia_ca.png new file mode 100644 index 0000000..dc1505d Binary files /dev/null and b/public/arma/markers/flags/croatia_ca.png differ diff --git a/public/arma/markers/flags/csat_ca.png b/public/arma/markers/flags/csat_ca.png new file mode 100644 index 0000000..a9053f7 Binary files /dev/null and b/public/arma/markers/flags/csat_ca.png differ diff --git a/public/arma/markers/flags/ctrg_ca.png b/public/arma/markers/flags/ctrg_ca.png new file mode 100644 index 0000000..2742705 Binary files /dev/null and b/public/arma/markers/flags/ctrg_ca.png differ diff --git a/public/arma/markers/flags/czechrepublic_ca.png b/public/arma/markers/flags/czechrepublic_ca.png new file mode 100644 index 0000000..bd6096e Binary files /dev/null and b/public/arma/markers/flags/czechrepublic_ca.png differ diff --git a/public/arma/markers/flags/denmark_ca.png b/public/arma/markers/flags/denmark_ca.png new file mode 100644 index 0000000..f0a55e6 Binary files /dev/null and b/public/arma/markers/flags/denmark_ca.png differ diff --git a/public/arma/markers/flags/eu_ca.png b/public/arma/markers/flags/eu_ca.png new file mode 100644 index 0000000..88174e4 Binary files /dev/null and b/public/arma/markers/flags/eu_ca.png differ diff --git a/public/arma/markers/flags/fia_ca.png b/public/arma/markers/flags/fia_ca.png new file mode 100644 index 0000000..f3c8021 Binary files /dev/null and b/public/arma/markers/flags/fia_ca.png differ diff --git a/public/arma/markers/flags/france_ca.png b/public/arma/markers/flags/france_ca.png new file mode 100644 index 0000000..c2e3d8f Binary files /dev/null and b/public/arma/markers/flags/france_ca.png differ diff --git a/public/arma/markers/flags/georgia_ca.png b/public/arma/markers/flags/georgia_ca.png new file mode 100644 index 0000000..d861b41 Binary files /dev/null and b/public/arma/markers/flags/georgia_ca.png differ diff --git a/public/arma/markers/flags/germany_ca.png b/public/arma/markers/flags/germany_ca.png new file mode 100644 index 0000000..02f33d2 Binary files /dev/null and b/public/arma/markers/flags/germany_ca.png differ diff --git a/public/arma/markers/flags/greece_ca.png b/public/arma/markers/flags/greece_ca.png new file mode 100644 index 0000000..ce27f8d Binary files /dev/null and b/public/arma/markers/flags/greece_ca.png differ diff --git a/public/arma/markers/flags/hungary_ca.png b/public/arma/markers/flags/hungary_ca.png new file mode 100644 index 0000000..0d03369 Binary files /dev/null and b/public/arma/markers/flags/hungary_ca.png differ diff --git a/public/arma/markers/flags/iceland_ca.png b/public/arma/markers/flags/iceland_ca.png new file mode 100644 index 0000000..a7b0b7d Binary files /dev/null and b/public/arma/markers/flags/iceland_ca.png differ diff --git a/public/arma/markers/flags/italy_ca.png b/public/arma/markers/flags/italy_ca.png new file mode 100644 index 0000000..f9b1e85 Binary files /dev/null and b/public/arma/markers/flags/italy_ca.png differ diff --git a/public/arma/markers/flags/luxembourg_ca.png b/public/arma/markers/flags/luxembourg_ca.png new file mode 100644 index 0000000..090a4c2 Binary files /dev/null and b/public/arma/markers/flags/luxembourg_ca.png differ diff --git a/public/arma/markers/flags/nato_ca.png b/public/arma/markers/flags/nato_ca.png new file mode 100644 index 0000000..de51697 Binary files /dev/null and b/public/arma/markers/flags/nato_ca.png differ diff --git a/public/arma/markers/flags/netherlands_ca.png b/public/arma/markers/flags/netherlands_ca.png new file mode 100644 index 0000000..befd0f6 Binary files /dev/null and b/public/arma/markers/flags/netherlands_ca.png differ diff --git a/public/arma/markers/flags/norway_ca.png b/public/arma/markers/flags/norway_ca.png new file mode 100644 index 0000000..40bf8d1 Binary files /dev/null and b/public/arma/markers/flags/norway_ca.png differ diff --git a/public/arma/markers/flags/poland_ca.png b/public/arma/markers/flags/poland_ca.png new file mode 100644 index 0000000..50b4b3c Binary files /dev/null and b/public/arma/markers/flags/poland_ca.png differ diff --git a/public/arma/markers/flags/portugal_ca.png b/public/arma/markers/flags/portugal_ca.png new file mode 100644 index 0000000..7d2b65e Binary files /dev/null and b/public/arma/markers/flags/portugal_ca.png differ diff --git a/public/arma/markers/flags/slovakia_ca.png b/public/arma/markers/flags/slovakia_ca.png new file mode 100644 index 0000000..2643b07 Binary files /dev/null and b/public/arma/markers/flags/slovakia_ca.png differ diff --git a/public/arma/markers/flags/slovenia_ca.png b/public/arma/markers/flags/slovenia_ca.png new file mode 100644 index 0000000..de20db3 Binary files /dev/null and b/public/arma/markers/flags/slovenia_ca.png differ diff --git a/public/arma/markers/flags/spain_ca.png b/public/arma/markers/flags/spain_ca.png new file mode 100644 index 0000000..cb6df78 Binary files /dev/null and b/public/arma/markers/flags/spain_ca.png differ diff --git a/public/arma/markers/flags/syndicat_ca.png b/public/arma/markers/flags/syndicat_ca.png new file mode 100644 index 0000000..2ccdf7b Binary files /dev/null and b/public/arma/markers/flags/syndicat_ca.png differ diff --git a/public/arma/markers/flags/tanoa_ca.png b/public/arma/markers/flags/tanoa_ca.png new file mode 100644 index 0000000..2f5b778 Binary files /dev/null and b/public/arma/markers/flags/tanoa_ca.png differ diff --git a/public/arma/markers/flags/tanoagendarmerie_ca.png b/public/arma/markers/flags/tanoagendarmerie_ca.png new file mode 100644 index 0000000..8f2c113 Binary files /dev/null and b/public/arma/markers/flags/tanoagendarmerie_ca.png differ diff --git a/public/arma/markers/flags/uk_ca.png b/public/arma/markers/flags/uk_ca.png new file mode 100644 index 0000000..dd57d5d Binary files /dev/null and b/public/arma/markers/flags/uk_ca.png differ diff --git a/public/arma/markers/flags/un_ca.png b/public/arma/markers/flags/un_ca.png new file mode 100644 index 0000000..90530e7 Binary files /dev/null and b/public/arma/markers/flags/un_ca.png differ diff --git a/public/arma/markers/flags/usa_ca.png b/public/arma/markers/flags/usa_ca.png new file mode 100644 index 0000000..aaa97ad Binary files /dev/null and b/public/arma/markers/flags/usa_ca.png differ diff --git a/public/arma/markers/flags/viper_ca.png b/public/arma/markers/flags/viper_ca.png new file mode 100644 index 0000000..c1ec4d1 Binary files /dev/null and b/public/arma/markers/flags/viper_ca.png differ diff --git a/public/arma/markers/handdrawn/ambush_ca.png b/public/arma/markers/handdrawn/ambush_ca.png new file mode 100644 index 0000000..95323b3 Binary files /dev/null and b/public/arma/markers/handdrawn/ambush_ca.png differ diff --git a/public/arma/markers/handdrawn/arrow_ca.png b/public/arma/markers/handdrawn/arrow_ca.png new file mode 100644 index 0000000..9932485 Binary files /dev/null and b/public/arma/markers/handdrawn/arrow_ca.png differ diff --git a/public/arma/markers/handdrawn/destroy_ca.png b/public/arma/markers/handdrawn/destroy_ca.png new file mode 100644 index 0000000..47cb828 Binary files /dev/null and b/public/arma/markers/handdrawn/destroy_ca.png differ diff --git a/public/arma/markers/handdrawn/dot_ca.png b/public/arma/markers/handdrawn/dot_ca.png new file mode 100644 index 0000000..af9cd4d Binary files /dev/null and b/public/arma/markers/handdrawn/dot_ca.png differ diff --git a/public/arma/markers/handdrawn/end_ca.png b/public/arma/markers/handdrawn/end_ca.png new file mode 100644 index 0000000..48728e2 Binary files /dev/null and b/public/arma/markers/handdrawn/end_ca.png differ diff --git a/public/arma/markers/handdrawn/flag_ca.png b/public/arma/markers/handdrawn/flag_ca.png new file mode 100644 index 0000000..8d6d856 Binary files /dev/null and b/public/arma/markers/handdrawn/flag_ca.png differ diff --git a/public/arma/markers/handdrawn/join_ca.png b/public/arma/markers/handdrawn/join_ca.png new file mode 100644 index 0000000..dd1edca Binary files /dev/null and b/public/arma/markers/handdrawn/join_ca.png differ diff --git a/public/arma/markers/handdrawn/objective_ca.png b/public/arma/markers/handdrawn/objective_ca.png new file mode 100644 index 0000000..3606645 Binary files /dev/null and b/public/arma/markers/handdrawn/objective_ca.png differ diff --git a/public/arma/markers/handdrawn/pickup_ca.png b/public/arma/markers/handdrawn/pickup_ca.png new file mode 100644 index 0000000..afedcd1 Binary files /dev/null and b/public/arma/markers/handdrawn/pickup_ca.png differ diff --git a/public/arma/markers/handdrawn/start_ca.png b/public/arma/markers/handdrawn/start_ca.png new file mode 100644 index 0000000..113331f Binary files /dev/null and b/public/arma/markers/handdrawn/start_ca.png differ diff --git a/public/arma/markers/handdrawn/unknown_ca.png b/public/arma/markers/handdrawn/unknown_ca.png new file mode 100644 index 0000000..c52e4d8 Binary files /dev/null and b/public/arma/markers/handdrawn/unknown_ca.png differ diff --git a/public/arma/markers/handdrawn/warning_ca.png b/public/arma/markers/handdrawn/warning_ca.png new file mode 100644 index 0000000..53d5e31 Binary files /dev/null and b/public/arma/markers/handdrawn/warning_ca.png differ diff --git a/public/arma/markers/military/ambush_ca.png b/public/arma/markers/military/ambush_ca.png new file mode 100644 index 0000000..4879017 Binary files /dev/null and b/public/arma/markers/military/ambush_ca.png differ diff --git a/public/arma/markers/military/arrow2_ca.png b/public/arma/markers/military/arrow2_ca.png new file mode 100644 index 0000000..560fceb Binary files /dev/null and b/public/arma/markers/military/arrow2_ca.png differ diff --git a/public/arma/markers/military/arrow_ca.png b/public/arma/markers/military/arrow_ca.png new file mode 100644 index 0000000..b17c1a3 Binary files /dev/null and b/public/arma/markers/military/arrow_ca.png differ diff --git a/public/arma/markers/military/box_ca.png b/public/arma/markers/military/box_ca.png new file mode 100644 index 0000000..3d5ed17 Binary files /dev/null and b/public/arma/markers/military/box_ca.png differ diff --git a/public/arma/markers/military/circle_ca.png b/public/arma/markers/military/circle_ca.png new file mode 100644 index 0000000..18b411a Binary files /dev/null and b/public/arma/markers/military/circle_ca.png differ diff --git a/public/arma/markers/military/destroy_ca.png b/public/arma/markers/military/destroy_ca.png new file mode 100644 index 0000000..998457f Binary files /dev/null and b/public/arma/markers/military/destroy_ca.png differ diff --git a/public/arma/markers/military/dot_ca.png b/public/arma/markers/military/dot_ca.png new file mode 100644 index 0000000..b7af4c6 Binary files /dev/null and b/public/arma/markers/military/dot_ca.png differ diff --git a/public/arma/markers/military/end_ca.png b/public/arma/markers/military/end_ca.png new file mode 100644 index 0000000..ad785b3 Binary files /dev/null and b/public/arma/markers/military/end_ca.png differ diff --git a/public/arma/markers/military/flag_ca.png b/public/arma/markers/military/flag_ca.png new file mode 100644 index 0000000..d5affa2 Binary files /dev/null and b/public/arma/markers/military/flag_ca.png differ diff --git a/public/arma/markers/military/join_ca.png b/public/arma/markers/military/join_ca.png new file mode 100644 index 0000000..07ac107 Binary files /dev/null and b/public/arma/markers/military/join_ca.png differ diff --git a/public/arma/markers/military/marker_ca.png b/public/arma/markers/military/marker_ca.png new file mode 100644 index 0000000..dade0d6 Binary files /dev/null and b/public/arma/markers/military/marker_ca.png differ diff --git a/public/arma/markers/military/objective_ca.png b/public/arma/markers/military/objective_ca.png new file mode 100644 index 0000000..61fa2a4 Binary files /dev/null and b/public/arma/markers/military/objective_ca.png differ diff --git a/public/arma/markers/military/pickup_ca.png b/public/arma/markers/military/pickup_ca.png new file mode 100644 index 0000000..532a0a4 Binary files /dev/null and b/public/arma/markers/military/pickup_ca.png differ diff --git a/public/arma/markers/military/start_ca.png b/public/arma/markers/military/start_ca.png new file mode 100644 index 0000000..de3a026 Binary files /dev/null and b/public/arma/markers/military/start_ca.png differ diff --git a/public/arma/markers/military/triangle_ca.png b/public/arma/markers/military/triangle_ca.png new file mode 100644 index 0000000..fe20c61 Binary files /dev/null and b/public/arma/markers/military/triangle_ca.png differ diff --git a/public/arma/markers/military/unknown_ca.png b/public/arma/markers/military/unknown_ca.png new file mode 100644 index 0000000..87d4d55 Binary files /dev/null and b/public/arma/markers/military/unknown_ca.png differ diff --git a/public/arma/markers/military/warning_ca.png b/public/arma/markers/military/warning_ca.png new file mode 100644 index 0000000..f724ecc Binary files /dev/null and b/public/arma/markers/military/warning_ca.png differ diff --git a/public/arma/markers/nato/b_air.png b/public/arma/markers/nato/b_air.png new file mode 100644 index 0000000..5abde86 Binary files /dev/null and b/public/arma/markers/nato/b_air.png differ diff --git a/public/arma/markers/nato/b_antiair.png b/public/arma/markers/nato/b_antiair.png new file mode 100644 index 0000000..2ba4ace Binary files /dev/null and b/public/arma/markers/nato/b_antiair.png differ diff --git a/public/arma/markers/nato/b_armor.png b/public/arma/markers/nato/b_armor.png new file mode 100644 index 0000000..8fe4304 Binary files /dev/null and b/public/arma/markers/nato/b_armor.png differ diff --git a/public/arma/markers/nato/b_art.png b/public/arma/markers/nato/b_art.png new file mode 100644 index 0000000..c552a81 Binary files /dev/null and b/public/arma/markers/nato/b_art.png differ diff --git a/public/arma/markers/nato/b_hq.png b/public/arma/markers/nato/b_hq.png new file mode 100644 index 0000000..e70146f Binary files /dev/null and b/public/arma/markers/nato/b_hq.png differ diff --git a/public/arma/markers/nato/b_inf.png b/public/arma/markers/nato/b_inf.png new file mode 100644 index 0000000..9808cc5 Binary files /dev/null and b/public/arma/markers/nato/b_inf.png differ diff --git a/public/arma/markers/nato/b_installation.png b/public/arma/markers/nato/b_installation.png new file mode 100644 index 0000000..2d4b873 Binary files /dev/null and b/public/arma/markers/nato/b_installation.png differ diff --git a/public/arma/markers/nato/b_maint.png b/public/arma/markers/nato/b_maint.png new file mode 100644 index 0000000..2f3bdf4 Binary files /dev/null and b/public/arma/markers/nato/b_maint.png differ diff --git a/public/arma/markers/nato/b_mech_inf.png b/public/arma/markers/nato/b_mech_inf.png new file mode 100644 index 0000000..0b6dff9 Binary files /dev/null and b/public/arma/markers/nato/b_mech_inf.png differ diff --git a/public/arma/markers/nato/b_med.png b/public/arma/markers/nato/b_med.png new file mode 100644 index 0000000..9061b1e Binary files /dev/null and b/public/arma/markers/nato/b_med.png differ diff --git a/public/arma/markers/nato/b_mortar.png b/public/arma/markers/nato/b_mortar.png new file mode 100644 index 0000000..4df1214 Binary files /dev/null and b/public/arma/markers/nato/b_mortar.png differ diff --git a/public/arma/markers/nato/b_motor_inf.png b/public/arma/markers/nato/b_motor_inf.png new file mode 100644 index 0000000..4b21667 Binary files /dev/null and b/public/arma/markers/nato/b_motor_inf.png differ diff --git a/public/arma/markers/nato/b_naval.png b/public/arma/markers/nato/b_naval.png new file mode 100644 index 0000000..3b0990c Binary files /dev/null and b/public/arma/markers/nato/b_naval.png differ diff --git a/public/arma/markers/nato/b_plane.png b/public/arma/markers/nato/b_plane.png new file mode 100644 index 0000000..3f5a08d Binary files /dev/null and b/public/arma/markers/nato/b_plane.png differ diff --git a/public/arma/markers/nato/b_recon.png b/public/arma/markers/nato/b_recon.png new file mode 100644 index 0000000..a3fc9fa Binary files /dev/null and b/public/arma/markers/nato/b_recon.png differ diff --git a/public/arma/markers/nato/b_service.png b/public/arma/markers/nato/b_service.png new file mode 100644 index 0000000..76f698a Binary files /dev/null and b/public/arma/markers/nato/b_service.png differ diff --git a/public/arma/markers/nato/b_support.png b/public/arma/markers/nato/b_support.png new file mode 100644 index 0000000..0160437 Binary files /dev/null and b/public/arma/markers/nato/b_support.png differ diff --git a/public/arma/markers/nato/b_uav.png b/public/arma/markers/nato/b_uav.png new file mode 100644 index 0000000..c190d3e Binary files /dev/null and b/public/arma/markers/nato/b_uav.png differ diff --git a/public/arma/markers/nato/b_unknown.png b/public/arma/markers/nato/b_unknown.png new file mode 100644 index 0000000..b32f4ca Binary files /dev/null and b/public/arma/markers/nato/b_unknown.png differ diff --git a/public/arma/markers/nato/c_air.png b/public/arma/markers/nato/c_air.png new file mode 100644 index 0000000..4d2013a Binary files /dev/null and b/public/arma/markers/nato/c_air.png differ diff --git a/public/arma/markers/nato/c_car.png b/public/arma/markers/nato/c_car.png new file mode 100644 index 0000000..069cca1 Binary files /dev/null and b/public/arma/markers/nato/c_car.png differ diff --git a/public/arma/markers/nato/c_plane.png b/public/arma/markers/nato/c_plane.png new file mode 100644 index 0000000..fa06b90 Binary files /dev/null and b/public/arma/markers/nato/c_plane.png differ diff --git a/public/arma/markers/nato/c_ship.png b/public/arma/markers/nato/c_ship.png new file mode 100644 index 0000000..75c43ae Binary files /dev/null and b/public/arma/markers/nato/c_ship.png differ diff --git a/public/arma/markers/nato/c_unknown.png b/public/arma/markers/nato/c_unknown.png new file mode 100644 index 0000000..df76809 Binary files /dev/null and b/public/arma/markers/nato/c_unknown.png differ diff --git a/public/arma/markers/nato/group_0.png b/public/arma/markers/nato/group_0.png new file mode 100644 index 0000000..9a47202 Binary files /dev/null and b/public/arma/markers/nato/group_0.png differ diff --git a/public/arma/markers/nato/group_1.png b/public/arma/markers/nato/group_1.png new file mode 100644 index 0000000..1349d45 Binary files /dev/null and b/public/arma/markers/nato/group_1.png differ diff --git a/public/arma/markers/nato/group_10.png b/public/arma/markers/nato/group_10.png new file mode 100644 index 0000000..467e5b1 Binary files /dev/null and b/public/arma/markers/nato/group_10.png differ diff --git a/public/arma/markers/nato/group_11.png b/public/arma/markers/nato/group_11.png new file mode 100644 index 0000000..db7bcb1 Binary files /dev/null and b/public/arma/markers/nato/group_11.png differ diff --git a/public/arma/markers/nato/group_2.png b/public/arma/markers/nato/group_2.png new file mode 100644 index 0000000..215d225 Binary files /dev/null and b/public/arma/markers/nato/group_2.png differ diff --git a/public/arma/markers/nato/group_3.png b/public/arma/markers/nato/group_3.png new file mode 100644 index 0000000..bbbcfd6 Binary files /dev/null and b/public/arma/markers/nato/group_3.png differ diff --git a/public/arma/markers/nato/group_4.png b/public/arma/markers/nato/group_4.png new file mode 100644 index 0000000..0bb49d0 Binary files /dev/null and b/public/arma/markers/nato/group_4.png differ diff --git a/public/arma/markers/nato/group_5.png b/public/arma/markers/nato/group_5.png new file mode 100644 index 0000000..90fbce4 Binary files /dev/null and b/public/arma/markers/nato/group_5.png differ diff --git a/public/arma/markers/nato/group_6.png b/public/arma/markers/nato/group_6.png new file mode 100644 index 0000000..3b8f482 Binary files /dev/null and b/public/arma/markers/nato/group_6.png differ diff --git a/public/arma/markers/nato/group_7.png b/public/arma/markers/nato/group_7.png new file mode 100644 index 0000000..13d056d Binary files /dev/null and b/public/arma/markers/nato/group_7.png differ diff --git a/public/arma/markers/nato/group_8.png b/public/arma/markers/nato/group_8.png new file mode 100644 index 0000000..418a3f1 Binary files /dev/null and b/public/arma/markers/nato/group_8.png differ diff --git a/public/arma/markers/nato/group_9.png b/public/arma/markers/nato/group_9.png new file mode 100644 index 0000000..72dd76d Binary files /dev/null and b/public/arma/markers/nato/group_9.png differ diff --git a/public/arma/markers/nato/n_air.png b/public/arma/markers/nato/n_air.png new file mode 100644 index 0000000..5d2cfb0 Binary files /dev/null and b/public/arma/markers/nato/n_air.png differ diff --git a/public/arma/markers/nato/n_antiair.png b/public/arma/markers/nato/n_antiair.png new file mode 100644 index 0000000..de784c7 Binary files /dev/null and b/public/arma/markers/nato/n_antiair.png differ diff --git a/public/arma/markers/nato/n_armor.png b/public/arma/markers/nato/n_armor.png new file mode 100644 index 0000000..690eede Binary files /dev/null and b/public/arma/markers/nato/n_armor.png differ diff --git a/public/arma/markers/nato/n_art.png b/public/arma/markers/nato/n_art.png new file mode 100644 index 0000000..cb2d30d Binary files /dev/null and b/public/arma/markers/nato/n_art.png differ diff --git a/public/arma/markers/nato/n_hq.png b/public/arma/markers/nato/n_hq.png new file mode 100644 index 0000000..a7af230 Binary files /dev/null and b/public/arma/markers/nato/n_hq.png differ diff --git a/public/arma/markers/nato/n_inf.png b/public/arma/markers/nato/n_inf.png new file mode 100644 index 0000000..15ff481 Binary files /dev/null and b/public/arma/markers/nato/n_inf.png differ diff --git a/public/arma/markers/nato/n_installation.png b/public/arma/markers/nato/n_installation.png new file mode 100644 index 0000000..e0b3468 Binary files /dev/null and b/public/arma/markers/nato/n_installation.png differ diff --git a/public/arma/markers/nato/n_maint.png b/public/arma/markers/nato/n_maint.png new file mode 100644 index 0000000..039f16f Binary files /dev/null and b/public/arma/markers/nato/n_maint.png differ diff --git a/public/arma/markers/nato/n_mech_inf.png b/public/arma/markers/nato/n_mech_inf.png new file mode 100644 index 0000000..8d8be0f Binary files /dev/null and b/public/arma/markers/nato/n_mech_inf.png differ diff --git a/public/arma/markers/nato/n_med.png b/public/arma/markers/nato/n_med.png new file mode 100644 index 0000000..b73ba8d Binary files /dev/null and b/public/arma/markers/nato/n_med.png differ diff --git a/public/arma/markers/nato/n_mortar.png b/public/arma/markers/nato/n_mortar.png new file mode 100644 index 0000000..49c9cb7 Binary files /dev/null and b/public/arma/markers/nato/n_mortar.png differ diff --git a/public/arma/markers/nato/n_motor_inf.png b/public/arma/markers/nato/n_motor_inf.png new file mode 100644 index 0000000..bc1b084 Binary files /dev/null and b/public/arma/markers/nato/n_motor_inf.png differ diff --git a/public/arma/markers/nato/n_naval.png b/public/arma/markers/nato/n_naval.png new file mode 100644 index 0000000..1f1d19b Binary files /dev/null and b/public/arma/markers/nato/n_naval.png differ diff --git a/public/arma/markers/nato/n_plane.png b/public/arma/markers/nato/n_plane.png new file mode 100644 index 0000000..1fd136e Binary files /dev/null and b/public/arma/markers/nato/n_plane.png differ diff --git a/public/arma/markers/nato/n_recon.png b/public/arma/markers/nato/n_recon.png new file mode 100644 index 0000000..997997a Binary files /dev/null and b/public/arma/markers/nato/n_recon.png differ diff --git a/public/arma/markers/nato/n_service.png b/public/arma/markers/nato/n_service.png new file mode 100644 index 0000000..71603d4 Binary files /dev/null and b/public/arma/markers/nato/n_service.png differ diff --git a/public/arma/markers/nato/n_support.png b/public/arma/markers/nato/n_support.png new file mode 100644 index 0000000..398bd34 Binary files /dev/null and b/public/arma/markers/nato/n_support.png differ diff --git a/public/arma/markers/nato/n_uav.png b/public/arma/markers/nato/n_uav.png new file mode 100644 index 0000000..97179e5 Binary files /dev/null and b/public/arma/markers/nato/n_uav.png differ diff --git a/public/arma/markers/nato/n_unknown.png b/public/arma/markers/nato/n_unknown.png new file mode 100644 index 0000000..51b60cc Binary files /dev/null and b/public/arma/markers/nato/n_unknown.png differ diff --git a/public/arma/markers/nato/o_air.png b/public/arma/markers/nato/o_air.png new file mode 100644 index 0000000..433e073 Binary files /dev/null and b/public/arma/markers/nato/o_air.png differ diff --git a/public/arma/markers/nato/o_antiair.png b/public/arma/markers/nato/o_antiair.png new file mode 100644 index 0000000..92a61bb Binary files /dev/null and b/public/arma/markers/nato/o_antiair.png differ diff --git a/public/arma/markers/nato/o_armor.png b/public/arma/markers/nato/o_armor.png new file mode 100644 index 0000000..6499c2c Binary files /dev/null and b/public/arma/markers/nato/o_armor.png differ diff --git a/public/arma/markers/nato/o_art.png b/public/arma/markers/nato/o_art.png new file mode 100644 index 0000000..861e952 Binary files /dev/null and b/public/arma/markers/nato/o_art.png differ diff --git a/public/arma/markers/nato/o_hq.png b/public/arma/markers/nato/o_hq.png new file mode 100644 index 0000000..a0fceae Binary files /dev/null and b/public/arma/markers/nato/o_hq.png differ diff --git a/public/arma/markers/nato/o_inf.png b/public/arma/markers/nato/o_inf.png new file mode 100644 index 0000000..69d44aa Binary files /dev/null and b/public/arma/markers/nato/o_inf.png differ diff --git a/public/arma/markers/nato/o_installation.png b/public/arma/markers/nato/o_installation.png new file mode 100644 index 0000000..c516b76 Binary files /dev/null and b/public/arma/markers/nato/o_installation.png differ diff --git a/public/arma/markers/nato/o_maint.png b/public/arma/markers/nato/o_maint.png new file mode 100644 index 0000000..544313e Binary files /dev/null and b/public/arma/markers/nato/o_maint.png differ diff --git a/public/arma/markers/nato/o_mech_inf.png b/public/arma/markers/nato/o_mech_inf.png new file mode 100644 index 0000000..84cc4c2 Binary files /dev/null and b/public/arma/markers/nato/o_mech_inf.png differ diff --git a/public/arma/markers/nato/o_med.png b/public/arma/markers/nato/o_med.png new file mode 100644 index 0000000..126f281 Binary files /dev/null and b/public/arma/markers/nato/o_med.png differ diff --git a/public/arma/markers/nato/o_mortar.png b/public/arma/markers/nato/o_mortar.png new file mode 100644 index 0000000..ab988e6 Binary files /dev/null and b/public/arma/markers/nato/o_mortar.png differ diff --git a/public/arma/markers/nato/o_motor_inf.png b/public/arma/markers/nato/o_motor_inf.png new file mode 100644 index 0000000..db0fffc Binary files /dev/null and b/public/arma/markers/nato/o_motor_inf.png differ diff --git a/public/arma/markers/nato/o_naval.png b/public/arma/markers/nato/o_naval.png new file mode 100644 index 0000000..3c5b8dd Binary files /dev/null and b/public/arma/markers/nato/o_naval.png differ diff --git a/public/arma/markers/nato/o_plane.png b/public/arma/markers/nato/o_plane.png new file mode 100644 index 0000000..4941cd1 Binary files /dev/null and b/public/arma/markers/nato/o_plane.png differ diff --git a/public/arma/markers/nato/o_recon.png b/public/arma/markers/nato/o_recon.png new file mode 100644 index 0000000..184fa79 Binary files /dev/null and b/public/arma/markers/nato/o_recon.png differ diff --git a/public/arma/markers/nato/o_service.png b/public/arma/markers/nato/o_service.png new file mode 100644 index 0000000..beaa8ee Binary files /dev/null and b/public/arma/markers/nato/o_service.png differ diff --git a/public/arma/markers/nato/o_support.png b/public/arma/markers/nato/o_support.png new file mode 100644 index 0000000..727359c Binary files /dev/null and b/public/arma/markers/nato/o_support.png differ diff --git a/public/arma/markers/nato/o_uav.png b/public/arma/markers/nato/o_uav.png new file mode 100644 index 0000000..28162ac Binary files /dev/null and b/public/arma/markers/nato/o_uav.png differ diff --git a/public/arma/markers/nato/o_unknown.png b/public/arma/markers/nato/o_unknown.png new file mode 100644 index 0000000..c109c5c Binary files /dev/null and b/public/arma/markers/nato/o_unknown.png differ diff --git a/public/arma/markers/nato/respawn_air_ca.png b/public/arma/markers/nato/respawn_air_ca.png new file mode 100644 index 0000000..73bfd3b Binary files /dev/null and b/public/arma/markers/nato/respawn_air_ca.png differ diff --git a/public/arma/markers/nato/respawn_armor_ca.png b/public/arma/markers/nato/respawn_armor_ca.png new file mode 100644 index 0000000..d8dfc4c Binary files /dev/null and b/public/arma/markers/nato/respawn_armor_ca.png differ diff --git a/public/arma/markers/nato/respawn_inf_ca.png b/public/arma/markers/nato/respawn_inf_ca.png new file mode 100644 index 0000000..9ca71bf Binary files /dev/null and b/public/arma/markers/nato/respawn_inf_ca.png differ diff --git a/public/arma/markers/nato/respawn_motor_ca.png b/public/arma/markers/nato/respawn_motor_ca.png new file mode 100644 index 0000000..86def8d Binary files /dev/null and b/public/arma/markers/nato/respawn_motor_ca.png differ diff --git a/public/arma/markers/nato/respawn_naval_ca.png b/public/arma/markers/nato/respawn_naval_ca.png new file mode 100644 index 0000000..58c2651 Binary files /dev/null and b/public/arma/markers/nato/respawn_naval_ca.png differ diff --git a/public/arma/markers/nato/respawn_para_ca.png b/public/arma/markers/nato/respawn_para_ca.png new file mode 100644 index 0000000..d366e7d Binary files /dev/null and b/public/arma/markers/nato/respawn_para_ca.png differ diff --git a/public/arma/markers/nato/respawn_plane_ca.png b/public/arma/markers/nato/respawn_plane_ca.png new file mode 100644 index 0000000..0b51231 Binary files /dev/null and b/public/arma/markers/nato/respawn_plane_ca.png differ diff --git a/public/arma/markers/nato/respawn_unknown_ca.png b/public/arma/markers/nato/respawn_unknown_ca.png new file mode 100644 index 0000000..b4399ea Binary files /dev/null and b/public/arma/markers/nato/respawn_unknown_ca.png differ diff --git a/public/arma/markers/nato/u_installation.png b/public/arma/markers/nato/u_installation.png new file mode 100644 index 0000000..b4722db Binary files /dev/null and b/public/arma/markers/nato/u_installation.png differ diff --git a/public/arma/markers/system/empty_ca.png b/public/arma/markers/system/empty_ca.png new file mode 100644 index 0000000..ef11122 Binary files /dev/null and b/public/arma/markers/system/empty_ca.png differ diff --git a/src/lib/arma-markers/catalog.generated.ts b/src/lib/arma-markers/catalog.generated.ts new file mode 100644 index 0000000..1c4a813 --- /dev/null +++ b/src/lib/arma-markers/catalog.generated.ts @@ -0,0 +1,2056 @@ +// Generated by src/tools/arma/extractMarkers.ts; do not edit by hand. +// Re-run: bun run markers:arma +import type { ArmaMarkerBrush, ArmaMarkerCategory, ArmaMarkerColor, ArmaMarkerType } from "./types"; + +export const ARMA_MARKER_TYPES: readonly ArmaMarkerType[] = [ + { + className: "Empty", + name: "Empty", + category: "System", + iconUrl: "/arma/markers/system/empty_ca.png", + color: [0.2, 0.1, 0.4, 1], + size: 0, + shadow: false, + scope: 1, + }, + { + className: "mil_objective", + name: "Objective", + category: "Military", + iconUrl: "/arma/markers/military/objective_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_marker", + name: "Marker", + category: "Military", + iconUrl: "/arma/markers/military/marker_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_flag", + name: "Flag", + category: "Military", + iconUrl: "/arma/markers/military/flag_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_arrow", + name: "Arrow", + category: "Military", + iconUrl: "/arma/markers/military/arrow_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_arrow2", + name: "Arrow (filled)", + category: "Military", + iconUrl: "/arma/markers/military/arrow2_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_ambush", + name: "Ambush", + category: "Military", + iconUrl: "/arma/markers/military/ambush_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_destroy", + name: "Destroy", + category: "Military", + iconUrl: "/arma/markers/military/destroy_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_start", + name: "Start", + category: "Military", + iconUrl: "/arma/markers/military/start_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_end", + name: "End", + category: "Military", + iconUrl: "/arma/markers/military/end_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_pickup", + name: "Pick Up", + category: "Military", + iconUrl: "/arma/markers/military/pickup_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_join", + name: "Join", + category: "Military", + iconUrl: "/arma/markers/military/join_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_warning", + name: "Warning", + category: "Military", + iconUrl: "/arma/markers/military/warning_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_unknown", + name: "Unknown", + category: "Military", + iconUrl: "/arma/markers/military/unknown_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_circle", + name: "Circle", + category: "Military", + iconUrl: "/arma/markers/military/circle_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_dot", + name: "Dot", + category: "Military", + iconUrl: "/arma/markers/military/dot_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_box", + name: "Square", + category: "Military", + iconUrl: "/arma/markers/military/box_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "mil_triangle", + name: "Triangle", + category: "Military", + iconUrl: "/arma/markers/military/triangle_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "hd_dot", + name: "Dot", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/dot_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_objective", + name: "Objective", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/objective_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_flag", + name: "Flag", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/flag_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_arrow", + name: "Arrow", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/arrow_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_ambush", + name: "Ambush", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/ambush_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_destroy", + name: "Destroy", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/destroy_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_start", + name: "Start", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/start_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_end", + name: "End", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/end_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_pickup", + name: "Pick Up", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/pickup_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_join", + name: "Join", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/join_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_warning", + name: "Warning", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/warning_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "hd_unknown", + name: "Unknown", + category: "Draw", + iconUrl: "/arma/markers/handdrawn/unknown_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 2, + }, + { + className: "b_unknown", + name: "Unknown", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_unknown.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_unknown", + name: "Unknown", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_unknown.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_unknown", + name: "Unknown", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_unknown.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_inf", + name: "Infantry", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_inf.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_inf", + name: "Infantry", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_inf.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_inf", + name: "Infantry", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_inf.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_motor_inf", + name: "Motorized", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_motor_inf.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_motor_inf", + name: "Motorized", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_motor_inf.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_motor_inf", + name: "Motorized", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_motor_inf.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_mech_inf", + name: "Mechanized", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_mech_inf.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_mech_inf", + name: "Mechanized", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_mech_inf.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_mech_inf", + name: "Mechanized", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_mech_inf.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_armor", + name: "Armor", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_armor.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_armor", + name: "Armor", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_armor.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_armor", + name: "Armor", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_armor.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_recon", + name: "Recon", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_recon.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_recon", + name: "Recon", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_recon.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_recon", + name: "Recon", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_recon.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_air", + name: "Helicopter", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_air.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_air", + name: "Helicopter", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_air.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_air", + name: "Helicopter", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_air.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_plane", + name: "Plane", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_plane.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_plane", + name: "Plane", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_plane.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_plane", + name: "Plane", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_plane.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_uav", + name: "UAV", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_uav.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_uav", + name: "UAV", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_uav.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_uav", + name: "UAV", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_uav.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_naval", + name: "Naval", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_naval.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_naval", + name: "Naval", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_naval.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_naval", + name: "Naval", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_naval.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_med", + name: "Medical", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_med.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_med", + name: "Medical", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_med.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_med", + name: "Medical", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_med.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_art", + name: "Artillery", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_art.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_art", + name: "Artillery", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_art.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_art", + name: "Artillery", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_art.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_mortar", + name: "Mortar", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_mortar.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_mortar", + name: "Mortar", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_mortar.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_mortar", + name: "Mortar", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_mortar.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_hq", + name: "HQ", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_hq.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_hq", + name: "HQ", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_hq.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_hq", + name: "HQ", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_hq.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_support", + name: "Support", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_support.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_support", + name: "Support", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_support.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_support", + name: "Support", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_support.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_maint", + name: "Maintenance", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_maint.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_maint", + name: "Maintenance", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_maint.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_maint", + name: "Maintenance", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_maint.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_service", + name: "Service", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_service.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_service", + name: "Service", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_service.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_service", + name: "Service", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_service.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_installation", + name: "Installation", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_installation.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_installation", + name: "Installation", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_installation.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_installation", + name: "Installation", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_installation.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "u_installation", + name: "Installation", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/u_installation.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "b_antiair", + name: "Anti-Air", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/nato/b_antiair.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_antiair", + name: "Anti-Air", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/nato/o_antiair.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_antiair", + name: "Anti-Air", + category: "NATO_Independent", + iconUrl: "/arma/markers/nato/n_antiair.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "c_unknown", + name: "Unknown", + category: "NATO_Civilian", + iconUrl: "/arma/markers/nato/c_unknown.png", + color: [0.4, 0, 0.5, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "c_car", + name: "Car", + category: "NATO_Civilian", + iconUrl: "/arma/markers/nato/c_car.png", + color: [0.4, 0, 0.5, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "c_ship", + name: "Ship", + category: "NATO_Civilian", + iconUrl: "/arma/markers/nato/c_ship.png", + color: [0.4, 0, 0.5, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "c_air", + name: "Helicopter", + category: "NATO_Civilian", + iconUrl: "/arma/markers/nato/c_air.png", + color: [0.4, 0, 0.5, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "c_plane", + name: "Plane", + category: "NATO_Civilian", + iconUrl: "/arma/markers/nato/c_plane.png", + color: [0.4, 0, 0.5, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_0", + name: "Fire Team", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_0.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_1", + name: "Squad", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_1.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_2", + name: "Section", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_2.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_3", + name: "Platoon", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_3.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_4", + name: "Company", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_4.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_5", + name: "Battalion", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_5.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_6", + name: "Regiment", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_6.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_7", + name: "Brigade", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_7.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_8", + name: "Division", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_8.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_9", + name: "Corps", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_9.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_10", + name: "Army", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_10.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "group_11", + name: "Army Group", + category: "NATO_Sizes", + iconUrl: "/arma/markers/nato/group_11.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "respawn_unknown", + name: "Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_unknown_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "respawn_inf", + name: "Infantry Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_inf_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "respawn_motor", + name: "Car Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_motor_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "respawn_armor", + name: "Armor Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_armor_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "respawn_air", + name: "Helicopter Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_air_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "respawn_plane", + name: "Plane Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_plane_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "respawn_naval", + name: "Naval Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_naval_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "respawn_para", + name: "Airborne Respawn", + category: "NATO_Respawn", + iconUrl: "/arma/markers/nato/respawn_para_ca.png", + color: [0, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "loc_Tree", + name: "Tree", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/tree_ca.png", + color: [0.45, 0.64, 0.33, 0.4], + size: 12, + shadow: false, + scope: 1, + }, + { + className: "loc_SmallTree", + name: "Small Tree", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/smalltree_ca.png", + color: [0.45, 0.64, 0.33, 0.4], + size: 12, + shadow: false, + scope: 1, + }, + { + className: "loc_Bush", + name: "Bush", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/bush_ca.png", + color: [0.45, 0.64, 0.33, 0.4], + size: 7, + shadow: false, + scope: 1, + }, + { + className: "loc_Church", + name: "Church", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/church_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Chapel", + name: "Chapel", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/chapel_ca.png", + color: [0, 0, 0, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Cross", + name: "Cross", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/cross_ca.png", + color: [0, 0, 0, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Rock", + name: "Rock", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/rock_ca.png", + color: [0.1, 0.1, 0.1, 0.8], + size: 12, + shadow: false, + scope: 1, + }, + { + className: "loc_Bunker", + name: "Bunker", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/bunker_ca.png", + color: [0, 0, 0, 1], + size: 14, + shadow: false, + scope: 1, + }, + { + className: "loc_Fountain", + name: "Fountain", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/fountain_ca.png", + color: [0, 0, 0, 1], + size: 11, + shadow: false, + scope: 1, + }, + { + className: "loc_ViewTower", + name: "View Tower", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/viewtower_ca.png", + color: [0, 0, 0, 1], + size: 16, + shadow: false, + scope: 1, + }, + { + className: "loc_Lighthouse", + name: "Lighthouse", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/lighthouse_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Quay", + name: "Pier", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/quay_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Fuelstation", + name: "Fuel Station", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/fuelstation_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Hospital", + name: "Hospital", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/hospital_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_BusStop", + name: "Bus Stop", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/busstop_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Transmitter", + name: "Radio Tower", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/transmitter_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Stack", + name: "Chimney Stack", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/stack_ca.png", + color: [0, 0, 0, 1], + size: 20, + shadow: false, + scope: 1, + }, + { + className: "loc_Ruin", + name: "Ruin", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/ruin_ca.png", + color: [0, 0, 0, 1], + size: 16, + shadow: false, + scope: 1, + }, + { + className: "loc_Tourism", + name: "Hotel", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/tourism_ca.png", + color: [0, 0, 0, 1], + size: 16, + shadow: false, + scope: 1, + }, + { + className: "loc_WaterTower", + name: "Water Tower", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/watertower_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_Power", + name: "Power Plant", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/power_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_PowerSolar", + name: "Solar Power Plant", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/powersolar_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_PowerWave", + name: "Wave Power Plant", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/powerwave_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_PowerWind", + name: "Wind Power Plant", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f/data/map/mapcontrol/powerwind_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "flag_NATO", + name: "NATO", + category: "Flags", + iconUrl: "/arma/markers/flags/nato_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_CSAT", + name: "CSAT", + category: "Flags", + iconUrl: "/arma/markers/flags/csat_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_AAF", + name: "Altis Armed Forces", + category: "Flags", + iconUrl: "/arma/markers/flags/aaf_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Altis", + name: "Altis", + category: "Flags", + iconUrl: "/arma/markers/flags/altis_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_AltisColonial", + name: "Altis (Colonial)", + category: "Flags", + iconUrl: "/arma/markers/flags/altiscolonial_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_FIA", + name: "FIA", + category: "Flags", + iconUrl: "/arma/markers/flags/fia_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_EU", + name: "European Union", + category: "Flags", + iconUrl: "/arma/markers/flags/eu_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_UN", + name: "United Nations", + category: "Flags", + iconUrl: "/arma/markers/flags/un_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Belgium", + name: "Belgium", + category: "Flags", + iconUrl: "/arma/markers/flags/belgium_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Canada", + name: "Canada", + category: "Flags", + iconUrl: "/arma/markers/flags/canada_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Croatia", + name: "Croatia", + category: "Flags", + iconUrl: "/arma/markers/flags/croatia_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_CzechRepublic", + name: "Czech Republic", + category: "Flags", + iconUrl: "/arma/markers/flags/czechrepublic_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Denmark", + name: "Denmark", + category: "Flags", + iconUrl: "/arma/markers/flags/denmark_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_France", + name: "France", + category: "Flags", + iconUrl: "/arma/markers/flags/france_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Georgia", + name: "Georgia", + category: "Flags", + iconUrl: "/arma/markers/flags/georgia_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Germany", + name: "Germany", + category: "Flags", + iconUrl: "/arma/markers/flags/germany_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Greece", + name: "Greece", + category: "Flags", + iconUrl: "/arma/markers/flags/greece_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Hungary", + name: "Hungary", + category: "Flags", + iconUrl: "/arma/markers/flags/hungary_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Iceland", + name: "Iceland", + category: "Flags", + iconUrl: "/arma/markers/flags/iceland_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Italy", + name: "Italy", + category: "Flags", + iconUrl: "/arma/markers/flags/italy_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Luxembourg", + name: "Luxembourg", + category: "Flags", + iconUrl: "/arma/markers/flags/luxembourg_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Netherlands", + name: "Netherlands", + category: "Flags", + iconUrl: "/arma/markers/flags/netherlands_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Norway", + name: "Norway", + category: "Flags", + iconUrl: "/arma/markers/flags/norway_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Poland", + name: "Poland", + category: "Flags", + iconUrl: "/arma/markers/flags/poland_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Portugal", + name: "Portugal", + category: "Flags", + iconUrl: "/arma/markers/flags/portugal_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Slovakia", + name: "Slovakia", + category: "Flags", + iconUrl: "/arma/markers/flags/slovakia_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Slovenia", + name: "Slovenia", + category: "Flags", + iconUrl: "/arma/markers/flags/slovenia_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Spain", + name: "Spain", + category: "Flags", + iconUrl: "/arma/markers/flags/spain_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_UK", + name: "UK", + category: "Flags", + iconUrl: "/arma/markers/flags/uk_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_USA", + name: "USA", + category: "Flags", + iconUrl: "/arma/markers/flags/usa_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "Select", + name: "Select", + category: "System", + iconUrl: "/arma/markers/a3/ui_f/data/igui/cfg/islandmap/iconplayer_ca.png", + color: [1, 0, 0, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "waypoint", + name: "Waypoint", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f/data/map/groupicons/waypoint.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "selector_selectable", + name: "Selector - Selectable", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f/data/map/groupicons/selector_selectable_ca.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "selector_selectedEnemy", + name: "Selector - Selected Enemy", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedenemy_ca.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "selector_selectedFriendly", + name: "Selector - Selected Friendly", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedfriendly_ca.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "selector_selectedMission", + name: "Selector - Selected Mission", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f/data/map/groupicons/selector_selectedmission_ca.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "KIA", + name: "KIA", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f_curator/data/cfgmarkers/kia_ca.png", + color: [0, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "Minefield", + name: "Minefield", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefield_ca.png", + color: [0.9, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "MinefieldAP", + name: "Minefield (AP)", + category: "Default", + iconUrl: "/arma/markers/a3/ui_f_curator/data/cfgmarkers/minefieldap_ca.png", + color: [0.9, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "flag_EAF", + name: "Livonian Defense Force", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_enoch/data/cfgmarkers/ldf_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Enoch", + name: "Livonia", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonia_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_EnochLooters", + name: "Livonian Looters", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_enoch/data/cfgmarkers/livonialooters_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Astra", + name: "Astra", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_enoch/data/cfgmarkers/astra_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Russia", + name: "Russia", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_enoch/data/cfgmarkers/russia_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Spetsnaz", + name: "Spetsnaz", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_enoch/data/cfgmarkers/spetsnaz_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_CTRG", + name: "CTRG", + category: "Flags", + iconUrl: "/arma/markers/flags/ctrg_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Viper", + name: "Viper", + category: "Flags", + iconUrl: "/arma/markers/flags/viper_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Syndicat", + name: "Syndikat", + category: "Flags", + iconUrl: "/arma/markers/flags/syndicat_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_Tanoa", + name: "Tanoa", + category: "Flags", + iconUrl: "/arma/markers/flags/tanoa_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_TanoaGendarmerie", + name: "Gendarmerie", + category: "Flags", + iconUrl: "/arma/markers/flags/tanoagendarmerie_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: false, + scope: 1, + }, + { + className: "flag_IDAP", + name: "International Development & Aid Project", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/idap_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: true, + scope: 1, + }, + { + className: "RedCrystal", + name: "Red Crystal", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/redcrystal_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: true, + scope: 2, + }, + { + className: "White", + name: "White", + category: "Flags", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/white_ca.png", + color: [1, 1, 1, 1], + size: 32, + shadow: true, + scope: 2, + }, + { + className: "loc_CivilDefense", + name: "Civil Defense", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/civildefense.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_CulturalProperty", + name: "Cultural Property", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/culturalproperty_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_DangerousForces", + name: "Dangerous Forces", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/dangerousforces_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "loc_SafetyZone", + name: "Safety Zone", + category: "Locations", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/safetyzone_ca.png", + color: [1, 1, 1, 1], + size: 24, + shadow: false, + scope: 1, + }, + { + className: "b_Ordnance", + name: "Ordnance", + category: "NATO_BLUFOR", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/b_ordnance_ca.png", + color: [0, 0.3, 0.6, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "o_Ordnance", + name: "Ordnance", + category: "NATO_OPFOR", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/o_ordnance_ca.png", + color: [0.5, 0, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, + { + className: "n_Ordnance", + name: "Ordnance", + category: "NATO_Independent", + iconUrl: "/arma/markers/a3/ui_f_orange/data/cfgmarkers/n_ordnance_ca.png", + color: [0, 0.5, 0, 1], + size: 29, + shadow: false, + scope: 1, + }, +]; + +export const ARMA_MARKER_COLORS: readonly ArmaMarkerColor[] = [ + { className: "Default", name: "Default", rgba: [0, 0, 0, 1], scope: 1 }, + { className: "ColorBlack", name: "Black", rgba: [0, 0, 0, 1], scope: 2 }, + { className: "ColorGrey", name: "Grey", rgba: [0.5, 0.5, 0.5, 1], scope: 1 }, + { className: "ColorRed", name: "Red", rgba: [0.9, 0, 0, 1], scope: 2 }, + { className: "ColorBrown", name: "Brown", rgba: [0.5, 0.25, 0, 1], scope: 1 }, + { className: "ColorOrange", name: "Orange", rgba: [0.85, 0.4, 0, 1], scope: 1 }, + { className: "ColorYellow", name: "Yellow", rgba: [0.85, 0.85, 0, 1], scope: 2 }, + { className: "ColorKhaki", name: "Khaki", rgba: [0.5, 0.6, 0.4, 1], scope: 1 }, + { className: "ColorGreen", name: "Green", rgba: [0, 0.8, 0, 1], scope: 2 }, + { className: "ColorBlue", name: "Blue", rgba: [0, 0, 1, 1], scope: 2 }, + { className: "ColorPink", name: "Pink", rgba: [1, 0.3, 0.4, 1], scope: 1 }, + { className: "ColorWhite", name: "White", rgba: [1, 1, 1, 1], scope: 2 }, + { className: "ColorWEST", name: "BLUFOR", rgba: [0, 0.3, 0.6, 1], scope: 2 }, + { className: "ColorEAST", name: "OPFOR", rgba: [0.5, 0, 0, 1], scope: 2 }, + { className: "ColorGUER", name: "Independent", rgba: [0, 0.5, 0, 1], scope: 2 }, + { className: "ColorCIV", name: "Civilian", rgba: [0.4, 0, 0.5, 1], scope: 2 }, + { className: "ColorUNKNOWN", name: "Unknown side", rgba: [0.7, 0.6, 0, 1], scope: 2 }, +]; + +export const ARMA_MARKER_BRUSHES: readonly ArmaMarkerBrush[] = [ + { className: "Solid", name: "Solid", drawBorder: false, pattern: "solid", scope: 1 }, + { + className: "SolidFull", + name: "Solid non-transparent", + drawBorder: false, + pattern: "solid", + scope: 1, + }, + { className: "SolidBorder", name: "Solid border", drawBorder: true, pattern: "solid", scope: 1 }, + { className: "Border", name: "Border", drawBorder: true, pattern: "none", scope: 1 }, + { + className: "Horizontal", + name: "Horizontal", + drawBorder: false, + pattern: "horizontal", + scope: 1, + }, + { className: "Vertical", name: "Vertical", drawBorder: false, pattern: "vertical", scope: 1 }, + { className: "Grid", name: "Grid", drawBorder: false, pattern: "grid", scope: 1 }, + { + className: "FDiagonal", + name: "Forward diagonal", + drawBorder: false, + pattern: "fdiagonal", + scope: 1, + }, + { + className: "BDiagonal", + name: "Backward diagonal", + drawBorder: false, + pattern: "bdiagonal", + scope: 1, + }, + { + className: "DiagGrid", + name: "Grid diagonal", + drawBorder: false, + pattern: "diaggrid", + scope: 1, + }, + { className: "Cross", name: "Cross", drawBorder: false, pattern: "cross", scope: 1 }, +]; + +export const ARMA_MARKER_CATEGORIES: readonly ArmaMarkerCategory[] = [ + { className: "Default", name: "Default" }, + { className: "System", name: "System" }, + { className: "Military", name: "Standard Military" }, + { className: "Draw", name: "Standard Drawn" }, + { className: "NATO_BLUFOR", name: "NATO - BLUFOR" }, + { className: "NATO_OPFOR", name: "NATO - OPFOR" }, + { className: "NATO_Independent", name: "NATO - Independent" }, + { className: "NATO_Civilian", name: "NATO - Civilian" }, + { className: "NATO_Sizes", name: "NATO - Unit Sizes" }, + { className: "NATO_Respawn", name: "Respawn" }, + { className: "Locations", name: "Map Locations" }, + { className: "Flags", name: "Flags" }, + { className: "Contact", name: "Arma 3 Contact" }, +]; diff --git a/src/lib/arma-markers/index.ts b/src/lib/arma-markers/index.ts new file mode 100644 index 0000000..437a581 --- /dev/null +++ b/src/lib/arma-markers/index.ts @@ -0,0 +1,103 @@ +/** + * Arma 3 marker catalog lookups (pure, client-safe). + * + * The generated catalog mirrors the game config, so every class name accepted + * here is a valid `setMarkerType` / `setMarkerColor` / `setMarkerBrush` value. + */ +import { + ARMA_MARKER_BRUSHES, + ARMA_MARKER_CATEGORIES, + ARMA_MARKER_COLORS, + ARMA_MARKER_TYPES, +} from "./catalog.generated"; +import type { + ArmaMarkerBrush, + ArmaMarkerCategory, + ArmaMarkerColor, + ArmaMarkerType, + ArmaRgba, +} from "./types"; + +export * from "./types"; +export { ARMA_MARKER_BRUSHES, ARMA_MARKER_CATEGORIES, ARMA_MARKER_COLORS, ARMA_MARKER_TYPES }; + +/** Arma's own marker defaults (what `createMarker` produces before any setters). */ +export const DEFAULT_MARKER_TYPE = "mil_dot"; +export const DEFAULT_MARKER_COLOR = "Default"; +export const DEFAULT_MARKER_BRUSH = "Solid"; + +const typeIndex = new Map( + ARMA_MARKER_TYPES.map((entry) => [entry.className.toLowerCase(), entry]), +); +const colorIndex = new Map( + ARMA_MARKER_COLORS.map((entry) => [entry.className.toLowerCase(), entry]), +); +const brushIndex = new Map( + ARMA_MARKER_BRUSHES.map((entry) => [entry.className.toLowerCase(), entry]), +); + +/** Arma class names are case-insensitive; lookups are too. */ +export function markerTypeOf(className: string | null | undefined): ArmaMarkerType | null { + return className ? (typeIndex.get(className.toLowerCase()) ?? null) : null; +} + +export function markerColorOf(className: string | null | undefined): ArmaMarkerColor | null { + return className ? (colorIndex.get(className.toLowerCase()) ?? null) : null; +} + +export function markerBrushOf(className: string | null | undefined): ArmaMarkerBrush | null { + return className ? (brushIndex.get(className.toLowerCase()) ?? null) : null; +} + +/** + * The color a marker actually renders with. "Default" (or an unknown class) + * means "the marker type's own config color", which for NATO symbols is the + * side color; areas and lines have no type, so they fall back to black like + * in-game. + */ +export function resolveMarkerRgba( + colorClass: string | null | undefined, + typeClass?: string | null, +): ArmaRgba { + const color = markerColorOf(colorClass); + if (color && color.className.toLowerCase() !== "default") return color.rgba; + const type = markerTypeOf(typeClass); + if (type) return type.color; + return color?.rgba ?? [0, 0, 0, 1]; +} + +function channelHex(value: number): string { + const clamped = Math.max(0, Math.min(1, value)); + return Math.round(clamped * 255) + .toString(16) + .padStart(2, "0"); +} + +/** "#rrggbb" for an Arma RGBA (alpha is applied separately as opacity). */ +export function rgbaToHex(rgba: ArmaRgba): string { + return `#${channelHex(rgba[0])}${channelHex(rgba[1])}${channelHex(rgba[2])}`; +} + +/** Marker types grouped by CfgMarkerClasses category, in config order. */ +export function markerTypesByCategory(): { + category: ArmaMarkerCategory; + types: ArmaMarkerType[]; +}[] { + const groups = new Map(); + for (const type of ARMA_MARKER_TYPES) { + const key = type.category; + const list = groups.get(key) ?? []; + list.push(type); + groups.set(key, list); + } + const known = new Map(ARMA_MARKER_CATEGORIES.map((c) => [c.className, c])); + const ordered: { category: ArmaMarkerCategory; types: ArmaMarkerType[] }[] = []; + for (const category of ARMA_MARKER_CATEGORIES) { + const types = groups.get(category.className); + if (types?.length) ordered.push({ category, types }); + } + for (const [key, types] of groups) { + if (!known.has(key)) ordered.push({ category: { className: key, name: key }, types }); + } + return ordered; +} diff --git a/src/lib/arma-markers/missionMarkers.ts b/src/lib/arma-markers/missionMarkers.ts new file mode 100644 index 0000000..e2a1742 --- /dev/null +++ b/src/lib/arma-markers/missionMarkers.ts @@ -0,0 +1,483 @@ +/** + * Mission planning-map markers: shape model, input validation, geometry and + * SQF export. Pure module (client-safe, no Payload imports). + * + * The model deliberately mirrors Arma's marker API (shape / type / color / + * brush / text / size / dir / alpha / polyline) so a plan can be exported as a + * `createMarker` script and pasted into the game. + * + * Coordinates are map meters (origin top-left, y runs south, like the rest of + * the map system). Arma positions are origin bottom-left with y north, so the + * export flips y against the world height. + */ +import { isPointArray, type MapPoint } from "@/lib/map/points"; +import { + DEFAULT_MARKER_BRUSH, + DEFAULT_MARKER_COLOR, + markerBrushOf, + markerColorOf, + markerTypeOf, + type ArmaBrushPattern, +} from "./index"; + +export const MISSION_MARKER_SHAPES = ["icon", "rectangle", "ellipse", "polyline"] as const; +export type MissionMarkerShape = (typeof MISSION_MARKER_SHAPES)[number]; + +export const MAX_MARKER_TEXT = 200; +export const MAX_POLYLINE_POINTS = 500; +export const MAX_MARKERS_PER_MISSION = 1000; +/** Area half-extent bounds in meters (Arma markerSize for areas). */ +export const MIN_AREA_SIZE = 1; +export const MAX_AREA_SIZE = 50_000; +/** Icon size is a scale multiplier (Arma markerSize for icons, default [1, 1]). */ +export const MIN_ICON_SCALE = 0.25; +export const MAX_ICON_SCALE = 8; +/** Matches the map's maxBounds padding: markers may sit slightly off-world. */ +const WORLD_MARGIN = 2000; + +export interface MissionMarkerData { + shape: MissionMarkerShape; + /** CfgMarkers class (icons only). */ + markerType: string | null; + /** CfgMarkerColors class. */ + color: string; + /** CfgMarkerBrushes class (areas only). */ + brush: string | null; + text: string; + /** Icon/area center; first vertex for polylines. */ + position: MapPoint; + /** Area half-extents in meters, or icon scale. */ + size: [number, number]; + /** Degrees clockwise from north (Arma markerDir). */ + direction: number; + alpha: number; + /** Polyline vertices (empty for other shapes). */ + points: MapPoint[]; +} + +export interface MissionMarker extends MissionMarkerData { + id: number; +} + +export interface WorldSize { + width: number; + height: number; +} + +function finite(value: unknown): value is number { + return typeof value === "number" && Number.isFinite(value); +} + +function clamp(value: number, min: number, max: number): number { + return Math.min(max, Math.max(min, value)); +} + +function round(value: number, places = 2): number { + const factor = 10 ** places; + return Math.round(value * factor) / factor; +} + +function inWorld(point: MapPoint, world: WorldSize): boolean { + return ( + point[0] >= -WORLD_MARGIN && + point[0] <= world.width + WORLD_MARGIN && + point[1] >= -WORLD_MARGIN && + point[1] <= world.height + WORLD_MARGIN + ); +} + +function asPoint(value: unknown): MapPoint | null { + if (!Array.isArray(value) || value.length !== 2) return null; + const [x, y] = value; + return finite(x) && finite(y) ? [round(x), round(y)] : null; +} + +export function normalizeDirection(value: unknown): number { + if (!finite(value)) return 0; + const wrapped = value % 360; + return round(wrapped < 0 ? wrapped + 360 : wrapped); +} + +export function sanitizeMarkerText(value: unknown): string { + if (typeof value !== "string") return ""; + return value + .replace(/[\r\n\t]+/g, " ") + .trim() + .slice(0, MAX_MARKER_TEXT); +} + +/** + * Validate and normalize untrusted marker input (server-side gate; the editor + * runs it too for instant feedback). Returns the clean data or an error string. + */ +export function normalizeMarkerInput( + input: unknown, + world: WorldSize, +): { ok: true; data: MissionMarkerData } | { ok: false; error: string } { + if (!input || typeof input !== "object") return { ok: false, error: "Invalid marker." }; + const raw = input as Record; + + const shape = raw.shape; + if (typeof shape !== "string" || !MISSION_MARKER_SHAPES.includes(shape as MissionMarkerShape)) { + return { ok: false, error: "Unknown marker shape." }; + } + const kind = shape as MissionMarkerShape; + + const colorClass = typeof raw.color === "string" && raw.color ? raw.color : DEFAULT_MARKER_COLOR; + const color = markerColorOf(colorClass); + if (!color) return { ok: false, error: `Unknown marker color "${colorClass}".` }; + + const alpha = finite(raw.alpha) ? round(clamp(raw.alpha, 0, 1)) : 1; + const direction = normalizeDirection(raw.direction); + const text = sanitizeMarkerText(raw.text); + + if (kind === "polyline") { + if (!isPointArray(raw.points, 2)) { + return { ok: false, error: "A line needs at least two points." }; + } + if (raw.points.length > MAX_POLYLINE_POINTS) { + return { ok: false, error: `A line can have at most ${MAX_POLYLINE_POINTS} points.` }; + } + const points = raw.points.map(([x, y]) => [round(x), round(y)] as MapPoint); + if (!points.every((point) => inWorld(point, world))) { + return { ok: false, error: "Line points must be on the map." }; + } + return { + ok: true, + data: { + shape: kind, + markerType: null, + color: color.className, + brush: null, + text, + position: points[0], + size: [1, 1], + direction: 0, + alpha, + points, + }, + }; + } + + const position = asPoint(raw.position); + if (!position) return { ok: false, error: "Marker position is required." }; + if (!inWorld(position, world)) return { ok: false, error: "Marker must be on the map." }; + + const size = Array.isArray(raw.size) ? raw.size : []; + const sizeA = finite(size[0]) ? size[0] : kind === "icon" ? 1 : 100; + const sizeB = finite(size[1]) ? size[1] : sizeA; + + if (kind === "icon") { + const typeClass = typeof raw.markerType === "string" ? raw.markerType : ""; + const type = markerTypeOf(typeClass); + if (!type) return { ok: false, error: `Unknown marker type "${typeClass}".` }; + return { + ok: true, + data: { + shape: kind, + markerType: type.className, + color: color.className, + brush: null, + text, + position, + size: [ + round(clamp(sizeA, MIN_ICON_SCALE, MAX_ICON_SCALE)), + round(clamp(sizeB, MIN_ICON_SCALE, MAX_ICON_SCALE)), + ], + direction, + alpha, + points: [], + }, + }; + } + + if (sizeA < MIN_AREA_SIZE || sizeB < MIN_AREA_SIZE) { + return { ok: false, error: `Area size must be at least ${MIN_AREA_SIZE} m.` }; + } + if (sizeA > MAX_AREA_SIZE || sizeB > MAX_AREA_SIZE) { + return { ok: false, error: `Area size must be at most ${MAX_AREA_SIZE} m.` }; + } + const brushClass = typeof raw.brush === "string" && raw.brush ? raw.brush : DEFAULT_MARKER_BRUSH; + const brush = markerBrushOf(brushClass); + if (!brush) return { ok: false, error: `Unknown marker brush "${brushClass}".` }; + + return { + ok: true, + data: { + shape: kind, + markerType: null, + color: color.className, + brush: brush.className, + text, + position, + size: [round(sizeA), round(sizeB)], + direction, + alpha, + points: [], + }, + }; +} + +/** + * Coerce a stored document into the marker model, tolerating legacy/partial + * rows (bad geometry yields null so one broken row never blanks the map). + */ +export function markerFromDoc(doc: { + id: number; + shape?: string | null; + markerType?: string | null; + color?: string | null; + brush?: string | null; + text?: string | null; + position?: unknown; + sizeA?: number | null; + sizeB?: number | null; + direction?: number | null; + alpha?: number | null; + points?: unknown; +}): MissionMarker | null { + const shape = MISSION_MARKER_SHAPES.includes(doc.shape as MissionMarkerShape) + ? (doc.shape as MissionMarkerShape) + : null; + if (!shape) return null; + const points = isPointArray(doc.points, 2) ? doc.points : []; + const position = asPoint(doc.position) ?? points[0] ?? null; + if (!position) return null; + if (shape === "polyline" && points.length < 2) return null; + return { + id: doc.id, + shape, + markerType: doc.markerType ?? null, + color: doc.color || DEFAULT_MARKER_COLOR, + brush: doc.brush ?? null, + text: doc.text ?? "", + position, + size: [doc.sizeA ?? 1, doc.sizeB ?? doc.sizeA ?? 1], + direction: doc.direction ?? 0, + alpha: doc.alpha ?? 1, + points, + }; +} + +/** Flat column shape for persisting a validated marker. */ +export function markerToDocFields(data: MissionMarkerData) { + return { + shape: data.shape, + markerType: data.markerType, + color: data.color, + brush: data.brush, + text: data.text, + position: data.position, + sizeA: data.size[0], + sizeB: data.size[1], + direction: data.direction, + alpha: data.alpha, + points: data.shape === "polyline" ? data.points : null, + }; +} + +// --------------------------------------------------------------------------- +// Geometry +// --------------------------------------------------------------------------- + +/** + * Rotate a local offset (east, north meters) by an Arma direction (degrees + * clockwise from north) and place it around a map-meter center (y south). + */ +function placeOffset( + center: MapPoint, + east: number, + north: number, + directionDeg: number, +): MapPoint { + const rad = (directionDeg * Math.PI) / 180; + const cos = Math.cos(rad); + const sin = Math.sin(rad); + const rotatedEast = east * cos + north * sin; + const rotatedNorth = -east * sin + north * cos; + return [center[0] + rotatedEast, center[1] - rotatedNorth]; +} + +/** + * Outline ring of a rectangle/ellipse area marker in map meters. size is the + * half-extent pair [a (east-west), b (north-south)] before rotation, matching + * Arma's markerSize semantics. + */ +export function areaOutline( + shape: "rectangle" | "ellipse", + center: MapPoint, + size: [number, number], + directionDeg: number, + segments = 64, +): MapPoint[] { + const [a, b] = size; + if (shape === "rectangle") { + return [ + placeOffset(center, -a, b, directionDeg), + placeOffset(center, a, b, directionDeg), + placeOffset(center, a, -b, directionDeg), + placeOffset(center, -a, -b, directionDeg), + ]; + } + const ring: MapPoint[] = []; + for (let i = 0; i < segments; i++) { + const t = (i / segments) * Math.PI * 2; + ring.push(placeOffset(center, a * Math.cos(t), b * Math.sin(t), directionDeg)); + } + return ring; +} + +/** + * Hatch line directions per brush pattern, in degrees on the map plane + * (0 = east, 90 = south since y runs south) before marker rotation. + */ +export function hatchAngles(pattern: ArmaBrushPattern): number[] { + switch (pattern) { + case "horizontal": + return [0]; + case "vertical": + return [90]; + case "fdiagonal": + return [-45]; + case "bdiagonal": + return [45]; + case "grid": + return [0, 90]; + case "diaggrid": + return [45, -45]; + case "cross": + return [0, 90, 45, -45]; + default: + return []; + } +} + +/** + * Parallel hatch segments clipped to a CONVEX ring (rectangles and ellipses + * are convex). `angleDeg` is the line direction on the map plane, `spacing` + * the gap between lines in meters. Capped so a tiny spacing on a huge area + * cannot explode the render. + */ +export function hatchSegments( + ring: MapPoint[], + angleDeg: number, + spacing: number, + maxLines = 400, +): [MapPoint, MapPoint][] { + if (ring.length < 3 || !(spacing > 0)) return []; + const rad = (angleDeg * Math.PI) / 180; + const dir: MapPoint = [Math.cos(rad), Math.sin(rad)]; + const normal: MapPoint = [-dir[1], dir[0]]; + + let minN = Infinity; + let maxN = -Infinity; + for (const [x, y] of ring) { + const n = x * normal[0] + y * normal[1]; + minN = Math.min(minN, n); + maxN = Math.max(maxN, n); + } + const span = maxN - minN; + const step = Math.max(spacing, span / maxLines); + const segments: [MapPoint, MapPoint][] = []; + + // Offset the first line by half a step so lines never hug the edge. + for (let offset = minN + step / 2; offset < maxN; offset += step) { + let tMin = Infinity; + let tMax = -Infinity; + for (let i = 0; i < ring.length; i++) { + const p = ring[i]; + const q = ring[(i + 1) % ring.length]; + const np = p[0] * normal[0] + p[1] * normal[1] - offset; + const nq = q[0] * normal[0] + q[1] * normal[1] - offset; + if ((np > 0 && nq > 0) || (np < 0 && nq < 0) || np === nq) continue; + const k = np / (np - nq); + const hit: MapPoint = [p[0] + (q[0] - p[0]) * k, p[1] + (q[1] - p[1]) * k]; + const t = hit[0] * dir[0] + hit[1] * dir[1]; + tMin = Math.min(tMin, t); + tMax = Math.max(tMax, t); + } + if (!Number.isFinite(tMin) || tMax - tMin <= 0) continue; + const base: MapPoint = [normal[0] * offset, normal[1] * offset]; + segments.push([ + [base[0] + dir[0] * tMin, base[1] + dir[1] * tMin], + [base[0] + dir[0] * tMax, base[1] + dir[1] * tMax], + ]); + } + return segments; +} + +// --------------------------------------------------------------------------- +// SQF export +// --------------------------------------------------------------------------- + +function sqfString(value: string): string { + return `"${value.replace(/"/g, '""')}"`; +} + +function sqfNumber(value: number): string { + return String(round(value)); +} + +/** Map meters (y south) to an Arma world position (y north). */ +export function toArmaPosition(point: MapPoint, worldHeight: number): [number, number] { + return [round(point[0]), round(worldHeight - point[1])]; +} + +/** + * Arma map grid reference (6 digits = 100 m squares, 8 digits = 10 m), the + * same numbers the in-game map shows: easting then northing, from the + * bottom-left origin. Off-world points clamp to the edge. + */ +export function armaGridRef(point: MapPoint, worldHeight: number, digits: 6 | 8 = 6): string { + const [x, y] = toArmaPosition(point, worldHeight); + const per = digits / 2; + const cell = 10 ** (5 - per); + const fmt = (value: number) => + String(Math.max(0, Math.floor(value / cell))) + .padStart(per, "0") + .slice(-per); + return `${fmt(x)} ${fmt(y)}`; +} + +/** + * A `createMarker` script that recreates the plan in-game (paste into the + * debug console or an init file). Marker names are prefixed and id-suffixed so + * a re-run collides instead of duplicating; existing ones are deleted first. + */ +export function markersToSqf( + markers: MissionMarker[], + worldHeight: number, + prefix = "ptf_plan", +): string { + const lines: string[] = [ + `// Generated by Polaris Task Force (${markers.length} markers).`, + 'private _m = "";', + ]; + for (const marker of markers) { + const name = sqfString(`${prefix}_${marker.id}`); + const [x, y] = toArmaPosition(marker.position, worldHeight); + lines.push(`deleteMarker ${name};`); + lines.push(`_m = createMarker [${name}, [${sqfNumber(x)}, ${sqfNumber(y)}]];`); + if (marker.shape === "icon") { + lines.push(`_m setMarkerShape "ICON";`); + lines.push(`_m setMarkerType ${sqfString(marker.markerType ?? "mil_dot")};`); + } else if (marker.shape === "polyline") { + const flat = marker.points + .map((point) => toArmaPosition(point, worldHeight).map(sqfNumber).join(", ")) + .join(", "); + lines.push(`_m setMarkerShape "POLYLINE";`); + lines.push(`_m setMarkerPolyline [${flat}];`); + } else { + lines.push(`_m setMarkerShape ${sqfString(marker.shape.toUpperCase())};`); + lines.push(`_m setMarkerBrush ${sqfString(marker.brush ?? "Solid")};`); + } + if (marker.shape !== "polyline") { + lines.push(`_m setMarkerSize [${sqfNumber(marker.size[0])}, ${sqfNumber(marker.size[1])}];`); + if (marker.direction) lines.push(`_m setMarkerDir ${sqfNumber(marker.direction)};`); + } + lines.push(`_m setMarkerColor ${sqfString(marker.color)};`); + if (marker.alpha !== 1) lines.push(`_m setMarkerAlpha ${sqfNumber(marker.alpha)};`); + if (marker.text) lines.push(`_m setMarkerText ${sqfString(marker.text)};`); + } + return `${lines.join("\n")}\n`; +} diff --git a/src/lib/arma-markers/types.ts b/src/lib/arma-markers/types.ts new file mode 100644 index 0000000..eb30df0 --- /dev/null +++ b/src/lib/arma-markers/types.ts @@ -0,0 +1,64 @@ +/** + * Arma 3 vanilla map marker catalog types. + * + * The catalog itself (`catalog.generated.ts`) is produced by + * `src/tools/arma/extractMarkers.ts` from the game's own config + * (CfgMarkers / CfgMarkerColors / CfgMarkerBrushes / CfgMarkerClasses), so + * class names match what `createMarker` / `setMarkerType` / `setMarkerColor` / + * `setMarkerBrush` expect in-game. Pure module (client-safe). + */ + +/** RGBA, each channel 0..1 (Arma config convention). */ +export type ArmaRgba = readonly [number, number, number, number]; + +export interface ArmaMarkerType { + /** CfgMarkers class name, e.g. "b_inf", "mil_objective". */ + className: string; + /** Resolved English display name. */ + name: string; + /** CfgMarkers `markerClass` (category), e.g. "NATO_BLUFOR", "Military". */ + category: string; + /** Public URL of the extracted (white, tint-able) icon PNG. */ + iconUrl: string; + /** Config color, side-color expressions resolved to Arma's default profile values. */ + color: ArmaRgba; + /** Config icon size in screen px (Arma convention, usually 32). */ + size: number; + shadow: boolean; + scope: number; +} + +export interface ArmaMarkerColor { + /** CfgMarkerColors class name, e.g. "ColorRed", "ColorWEST". */ + className: string; + name: string; + /** Side colors resolved to Arma's default profile values. */ + rgba: ArmaRgba; + scope: number; +} + +export type ArmaBrushPattern = + | "solid" + | "none" + | "horizontal" + | "vertical" + | "grid" + | "fdiagonal" + | "bdiagonal" + | "diaggrid" + | "cross"; + +export interface ArmaMarkerBrush { + /** CfgMarkerBrushes class name, e.g. "Solid", "Border", "Grid". */ + className: string; + name: string; + drawBorder: boolean; + pattern: ArmaBrushPattern; + scope: number; +} + +export interface ArmaMarkerCategory { + /** CfgMarkerClasses class name (matches ArmaMarkerType.category). */ + className: string; + name: string; +} diff --git a/src/tools/arma/extractMarkers.ts b/src/tools/arma/extractMarkers.ts new file mode 100644 index 0000000..20b7ef0 --- /dev/null +++ b/src/tools/arma/extractMarkers.ts @@ -0,0 +1,1047 @@ +#!/usr/bin/env bun +/** + * Extract every vanilla Arma 3 map marker (CfgMarkers) icon from the installed + * game into the web app, plus the marker colors, brushes and categories. + * + * Pipeline: + * 1. Discover PBOs in /Dta, /Addons and every //addons|Addons + * (mod folders starting with "@" are skipped: vanilla only). + * 2. Inspect each PBO (prefix + file list), extract every config.bin and keep + * the ones mentioning CfgMarkers / CfgMarkerColors / CfgMarkerBrushes / + * CfgMarkerClasses; derapify those to text and parse them into class trees. + * 3. Merge the four sections across addons (later addon wins per class) and + * resolve class inheritance within each section. + * 4. Resolve $STR_ display names through every stringtable.xml found. + * 5. Extract and convert each referenced icon .paa to PNG under + * public/arma/markers, then write src/lib/arma-markers/catalog.generated.ts. + * + * CLI: + * [--game ] Arma 3 install root (default: /mnt/LData/SteamLibrary/steamapps/common/Arma 3) + * [--hemtt ] hemtt binary (default: ~/.local/bin/hemtt, then PATH) + * [--work ] temp work dir, wiped at start (default: /tmp/opencode/arma-markers) + * + * Re-run: bun run markers:arma + */ +import { execFile, spawnSync } from "child_process"; +import { + closeSync, + existsSync, + mkdirSync, + openSync, + readFileSync, + readSync, + readdirSync, + rmSync, + writeFileSync, +} from "fs"; +import { homedir } from "os"; +import { dirname, join, resolve } from "path"; +import type { + ArmaBrushPattern, + ArmaMarkerBrush, + ArmaMarkerCategory, + ArmaMarkerColor, + ArmaMarkerType, + ArmaRgba, +} from "../../lib/arma-markers/types"; + +// --------------------------------------------------------------------------- +// CLI +// --------------------------------------------------------------------------- + +const REPO_ROOT = resolve(dirname(new URL(import.meta.url).pathname), "../../.."); +const DEFAULT_GAME = "/mnt/LData/SteamLibrary/steamapps/common/Arma 3"; +const DEFAULT_WORK = "/tmp/opencode/arma-markers"; +const OUT_PNG_DIR = join(REPO_ROOT, "public/arma/markers"); +const OUT_MODULE = join(REPO_ROOT, "src/lib/arma-markers/catalog.generated.ts"); +const CORE_MARKER_ROOT = "a3/ui_f/data/map/markers/"; +const CONCURRENCY = 8; + +function argValue(name: string): string | undefined { + const i = process.argv.indexOf(`--${name}`); + return i >= 0 && i + 1 < process.argv.length ? process.argv[i + 1] : undefined; +} + +function resolveHemtt(explicit?: string): string { + if (explicit) { + if (existsSync(explicit)) return explicit; + throw new Error(`hemtt not found at ${explicit}`); + } + const local = join(homedir(), ".local/bin/hemtt"); + if (existsSync(local)) return local; + const probe = spawnSync("which", ["hemtt"], { encoding: "utf8" }); + if (probe.status === 0 && probe.stdout.trim()) return probe.stdout.trim(); + throw new Error("hemtt not found; pass --hemtt "); +} + +const GAME = argValue("game") ?? DEFAULT_GAME; +const WORK = argValue("work") ?? DEFAULT_WORK; +const HEMTT = resolveHemtt(argValue("hemtt")); + +const warnings: string[] = []; +function warn(msg: string): void { + warnings.push(msg); + console.warn(`WARN ${msg}`); +} + +// --------------------------------------------------------------------------- +// hemtt helpers +// --------------------------------------------------------------------------- + +interface RunResult { + code: number; + stdout: string; + stderr: string; +} + +function run(args: string[]): Promise { + return new Promise((done) => { + execFile( + HEMTT, + args, + { cwd: WORK, encoding: "utf8", maxBuffer: 512 * 1024 * 1024 }, + (err, stdout, stderr) => { + const code = err ? (typeof err.code === "number" ? err.code : 1) : 0; + done({ code, stdout: stdout ?? "", stderr: stderr ?? "" }); + }, + ); + }); +} + +async function mapLimit(items: T[], limit: number, fn: (item: T) => Promise): Promise { + const out: R[] = new Array(items.length); + let next = 0; + const workers = Array.from({ length: Math.min(limit, items.length) }, async () => { + while (next < items.length) { + const i = next++; + out[i] = await fn(items[i]); + } + }); + await Promise.all(workers); + return out; +} + +// --------------------------------------------------------------------------- +// PBO discovery + inspection +// --------------------------------------------------------------------------- + +interface PboInfo { + path: string; + /** Lowercase, "/"-separated, no leading or trailing slash. */ + prefix: string; + /** Exact file paths as stored in the PBO (backslash separated). */ + files: string[]; +} + +function discoverPbos(game: string): string[] { + // Dta holds the engine base config (bin.pbo) and core strings (languagecore_f.pbo); it loads first. + const dirs = [join(game, "Dta"), join(game, "Addons")]; + const entries = readdirSync(game, { withFileTypes: true }) + .filter((e) => e.isDirectory() && !e.name.startsWith("@")) + .map((e) => e.name) + .sort((a, b) => a.localeCompare(b)); + for (const name of entries) { + for (const sub of ["addons", "Addons"]) { + const cand = join(game, name, sub); + if (existsSync(cand) && !dirs.includes(cand)) dirs.push(cand); + } + } + const out: string[] = []; + for (const d of dirs) { + if (!existsSync(d)) continue; + const pbos = readdirSync(d) + .filter((n) => n.toLowerCase().endsWith(".pbo")) + .sort((a, b) => a.localeCompare(b)); + for (const n of pbos) out.push(join(d, n)); + } + return out; +} + +/** Reads the "prefix" header straight from the PBO (fallback when inspect output lacks it). */ +function readPboPrefix(pboPath: string): string { + let fd: number | undefined; + try { + fd = openSync(pboPath, "r"); + const buf = Buffer.alloc(64 * 1024); + const len = readSync(fd, buf, 0, buf.length, 0); + let pos = 0; + const cstr = (): string => { + const end = buf.indexOf(0, pos); + if (end < 0 || end >= len) throw new Error("truncated"); + const v = buf.toString("latin1", pos, end); + pos = end + 1; + return v; + }; + cstr(); + const mime = buf.readUInt32LE(pos); + pos += 20; + if (mime !== 0x56657273) return ""; + for (;;) { + const key = cstr(); + if (!key) break; + const value = cstr(); + if (key.toLowerCase() === "prefix") return value; + } + } catch { + // not a readable PBO header + } finally { + if (fd !== undefined) closeSync(fd); + } + return ""; +} + +function normPath(p: string): string { + return p.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "").toLowerCase(); +} + +async function inspectPbo(pboPath: string): Promise { + const r = await run(["utils", "pbo", "inspect", "--format", "json", pboPath]); + if (r.code !== 0 || !r.stdout) { + warn(`could not inspect ${pboPath}`); + return undefined; + } + const prefixLine = r.stdout.match(/^\s*-\s*prefix:\s*(.+)$/im); + const prefix = normPath(prefixLine?.[1] ?? readPboPrefix(pboPath)); + const start = r.stdout.indexOf("[{"); + if (start < 0) return { path: pboPath, prefix, files: [] }; + try { + const parsed: unknown = JSON.parse(r.stdout.slice(start, r.stdout.lastIndexOf("]") + 1)); + const files: string[] = []; + if (Array.isArray(parsed)) { + for (const entry of parsed) { + if (entry && typeof entry === "object" && "filename" in entry) { + const f = (entry as { filename: unknown }).filename; + if (typeof f === "string" && f) files.push(f); + } + } + } + return { path: pboPath, prefix, files }; + } catch { + warn(`unparseable inspect output for ${pboPath}`); + return undefined; + } +} + +// --------------------------------------------------------------------------- +// Config parser (derapified text) +// --------------------------------------------------------------------------- + +type CfgValue = number | string | CfgValue[]; + +interface CfgClass { + name: string; + parent?: string; + /** Property keys lowercased. */ + props: Map; + /** Child classes keyed by lowercase name, insertion ordered. */ + classes: Map; + /** True for `class X;` forward declarations with no body. */ + external: boolean; +} + +type Tok = + | { t: "id"; v: string } + | { t: "str"; v: string } + | { t: "sym"; v: string }; + +function tokenize(src: string): Tok[] { + const toks: Tok[] = []; + const n = src.length; + let i = 0; + while (i < n) { + const c = src[i]; + if (c === " " || c === "\t" || c === "\r" || c === "\n") { + i++; + continue; + } + if (c === "/" && src[i + 1] === "/") { + while (i < n && src[i] !== "\n") i++; + continue; + } + if (c === "/" && src[i + 1] === "*") { + const end = src.indexOf("*/", i + 2); + i = end < 0 ? n : end + 2; + continue; + } + if (c === "#") { + // preprocessor leftovers: skip the line + while (i < n && src[i] !== "\n") i++; + continue; + } + if (c === '"' || c === "'") { + const q = c; + let v = ""; + i++; + while (i < n) { + if (src[i] === q) { + if (src[i + 1] === q) { + v += q; + i += 2; + continue; + } + i++; + break; + } + v += src[i++]; + } + toks.push({ t: "str", v }); + continue; + } + if ("{}[];=:,+".includes(c)) { + toks.push({ t: "sym", v: c }); + i++; + continue; + } + let j = i; + while (j < n && !" \t\r\n{}[];=:,\"'".includes(src[j])) { + if (src[j] === "+" && j > i && !/[eE]/.test(src[j - 1])) break; + j++; + } + if (j === i) j = i + 1; + toks.push({ t: "id", v: src.slice(i, j) }); + i = j; + } + return toks; +} + +function newClass(name: string, parent?: string, external = false): CfgClass { + return { name, parent, props: new Map(), classes: new Map(), external }; +} + +function scalarOf(tok: Tok): CfgValue { + if (tok.t === "str") return tok.v; + const num = Number(tok.v); + return tok.v !== "" && Number.isFinite(num) ? num : tok.v; +} + +class Parser { + private pos = 0; + constructor(private readonly toks: Tok[]) {} + + private peek(off = 0): Tok | undefined { + return this.toks[this.pos + off]; + } + + private isSym(tok: Tok | undefined, v: string): boolean { + return !!tok && tok.t === "sym" && tok.v === v; + } + + parseBody(target: CfgClass, untilBrace: boolean): void { + while (this.pos < this.toks.length) { + const tok = this.peek(); + if (!tok) return; + if (this.isSym(tok, "}")) { + if (untilBrace) { + this.pos++; + if (this.isSym(this.peek(), ";")) this.pos++; + return; + } + this.pos++; + continue; + } + if (this.isSym(tok, ";")) { + this.pos++; + continue; + } + if (tok.t === "id" && tok.v === "class") { + this.parseClass(target); + continue; + } + if (tok.t === "id" && tok.v === "delete") { + const nameTok = this.peek(1); + this.pos += 2; + if (this.isSym(this.peek(), ";")) this.pos++; + if (nameTok) target.classes.delete(nameTok.v.toLowerCase()); + continue; + } + if (tok.t === "id" && this.isSym(this.peek(1), "[") && this.isSym(this.peek(2), "]")) { + this.parseArrayProp(target, tok.v); + continue; + } + if (tok.t === "id" && this.isSym(this.peek(1), "=")) { + this.parseScalarProp(target, tok.v); + continue; + } + // Unknown statement (enum, stray tokens): skip to the end of it. + this.skipStatement(); + } + } + + private skipStatement(): void { + let depth = 0; + while (this.pos < this.toks.length) { + const tok = this.toks[this.pos++]; + if (this.isSym(tok, "{")) depth++; + else if (this.isSym(tok, "}")) { + depth--; + if (depth <= 0) { + if (this.isSym(this.peek(), ";")) this.pos++; + return; + } + } else if (this.isSym(tok, ";") && depth === 0) return; + } + } + + private parseClass(target: CfgClass): void { + this.pos++; // class + const nameTok = this.toks[this.pos++]; + const name = nameTok?.v ?? ""; + let parent: string | undefined; + if (this.isSym(this.peek(), ":")) { + this.pos++; + parent = this.toks[this.pos++]?.v; + } + const key = name.toLowerCase(); + if (this.isSym(this.peek(), "{")) { + this.pos++; + const existing = target.classes.get(key); + const cls = existing && !existing.external ? existing : newClass(name, parent); + if (existing && !existing.external && parent !== undefined) cls.parent = parent; + cls.external = false; + if (!existing || existing.external) { + if (existing) target.classes.delete(key); + target.classes.set(key, cls); + } + this.parseBody(cls, true); + return; + } + if (this.isSym(this.peek(), ";")) this.pos++; + if (!target.classes.has(key)) target.classes.set(key, newClass(name, parent, true)); + } + + private parseScalarProp(target: CfgClass, key: string): void { + this.pos += 2; // key = + const parts: Tok[] = []; + while (this.pos < this.toks.length && !this.isSym(this.peek(), ";")) { + const t = this.toks[this.pos]; + if (this.isSym(t, "}")) break; + parts.push(t); + this.pos++; + } + if (this.isSym(this.peek(), ";")) this.pos++; + const value: CfgValue = + parts.length === 1 ? scalarOf(parts[0]) : parts.map((p) => p.v).join(" "); + target.props.set(key.toLowerCase(), value); + } + + private parseArrayProp(target: CfgClass, key: string): void { + this.pos += 3; // key [ ] + let append = false; + if (this.isSym(this.peek(), "+")) { + append = true; + this.pos++; + } + if (this.isSym(this.peek(), "=")) this.pos++; + const value = this.isSym(this.peek(), "{") ? this.parseArrayLiteral() : []; + if (this.isSym(this.peek(), ";")) this.pos++; + const k = key.toLowerCase(); + const prev = target.props.get(k); + target.props.set(k, append && Array.isArray(prev) ? [...prev, ...value] : value); + } + + private parseArrayLiteral(): CfgValue[] { + this.pos++; // { + const out: CfgValue[] = []; + while (this.pos < this.toks.length) { + const tok = this.peek(); + if (!tok) break; + if (this.isSym(tok, "}")) { + this.pos++; + break; + } + if (this.isSym(tok, ",")) { + this.pos++; + continue; + } + if (this.isSym(tok, "{")) { + out.push(this.parseArrayLiteral()); + continue; + } + out.push(scalarOf(tok)); + this.pos++; + } + return out; + } +} + +function parseConfig(src: string): CfgClass { + const root = newClass(""); + new Parser(tokenize(src)).parseBody(root, false); + return root; +} + +// --------------------------------------------------------------------------- +// Section merging + inheritance +// --------------------------------------------------------------------------- + +const SECTION_NAMES = ["CfgMarkers", "CfgMarkerColors", "CfgMarkerBrushes", "CfgMarkerClasses"]; + +interface MergedEntry { + name: string; + parent?: string; + props: Map; +} + +/** Ordered merge of one Cfg section across addons (later addon wins per property). */ +class Section { + readonly entries = new Map(); + + merge(cls: CfgClass): void { + for (const [key, child] of cls.classes) { + if (child.external) { + if (!this.entries.has(key)) { + this.entries.set(key, { name: child.name, parent: child.parent, props: new Map() }); + } + continue; + } + const existing = this.entries.get(key); + if (!existing) { + this.entries.set(key, { name: child.name, parent: child.parent, props: new Map(child.props) }); + continue; + } + if (child.parent !== undefined) existing.parent = child.parent; + for (const [k, v] of child.props) existing.props.set(k, v); + } + } + + resolve(key: string, seen: Set = new Set()): Map { + const entry = this.entries.get(key.toLowerCase()); + if (!entry) return new Map(); + if (seen.has(key.toLowerCase())) { + warn(`inheritance cycle at ${entry.name}`); + return new Map(entry.props); + } + seen.add(key.toLowerCase()); + const base = entry.parent ? this.resolve(entry.parent, seen) : new Map(); + if (entry.parent && !this.entries.has(entry.parent.toLowerCase())) { + warn(`class ${entry.name} inherits unknown parent ${entry.parent}`); + } + for (const [k, v] of entry.props) base.set(k, v); + return base; + } +} + +// --------------------------------------------------------------------------- +// Value helpers +// --------------------------------------------------------------------------- + +function num(v: CfgValue | undefined, fallback: number): number { + if (typeof v === "number") return v; + if (typeof v === "string") { + const n = Number(v.trim()); + if (v.trim() !== "" && Number.isFinite(n)) return n; + } + return fallback; +} + +function str(v: CfgValue | undefined): string { + return typeof v === "string" ? v : typeof v === "number" ? String(v) : ""; +} + +const SIDE_DEFAULTS: Record = { + blufor: [0, 0.3, 0.6, 1], + opfor: [0.5, 0, 0, 1], + independent: [0, 0.5, 0, 1], + civilian: [0.4, 0, 0.5, 1], + unknown: [0.7, 0.6, 0, 1], +}; +const SIDE_ALIASES: Record = { + blufor: "blufor", + west: "blufor", + opfor: "opfor", + east: "opfor", + independent: "independent", + guer: "independent", + resistance: "independent", + civilian: "civilian", + civ: "civilian", + unknown: "unknown", +}; +const CHANNEL_INDEX: Record = { r: 0, g: 1, b: 2, a: 3 }; + +function round3(n: number): number { + return Math.round(n * 1000) / 1000; +} + +function resolveColor(raw: CfgValue | undefined, owner: string): ArmaRgba { + if (!Array.isArray(raw)) { + warn(`${owner}: missing color, using black`); + return [0, 0, 0, 1]; + } + const out: number[] = []; + for (const el of raw) { + if (typeof el === "number") { + out.push(el); + continue; + } + if (Array.isArray(el)) { + warn(`${owner}: nested color array, using 0`); + out.push(0); + continue; + } + const s = el.trim(); + const asNum = Number(s); + if (s !== "" && Number.isFinite(asNum)) { + out.push(asNum); + continue; + } + const sideVar = s.match(/Map_([A-Za-z]+)_([RGBA])\b/); + if (sideVar) { + const side = SIDE_ALIASES[sideVar[1].toLowerCase()]; + const ch = CHANNEL_INDEX[sideVar[2].toLowerCase()]; + if (side && ch !== undefined) { + out.push(SIDE_DEFAULTS[side][ch]); + continue; + } + } + const sideToken = s.match(/^color_?([A-Za-z]+)$/i); + if (sideToken && SIDE_ALIASES[sideToken[1].toLowerCase()]) { + // Unexpanded macro like "color_Civilian": the side RGB, alpha from the next element. + const side = SIDE_DEFAULTS[SIDE_ALIASES[sideToken[1].toLowerCase()]]; + warn(`${owner}: color token "${s}" expanded to the ${sideToken[1]} default profile RGB`); + out.push(side[0], side[1], side[2]); + continue; + } + const fallback = s.match(/getvariable\s*\[\s*['"][^'"]*['"]\s*,\s*(-?[\d.]+)\s*\]/i); + if (fallback) { + warn(`${owner}: unrecognized color expression ${s}, using fallback ${fallback[1]}`); + out.push(Number(fallback[1])); + continue; + } + warn(`${owner}: unparseable color element ${s}, using 0`); + out.push(0); + } + if (out.length !== 4) { + warn(`${owner}: color has ${out.length} channels, normalizing to 4`); + while (out.length < 3) out.push(0); + if (out.length < 4) out.push(1); + out.length = 4; + } + return [round3(out[0]), round3(out[1]), round3(out[2]), round3(out[3])]; +} + +// --------------------------------------------------------------------------- +// Stringtables +// --------------------------------------------------------------------------- + +function decodeXml(s: string): string { + return s + .replace(//g, "$1") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/&#(\d+);/g, (_, d: string) => String.fromCharCode(Number(d))) + .replace(/&#x([0-9a-f]+);/gi, (_, h: string) => String.fromCharCode(parseInt(h, 16))) + .replace(/&/g, "&") + .trim(); +} + +function parseStringtable(xml: string, wanted: Set, into: Map): void { + const keyRe = /([\s\S]*?)<\/Key>/gi; + let m: RegExpExecArray | null; + while ((m = keyRe.exec(xml))) { + const id = m[1].toLowerCase(); + if (!wanted.has(id)) continue; + const body = m[2]; + const en = body.match(/([\s\S]*?)<\/English>/i); + const orig = body.match(/([\s\S]*?)<\/Original>/i); + const text = decodeXml((en ?? orig)?.[1] ?? ""); + if (text) into.set(id, text); + } +} + +function humanize(className: string): string { + const s = className + .replace(/^(mil|hd|loc|respawn|n|b|o|c|u)_/i, "") + .replace(/_/g, " ") + .replace(/([a-z])([A-Z])/g, "$1 $2") + .trim(); + return s.replace(/\b\w/g, (c) => c.toUpperCase()) || className; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +const BRUSH_PATTERNS: Record = { + solid: "solid", + solidfull: "solid", + solidborder: "solid", + border: "none", + horizontal: "horizontal", + vertical: "vertical", + grid: "grid", + fdiagonal: "fdiagonal", + bdiagonal: "bdiagonal", + diaggrid: "diaggrid", + cross: "cross", +}; + +function brushPattern(className: string, texture: string): ArmaBrushPattern { + const byName = BRUSH_PATTERNS[className.toLowerCase()]; + if (byName) return byName; + const base = normPath(texture) + .split("/") + .pop() + ?.replace(/(_ca)?\.paa$/, ""); + if (base && BRUSH_PATTERNS[base]) return BRUSH_PATTERNS[base]; + warn(`brush ${className}: unknown pattern (texture "${texture}"), using solid`); + return "solid"; +} + +function toTexturePath(icon: string): string { + let p = normPath(icon); + if (p && !/\.[a-z0-9]+$/.test(p.split("/").pop() ?? "")) p += ".paa"; + return p; +} + +function outRelFor(texturePath: string): string { + const rel = texturePath.startsWith(CORE_MARKER_ROOT) + ? texturePath.slice(CORE_MARKER_ROOT.length) + : texturePath; + return rel.replace(/\.paa$/, ".png"); +} + +async function main(): Promise { + const t0 = Date.now(); + if (!existsSync(join(GAME, "Addons"))) throw new Error(`no Addons dir under ${GAME}`); + rmSync(WORK, { recursive: true, force: true }); + mkdirSync(WORK, { recursive: true }); + console.log(`game: ${GAME}\nhemtt: ${HEMTT}\nwork: ${WORK}`); + + // 1. discover + inspect + const pboPaths = discoverPbos(GAME); + console.log(`found ${pboPaths.length} PBOs; inspecting...`); + const inspected = await mapLimit(pboPaths, CONCURRENCY, inspectPbo); + const pbos = inspected.filter((p): p is PboInfo => !!p); + + // 2. extract config.bin files and keep those mentioning the marker sections + interface ConfigJob { + pbo: PboInfo; + file: string; + idx: number; + } + const configJobs: ConfigJob[] = []; + for (const pbo of pbos) { + for (const f of pbo.files) { + const base = f.split("\\").pop()?.toLowerCase(); + if (base === "config.bin") configJobs.push({ pbo, file: f, idx: configJobs.length }); + } + } + console.log(`scanning ${configJobs.length} config.bin files...`); + const cfgDir = join(WORK, "configs"); + mkdirSync(cfgDir, { recursive: true }); + const needles = SECTION_NAMES.map((s) => s.toLowerCase()); + const relevant = ( + await mapLimit(configJobs, CONCURRENCY, async (job) => { + const out = join(cfgDir, `${job.idx}.bin`); + const r = await run(["utils", "pbo", "extract", job.pbo.path, job.file, out]); + if (r.code !== 0 || !existsSync(out)) { + warn(`could not extract ${job.file} from ${job.pbo.path}`); + return undefined; + } + const text = readFileSync(out).toString("latin1").toLowerCase(); + if (!needles.some((n) => text.includes(n))) { + rmSync(out, { force: true }); + return undefined; + } + const cpp = join(cfgDir, `${job.idx}.cpp`); + const d = await run(["utils", "config", "derapify", out, cpp]); + if (d.code !== 0 || !existsSync(cpp)) { + warn(`could not derapify ${job.file} from ${job.pbo.path}`); + return undefined; + } + return { job, cpp }; + }) + ).filter((x): x is { job: ConfigJob; cpp: string } => !!x); + + // 3. parse + merge in load order (Addons first, then DLC dirs; discovery order) + const sections = new Map(SECTION_NAMES.map((n) => [n.toLowerCase(), new Section()])); + const contributors: string[] = []; + for (const { job, cpp } of relevant) { + const root = parseConfig(readFileSync(cpp, "utf8")); + const found: string[] = []; + for (const name of SECTION_NAMES) { + const cls = root.classes.get(name.toLowerCase()); + if (!cls || cls.external) continue; + const section = sections.get(name.toLowerCase()); + if (!section) continue; + section.merge(cls); + found.push(`${name}(${cls.classes.size})`); + } + if (found.length) { + contributors.push(`${job.pbo.path.slice(GAME.length + 1)} [${job.file}]: ${found.join(" ")}`); + } + } + console.log(`config contributors:\n ${contributors.join("\n ")}`); + + const markers = sections.get("cfgmarkers") as Section; + const colorsSec = sections.get("cfgmarkercolors") as Section; + const brushesSec = sections.get("cfgmarkerbrushes") as Section; + const classesSec = sections.get("cfgmarkerclasses") as Section; + + // file index: full lowercase path -> pbo + original file name + const fileIndex = new Map(); + for (const pbo of pbos) { + for (const f of pbo.files) { + const full = pbo.prefix ? `${pbo.prefix}/${normPath(f)}` : normPath(f); + if (!fileIndex.has(full)) fileIndex.set(full, { pbo, file: f }); + } + } + + // 4. collect resolved records + interface PendingType { + className: string; + nameRaw: string; + category: string; + texture: string; + color: ArmaRgba; + size: number; + shadow: boolean; + scope: number; + } + const skipped: string[] = []; + const categoryByLower = new Map(); + for (const e of classesSec.entries.values()) categoryByLower.set(e.name.toLowerCase(), e.name); + + const pendingTypes: PendingType[] = []; + for (const [key, entry] of markers.entries) { + const p = markers.resolve(key); + const scope = num(p.get("scope"), 0); + if (scope < 1) continue; + const icon = str(p.get("icon")).trim(); + if (!icon) { + skipped.push(`${entry.name}: empty icon`); + continue; + } + if (icon.startsWith("#")) { + skipped.push(`${entry.name}: procedural icon ${icon}`); + continue; + } + const texture = toTexturePath(icon); + if (!fileIndex.has(texture)) { + skipped.push(`${entry.name}: texture not found in any PBO (${icon})`); + continue; + } + const rawCat = str(p.get("markerclass")).trim() || "Default"; + let category = categoryByLower.get(rawCat.toLowerCase()); + if (!category) { + warn(`marker ${entry.name}: markerClass "${rawCat}" not in CfgMarkerClasses`); + category = rawCat; + } + pendingTypes.push({ + className: entry.name, + nameRaw: str(p.get("name")), + category, + texture, + color: resolveColor(p.get("color"), `marker ${entry.name}`), + size: num(p.get("size"), 32), + shadow: num(p.get("shadow"), 0) !== 0, + scope, + }); + } + + interface PendingColor { + className: string; + nameRaw: string; + rgba: ArmaRgba; + scope: number; + } + const pendingColors: PendingColor[] = []; + for (const [key, entry] of colorsSec.entries) { + const p = colorsSec.resolve(key); + const scope = num(p.get("scope"), 0); + if (scope < 1) continue; + pendingColors.push({ + className: entry.name, + nameRaw: str(p.get("name")), + rgba: resolveColor(p.get("color"), `color ${entry.name}`), + scope, + }); + } + + interface PendingBrush { + className: string; + nameRaw: string; + drawBorder: boolean; + pattern: ArmaBrushPattern; + scope: number; + } + const pendingBrushes: PendingBrush[] = []; + for (const [key, entry] of brushesSec.entries) { + const p = brushesSec.resolve(key); + const scope = num(p.get("scope"), 0); + if (scope < 1) continue; + pendingBrushes.push({ + className: entry.name, + nameRaw: str(p.get("name")), + drawBorder: num(p.get("drawborder"), 0) !== 0, + pattern: brushPattern(entry.name, str(p.get("texture"))), + scope, + }); + } + + const pendingCategories: { className: string; nameRaw: string }[] = []; + for (const [key, entry] of classesSec.entries) { + const p = classesSec.resolve(key); + if (p.has("scope") && num(p.get("scope"), 0) < 1) continue; + pendingCategories.push({ + className: entry.name, + nameRaw: str(p.get("displayname") ?? p.get("name")), + }); + } + + // 5. stringtables + const wanted = new Set(); + const allRaw = [ + ...pendingTypes.map((x) => x.nameRaw), + ...pendingColors.map((x) => x.nameRaw), + ...pendingBrushes.map((x) => x.nameRaw), + ...pendingCategories.map((x) => x.nameRaw), + ]; + for (const raw of allRaw) if (raw.startsWith("$")) wanted.add(raw.slice(1).toLowerCase()); + const strings = new Map(); + const stDir = join(WORK, "stringtables"); + mkdirSync(stDir, { recursive: true }); + const stJobs: { pbo: PboInfo; file: string; idx: number }[] = []; + for (const pbo of pbos) { + for (const f of pbo.files) { + if (f.split("\\").pop()?.toLowerCase() === "stringtable.xml") { + stJobs.push({ pbo, file: f, idx: stJobs.length }); + } + } + } + console.log(`reading ${stJobs.length} stringtables for ${wanted.size} keys...`); + const stFiles = await mapLimit(stJobs, CONCURRENCY, async (job) => { + const out = join(stDir, `${job.idx}.xml`); + const r = await run(["utils", "pbo", "extract", job.pbo.path, job.file, out]); + return r.code === 0 && existsSync(out) ? out : undefined; + }); + for (const f of stFiles) if (f) parseStringtable(readFileSync(f, "utf8"), wanted, strings); + + const resolveName = (raw: string, className: string, kind: string): string => { + const trimmed = raw.trim(); + if (!trimmed) { + warn(`${kind} ${className}: no name, derived from class name`); + return humanize(className); + } + if (!trimmed.startsWith("$")) return trimmed; + const hit = strings.get(trimmed.slice(1).toLowerCase()); + if (hit) return hit; + warn(`${kind} ${className}: unresolved ${trimmed}, derived from class name`); + return humanize(className); + }; + + // 6. textures + rmSync(OUT_PNG_DIR, { recursive: true, force: true }); + mkdirSync(OUT_PNG_DIR, { recursive: true }); + const textures = [...new Set(pendingTypes.map((t) => t.texture))]; + const paaDir = join(WORK, "paa"); + mkdirSync(paaDir, { recursive: true }); + console.log(`converting ${textures.length} unique textures...`); + const failedTextures = new Set(); + await mapLimit(textures, CONCURRENCY, async (tex) => { + const src = fileIndex.get(tex); + if (!src) { + failedTextures.add(tex); + return; + } + const paa = join(paaDir, tex); + mkdirSync(dirname(paa), { recursive: true }); + const r = await run(["utils", "pbo", "extract", src.pbo.path, src.file, paa]); + if (r.code !== 0 || !existsSync(paa)) { + warn(`could not extract ${tex}`); + failedTextures.add(tex); + return; + } + const png = join(OUT_PNG_DIR, outRelFor(tex)); + mkdirSync(dirname(png), { recursive: true }); + const c = await run(["utils", "paa", "convert", paa, png]); + if (c.code !== 0 || !existsSync(png)) { + warn(`could not convert ${tex}: ${c.stderr.trim().split("\n").pop() ?? ""}`); + failedTextures.add(tex); + } + }); + + // 7. final records + const types: ArmaMarkerType[] = []; + for (const t of pendingTypes) { + if (failedTextures.has(t.texture)) { + skipped.push(`${t.className}: texture conversion failed (${t.texture})`); + continue; + } + types.push({ + className: t.className, + name: resolveName(t.nameRaw, t.className, "marker"), + category: t.category, + iconUrl: `/arma/markers/${outRelFor(t.texture)}`, + color: t.color, + size: t.size, + shadow: t.shadow, + scope: t.scope, + }); + } + const colors: ArmaMarkerColor[] = pendingColors.map((c) => ({ + className: c.className, + name: resolveName(c.nameRaw, c.className, "color"), + rgba: c.rgba, + scope: c.scope, + })); + const brushes: ArmaMarkerBrush[] = pendingBrushes.map((b) => ({ + className: b.className, + name: resolveName(b.nameRaw, b.className, "brush"), + drawBorder: b.drawBorder, + pattern: b.pattern, + scope: b.scope, + })); + const categories: ArmaMarkerCategory[] = pendingCategories.map((c) => ({ + className: c.className, + name: resolveName(c.nameRaw, c.className, "category"), + })); + + // 8. module + const body = [ + "// Generated by src/tools/arma/extractMarkers.ts; do not edit by hand.", + "// Re-run: bun run markers:arma", + 'import type {\n ArmaMarkerBrush,\n ArmaMarkerCategory,\n ArmaMarkerColor,\n ArmaMarkerType,\n} from "./types";', + "", + `export const ARMA_MARKER_TYPES: readonly ArmaMarkerType[] = ${JSON.stringify(types)};`, + "", + `export const ARMA_MARKER_COLORS: readonly ArmaMarkerColor[] = ${JSON.stringify(colors)};`, + "", + `export const ARMA_MARKER_BRUSHES: readonly ArmaMarkerBrush[] = ${JSON.stringify(brushes)};`, + "", + `export const ARMA_MARKER_CATEGORIES: readonly ArmaMarkerCategory[] = ${JSON.stringify(categories)};`, + "", + ].join("\n"); + writeFileSync(OUT_MODULE, body); + const fmt = spawnSync("bunx", ["prettier", "--write", OUT_MODULE], { + cwd: REPO_ROOT, + encoding: "utf8", + }); + if (fmt.status !== 0) warn(`prettier failed: ${fmt.stderr.trim()}`); + + // 9. summary + const perCat = new Map(); + for (const t of types) perCat.set(t.category, (perCat.get(t.category) ?? 0) + 1); + const pngCount = textures.length - failedTextures.size; + console.log("\nSkipped marker classes (scope >= 1):"); + for (const s of skipped) console.log(` ${s}`); + if (!skipped.length) console.log(" (none)"); + console.log("\nSummary:"); + console.log(` marker types: ${types.length}`); + for (const [cat, n] of perCat) console.log(` ${cat}: ${n}`); + console.log(` colors: ${colors.length}`); + console.log(` brushes: ${brushes.length}`); + console.log(` categories: ${categories.length}`); + console.log(` PNGs written: ${pngCount} (to ${OUT_PNG_DIR})`); + console.log(` warnings: ${warnings.length}`); + console.log(` module: ${OUT_MODULE}`); + console.log(` done in ${((Date.now() - t0) / 1000).toFixed(1)}s`); +} + +main().catch((e: unknown) => { + console.error(e instanceof Error ? (e.stack ?? e.message) : String(e)); + process.exit(1); +});