diff --git a/.prettierrc b/.prettierrc index 92cde39..40b6472 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "singleQuote": true + "singleQuote": true, + "printWidth": 80 } \ No newline at end of file diff --git a/connectors/squad-layers/layers.json b/connectors/data/layers.json similarity index 93% rename from connectors/squad-layers/layers.json rename to connectors/data/layers.json index 5d51f0e..931cdeb 100644 --- a/connectors/squad-layers/layers.json +++ b/connectors/data/layers.json @@ -2,8 +2,7 @@ { "layer": "Al Basrah AAS v1", "map": "Al Basrah", - "layerClassname": "Al_Basrah_AAS_v1", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_AAS_v1", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v1", @@ -30,8 +29,7 @@ { "layer": "Al Basrah Insurgency v1", "map": "Al Basrah", - "layerClassname": "Al_Basrah_Insurgency_v1", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_Insurgency_v1", "mapSize": "2x2 km", "gamemode": "Insurgency", "version": "v1", @@ -58,8 +56,7 @@ { "layer": "Al Basrah Invasion v1", "map": "Al Basrah", - "layerClassname": "Al_Basrah_Invasion_v1", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_Invasion_v1", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v1", @@ -86,8 +83,7 @@ { "layer": "Al Basrah Invasion v2", "map": "Al Basrah", - "layerClassname": "Al_Basrah_Invasion_v2", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_Invasion_v2", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v2", @@ -114,8 +110,7 @@ { "layer": "Al Basrah RAAS v1", "map": "Al Basrah", - "layerClassname": "Al_Basrah_RAAS_v1", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_RAAS_v1", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v1", @@ -142,8 +137,7 @@ { "layer": "Al Basrah Skirmish v1", "map": "Al Basrah", - "layerClassname": "Al_Basrah_Skirmish_v1", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_Skirmish_v1", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v1", @@ -170,8 +164,7 @@ { "layer": "Al Basrah Skirmish v2", "map": "Al Basrah", - "layerClassname": "Al_Basrah_Skirmish_v2", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_Skirmish_v2", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v2", @@ -198,8 +191,7 @@ { "layer": "Al Basrah TC v1", "map": "Al Basrah", - "layerClassname": "Al_Basrah_TC_v1", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_TC_v1", "mapSize": "2x2 km", "gamemode": "TC", "version": "v1", @@ -222,8 +214,7 @@ { "layer": "Al Basrah TC v2", "map": "Al Basrah", - "layerClassname": "Al_Basrah_TC_v2", - "mapClassname": "Al_Basrah", + "layerClassname": "Albasrah_TC_v2", "mapSize": "2x2 km", "gamemode": "TC", "version": "v2", @@ -247,7 +238,6 @@ "layer": "Belaya AAS v1", "map": "Belaya", "layerClassname": "Belaya_AAS_v1", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "AAS", "version": "v1", @@ -275,7 +265,6 @@ "layer": "Belaya Invasion v1", "map": "Belaya", "layerClassname": "Belaya_Invasion_v1", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "Invasion", "version": "v1", @@ -303,7 +292,6 @@ "layer": "Belaya Invasion v2", "map": "Belaya", "layerClassname": "Belaya_Invasion_v2", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "Invasion", "version": "v2", @@ -331,7 +319,6 @@ "layer": "Belaya Invasion v3", "map": "Belaya", "layerClassname": "Belaya_Invasion_v3", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "Invasion", "version": "v3", @@ -359,7 +346,6 @@ "layer": "Belaya RAAS v1", "map": "Belaya", "layerClassname": "Belaya_RAAS_v1", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "RAAS", "version": "v1", @@ -387,7 +373,6 @@ "layer": "Belaya RAAS v2", "map": "Belaya", "layerClassname": "Belaya_RAAS_v2", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "RAAS", "version": "v2", @@ -415,7 +400,6 @@ "layer": "Belaya RAAS v3", "map": "Belaya", "layerClassname": "Belaya_RAAS_v3", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "RAAS", "version": "v3", @@ -443,7 +427,6 @@ "layer": "Belaya Skirmish v1", "map": "Belaya", "layerClassname": "Belaya_Skirmish_v1", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "Skirmish", "version": "v1", @@ -471,7 +454,6 @@ "layer": "Belaya TC v1", "map": "Belaya", "layerClassname": "Belaya_TC_v1", - "mapClassname": "Belaya", "mapSize": "2x4 km", "gamemode": "TC", "version": "v1", @@ -495,7 +477,6 @@ "layer": "Chora AAS v1", "map": "Chora", "layerClassname": "Chora_AAS_v1", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "AAS", "version": "v1", @@ -523,7 +504,6 @@ "layer": "Chora AAS v2", "map": "Chora", "layerClassname": "Chora_AAS_v2", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "AAS", "version": "v2", @@ -551,7 +531,6 @@ "layer": "Chora Insurgency v1", "map": "Chora", "layerClassname": "Chora_Insurgency_v1", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "Insurgency", "version": "v1", @@ -579,7 +558,6 @@ "layer": "Chora Invasion v1", "map": "Chora", "layerClassname": "Chora_Invasion_v1", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "Invasion", "version": "v1", @@ -607,7 +585,6 @@ "layer": "Chora Invasion v2", "map": "Chora", "layerClassname": "Chora_Invasion_v2", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "Invasion", "version": "v2", @@ -635,7 +612,6 @@ "layer": "Chora RAAS v1", "map": "Chora", "layerClassname": "Chora_RAAS_v1", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "RAAS", "version": "v1", @@ -663,7 +639,6 @@ "layer": "Chora RAAS v2", "map": "Chora", "layerClassname": "Chora_RAAS_v2", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "RAAS", "version": "v2", @@ -691,7 +666,6 @@ "layer": "Chora Skirmish v1", "map": "Chora", "layerClassname": "Chora_Skirmish_v1", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "Skirmish", "version": "v1", @@ -719,7 +693,6 @@ "layer": "Chora TC v1", "map": "Chora", "layerClassname": "Chora_TC_v1", - "mapClassname": "Chora", "mapSize": "1x2 km", "gamemode": "TC", "version": "v1", @@ -743,7 +716,6 @@ "layer": "Fool's Road AAS v1", "map": "Fool's Road", "layerClassname": "FoolsRoad_AAS_v1", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v1", @@ -771,7 +743,6 @@ "layer": "Fool's Road AAS v2", "map": "Fool's Road", "layerClassname": "FoolsRoad_AAS_v2", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v2", @@ -799,7 +770,6 @@ "layer": "Fool's Road Destruction v1", "map": "Fool's Road", "layerClassname": "FoolsRoad_Destruction_v1", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "Destruction", "version": "v1", @@ -827,7 +797,6 @@ "layer": "Fool's Road Invasion v1", "map": "Fool's Road", "layerClassname": "FoolsRoad_Invasion_v1", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v1", @@ -855,7 +824,6 @@ "layer": "Fool's Road RAAS v1", "map": "Fool's Road", "layerClassname": "FoolsRoad_RAAS_v1", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v1", @@ -883,7 +851,6 @@ "layer": "Fool's Road RAAS v2", "map": "Fool's Road", "layerClassname": "FoolsRoad_RAAS_v2", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v2", @@ -911,7 +878,6 @@ "layer": "Fool's Road RAAS v3", "map": "Fool's Road", "layerClassname": "FoolsRoad_RAAS_v3", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v3", @@ -939,7 +905,6 @@ "layer": "Fool's Road Skirmish v1", "map": "Fool's Road", "layerClassname": "FoolsRoad_Skirmish_v1", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v1", @@ -967,7 +932,6 @@ "layer": "Fool's Road Skirmish v2", "map": "Fool's Road", "layerClassname": "FoolsRoad_Skirmish_v2", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v2", @@ -995,7 +959,6 @@ "layer": "Fool's Road TC v1", "map": "Fool's Road", "layerClassname": "FoolsRoad_TC_v1", - "mapClassname": "Fools_Road", "mapSize": "2x2 km", "gamemode": "TC", "version": "v1", @@ -1019,7 +982,6 @@ "layer": "Gorodok AAS v1", "map": "Gorodok", "layerClassname": "Gorodok_AAS_v1", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "AAS", "version": "v1", @@ -1047,7 +1009,6 @@ "layer": "Gorodok Destruction v1", "map": "Gorodok", "layerClassname": "Gorodok_Destruction_v1", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "Destruction", "version": "v1", @@ -1075,7 +1036,6 @@ "layer": "Gorodok Insurgency v1", "map": "Gorodok", "layerClassname": "Gorodok_Insurgency_v1", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "Insurgency", "version": "v1", @@ -1103,7 +1063,6 @@ "layer": "Gorodok Invasion v1", "map": "Gorodok", "layerClassname": "Gorodok_Invasion_v1", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "Invasion", "version": "v1", @@ -1131,7 +1090,6 @@ "layer": "Gorodok Invasion v2", "map": "Gorodok", "layerClassname": "Gorodok_Invasion_v2", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "Invasion", "version": "v2", @@ -1159,7 +1117,6 @@ "layer": "Gorodok RAAS v1", "map": "Gorodok", "layerClassname": "Gorodok_RAAS_v1", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "RAAS", "version": "v1", @@ -1187,7 +1144,6 @@ "layer": "Gorodok RAAS v2", "map": "Gorodok", "layerClassname": "Gorodok_RAAS_v2", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "RAAS", "version": "v2", @@ -1215,7 +1171,6 @@ "layer": "Gorodok RAAS v3", "map": "Gorodok", "layerClassname": "Gorodok_RAAS_v3", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "RAAS", "version": "v3", @@ -1243,7 +1198,6 @@ "layer": "Gorodok RAAS v4", "map": "Gorodok", "layerClassname": "Gorodok_RAAS_v4", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "RAAS", "version": "v4", @@ -1271,7 +1225,6 @@ "layer": "Gorodok RAAS v5", "map": "Gorodok", "layerClassname": "Gorodok_RAAS_v5", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "RAAS", "version": "v5", @@ -1299,7 +1252,6 @@ "layer": "Gorodok Skirmish v1", "map": "Gorodok", "layerClassname": "Gorodok_Skirmish_v1", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "Skirmish", "version": "v1", @@ -1327,7 +1279,6 @@ "layer": "Gorodok TC v1", "map": "Gorodok", "layerClassname": "Gorodok_TC_v1", - "mapClassname": "Gorodok", "mapSize": "", "gamemode": "TC", "version": "v1", @@ -1351,7 +1302,6 @@ "layer": "Jensen's Range Training v1", "map": "Jensen's Range", "layerClassname": "Jensens_Range_v1", - "mapClassname": "Jensens_Range", "mapSize": "4x4 km", "gamemode": "Training", "version": "v1", @@ -1379,7 +1329,6 @@ "layer": "Jensen's Range Training v2", "map": "Jensen's Range", "layerClassname": "Jensens_Range_v2", - "mapClassname": "Jensens_Range", "mapSize": "4x4 km", "gamemode": "Training", "version": "v2", @@ -1407,7 +1356,6 @@ "layer": "Jensen's Range Training v3", "map": "Jensen's Range", "layerClassname": "Jensens_Range_v3", - "mapClassname": "Jensens_Range", "mapSize": "4x4 km", "gamemode": "Training", "version": "v3", @@ -1435,7 +1383,6 @@ "layer": "Kamdesh AAS v1", "map": "Kamdesh", "layerClassname": "Kamdesh_AAS_v1", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "AAS", "version": "v1", @@ -1463,7 +1410,6 @@ "layer": "Kamdesh Insurgency v1", "map": "Kamdesh", "layerClassname": "Kamdesh_Insurgency_v1", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "Insurgency", "version": "v1", @@ -1491,7 +1437,6 @@ "layer": "Kamdesh Insurgency v2", "map": "Kamdesh", "layerClassname": "Kamdesh_Insurgency_v2", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "Insurgency", "version": "v2", @@ -1519,7 +1464,6 @@ "layer": "Kamdesh Invasion v1", "map": "Kamdesh", "layerClassname": "Kamdesh_Invasion_v1", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v1", @@ -1547,7 +1491,6 @@ "layer": "Kamdesh Invasion v2", "map": "Kamdesh", "layerClassname": "Kamdesh_Invasion_v2", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v2", @@ -1575,7 +1518,6 @@ "layer": "Kamdesh Invasion v3", "map": "Kamdesh", "layerClassname": "Kamdesh_Invasion_v3", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v3", @@ -1603,7 +1545,6 @@ "layer": "Kamdesh RAAS v1", "map": "Kamdesh", "layerClassname": "Kamdesh_RAAS_v1", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v1", @@ -1631,7 +1572,6 @@ "layer": "Kamdesh RAAS v2", "map": "Kamdesh", "layerClassname": "Kamdesh_RAAS_v2", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v2", @@ -1659,7 +1599,6 @@ "layer": "Kamdesh RAAS v3", "map": "Kamdesh", "layerClassname": "Kamdesh_RAAS_v3", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v3", @@ -1687,7 +1626,6 @@ "layer": "Kamdesh RAAS v4", "map": "Kamdesh", "layerClassname": "Kamdesh_RAAS_v4", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v4", @@ -1715,7 +1653,6 @@ "layer": "Kamdesh Skirmish v1", "map": "Kamdesh", "layerClassname": "Kamdesh_Skirmish_v1", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "Skirmish", "version": "v1", @@ -1743,7 +1680,6 @@ "layer": "Kamdesh TC v1", "map": "Kamdesh", "layerClassname": "Kamdesh_TC_v1", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "TC", "version": "v1", @@ -1767,7 +1703,6 @@ "layer": "Kamdesh TC v2", "map": "Kamdesh", "layerClassname": "Kamdesh_TC_v2", - "mapClassname": "Kamdesh", "mapSize": "4x4 km", "gamemode": "TC", "version": "v2", @@ -1791,7 +1726,6 @@ "layer": "Kohat AAS v1", "map": "Kohat", "layerClassname": "Kohat_AAS_v1", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "AAS", "version": "v1", @@ -1819,7 +1753,6 @@ "layer": "Kohat Insurgency v1", "map": "Kohat", "layerClassname": "Kohat_Insurgency_v1", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "Insurgency", "version": "v1", @@ -1847,7 +1780,6 @@ "layer": "Kohat Invasion v1", "map": "Kohat", "layerClassname": "Kohat_Invasion_v1", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v1", @@ -1875,7 +1807,6 @@ "layer": "Kohat Invasion v2", "map": "Kohat", "layerClassname": "Kohat_Invasion_v2", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v2", @@ -1903,7 +1834,6 @@ "layer": "Kohat RAAS v1", "map": "Kohat", "layerClassname": "Kohat_RAAS_v1", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v1", @@ -1931,7 +1861,6 @@ "layer": "Kohat RAAS v2", "map": "Kohat", "layerClassname": "Kohat_RAAS_v2", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v2", @@ -1959,7 +1888,6 @@ "layer": "Kohat RAAS v3", "map": "Kohat", "layerClassname": "Kohat_RAAS_v3", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v3", @@ -1987,7 +1915,6 @@ "layer": "Kohat RAAS v4", "map": "Kohat", "layerClassname": "Kohat_RAAS_v4", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v4", @@ -2015,7 +1942,6 @@ "layer": "Kohat Skirmish v1", "map": "Kohat", "layerClassname": "Kohat_Skirmish_v1", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "Skirmish", "version": "v1", @@ -2043,7 +1969,6 @@ "layer": "Kohat TC v1", "map": "Kohat", "layerClassname": "Kohat_TC_v1", - "mapClassname": "Kohat", "mapSize": "4x4 km", "gamemode": "TC", "version": "v1", @@ -2067,7 +1992,6 @@ "layer": "Kokan AAS v1", "map": "Kokan", "layerClassname": "Kokan_Valley_AAS_v1", - "mapClassname": "Kokan_Valley", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v1", @@ -2076,11 +2000,11 @@ "commander": true, "flagCount": 7, "teamOne": { - "faction": "GB", + "faction": "RUS_DE", "tickets": "250" }, "teamTwo": { - "faction": "RUS_DE", + "faction": "INS", "tickets": "250" }, "tanks": "N/A", @@ -2095,7 +2019,6 @@ "layer": "Kokan Insurgency v1", "map": "Kokan", "layerClassname": "Kokan_Valley_Insurgency_v1", - "mapClassname": "Kokan_Valley", "mapSize": "2x2 km", "gamemode": "Insurgency", "version": "v1", @@ -2123,7 +2046,6 @@ "layer": "Kokan Invasion v1", "map": "Kokan", "layerClassname": "Kokan_Valley_Invasion_v1", - "mapClassname": "Kokan_Valley", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v1", @@ -2151,7 +2073,6 @@ "layer": "Kokan RAAS v1", "map": "Kokan", "layerClassname": "Kokan_Valley_RAAS_v1", - "mapClassname": "Kokan_Valley", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v1", @@ -2179,7 +2100,6 @@ "layer": "Kokan RAAS v2", "map": "Kokan", "layerClassname": "Kokan_Valley_RAAS_v2", - "mapClassname": "Kokan_Valley", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v2", @@ -2207,7 +2127,6 @@ "layer": "Kokan Skirmish v1", "map": "Kokan", "layerClassname": "Kokan_Valley_Skirmish_v1", - "mapClassname": "Kokan_Valley", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v1", @@ -2235,7 +2154,6 @@ "layer": "Kokan TC v1", "map": "Kokan", "layerClassname": "Kokan_Valley_TC_v1", - "mapClassname": "Kokan_Valley", "mapSize": "2x2 km", "gamemode": "TC", "version": "v1", @@ -2259,7 +2177,6 @@ "layer": "Logar Valley AAS v1", "map": "Logar Valley", "layerClassname": "LogarValley_AAS_v1", - "mapClassname": "Logar_Valley", "mapSize": "1x1 km", "gamemode": "AAS", "version": "v1", @@ -2287,7 +2204,6 @@ "layer": "Logar Valley AAS v2", "map": "Logar Valley", "layerClassname": "LogarValley_AAS_v2", - "mapClassname": "Logar_Valley", "mapSize": "1x1 km", "gamemode": "AAS", "version": "v2", @@ -2315,7 +2231,6 @@ "layer": "Logar Valley Insurgency v1", "map": "Logar Valley", "layerClassname": "LogarValley_Insurgency_v1", - "mapClassname": "Logar_Valley", "mapSize": "1x1 km", "gamemode": "Insurgency", "version": "v1", @@ -2343,7 +2258,6 @@ "layer": "Logar Valley RAAS v1", "map": "Logar Valley", "layerClassname": "LogarValley_RAAS_v1", - "mapClassname": "Logar_Valley", "mapSize": "1x1 km", "gamemode": "RAAS", "version": "v1", @@ -2371,7 +2285,6 @@ "layer": "Logar Valley Skirmish v1", "map": "Logar Valley", "layerClassname": "LogarValley_Skirmish_v1", - "mapClassname": "Logar_Valley", "mapSize": "1x1 km", "gamemode": "Skirmish", "version": "v1", @@ -2399,7 +2312,6 @@ "layer": "Logar Valley TC v1", "map": "Logar Valley", "layerClassname": "LogarValley_TC_v1", - "mapClassname": "Logar_Valley", "mapSize": "1x1 km", "gamemode": "TC", "version": "v1", @@ -2423,7 +2335,6 @@ "layer": "Mestia AAS v1", "map": "Mestia", "layerClassname": "Mestia_AAS_v1", - "mapClassname": "Mestia", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v1", @@ -2451,7 +2362,6 @@ "layer": "Mestia Invasion v1", "map": "Mestia", "layerClassname": "Mestia_Invasion_v1", - "mapClassname": "Mestia", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v1", @@ -2479,7 +2389,6 @@ "layer": "Mestia Invasion v2", "map": "Mestia", "layerClassname": "Mestia_Invasion_v2", - "mapClassname": "Mestia", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v2", @@ -2507,7 +2416,6 @@ "layer": "Mestia RAAS v1", "map": "Mestia", "layerClassname": "Mestia_RAAS_v1", - "mapClassname": "Mestia", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v1", @@ -2535,7 +2443,6 @@ "layer": "Mestia Skirmish v1", "map": "Mestia", "layerClassname": "Mestia_Skirmish_v1", - "mapClassname": "Mestia", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v1", @@ -2563,7 +2470,6 @@ "layer": "Mestia TC v1", "map": "Mestia", "layerClassname": "Mestia_TC_v1", - "mapClassname": "Mestia", "mapSize": "2x2 km", "gamemode": "TC", "version": "v1", @@ -2587,7 +2493,6 @@ "layer": "Mutaha AAS v1", "map": "Mutaha", "layerClassname": "Mutaha_AAS_v1", - "mapClassname": "Mutaha", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v1", @@ -2615,7 +2520,6 @@ "layer": "Mutaha Invasion v1", "map": "Mutaha", "layerClassname": "Mutaha_Invasion_v1", - "mapClassname": "Mutaha", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v1", @@ -2643,7 +2547,6 @@ "layer": "Mutaha RAAS v1", "map": "Mutaha", "layerClassname": "Mutaha_RAAS_v1", - "mapClassname": "Mutaha", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v1", @@ -2671,7 +2574,6 @@ "layer": "Mutaha Skirmish v1", "map": "Mutaha", "layerClassname": "Mutaha_Skirmish_v1", - "mapClassname": "Mutaha", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v1", @@ -2699,7 +2601,6 @@ "layer": "Mutaha TC v1", "map": "Mutaha", "layerClassname": "Mutaha_TC_v1", - "mapClassname": "Mutaha", "mapSize": "2x2 km", "gamemode": "TC", "version": "v1", @@ -2723,7 +2624,6 @@ "layer": "Mutaha TC v2", "map": "Mutaha", "layerClassname": "Mutaha_TC_v2", - "mapClassname": "Mutaha", "mapSize": "2x2 km", "gamemode": "TC", "version": "v2", @@ -2747,7 +2647,6 @@ "layer": "Narva AAS v1", "map": "Narva", "layerClassname": "Narva_AAS_v1", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v1", @@ -2775,7 +2674,6 @@ "layer": "Narva AAS v2", "map": "Narva", "layerClassname": "Narva_AAS_v2", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v2", @@ -2803,7 +2701,6 @@ "layer": "Narva AAS v3", "map": "Narva", "layerClassname": "Narva_AAS_v3", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "AAS", "version": "v3", @@ -2831,7 +2728,6 @@ "layer": "Narva Destruction v1", "map": "Narva", "layerClassname": "Narva_Destruction_v1", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "Destruction", "version": "v1", @@ -2859,7 +2755,6 @@ "layer": "Narva Invasion v1", "map": "Narva", "layerClassname": "Narva_Invasion_v1", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v1", @@ -2887,7 +2782,6 @@ "layer": "Narva Invasion v2", "map": "Narva", "layerClassname": "Narva_Invasion_v2", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "Invasion", "version": "v2", @@ -2915,7 +2809,6 @@ "layer": "Narva RAAS v1", "map": "Narva", "layerClassname": "Narva_RAAS_v1", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "RAAS", "version": "v1", @@ -2943,7 +2836,6 @@ "layer": "Narva Skirmish v1", "map": "Narva", "layerClassname": "Narva_Skirmish_v1", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "Skirmish", "version": "v1", @@ -2971,7 +2863,6 @@ "layer": "Narva TC v1", "map": "Narva", "layerClassname": "Narva_TC_v1", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "TC", "version": "v1", @@ -2995,7 +2886,6 @@ "layer": "Narva TC v2", "map": "Narva", "layerClassname": "Narva_TC_v2", - "mapClassname": "Narva", "mapSize": "2x2 km", "gamemode": "TC", "version": "v2", @@ -3019,7 +2909,6 @@ "layer": "Skorpo AAS v1", "map": "Skorpo", "layerClassname": "Skorpo_AAS_v1", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "AAS", "version": "v1", @@ -3047,7 +2936,6 @@ "layer": "Skorpo Invasion v1", "map": "Skorpo", "layerClassname": "Skorpo_Invasion_v1", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "Invasion", "version": "v1", @@ -3075,7 +2963,6 @@ "layer": "Skorpo Invasion v2", "map": "Skorpo", "layerClassname": "Skorpo_Invasion_v2", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "Invasion", "version": "v2", @@ -3103,7 +2990,6 @@ "layer": "Skorpo RAAS v1", "map": "Skorpo", "layerClassname": "Skorpo_RAAS_v1", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v1", @@ -3131,7 +3017,6 @@ "layer": "Skorpo RAAS v2", "map": "Skorpo", "layerClassname": "Skorpo_RAAS_v2", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v2", @@ -3159,7 +3044,6 @@ "layer": "Skorpo RAAS v3", "map": "Skorpo", "layerClassname": "Skorpo_RAAS_v3", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v3", @@ -3187,7 +3071,6 @@ "layer": "Skorpo RAAS v4", "map": "Skorpo", "layerClassname": "Skorpo_RAAS_v4", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v4", @@ -3215,7 +3098,6 @@ "layer": "Skorpo Skirmish v1", "map": "Skorpo", "layerClassname": "Skorpo_Skirmish_v1", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "Skirmish", "version": "v1", @@ -3243,7 +3125,6 @@ "layer": "Skorpo TC v1", "map": "Skorpo", "layerClassname": "Skorpo_TC_v1", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "TC", "version": "v1", @@ -3267,7 +3148,6 @@ "layer": "Skorpo TC v2", "map": "Skorpo", "layerClassname": "Skorpo_TC_v2", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "TC", "version": "v2", @@ -3291,7 +3171,6 @@ "layer": "Skorpo TC v3", "map": "Skorpo", "layerClassname": "Skorpo_TC_v3", - "mapClassname": "Skorpo", "mapSize": "5x5 km", "gamemode": "TC", "version": "v3", @@ -3315,7 +3194,6 @@ "layer": "Sumari AAS v1", "map": "Sumari", "layerClassname": "Sumari_AAS_v1", - "mapClassname": "Sumari", "mapSize": "1x1 km", "gamemode": "AAS", "version": "v1", @@ -3343,7 +3221,6 @@ "layer": "Sumari Invasion v1", "map": "Sumari", "layerClassname": "Sumari_Invasion_v1", - "mapClassname": "Sumari", "mapSize": "1x1 km", "gamemode": "Invasion", "version": "v1", @@ -3371,7 +3248,6 @@ "layer": "Sumari Insurgency v1", "map": "Sumari", "layerClassname": "Sumari_Insurgency_v1", - "mapClassname": "Sumari", "mapSize": "1x1 km", "gamemode": "Insurgency", "version": "v1", @@ -3399,7 +3275,6 @@ "layer": "Sumari RAAS v1", "map": "Sumari", "layerClassname": "Sumari_RAAS_v1", - "mapClassname": "Sumari", "mapSize": "1x1 km", "gamemode": "RAAS", "version": "v1", @@ -3427,7 +3302,6 @@ "layer": "Sumari RAAS v2", "map": "Sumari", "layerClassname": "Sumari_RAAS_v2", - "mapClassname": "Sumari", "mapSize": "1x1 km", "gamemode": "RAAS", "version": "v2", @@ -3455,7 +3329,6 @@ "layer": "Sumari Skirmish v1", "map": "Sumari", "layerClassname": "Sumari_Skirmish_v1", - "mapClassname": "Sumari", "mapSize": "1x1 km", "gamemode": "Skirmish", "version": "v1", @@ -3483,7 +3356,6 @@ "layer": "Sumari TC v1", "map": "Sumari", "layerClassname": "Sumari_TC_v1", - "mapClassname": "Sumari", "mapSize": "1x1 km", "gamemode": "TC", "version": "v1", @@ -3507,7 +3379,6 @@ "layer": "Tallil Outskirts AAS v1", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_AAS_v1", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "AAS", "version": "v1", @@ -3535,7 +3406,6 @@ "layer": "Tallil Outskirts Invasion v1", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Invasion_v1", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v1", @@ -3563,7 +3433,6 @@ "layer": "Tallil Outskirts Invasion v2", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Invasion_v2", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v2", @@ -3591,7 +3460,6 @@ "layer": "Tallil Outskirts Invasion v3", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Invasion_v3", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Invasion", "version": "v3", @@ -3619,7 +3487,6 @@ "layer": "Tallil Outskirts RAAS v1", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_RAAS_v1", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v1", @@ -3647,7 +3514,6 @@ "layer": "Tallil Outskirts RAAS v2", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_RAAS_v2", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v2", @@ -3675,7 +3541,6 @@ "layer": "Tallil Outskirts RAAS v3", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_RAAS_v3", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v3", @@ -3703,7 +3568,6 @@ "layer": "Tallil Outskirts RAAS v4", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_RAAS_v4", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "RAAS", "version": "v4", @@ -3731,7 +3595,6 @@ "layer": "Tallil Outskirts Skirmish v1", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Skirmish_v1", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Skirmish", "version": "v1", @@ -3759,7 +3622,6 @@ "layer": "Tallil Outskirts Skirmish v2", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Skirmish_v2", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Skirmish", "version": "v2", @@ -3787,7 +3649,6 @@ "layer": "Tallil Outskirts Skirmish v3", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Skirmish_v3", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Skirmish", "version": "v3", @@ -3815,7 +3676,6 @@ "layer": "Tallil Outskirts Tanks v1", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Tanks_v1", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Tanks", "version": "v1", @@ -3839,7 +3699,6 @@ "layer": "Tallil Outskirts Tanks v2", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_Tanks_v2", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "Tanks", "version": "v2", @@ -3863,7 +3722,6 @@ "layer": "Tallil Outskirts TC v1", "map": "Tallil Outskirts", "layerClassname": "Tallil_Outskirts_TC_v1", - "mapClassname": "Tallil_Outskirts", "mapSize": "4x4 km", "gamemode": "TC", "version": "v1", @@ -3887,7 +3745,6 @@ "layer": "Tutorials Infantry Training Tutorial", "map": "Tutorials", "layerClassname": "Tutorials_Infantry_Training_Tutorial", - "mapClassname": "Tutorials", "mapSize": "", "gamemode": "Infantry", "version": "Training", @@ -3911,7 +3768,6 @@ "layer": "Tutorials Helicopter Training Tutorial", "map": "Tutorials", "layerClassname": "Tutorials_Helicopter_Training_Tutorial", - "mapClassname": "Tutorials", "mapSize": "", "gamemode": "Helicopter", "version": "Training", @@ -3935,7 +3791,6 @@ "layer": "Yehorivka AAS v1", "map": "Yehorivka", "layerClassname": "Yehorivka_AAS_v1", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "AAS", "version": "v1", @@ -3963,7 +3818,6 @@ "layer": "Yehorivka AAS v2", "map": "Yehorivka", "layerClassname": "Yehorivka_AAS_v2", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "AAS", "version": "v2", @@ -3991,7 +3845,6 @@ "layer": "Yehorivka Destruction v1", "map": "Yehorivka", "layerClassname": "Yehorivka_Destruction_v1", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "Destruction", "version": "v1", @@ -4019,7 +3872,6 @@ "layer": "Yehorivka Invasion v1", "map": "Yehorivka", "layerClassname": "Yehorivka_Invasion_v1", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "Invasion", "version": "v1", @@ -4047,7 +3899,6 @@ "layer": "Yehorivka Invasion v2", "map": "Yehorivka", "layerClassname": "Yehorivka_Invasion_v2", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "Invasion", "version": "v2", @@ -4075,7 +3926,6 @@ "layer": "Yehorivka RAAS v1", "map": "Yehorivka", "layerClassname": "Yehorivka_RAAS_v1", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v1", @@ -4103,7 +3953,6 @@ "layer": "Yehorivka RAAS v2", "map": "Yehorivka", "layerClassname": "Yehorivka_RAAS_v2", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v2", @@ -4131,7 +3980,6 @@ "layer": "Yehorivka RAAS v3", "map": "Yehorivka", "layerClassname": "Yehorivka_RAAS_v3", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v3", @@ -4159,7 +4007,6 @@ "layer": "Yehorivka RAAS v4", "map": "Yehorivka", "layerClassname": "Yehorivka_RAAS_v4", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v4", @@ -4187,7 +4034,6 @@ "layer": "Yehorivka RAAS v5", "map": "Yehorivka", "layerClassname": "Yehorivka_RAAS_v5", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "RAAS", "version": "v5", @@ -4215,7 +4061,6 @@ "layer": "Yehorivka Skirmish v1", "map": "Yehorivka", "layerClassname": "Yehorivka_Skirmish_v1", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "Skirmish", "version": "v1", @@ -4243,7 +4088,6 @@ "layer": "Yehorivka Skirmish v2", "map": "Yehorivka", "layerClassname": "Yehorivka_Skirmish_v2", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "Skirmish", "version": "v2", @@ -4271,7 +4115,6 @@ "layer": "Yehorivka Skirmish v3", "map": "Yehorivka", "layerClassname": "Yehorivka_Skirmish_v3", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "Skirmish", "version": "v3", @@ -4299,7 +4142,6 @@ "layer": "Yehorivka TC v1", "map": "Yehorivka", "layerClassname": "Yehorivka_TC_v1", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "TC", "version": "v1", @@ -4323,7 +4165,6 @@ "layer": "Yehorivka TC v2", "map": "Yehorivka", "layerClassname": "Yehorivka_TC_v2", - "mapClassname": "Yehorivka", "mapSize": "5x5 km", "gamemode": "TC", "version": "v2", @@ -4344,10 +4185,9 @@ "newForVersion": false }, { - "layer": "CAF_Al_Basrah_Invasion_v1", - "map": "CAF_Al_Basrah", - "layerClassname": "CAF_Al_Basrah_Invasion_v1", - "mapClassname": "CAF_Al_Basrah", + "layer": "CAF_Albasrah_Invasion_v1", + "map": "CAF Al Basrah", + "layerClassname": "CAF_Albasrah_Invasion_v1", "gamemode": "Invasion", "version": "v1", "dlc": "CAF", @@ -4373,9 +4213,8 @@ }, { "layer": "CAF_Gorodok_Invasion_v1", - "map": "CAF_Gorodok", + "map": "CAF Gorodok", "layerClassname": "CAF_Gorodok_Invasion_v1", - "mapClassname": "CAF_Gorodok", "gamemode": "Invasion", "version": "v1", "dlc": "CAF", @@ -4401,9 +4240,8 @@ }, { "layer": "CAF_Gorodok_RAAS_v1", - "map": "CAF_Gorodok", + "map": "CAF Gorodok", "layerClassname": "CAF_Gorodok_RAAS_v1", - "mapClassname": "CAF_Gorodok", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -4429,9 +4267,8 @@ }, { "layer": "CAF_Gorodok_RAAS_v2", - "map": "CAF_Gorodok", + "map": "CAF Gorodok", "layerClassname": "CAF_Gorodok_RAAS_v2", - "mapClassname": "CAF_Gorodok", "gamemode": "RAAS", "version": "v2", "dlc": "CAF", @@ -4457,9 +4294,8 @@ }, { "layer": "CAF_Gorodok_TC_v1", - "map": "CAF_Gorodok", + "map": "CAF Gorodok", "layerClassname": "CAF_Gorodok_TC_v1", - "mapClassname": "CAF_Gorodok", "gamemode": "TC", "version": "v1", "dlc": "CAF", @@ -4481,9 +4317,8 @@ }, { "layer": "CAF_Jensens_Range_v4", - "map": "CAF_Jensens_Range", + "map": "CAF Jensen's Range", "layerClassname": "CAF_Jensens_Range_v4", - "mapClassname": "CAF_Jensens_Range", "gamemode": "Training", "version": "v4", "dlc": "CAF", @@ -4509,9 +4344,8 @@ }, { "layer": "CAF_Kamdesh_Invasion_v1", - "map": "CAF_Kamdesh", + "map": "CAF Kamdesh", "layerClassname": "CAF_Kamdesh_Invasion_v1", - "mapClassname": "CAF_Kamdesh", "gamemode": "Invasion", "version": "v1", "dlc": "CAF", @@ -4537,9 +4371,8 @@ }, { "layer": "CAF_Kamdesh_RAAS_v1", - "map": "CAF_Kamdesh", + "map": "CAF Kamdesh", "layerClassname": "CAF_Kamdesh_RAAS_v1", - "mapClassname": "CAF_Kamdesh", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -4565,9 +4398,8 @@ }, { "layer": "CAF_Kamdesh_TC_v1", - "map": "CAF_Kamdesh", + "map": "CAF Kamdesh", "layerClassname": "CAF_Kamdesh_TC_v1", - "mapClassname": "CAF_Kamdesh", "gamemode": "TC", "version": "v1", "dlc": "CAF", @@ -4589,9 +4421,8 @@ }, { "layer": "CAF_Kohat_Invasion_v1", - "map": "CAF_Kohat", + "map": "CAF Kohat", "layerClassname": "CAF_Kohat_Invasion_v1", - "mapClassname": "CAF_Kohat", "gamemode": "Invasion", "version": "v1", "dlc": "CAF", @@ -4617,9 +4448,8 @@ }, { "layer": "CAF_Manic_AAS_v1", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_AAS_v1", - "mapClassname": "CAF_Manic", "gamemode": "AAS", "version": "v1", "dlc": "CAF", @@ -4645,9 +4475,8 @@ }, { "layer": "CAF_Manic_Invasion_v1", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_Invasion_v1", - "mapClassname": "CAF_Manic", "gamemode": "Invasion", "version": "v1", "dlc": "CAF", @@ -4673,9 +4502,8 @@ }, { "layer": "CAF_Manic_Invasion_v2", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_Invasion_v2", - "mapClassname": "CAF_Manic", "gamemode": "Invasion", "version": "v2", "dlc": "CAF", @@ -4701,9 +4529,8 @@ }, { "layer": "CAF_Manic_RAAS_v1", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_RAAS_v1", - "mapClassname": "CAF_Manic", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -4729,9 +4556,8 @@ }, { "layer": "CAF_Manic_RAAS_v2", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_RAAS_v2", - "mapClassname": "CAF_Manic", "gamemode": "RAAS", "version": "v2", "dlc": "CAF", @@ -4757,9 +4583,8 @@ }, { "layer": "CAF_Manic_RAAS_v3", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_RAAS_v3", - "mapClassname": "CAF_Manic", "gamemode": "RAAS", "version": "v3", "dlc": "CAF", @@ -4785,9 +4610,8 @@ }, { "layer": "CAF_Manic_RAAS_v4", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_RAAS_v4", - "mapClassname": "CAF_Manic", "gamemode": "RAAS", "version": "v4", "dlc": "CAF", @@ -4813,9 +4637,8 @@ }, { "layer": "CAF_Manic_Skirmish_v1", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_Skirmish_v1", - "mapClassname": "CAF_Manic", "gamemode": "Skirmish", "version": "v1", "dlc": "CAF", @@ -4841,9 +4664,8 @@ }, { "layer": "CAF_Manic_Skirmish_v2", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_Skirmish_v2", - "mapClassname": "CAF_Manic", "gamemode": "Skirmish", "version": "v2", "dlc": "CAF", @@ -4869,9 +4691,8 @@ }, { "layer": "CAF_Manic_TC_v1", - "map": "CAF_Manic", + "map": "CAF Manic-5", "layerClassname": "CAF_Manic_TC_v1", - "mapClassname": "CAF_Manic", "gamemode": "TC", "version": "v1", "dlc": "CAF", @@ -4893,9 +4714,8 @@ }, { "layer": "CAF_Mestia_RAAS_v1", - "map": "CAF_Mestia", + "map": "CAF Mestia", "layerClassname": "CAF_Mestia_RAAS_v1", - "mapClassname": "CAF_Mestia", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -4921,9 +4741,8 @@ }, { "layer": "CAF_Mutaha_RAAS_v1", - "map": "CAF_Mutaha", + "map": "CAF Mutaha", "layerClassname": "CAF_Mutaha_RAAS_v1", - "mapClassname": "CAF_Mutaha", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -4949,9 +4768,8 @@ }, { "layer": "CAF_Nanisivik_Invasion_v1", - "map": "CAF_Nanisivik", + "map": "CAF Nanisivik", "layerClassname": "CAF_Nanisivik_Invasion_v1", - "mapClassname": "CAF_Nanisivik", "gamemode": "Invasion", "version": "v1", "dlc": "CAF", @@ -4977,9 +4795,8 @@ }, { "layer": "CAF_Nanisivik_RAAS_v1", - "map": "CAF_Nanisivik", + "map": "CAF Nanisivik", "layerClassname": "CAF_Nanisivik_RAAS_v1", - "mapClassname": "CAF_Nanisivik", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -5005,9 +4822,8 @@ }, { "layer": "CAF_Narva_RAAS_v1", - "map": "CAF_Narva", + "map": "CAF Narva", "layerClassname": "CAF_Narva_RAAS_v1", - "mapClassname": "CAF_Narva", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -5033,9 +4849,8 @@ }, { "layer": "CAF_Tallil_Outskirts_RAAS_v1", - "map": "CAF_Tallil_Outskirts", + "map": "CAF Tallil Outskirts", "layerClassname": "CAF_Tallil_Outskirts_RAAS_v1", - "mapClassname": "CAF_Tallil_Outskirts", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -5061,9 +4876,8 @@ }, { "layer": "CAF_Yehorivka_Invasion_v1", - "map": "CAF_Yehorivka", + "map": "CAF Yehorivka", "layerClassname": "CAF_Yehorivka_Invasion_v1", - "mapClassname": "CAF_Yehorivka", "gamemode": "Invasion", "version": "v1", "dlc": "CAF", @@ -5089,9 +4903,8 @@ }, { "layer": "CAF_Yehorivka_RAAS_v1", - "map": "CAF_Yehorivka", + "map": "CAF Yehorivka", "layerClassname": "CAF_Yehorivka_RAAS_v1", - "mapClassname": "CAF_Yehorivka", "gamemode": "RAAS", "version": "v1", "dlc": "CAF", @@ -5117,9 +4930,8 @@ }, { "layer": "CAF_Yehorivka_TC_v1", - "map": "CAF_Yehorivka", + "map": "CAF Yehorivka", "layerClassname": "CAF_Yehorivka_TC_v1", - "mapClassname": "CAF_Yehorivka", "gamemode": "TC", "version": "v1", "dlc": "CAF", diff --git a/connectors/package.json b/connectors/package.json index d866214..d8c238c 100644 --- a/connectors/package.json +++ b/connectors/package.json @@ -3,10 +3,12 @@ "version": "1.0.0", "type": "module", "exports": { - "./squad-layers": "./squad-layers/index.js", - "./scbl": "./scbl.js" + "./scbl": "./scbl.js", + "./squad-layer-filter": "./squad-layer-filter.js", + "./squad-layers": "./squad-layers.js" }, "dependencies": { + "didyoumean": "^1.2.1", "graphql-request": "^1.8.2" } } diff --git a/connectors/squad-layer-filter.js b/connectors/squad-layer-filter.js new file mode 100644 index 0000000..4bc771d --- /dev/null +++ b/connectors/squad-layer-filter.js @@ -0,0 +1,219 @@ +import fs from 'fs'; +import SquadLayers, { + SquadLayers as SquadLayersClass +} from './squad-layers.js'; + +export default class SquadLayerFilter extends SquadLayersClass { + constructor(layers, activeLayerFilter = null) { + super(layers); + + if (activeLayerFilter === null) { + this.activeLayerFilter = null; + } else { + this.activeLayerFilter = { + historyResetTime: 5 * 60 * 60 * 1000, + layerHistoryTolerance: 8, + mapHistoryTolerance: 4, + gamemodeHistoryTolerance: { + // defaults as off + ...activeLayerFilter.gamemodeHistoryTolerance + }, + playerCountComplianceEnabled: true, + ...activeLayerFilter + }; + } + } + + static buildFromList(layerNames, activeLayerFilter) { + return new SquadLayerFilter(layerNames, activeLayerFilter); + } + + static buildFromDidYouMeanList(layerNames, activeLayerFilter) { + const layers = []; + for (const layerName of layerNames) { + const layer = SquadLayers.getLayerByDidYouMean( + layerName, + SquadLayers.getLayerNames() + ); + if (layer) layers.push(layer); + } + return new SquadLayerFilter(layers, activeLayerFilter); + } + + static buildFromFile(filename, activeLayerFilter, delimiter = '\n') { + const lines = fs + .readFileSync('./connectors/data/layers.json', 'utf8') + .split(delimiter); + const layers = []; + + const validLayerNames = SquadLayers.getLayerNames(); + + for (const line of lines) { + if (validLayerNames.contains(line)) + layers.push(SquadLayers.getLayerByLayerName(line)); + } + return new SquadLayerFilter(layers, activeLayerFilter); + } + + static buildFromFilter(filter = {}, activeLayerFilter) { + const whitelistedLayers = filter.whitelistedLayers || null; + const blacklistedLayers = filter.blacklistedLayers || null; + const whitelistedMaps = filter.whitelistedMaps || null; + const blacklistedMaps = filter.blacklistedMaps || null; + const whitelistedGamemodes = filter.whitelistedGamemodes || null; + const blacklistedGamemodes = filter.blacklistedGamemodes || ['Training']; + const flagCountMin = filter.flagCountMin || null; + const flagCountMax = filter.flagCountMax || null; + const hasCommander = filter.hasCommander || null; + const hasTanks = filter.hasTanks || null; + const hasHelicopters = filter.hasHelicopters || null; + + const layers = []; + + for (const layer of SquadLayers.getLayers()) { + // Whitelist / Blacklist Layers + if ( + whitelistedLayers !== null && + !whitelistedLayers.includes(layer.layer) + ) + continue; + if (blacklistedLayers !== null && blacklistedLayers.includes(layer.layer)) + continue; + + // Whitelist / Blacklist Maps + if (whitelistedMaps !== null && !whitelistedMaps.includes(layer.map)) + continue; + if (blacklistedMaps !== null && blacklistedMaps.includes(layer.map)) + continue; + + // Whitelist / Blacklist Gamemodes + if ( + whitelistedGamemodes !== null && + !whitelistedGamemodes.includes(layer.gamemode) + ) + continue; + if ( + blacklistedGamemodes !== null && + blacklistedGamemodes.includes(layer.gamemode) + ) + continue; + + // Flag Count + if (flagCountMin !== null && layer.flagCount < flagCountMin) continue; + if (flagCountMax !== null && layer.flagCount > flagCountMax) continue; + + // Other Properties + if (hasCommander !== null && layer.commander !== hasCommander) continue; + if (hasTanks !== null && (layer.tanks !== 'N/A') !== hasTanks) continue; + if ( + hasHelicopters !== null && + (layer.helicopters !== 'N/A') !== hasHelicopters + ) + continue; + + layers.push(layer); + } + + return new SquadLayerFilter(layers, activeLayerFilter); + } + + inLayerPool(layer) { + if (typeof layer === 'object') layer = layer.layer; + return super.getLayerNames().includes(layer); + } + + isLayerHistoryCompliant(server, layer) { + if (this.activeLayerFilter === null) return true; + + if (typeof layer === 'object') layer = layer.layer; + + for ( + let i = 0; + i < + Math.min( + server.layerHistory.length, + this.activeLayerFilter.layerHistoryTolerance + ); + i++ + ) { + if ( + new Date() - server.layerHistory[i].time > + this.activeLayerFilter.historyResetTime + ) + return true; + if (server.layerHistory[i].layer === layer) return false; + } + return true; + } + + isMapHistoryCompliant(server, layer) { + if (this.activeLayerFilter === null) return true; + + if (typeof layer === 'string') + layer = SquadLayers.getLayerByLayerName(layer); + + for ( + let i = 0; + i < + Math.min( + server.layerHistory.length, + this.activeLayerFilter.mapHistoryTolerance + ); + i++ + ) { + if ( + new Date() - server.layerHistory[i].time > + this.activeLayerFilter.historyResetTime + ) + return true; + if (server.layerHistory[i].map === layer.map) return false; + } + return true; + } + + isGamemodeHistoryCompliant(server, layer) { + if (this.activeLayerFilter === null) return true; + + if (typeof layer === 'string') + layer = SquadLayers.getLayerByLayerName(layer); + + const gamemodeHistoryTolerance = this.activeLayerFilter + .gamemodeHistoryTolerance[layer.gamemode]; + if (!gamemodeHistoryTolerance) return true; + + for ( + let i = 0; + i < Math.min(server.layerHistory.length, gamemodeHistoryTolerance); + i++ + ) { + if ( + new Date() - server.layerHistory[i].time > + this.activeLayerFilter.historyResetTime + ) + return true; + + const historyLayer = SquadLayers.getLayerByLayerName( + server.layerHistory[i].layer + ); + if (historyLayer && historyLayer.gamemode === layer.gamemode) + return false; + } + return true; + } + + isPlayerCountCompliant(server, layer) { + if ( + this.activeLayerFilter === null || + this.playerCountComplianceEnabled === false + ) + return true; + + if (typeof layer === 'string') + layer = SquadLayers.getLayerByLayerName(layer); + + return !( + server.players.length > layer.estimatedSuitablePlayerCount.max || + server.players.length < layer.estimatedSuitablePlayerCount.min + ); + } +} diff --git a/connectors/squad-layers.js b/connectors/squad-layers.js new file mode 100644 index 0000000..ab95584 --- /dev/null +++ b/connectors/squad-layers.js @@ -0,0 +1,57 @@ +import fs from 'fs'; + +import didYouMean from 'didyoumean'; + +class SquadLayers { + constructor(layers) { + if (Array.isArray(layers)) { + this.layers = layers; + } else { + this.layers = JSON.parse( + fs.readFileSync('./connectors/data/layers.json', 'utf8') + ); + } + + for (let i = 0; i < this.layers.length; i++) { + this.layers[i] = { + ...this.layers[i], + layerNumber: i + 1 + }; + } + } + + getLayers() { + return this.layers; + } + + getLayerNames() { + return this.layers.map(layer => layer.layer); + } + + getLayerByLayerName(layerName) { + const layer = this.layers.filter(layer => layer.layer === layerName); + return layer.length === 1 ? layer[0] : null; + } + + getLayerByLayerClassname(layerClassname) { + const layer = this.layers.filter( + layer => layer.layerClassname === layerClassname + ); + return layer.length === 1 ? layer[0] : null; + } + + getLayerByDidYouMean(layerName) { + layerName = didYouMean(layerName, this.getLayerNames()); + + const layer = this.layers.filter(layer => layer.layer === layerName); + return layer.length === 1 ? layer[0] : null; + } + + getLayerByNumber(number) { + const layer = this.layers.filter(layer => layer.layerNumber === number); + return layer.length === 1 ? layer[0] : null; + } +} + +export { SquadLayers }; +export default new SquadLayers(); diff --git a/connectors/squad-layers/index.js b/connectors/squad-layers/index.js deleted file mode 100644 index eb1e5ce..0000000 --- a/connectors/squad-layers/index.js +++ /dev/null @@ -1,117 +0,0 @@ -import fs from 'fs'; - -class SquadLayers { - constructor() { - this.layers = JSON.parse( - fs.readFileSync('./connectors/squad-layers/layers.json', 'utf8') - ); - } - - getLayerByLayerName(layerName) { - const layer = this.layers.filter(layer => layer.layer === layerName); - return layer.length === 1 ? layer[0] : null; - } - - getLayerByLayerClassname(layerClassname) { - const layer = this.layers.filter( - layer => layer.layerClassname === layerClassname - ); - return layer.length === 1 ? layer[0] : null; - } - - getLayerNames() { - return this.layers.map(layer => layer.layer); - } - - getFilteredLayers(filter = {}) { - const whitelistedLayers = filter.whitelistedLayers || null; - const blacklistedLayers = filter.blacklistedLayers || null; - const whitelistedMaps = filter.whitelistedMaps || null; - const blacklistedMaps = filter.blacklistedMaps || null; - const whitelistedGamemodes = filter.whitelistedGamemodes || null; - const blacklistedGamemodes = filter.blacklistedGamemodes || ['Training']; - const flagCountMin = filter.flagCountMin || null; - const flagCountMax = filter.flagCountMax || null; - const hasCommander = filter.hasCommander || null; - const hasTanks = filter.hasTanks || null; - const hasHelicopters = filter.hasHelicopters || null; - - const layers = []; - - for (const layer of this.layers) { - // Whitelist / Blacklist Layers - if ( - whitelistedLayers !== null && - !whitelistedLayers.includes(layer.layer) - ) - continue; - if (blacklistedLayers !== null && blacklistedLayers.includes(layer.layer)) - continue; - - // Whitelist / Blacklist Maps - if (whitelistedMaps !== null && !whitelistedMaps.includes(layer.map)) - continue; - if (blacklistedMaps !== null && blacklistedMaps.includes(layer.map)) - continue; - - // Whitelist / Blacklist Gamemodes - if ( - whitelistedGamemodes !== null && - !whitelistedGamemodes.includes(layer.gamemode) - ) - continue; - if ( - blacklistedGamemodes !== null && - blacklistedGamemodes.includes(layer.gamemode) - ) - continue; - - // Flag Count - if (flagCountMin !== null && layer.flagCount < flagCountMin) continue; - if (flagCountMax !== null && layer.flagCount > flagCountMax) continue; - - // Other Properties - if (hasCommander !== null && layer.commander !== hasCommander) continue; - if (hasTanks !== null && (layer.tanks !== 'N/A') !== hasTanks) continue; - if ( - hasHelicopters !== null && - (layer.helicopters !== 'N/A') !== hasHelicopters - ) - continue; - - layers.push(layer.layer); - } - - return layers; - } - - isHistoryCompliant(layerHistory, layer, options = {}) { - const layerTolerance = options.layerTolerance || 4; - const mapTolerance = options.mapTolerance || 2; - const timeTolerance = options.timeTolerance || 5 * 60 * 60 * 1000; - - for (let i = 0; i < layerHistory.length; i++) { - if (i >= Math.max(layerHistory, mapTolerance)) return true; - if (new Date() - layerHistory[i].time > timeTolerance) return true; - - if ( - i < layerTolerance && - layerHistory[i].map === this.getLayerByLayerName(layer).map - ) - return false; - if (i < layerTolerance && layerHistory[i].layer === layer) return false; - } - return true; - } - - isPlayerCountCompliant(playerCount, layer) { - return !( - playerCount > - this.getLayerByLayerName(layer).estimatedSuitablePlayerCount.max || - playerCount < - this.getLayerByLayerName(layer).estimatedSuitablePlayerCount.min - ); - } -} - -export default new SquadLayers(); diff --git a/index.js b/index.js index 5f3d654..4f28af5 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ import mysql from 'mysql'; import Influx from 'influx'; import Server from 'squad-server'; +import SquadLayerFilter from 'connectors/squad-layer-filter'; import { discordAdminCamLogs, @@ -36,7 +37,9 @@ async function main() { await discordTeamkill(server, discordClient, 'discordChannelID'); // in game features - mapvote(server); + const squadLayerFilter = SquadLayerFilter.buildFromFilter({}); + mapvote(server, 'didyoumean', squadLayerFilter, {}); + teamRandomizer(server); // MySQL Plugins diff --git a/package.json b/package.json index 2f952dd..b4df1fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "SquadJS", - "version": "1.0.9", + "version": "1.0.10", "repository": "https://github.com/Thomas-Smyth/SquadJS.git", "author": "Thomas Smyth ", "license": "MIT", @@ -18,6 +18,7 @@ }, "type": "module", "dependencies": { + "connectors": "1.0.0", "discord.js": "^12.2.0", "influx": "^5.5.1", "mysql": "^2.18.1", diff --git a/plugins/auto-tk-warn/index.js b/plugins/auto-tk-warn/index.js index 54fa8ab..9896b0e 100644 --- a/plugins/auto-tk-warn/index.js +++ b/plugins/auto-tk-warn/index.js @@ -1,6 +1,6 @@ import { LOG_PARSER_TEAMKILL } from 'squad-server/events/log-parser'; -export default async function(server, options= {}) { +export default async function(server, options = {}) { if (!server) throw new Error( 'DiscordAdminCamLogs must be provided with a reference to the server.' @@ -8,8 +8,10 @@ export default async function(server, options= {}) { server.on(LOG_PARSER_TEAMKILL, info => { // ignore suicides - if(info.attacker.steamID === info.victim.steamID) return; - server.rcon.execute(`AdminWarn "${info.attacker.steamID}" ${options.message || 'Please apologise for ALL TKs in ALL chat!'}`); + if (info.attacker.steamID === info.victim.steamID) return; + server.rcon.execute( + `AdminWarn "${info.attacker.steamID}" ${options.message || + 'Please apologise for ALL TKs in ALL chat!'}` + ); }); - } diff --git a/plugins/discord-admin-cam-logs/index.js b/plugins/discord-admin-cam-logs/index.js index 2128d02..04a1f6b 100644 --- a/plugins/discord-admin-cam-logs/index.js +++ b/plugins/discord-admin-cam-logs/index.js @@ -1,12 +1,10 @@ import { COPYRIGHT_MESSAGE } from 'core/config'; -import { LOG_PARSER_PLAYER_POSSESS, LOG_PARSER_PLAYER_UNPOSSESS } from 'squad-server/events/log-parser'; +import { + LOG_PARSER_PLAYER_POSSESS, + LOG_PARSER_PLAYER_UNPOSSESS +} from 'squad-server/events/log-parser'; -export default async function( - server, - discordClient, - channelID, - options = {} -) { +export default async function(server, discordClient, channelID, options = {}) { if (!server) throw new Error( 'DiscordAdminCamLogs must be provided with a reference to the server.' @@ -59,7 +57,8 @@ export default async function( }); server.on(LOG_PARSER_PLAYER_UNPOSSESS, info => { - if (info.switchPossess === true || !(info.player.steamID in adminsInCam)) return; + if (info.switchPossess === true || !(info.player.steamID in adminsInCam)) + return; channel.send({ embed: { @@ -81,7 +80,7 @@ export default async function( value: `${Math.round( (info.time.getTime() - adminsInCam[info.player.steamID].getTime()) / - 60000 + 60000 )} mins` } ], diff --git a/plugins/discord-chat/index.js b/plugins/discord-chat/index.js index 6dada71..e519edd 100644 --- a/plugins/discord-chat/index.js +++ b/plugins/discord-chat/index.js @@ -1,12 +1,7 @@ import { COPYRIGHT_MESSAGE } from 'core/config'; import { RCON_CHAT_MESSAGE } from 'squad-server/events/rcon'; -export default async function( - server, - discordClient, - channelID, - options = {} -) { +export default async function(server, discordClient, channelID, options = {}) { if (!server) throw new Error( 'DiscordChat must be provided with a reference to the server.' diff --git a/plugins/discord-debug/index.js b/plugins/discord-debug/index.js index 54d45f6..0269da3 100644 --- a/plugins/discord-debug/index.js +++ b/plugins/discord-debug/index.js @@ -1,9 +1,4 @@ -export default async function( - server, - discordClient, - channelID, - events = [] -) { +export default async function(server, discordClient, channelID, events = []) { if (!server) throw new Error( 'DiscordDebug must be provided with a reference to the server.' diff --git a/plugins/discord-server-status/README.md b/plugins/discord-server-status/README.md index b614425..0c4436e 100644 --- a/plugins/discord-server-status/README.md +++ b/plugins/discord-server-status/README.md @@ -24,6 +24,8 @@ await discordServerStatus( discordClient, { // options - the options included below display the defaults and can be removed for simplicity. color: 16761867, // color of embed + colorGradient: true, // gradient color based on player count + connectLink: true, // show Steam connect link command: '!server', // command used to send message disableStatus: false // disable bot status as server status } diff --git a/plugins/discord-server-status/index.js b/plugins/discord-server-status/index.js index b90c65b..e156a3f 100644 --- a/plugins/discord-server-status/index.js +++ b/plugins/discord-server-status/index.js @@ -1,7 +1,14 @@ -import { COPYRIGHT_MESSAGE } from 'core/config'; +import tinygradient from 'tinygradient'; +import { COPYRIGHT_MESSAGE } from 'core/config'; import { SERVER_A2S_UPDATED } from 'squad-server/events/server'; +const gradient = tinygradient([ + { color: '#ff0000', pos: 0 }, + { color: '#ffff00', pos: 0.5 }, + { color: '#00ff00', pos: 1 } +]); + function makeEmbed(server, options) { let players = `${server.playerCount}`; if (server.publicQueue + server.reserveQueue > 0) @@ -9,26 +16,39 @@ function makeEmbed(server, options) { players += ` / ${server.publicSlots}`; if (server.reserveSlots > 0) players += ` (+${server.reserveSlots})`; + const fields = [ + { + name: 'Players', + value: `\`\`\`${players}\`\`\`` + }, + { + name: 'Current Layer', + value: `\`\`\`${server.currentLayer}\`\`\``, + inline: true + }, + { + name: 'Next Layer', + value: `\`\`\`${server.nextLayer || 'Unknown'}\`\`\``, + inline: true + } + ]; + + if (options.connectLink) + fields.push({ + name: 'Join Server', + value: `steam://connect/${server.host}:${server.queryPort}` + }); + return { embed: { title: server.serverName, - color: options.color, - fields: [ - { - name: 'Players', - value: `\`\`\`${players}\`\`\`` - }, - { - name: 'Current Layer', - value: `\`\`\`${server.currentLayer}\`\`\``, - inline: true - }, - { - name: 'Next Layer', - value: `\`\`\`${server.nextLayer || 'Unknown'}\`\`\``, - inline: true - } - ], + color: options.colorGradient + ? parseInt( + gradient.rgbAt(server.playerCount / server.publicSlots).toHex(), + 16 + ) + : options.color, + fields: fields, timestamp: new Date().toISOString(), footer: { text: `Server Status by ${COPYRIGHT_MESSAGE}` @@ -48,6 +68,8 @@ export default async function(server, discordClient, options = {}) { options = { color: 16761867, + colorGradient: true, + connectLink: true, command: '!server', disableStatus: false, ...options @@ -82,6 +104,10 @@ export default async function(server, discordClient, options = {}) { }); server.on(SERVER_A2S_UPDATED, () => { - if(!options.disableStatus) discordClient.user.setActivity(`(${server.playerCount}/${server.publicSlots}) ${server.currentLayer}`, { type: 'WATCHING' }); + if (!options.disableStatus) + discordClient.user.setActivity( + `(${server.playerCount}/${server.publicSlots}) ${server.currentLayer}`, + { type: 'WATCHING' } + ); }); } diff --git a/plugins/discord-teamkill/README.md b/plugins/discord-teamkill/README.md index 1dd874a..0e7512a 100644 --- a/plugins/discord-teamkill/README.md +++ b/plugins/discord-teamkill/README.md @@ -24,7 +24,9 @@ await discordTeamkill( discordClient, 'discordChannelID', { // options - the options included below display the defaults and can be removed for simplicity. - color: 16761867 // color of embed + teamkillColor: 16761867, // colour of TK embed + suicideColor: 16761867, // colour of suicide embed + ignoreSuicides: false, // ignore suicide events } ); ``` diff --git a/plugins/discord-teamkill/index.js b/plugins/discord-teamkill/index.js index 7f8390c..c5d58c2 100644 --- a/plugins/discord-teamkill/index.js +++ b/plugins/discord-teamkill/index.js @@ -1,12 +1,7 @@ import { COPYRIGHT_MESSAGE } from 'core/config'; import { LOG_PARSER_TEAMKILL } from 'squad-server/events/log-parser'; -export default async function( - server, - discordClient, - channelID, - options = {} -) { +export default async function(server, discordClient, channelID, options = {}) { if (!server) throw new Error( 'DiscordTeamKill must be provided with a reference to the server.' @@ -21,7 +16,9 @@ export default async function( throw new Error('DiscordTeamkill must be provided with a channel ID.'); options = { - color: 16761867, + teamkillColor: 16761867, + suicideColor: 16761867, + ignoreSuicides: false, disableSCBL: false, ...options }; @@ -30,6 +27,7 @@ export default async function( server.on(LOG_PARSER_TEAMKILL, info => { if (!info.attacker) return; + if (options.ignoreSuicides && info.suicide) return; const fields = [ { @@ -58,15 +56,18 @@ export default async function( } ]; - if(!options.disableSCBL) fields.push({ - name: 'Squad Community Ban List', - value: `[Attacker's Bans](https://squad-community-ban-list.com/search/${info.attacker.steamID})\n[Victims's Bans](https://squad-community-ban-list.com/search/${info.victim.steamID})` - }); + if (!options.disableSCBL) + fields.push({ + name: 'Squad Community Ban List', + value: `[Attacker's Bans](https://squad-community-ban-list.com/search/${info.attacker.steamID})\n[Victims's Bans](https://squad-community-ban-list.com/search/${info.victim.steamID})` + }); channel.send({ embed: { - title: `${info.attacker.steamID === info.victim.steamID ? 'Suicide' : 'Teamkill'}: ${info.attacker.name}`, - color: options.color, + title: `${info.suicide ? 'Suicide' : 'Teamkill'}: ${ + info.attacker.name + }`, + color: info.suicide ? options.suicideColor : options.teamkillColor, fields: fields, timestamp: info.time.toISOString(), footer: { diff --git a/plugins/influxdb-log/index.js b/plugins/influxdb-log/index.js index a5acfbc..7669f09 100644 --- a/plugins/influxdb-log/index.js +++ b/plugins/influxdb-log/index.js @@ -16,7 +16,7 @@ export default function(server, influxDB, options = {}) { if (!influxDB) throw new Error('InfluxDBLog must be provided with a InfluxDB connection.'); - + const serverID = options.overrideServerID || server.id; let points = []; diff --git a/plugins/mapvote/README.md b/plugins/mapvote/README.md index 3cb32c0..803cabb 100644 --- a/plugins/mapvote/README.md +++ b/plugins/mapvote/README.md @@ -2,35 +2,120 @@ Logo -#### SquadJS - Mapvote +#### SquadJS - Map Vote -## About -The mapvote plugin uses a "did you mean?" system to allow for players to vote for a wide range of layers. Command information for using the plugin in-game can be accessed by typing `!mapvote help` into in-game chat. +## Map Vote "Did you mean?" +### About +Map Vote "Did you mean?" is best suited for servers who wish to allow players to vote for any layer in a large pool of options as it allows players to vote by specifying the layer name in chat. It uses a "did you mean?" algorithm to correct misspelling in layer names making it easier for players to vote. -## Installation -Place the following into your `index.js` file. The filters / options below are optional and can be removed without affecting functionality, however, the default options are shown for reference. +Commands: + * `!mapvote help` - Shows other commands players can use. + * `!mapvote results` - See the map vote results. + * `!mapvote ` - Vote for a specific layer. Misspelling will be corrected where possible. + + + * `!mapvote start` (Admin chat only) - Starts a new map vote. + * `!mapvote restart` (Admin chat only) - Restarts a map vote. + * `!mapvote end` (Admin chat only) - Ends a map vote and announces the winner. + * `!mapvote destroy` (Admin chat only) - End a map vote without announcing the winner. + +### Installation +Add the following two lines at the top of your index.js file to import the required components: +```js +import SquadLayerFilter from 'connectors/squad-layer-filter'; +import { mapvote } from 'plugins'; +``` + +To control which constraints, e.g. map history and player count compliant, you need to create an active layer filter. +```js +const activeLayerFilter = { + historyResetTime: 5 * 60 * 60 * 1000, // after 5 hours the layer history is ignored. null if off + layerHistoryTolerance: 8, // a layer can be only played once every x layers. null if off + mapHistoryTolerance: 4, // a map can only be played once every x layers. null if off + gamemodeHistoryTolerance: { + Invasion: 4 // invasion can only be played once every x layers + // if not specified they will default to off + }, + playerCountComplianceEnabled: true // filter layers based on suggested player counts if true +}; +``` + +You can turn off all options with: +```js +const activeLayerFilter = null; +``` + +Create a layer pool with one of the following options: +```js +// from a list +const squadLayerFilter = SquadLayerFilter.buildFromFilter(['Layer name 1', 'layer name 2'], activeLayerFilter); + +// from a file of layer anmes separated by new lines +const squadLayerFilter = SquadLayerFilter.buildFromFile('filename', activeLayerFilter); + +// from a filter +const squadLayerFilter = SquadLayerFilter.buildFromFilter( + { // these options can also be turned off by replacing the value with null + whitelistedLayers: null, // a list of layers that can be played + blacklistedLayers: null, // a list of layers that cannot be played + whitelistedMaps: null, // a list of maps that can be played + blacklistedMaps: null, // a list of maps that cannot be played + whitelistedGamemodes: null, // a list of gamemodes that can be played + blacklistedGamemodes: ['Training'], // a list of gamemodes that cannot be played + flagCountMin: null, // layers must have move than this number of flags + flagCountMax: null, // layers must have less than this number of flags + hasCommander: null, // layer must have a commander + hasTanks: null, // layer must have tanks + hasHelicopters: null // layer must have helicopters + }, + activeLayerFilter +); +``` + +Setup the map vote plugin: ```js mapvote( - server, - { // layer filter to limit layers - remove or edit the below options to adjust the filter. Leaving this blank will remove all training layers as a default. - whitelistedLayers: ['layer name'], // an array of layers that can be played - blacklistedLayers: ['layer name'], // an array of layers that cannot be played - whitelistedMaps: ['map name'], // an array of maps that can be played - blacklistedMaps: ['map name'], // an array of maps that cannot be played - default removes training maps - whitelistedGamemodes: ['gamemode name'], // an array of gamemodes that can be played - blacklistedGamemodes: ['gamemode name'], // an array of gamemodes that cannot be played - flagCountMin: 4, // the minimum number of flags the layer must have - flagCountMax: 7, // the maximum number of flags the layer must have - hasCommander: true, // has commander enabled - hasTanks: true, // has tanks - hasHelicopters: true // has helicopters - }, - { // options - remove or edit the below options. The defaults are shown. - command: '!mapvote', // the command name used to access the vote - layerTolerance: 4, // the number of other layers that must be played before the layer can be revoted for - mapTolerance: 2, // the number of other maps that must be played before the layer can be revoted for - timeTolerance: 5 * 60 * 60 * 1000 // the time that must pass before the above are ignored + server, + 'didyoumean', + squadLayerFilter, + { + alwaysOn: true, // map vote will start without admin interaction if true + minPlayerCount: null, // this number of players must be online before they can vote. null is off + minVoteCount: null, // this number of votes must be counted before a layer is selected. null is off + } +); +``` + +## Map Vote "123" +### About +Map Vote "123" is best suited for servers who want to allow admins to create map votes that allow players to easily choose from a small selection of layers. + +Commands: + * `!mapvote help` - Shows other commands players can use. + * `!mapvote results` - See the map vote results. + * `` - Vote for a specific layer via it's associated number. + + + * `!mapvote start , ` (Admin chat only) - Starts a new map vote. + * `!mapvote restart` (Admin chat only) - Restarts a map vote with the same layers. + * `!mapvote end` (Admin chat only) - Ends a map vote and announces the winner. + * `!mapvote destroy` (Admin chat only) - End a map vote without announcing the winner. + +### Installation +Add the following two lines at the top of your index.js file to import the required components: +```js +import SquadLayerFilter from 'connectors/squad-layer-filter'; +import { mapvote } from 'plugins'; +``` + +Setup the map vote plugin: +```js +mapvote( + server, + '123', + { + minVoteCount: null, // this number of votes must be counted before a layer is selected. null is off } ); ``` diff --git a/plugins/mapvote/index.js b/plugins/mapvote/index.js index cb67b9b..c41c8f7 100644 --- a/plugins/mapvote/index.js +++ b/plugins/mapvote/index.js @@ -1,145 +1,15 @@ -import didYouMean from 'didyoumean'; +import mapvoteDidYouMean from './mapvote-did-you-mean.js'; +import mapvote123 from './mapvote-123.js'; -import { COPYRIGHT_MESSAGE } from 'core/config'; - -import SquadLayers from 'connectors/squad-layers'; - -import { RCON_CHAT_MESSAGE } from 'squad-server/events/rcon'; -import { SERVER_LAYER_CHANGE } from 'squad-server/events/server'; - -export default function(server, layerFilter = {}, options = {}) { - if (!server) - throw new Error('Mapvote must be provided with a reference to the server.'); - - const command = options.command || '!mapvote'; - const commandRegex = new RegExp(`^${command} ([A-z0-9'_ ]*)`, 'i'); - const rotation = SquadLayers.getFilteredLayers(layerFilter); - - let voteCounts = {}; - let votes = {}; - let currentWinner = null; - - function getResults() { - let results; - - results = Object.keys(voteCounts).map(layer => { - return { - layer: layer, - voteCount: voteCounts[layer] - }; - }); - - results = results.sort((a, b) => { - if (a.voteCount > b.voteCount) return -1; - if (a.voteCount < b.voteCount) return 1; - else return Math.random() < 0.5 ? 1 : -1; - }); - - return results; +export default function(server, mode, ...args) { + switch (mode) { + case 'didyoumean': + mapvoteDidYouMean(server, ...args); + break; + case '123': + mapvote123(server, ...args); + break; + default: + throw new Error('Invalid mode.'); } - - server.on(SERVER_LAYER_CHANGE, () => { - voteCounts = {}; - votes = {}; - currentWinner = null; - }); - - server.on(RCON_CHAT_MESSAGE, info => { - const match = info.message.match(commandRegex); - if (!match) return; - - if (match[1] === 'help') { - // show help options - server.rcon.execute( - `AdminWarn "${info.steamID}" You may use any of the following commands in chat:` - ); - server.rcon.execute( - `AdminWarn "${info.steamID}" !mapvote results - View the current vote counts.` - ); - server.rcon.execute( - `AdminWarn "${info.steamID}" !mapvote - Vote for the specified layer.` - ); - server.rcon.execute( - `AdminWarn "${info.steamID}" When inputting a layer name, we autocorrect any miss spelling.` - ); - } else if (match[1] === 'results') { - // display results to player - const results = getResults(); - - if (results.length === 0) { - server.rcon.execute( - `AdminWarn "${info.steamID}" No one has voted yet.` - ); - } else { - server.rcon.execute( - `AdminWarn "${info.steamID}" The current vote counts are as follows:` - ); - for (const result of results) { - if (result.voteCount === 0) continue; - - server.rcon.execute( - `AdminWarn "${info.steamID}" ${result.layer} - ${ - result.voteCount - } vote${result.voteCount > 1 ? 's' : ''}.` - ); - } - } - } else { - const layer = didYouMean(match[1], SquadLayers.getLayerNames()); - - // check layer is valid - if (layer === null) { - server.rcon.execute( - `AdminWarn "${info.steamID}" ${match[1]} is not a valid layer name.` - ); - return; - } - - if (!rotation.includes(layer)) { - server.rcon.execute( - `AdminWarn "${info.steamID}" ${layer} is not in the rotation.` - ); - return; - } - - if ( - !SquadLayers.isHistoryCompliant(server.layerHistory, layer, options) - ) { - server.rcon.execute( - `AdminWarn "${info.steamID}" ${layer} has been played too recently.` - ); - return; - } - - // remove existing votes - if (info.steamID in votes) voteCounts[votes[info.steamID]]--; - - // add new vote - if (layer in voteCounts) voteCounts[layer]++; - else voteCounts[layer] = 1; - - // save what layer they votes for - votes[info.steamID] = layer; - - // info them of their vote - server.rcon.execute( - `AdminWarn "${info.steamID}" You voted for ${layer}.` - ); - server.rcon.execute( - `AdminWarn "${info.steamID}" Powered by: ${COPYRIGHT_MESSAGE}` - ); - - // check for new winner - const newWinner = getResults()[0].layer; - - if (currentWinner !== newWinner) { - server.rcon.execute(`AdminSetNextMap ${newWinner}`); - server.rcon.execute(`AdminBroadcast New Map Vote Winner: ${newWinner}`); - server.rcon.execute( - `AdminBroadcast Participate in the map vote by typing "!mapvote help" in chat.` - ); - currentWinner = newWinner; - } - } - }); } diff --git a/plugins/mapvote/mapvote-123.js b/plugins/mapvote/mapvote-123.js new file mode 100644 index 0000000..de5b13a --- /dev/null +++ b/plugins/mapvote/mapvote-123.js @@ -0,0 +1,157 @@ +import SquadLayerFilter from 'connectors/squad-layer-filter'; +import { COPYRIGHT_MESSAGE } from 'core/config'; +import { LOG_PARSER_NEW_GAME } from 'squad-server/events/log-parser'; +import { RCON_CHAT_MESSAGE } from 'squad-server/events/rcon'; + +import MapVote from './mapvote.js'; + +export default function(server, options = {}) { + let mapvote = null; + + options = { + minVoteCount: null, + ...options + }; + + server.on(LOG_PARSER_NEW_GAME, () => { + mapvote = null; + }); + + server.on(RCON_CHAT_MESSAGE, async info => { + const voteMatch = info.message.match(/^([0-9])/); + if (voteMatch) { + if (!mapvote) return; + try { + const layerName = await mapvote.makeVoteByNumber( + info.steamID, + parseInt(voteMatch[1]) + ); + await server.rcon.warn(info.steamID, `You voted for ${layerName}.`); + } catch (err) { + await server.rcon.warn(info.steamID, err.message); + } + await server.rcon.warn(info.steamID, `Powered by: ${COPYRIGHT_MESSAGE}`); + } + + const commandMatch = info.message.match(/^!mapvote ?(.*)/); + if (commandMatch) { + if (commandMatch[1].startsWith('start')) { + if (info.chat !== 'ChatAdmin') return; + + if (mapvote) { + await server.rcon.warn(info.steamID, 'A mapvote has already begun.'); + } else { + mapvote = new MapVote( + server, + SquadLayerFilter.buildFromDidYouMeanList( + commandMatch[1].replace('start ', '').split(', ') + ), + { minVoteCount: options.minVoteCount } + ); + + mapvote.on('NEW_WINNER', async results => { + await server.rcon.broadcast( + `New Map Vote Winner: ${results[0].layer.layer}. Participate in the map vote by typing "!mapvote help" in chat.` + ); + }); + + await server.rcon.broadcast( + `A new map vote has started. Participate in the map vote by typing "!mapvote help" in chat.` + ); + } + return; + } + + if (!mapvote) { + await server.rcon.warn(info.steamID, 'A map vote has not begun.'); + return; + } + + if (commandMatch[1] === 'restart') { + if (info.chat !== 'ChatAdmin') return; + + mapvote = new MapVote(server, mapvote.squadLayerFilter, { + minVoteCount: options.minVoteCount + }); + + mapvote.on('NEW_WINNER', async results => { + await server.rcon.broadcast( + `New Map Vote Winner: ${results[0].layer}. Participate in the map vote by typing "!mapvote help" in chat.` + ); + }); + + await server.rcon.broadcast( + `A new map vote has started. Participate in the map vote by typing "!mapvote help" in chat.` + ); + return; + } + + if (commandMatch[1] === 'end') { + if (info.chat !== 'ChatAdmin') return; + + const results = mapvote.getResults(); + + if (results.length === 0) + await server.rcon.broadcast(`No layer gained enough votes to win.`); + else + await server.rcon.broadcast( + `${mapvote.getResults()[0].layer.layer} won the mapvote!` + ); + + mapvote = null; + return; + } + + if (commandMatch[1] === 'destroy') { + if (info.chat !== 'ChatAdmin') return; + mapvote = null; + return; + } + + if (commandMatch[1] === 'help') { + await server.rcon.warn( + info.steamID, + 'To vote type the layer number into chat:' + ); + for (const layer of mapvote.squadLayerFilter.getLayers()) { + await server.rcon.warn( + info.steamID, + `${layer.layerNumber} - ${layer.layer}` + ); + } + + if (options.minVoteCount !== null) + await server.rcon.warn( + info.steamID, + `${options.minVoteCount} votes need to be made for a winner to be selected.` + ); + + await server.rcon.warn( + info.steamID, + 'To see current results type into chat: !mapvote results' + ); + } + + if (commandMatch[1] === 'results') { + const results = mapvote.getResults(); + + if (results.length === 0) { + await server.rcon.warn(info.steamID, 'No one has voted yet.'); + } else { + await server.rcon.warn( + info.steamID, + 'The current vote counts are as follows:' + ); + for (const result of results) { + await server.rcon.warn( + info.steamID, + `${result.layer.layerNumber} - ${result.layer.layer} (${ + result.votes + } vote${result.votes > 1 ? 's' : ''})` + ); + } + } + } + } + }); +} diff --git a/plugins/mapvote/mapvote-did-you-mean.js b/plugins/mapvote/mapvote-did-you-mean.js new file mode 100644 index 0000000..1d4a905 --- /dev/null +++ b/plugins/mapvote/mapvote-did-you-mean.js @@ -0,0 +1,163 @@ +import { COPYRIGHT_MESSAGE } from 'core/config'; +import { LOG_PARSER_NEW_GAME } from 'squad-server/events/log-parser'; +import { RCON_CHAT_MESSAGE } from 'squad-server/events/rcon'; + +import MapVote from './mapvote.js'; + +export default function(server, squadLayerFilter, options = {}) { + options = { + alwaysOn: true, + minPlayerCount: null, + minVoteCount: null, + ...options + }; + + let mapvote; + let manuallyCreated; + + async function newMapvote(manuallyCreatedOption = true) { + mapvote = new MapVote(server, squadLayerFilter, { + minVoteCount: options.minVoteCount + }); + + manuallyCreated = manuallyCreatedOption; + + mapvote.on('NEW_WINNER', async results => { + await server.rcon.broadcast( + `New Map Vote Winner: ${results[0].layer.layer}. Participate in the map vote by typing "!mapvote help" in chat.` + ); + }); + + if (manuallyCreated) + await server.rcon.broadcast( + `A new map vote has started. Participate in the map vote by typing "!mapvote help" in chat.` + ); + } + + if (options.alwaysOn) newMapvote(false); + + server.on(LOG_PARSER_NEW_GAME, () => { + if (options.alwaysOn) { + newMapvote(false); + } else { + mapvote = null; + } + }); + + server.on(RCON_CHAT_MESSAGE, async info => { + const match = info.message.match(/^!mapvote ?(.*)/); + if (!match) return; + + if (match[1] === 'help') { + await server.rcon.warn( + info.steamID, + 'You may use any of the following commands in chat:' + ); + await server.rcon.warn( + info.steamID, + '!mapvote results - View the current vote counts.' + ); + await server.rcon.warn( + info.steamID, + '!mapvote - Vote for the specified layer.' + ); + await server.rcon.warn( + info.steamID, + 'When inputting a layer name, we autocorrect any miss spelling.' + ); + + if (options.minVoteCount !== null) + await server.rcon.warn( + info.steamID, + `${options.minVoteCount} votes need to be made for a winner to be selected.` + ); + + return; + } + + if (match[1] === 'start') { + if (info.chat !== 'ChatAdmin') return; + + if (mapvote) { + await server.rcon.warn(info.steamID, 'A mapvote has already begun.'); + } else { + await newMapvote(); + } + return; + } + + if (!mapvote) { + await server.rcon.warn(info.steamID, 'A map vote has not begun.'); + return; + } + + if (match[1] === 'restart') { + if (info.chat !== 'ChatAdmin') return; + await newMapvote(); + return; + } + + if (match[1] === 'end') { + if (info.chat !== 'ChatAdmin') return; + + const results = mapvote.getResults(true); + + if (results.length === 0) + await server.rcon.broadcast(`No layer gained enough votes to win.`); + else + await server.rcon.broadcast( + `${mapvote.getResults()[0].layer.layer} won the mapvote!` + ); + + mapvote = null; + return; + } + + if (match[1] === 'destroy') { + if (info.chat !== 'ChatAdmin') return; + mapvote = null; + return; + } + + if (match[1] === 'results') { + const results = mapvote.getResults(); + + if (results.length === 0) { + await server.rcon.warn(info.steamID, 'No one has voted yet.'); + } else { + await server.rcon.warn( + info.steamID, + 'The current vote counts are as follows:' + ); + for (const result of results) { + await server.rcon.warn( + info.steamID, + `${result.layer.layer} - ${result.votes} vote${ + result.votes > 1 ? 's' : '' + }` + ); + } + return; + } + } + + if (!manuallyCreated && server.players.length < options.minPlayerCount) { + await server.rcon.warn( + info.steamID, + 'Not enough players online to vote.' + ); + return; + } + + try { + const layerName = await mapvote.makeVoteByDidYouMean( + info.steamID, + match[1] + ); + await server.rcon.warn(info.steamID, `You voted for ${layerName}.`); + } catch (err) { + await server.rcon.warn(info.steamID, err.message); + } + await server.rcon.warn(info.steamID, `Powered by: ${COPYRIGHT_MESSAGE}`); + }); +} diff --git a/plugins/mapvote/mapvote.js b/plugins/mapvote/mapvote.js new file mode 100644 index 0000000..a3f52e2 --- /dev/null +++ b/plugins/mapvote/mapvote.js @@ -0,0 +1,102 @@ +import EventEmitter from 'events'; + +import SquadLayers from 'connectors/squad-layers'; + +export default class MapVote extends EventEmitter { + constructor(server, squadLayerFilter, options = {}) { + super(); + this.server = server; + this.squadLayerFilter = squadLayerFilter; + + this.layerVotes = {}; + this.playerVotes = {}; + this.currentWinner = null; + + this.minVoteCount = options.minVoteCount || null; + } + + addVote(identifier, layerName) { + if (this.layerVotes[layerName]) { + this.layerVotes[layerName] += 1; + } else { + this.layerVotes[layerName] = 1; + } + this.playerVotes[identifier] = layerName; + } + + removeVote(identifier) { + if (!this.playerVotes[identifier]) return; + + if (this.layerVotes[this.playerVotes[identifier]]) + this.layerVotes[this.playerVotes[identifier]] -= 1; + if (this.layerVotes[this.playerVotes[identifier]] === 0) + delete this.layerVotes[this.playerVotes[identifier]]; + + delete this.playerVotes[identifier]; + } + + getResults(applyMinVoteCount = false) { + if ( + !applyMinVoteCount || + this.minVoteCount === null || + Object.keys(this.playerVotes).length >= this.minVoteCount + ) { + return Object.keys(this.layerVotes) + .map(layerName => ({ + layer: this.squadLayerFilter.getLayerByLayerName(layerName), + votes: this.layerVotes[layerName] + })) + .sort((a, b) => { + if (a.votes > b.votes) return -1; + if (a.votes < b.votes) return 1; + else return Math.random() < 0.5 ? 1 : -1; + }); + } else return []; + } + + async makeVote(identifier, layer) { + layer = SquadLayers.getLayerByLayerName(layer); + + if (!this.squadLayerFilter.inLayerPool(layer)) + throw new Error(`${layer.layer} is not in layer pool.`); + + if (!this.squadLayerFilter.isLayerHistoryCompliant(this.server, layer)) + throw new Error(`${layer.layer} was played too recently.`); + if (!this.squadLayerFilter.isMapHistoryCompliant(this.server, layer)) + throw new Error(`${layer.map} was played too recently.`); + if (!this.squadLayerFilter.isGamemodeHistoryCompliant(this.server, layer)) + throw new Error(`${layer.gamemode} was played too recently.`); + if (!this.squadLayerFilter.isPlayerCountCompliant(this.server, layer)) + throw new Error( + `${layer.layer} is only suitable for a player count between ${layer.estimatedSuitablePlayerCount.min} and ${layer.estimatedSuitablePlayerCount.max}.` + ); + + this.removeVote(identifier); + this.addVote(identifier, layer.layer); + + const results = this.getResults(true); + + if (results.length > 0) { + if (results[0] !== this.currentWinner) { + await this.server.rcon.execute( + `AdminSetNextMap ${results[0].layer.layer}` + ); + this.emit('NEW_WINNER', results); + this.currentWinner = results[0]; + } + } + + return layer.layer; + } + + async makeVoteByDidYouMean(identifier, layerName) { + const layer = SquadLayers.getLayerByDidYouMean(layerName); + if (layer === null) throw new Error(`${layerName} is not a Squad layer.`); + return this.makeVote(identifier, layer.layer); + } + + async makeVoteByNumber(identifier, number) { + const layer = this.squadLayerFilter.getLayerByNumber(number); + return this.makeVote(identifier, layer.layer); + } +} diff --git a/plugins/package.json b/plugins/package.json index ca9e6d6..b19d805 100644 --- a/plugins/package.json +++ b/plugins/package.json @@ -9,6 +9,7 @@ "connectors": "1.0.0", "didyoumean": "^1.2.1", "influx": "^5.5.1", - "squad-server": "1.0.0" + "squad-server": "1.0.0", + "tinygradient": "^1.1.2" } } diff --git a/squad-server/events/log-parser.js b/squad-server/events/log-parser.js index e6d338d..7d10bee 100644 --- a/squad-server/events/log-parser.js +++ b/squad-server/events/log-parser.js @@ -40,6 +40,7 @@ const LOG_PARSER_PLAYER_DAMAGED = 'LOG_PARSER_PLAYER_DAMAGED'; * - attackerPlayerController - PlayerController of the attacking player. * - weapon - The classname of the weapon used. * - teamkill - Whether the kill was a teamkill. + * - suicide - Was the kill a suicide. */ const LOG_PARSER_PLAYER_DIED = 'LOG_PARSER_PLAYER_DIED'; @@ -63,6 +64,7 @@ const LOG_PARSER_PLAYER_POSSESS = 'LOG_PARSER_PLAYER_POSSESS'; * - attackerPlayerController - PlayerController of the attacking player. * - weapon - The classname of the weapon used. * - teamkill - Whether the kill was a teamkill. + * - suicide - Was the kill a suicide. * - reviver - PlayerObject of the reviving player. */ const LOG_PARSER_PLAYER_REVIVED = 'LOG_PARSER_PLAYER_REVIVED'; @@ -86,6 +88,7 @@ const LOG_PARSER_PLAYER_UNPOSSESS = 'LOG_PARSER_PLAYER_UNPOSSESS'; * - attackerPlayerController - PlayerController of the attacking player. * - weapon - The classname of the weapon used. * - teamkill - Whether the kill was a teamkill. + * - suicide - Was the kill a suicide. */ const LOG_PARSER_TEAMKILL = 'LOG_PARSER_TEAMKILL'; @@ -99,6 +102,7 @@ const LOG_PARSER_TEAMKILL = 'LOG_PARSER_TEAMKILL'; * - attackerPlayerController - PlayerController of the attacking player. * - weapon - The classname of the weapon used. * - teamkill - Whether the kill was a teamkill. + * - suicide - Was the kill a suicide. */ const LOG_PARSER_PLAYER_WOUNDED = 'LOG_PARSER_PLAYER_WOUNDED'; diff --git a/squad-server/log-parser/rules/player-damaged.js b/squad-server/log-parser/rules/player-damaged.js index 32ceb35..e0a7559 100644 --- a/squad-server/log-parser/rules/player-damaged.js +++ b/squad-server/log-parser/rules/player-damaged.js @@ -14,6 +14,7 @@ export default { }; data.teamkill = data.victim.teamID === data.attacker.teamID; + data.suicide = data.victim.steamID === data.attacker.steamID; logParser.eventStore[args[3]] = data; diff --git a/squad-server/log-parser/rules/player-possess.js b/squad-server/log-parser/rules/player-possess.js index c8ed5cf..7f4b288 100644 --- a/squad-server/log-parser/rules/player-possess.js +++ b/squad-server/log-parser/rules/player-possess.js @@ -1,6 +1,4 @@ -import { - LOG_PARSER_PLAYER_POSSESS -} from '../../events/log-parser.js'; +import { LOG_PARSER_PLAYER_POSSESS } from '../../events/log-parser.js'; export default { regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnPossess\(\): PC=(.+) Pawn=([A-z0-9_]+)_C/, diff --git a/squad-server/log-parser/rules/player-revived.js b/squad-server/log-parser/rules/player-revived.js index 8b0dd64..b8ed547 100644 --- a/squad-server/log-parser/rules/player-revived.js +++ b/squad-server/log-parser/rules/player-revived.js @@ -9,7 +9,7 @@ export default { raw: args[0], time: args[1], chainID: args[2], - victim: await logParser.server.getPlayerByName(args[5]), + victim: await logParser.server.getPlayerByName(args[4]), reviver: await logParser.server.getPlayerByName(args[3]) }; diff --git a/squad-server/log-parser/rules/player-un-possess.js b/squad-server/log-parser/rules/player-un-possess.js index d021ff9..44a1fa9 100644 --- a/squad-server/log-parser/rules/player-un-possess.js +++ b/squad-server/log-parser/rules/player-un-possess.js @@ -1,6 +1,4 @@ -import { - LOG_PARSER_PLAYER_UNPOSSESS -} from '../../events/log-parser.js'; +import { LOG_PARSER_PLAYER_UNPOSSESS } from '../../events/log-parser.js'; export default { regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnUnPossess\(\): PC=(.+)/, @@ -13,7 +11,11 @@ export default { switchPossess: false }; - if(args[3] in logParser.eventStore && logParser.eventStore[args[3]] === args[2]) data.switchPossess = true; + if ( + args[3] in logParser.eventStore && + logParser.eventStore[args[3]] === args[2] + ) + data.switchPossess = true; delete logParser.eventStore[args[3]]; logParser.server.emit(LOG_PARSER_PLAYER_UNPOSSESS, data); diff --git a/squad-server/rcon/index.js b/squad-server/rcon/index.js index 1c00f5f..a5d0f15 100644 --- a/squad-server/rcon/index.js +++ b/squad-server/rcon/index.js @@ -57,9 +57,7 @@ export default class Rcon { async getMapInfo() { const response = await this.execute('ShowNextMap'); - const match = response.match( - /^Current map is (.+), Next map is (.*)/ - ); + const match = response.match(/^Current map is (.+), Next map is (.*)/); return { currentLayer: match[1], nextLayer: match[2].length === 0 ? null : match[2] @@ -89,6 +87,14 @@ export default class Rcon { return players; } + async broadcast(message) { + await this.execute(`AdminBroadcast ${message}`); + } + + async warn(steamID, message) { + await this.execute(`AdminWarn "${steamID}" ${message}`); + } + /* Core socket functionality */ connect() { this.verbose('Method Exec: connect()'); @@ -166,7 +172,7 @@ export default class Rcon { this.client.once('close', onClose); this.client.once('error', onError); - this.client.disconnect(); + this.client.end(); }); }