diff --git a/TO-DO.txt b/TO-DO.txt index d92329d..e421d33 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -1,11 +1,19 @@ TO DO: + - Increase AI aggressivity: + + Make AI more keen on moving into incoming fire to reach move positions + - Make AI increase their use of suppressive fire on targets + - Fix spawn menu camera not being used on JIP (camera is following the player, who gets teleported when switching sides) + - Also fix R2T camera not showing on JIP - Fix occasional double-kill registration for an individual unit - Maybe why kills/vehicle destruction sometimes awards double the points that it should (including headshot damage)? - Fix loadouts sometimes not getting applied to AI units (same issue as gAI when locality changes?) - Fix spotting animation not working (same issue as healing action) - Implement resupply ability to support: - - Similar animations/UI icons to medic healing, but for rearming + + Similar animations/UI icons to medic healing, but for rearming - Prevent players from looting bodies/opening their gear + + Add score to the resupplying unit + + Resupply the recipient as a fraction of their overall ammunition + + Calculate and cache ammo count in getOverallAmmo, and invalidate the cache via onInit, onReloaded, setRoleLoadout - Add a repair ability to the engineer role - Implement looping action logic - Play a short unit gesture/animation @@ -33,6 +41,14 @@ TO DO: - Search for "DEBUG" and "TODO" keywords DONE: + - Flip kill feed horizontally (apparently it's not very readable from right to left) + - Add death/revive sounds + - Add resupplying action sounds + - Add medical action sounds + - Add automatic magazine repacking on reload + - Add a custom headshot sound (satisfaction factor) + - Fix being able to change role GVAR by selecting a new role in the spawnmenu post-spawn + - Fix AI medics spam-healing themselves on damage and immediately reviving unconscious units - Fix unconsciousState AI subsystem referencing serverside variables (causing AI to not respawn) - Fix spawn menu inconsistencies + Sides with no tickets don't get greyed out diff --git a/description.ext b/description.ext index 1d197a1..f7dd4a2 100644 --- a/description.ext +++ b/description.ext @@ -12,8 +12,7 @@ onLoadName = __EVAL(MACRO_MISSION_NAME); onLoadMission = __EVAL(MACRO_MISSION_FRAMEWORK_GAMEMODE + " (v" + MACRO_MISSION_FRAMEWORK_VERSION + ")"); overviewText = __EVAL(MACRO_MISSION_FRAMEWORK_GAMEMODE + " - " + MACRO_MISSION_NAME + " (v" + MACRO_MISSION_FRAMEWORK_VERSION + ")

Made by Cre8or
(C) 2019 - 2024"); -// TODO: Add custom overview and banner images -//overviewPicture = MACRO_MISSION_OVERVIEW_IMAGE; +overviewPicture = MACRO_MISSION_OVERVIEW_IMAGE; loadScreen = MACRO_MISSION_LOADING_BANNER; joinUnassigned = 0; // Auto-slot players @@ -21,8 +20,11 @@ disabledAI = 1; //briefing = 0; debriefing = 0; saving = 0; +respawnDialog=0; +respawn=3; +respawnDelay=2; allowFunctionsRecompile = __EVAL((isServer and !isDedicated) or is3DENMultiplayer or is3DENPreview); - +/* showHUD[] = { 1, // Scripted HUD (same as showHUD command) 1, // Vehicle and unit info @@ -36,7 +38,7 @@ showHUD[] = { 0, // "x killed by y" systemChat messages 1 // force show drawIcon3D icons }; - +*/ diff --git a/init3DEN.sqf b/init3DEN.sqf index 0b234a5..d023b87 100644 --- a/init3DEN.sqf +++ b/init3DEN.sqf @@ -144,7 +144,6 @@ private _EH_eachFrame = addMissionEventHandler ["EachFrame", { // Enforce a valid naming scheme if (count _name != 8) then { systemChat format ["WARNING: ""%1"" is not a valid sector name!", _name]; - //breakTo QGVAR(eden_eachFrame); continue; }; _letter = _name select [7, 1]; @@ -176,23 +175,45 @@ private _EH_eachFrame = addMissionEventHandler ["EachFrame", { + // Update the flag's texture and position on the pole to match the sector parameters - _textureEmpty = "a3\data_f\flags\flag_white_co.paa"; _textureIcon = "a3\ui_f\data\GUI\Rsc\RscDisplayMultiplayerSetup\flag_bluefor_empty_ca.paa"; - _texture = _textureEmpty; + _texture = MACRO_TEXTURE_FLAG_EMPTY; _level = 0; _activation = (_sector get3DENAttribute "ActivationBy") # 0; switch (_activation select [0, 4]) do { case "EAST": { - _texture = MACRO_FLAG_TEXTURE_EAST; + #if __has_include("mission\sides\data_side_east.inc") + _texture = ( + #include "mission\sides\data_side_east.inc" + ) param [2, MACRO_TEXTURE_FLAG_EMPTY]; + #else + _texture = "a3\data_f\flags\flag_red_co.paa"; + systemChat "[CONQUEST] ERROR: A critical file is missing! (mission\sides\data_side_east.inc)"; + #endif + _level = 1; }; case "GUER": { - _texture = MACRO_FLAG_TEXTURE_RESISTANCE; + #if __has_include("mission\sides\data_side_resistance.inc") + _texture = ( + #include "mission\sides\data_side_resistance.inc" + ) param [2, MACRO_TEXTURE_FLAG_EMPTY]; + #else + _texture = "a3\data_f\flags\flag_green_co.paa"; + systemChat "[CONQUEST] ERROR: A critical file is missing! (mission\sides\data_side_resistance.inc)"; + #endif _level = 1; }; case "WEST": { - _texture = MACRO_FLAG_TEXTURE_WEST; + #if __has_include("mission\sides\data_side_west.inc") + _texture = ( + #include "mission\sides\data_side_west.inc" + ) param [2, MACRO_TEXTURE_FLAG_EMPTY]; + #else + _texture = "a3\data_f\flags\flag_blue_co.paa"; + systemChat "[CONQUEST] ERROR: A critical file is missing! (mission\sides\data_side_west.inc)"; + #endif _level = 1; }; }; @@ -200,7 +221,7 @@ private _EH_eachFrame = addMissionEventHandler ["EachFrame", { // Validate the texture path if (!fileExists _texture) then { - _texture = _textureEmpty; + _texture = MACRO_TEXTURE_FLAG_EMPTY; } else { _textureIcon = _texture; }; diff --git a/mission.sqm b/mission.sqm index e70fc62..95e87bc 100644 --- a/mission.sqm +++ b/mission.sqm @@ -497,9 +497,6 @@ randomSeed=7845369; class ScenarioData { author="Cre8or"; - respawnDialog=0; - respawn=3; - respawnDelay=2; }; class CustomAttributes { diff --git a/mission/loadouts/data_loadouts_east.inc b/mission/loadouts/data_loadouts_east.inc deleted file mode 100644 index 5f28bdd..0000000 --- a/mission/loadouts/data_loadouts_east.inc +++ /dev/null @@ -1,81 +0,0 @@ -/* ----------------------------------------- ROLE LOADOUTS (OPFOR / EAST) ---------------------------------------- - -USAGE: - - 1: Copy the loadout that you want to edit (large array under every role enum macro) - 2: Apply the loadout onto yourself (either using ACE3's Arsenal, or the setUnitLoadout scripting command) - 3: Modify the loadout to your heart's content - 4: Export the edited loadout back into the array format (either using ACE3's Arsenal, or the getUnitLoad scripting command) - 5: Paste the loadout into the corresponding role array - 6: Save - 7: Done! - - *DO NOT ADD ANYTHING ELSE* - NOT EVEN A COMMA (,) OR A SEMICOLON (;) AT THE END OF THE FILE! NOTHING! NYET! NADA! - - NOTE: Even if you're not using this side, DO NOT EMPTY THIS FILE! Otherwise, things WILL break! Just leave the default values in, instead. */ - - - - - -[ - [ // Spec Ops - MACRO_ENUM_ROLE_SPECOPS, - [["arifle_Katiba_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],[],["hgun_ACPC2_F","muzzle_snds_acp","","",["9Rnd_45ACP_Mag",8],[],""],["U_O_CombatUniform_ocamo",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1]]],["V_TacVest_brn",[["9Rnd_45ACP_Mag",3,8],["30Rnd_65x39_caseless_green_mag_Tracer",6,30],["HandGrenade",1,1]]],[],"H_ShemagOpen_tan","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Sniper - MACRO_ENUM_ROLE_SNIPER, - [["srifle_DMR_05_tan_f","","acc_flashlight","optic_KHS_blk",["10Rnd_93x64_DMR_05_Mag",10],[],"bipod_02_F_hex"],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_FullGhillie_sard",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1]]],["V_Chestrig_rgr",[["HandGrenade",1,1],["APERSTripMine_Wire_Mag",1,1],["ACE_FlareTripMine_Mag",1,1],["10Rnd_93x64_DMR_05_Mag",4,10],["16Rnd_9x21_green_Mag",2,17]]],[],"","G_Balaclava_oli",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Assault - MACRO_ENUM_ROLE_ASSAULT, - [["arifle_Katiba_GL_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],["1Rnd_HE_Grenade_shell",1],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_TacVest_brn",[["1Rnd_HE_Grenade_shell",3,1],["16Rnd_9x21_green_Mag",2,17],["30Rnd_65x39_caseless_green_mag_Tracer",6,30]]],[],"H_HelmetO_ocamo","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Support - MACRO_ENUM_ROLE_SUPPORT, - [["LMG_Zafir_F","","acc_flashlight","optic_ACO_grn",["150Rnd_762x54_Box_Tracer",150],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["150Rnd_762x54_Box_Tracer",1,150],["HandGrenade",1,1]]],["B_FieldPack_cbr",[["150Rnd_762x54_Box_Tracer",2,150]]],"H_HelmetO_ocamo","G_Bandanna_beast",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Engineer - MACRO_ENUM_ROLE_ENGINEER, - [["arifle_ARX_hex_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1],["MineDetector",1]]],["V_TacVest_brn",[["16Rnd_9x21_green_Mag",2,17],["HandGrenade",1,1],["30Rnd_65x39_caseless_green_mag_Tracer",7,30]]],["B_AssaultPack_ocamo",[["SLAMDirectionalMine_Wire_Mag",3,1]]],"H_HelmetO_ocamo","G_Bandanna_blk",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Medic - MACRO_ENUM_ROLE_MEDIC, - [["arifle_CTAR_hex_F","","acc_flashlight","optic_ACO_grn",["30Rnd_580x42_Mag_Tracer_F",30],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_personalAidKit",2]]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["30Rnd_580x42_Mag_Tracer_F",7,30],["SmokeShell",2,1]]],["B_AssaultPack_cbr",[["ACE_personalAidKit",16]]],"H_HelmetSpecO_ocamo","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Anti Tank - MACRO_ENUM_ROLE_ANTITANK, - [["arifle_Katiba_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],["launch_RPG32_F","","","",["RPG32_F",1],[],""],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["30Rnd_65x39_caseless_green_mag_Tracer",7,30],["HandGrenade",1,1]]],["B_Kitbag_cbr",[["RPG32_F",2,1]]],"H_HelmetO_ocamo","G_Balaclava_blk",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ] -] diff --git a/mission/loadouts/data_loadouts_resistance.inc b/mission/loadouts/data_loadouts_resistance.inc deleted file mode 100644 index 8045a36..0000000 --- a/mission/loadouts/data_loadouts_resistance.inc +++ /dev/null @@ -1,82 +0,0 @@ -/* ----------------------------------------- ROLE LOADOUTS (INDFOR / RESISTANCE) ---------------------------------------- - -USAGE: - - 1: Copy the loadout that you want to edit (large array under every role enum macro) - 2: Apply the loadout onto yourself (either using ACE3's Arsenal, or the setUnitLoadout scripting command) - 3: Modify the loadout to your heart's content - 4: Export the edited loadout back into the array format (either using ACE3's Arsenal, or the getUnitLoad scripting command) - 5: Paste the loadout into the corresponding role array - 6: Save - 7: Done! - - *DO NOT ADD ANYTHING ELSE* - NOT EVEN A COMMA (,) OR A SEMICOLON (;) AT THE END OF THE FILE! NOTHING! NYET! NADA! - - NOTE: Even if you're not using this side, DO NOT EMPTY THIS FILE! Otherwise, things WILL break! Just leave the default values in, instead. */ - - - - - -[ - [ // Spec Ops - MACRO_ENUM_ROLE_SPECOPS, - //[["arifle_TRG21_GL_F","muzzle_snds_M","acc_flashlight","optic_MRCO",["30Rnd_556x45_Stanag",30],["1Rnd_HE_Grenade_shell",1],""],[],["hgun_Pistol_heavy_01_F","","","optic_MRD",["11Rnd_45ACP_Mag",11],[],""],["U_B_CTRG_3",[["ACE_fieldDressing",2],["ACE_packingBandage",3],["ACE_morphine",2],["ACE_tourniquet",2],["ACE_epinephrine",1],["ACE_salineIV_250",1],["ACE_splint",2],["SmokeShell",1,1],["Chemlight_green",1,1]]],["V_TacVestIR_blk",[["11Rnd_45ACP_Mag",2,11],["SmokeShellGreen",1,1],["Chemlight_green",1,1],["1Rnd_HE_Grenade_shell",5,1],["30Rnd_556x45_Stanag_Tracer_Red",9,30],["HandGrenade",2,1]]],["B_Carryall_green_F",[["ToolKit",1],["MineDetector",1]]],"H_Cap_khaki_specops_UK","",["Binocular","","","",[],[],""],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch","ACE_NVG_Gen4"]] - [["arifle_TRG21_F","","acc_flashlight","optic_ACO_grn_smg",["30Rnd_556x45_Stanag_Tracer_Green",30],[],""],[],["hgun_Pistol_heavy_01_green_F","muzzle_snds_acp","acc_flashlight_pistol","",["11Rnd_45ACP_Mag",15],[],""],["U_I_CombatUniform",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1]]],["V_PlateCarrierIA2_dgtl",[["HandGrenade",1,1],["11Rnd_45ACP_Mag",2,15],["30Rnd_556x45_Stanag_Tracer_Green",7,30]]],[],"H_MilCap_dgtl","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Sniper - MACRO_ENUM_ROLE_SNIPER, - [["srifle_DMR_05_tan_f","","acc_flashlight","optic_KHS_blk",["10Rnd_93x64_DMR_05_Mag",10],[],"bipod_02_F_hex"],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_FullGhillie_sard",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1]]],["V_Chestrig_rgr",[["HandGrenade",1,1],["APERSTripMine_Wire_Mag",1,1],["ACE_FlareTripMine_Mag",1,1],["10Rnd_93x64_DMR_05_Mag",4,10],["16Rnd_9x21_green_Mag",2,17]]],[],"","G_Balaclava_oli",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Assault - MACRO_ENUM_ROLE_ASSAULT, - [["arifle_Katiba_GL_F","","acc_flashlight","optic_ACO_grn_smg",["30Rnd_65x39_caseless_green_mag_Tracer",30],["1Rnd_HE_Grenade_shell",1],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_I_CombatUniform",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_TacVest_brn",[["1Rnd_HE_Grenade_shell",3,1],["16Rnd_9x21_green_Mag",2,17],["30Rnd_65x39_caseless_green_mag_Tracer",6,30]]],[],"H_HelmetO_ocamo","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Support - MACRO_ENUM_ROLE_SUPPORT, - [["LMG_Zafir_F","","acc_flashlight","optic_ACO_grn_smg",["150Rnd_762x54_Box_Tracer",150],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_I_CombatUniform_shortsleeve",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["150Rnd_762x54_Box_Tracer",1,150],["HandGrenade",1,1]]],["B_FieldPack_cbr",[["150Rnd_762x54_Box_Tracer",2,150]]],"H_HelmetO_ocamo","G_Bandanna_beast",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Engineer - MACRO_ENUM_ROLE_ENGINEER, - [["arifle_ARX_hex_F","","acc_flashlight","optic_ACO_grn_smg",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_I_CombatUniform",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1],["MineDetector",1]]],["V_TacVest_brn",[["16Rnd_9x21_green_Mag",2,17],["HandGrenade",1,1],["30Rnd_65x39_caseless_green_mag_Tracer",7,30]]],["B_AssaultPack_ocamo",[["SLAMDirectionalMine_Wire_Mag",3,1]]],"H_HelmetO_ocamo","G_Bandanna_blk",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Medic - MACRO_ENUM_ROLE_MEDIC, - [["arifle_CTAR_hex_F","","acc_flashlight","optic_ACO_grn_smg",["30Rnd_580x42_Mag_Tracer_F",30],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_I_CombatUniform",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_personalAidKit",2]]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["30Rnd_580x42_Mag_Tracer_F",7,30],["SmokeShell",2,1]]],["B_AssaultPack_cbr",[["ACE_personalAidKit",16]]],"H_HelmetSpecO_ocamo","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Anti Tank - MACRO_ENUM_ROLE_ANTITANK, - [["arifle_Katiba_F","","acc_flashlight","optic_ACO_grn_smg",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],["launch_RPG32_F","","","",["RPG32_F",1],[],""],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_I_CombatUniform",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["30Rnd_65x39_caseless_green_mag_Tracer",7,30],["HandGrenade",1,1]]],["B_Kitbag_cbr",[["RPG32_F",2,1]]],"H_HelmetO_ocamo","G_Balaclava_blk",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ] -] diff --git a/mission/loadouts/data_loadouts_west.inc b/mission/loadouts/data_loadouts_west.inc deleted file mode 100644 index c9bc80d..0000000 --- a/mission/loadouts/data_loadouts_west.inc +++ /dev/null @@ -1,81 +0,0 @@ -/* ----------------------------------------- ROLE LOADOUTS (BLUFOR / WEST) ---------------------------------------- - -USAGE: - - 1: Copy the loadout that you want to edit (large array under every role enum macro) - 2: Apply the loadout onto yourself (either using ACE3's Arsenal, or the setUnitLoadout scripting command) - 3: Modify the loadout to your heart's content - 4: Export the edited loadout back into the array format (either using ACE3's Arsenal, or the getUnitLoad scripting command) - 5: Paste the loadout into the corresponding role array - 6: Save - 7: Done! - - *DO NOT ADD ANYTHING ELSE* - NOT EVEN A COMMA (,) OR A SEMICOLON (;) AT THE END OF THE FILE! NOTHING! NYET! NADA! - - NOTE: Even if you're not using this side, DO NOT EMPTY THIS FILE! Otherwise, things WILL break! Just leave the default values in, instead. */ - - - - - -[ - [ // Spec Ops - MACRO_ENUM_ROLE_SPECOPS, - [["arifle_MX_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],[],""],[],["hgun_Pistol_heavy_01_MRD_F","muzzle_snds_acp","acc_flashlight_pistol","",["11Rnd_45ACP_Mag",11],[],""],["U_B_CombatUniform_mcam",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1]]],["V_PlateCarrier2_rgr",[["30Rnd_65x39_caseless_black_mag_Tracer",7,30],["HandGrenade",1,1],["11Rnd_45ACP_Mag",2,11]]],[],"H_Cap_headphones","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Sniper - MACRO_ENUM_ROLE_SNIPER, - [["srifle_DMR_02_camo_F","","acc_flashlight","optic_KHS_blk",["10Rnd_338_Mag",10],[],"bipod_01_F_blk"],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_FullGhillie_sard",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1]]],["V_Chestrig_rgr",[["HandGrenade",1,1],["APERSTripMine_Wire_Mag",1,1],["ACE_FlareTripMine_Mag",1,1],["16Rnd_9x21_red_Mag",2,17],["10Rnd_338_Mag",4,10]]],[],"","G_Bandanna_tan",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Assault - MACRO_ENUM_ROLE_ASSAULT, - [["arifle_MX_GL_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],["1Rnd_HE_Grenade_shell",1],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_PlateCarrier1_rgr",[["30Rnd_65x39_caseless_black_mag_Tracer",7,30],["16Rnd_9x21_red_Mag",2,17],["1Rnd_HE_Grenade_shell",2,1]]],[],"H_HelmetSpecB","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Support - MACRO_ENUM_ROLE_SUPPORT, - [["LMG_03_F","","acc_flashlight","",["200Rnd_556x45_Box_Tracer_Red_F",200],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam_vest",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_PlateCarrier1_rgr",[["200Rnd_556x45_Box_Tracer_Red_F",2,200],["HandGrenade",1,1],["16Rnd_9x21_red_Mag",2,17]]],["B_Kitbag_mcamo",[]],"H_HelmetB_sand","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Engineer - MACRO_ENUM_ROLE_ENGINEER, - [["SMG_02_F","","acc_flashlight","optic_Aco",["30Rnd_9x21_Mag_SMG_02_Tracer_Red",30],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_DefusalKit",1],["MineDetector",1]]],["V_PlateCarrier2_rgr",[["16Rnd_9x21_red_Mag",2,17],["HandGrenade",1,1],["30Rnd_9x21_Mag_SMG_02_Tracer_Red",9,30]]],["B_AssaultPack_khk",[["SLAMDirectionalMine_Wire_Mag",3,1]]],"H_HelmetB_grass","G_Balaclava_TI_blk_F",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Medic - MACRO_ENUM_ROLE_MEDIC, - [["arifle_SPAR_01_blk_F","","acc_flashlight","optic_Holosight_blk_F",["30Rnd_556x45_Stanag_Tracer_Red",30],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[["ACE_elasticBandage",10],["ACE_morphine",5],["ACE_personalAidKit",2]]],["V_PlateCarrier1_rgr",[["16Rnd_9x21_red_Mag",2,17],["30Rnd_556x45_Stanag_Tracer_Red",7,30],["SmokeShell",2,1]]],["B_AssaultPack_mcamo",[["ACE_personalAidKit",16]]],"H_HelmetB_camo","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ], - - - - - - [ // Anti Tank - MACRO_ENUM_ROLE_ANTITANK, - [["arifle_MX_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],[],""],["launch_MRAWS_olive_rail_F","","","",["MRAWS_HEAT_F",1],[],""],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[["ACE_elasticBandage",10],["ACE_morphine",5]]],["V_PlateCarrier_Kerry",[["16Rnd_9x21_red_Mag",2,17],["30Rnd_65x39_caseless_black_mag_Tracer",7,30]]],["B_Kitbag_rgr",[["MRAWS_HEAT_F",2,1]]],"H_HelmetB_snakeskin","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] - ] -] diff --git a/mission/res/overview.paa b/mission/res/overview.paa new file mode 100644 index 0000000..4c1f8bf Binary files /dev/null and b/mission/res/overview.paa differ diff --git a/mission/settings.inc b/mission/settings.inc index 20aa984..d2fe662 100644 --- a/mission/settings.inc +++ b/mission/settings.inc @@ -4,7 +4,7 @@ #define MACRO_MISSION_NAME "Gulf of Malden" - #define MACRO_MISSION_OVERVIEW_IMAGE "mission\res\overview.paa" + #define MACRO_MISSION_OVERVIEW_IMAGE "" #define MACRO_MISSION_LOADING_BANNER "mission\res\banner.paa" @@ -24,17 +24,6 @@ #define MACRO_AI_SPAWNWEIGHT_RESISTANCE 0 // The weight towards spawning AI on the "resistance" (INDFOR) side #define MACRO_AI_SPAWNWEIGHT_WEST 1 // The weight towards spawning AI on the "west" (BLUFOR) side - // NOTE: Faces and voices parameters can either accept a single value (e.g. "farsi"), or an array of values (e.g. ["polish", "russian"]) - // Accepted values for voices are: "chinese", "english_fr", "english_gr", "english_uk", "english_us", "farsi", "french", "polish", "russian" - #define MACRO_AI_VOICES_EAST "farsi" - #define MACRO_AI_VOICES_RESISTANCE "english_gr" - #define MACRO_AI_VOICES_WEST "english_us" - - // Accepted values for faces are: "african", "african_camo", "asian", "asian_camo", "greek", "greek_camo", "livonian", "persian", "persian_camo", "russian", "tanoan", "white", "white_camo" - #define MACRO_AI_FACES_EAST ["persian", "persian_camo"] - #define MACRO_AI_FACES_RESISTANCE "greek" - #define MACRO_AI_FACES_WEST ["white", "livonian", "african"] - @@ -43,8 +32,8 @@ // GAMEPLAY SETTINGS // ---------------------------------------------------------------------------------------------------------------------------------- // To modify the following properties, refer to the code snippets in the respective comments behind them - #define MACRO_MISSION_CAMERAPOSITION [5233.83,9852.79,223.421] // The position of the camera when showing the spawn menu - acquired via: getPosWorld get3DENCamera - #define MACRO_MISSION_CAMERADIRECTION [0.996809,-0.0793498,0.0157021] // The diorection of the camera when showing the spawn menu - acquired via: vectorDir get3DENCamera + #define MACRO_MISSION_CAMERAPOSITION [5233.83,9852.79,223.421] // The position of the camera when showing the spawn menu. Acquired via: copyToClipboard str getPosWorld get3DENCamera + #define MACRO_MISSION_CAMERADIRECTION [0.996809,-0.0793498,0.0157021] // The diorection of the camera when showing the spawn menu. Acquired via: copyToClipboard str vectorDir get3DENCamera // Gamemode #define MACRO_GM_STARTINGTICKETS 200 // How many respawn tickets each side should start with @@ -56,7 +45,7 @@ // Damage balancing #define MACRO_GM_UNIT_DAMAGEMUL_HEADSHOT 3 // Multiplier for the damage dealt by headshots - #define MACRO_GM_UNIT_DAMAGEMUL_BULLET 1 // Multiplier for the damage units receive from that bullet hits + #define MACRO_GM_UNIT_DAMAGEMUL_BULLET 1.5 // Multiplier for the damage units receive from that bullet hits #define MACRO_GM_UNIT_DAMAGEMUL_EXPLOSIVE 1 // Multiplier for the damage units receive from explosions #define MACRO_GM_UNIT_DAMAGEMUL_FALLDAMAGE 1 // Multiplier for the damage units receive from falling #define MACRO_GM_UNIT_DAMAGEMUL_ROADKILL 1 // Multiplier for the damage units receive when getting hit/run over by vehicles @@ -68,21 +57,6 @@ -// ---------------------------------------------------------------------------------------------------------------------------------- -// SIDES -// ---------------------------------------------------------------------------------------------------------------------------------- - #define MACRO_SIDE_NAME_EAST "CSAT" - #define MACRO_SIDE_NAME_RESISTANCE "AAF" - #define MACRO_SIDE_NAME_WEST "NATO" - - #define MACRO_FLAG_TEXTURE_EAST "a3\data_f\Flags\flag_CSAT_co.paa" - #define MACRO_FLAG_TEXTURE_RESISTANCE "a3\data_f\Flags\flag_AAF_co.paa" - #define MACRO_FLAG_TEXTURE_WEST "a3\data_f\Flags\flag_nato_co.paa" - - - - - // ---------------------------------------------------------------------------------------------------------------------------------- // LOADOUTS // ---------------------------------------------------------------------------------------------------------------------------------- diff --git a/mission/sides/data_side_east.inc b/mission/sides/data_side_east.inc new file mode 100644 index 0000000..df00586 --- /dev/null +++ b/mission/sides/data_side_east.inc @@ -0,0 +1,111 @@ +/* +---------------------------------------- SIDE DATA FILE (OPFOR / EAST) ---------------------------------------- + +USAGE: + + Modifying loadouts: + 1: Copy the loadout that you want to edit (large array under every role enum macro) + 2: Apply the loadout onto yourself (either using ACE3's Arsenal, or the setUnitLoadout scripting command) + 3: Modify the loadout to your heart's content + 4: Export the edited loadout back into the array format (using the getUnitLoad scripting command) + 5: Paste the loadout into the corresponding role array + 6: Save + 7: Done! + + *DO NOT ADD ANYTHING ELSE* - NOT EVEN A COMMA (,) OR A SEMICOLON (;) AT THE END OF THE FILE! NOTHING! NYET! NADA! */ + +#include "..\..\res\common\macros.inc" + + + + + +[ + // Side name (short format, ideally 5 characters or less) + "CSAT", + + // Side name (long format, ideally up to 40 characters) + "Canton Protocol Strategic Alliance Treaty", + + // Texture path to a flag representing this side (ArmA 3 has many country flags you can use) + "a3\data_f\Flags\flag_CSAT_co.paa", + + // The sets of faces that AI units on this side should use. Intended for immersion. + // Possible values: "african", "african_camo", "asian", "asian_camo", "greek", "greek_camo", "livonian", "persian", "persian_camo", "russian", "tanoan", "white", "white_camo" + // NOTE: This setting accepts a single value (e.g. "greek"), or an array of values (e.g. ["african", "african_camo"]) + ["persian", "persian_camo"], + + // The spoken voice presets that AI units on this side should use (for things like automatic call-outs). Intended for immersion. + // Possible values: "chinese", "english_fr", "english_gr", "english_uk", "english_us", "farsi", "french", "polish", "russian" + // NOTE: This setting accepts a single value (e.g. "farsi"), or an array of values (e.g. ["english_uk", "english_us"]) + "farsi", + + + + + + // Loadouts + [ + [ // Spec Ops + MACRO_ENUM_ROLE_SPECOPS, + [["arifle_Katiba_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],[],["hgun_ACPC2_F","muzzle_snds_acp","","",["9Rnd_45ACP_Mag",8],[],""],["U_O_CombatUniform_ocamo",[]],["V_TacVest_brn",[["9Rnd_45ACP_Mag",3,8],["30Rnd_65x39_caseless_green_mag_Tracer",6,30],["HandGrenade",1,1]]],[],"H_ShemagOpen_tan","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Sniper + MACRO_ENUM_ROLE_SNIPER, + [["srifle_DMR_05_tan_f","","acc_flashlight","optic_KHS_blk",["10Rnd_93x64_DMR_05_Mag",10],[],"bipod_02_F_hex"],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_FullGhillie_sard",[]],["V_Chestrig_rgr",[["HandGrenade",1,1],["10Rnd_93x64_DMR_05_Mag",5,10],["16Rnd_9x21_green_Mag",2,17]]],[],"","G_Balaclava_oli",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Assault + MACRO_ENUM_ROLE_ASSAULT, + [["arifle_Katiba_GL_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],["1Rnd_HE_Grenade_shell",1],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[]],["V_TacVest_brn",[["1Rnd_HE_Grenade_shell",4,1],["16Rnd_9x21_green_Mag",2,17],["30Rnd_65x39_caseless_green_mag_Tracer",6,30]]],[],"H_HelmetO_ocamo","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Support + MACRO_ENUM_ROLE_SUPPORT, + [["LMG_Zafir_F","","acc_flashlight","optic_ACO_grn",["150Rnd_762x54_Box_Tracer",150],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["150Rnd_762x54_Box_Tracer",1,150],["HandGrenade",1,1]]],["B_FieldPack_cbr",[["150Rnd_762x54_Box_Tracer",2,150]]],"H_HelmetO_ocamo","G_Bandanna_beast",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_RESUPPLY] + ], + + + + + + [ // Engineer + MACRO_ENUM_ROLE_ENGINEER, + [["arifle_ARX_hex_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[["MineDetector",1]]],["V_TacVest_brn",[["16Rnd_9x21_green_Mag",2,17],["HandGrenade",1,1],["30Rnd_65x39_caseless_green_mag_Tracer",7,30]]],["B_AssaultPack_ocamo",[["SLAMDirectionalMine_Wire_Mag",3,1]]],"H_HelmetO_ocamo","G_Bandanna_blk",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_REPAIR] + ], + + + + + + [ // Medic + MACRO_ENUM_ROLE_MEDIC, + [["arifle_CTAR_hex_F","","acc_flashlight","optic_ACO_grn",["30Rnd_580x42_Mag_Tracer_F",30],[],""],[],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["30Rnd_580x42_Mag_Tracer_F",7,30],["SmokeShell",2,1]]],["B_AssaultPack_cbr",[]],"H_HelmetSpecO_ocamo","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_HEAL] + ], + + + + + + [ // Anti Tank + MACRO_ENUM_ROLE_ANTITANK, + [["arifle_Katiba_F","","acc_flashlight","optic_ACO_grn",["30Rnd_65x39_caseless_green_mag_Tracer",30],[],""],["launch_RPG32_F","","","",["RPG32_F",1],[],""],["hgun_Rook40_F","","","",["16Rnd_9x21_green_Mag",17],[],""],["U_O_CombatUniform_ocamo",[]],["V_TacVest_khk",[["16Rnd_9x21_green_Mag",2,17],["30Rnd_65x39_caseless_green_mag_Tracer",7,30],["HandGrenade",1,1]]],["B_Kitbag_cbr",[["RPG32_F",2,1]]],"H_HelmetO_ocamo","G_Balaclava_blk",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ] + ] +] diff --git a/mission/sides/data_side_resistance.inc b/mission/sides/data_side_resistance.inc new file mode 100644 index 0000000..21d13f4 --- /dev/null +++ b/mission/sides/data_side_resistance.inc @@ -0,0 +1,111 @@ +/* +---------------------------------------- SIDE DATA FILE (INDFOR / RESISTANCE) ---------------------------------------- + +USAGE: + + Modifying loadouts: + 1: Copy the loadout that you want to edit (large array under every role enum macro) + 2: Apply the loadout onto yourself (either using ACE3's Arsenal, or the setUnitLoadout scripting command) + 3: Modify the loadout to your heart's content + 4: Export the edited loadout back into the array format (using the getUnitLoad scripting command) + 5: Paste the loadout into the corresponding role array + 6: Save + 7: Done! + + *DO NOT ADD ANYTHING ELSE* - NOT EVEN A COMMA (,) OR A SEMICOLON (;) AT THE END OF THE FILE! NOTHING! NYET! NADA! */ + +#include "..\..\res\common\macros.inc" + + + + + +[ + // Side name (short format, ideally 5 characters or less) + "AAF", + + // Side name (long format, ideally up to 40 characters) + "Altis Armed Forces", + + // Texture path to a flag representing this side (ArmA 3 has many country flags you can use) + "a3\data_f\Flags\flag_AAF_co.paa", + + // The sets of faces that AI units on this side should use. Intended for immersion. + // Possible values: "african", "african_camo", "asian", "asian_camo", "greek", "greek_camo", "livonian", "persian", "persian_camo", "russian", "tanoan", "white", "white_camo" + // NOTE: This setting accepts a single value (e.g. "greek"), or an array of values (e.g. ["african", "african_camo"]) + "english_gr", + + // The spoken voice presets that AI units on this side should use (for things like automatic call-outs). Intended for immersion. + // Possible values: "chinese", "english_fr", "english_gr", "english_uk", "english_us", "farsi", "french", "polish", "russian" + // NOTE: This setting accepts a single value (e.g. "farsi"), or an array of values (e.g. ["english_uk", "english_us"]) + ["greek", "greek_camo"], + + + + + + // Loadouts + [ + [ // Spec Ops + MACRO_ENUM_ROLE_SPECOPS, + [["arifle_MX_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],[],""],[],["hgun_Pistol_heavy_01_MRD_F","muzzle_snds_acp","acc_flashlight_pistol","",["11Rnd_45ACP_Mag",11],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier2_rgr",[["30Rnd_65x39_caseless_black_mag_Tracer",7,30],["HandGrenade",1,1],["11Rnd_45ACP_Mag",2,11]]],[],"H_Cap_headphones","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Sniper + MACRO_ENUM_ROLE_SNIPER, + [["srifle_DMR_02_camo_F","","acc_flashlight","optic_KHS_blk",["10Rnd_338_Mag",10],[],"bipod_01_F_blk"],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_FullGhillie_sard",[]],["V_Chestrig_rgr",[["HandGrenade",1,1],["16Rnd_9x21_red_Mag",2,17],["10Rnd_338_Mag",5,10]]],[],"","G_Bandanna_tan",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Assault + MACRO_ENUM_ROLE_ASSAULT, + [["arifle_MX_GL_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],["1Rnd_HE_Grenade_shell",1],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier1_rgr",[["30Rnd_65x39_caseless_black_mag_Tracer",7,30],["16Rnd_9x21_red_Mag",2,17],["1Rnd_HE_Grenade_shell",4,1]]],[],"H_HelmetSpecB","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Support + MACRO_ENUM_ROLE_SUPPORT, + [["LMG_03_F","","acc_flashlight","",["200Rnd_556x45_Box_Tracer_Red_F",200],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam_vest",[]],["V_PlateCarrier1_rgr",[["200Rnd_556x45_Box_Tracer_Red_F",2,200],["HandGrenade",1,1],["16Rnd_9x21_red_Mag",2,17]]],["B_Kitbag_mcamo",[]],"H_HelmetB_sand","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_RESUPPLY] + ], + + + + + + [ // Engineer + MACRO_ENUM_ROLE_ENGINEER, + [["SMG_02_F","","acc_flashlight","optic_Aco",["30Rnd_9x21_Mag_SMG_02_Tracer_Red",30],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[["MineDetector",1]]],["V_PlateCarrier2_rgr",[["16Rnd_9x21_red_Mag",2,17],["HandGrenade",1,1],["30Rnd_9x21_Mag_SMG_02_Tracer_Red",9,30]]],["B_AssaultPack_khk",[["SLAMDirectionalMine_Wire_Mag",3,1]]],"H_HelmetB_grass","G_Balaclava_TI_blk_F",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_REPAIR] + ], + + + + + + [ // Medic + MACRO_ENUM_ROLE_MEDIC, + [["arifle_SPAR_01_blk_F","","acc_flashlight","optic_Holosight_blk_F",["30Rnd_556x45_Stanag_Tracer_Red",30],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier1_rgr",[["16Rnd_9x21_red_Mag",2,17],["30Rnd_556x45_Stanag_Tracer_Red",7,30],["SmokeShell",2,1]]],["B_AssaultPack_mcamo",[]],"H_HelmetB_camo","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_HEAL] + ], + + + + + + [ // Anti Tank + MACRO_ENUM_ROLE_ANTITANK, + [["arifle_MX_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],[],""],["launch_MRAWS_olive_rail_F","","","",["MRAWS_HEAT_F",1],[],""],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier_Kerry",[["16Rnd_9x21_red_Mag",2,17],["30Rnd_65x39_caseless_black_mag_Tracer",7,30]]],["B_Kitbag_rgr",[["MRAWS_HEAT_F",2,1]]],"H_HelmetB_snakeskin","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ] + ] +] diff --git a/mission/sides/data_side_west.inc b/mission/sides/data_side_west.inc new file mode 100644 index 0000000..1fc66e3 --- /dev/null +++ b/mission/sides/data_side_west.inc @@ -0,0 +1,111 @@ +/* +---------------------------------------- SIDE DATA FILE (BLUFOR / WEST) ---------------------------------------- + +USAGE: + + Modifying loadouts: + 1: Copy the loadout that you want to edit (large array under every role enum macro) + 2: Apply the loadout onto yourself (either using ACE3's Arsenal, or the setUnitLoadout scripting command) + 3: Modify the loadout to your heart's content + 4: Export the edited loadout back into the array format (using the getUnitLoad scripting command) + 5: Paste the loadout into the corresponding role array + 6: Save + 7: Done! + + *DO NOT ADD ANYTHING ELSE* - NOT EVEN A COMMA (,) OR A SEMICOLON (;) AT THE END OF THE FILE! NOTHING! NYET! NADA! */ + +#include "..\..\res\common\macros.inc" + + + + + +[ + // Side name (short format, ideally 5 characters or less) + "NATO", + + // Side name (long format, ideally up to 40 characters) + "North Atlantic Treaty Organisation", + + // Texture path to a flag representing this side (ArmA 3 has many country flags you can use) + "a3\data_f\Flags\flag_nato_co.paa", + + // The sets of faces that AI units on this side should use. Intended for immersion. + // Possible values: "african", "african_camo", "asian", "asian_camo", "greek", "greek_camo", "livonian", "persian", "persian_camo", "russian", "tanoan", "white", "white_camo" + // NOTE: This setting accepts a single value (e.g. "greek"), or an array of values (e.g. ["african", "african_camo"]) + ["white", "white_camo", "livonian", "african"], + + // The spoken voice presets that AI units on this side should use (for things like automatic call-outs). Intended for immersion. + // Possible values: "chinese", "english_fr", "english_gr", "english_uk", "english_us", "farsi", "french", "polish", "russian" + // NOTE: This setting accepts a single value (e.g. "farsi"), or an array of values (e.g. ["english_uk", "english_us"]) + "english_us", + + + + + + // Loadouts + [ + [ // Spec Ops + MACRO_ENUM_ROLE_SPECOPS, + [["arifle_MX_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],[],""],[],["hgun_Pistol_heavy_01_MRD_F","muzzle_snds_acp","acc_flashlight_pistol","",["11Rnd_45ACP_Mag",11],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier2_rgr",[["30Rnd_65x39_caseless_black_mag_Tracer",7,30],["HandGrenade",1,1],["11Rnd_45ACP_Mag",2,11]]],[],"H_Cap_headphones","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Sniper + MACRO_ENUM_ROLE_SNIPER, + [["srifle_DMR_02_camo_F","","acc_flashlight","optic_KHS_blk",["10Rnd_338_Mag",10],[],"bipod_01_F_blk"],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_FullGhillie_sard",[]],["V_Chestrig_rgr",[["HandGrenade",1,1],["16Rnd_9x21_red_Mag",2,17],["10Rnd_338_Mag",5,10]]],[],"","G_Bandanna_tan",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Assault + MACRO_ENUM_ROLE_ASSAULT, + [["arifle_MX_GL_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],["1Rnd_HE_Grenade_shell",1],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier1_rgr",[["30Rnd_65x39_caseless_black_mag_Tracer",7,30],["16Rnd_9x21_red_Mag",2,17],["1Rnd_HE_Grenade_shell",4,1]]],[],"H_HelmetSpecB","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ], + + + + + + [ // Support + MACRO_ENUM_ROLE_SUPPORT, + [["LMG_03_F","","acc_flashlight","",["200Rnd_556x45_Box_Tracer_Red_F",200],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam_vest",[]],["V_PlateCarrier1_rgr",[["200Rnd_556x45_Box_Tracer_Red_F",2,200],["HandGrenade",1,1],["16Rnd_9x21_red_Mag",2,17]]],["B_Kitbag_mcamo",[]],"H_HelmetB_sand","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_RESUPPLY] + ], + + + + + + [ // Engineer + MACRO_ENUM_ROLE_ENGINEER, + [["SMG_02_F","","acc_flashlight","optic_Aco",["30Rnd_9x21_Mag_SMG_02_Tracer_Red",30],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[["MineDetector",1]]],["V_PlateCarrier2_rgr",[["16Rnd_9x21_red_Mag",2,17],["HandGrenade",1,1],["30Rnd_9x21_Mag_SMG_02_Tracer_Red",9,30]]],["B_AssaultPack_khk",[["SLAMDirectionalMine_Wire_Mag",3,1]]],"H_HelmetB_grass","G_Balaclava_TI_blk_F",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_REPAIR] + ], + + + + + + [ // Medic + MACRO_ENUM_ROLE_MEDIC, + [["arifle_SPAR_01_blk_F","","acc_flashlight","optic_Holosight_blk_F",["30Rnd_556x45_Stanag_Tracer_Red",30],[],""],[],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier1_rgr",[["16Rnd_9x21_red_Mag",2,17],["30Rnd_556x45_Stanag_Tracer_Red",7,30],["SmokeShell",2,1]]],["B_AssaultPack_mcamo",[]],"H_HelmetB_camo","G_Combat",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]], + [MACRO_ENUM_LOADOUT_ABILITY_HEAL] + ], + + + + + + [ // Anti Tank + MACRO_ENUM_ROLE_ANTITANK, + [["arifle_MX_Black_F","","acc_flashlight","optic_Aco",["30Rnd_65x39_caseless_black_mag_Tracer",30],[],""],["launch_MRAWS_olive_rail_F","","","",["MRAWS_HEAT_F",1],[],""],["hgun_P07_F","","","",["16Rnd_9x21_red_Mag",17],[],""],["U_B_CombatUniform_mcam",[]],["V_PlateCarrier_Kerry",[["16Rnd_9x21_red_Mag",2,17],["30Rnd_65x39_caseless_black_mag_Tracer",7,30]]],["B_Kitbag_rgr",[["MRAWS_HEAT_F",2,1]]],"H_HelmetB_snakeskin","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] + ] + ] +] diff --git a/res/common/macros.inc b/res/common/macros.inc index 8ad4b39..b6a684c 100644 --- a/res/common/macros.inc +++ b/res/common/macros.inc @@ -225,7 +225,7 @@ // End Screen #define MACRO_POS_ES_TOP_TEXTSIZE MACRO_UI_TEXTSIZE_5 - #define MACRO_POS_ES_TICKETS_TEXTSIZE MACRO_UI_TEXTSIZE_2 + #define MACRO_POS_ES_TICKETS_TEXTSIZE MACRO_UI_TEXTSIZE_3 // Kill Feed #define MACRO_POS_KF_Y (0.25 * safeZoneH) @@ -305,9 +305,9 @@ #define MACRO_UI_SECTORFLAGS_SCALEOFFSET 0.03 // When zooming in on the map, the sector flag icons will eventually increase to twice their normal size. This offset lets you choose at which zoom level this transition should happen. Larger numbers lean towards "zoomed out", smaller numbers towards "zoomed in" (logarithmic scale). Accepted range is 0 to 1 // Unit Icons - #define MACRO_UI_ICONS3D_MAXDISTANCE_INF 300 // The maximum distance, in meters, at which infantry 3D icons are visible - #define MACRO_UI_ICONS3D_MAXDISTANCE_VEH 1000 // The maximum distance, in meters, at which vehicle 3D icons are visible - #define MACRO_UI_ICONS3D_MAXDISTANCE_MEDIC 80 // The maximum distance, in meters, at which medic-specific infantry icons (units in need of healing, and nearby medics) are visible + #define MACRO_UI_ICONS3D_MAXDISTANCE_INF 500 // The maximum distance, in meters, at which infantry 3D icons are visible + #define MACRO_UI_ICONS3D_MAXDISTANCE_VEH 1500 // The maximum distance, in meters, at which vehicle 3D icons are visible + #define MACRO_UI_ICONS3D_MAXDISTANCE_ROLEACTION 80 // The maximum distance, in meters, at which role-specific infantry icons (healing/resupplying/repairing) are visible // Combat Area #define MACRO_CA_CORNEROFFSET 50000 // How much the combat area should overextend (beyond the map corners), in meters @@ -320,6 +320,8 @@ #define MACRO_FONT_UI_THIN "PuristaLight" #define MACRO_FONT_UI_MEDIUM "PuristaMedium" + #define MACRO_TEXTURE_FLAG_EMPTY "a3\data_f\flags\flag_white_co.paa" + @@ -361,18 +363,22 @@ // Actions #define MACRO_ACT_SPOTTING_MINVISIBILITY 0.5 // The minimum threshold that visibility checks must exceed for a target to be considered spottable (return value of checkVisibility) #define MACRO_ACT_SPOTTING_DURATION 6 // How long, in seconds, a hostile unit/vehicle is visible for when spotted - #define MACRO_ACT_SPOTTING_COOLDOWNDURATION 17 // How long the spotting action remains blocked for, in seconds, when in cooldown - #define MACRO_ACT_SPOTTING_COSTPERUSE 4 // How much it costs to use the spotting action. Divice the cooldown duration by this value to determine how often the action may be triggered (at once) before entering cooldown + #define MACRO_ACT_SPOTTING_COOLDOWNDURATION 15 // How long the spotting action remains blocked for, in seconds, when in cooldown + #define MACRO_ACT_SPOTTING_COSTPERUSE 2 // How much it costs to use the spotting action. Divice the cooldown duration by this value to determine how often the action may be triggered (at once) before entering cooldown + + #define MACRO_ACT_RESUPPLYUNIT_MAXDISTANCE 2 // The distance, in meters, below which support units must be to friendly units in order to resupply them + #define MACRO_ACT_RESUPPLYUNIT_AMOUNT 0.1 // The amount of ammunition that is added to friendly units upon resupplying, expressed as fraction of their loadout's maximum ammunition + #define MACRO_ACT_RESUPPLYUNIT_COOLDOWN 1 // The cooldown duration, in seconds, between consecutive resupply actions #define MACRO_ACT_HEALUNIT_MAXDISTANCE 2 // The distance, in meters, below which medics must be to their patients in order to heal them #define MACRO_ACT_HEALUNIT_AMOUNT 0.15 // The amount of health that is added to patients upon healing, expressed as fraction of their maximum health - #define MACRO_ACT_HEALUNIT_COOLDOWN 1 // The cooldown duration, in seconds, between two consecutive healing actions that medics must wait + #define MACRO_ACT_HEALUNIT_COOLDOWN 1 // The cooldown duration, in seconds, between consecutive healing actions #define MACRO_KEYBIND_TOGGLESPAWNMENU DIK_F1 - #define MACRO_KEYBIND_HEAL DIK_4 - #define MACRO_KEYBIND_REARM DIK_5 - #define MACRO_KEYBIND_REPAIR DIK_6 #define MACRO_KEYBIND_GIVEUP DIK_SPACE + #define MACRO_KEYBIND_RESUPPLY DIK_4 + #define MACRO_KEYBIND_REPAIR DIK_4 + #define MACRO_KEYBIND_HEAL DIK_4 // Combat Area #define MACRO_CA_PLAYER_INTERVAL 0.1 // The interval, in seconds, between two consecutive combat area checks for the player @@ -402,13 +408,16 @@ #define MACRO_AI_DRIVERCONTROL_INTERVAL 0.25 // Interval, in seconds, between two update cycles of the AI driverControl system (each vehicle driver gets updated every this often) #define MACRO_AI_COMMANDER_INTERVAL 0.25 // Interval, in seconds, between two update cycles of the AI commander system (per side; * 3 for a full cycle) #define MACRO_AI_DOMOVEINTERVAL 5 // The interval, in seconds, between two doMove commands being issued to AI units (as they sometimes need to be rescheduled to move) - #define MACRO_AI_MEDICAL_MAXACTIONDISTANCE 20 // The maximum distance, in meters, that AI units are willing to travel in order to perform medical actions on others, or to receive medical attention - #define MACRO_AI_MEDICAL_CHANGESTANCEDISTANCE 8 // The distance to the patient, in meters, below which the unit will adjust its stance to that of the patient - #define MACRO_AI_MEDICAL_PATIENT_STOPDURATIONPERHEAL 2 // For how long an AI unit remains stopped, in seconds, after being healed (prevents them from running away while being healed) + #define MACRO_AI_CARELESSDURATION 5 // The duration, in seconds, during which AI units will be careless when being forced to move + #define MACRO_AI_ROLEACTION_MAXDISTANCE_UNIT 20 // The maximum distance, in meters, below which AI units will consider performing role-specific actions on nearby friendly units (and, for the recipients, to seek out the action-providing units) + #define MACRO_AI_ROLEACTION_CHANGESTANCEDISTANCE 8 // The distance to the recipient, in meters, below which units performing role-specific actions will adjust their stance to that of the recipient + #define MACRO_AI_ROLEACTION_RECIPIENT_STOPDURATION 1 // For how long an AI unit remains stopped, in seconds, after being healed or resupplied, ontop of the performed action's cooldown duration (prevents them from running away while still being healed/resupplied) + #define MACRO_AI_MEDICAL_SELFHEALCOOLDOWN 3 // The duration, in seconds, that a medic must wait after being injured before being allowed to heal itself (to combat heal-spamming) + #define MACRO_AI_MEDICAL_INITIALREVIVEDELAY 2 // The initial delay, in seconds, after a unit has gone unconscious before AI medics are allowed to revive them (to combat revive-spamming) #define MACRO_AI_COMMANDER_STRATEGICVALUE_DECREASEPERGROUP 4 // By how much a sector's current strategic value is divided, for each group ordered to it (per iteration of the commander system) #define MACRO_AI_COMMANDER_SECTOR_MINLEVELOWNED 0.8 // The minimum capturing level of owned sectors for them to be considered as candidates #define MACRO_AI_COMMANDER_WAYPOINT_MINDISTANCE 5 // Minimum distance, in meters, that a potential new waypoint must be from a group's current waypoint to be submitted - #define MACRO_AI_COMMANDER_NEWORDER_COOLDOWN 20 // Average (+/-25%) cooldown, in seconds, between two orders to a group from the AI commander (even if the orders are identical) + #define MACRO_AI_COMMANDER_NEWORDER_COOLDOWN 30 // Average (+/-25%) cooldown, in seconds, between two orders to a group from the AI commander (even if the orders are identical) // Gamemode #define MACRO_GM_KILLASSISTDURATION 10 // How long (in seconds), after damaging an enemy unit, it is possible to receive a kill assist for @@ -460,10 +469,14 @@ // Health #define MACRO_UNIT_HEALTH_FALLDAMAGEHEIGHT 3 // The height in meters beyond which units receive fall damage when jumping off of surfaces (assuming no initial velocity) - #define MACRO_UNIT_HEALTH_THRESHOLDLOW 0.5 // The threshold of health below which a unit is considered to be at low health + #define MACRO_UNIT_HEALTH_THRESHOLDLOW 0.5 // The threshold of health below which a unit is considered to be at low health. Below this threshold, AI units will seek out nearby medics. + + // Ammo + #define MACRO_UNIT_AMMO_THRESHOLDLOW 0.5 // The threshold of overall ammo below which a unit is considered to be at low ammo. Below this threshold, AI units will seek out nearby support units. + #define MACRO_UNIT_AMMO_RESUPPLYCOOLDOWN 5 // The cooldown duration, in seconds, that nits who have been fully resupplied must wait before being allowed to receive supplies again // Framework - #define MACRO_MISSION_FRAMEWORK_VERSION "0.14.662" // The version of this framework + #define MACRO_MISSION_FRAMEWORK_VERSION "0.16.794" // The version of this framework #define MACRO_MISSION_FRAMEWORK_GAMEMODE "Conquest" // Multiplayer settings (referenced by the Steam server browser) @@ -507,6 +520,7 @@ #define MACRO_SCORE_SECTOR_CAPTURING 20 // Triggered for every 10% of level change #define MACRO_SCORE_SECTOR_CAPTURED 300 + #define MACRO_SCORE_RESUPPLY 1 // Per percentage of ammo resupplied #define MACRO_SCORE_HEAL 1 // Per health point healed #define MACRO_SCORE_REVIVE 50 @@ -536,10 +550,10 @@ #define MACRO_ENUM_SCORE_SECTOR_CAPTURED 2 //TODO: Add messages for the following macros - #define MACRO_ENUM_SCORE_HEAL 10 - #define MACRO_ENUM_SCORE_REVIVE 11 - //#define MACRO_ENUM_SCORE_REPAIR 12 - //#define MACRO_ENUM_SCORE_REARM 13 + #define MACRO_ENUM_SCORE_RESUPPLY 10 + #define MACRO_ENUM_SCORE_REPAIR 11 + #define MACRO_ENUM_SCORE_HEAL 12 + #define MACRO_ENUM_SCORE_REVIVE 13 #define MACRO_ENUM_SCORE_SUICIDE 20 #define MACRO_ENUM_SCORE_DESERTING 21 @@ -768,6 +782,7 @@ #define MACRO_ENUM_KF_ICON_MINE 3 #define MACRO_ENUM_KF_ICON_EXPLOSIVE 4 + #define MACRO_KF_ICON_UNKNOWN "a3\ui_f\data\IGUI\Cfg\simpleTasks\types\unknown_ca.paa" #define MACRO_KF_ICON_HEADSHOT "a3\ui_f\data\IGUI\Cfg\simpleTasks\types\kill_ca.paa" #define MACRO_KF_ICON_ROADKILL "a3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_driver_ca.paa" #define MACRO_KF_ICON_MINE "a3\ui_f\data\IGUI\Cfg\simpleTasks\types\mine_ca.paa" @@ -795,13 +810,17 @@ // AI REQUEST PIORITIES // ------------------------------------------------------------------------------------------------------------------------------------------------ - #define MACRO_ENUM_AI_PRIO_BASESETTINGS 0 + #define MACRO_ENUM_AI_PRIO_BASESETTINGS 0 + #define MACRO_ENUM_AI_PRIO_CARELESSMOVE 1 - #define MACRO_ENUM_AI_PRIO_MEDICAL 1 - #define MACRO_ENUM_AI_PRIO_DODGEVEHICLE 2 + #define MACRO_ENUM_AI_PRIO_RESUPPLY 10 + #define MACRO_ENUM_AI_PRIO_REPAIR 11 + #define MACRO_ENUM_AI_PRIO_MEDICAL 12 - #define MACRO_ENUM_AI_PRIO_UNCONSCIOUS 10 - #define MACRO_ENUM_AI_PRIO_SAFESTART 11 + #define MACRO_ENUM_AI_PRIO_DODGEVEHICLE 20 + + #define MACRO_ENUM_AI_PRIO_UNCONSCIOUS 30 + #define MACRO_ENUM_AI_PRIO_SAFESTART 31 @@ -811,10 +830,27 @@ // AI MOVE TYPES // ------------------------------------------------------------------------------------------------------------------------------------------------ - #define MACRO_ENUM_AI_MOVETYPE_HALT 0 - #define MACRO_ENUM_AI_MOVETYPE_REGROUP 1 - #define MACRO_ENUM_AI_MOVETYPE_GOAL 2 - #define MACRO_ENUM_AI_MOVETYPE_ACTION 3 + #define MACRO_ENUM_AI_MOVETYPE_HALT 0 + #define MACRO_ENUM_AI_MOVETYPE_REGROUP 1 + #define MACRO_ENUM_AI_MOVETYPE_GOAL 2 + #define MACRO_ENUM_AI_MOVETYPE_ACTION 3 + + + + + +// ------------------------------------------------------------------------------------------------------------------------------------------------ +// UNIT SOUND ENUMERATIONS +// ------------------------------------------------------------------------------------------------------------------------------------------------ + + #define MACRO_ENUM_SOUND_INVALID -1 + + #define MACRO_ENUM_SOUND_RESUPPLY 0 + #define MACRO_ENUM_SOUND_REPAIR 1 + #define MACRO_ENUM_SOUND_HEAL 2 + + #define MACRO_ENUM_SOUND_VO_DEATH 10 + #define MACRO_ENUM_SOUND_VO_REVIVE 11 @@ -840,3 +876,5 @@ //#define MACRO_DEBUG_NM_NODES_VEH //#define MACRO_DEBUG_NM_NODE_IDS //#define MACRO_DEBUG_NM_PATH + + //#define MACRO_DEBUG_UI_MAP_OVERVIEWMODE diff --git a/res/params.inc b/res/params.inc index aac1ade..f297eb6 100644 --- a/res/params.inc +++ b/res/params.inc @@ -36,19 +36,19 @@ class Param_AI_MaxUnitsPerGroup { }; class Param_AI_SpawnWeight_East { - title = __EVAL("AI spawn bias for side """ + MACRO_SIDE_NAME_EAST + """:"); + title = __EVAL("AI spawn bias for OPFOR:"); values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; default = MACRO_AI_SPAWNWEIGHT_EAST; }; class Param_AI_SpawnWeight_Resistance { - title = __EVAL("AI spawn bias for side """ + MACRO_SIDE_NAME_RESISTANCE + """:"); + title = __EVAL("AI spawn bias for INDFOR:"); values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; default = MACRO_AI_SPAWNWEIGHT_RESISTANCE; }; class Param_AI_SpawnWeight_West { - title = __EVAL("AI spawn bias for side """ + MACRO_SIDE_NAME_WEST + """:"); + title = __EVAL("AI spawn bias for side BLUFOR:"); values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; default = MACRO_AI_SPAWNWEIGHT_WEST; }; diff --git a/res/sounds/headshot.ogg b/res/sounds/headshot.ogg new file mode 100644 index 0000000..c2688bc Binary files /dev/null and b/res/sounds/headshot.ogg differ diff --git a/res/sounds/sounds.inc b/res/sounds/sounds.inc index 27d096a..d6326bc 100644 --- a/res/sounds/sounds.inc +++ b/res/sounds/sounds.inc @@ -1,19 +1,23 @@ class GVAR(HitMarker) : GVAR(Default) { - sound[] = {"res\sounds\hitMarker.ogg", db+5, 1}; + sound[] = {"res\sounds\hitMarker.ogg", 1, 1}; }; class GVAR(EnemyKilled) : GVAR(Default) { - sound[] = {"@a3\3DEN\Data\Sound\CfgSound\notificationDefault.wss", db+5, 1}; + sound[] = {"@a3\3DEN\Data\Sound\CfgSound\notificationDefault.wss", 1, 1}; }; class GVAR(FriendlyKilled) : GVAR(Default) { - sound[] = {"@a3\3DEN\Data\Sound\CfgSound\notificationWarning.wss", db+5, 1}; + sound[] = {"@a3\3DEN\Data\Sound\CfgSound\notificationWarning.wss", 1, 1}; }; class GVAR(SideDefeated) : GVAR(Default) { sound[] = {"@a3\missions_f_beta\data\sounds\firing_drills\drill_finish.wss", db+5, 1}; }; +class GVAR(Headshot) : GVAR(Default) { + sound[] = {"res\sounds\headshot.ogg", 1, 1}; +}; + class GVAR(TicketsLow_Siren) : GVAR(Default) { sound[] = {"@a3\data_f_curator\sound\cfgsounds\air_raid", db+5, 1}; }; @@ -23,13 +27,347 @@ class GVAR(TicketsLow_Siren) : GVAR(Default) { class GVAR(BulletHit_1) : GVAR(Default) { - sound[] = {"res\sounds\hit_1.ogg", db+5, 1}; + sound[] = {"res\sounds\hit_1.ogg", 1, 1}; }; class GVAR(BulletHit_2) : GVAR(Default) { - sound[] = {"res\sounds\hit_2.ogg", db+5, 1}; + sound[] = {"res\sounds\hit_2.ogg", 1, 1}; }; class GVAR(BulletHit_3) : GVAR(Default) { - sound[] = {"res\sounds\hit_3.ogg", db+5, 1}; + sound[] = {"res\sounds\hit_3.ogg", 1, 1}; +}; + + + + + +class GVAR(Unit_Resupply_1) : GVAR(Default) { + sound[] = {"@a3\sounds_f\weapons\Closure\sfx9.wss", db+14, 1}; +}; + +class GVAR(Unit_Resupply_2) : GVAR(Default) { + sound[] = {"@a3\sounds_f\weapons\Closure\Mk200_closure_1.wss", db+12, 1}; +}; + + + + + +class GVAR(Unit_Heal_1) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Other\firstaid_01.wss", db+4, 1.2}; // offset 2.5 +}; + +class GVAR(Unit_Heal_2) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Other\firstaid_02.wss", db+6, 1}; // offset 4.15 +}; + +class GVAR(Unit_Heal_3) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Other\firstaid_03.wss", db+14, 1}; // offset 4.8 +}; + +class GVAR(Unit_Heal_4) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Other\firstaid_04.wss", db+8, 1}; // offset 3.1 +}; + +class GVAR(Unit_Heal_5) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Other\firstaid_06.wss", db+6, 1.2}; // offset 3.6 +}; + +class GVAR(Unit_Heal_6) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\ingame\AinvPknlMstpSlayWpstDnon_medicIn.wss", db+6, 1}; +}; + +class GVAR(Unit_Heal_7) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\ingame\AinvPknlMstpSlayWpstDnon_medicOut.wss", db+6, 1}; +}; + +class GVAR(Unit_Heal_8) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\ingame\AinvPpneMstpSlayWpstDnon_medicIn.wss", db+6, 1}; +}; + +class GVAR(Unit_Heal_9) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\ingame\AinvPpneMstpSlayWpstDnon_medicout.wss", db+6, 1}; +}; + + + + + +class GVAR(Unit_VO_Revive_1) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_choke_01.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_2) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_choke_02.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_3) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_choke_03.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_4) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_choke_04.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_5) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_choke_01.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_6) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_choke_02.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_7) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_choke_03.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_8) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_choke_04.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_9) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_choke_03.wss", db+5, 1}; +}; + +class GVAR(Unit_VO_Revive_10) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_choke_04.wss", db+5, 1}; +}; + + + + +/* +class GVAR(Unit_VO_Death_1) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_01.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_2) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_02.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_3) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_04.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_4) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_08.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_5) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_11.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_6) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_12.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_7) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_hit_02.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_8) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_hit_07.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_9) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_hit_09.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_10) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_hit_10.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_11) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person2\P2_hit_01.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_12) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person2\P2_hit_05.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_13) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person2\P2_hit_07.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_14) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_01.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_15) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_03.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_16) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_11.wss", db+14, 1}; +}; + +class GVAR(Unit_VO_Death_17) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_12.wss", db+14, 1}; +}; +*/ + + + + + +class GVAR(Unit_VO_Death_Loud_1) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male02ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_3.ogg", db+10, 1}; +}; + +class GVAR(Unit_VO_Death_Loud_2) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male04ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_4.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_3) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male05ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_2.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_4) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male05ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_3.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_5) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male05ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_4.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_6) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male06ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_1.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_7) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male06ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_4.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_8) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male09ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_1.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_9) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male11ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_4.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_10) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_01.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_11) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_11.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_12) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_12.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_13) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_hit_03.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_14) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person1\P1_hit_05.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_15) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_03.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Loud_16) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_10.wss", db+14, 1}; +}; + + + + + +class GVAR(Unit_VO_Death_Quiet_1) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male01ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_2.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_2) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male01ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_3.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_3) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male03ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_3.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_4) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male03ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_4.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_5) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male04ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_2.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_6) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male07ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_2.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_7) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male08ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_2.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_8) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male12ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_2.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_9) : GVAR(Default) { + sound[] = {"@a3\dubbing_radio_f\data\eng\Male12ENG\RadioProtocolENG\Combat\200_CombatShouts\ScreamingE_4.ogg", db+10, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_10) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person0\P0_hit_04.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_11) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person2\P2_hit_06.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_12) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person2\P2_hit_08.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_13) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_01.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_14) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_04.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_15) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_06.wss", db+14, 1}; +}; + + +class GVAR(Unit_VO_Death_Quiet_16) : GVAR(Default) { + sound[] = {"@a3\sounds_f\characters\human-sfx\Person3\P3_hit_12.wss", db+14, 1}; }; diff --git a/res/ui/dialogs/rscSpawnMenu.inc b/res/ui/dialogs/rscSpawnMenu.inc index f825378..566cac9 100644 --- a/res/ui/dialogs/rscSpawnMenu.inc +++ b/res/ui/dialogs/rscSpawnMenu.inc @@ -124,7 +124,7 @@ class GVAR(RscSpawnMenu) { // Flag, Left class Flag_Picture_Left : GVAR(RscPictureNoAR) { idc = MACRO_IDC_SM_SIDE_FLAG_LEFT_PICTURE; - text = "a3\data_f\Flags\flag_white_co.paa"; + text = MACRO_TEXTURE_FLAG_EMPTY; x = pixelW * MACRO_POS_SPACER_X; y = pixelH * MACRO_POS_SPACER_Y; w = MACRO_POS_SM_WIDTH / 3 - pixelW * (MACRO_POS_SPACER_X * 2 + 1); diff --git a/res/ui/rscTitles/rscEndScreen.inc b/res/ui/rscTitles/rscEndScreen.inc index ad71b2e..18a0f7c 100644 --- a/res/ui/rscTitles/rscEndScreen.inc +++ b/res/ui/rscTitles/rscEndScreen.inc @@ -37,7 +37,7 @@ class GVAR(RscEndScreen) { // Flag Picture, Left class Flag_Picture_Left : GVAR(RscPictureNoAR) { idc = MACRO_IDC_ES_FLAG_LEFT_PICTURE; - text = "a3\data_f\Flags\flag_white_co.paa"; + text = MACRO_TEXTURE_FLAG_EMPTY; x = 0; y = 0.3; w = 0.3; diff --git a/res/ui/rscTitles/rscSectorHUD.inc b/res/ui/rscTitles/rscSectorHUD.inc index 45f4304..9d75032 100644 --- a/res/ui/rscTitles/rscSectorHUD.inc +++ b/res/ui/rscTitles/rscSectorHUD.inc @@ -38,7 +38,7 @@ class GVAR(RscSectorHUD) { class Sector_Flag : GVAR(RscPictureNoAR) { idc = MACRO_IDC_SHUD_FLAG_PICTURE; - text = "a3\data_f\Flags\flag_white_co.paa"; + text = MACRO_TEXTURE_FLAG_EMPTY; x = MACRO_POS_SHUD_SIDE_WIDTH; w = MACRO_POS_SHUD_FLAG_WIDTH; h = MACRO_POS_SHUD_HEIGHT; diff --git a/res/ui/ui.inc b/res/ui/ui.inc index 423ae28..c39b925 100644 --- a/res/ui/ui.inc +++ b/res/ui/ui.inc @@ -84,11 +84,11 @@ class GVAR(RscScoreFeed_Text_Left) : GVAR(RscScoreFeed_Text) { }; class GVAR(RscKillFeed_Name_Killer) : GVAR(RscText) { - style = ST_LEFT; + style = ST_RIGHT; sizeEx = MACRO_POS_KF_ENTRY_TEXTSIZE; }; class GVAR(RscKillFeed_Name_Victim) : GVAR(RscKillFeed_Name_Killer) { - style = ST_RIGHT; + style = ST_LEFT; }; diff --git a/scripts/actions/fn_act_registerKeybindings.sqf b/scripts/actions/fn_act_registerKeybindings.sqf index 08c87ee..107c65a 100644 --- a/scripts/actions/fn_act_registerKeybindings.sqf +++ b/scripts/actions/fn_act_registerKeybindings.sqf @@ -81,7 +81,37 @@ if (GVAR(hasMod_ace_finger)) then { -// Healing +// Role action: resupplying +[ + MACRO_MISSION_FRAMEWORK_GAMEMODE, + QGVAR(kb_resupplyUnit), + "Resupply unit/self", + {([player] call FUNC(act_tryResupplyUnit)) param [0, false]}, + "", + [MACRO_KEYBIND_RESUPPLY, [false, false, false]], + true, + 0, + false +] call CBA_fnc_addKeybind; + + +/* +// Role action: Repairing +[ + MACRO_MISSION_FRAMEWORK_GAMEMODE, + QGVAR(kb_repairVehicle), + "Repair vehicle", + {([player] call FUNC(act_tryHealUnit)) param [0, false]}, + "", + [MACRO_KEYBIND_REPAIR, [false, false, false]], + true, + 0, + false +] call CBA_fnc_addKeybind; +*/ + + +// Role action: Healing [ MACRO_MISSION_FRAMEWORK_GAMEMODE, QGVAR(kb_healUnit), @@ -109,15 +139,3 @@ GVAR(kb_act_pressed_giveUp) = false; 0, false ] call CBA_fnc_addKeybind; - - - - - -// Add compatibility for ACE's custom events -if (GVAR(hasMod_ace_throwing)) then { - MACRO_FNC_INITVAR(GVAR(EH_ace_firedPlayer), -1); - - ["ace_firedPlayer", GVAR(EH_ace_firedPlayer)] call CBA_fnc_removeEventHandler; - GVAR(EH_ace_firedPlayer) = ["ace_firedPlayer", FUNC(unit_onFired)] call CBA_fnc_addEventHandler; -}; diff --git a/scripts/actions/fn_act_spotTarget.sqf b/scripts/actions/fn_act_spotTarget.sqf index 663a7dd..cab3665 100644 --- a/scripts/actions/fn_act_spotTarget.sqf +++ b/scripts/actions/fn_act_spotTarget.sqf @@ -45,7 +45,7 @@ if (_newCost > _time) exitWith { GVAR(spotTarget_cost) = _time; GVAR(spotTarget_cooldown) = true; - playSoundUI ["addItemFailed", 0.5, 1, true]; + playSoundUI ["addItemFailed", 3, 1, true]; true; }; @@ -141,12 +141,12 @@ if ( if (!isNull _target) then { [_player, _target] remoteExecCall [QFUNC(gm_spotTargetLocal), 0, false]; - playSoundUI ["TacticalPing4", 1, 1, true]; + playSoundUI ["TacticalPing4", 2.5, 1, true]; [_player] call FUNC(anim_gesturePoint); } else { - playSoundUI ["WeaponRestedOn", 1.5, 1, true]; + playSoundUI ["WeaponRestedOn", 5, 1, true]; }; diff --git a/scripts/actions/fn_act_tryHealUnit.sqf b/scripts/actions/fn_act_tryHealUnit.sqf index a52c667..7b9aa77 100644 --- a/scripts/actions/fn_act_tryHealUnit.sqf +++ b/scripts/actions/fn_act_tryHealUnit.sqf @@ -24,10 +24,11 @@ params [ // Preconditions if ( !local _medic - or {!isTouchingGround _medic} + or {vehicle _medic != _medic} or {_medic getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID] != MACRO_ENUM_ROLE_MEDIC} or {[_medic] call FUNC(unit_isReloading)} or {!([_medic] call FUNC(unit_isAlive))} + or {!isTouchingGround _medic} ) exitWith { [false, objNull] }; @@ -61,8 +62,13 @@ private _medicSide = _medic getVariable [QGVAR(side), sideEmpty]; private _candidates = [[_target], allUnits] select (isNull _target); _candidates = _candidates select { _x distanceSqr _medic <= _c_maxActionDistSqr + and {_x == vehicle _x} + and {_x getVariable [QGVAR(isSpawned), false]} and {(_x getVariable [QGVAR(side), sideEmpty]) == _medicSide} - and {[_x] call FUNC(unit_needsHealing)} + and { + _x getVariable [QGVAR(health), 1] < 1 + or {_x getVariable [QGVAR(isUnconscious), false]} + } }; if (_candidates isEqualTo []) exitWith {[false, objNull]}; @@ -90,10 +96,12 @@ if (isNull _target) then { _target = (_candidatesSorted param [0, []]) param [1, objNull]; // If no candidate is found, check if the medic needs healing - if (isNull _target) then { - if ([_medic] call FUNC(unit_needsHealing)) then { - _target = _medic; - }; + if ( + isNull _target + and {_medic == vehicle _medic} + and {_medic getVariable [QGVAR(health), 1] < 1} + ) then { + _target = _medic; }; // Validate the target @@ -121,8 +129,11 @@ if (!isPlayer _medic) then { _medic doWatch _target; }; -// Inform the server about the successful healing action +// Inform the unit about the successful healing action [_medic, _target] remoteExecCall [QFUNC(unit_onHealUnit), _target, false]; +// Handle action sounds +[_medic, MACRO_ENUM_SOUND_HEAL] remoteExecCall [QFUNC(unit_playSound), 0, false]; + // Return the unit that was healed [true, _target]; diff --git a/scripts/actions/fn_act_tryResupplyUnit.sqf b/scripts/actions/fn_act_tryResupplyUnit.sqf new file mode 100644 index 0000000..6b60e42 --- /dev/null +++ b/scripts/actions/fn_act_tryResupplyUnit.sqf @@ -0,0 +1,136 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [LA][GE] + Makes a support unit attempt to resupply a friendly unit. + + If a recipient unit is passed, the support unit will attempt to resupply that unit. Otherwise, the function attempts + to determine a candidate recipient from the support unit's viewing direction. + Arguments: + 0: The support unit + 1: The intended recipient unit (optional, default: objNull) + Returns: + 0: Whether or not the resupply attempt was successful + 1: The unit that was resupplied +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params [ + ["_support", objNull, [objNull]], + ["_recipient", objNull, [objNull]] +]; + +// Preconditions +if ( + !local _support + or {vehicle _support != _support} + or {_support getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID] != MACRO_ENUM_ROLE_SUPPORT} + or {[_support] call FUNC(unit_isReloading)} + or {!([_support] call FUNC(unit_isAlive))} + or {!isTouchingGround _support} +) exitWith { + [false, objNull] +}; + + + + + +// Set up some constants +private _c_maxActionDistSqr = MACRO_ACT_RESUPPLYUNIT_MAXDISTANCE ^ 2; + +// Set up some variables +private _time = time; +private _cooldown = _support getVariable [QGVAR(act_tryResupplyUnit_cooldown), -1]; + + + + + +// Enforce the cooldown +if (_time < _cooldown) exitWith {[false, objNull]}; + +_support setVariable [QGVAR(act_tryResupplyUnit_cooldown), _time + MACRO_ACT_RESUPPLYUNIT_COOLDOWN, false]; + + + + + +// First filter: ensure candidate units are on the same side and need resupplying +private _supportSide = _support getVariable [QGVAR(side), sideEmpty]; +private _candidates = [[_recipient], allUnits] select (isNull _recipient); +_candidates = _candidates select { + _x distanceSqr _support <= _c_maxActionDistSqr + and {_x == vehicle _x} + and {_x getVariable [QGVAR(isSpawned), false]} + and {(_x getVariable [QGVAR(side), sideEmpty]) == _supportSide} + and {_time > _x getVariable [QGVAR(resupplyCooldown), 0]} + and {[_x] call FUNC(lo_getOverallAmmo) < 1} +}; + +if (_candidates isEqualTo []) exitWith {[false, objNull]}; + +// If no recipient was specified, check the candidates +if (isNull _recipient) then { + + // Sort the candidates by score (alignment with the support unit's eye direction) + private _posStart = AGLtoASL positionCameraToWorld [0,0,0]; + private _dir = _posStart vectorFromTo AGLtoASL positionCameraToWorld [0,0,1]; + private _posEnd = _posStart vectorAdd (_dir vectorMultiply viewDistance); + + private _candidatesSorted = []; + private ["_posX", "_angle"]; + { + _posX = AGLtoASL unitAimPositionVisual _x; + _angle = (_posStart vectorFromTo _posX) distanceSqr _dir; + + if (_angle < 1) then { // Roughly a 45° cone + _candidatesSorted pushBack [_angle, _x]; + }; + } forEach _candidates; + + _candidatesSorted sort true; + _recipient = (_candidatesSorted param [0, []]) param [1, objNull]; + + // If no candidate is found, check if the support unit needs resupplying + if ( + isNull _recipient + and {_support == vehicle _support} + and {_time > _support getVariable [QGVAR(resupplyCooldown), 0]} + and {[_support] call FUNC(lo_getOverallAmmo) < 1} + ) then { + _recipient = _support; + }; + + // Validate the recipient + if (isNull _recipient) exitWith {[false, objNull]}; +}; + + + + + +if ( + _support != _recipient + and {stance _support != "PRONE"} +) then { + _support playActionNow "GestureEmpty"; + _support playActionNow "GestureGo"; +} else { + _support action ["TakeWeapon", objNull, "Throw"]; +}; + +// Special behaviour for AI: face the patient +if (!isPlayer _support) then { + _support doWatch _recipient; +}; + +// Inform the unit about the successful resupply action +[_support, _recipient] remoteExecCall [QFUNC(unit_onResupplyUnit), _recipient, false]; + +// Handle action sounds +[_support, MACRO_ENUM_SOUND_RESUPPLY] remoteExecCall [QFUNC(unit_playSound), 0, false]; + +// Return the unit that was resupplied +[true, _recipient]; diff --git a/scripts/ai/fn_ai_generateIdentities.sqf b/scripts/ai/fn_ai_generateIdentities.sqf index 5a1f97b..b0030c4 100644 --- a/scripts/ai/fn_ai_generateIdentities.sqf +++ b/scripts/ai/fn_ai_generateIdentities.sqf @@ -42,7 +42,7 @@ private _totalWeight = 0; private _sideIndexThresholds = []; private _prevSideIndex = -1; private _allNames_copy = []; -private ["_sideFaces", "_sideSpeakers"]; +private ["_faces", "_voices", "_sideFaces", "_sideSpeakers"]; private ["_sideIndex", "_unitIndex", "_sideRoles", "_rolesCount", "_namesCount", "_facesCount", "_speakersCount"]; GVAR(sv_AIIdentities) = []; @@ -52,14 +52,17 @@ GVAR(sv_AIIdentities) = []; // Compile the data for all required sides { - _x params ["_side", "_sideWeight", "_faces", "_speakers"]; + _x params ["_side", "_sideWeight"]; + + _faces = missionNamespace getVariable [format [QGVAR(aiFaces_%1), _side], []]; + _voices = missionNamespace getVariable [format [QGVAR(aiVoices_%1), _side], []]; // Only continue if this side exists if (_side in GVAR(sides)) then { _totalWeight = _totalWeight + (_sideWeight max 0); _sideFaces = [_faces] call FUNC(ai_getFaces); - _sideSpeakers = [_speakers] call FUNC(ai_getVoices); + _sideSpeakers = [_voices] call FUNC(ai_getVoices); } else { _sideFaces = []; _sideSpeakers = []; @@ -73,9 +76,9 @@ GVAR(sv_AIIdentities) = []; _sideIndexThresholds pushBack _totalWeight; } forEach [ - [east, GVAR(param_AI_spawnWeight_east), MACRO_AI_FACES_EAST, MACRO_AI_VOICES_EAST], - [resistance, GVAR(param_AI_spawnWeight_resistance), MACRO_AI_FACES_RESISTANCE, MACRO_AI_VOICES_RESISTANCE], - [west, GVAR(param_AI_spawnWeight_west), MACRO_AI_FACES_WEST, MACRO_AI_VOICES_WEST] + [east, GVAR(param_AI_spawnWeight_east)], + [resistance, GVAR(param_AI_spawnWeight_resistance)], + [west, GVAR(param_AI_spawnWeight_west)] ]; // Scale to the AI count @@ -137,15 +140,15 @@ for "_i" from 0 to GVAR(param_AI_maxCount) - 1 do { and {_speakersCount > 0} ) then { // Generate a new identity and add it to the global array - GVAR(sv_AIIdentities) pushBack [ // NOTE: Refer to the AI identity enums in macros.hpp! The order MUST match! - _i, // MACRO_ENUM_AIIDENTITY_UNITINDEX - _sideIndex, // MACRO_ENUM_AIIDENTITY_SIDEINDEX - floor (_unitIndex / GVAR(param_AI_maxUnitsPerGroup)), // MACRO_ENUM_AIIDENTITY_GROUPINDEX - (_unitIndex mod GVAR(param_AI_maxUnitsPerGroup)) == 0, // MACRO_ENUM_AIIDENTITY_ISLEADER - _sideRoles deleteAt floor (random _rolesCount), // MACRO_ENUM_AIIDENTITY_ROLE - _allNames_copy deleteAt floor (random _namesCount), // MACRO_ENUM_AIIDENTITY_NAME - _sideFaces deleteAt floor (random _facesCount), // MACRO_ENUM_AIIDENTITY_FACE - _sideSpeakers deleteAt floor (random _speakersCount) // MACRO_ENUM_AIIDENTITY_SPEAKER + GVAR(sv_AIIdentities) pushBack [ // NOTE: Refer to the AI identity enums in macros.hpp! The order MUST match! + _i, // MACRO_ENUM_AIIDENTITY_UNITINDEX + _sideIndex, // MACRO_ENUM_AIIDENTITY_SIDEINDEX + floor (_unitIndex / GVAR(param_AI_maxUnitsPerGroup)), // MACRO_ENUM_AIIDENTITY_GROUPINDEX + (_unitIndex mod GVAR(param_AI_maxUnitsPerGroup)) == 0, // MACRO_ENUM_AIIDENTITY_ISLEADER + _sideRoles deleteAt floor (random _rolesCount), // MACRO_ENUM_AIIDENTITY_ROLE + _allNames_copy deleteAt floor (random _namesCount), // MACRO_ENUM_AIIDENTITY_NAME + _sideFaces deleteAt floor (random _facesCount), // MACRO_ENUM_AIIDENTITY_FACE + _sideSpeakers deleteAt floor (random _speakersCount) // MACRO_ENUM_AIIDENTITY_SPEAKER ]; } else { GVAR(sv_AIIdentities) pushBack [ diff --git a/scripts/ai/fn_ai_resetRespawnTime.sqf b/scripts/ai/fn_ai_resetRespawnTime.sqf index aa03e98..d157b95 100644 --- a/scripts/ai/fn_ai_resetRespawnTime.sqf +++ b/scripts/ai/fn_ai_resetRespawnTime.sqf @@ -31,9 +31,12 @@ private _time = time; -// Interface with unitControl to allow unconscious units to give up and bleed out if ([_unit, true] call FUNC(unit_isAlive)) then { + // Interface with unitControl to allow unconscious units to give up and bleed out _unit setVariable [QGVAR(ai_unitControl_unconsciousState_respawnTime), _time + GVAR(param_gm_unit_respawnDelay), false]; + + // Interface with subSys_handleMedical to prevent revive spam + _unit setVariable [QGVAR(ai_unitControl_handleMedical_reviveTime), _time + MACRO_AI_MEDICAL_INITIALREVIVEDELAY, false]; }; // Special case: the server is in charge of respawning units, and uses additional variables. diff --git a/scripts/ai/fn_ai_sys_commander.sqf b/scripts/ai/fn_ai_sys_commander.sqf index 22ddf68..5e85bd8 100644 --- a/scripts/ai/fn_ai_sys_commander.sqf +++ b/scripts/ai/fn_ai_sys_commander.sqf @@ -213,11 +213,11 @@ GVAR(ai_sys_commander_EH) = addMissionEventHandler ["EachFrame", { if (_canReceiveOrders) then { if (_attackPoints isEqualTo []) then { if !(_waypointPos distanceSqr _newWaypointPos < MACRO_AI_COMMANDER_WAYPOINT_MINDISTANCE ^ 2) then { - _group addWaypoint [ASLtoATL _newWaypointPos, 0, 1]; + _group addWaypoint [_newWaypointPos, -1, 1]; // Exact placement in format ASL }; } else { if ((_attackPoints findIf {_waypointPos distanceSqr _x < MACRO_AI_COMMANDER_WAYPOINT_MINDISTANCE ^ 2}) < 0) then { - _group addWaypoint [ASLtoATL selectRandom _attackPoints, 0, 1]; + _group addWaypoint [selectRandom _attackPoints, -1, 1]; // Exact placement in format ASL }; }; @@ -236,8 +236,8 @@ GVAR(ai_sys_commander_EH) = addMissionEventHandler ["EachFrame", { (GVAR(debug_ai_commander_data) # GVAR(ai_sys_commander_side_index)) set [_forEachIndex, [ groupId _group, _debug_colour, - ASLtoATL _centerPos, - ASLtoATL _waypointPos, + ASLtoAGL _centerPos, + ASLtoAGL _waypointPos, [GVAR(ai_sys_commander_side)] call FUNC(gm_getFlagTexture) ]]; #endif @@ -344,8 +344,8 @@ removeMissionEventHandler ["Draw3D", GVAR(ai_sys_commander_EH_draw3D_debug)]; private _distMul = 0.25 * (_centerPos distance2D _waypointPos); for "_i" from 0 to _count - 1 do { drawLine3D [ - ASLtoATL (ATLtoASL _centerPos vectorAdd (_diff vectorMultiply (_i / _count)) vectorAdd [0, 0, _distMul * sin (180 * _i / _count)]), - ASLtoATL (ATLtoASL _centerPos vectorAdd (_diff vectorMultiply ((_i + 1) / _count)) vectorAdd [0, 0, _distMul * sin (180 * (_i + 1) / _count)]), + ASLtoAGL (ATLtoASL _centerPos vectorAdd (_diff vectorMultiply (_i / _count)) vectorAdd [0, 0, _distMul * sin (180 * _i / _count)]), + ASLtoAGL (ATLtoASL _centerPos vectorAdd (_diff vectorMultiply ((_i + 1) / _count)) vectorAdd [0, 0, _distMul * sin (180 * (_i + 1) / _count)]), [0, 1, 0.0, 1] ]; }; diff --git a/scripts/ai/fn_ai_sys_groupKnowledge.sqf b/scripts/ai/fn_ai_sys_groupKnowledge.sqf index b9ced0f..9fb3deb 100644 --- a/scripts/ai/fn_ai_sys_groupKnowledge.sqf +++ b/scripts/ai/fn_ai_sys_groupKnowledge.sqf @@ -20,7 +20,7 @@ // Define some macros -#define MACRO_SYS_GROUPKNOWLEDGE_INTERVAL 3 +#define MACRO_SYS_GROUPKNOWLEDGE_INTERVAL 20 #define MACRO_AI_MINKNOWLEDGE 0.1 // Set up some variables diff --git a/scripts/ai/fn_ai_sys_unitControl.sqf b/scripts/ai/fn_ai_sys_unitControl.sqf index f871f4a..bfc707f 100644 --- a/scripts/ai/fn_ai_sys_unitControl.sqf +++ b/scripts/ai/fn_ai_sys_unitControl.sqf @@ -52,7 +52,7 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { private _missionSafeStart = (GVAR(missionState) != MACRO_ENUM_MISSION_LIVE); // Update candidate units - private ["_unit", "_role", "_side", "_group", "_leader", "_isLeader", "_isLeaderPlayer", "_unitPos", "_unitVeh", "_isInVehicle", "_changedVehicle", "_isDriver", "_isUnconscious", "_actionPos", "_moveType"]; + private ["_unit", "_role", "_side", "_group", "_leader", "_isLeader", "_isLeaderPlayer", "_unitPos", "_unitVeh", "_isInVehicle", "_changedVehicle", "_isDriver", "_isUnconscious", "_isReloading", "_actionPos", "_moveType", "_switchToCareless"]; for "_unitIndex" from GVAR(ai_sys_unitControl_index) to 0 step -1 do { scopeName QGVAR(ai_sys_unitControl_loop); @@ -82,8 +82,9 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { _changedVehicle = (_isInVehicle != _unit getVariable [QGVAR(ai_sys_unitControl_isInVehicle), _isInVehicle]); _isDriver = (_isInVehicle and {_unit == driver _unitVeh}); _isUnconscious = _unit getVariable [QGVAR(isUnconscious), false]; + _isReloading = [_unit] call FUNC(unit_isReloading); - // Basic AI settings + // Base AI settings (may be overriden by subsystems) _unit setCombatBehaviour "AWARE"; _unit setSpeedMode "FULL"; _unit allowFleeing 0; @@ -106,8 +107,9 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { }; // Define shared variables - _actionPos = []; - _moveType = _unit getVariable [QGVAR(ai_sys_unitControl_moveType), MACRO_ENUM_AI_MOVETYPE_HALT]; + _actionPos = []; + _moveType = _unit getVariable [QGVAR(ai_sys_unitControl_moveType), MACRO_ENUM_AI_MOVETYPE_HALT]; + _switchToCareless = false; scopeName QGVAR(ai_sys_unitControl_loop_live); @@ -127,6 +129,9 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { // Handle medical actions (healing other units / seeking medical attention) #include "unitControl\subSys_handleMedical.sqf"; + // Handle support actions (resupplying units / seeking support units) + #include "unitControl\subSys_handleResupply.sqf"; + // ---- Goals ---- // Determine where the unit should be moving (waypoint, individual orders, etc) #include "unitControl\subSys_planNextMovePos.sqf"; @@ -159,7 +164,7 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { and {[_x, true] call FUNC(unit_isAlive)} // Include unconscious units }; - private ["_sideX", "_unitsX", "_unitsAlive", "_unitsInjured", "_unitsUnconscious"]; + private ["_sideX", "_unitsX", "_unitsAlive", "_unitsLowAmmmo", "_unitsNearFullAmmo", "_unitsLowHealth", "_unitsNearHealthy", "_unitsUnconscious", "_unitsSupport", "_unitsEngineer", "_unitsMedic", "_roleX", "_ammoX", "_healthX"]; { if (_x == sideEmpty) then { continue; @@ -167,12 +172,15 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { _sideX = _x; _unitsX = _allUnits select {_x getVariable [QGVAR(side), sideEmpty] == _sideX}; - _unitsAlive = []; // All alive units on this side - _unitsInjured = []; // Alive Units on this side who are injured - _unitsUnconscious = []; // Unconscious units on this side - _unitsSupport = []; // All alive support units on this side - _unitsEngineer = []; // All alive engineer units on this side - _unitsMedic = []; // All alive medic units on this side + _unitsAlive = []; // All alive units on this side + _unitsLowAmmmo = []; // Alive units on this side who are considered low on ammo (below threshold) + _unitsNearFullAmmo = []; // Alive units on this side who need ammo, but are not low on ammo (near full) + _unitsLowHealth = []; // Alive Units on this side who are considered low on health (below threshold) + _unitsNearHealthy = []; // Alive Units on this side who are injured, but not low on health (near full) + _unitsUnconscious = []; // Unconscious units on this side + _unitsSupport = []; // All alive support units on this side + _unitsEngineer = []; // All alive engineer units on this side + _unitsMedic = []; // All alive medic units on this side { if (vehicle _x != _x) then { @@ -180,15 +188,39 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { }; if (_x getVariable [QGVAR(isUnconscious), false]) then { - _unitsUnconscious pushBack _x; + if (_time > _x getVariable [QGVAR(ai_unitControl_handleMedical_reviveTime), -1]) then { + _unitsUnconscious pushBack _x; + }; } else { _unitsAlive pushBack _x; + _roleX = _x getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID]; + _ammoX = [_x] call FUNC(lo_getOverallAmmo); + _healthX = _x getVariable [QGVAR(health), 1]; + + if ( + _roleX != MACRO_ENUM_ROLE_SUPPORT + and {_ammoX < 1} + and {_time > _x getVariable [QGVAR(resupplyCooldown), 0]} + ) then { + if (_ammoX < MACRO_UNIT_AMMO_THRESHOLDLOW) then { + _unitsLowAmmmo pushBack _x; + } else { + _unitsNearFullAmmo pushBack _x; + }; + }; - if (_x getVariable [QGVAR(health), 1] < 1) then { - _unitsInjured pushBack _x; + if ( + _roleX != MACRO_ENUM_ROLE_MEDIC + and {_healthX < 1} + ) then { + if (_healthX < MACRO_UNIT_HEALTH_THRESHOLDLOW) then { + _unitsLowHealth pushBack _x; + } else { + _unitsNearHealthy pushBack _x; + }; }; - switch (_x getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID]) do { + switch (_roleX) do { case MACRO_ENUM_ROLE_SUPPORT: {_unitsSupport pushBack _x}; case MACRO_ENUM_ROLE_ENGINEER: {_unitsEngineer pushBack _x}; case MACRO_ENUM_ROLE_MEDIC: {_unitsMedic pushBack _x}; @@ -197,7 +229,10 @@ GVAR(ai_sys_unitControl_EH) = addMissionEventHandler ["EachFrame", { } forEach _unitsX; GVAR(ai_sys_unitControl_cache) set [format ["unitsAlive_%1", _sideX], _unitsAlive]; - GVAR(ai_sys_unitControl_cache) set [format ["unitsInjured_%1", _sideX], _unitsInjured]; + GVAR(ai_sys_unitControl_cache) set [format ["unitsLowAmmo_%1", _sideX], _unitsLowAmmmo]; + GVAR(ai_sys_unitControl_cache) set [format ["unitsNearFullAmmo_%1", _sideX], _unitsNearFullAmmo]; + GVAR(ai_sys_unitControl_cache) set [format ["unitsLowHealth_%1", _sideX], _unitsLowHealth]; + GVAR(ai_sys_unitControl_cache) set [format ["unitsNearHealthy_%1", _sideX], _unitsNearHealthy]; GVAR(ai_sys_unitControl_cache) set [format ["unitsUnconscious_%1", _sideX], _unitsUnconscious]; GVAR(ai_sys_unitControl_cache) set [format ["unitsSupport_%1", _sideX], _unitsSupport]; GVAR(ai_sys_unitControl_cache) set [format ["unitsEngineer_%1", _sideX], _unitsEngineer]; diff --git a/scripts/ai/unitControl/subSys_claimVehicle.sqf b/scripts/ai/unitControl/subSys_claimVehicle.sqf index 293a48c..5c6274f 100644 --- a/scripts/ai/unitControl/subSys_claimVehicle.sqf +++ b/scripts/ai/unitControl/subSys_claimVehicle.sqf @@ -196,8 +196,9 @@ if ( if (_isClaimedVehValid) then { // Custom action: move to the claimed vehicle - _actionPos = getPosWorld _claimedVeh; - _actionPos = _actionPos vectorAdd [1 - random 2, 1 - random 2, 0]; // Randomness to help the unit get close enough + _actionPos = getPosWorld _claimedVeh; + _actionPos = _actionPos vectorAdd [1 - random 2, 1 - random 2, 0]; // Randomness to help the unit get close enough + _switchToCareless = true; // Allows switching to careless mode in order to move // Mount up if (_unitPos distanceSqr _actionPos < MACRO_AI_CLAIMVEHICLE_MAXDIST_MOUNT ^ 2) then { diff --git a/scripts/ai/unitControl/subSys_handleMedical.sqf b/scripts/ai/unitControl/subSys_handleMedical.sqf index 29cc93d..03ce0dd 100644 --- a/scripts/ai/unitControl/subSys_handleMedical.sqf +++ b/scripts/ai/unitControl/subSys_handleMedical.sqf @@ -5,8 +5,8 @@ if (!_isInVehicle and {_actionPos isEqualTo []}) then { // Set up some constants private _c_maxActionDistSqr = MACRO_ACT_HEALUNIT_MAXDISTANCE ^ 2; - private _c_maxMedicalDistSqr = MACRO_AI_MEDICAL_MAXACTIONDISTANCE ^ 2; - private _c_changeStanceDistSqr = MACRO_AI_MEDICAL_CHANGESTANCEDISTANCE ^ 2; + private _c_maxMedicalDistSqr = MACRO_AI_ROLEACTION_MAXDISTANCE_UNIT ^ 2; + private _c_changeStanceDistSqr = MACRO_AI_ROLEACTION_CHANGESTANCEDISTANCE ^ 2; // Set up some variables private _health = _unit getVariable [QGVAR(health), 1]; @@ -16,49 +16,82 @@ if (!_isInVehicle and {_actionPos isEqualTo []}) then { if (_role == MACRO_ENUM_ROLE_MEDIC) then { - private _patient = objNull; - private _patientDistSqr = 0; + private _patientDistSqr = _c_maxMedicalDistSqr; + private _selfHealTime = _unit getVariable [QGVAR(ai_unitControl_handleMedical_selfHealTime), -1]; + private ["_distSqrX"]; + + // Prioritise self-care (a live medic is a good medic) + if (_health < 1) then { + + if (_selfHealTime < 0) then { + _selfHealTime = _time + MACRO_AI_MEDICAL_SELFHEALCOOLDOWN; + _unit setVariable [QGVAR(ai_unitControl_handleMedical_selfHealTime), _selfHealTime, false]; + }; + if (_time > _selfHealTime) then { + [_unit, _unit] call FUNC(act_tryHealUnit); + _switchToCareless = true; // Go into careless mode while healing - // Prioritise self-care - if ([_unit] call FUNC(unit_needsHealing)) then { - _patient = _unit; + // Nobody to move towards + breakTo QGVAR(ai_sys_unitControl_loop_live); + }; } else { - // Look for nearby injured units to heal, while priorising unconscious units over wounded ones - private _unitsInjured = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsInjured_%1", _side], []]; - private _unitsUnconscious = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsUnconscious_%1", _side], []]; - _patientDistSqr = _c_maxMedicalDistSqr; - private ["_distSqrX"]; + if (_selfHealTime > 0) then { + _unit setVariable [QGVAR(ai_unitControl_handleMedical_selfHealTime), -1, false]; + }; + }; + + // If the medic is healthy, prioritise reviving unconscious units + private _unitsUnconscious = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsUnconscious_%1", _side], []]; + + { + _distSqrX = _x distanceSqr _unit; + + if (_distSqrX < _patientDistSqr) then { + _patientDistSqr = _distSqrX; + _patient = _x; + }; + } forEach _unitsUnconscious; + + // If no units are unconscious, prioritise healing units who are low on health + if (isNull _patient) then { + private _unitsLowHealth = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsLowHealth_%1", _side], []]; { - _distSqrX = _x distanceSqr _unit; + _distSqrX = _unit distanceSqr _x; if (_distSqrX < _patientDistSqr) then { _patientDistSqr = _distSqrX; _patient = _x; }; - } forEach _unitsUnconscious; + } forEach _unitsLowHealth; + }; - if (isNull _patient) then { - { - _distSqrX = _unit distanceSqr _x; + // If no units are low on health, heal nearby units that aren't fully healed + if (isNull _patient) then { + private _unitsNearHealthy = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsNearHealthy_%1", _side], []]; + _patientDistSqr = _c_maxActionDistSqr; - if (_distSqrX < _patientDistSqr) then { - _patientDistSqr = _distSqrX; - _patient = _x; - }; - } forEach _unitsInjured; - }; + { + _distSqrX = _unit distanceSqr _x; + + if (_distSqrX < _patientDistSqr) then { + _patientDistSqr = _distSqrX; + _patient = _x; + }; + } forEach _unitsNearHealthy; }; + // If still nobody needs healing, exit the subsystem if (isNull _patient) then { breakTo QGVAR(ai_sys_unitControl_loop_live); }; - // If we found a patient, head to them and try to heal them + // Head to the patient _actionPos = getPosWorld _patient; + // Match their stance if ( _patientDistSqr < _c_changeStanceDistSqr and {_patient getVariable [QGVAR(isUnconscious), false] or {stance _patient != "STAND"}} @@ -70,24 +103,26 @@ if (!_isInVehicle and {_actionPos isEqualTo []}) then { [_unit, _patient] call FUNC(act_tryHealUnit); _shouldStop = true; } else { - _actionPos = _actionPos vectorAdd [1 - random 2, 1 - random 2, 0]; // Randomness to help the unit get close enough + _actionPos = _actionPos vectorAdd [1 - random 2, 1 - random 2, 0]; // Randomness to help the unit get close enough + _switchToCareless = true; // Allows switching to careless mode in order to move }; } else { - // If the unit is healthy enough, nothing needs to be done - if (_health >= MACRO_UNIT_HEALTH_THRESHOLDLOW) then { - - // If the unit is in the middle of being healed, stop a little long - if (_health < 1 and {_time < _unit getVariable [QGVAR(ai_unitControl_handleMedical_stopTime), -1]}) then { - _shouldStop = true; - }; + // If the unit is in the middle of being healed, stay put + if (_time < _unit getVariable [QGVAR(ai_unitControl_handleMedical_stopTime), -1]) then { + _shouldStop = true; + breakTo QGVAR(ai_sys_unitControl_loop_live); + }; + // Only look for a medic when low on health + if (_health > MACRO_UNIT_HEALTH_THRESHOLDLOW) then { breakTo QGVAR(ai_sys_unitControl_loop_live); }; - private _medic = objNull; - private _medicDistSqr = _c_maxMedicalDistSqr; + private _medic = objNull; + private _medicDistSqr = _c_maxMedicalDistSqr; + private _unitsMedic = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsMedic_%1", _side], []]; private ["_distSqrX"]; { @@ -97,13 +132,14 @@ if (!_isInVehicle and {_actionPos isEqualTo []}) then { _medicDistSqr = _distSqrX; _medic = _x; }; - } forEach (GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsMedic_%1", _side], []]); + } forEach _unitsMedic; + // If there is no medic in the vicinity, exit the subsystem if (isNull _medic) then { breakTo QGVAR(ai_sys_unitControl_loop_live); }; - // If we found a nearby medic, head towards them + // Head to the medic _actionPos = getPosWorld _medic; if (_medicDistSqr < _c_changeStanceDistSqr and {stance _medic != "STAND"}) then { @@ -124,3 +160,7 @@ if (!_isInVehicle and {_actionPos isEqualTo []}) then { // Handle stopping [_shouldStop, MACRO_ENUM_AI_PRIO_MEDICAL, _unit, ["PATH"], false] call FUNC(ai_toggleFeature); + +if (_shouldStop) then { + _unit setUnitPos "MIDDLE"; +}; diff --git a/scripts/ai/unitControl/subSys_handleResupply.sqf b/scripts/ai/unitControl/subSys_handleResupply.sqf new file mode 100644 index 0000000..5b507bf --- /dev/null +++ b/scripts/ai/unitControl/subSys_handleResupply.sqf @@ -0,0 +1,141 @@ +private _shouldStop = false; + +// Only handle resupply actions if not already doing something else, including driving +if (!_isInVehicle and {_actionPos isEqualTo []}) then { + + // Set up some constants + private _c_maxActionDistSqr = MACRO_ACT_RESUPPLYUNIT_MAXDISTANCE ^ 2; + private _c_maxResupplyDistSqr = MACRO_AI_ROLEACTION_MAXDISTANCE_UNIT ^ 2; + private _c_changeStanceDistSqr = MACRO_AI_ROLEACTION_CHANGESTANCEDISTANCE ^ 2; + + // Set up some variables + private _ammo = [_unit] call FUNC(lo_getOverallAmmo); + + + + + + if (_role == MACRO_ENUM_ROLE_SUPPORT) then { + private _recipient = objNull; + private _recipientDistSqr = _c_maxResupplyDistSqr; + private _unitsLowAmmmo = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsLowAmmo_%1", _side], []]; + private ["_distSqrX"]; + + // Prioritise resupplying units who are low on ammo + { + if (_x == _unit) then { + continue; + }; + + _distSqrX = _x distanceSqr _unit; + + if (_distSqrX < _recipientDistSqr) then { + _recipientDistSqr = _distSqrX; + _recipient = _x; + }; + } forEach _unitsLowAmmmo; + + if (isNull _recipient) then { + private _unitsNearFullAmmo = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsNearFullAmmo_%1", _side], []]; + _recipientDistSqr = _c_maxActionDistSqr; + + // If no units are low on ammo, resupply nearby units that aren't fully resupplied + { + if (_x == _unit) then { + continue; + }; + + _distSqrX = _x distanceSqr _unit; + + if (_distSqrX < _recipientDistSqr) then { + _recipientDistSqr = _distSqrX; + _recipient = _x; + }; + } forEach _unitsNearFullAmmo; + }; + + // If no nearby units need resupplying, consider resupplying oneself + if (isNull _recipient) then { + if (_ammo < 1) then { + [_unit, _unit] call FUNC(act_tryResupplyUnit); + _switchToCareless = true; // Go into careless mode while resupplying + }; + + // Nobody to move towards + breakTo QGVAR(ai_sys_unitControl_loop_live); + }; + + // Head to the recipients + _actionPos = getPosWorld _recipient; + + // Match their stance + if (_recipientDistSqr < _c_changeStanceDistSqr and {stance _recipient != "STAND"}) then { + _unit setUnitPos "MIDDLE"; + }; + + if (_recipientDistSqr < _c_maxActionDistSqr) then { + [_unit, _recipient] call FUNC(act_tryResupplyUnit); + _shouldStop = true; + } else { + _actionPos = _actionPos vectorAdd [1 - random 2, 1 - random 2, 0]; // Randomness to help the unit get close enough + _switchToCareless = true; // Allows switching to careless mode in order to move + }; + + } else { + + // If the unit is in the middle of being resupplied, stay put + if (_time < _unit getVariable [QGVAR(ai_unitControl_handleResupply_stopTime), -1]) then { + _shouldStop = true; + breakTo QGVAR(ai_sys_unitControl_loop_live); + }; + + // Only look for a support when low on ammo + if (_ammo >= MACRO_UNIT_AMMO_THRESHOLDLOW) then { + breakTo QGVAR(ai_sys_unitControl_loop_live); + }; + + private _support = objNull; + private _supportDistSqr = _c_maxResupplyDistSqr; + private _unitsSupport = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsSupport_%1", _side], []]; + private ["_distSqrX"]; + + { + _distSqrX = _unit distanceSqr _x; + + if (_distSqrX < _supportDistSqr) then { + _supportDistSqr = _distSqrX; + _support = _x; + }; + } forEach _unitsSupport; + + // If there is no support in the vicinity, exit the subsystem + if (isNull _support) then { + breakTo QGVAR(ai_sys_unitControl_loop_live); + }; + + // Head to the support + _actionPos = getPosWorld _support; + + // Crouch while awaiting resupply + if (_supportDistSqr < _c_changeStanceDistSqr and {stance _support != "STAND"}) then { + _unit setUnitPos "MIDDLE"; + }; + + if (_supportDistSqr < _c_maxActionDistSqr) then { + _shouldStop = true; + } else { + _actionPos = _actionPos vectorAdd [1 - random 2, 1 - random 2, 0]; // Randomness to help the unit get close enough + }; + }; +}; + + + + + +// Handle stopping +[_shouldStop, MACRO_ENUM_AI_PRIO_RESUPPLY, _unit, ["PATH"], false] call FUNC(ai_toggleFeature); + +if (_shouldStop) then { + _unit setUnitPos "MIDDLE"; +}; diff --git a/scripts/ai/unitControl/subSys_moveToPos.sqf b/scripts/ai/unitControl/subSys_moveToPos.sqf index 8351901..cbc9dc7 100644 --- a/scripts/ai/unitControl/subSys_moveToPos.sqf +++ b/scripts/ai/unitControl/subSys_moveToPos.sqf @@ -8,7 +8,7 @@ private _movePos = (switch (_moveType) do { case MACRO_ENUM_AI_MOVETYPE_ACTION: {_actionPos}; default {[]}; // No move order }); -private _pathData = _unit getVariable [QGVAR(ai_unitControl_moveToPos_pathData), []]; +private _pathData = _unit getVariable [QGVAR(ai_unitControl_moveToPos_pathData), []]; @@ -124,10 +124,34 @@ if (!_isInVehicle) then { _unit setVariable [QGVAR(ai_unitControl_moveToPos_nextMoveTime), _time + MACRO_AI_DOMOVEINTERVAL, false]; _unit setVariable [QGVAR(ai_unitControl_planNextMovePos_destinationPos), _newDestinationPos, false]; }; -}; + // If the unit has a goal, try to keep it moving by periodically setting it to be careless. + if (_switchToCareless or {_unit getVariable [QGVAR(ai_unitControl_moveToPos_finished), false]}) then { + breakTo QGVAR(ai_sys_unitControl_loop_live); + }; + + // For the first couple seconds after computing a path, the unit is made careless to get it moving. + // Also move while reloading, as the unit is combat ineffective during that time. + // Finally, also move post-reloading for a brief time + _switchToCareless = ( + _isReloading + or { + (stance _unit) in ["STAND", "CROUCH"] + and { + _time < (_unit getVariable [QGVAR(ai_unitControl_moveToPos_nextUpdate), 0]) + MACRO_AI_CARELESSDURATION - MACRO_AI_PATHFINDINTERVAL_INF + or {_time < _unit getVariable [QGVAR(ai_unitControl_moveToPos_reloadTime), 0]} + } + } + ); +}; + +if (_switchToCareless) then { + _unit setCombatBehaviour "CARELESS"; +}; +[_switchToCareless, MACRO_ENUM_AI_PRIO_CARELESSMOVE, _unit, ["TARGET", "AUTOTARGET", "COVER", "CHECKVISIBLE"], false] call FUNC(ai_toggleFeature); + diff --git a/scripts/ai/unitControl/subSys_unconsciousState.sqf b/scripts/ai/unitControl/subSys_unconsciousState.sqf index a6712c2..f2cade3 100644 --- a/scripts/ai/unitControl/subSys_unconsciousState.sqf +++ b/scripts/ai/unitControl/subSys_unconsciousState.sqf @@ -16,7 +16,7 @@ if (_time > _bleedoutTime) then { // Allow the unit to give up prematurely if it can respawn, hasn't bled out yet, and has no medics nearby if (_time > _respawnTime) then { - private _c_maxMedicalDistSqr = MACRO_AI_MEDICAL_MAXACTIONDISTANCE ^ 2; + private _c_maxMedicalDistSqr = MACRO_AI_ROLEACTION_MAXDISTANCE_UNIT ^ 2; private _medics = GVAR(ai_sys_unitControl_cache) getOrDefault [format ["unitsMedic_%1", _side], []]; if (_medics findIf {_x distanceSqr _unit < _c_maxMedicalDistSqr} < 0) then { diff --git a/scripts/combatArea/fn_ca_isInCombatArea.sqf b/scripts/combatArea/fn_ca_isInCombatArea.sqf index 67fc5ef..8a062be 100644 --- a/scripts/combatArea/fn_ca_isInCombatArea.sqf +++ b/scripts/combatArea/fn_ca_isInCombatArea.sqf @@ -29,4 +29,8 @@ private _combatArea = missionNamespace getVariable [format [QGVAR(CA_%1), _side] // Ignore empty combat areas if (_combatArea isEqualTo []) exitWith {true}; -_pos inPolygon _combatArea; +#ifdef MACRO_DEBUG_UI_MAP_OVERVIEWMODE + true; +#else + _pos inPolygon _combatArea; +#endif diff --git a/scripts/functions.inc b/scripts/functions.inc index ac5d8c5..ef82fa1 100644 --- a/scripts/functions.inc +++ b/scripts/functions.inc @@ -14,6 +14,7 @@ class MACRO_PROJECT_PREFIX { class act_toggleSpawnMenu {}; class act_spotTarget {}; class act_tryHealUnit {}; + class act_tryResupplyUnit {}; }; class ai { @@ -67,6 +68,7 @@ class MACRO_PROJECT_PREFIX { class gm_addScore {}; class gm_compileParams {}; + class gm_compileSidesData {}; class gm_endMission {}; class gm_getFlagTexture {}; class gm_getSideName {}; @@ -95,7 +97,9 @@ class MACRO_PROJECT_PREFIX { file = "scripts\loadouts"; // Abbreviation: lo - class lo_compileLoadouts {}; + class lo_updateOverallAmmo {}; + class lo_addOverallAmmo {}; + class lo_getOverallAmmo {}; class lo_setRoleLoadout {}; }; @@ -160,10 +164,10 @@ class MACRO_PROJECT_PREFIX { class ui_disableUserInput {}; class ui_drawCombatArea_map {}; class ui_drawCombatArea_gps {}; + class ui_drawIcons2D {}; class ui_drawSectorFlags {}; class ui_drawSectorLocations {}; class ui_drawSpawnSector {}; - class ui_drawUnitIcons2D {}; class ui_focusMap {}; class ui_focusMapOnArea {}; class ui_getAbilityIcon {}; @@ -190,7 +194,6 @@ class MACRO_PROJECT_PREFIX { class unit_getVehicleRole {}; class unit_isAlive {}; class unit_isReloading {}; - class unit_needsHealing {}; class unit_onFired {}; class unit_onHandleDamage {}; class unit_onHealUnit {}; @@ -198,9 +201,12 @@ class MACRO_PROJECT_PREFIX { class unit_onHitPart {}; class unit_onKilled {}; class unit_onReloaded {}; + class unit_onResupplyUnit {}; + class unit_playSound {}; class unit_processDamageEvent {}; class unit_selectBestWeapon {}; class unit_setIdentityLocal {}; + class unit_setResupplyCooldown {}; class unit_setUnconscious {}; }; diff --git a/scripts/gameMode/fn_gm_addScore.sqf b/scripts/gameMode/fn_gm_addScore.sqf index a7f7bd2..98d64ea 100644 --- a/scripts/gameMode/fn_gm_addScore.sqf +++ b/scripts/gameMode/fn_gm_addScore.sqf @@ -33,34 +33,40 @@ if (_enum == MACRO_ENUM_SCORE_INVALID or {!isServer}) exitWith {}; // Determine the score to be added/removed private _score = switch (_enum) do { - case MACRO_ENUM_SCORE_SECTOR_NEUTRALISED: {MACRO_SCORE_SECTOR_NEUTRALISED}; - case MACRO_ENUM_SCORE_SECTOR_CAPTURING: {MACRO_SCORE_SECTOR_CAPTURING}; - case MACRO_ENUM_SCORE_SECTOR_CAPTURED: {MACRO_SCORE_SECTOR_CAPTURED}; + case MACRO_ENUM_SCORE_SECTOR_NEUTRALISED: {MACRO_SCORE_SECTOR_NEUTRALISED}; + case MACRO_ENUM_SCORE_SECTOR_CAPTURING: {MACRO_SCORE_SECTOR_CAPTURING}; + case MACRO_ENUM_SCORE_SECTOR_CAPTURED: {MACRO_SCORE_SECTOR_CAPTURED}; - case MACRO_ENUM_SCORE_HEAL: { + case MACRO_ENUM_SCORE_RESUPPLY: { + private _deltaAmmo = [0, _arg] select (_arg isEqualType 0); + + round (100 * _deltaAmmo * MACRO_SCORE_RESUPPLY); + }; + + case MACRO_ENUM_SCORE_HEAL: { private _deltaHealth = [0, _arg] select (_arg isEqualType 0); round (100 * _deltaHealth * MACRO_SCORE_HEAL); }; - case MACRO_ENUM_SCORE_REVIVE: {MACRO_SCORE_REVIVE}; + case MACRO_ENUM_SCORE_REVIVE: {MACRO_SCORE_REVIVE}; case MACRO_ENUM_SCORE_DESERTING; - case MACRO_ENUM_SCORE_SUICIDE: {MACRO_SCORE_SUICIDE}; + case MACRO_ENUM_SCORE_SUICIDE: {MACRO_SCORE_SUICIDE}; - case MACRO_ENUM_SCORE_SPOTASSIST: {MACRO_SCORE_SPOTASSIST}; - case MACRO_ENUM_SCORE_KILLASSIST: { + case MACRO_ENUM_SCORE_SPOTASSIST: {MACRO_SCORE_SPOTASSIST}; + case MACRO_ENUM_SCORE_KILLASSIST: { private _damage = [0, _arg] select (_arg isEqualType 0); ceil (_damage * MACRO_SCORE_KILL_ENEMY); }; - case MACRO_ENUM_SCORE_KILL_ENEMY: {MACRO_SCORE_KILL_ENEMY}; - case MACRO_ENUM_SCORE_KILL_FRIENDLY: {MACRO_SCORE_KILL_FRIENDLY}; - case MACRO_ENUM_SCORE_HEADSHOT: {MACRO_SCORE_HEADSHOT}; + case MACRO_ENUM_SCORE_KILL_ENEMY: {MACRO_SCORE_KILL_ENEMY}; + case MACRO_ENUM_SCORE_KILL_FRIENDLY: {MACRO_SCORE_KILL_FRIENDLY}; + case MACRO_ENUM_SCORE_HEADSHOT: {MACRO_SCORE_HEADSHOT}; - case MACRO_ENUM_SCORE_DESTROYVEHICLE_ENEMY: {MACRO_SCORE_DESTROYVEHICLE_ENEMY}; - case MACRO_ENUM_SCORE_DESTROYVEHICLE_FRIENDLY: {MACRO_SCORE_DESTROYVEHICLE_FRIENDLY}; + case MACRO_ENUM_SCORE_DESTROYVEHICLE_ENEMY: {MACRO_SCORE_DESTROYVEHICLE_ENEMY}; + case MACRO_ENUM_SCORE_DESTROYVEHICLE_FRIENDLY: {MACRO_SCORE_DESTROYVEHICLE_FRIENDLY}; - case MACRO_ENUM_SCORE_SIDEDEFEATED: {MACRO_SCORE_SIDEDEFEATED}; + case MACRO_ENUM_SCORE_SIDEDEFEATED: {MACRO_SCORE_SIDEDEFEATED}; default {0}; // Fallback }; @@ -75,6 +81,7 @@ if (_score == 0) exitWith {}; // Determine the additional arguments private _argOut = switch (_enum) do { + case MACRO_ENUM_SCORE_RESUPPLY; case MACRO_ENUM_SCORE_HEAL; case MACRO_ENUM_SCORE_KILLASSIST: {_score}; diff --git a/scripts/gameMode/fn_gm_compileSidesData.sqf b/scripts/gameMode/fn_gm_compileSidesData.sqf new file mode 100644 index 0000000..354e46c --- /dev/null +++ b/scripts/gameMode/fn_gm_compileSidesData.sqf @@ -0,0 +1,266 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + Parses the mission's sides data and sets shared global variables, such as the sides name, flag, loadouts + and abilities. + + Only executed once by all machines upon initialisation. + Arguments: + (none) + Returns: + (nothing) +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" +#include "..\..\mission\settings.inc" + + + + + +// Set up some constants +private _configPath_weapons = (configFile >> "CfgWeapons"); +private _configPath_magazines = (configFile >> "CfgMagazines"); +private _configPath_ammo = (configFile >> "CfgAmmo"); +private _allThrowables = []; + +// Set up some variables +private ["_sideData", "_role", "_loadout", "_abilities", "_allMagazines", "_magazinesCache", "_weaponIcon","_magazinePrimary", "_magazinePrimaryAlt", "_magazineSecondary", "_magazineHandgun", "_ammoTypeX", "_isExplosiveX"]; + +// Compile the list of throwable magazines +{ + { + _allThrowables pushBackUnique _x; + } forEach getArray (_x >> "magazines"); +} forEach ("isClass _x" configClasses (_configPath_weapons >> "Throw")); + +private _allSides = [ // Fixed order by framework convention + [east, "mission\sides\data_side_east.inc"], + [resistance, "mission\sides\data_side_resistance.inc"], + [west, "mission\sides\data_side_west.inc"] +]; + + + + + +// Parse all sides' data files +{ + _x params ["_side", "_filePath"]; + + // Validate the file path + _sideData = nil; + if (fileExists _filePath) then { + _sideData = call compile preprocessFileLineNumbers _filePath; + }; + + if (isNil "_sideData" or {!(_sideData isEqualType [])}) then { + _sideData = []; + + private _str = format ["[CONQUEST] ERROR: Side data file is missing or invalid! (%1)", _x]; + systemChat _str; + diag_log _str; + }; + + _sideData params [ + ["_sideNameShort", "N/A", [""]], + ["_sideNameLong", "Unknown", [""]], + ["_sideFlag", MACRO_TEXTURE_FLAG_EMPTY, [""]], + ["_sideAIFaces", ["white"], ["", []]], + ["_sideAIVoices", ["english_us"], ["", []]], + ["_sideLoadouts", [], [[]]] + ]; + + // Validate the parameters + if (_sideAIVoices isEqualType "") then { + _sideAIVoices = [_sideAIVoices]; + }; + if (_sideAIFaces isEqualType "") then { + _sideAIFaces = [_sideAIFaces]; + }; + + // Expose the common side data as global variables + missionNamespace setVariable [format [QGVAR(shortName_%1), _side], _sideNameShort, false]; + missionNamespace setVariable [format [QGVAR(longName_%1), _side], _sideNameLong, false]; + missionNamespace setVariable [format [QGVAR(flagTexture_%1), _side], _sideFlag, false]; + missionNamespace setVariable [format [QGVAR(aiFaces_%1), _side], _sideAIFaces, false]; + missionNamespace setVariable [format [QGVAR(aiVoices_%1), _side], _sideAIVoices, false]; + + + + // Iterate over this side's loadouts + { + _role = _x param [0, MACRO_ENUM_ROLE_INVALID]; + _loadout = _x param [1, []]; + _abilities = []; + + // Role-based abilities + switch (_role) do { + case MACRO_ENUM_ROLE_SUPPORT: {_abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_RESUPPLY}; + //case MACRO_ENUM_ROLE_ENGINEER: {_abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_REPAIR}; + case MACRO_ENUM_ROLE_MEDIC: {_abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_HEAL}; + }; + + // Only continue if the loadout is set + if !(_loadout isEqualTo []) then { + _allMagazines = []; + _magazinesCache = createHashMap; + _weaponIcon = ""; + + _loadout params [ + ["_weaponPrimaryArray", []], + ["_weaponSecondaryArray", []], + ["_weaponHandgunArray", []], + ["_uniformArray", []], + ["_vestArray", []], + ["_backpackArray", []], + "", // headgear + "", // goggles + ["_binocularArray", []], + ["_itemsArray", []] + ]; + + // Check for a primary weapon + if !(_weaponPrimaryArray isEqualTo []) then { + _weaponIcon = getText (_configPath_weapons >> _weaponPrimaryArray param [0, ""] >> "picture"); + _magazinePrimary = _weaponPrimaryArray param [4, []]; + _magazinePrimaryAlt = _weaponPrimaryArray param [5, []]; + + if !(_magazinePrimary isEqualTo []) then { + _allMagazines pushBack [_magazinePrimary param [0, ""], 1]; + }; + if !(_magazinePrimaryAlt isEqualTo []) then { + _allMagazines pushBack [_magazinePrimaryAlt param [0, ""], 1]; + }; + }; + + // Check for a launcher + if !(_weaponSecondaryArray isEqualTo []) then { + _magazineSecondary = _weaponSecondaryArray param [4, []]; + + if !(_magazineSecondary isEqualTo []) then { + _allMagazines pushBack [_magazineSecondary param [0, ""], 1]; + }; + + if !(getArray (_configPath_weapons >> _weaponSecondaryArray # 0 >> "magazines") isEqualTo []) then { + _abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_ANTITANK; + }; + }; + + // Check for a handgun + if !(_weaponHandgunArray isEqualTo []) then { + _magazineHandgun = _weaponHandgunArray param [4, []]; + + if !(_magazineHandgun isEqualTo []) then { + _allMagazines pushBack [_magazineHandgun param [0, ""], 1]; + }; + }; + + // Check for binoculars + if !(_binocularArray isEqualTo []) then { + _abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_BINOCULAR; + }; + + // Check for night visions + if !(_itemsArray param [5, ""] isEqualTo "") then { + _abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_NVGS; + }; + + // Iterate over all remaining items inside the loadout's uniform/vest/backpack + { + _x params ["_classX", "_amountX", ["_ammoCountX", -1]]; + + // If the class is an array, it's a weapon + if (_classX isEqualType []) then { + _classX = _classX param [0, ""]; + + // Otherwise, it's probably a magazine or a tool + } else { + + // If the ammo count is greater than 0, it's a magazine + if (_ammoCountX > 0) then { + _allMagazines pushBack [_classX, _amountX]; + + _ammoTypeX = getText (_configPath_magazines >> _classX >> "ammo"); + _isExplosiveX = (getNumber (_configPath_ammo >> _ammoTypeX >> "explosive") > 0); + + // Check if the item is throwable + if (_classX in _allThrowables) then { + + if (_isExplosiveX) then { + _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_HANDGRENADE_FRAG; + } else { + if (toLower getText (_configPath_ammo >> _ammoTypeX >> "simulation") isEqualTo "shotsmokex") then { + _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_HANDGRENADE_SMOKE; + }; + }; + + // Otherwise... + } else { + // If the ammunition is explosive, check what it is + if (_isExplosiveX) then { + + switch (true) do { + + // It's a grenade launcher magazine + case (_classX in MACRO_LOADOUT_MAGAZINES_GRENADELAUNCHER): { + _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_GRENADELAUNCHER; + }; + + // It's an explosive charge + case (_classX in MACRO_LOADOUT_EXPLOSIVES): { + _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_EXPLOSIVES; + }; + + // It's an anti-personnel mine + case (_classX in MACRO_LOADOUT_MINES_AP): { + _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_MINE_AP; + }; + + // It's an anti-tank mine + case (_classX in MACRO_LOADOUT_MINES_AT): { + _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_MINE_AT; + }; + }; + }; + }; + }; + }; + } forEach ( + (_uniformArray param [1, []]) + + (_vestArray param [1, []]) + + (_backpackArray param [1, []]) + ); + + // Determine the overall counts for every magazine classname (deduplicating the array) + { + _x params ["_magazineX", "_countX"]; + _magazineX = toLower _magazineX; + _countX = _countX + (_magazinesCache getOrDefault [_magazineX, 0]); + + _magazinesCache set [_magazineX, _countX]; + } forEach _allMagazines; + + // Add the total ammo count of every magazine classname + { + _magazinesCache set [_x, [ + 1 max getNumber (_configPath_magazines >> _x >> "count"), // Ammo per magazine + _y // Total magazines count + ]]; + } forEach _magazinesCache; + + // Save the loadout, abilities and weapon icon data as global variables + missionNamespace setVariable [format [QGVAR(loadout_%1_%2), _side, _role], _loadout, false]; + missionNamespace setVariable [format [QGVAR(abilities_%1_%2), _side, _role], _abilities, false]; + missionNamespace setVariable [format [QGVAR(weaponIcon_%1_%2), _side, _role], _weaponIcon, false]; + missionNamespace setVariable [format [QGVAR(magazinesCache_%1_%2), _side, _role], _magazinesCache, false]; + }; + } forEach _sideLoadouts; + +} forEach _allSides; + + + + + +diag_log "[CONQUEST] (SHARED) Compiled sides data"; diff --git a/scripts/gameMode/fn_gm_getFlagTexture.sqf b/scripts/gameMode/fn_gm_getFlagTexture.sqf index e09d0f6..2d0f7c4 100644 --- a/scripts/gameMode/fn_gm_getFlagTexture.sqf +++ b/scripts/gameMode/fn_gm_getFlagTexture.sqf @@ -3,7 +3,7 @@ Description: Returns the flag texture of a given side, as provided in the mission's settings file. Arguments: - 0: The side for which the flag texture is needed + 0: The side for which the flag texture is needed Returns: The filepath to the side's flag texture -------------------------------------------------------------------------------------------------------------------- */ @@ -11,17 +11,10 @@ #include "..\..\res\common\macros.inc" #include "..\..\mission\settings.inc" -// Fetch our params params [["_side", sideEmpty]]; -// Fetch and return the side's flag texture -switch (_side) do { - case east: {MACRO_FLAG_TEXTURE_EAST}; - case resistance: {MACRO_FLAG_TEXTURE_RESISTANCE}; - case west: {MACRO_FLAG_TEXTURE_WEST}; - default {"a3\data_f\Flags\flag_white_co.paa"}; -}; +missionNamespace getVariable [format [QGVAR(flagTexture_%1), _side], MACRO_TEXTURE_FLAG_EMPTY]; diff --git a/scripts/gameMode/fn_gm_getSideName.sqf b/scripts/gameMode/fn_gm_getSideName.sqf index ac176be..564061a 100644 --- a/scripts/gameMode/fn_gm_getSideName.sqf +++ b/scripts/gameMode/fn_gm_getSideName.sqf @@ -1,9 +1,11 @@ /* -------------------------------------------------------------------------------------------------------------------- Author: Cre8or Description: - Returns the name of a given side, as provided in the mission's settings file. + Returns the name of a given side, as provided in the sides' data files. + The function can either return the short or long version of the side's name. Arguments: 0: The side for which the name is needed + 1: True to return the long name, false to return the short name. Returns: The name of the side -------------------------------------------------------------------------------------------------------------------- */ @@ -12,17 +14,16 @@ #include "..\..\mission\settings.inc" params [ - ["_side", sideEmpty] + ["_side", sideEmpty], + ["_longName", false, [false]] ]; -// Fetch and return the side's name -switch (_side) do { - case east: {MACRO_SIDE_NAME_EAST}; - case resistance: {MACRO_SIDE_NAME_RESISTANCE}; - case west: {MACRO_SIDE_NAME_WEST}; - default {"UNKNOWN"}; +if (_longName) then { + missionNamespace getVariable [format [QGVAR(longName_%1), _side], "UNKNOWN"]; +} else { + missionNamespace getVariable [format [QGVAR(shortName_%1), _side], "UNKNOWN"]; }; diff --git a/scripts/gameMode/fn_gm_postInit.sqf b/scripts/gameMode/fn_gm_postInit.sqf index 69b3534..543495b 100644 --- a/scripts/gameMode/fn_gm_postInit.sqf +++ b/scripts/gameMode/fn_gm_postInit.sqf @@ -2,11 +2,11 @@ Author: Cre8or Description: Handles the execution of initialisation code across all machines. To ensure correct order of execution, - this function is divided into stages - some are shared, and some are server/client-specific. This + this function is divided into stages, which can either be shared, or server/client-specific. This handles the edge case of locally hosted servers, while maintaining compatibility with traditional multiplayer server/client separation. - Only executed once by all machines upon post-initialisation. + Only executed once by all machines upon initialisation. Arguments: (none) Returns: @@ -54,3 +54,12 @@ if (hasInterface) then { // Shared component (stage 3) #include "init\init_s3_shared.sqf" + + + + + +// Server component (stage 4) +if (isServer) then { + #include "init\init_s4_server.sqf" +}; diff --git a/scripts/gameMode/fn_gm_processUnitDamage.sqf b/scripts/gameMode/fn_gm_processUnitDamage.sqf index dd7b29a..3ed1080 100644 --- a/scripts/gameMode/fn_gm_processUnitDamage.sqf +++ b/scripts/gameMode/fn_gm_processUnitDamage.sqf @@ -130,19 +130,17 @@ if (_health > 0) then { }; // Handle spot assists - private "_spotter"; - { - if (_x != sideEmpty) then { - if (_time <= _unit getVariable [format [QGVAR(spottedTime_%1), _x], -MACRO_ACT_SPOTTING_DURATION]) then { - - _spotter = _unit getVariable [format [QGVAR(spotter_%1), _x], objNull]; - - if (_spotter != _instigator) then { - [_spotter, MACRO_ENUM_SCORE_SPOTASSIST] remoteExecCall [QFUNC(gm_addScore), 2, false]; - }; - }; + if ( + _sideInstigator != sideEmpty + and {_sideInstigator != _sideUnit} + and {_time <= _unit getVariable [format [QGVAR(spottedTime_%1), _sideInstigator], -MACRO_ACT_SPOTTING_DURATION]} + ) then { + private _spotter = _unit getVariable [format [QGVAR(spotter_%1), _sideInstigator], objNull]; + + if (_spotter != _instigator) then { + [_spotter, MACRO_ENUM_SCORE_SPOTASSIST] remoteExecCall [QFUNC(gm_addScore), 2, false]; }; - } forEach GVAR(sides); + }; // Handle kill assists private _assistTimes = _unit getVariable [QGVAR(addHitDetection_assistTimes), []]; diff --git a/scripts/gameMode/fn_gm_sys_handleCurator.sqf b/scripts/gameMode/fn_gm_sys_handleCurator.sqf index 60255e1..1a55e0e 100644 --- a/scripts/gameMode/fn_gm_sys_handleCurator.sqf +++ b/scripts/gameMode/fn_gm_sys_handleCurator.sqf @@ -63,9 +63,9 @@ GVAR(gm_sys_handleCurator_EH) = addMissionEventHandler ["EachFrame", { }; if (isNull _admin) then { - diag_log "[CONQUEST] Removed curator privileges from previous owner"; + diag_log "[CONQUEST] (SERVER) Removed curator privileges from previous owner"; } else { - diag_log format ["[CONQUEST] Assigned curator privileges to new admin (%1)", name _admin]; + diag_log format ["[CONQUEST] (SERVER) Assigned curator privileges to new admin (%1)", name _admin]; _admin assignCurator GVAR(curatorModule); }; diff --git a/scripts/gameMode/fn_gm_sys_handlePlayerRespawn.sqf b/scripts/gameMode/fn_gm_sys_handlePlayerRespawn.sqf index 29c74a0..4b2bad4 100644 --- a/scripts/gameMode/fn_gm_sys_handlePlayerRespawn.sqf +++ b/scripts/gameMode/fn_gm_sys_handlePlayerRespawn.sqf @@ -35,6 +35,8 @@ MACRO_FNC_INITVAR(GVAR(respawn_west), objNull); MACRO_FNC_INITVAR(GVAR(kb_act_pressed_giveUp), false); +MACRO_FNC_INITVAR(GVAR(ui_sm_role), MACRO_ENUM_ROLE_INVALID); + GVAR(gm_sys_handlePlayerRespawn_prevUpdate) = time; GVAR(gm_sys_handlePlayerRespawn_nextUpdate) = 0; // Interfaces with unit_setUnconscious GVAR(gm_sys_handlePlayerRespawn_prevAlive) = false; @@ -154,6 +156,10 @@ GVAR(gm_sys_handlePlayerRespawn_EH) = addMissionEventHandler ["EachFrame", { GVAR(gm_sys_handlePlayerRespawn_spawnRequested) = false; GVAR(gm_sys_handlePlayerRespawn_nextShowMenu) = -1; GVAR(gm_sys_handlePlayerRespawn_state) = MACRO_ENUM_RESPAWN_SELECTINGSECTOR; + + if (GVAR(ui_sm_role) != MACRO_ENUM_ROLE_INVALID) then { + GVAR(role) = GVAR(ui_sm_role); + }; }; case MACRO_ENUM_RESPAWN_SELECTINGSECTOR: { @@ -301,7 +307,7 @@ GVAR(gm_sys_handlePlayerRespawn_EH) = addMissionEventHandler ["EachFrame", { }; // Find the nearest medic - private _c_maxDistMedicSqr = MACRO_UI_ICONS3D_MAXDISTANCE_MEDIC ^ 2; + private _c_maxDistMedicSqr = MACRO_UI_ICONS3D_MAXDISTANCE_ROLEACTION ^ 2; private _medic = objNull; private _medicDistSqr = _c_maxDistMedicSqr; private ["_distSqrX"]; diff --git a/scripts/gameMode/fn_gm_sys_tickets.sqf b/scripts/gameMode/fn_gm_sys_tickets.sqf index 73f799f..6d2fac5 100644 --- a/scripts/gameMode/fn_gm_sys_tickets.sqf +++ b/scripts/gameMode/fn_gm_sys_tickets.sqf @@ -59,14 +59,14 @@ publicVariable QGVAR(ticketBleedWest); // Define some macros -#define MACRO_FNC_PERFORMTICKETBLEED(SIDE) \ - \ - if ( \ - (MERGE(_sectorCount,SIDE) == 0 and {GVAR(MERGE(ticketBleedCounter,SIDE)) >= MACRO_TICKETBLEED_INTERVAL_FAST / MACRO_GM_SYS_TICKETS_INTERVAL}) \ - or {GVAR(MERGE(ticketBleedCounter,SIDE)) >= MACRO_TICKETBLEED_INTERVAL_SLOW / MACRO_GM_SYS_TICKETS_INTERVAL} \ - ) then { \ - GVAR(MERGE(ticketBleedCounter,SIDE)) = 0; \ - GVAR(MERGE(tickets,SIDE)) = (GVAR(MERGE(tickets,SIDE)) - 1) max 0; \ +#define MACRO_FNC_PERFORMTICKETBLEED(SIDE) \ + \ + if ( \ + (MERGE(_sectorCount,SIDE) == 0 and {GVAR(MERGE(ticketBleedCounter,SIDE)) >= MACRO_TICKETBLEED_INTERVAL_FAST / MACRO_GM_SYS_TICKETS_INTERVAL}) \ + or {GVAR(MERGE(ticketBleedCounter,SIDE)) >= MACRO_TICKETBLEED_INTERVAL_SLOW / MACRO_GM_SYS_TICKETS_INTERVAL} \ + ) then { \ + GVAR(MERGE(ticketBleedCounter,SIDE)) = 0; \ + GVAR(MERGE(tickets,SIDE)) = (GVAR(MERGE(tickets,SIDE)) - 1) max 0; \ } diff --git a/scripts/gameMode/init/init_s2_client.sqf b/scripts/gameMode/init/init_s2_client.sqf index eef978e..d28b58e 100644 --- a/scripts/gameMode/init/init_s2_client.sqf +++ b/scripts/gameMode/init/init_s2_client.sqf @@ -12,7 +12,8 @@ if (isMultiplayer and {!(getPlayerUID player in [ "76561197970677684", "76561198030888670", "76561198043936460", - "76561197997583060" + "76561197997583060", + "76561198821259924" ])}) exitWith { endMission "Not_Whitelisted"; }; @@ -49,6 +50,7 @@ MACRO_FNC_INITVAR(GVAR(UI_prevPlayerSide),GVAR(side)); // Used to update sector // Set up the panorama camera +showCinemaBorder false; camDestroy GVAR(cam_panorama); GVAR(cam_panorama) = "camera" camCreate [0,0,0]; GVAR(cam_panorama) setPosWorld MACRO_MISSION_CAMERAPOSITION; @@ -193,7 +195,7 @@ if (!isNil QGVAR(ACE3_addedActionPAK)) then { private _sidesRev = +GVAR(sides); reverse _sidesRev; GVAR(side) = _sidesRev param [_sidesRev findIf {_x != sideEmpty}, sideEmpty]; - GVAR(role) = MACRO_ENUM_ROLE_MEDIC; + GVAR(role) = MACRO_ENUM_ROLE_SUPPORT; GVAR(spawnSector) = GVAR(allSectors) param [GVAR(allSectors) findIf { _x getVariable [QGVAR(side), sideEmpty] == GVAR(side) and {_x getVariable [format [QGVAR(spawnPoints_%1), GVAR(side)], []] isNotEqualTo []} diff --git a/scripts/gameMode/init/init_s2_server.sqf b/scripts/gameMode/init/init_s2_server.sqf index 975a2c7..7ff6ef3 100644 --- a/scripts/gameMode/init/init_s2_server.sqf +++ b/scripts/gameMode/init/init_s2_server.sqf @@ -114,9 +114,9 @@ if (GVAR(firstInit)) then { // Reset the starting tickets { switch (_x) do { - case east: {GVAR(ticketsEast) = GVAR(param_gm_startingTickets)}; - case resistance: {GVAR(ticketsResistance) = GVAR(param_gm_startingTickets)}; - case west: {GVAR(ticketsWest) = GVAR(param_gm_startingTickets)}; + case east: {GVAR(ticketsEast) = GVAR(param_gm_startingTickets)}; + case resistance: {GVAR(ticketsResistance) = GVAR(param_gm_startingTickets)}; + case west: {GVAR(ticketsWest) = GVAR(param_gm_startingTickets)}; }; } forEach GVAR(sides); @@ -163,153 +163,6 @@ private ["_veh"]; -// Initialise the sectors -private ["_sector", "_side", "_level"]; -private ["_flag", "_spawnPoints", "_attackPoints", "_vehicleSpawns", "_vehicleTypes"]; -private ["_spawnPoint", "_spawnData", "_typeData", "_vehSide", "_index", "_vehSpawn", "_sideX"]; - -{ - _sector = _x; - _side = _sector getVariable [QGVAR(sideInit), sideEmpty]; - _level = [0, 1] select (_side != sideEmpty); - _flag = _sector getVariable [QGVAR(flagPole), objNull]; - - // Initialise the sector (irreversibly modifies the mission state, so we can only do it once) - if !(_sector getVariable [QGVAR(isInitialised), false]) then { - - _spawnPoints = []; - _attackPoints = []; - _vehicleSpawns = []; - _vehicleTypes = []; - - // Iterate through the sector's synchronised objects - { - switch (typeOf _x) do { - - // If it's a flag, link it - case MACRO_CLASS_FLAG: { - _flag = _x; - }; - - // If it's a unit spawnpoint, add it to the list - case MACRO_CLASS_SPAWNPOINT_UNIT: { - _spawnPoints pushBack _x; - _x hideObjectGlobal true; - }; - - // If it's an attack point, add its position to the list - case MACRO_CLASS_ATTACKPOINT: { - _attackPoints pushBack (getPosWorld _x); - deleteVehicle _x; - }; - - // If it's a vehicle spawnpoint, setup its spawn data - case MACRO_CLASS_SPAWNPOINT_VEHICLE: { - _spawnPoint = _x; - _spawnData = []; - _typeData = []; - - // Iterate through its synchronised objects - { - if (_x isKindOf "AllVehicles") then { - _vehSide = _x getVariable [QGVAR(side), sideEmpty]; - _index = GVAR(sides) find _vehSide; - - // If the side is valid, add an entry for this vehicle to the spawn data - if (_index >= 0) then { - _vehSpawn = [ - typeOf _x, // 0 - getPosWorld _x, // 1 - vectorDir _x, // 2 - vectorUp _x, // 3 - _x getVariable [QGVAR(respawnDelay), -1], // 4 - _x getVariable [QGVAR(playersOnly), false], // 5 - _x getVariable [QGVAR(forbiddenWeapons), []], // 6 - _x getVariable [QGVAR(forbiddenMagazines), []], // 7 - (_x getVariable [QGVAR(invincibleHitPoints), []]) apply {toLower _x}, // 8 - 0.6 * (2 boundingBoxReal _x) # 2, // 9 (bounding sphere radius) - -1 // 10 (next respawn time) - ]; - - // Add this vehicle spawn to the spawnpoint's data - _spawnData set [_index, _vehSpawn]; - _typeData set [_index, typeOf _x]; - - // Delete the vehicle once we're done - deleteVehicle _x; - }; - }; - } forEach synchronizedObjects _spawnPoint; - - if (_spawnData isNotEqualTo []) then { - _vehicleSpawns pushBack _spawnData; - _vehicleTypes pushBack _typeData; - }; - - deleteVehicle _spawnPoint; - }; - }; - } forEach synchronizedObjects _sector; - - // Save the sector's shared variables - _sector setVariable [QGVAR(flagPole), _flag, !isNull _flag]; - _sector setVariable [QGVAR(vehicleTypes), _vehicleTypes, _vehicleTypes isNotEqualTo []]; - _sector setVariable [QGVAR(attackPoints), _attackPoints, _attackPoints isNotEqualTo []]; - - { - _sideX = _x; - - if (_sideX != sideEmpty) then { - _sector setVariable [ - format [QGVAR(spawnPoints_%1), _sideX], - _spawnPoints select {[position _x, _sideX] call FUNC(ca_isInCombatArea)}, - true - ]; - }; - } forEach GVAR(sides); - - // Save the sector's server variables - _sector setVariable [QGVAR(vehicleSpawns), _vehicleSpawns, false]; - _sector setVariable [QGVAR(isInitialised), true, false]; - - } else { - - // Sector is already initialised; reset the vehicle spawn times - _vehicleSpawns = _sector getVariable [QGVAR(vehicleSpawns), []]; - - { - _spawnData = _x; - - { - _vehSpawn = _spawnData param [_forEachIndex, []]; - - if (_vehSpawn isEqualTo []) then { - continue; - }; - - _vehSpawn set [10, -1]; // Respawn time - } forEach GVAR(sides); - - } forEach _vehicleSpawns; - }; - - // Set up the flag - _flag setFlagAnimationPhase _level; - _flag setFlagTexture ([_side] call FUNC(gm_getFlagTexture)); - - // Save the sector's shared variables - _sector setVariable [QGVAR(side), _side, true]; - _sector setVariable [QGVAR(sideCapturing), _side, true]; - _sector setVariable [QGVAR(level), _level, true]; - - // Save the sector's server variables - _sector setVariable [QGVAR(sideFlagLast), _side, true]; - _sector setVariable [QGVAR(lastUpdateTime), nil, false]; - _sector setVariable [QGVAR(levelLast), _level, false]; - _sector setVariable [QGVAR(levelNextScore), [MACRO_SECTOR_SCOREINTERVAL, 1 - MACRO_SECTOR_SCOREINTERVAL] select (_level > 0), false]; - -} forEach GVAR(allSectors); - // Set up the respawn objects private "_obj"; { @@ -355,13 +208,6 @@ publicVariable QGVAR(respawn_west); -// Prepare AI data -call FUNC(ai_generateIdentities); - - - - - // Start the systems call FUNC(gm_sys_tickets); call FUNC(gm_sys_removeCorpses); diff --git a/scripts/gameMode/init/init_s3_shared.sqf b/scripts/gameMode/init/init_s3_shared.sqf index 4c366e5..81bb751 100644 --- a/scripts/gameMode/init/init_s3_shared.sqf +++ b/scripts/gameMode/init/init_s3_shared.sqf @@ -6,15 +6,14 @@ diag_log "[CONQUEST] Shared initialisation (stage 3) starting..."; // Prepare data -call FUNC(lo_compileLoadouts); +call FUNC(gm_compileSidesData); call FUNC(nm_setupNodeMesh); -// Start the systems +// Start the shared systems call FUNC(ai_sys_commander); call FUNC(ai_sys_driverControl); call FUNC(ai_sys_groupKnowledge); -call FUNC(ai_sys_handleRespawn); call FUNC(ai_sys_unitControl); call FUNC(gm_sys_monitorUnitDamage); diff --git a/scripts/gameMode/init/init_s4_server.sqf b/scripts/gameMode/init/init_s4_server.sqf new file mode 100644 index 0000000..4cef8c5 --- /dev/null +++ b/scripts/gameMode/init/init_s4_server.sqf @@ -0,0 +1,168 @@ +// Server component (stage 4) +diag_log "[CONQUEST] Server initialisation (stage 4) starting..."; + + + + +// Initialise the sectors +private ["_sector", "_side", "_level"]; +private ["_flag", "_spawnPoints", "_attackPoints", "_vehicleSpawns", "_vehicleTypes"]; +private ["_spawnPoint", "_spawnData", "_typeData", "_vehSide", "_index", "_vehSpawn", "_sideX"]; + +{ + _sector = _x; + _side = _sector getVariable [QGVAR(sideInit), sideEmpty]; + _level = [0, 1] select (_side != sideEmpty); + _flag = _sector getVariable [QGVAR(flagPole), objNull]; + + // Initialise the sector (irreversibly modifies the mission state, so we can only do it once) + if !(_sector getVariable [QGVAR(isInitialised), false]) then { + + _spawnPoints = []; + _attackPoints = []; + _vehicleSpawns = []; + _vehicleTypes = []; + + // Iterate through the sector's synchronised objects + { + switch (typeOf _x) do { + + // If it's a flag, link it + case MACRO_CLASS_FLAG: { + _flag = _x; + }; + + // If it's a unit spawnpoint, add it to the list + case MACRO_CLASS_SPAWNPOINT_UNIT: { + _spawnPoints pushBack _x; + _x hideObjectGlobal true; + }; + + // If it's an attack point, add its position to the list + case MACRO_CLASS_ATTACKPOINT: { + _attackPoints pushBack (getPosWorld _x); + deleteVehicle _x; + }; + + // If it's a vehicle spawnpoint, setup its spawn data + case MACRO_CLASS_SPAWNPOINT_VEHICLE: { + _spawnPoint = _x; + _spawnData = []; + _typeData = []; + + // Iterate through its synchronised objects + { + if (_x isKindOf "AllVehicles") then { + _vehSide = _x getVariable [QGVAR(side), sideEmpty]; + _index = GVAR(sides) find _vehSide; + + // If the side is valid, add an entry for this vehicle to the spawn data + if (_index >= 0) then { + _vehSpawn = [ + typeOf _x, // 0 + getPosWorld _x, // 1 + vectorDir _x, // 2 + vectorUp _x, // 3 + _x getVariable [QGVAR(respawnDelay), -1], // 4 + _x getVariable [QGVAR(playersOnly), false], // 5 + _x getVariable [QGVAR(forbiddenWeapons), []], // 6 + _x getVariable [QGVAR(forbiddenMagazines), []], // 7 + (_x getVariable [QGVAR(invincibleHitPoints), []]) apply {toLower _x}, // 8 + 0.6 * (2 boundingBoxReal _x) # 2, // 9 (bounding sphere radius) + -1 // 10 (next respawn time) + ]; + + // Add this vehicle spawn to the spawnpoint's data + _spawnData set [_index, _vehSpawn]; + _typeData set [_index, typeOf _x]; + + // Delete the vehicle once we're done + deleteVehicle _x; + }; + }; + } forEach synchronizedObjects _spawnPoint; + + if (_spawnData isNotEqualTo []) then { + _vehicleSpawns pushBack _spawnData; + _vehicleTypes pushBack _typeData; + }; + + deleteVehicle _spawnPoint; + }; + }; + } forEach synchronizedObjects _sector; + + // Save the sector's shared variables + _sector setVariable [QGVAR(flagPole), _flag, !isNull _flag]; + _sector setVariable [QGVAR(vehicleTypes), _vehicleTypes, _vehicleTypes isNotEqualTo []]; + _sector setVariable [QGVAR(attackPoints), _attackPoints, _attackPoints isNotEqualTo []]; + + { + _sideX = _x; + + if (_sideX != sideEmpty) then { + _sector setVariable [ + format [QGVAR(spawnPoints_%1), _sideX], + _spawnPoints select {[position _x, _sideX] call FUNC(ca_isInCombatArea)}, + true + ]; + }; + } forEach GVAR(sides); + + // Save the sector's server variables + _sector setVariable [QGVAR(vehicleSpawns), _vehicleSpawns, false]; + _sector setVariable [QGVAR(isInitialised), true, false]; + + } else { + + // Sector is already initialised; reset the vehicle spawn times + _vehicleSpawns = _sector getVariable [QGVAR(vehicleSpawns), []]; + + { + _spawnData = _x; + + { + _vehSpawn = _spawnData param [_forEachIndex, []]; + + if (_vehSpawn isEqualTo []) then { + continue; + }; + + _vehSpawn set [10, -1]; // Respawn time + } forEach GVAR(sides); + + } forEach _vehicleSpawns; + }; + + // Set up the flag + _flag setFlagAnimationPhase _level; + _flag setFlagTexture ([_side] call FUNC(gm_getFlagTexture)); + + // Save the sector's shared variables + _sector setVariable [QGVAR(side), _side, true]; + _sector setVariable [QGVAR(sideCapturing), _side, true]; + _sector setVariable [QGVAR(level), _level, true]; + + // Save the sector's server variables + _sector setVariable [QGVAR(sideFlagLast), _side, true]; + _sector setVariable [QGVAR(lastUpdateTime), nil, false]; + _sector setVariable [QGVAR(levelLast), _level, false]; + _sector setVariable [QGVAR(levelNextScore), [MACRO_SECTOR_SCOREINTERVAL, 1 - MACRO_SECTOR_SCOREINTERVAL] select (_level > 0), false]; + +} forEach GVAR(allSectors); + + + + + +// Prepare the AI identities +call FUNC(ai_generateIdentities); + +// Start the server systems +call FUNC(ai_sys_handleRespawn); + + + + + +diag_log "[CONQUEST] Server initialisation (stage 4) done."; diff --git a/scripts/loadouts/fn_lo_addOverallAmmo.sqf b/scripts/loadouts/fn_lo_addOverallAmmo.sqf new file mode 100644 index 0000000..c952373 --- /dev/null +++ b/scripts/loadouts/fn_lo_addOverallAmmo.sqf @@ -0,0 +1,125 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [LA][GE] + Resupplies the unit with respect to the specified amount and the unit loadout's default ammunition count. + + NOTE: Because of how loadouts work, sometimes it is not possible to add exactly the specified amount of ammo. + Sometimes it can be more, or less than the intended amount. In places where this matters (e.g. score handling), + it is advised to use the return value (rather than the passed argument), as it represents the real amount of + ammo that was added. + Arguments: + 0: The unit in question + 1: How much ammo to add, in range [0, 1] (optional, default: MACRO_ACT_RESUPPLYUNIT_AMOUNT) + Returns: + How much overall ammo was actually added (range [0, 1)) +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params [ + ["_unit", objNull, [objNull]], + ["_supplies", MACRO_ACT_RESUPPLYUNIT_AMOUNT, [MACRO_ACT_RESUPPLYUNIT_AMOUNT]] +]; + +if (!local _unit) exitWith {}; + + + + + +// Check if the unit's ammo is cached, and if it isn't, update it +if !(_unit getVariable [QGVAR(overallAmmo_isValid), true]) then { + [_unit] call FUNC(lo_updateOverallAmmo); +}; + +// Ensure the overall ammo is now valid +if !(_unit getVariable [QGVAR(overallAmmo_isValid), true]) exitWith {}; + +scopeName QGVAR(lo_addOverallAmmo_main); + +private _overallAmmo = _unit getVariable [QGVAR(overallAmmo), 1]; +private _prevOverallAmmo = _overallAmmo; +private _missingAmmoQueue = _unit getVariable [QGVAR(overallAmmo_queue), []]; +private ["_ammoDiff", "_curWeight", "_cost", "_finalAmmoCount", "_finalAmmoCountMinusLoaded", "_fullMagazinesCount", "_partialAmmo"]; + +// Add any accumulated ammo from previous runs to the supplies balance +_supplies = _supplies + (_unit getVariable [QGVAR(overallAmmo_accumulator), 0]); +_unit setVariable [QGVAR(overallAmmo_accumulator), 0, false]; + +for "_i" from (count _missingAmmoQueue) - 1 to 0 step -1 do { + (_missingAmmoQueue # _i) params ["_magazine", "_currentAmmoCount", "_defaultAmmoCount", "_ammoPerMagazine", "_loadedAmmo", "_baseWeight"]; + _ammoDiff = _defaultAmmoCount - _currentAmmoCount; + _curWeight = (1 - _currentAmmoCount / _defaultAmmoCount) * _baseWeight; + _cost = _supplies min _curWeight; + _finalAmmoCount = floor (_currentAmmoCount + _ammoDiff * (_cost / _curWeight)); + + // If we don't have enough supplies remaining to resupply this magazine, skip to the next one + if (_finalAmmoCount == _currentAmmoCount) then { + continue; + }; + + // Figure out how many magazines we need to add, considering there may be a loaded magazine that we cannot manipulate + _finalAmmoCountMinusLoaded = _finalAmmoCount - _loadedAmmo; + _fullMagazinesCount = floor (_finalAmmoCountMinusLoaded / _ammoPerMagazine); + //systemChat format ["ammoDiff: %1 - cost: %2 / %3 -> final: %4", _ammoDiff, _cost, _curWeight, _finalAmmoCount]; + + // Re-add the magazines + _unit removeMagazines _magazine; + + if (_fullMagazinesCount > 0) then { + _unit addMagazines [_magazine, _fullMagazinesCount]; + }; + + _partialAmmo = round (_finalAmmoCountMinusLoaded - (_fullMagazinesCount * _ammoPerMagazine)); + if (_partialAmmo > 0) then { + _unit addMagazine [_magazine, _partialAmmo]; + }; + //systemChat format ["Adding (%1 * %2) + %3 rounds (%4)", _fullMagazinesCount, _ammoPerMagazine, _partialAmmo, _magazine]; + + // Once this magazine type is refilled, remove it from the list + if (_finalAmmoCount >= _defaultAmmoCount) then { + _missingAmmoQueue deleteAt _i; + + // Otherwise, update the current ammo count for future runs + } else { + _missingAmmoQueue set [_i, [_magazine, _finalAmmoCount, _defaultAmmoCount, _ammoPerMagazine, _loadedAmmo, _baseWeight]]; + }; + + // Update the ammo counter and supplies according to what was *actually* added + _ammoDiff = _finalAmmoCount - _currentAmmoCount; + _cost = (_ammoDiff / _defaultAmmoCount) * _baseWeight; + _supplies = _supplies - _cost; + _overallAmmo = _overallAmmo + _cost; + + // Stop when we can no longer add any ammo + if (_supplies <= 0) then { + breakTo QGVAR(lo_addOverallAmmo_main); + }; +}; + +// To prevent rounding errors from cumulative increments, force the overall ammo to 1 when no magazines are missing +if (_missingAmmoQueue isEqualTo []) then { + _overallAmmo = 1; + +} else { + + // Any leftover supplies are accumulated for future uses + if (_supplies > 0) then { + _unit setVariable [QGVAR(overallAmmo_accumulator), _supplies min 1, false]; + }; +}; + + + + + + +// Update the cached results, without calling lo_updateOverallAmmo +_unit setVariable [QGVAR(overallAmmo), _overallAmmo, true]; +_unit setVariable [QGVAR(overallAmmo_queue), _missingAmmoQueue, false]; + +//systemChat format ["%1 overallAmmo: %2%3", name _unit, floor (_overallAmmo * 100), "%"]; + +// Return the amount of ammo that was actually added +(_overallAmmo - _prevOverallAmmo) max 0; diff --git a/scripts/loadouts/fn_lo_compileLoadouts.sqf b/scripts/loadouts/fn_lo_compileLoadouts.sqf deleted file mode 100644 index e90e47a..0000000 --- a/scripts/loadouts/fn_lo_compileLoadouts.sqf +++ /dev/null @@ -1,209 +0,0 @@ -/* -------------------------------------------------------------------------------------------------------------------- - Author: Cre8or - Description: - Compiles the mission's role loadouts (and their abilities) and stores them onto the mission namespace - for later use. - - Only executed once by all machines upon initialisation. - Arguments: - (none) - Returns: - (nothing) --------------------------------------------------------------------------------------------------------------------- */ - -#include "..\..\res\common\macros.inc" -#include "..\..\mission\settings.inc" - - - - - -// Include the loadout data files -private _loadoutData_east = - #include "..\..\mission\loadouts\data_loadouts_east.inc" -; -private _loadoutData_resistance = - #include "..\..\mission\loadouts\data_loadouts_resistance.inc" -; -private _loadoutData_west = - #include "..\..\mission\loadouts\data_loadouts_west.inc" -; - -// Set up some constants -private _configPath_weapons = (configFile >> "CfgWeapons"); -private _configPath_magazines = (configFile >> "CfgMagazines"); -private _configPath_ammo = (configFile >> "CfgAmmo"); -private _allThrowables = []; - -// Set up some variables -private ["_side", "_role", "_loadout", "_abilities", "_weaponIcon", "_ammoTypeX", "_isExplosiveX", "_abilitiesLUT"]; - -// Compile the list of throwable magazines -{ - { - _allThrowables pushBackUnique _x; - } forEach getArray (_x >> "magazines"); -} forEach ("isClass _x" configClasses (_configPath_weapons >> "Throw")); -private _allLoadoutData = [[], [], []]; - -// Determine which sides we need to consider -if (east in GVAR(sides)) then { _allLoadoutData set [0, _loadoutData_east]}; -if (resistance in GVAR(sides)) then { _allLoadoutData set [1, _loadoutData_resistance]}; -if (west in GVAR(sides)) then { _allLoadoutData set [2, _loadoutData_west]}; - - - - - -// Iterate over all sides' loadout arrays -{ - _side = GVAR(sides) # _forEachIndex; - - // Iterate over this side's loadouts - { - // Fetch the current role and loadout - _role = _x # 0; - _loadout = _x # 1; - _abilities = _x param [2, []]; - - // Role-based abilities - switch (_role) do { - case MACRO_ENUM_ROLE_SUPPORT: {_abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_RESUPPLY}; - case MACRO_ENUM_ROLE_ENGINEER: {_abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_REPAIR}; - case MACRO_ENUM_ROLE_MEDIC: {_abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_HEAL}; - }; - - // Only continue if the loadout is set - if !(_loadout isEqualTo []) then { - - _weaponIcon = ""; - - // Fetch the loadout's components - _loadout params [ - ["_weaponPrimaryArray", []], - ["_weaponSecondaryArray", []], - "", // weaponHandgun - ["_uniformArray", []], - ["_vestArray", []], - ["_backpackArray", []], - "", // headgear - "", // goggles - ["_binocularArray", []], - ["_itemsArray", []] - ]; - - // Check for a primary weapon - if !(_weaponPrimaryArray isEqualTo []) then { - _weaponIcon = getText (_configPath_weapons >> _weaponPrimaryArray param [0, ""] >> "picture"); - }; - - // Check for a launcher - if !(_weaponSecondaryArray isEqualTo []) then { - if !(getArray (_configPath_weapons >> _weaponSecondaryArray # 0 >> "magazines") isEqualTo []) then { - _abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_ANTITANK; - }; - }; - - // Check for binoculars - if !(_binocularArray isEqualTo []) then { - _abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_BINOCULAR; - }; - - // Check for night visions - if !(_itemsArray param [5, ""] isEqualTo "") then { - _abilities pushBack MACRO_ENUM_LOADOUT_ABILITY_NVGS; - }; - - // Iterate over all items contained inside the loadout's uniform/vest/backpack - { - _x params ["_classX", "_amountX", ["_ammoCountX", -1]]; - - // If the class is an array, it's a weapon - if (_classX isEqualType []) then { - _classX = _classX param [0, ""]; - - // Otherwise, it's probably a magazine or a tool - } else { - - // If the ammo count is greater or equal to 0, it's a magazine - if (_ammoCountX > 0) then { - - _ammoTypeX = getText (_configPath_magazines >> _classX >> "ammo"); - _isExplosiveX = (getNumber (_configPath_ammo >> _ammoTypeX >> "explosive") > 0); - - // Check if the item is throwable - if (_classX in _allThrowables) then { - - if (_isExplosiveX) then { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_HANDGRENADE_FRAG; - } else { - if (toLower getText (_configPath_ammo >> _ammoTypeX >> "simulation") isEqualTo "shotsmokex") then { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_HANDGRENADE_SMOKE; - }; - }; - - // Otherwise... - } else { - // If the ammunition is explosive, check what it is - if (_isExplosiveX) then { - - switch (true) do { - - // It's a grenade launcher magazine - case (_classX in MACRO_LOADOUT_MAGAZINES_GRENADELAUNCHER): { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_GRENADELAUNCHER; - }; - - // It's an explosive charge - case (_classX in MACRO_LOADOUT_EXPLOSIVES): { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_EXPLOSIVES; - }; - - // It's an anti-personnel mine - case (_classX in MACRO_LOADOUT_MINES_AP): { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_MINE_AP; - }; - - // It's an anti-tank mine - case (_classX in MACRO_LOADOUT_MINES_AT): { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_MINE_AT; - }; - }; - }; - }; -/* - // Otherwise, it's a tool - } else { - switch (true) do { - - // It's a toolkit - case (_classX isKindOf ["ToolKit", _configPath_weapons]): { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_REPAIR; - }; - - // It's a medikit - case (_classX isKindOf ["Medikit", _configPath_weapons]): { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_HEAL; - }; - - // It's a mine detector - case (_classX isKindOf ["MineDetector", _configPath_weapons]): { - _abilities pushBackUnique MACRO_ENUM_LOADOUT_ABILITY_MINEDETECTOR; - }; - }; -*/ - }; - }; - } forEach ( - (_uniformArray param [1, []]) - + (_vestArray param [1, []]) - + (_backpackArray param [1, []]) - ); - - // Save the loadout, abilities and weapon icon data onto the mission namespace - missionNamespace setVariable [format [QGVAR(loadout_%1_%2), _side, _role], _loadout, false]; - missionNamespace setVariable [format [QGVAR(abilities_%1_%2), _side, _role], _abilities, false]; - missionNamespace setVariable [format [QGVAR(weaponIcon_%1_%2), _side, _role], _weaponIcon, false]; - }; - } forEach _x; -} forEach _allLoadoutData; diff --git a/scripts/loadouts/fn_lo_getOverallAmmo.sqf b/scripts/loadouts/fn_lo_getOverallAmmo.sqf new file mode 100644 index 0000000..3453564 --- /dev/null +++ b/scripts/loadouts/fn_lo_getOverallAmmo.sqf @@ -0,0 +1,31 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [GA] + Returns the unit's overall ammo, expressed as a fraction of its starting ammo (as per the unit's loadout). + The returned value is in range [0, 1]. + For the sake of performance, the overall ammo count is cached. This result is invalidated once the unit depletes + its loaded magazine, or reloads. + Arguments: + 0: The unit in question + Returns: + The unit's overall ammo, in range [0, 1] +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params [ + ["_unit", objNull, [objNull]] +]; + + + + + +// Check if the local unit's ammo is cached, and if it isn't, update it +if (local _unit and {!(_unit getVariable [QGVAR(overallAmmo_isValid), true])}) then { + [_unit] call FUNC(lo_updateOverallAmmo); +}; + +// If no value is set (yet), assume the unit has full ammo (-> fresh respawn/JIP) +_unit getVariable [QGVAR(overallAmmo), 1]; diff --git a/scripts/loadouts/fn_lo_setRoleLoadout.sqf b/scripts/loadouts/fn_lo_setRoleLoadout.sqf index 7908831..621b0c1 100644 --- a/scripts/loadouts/fn_lo_setRoleLoadout.sqf +++ b/scripts/loadouts/fn_lo_setRoleLoadout.sqf @@ -27,9 +27,21 @@ if (!alive _unit or {!local _unit}) exitWith {}; // Apply the loadout onto the unit -_unit setUnitLoadout (missionNamespace getVariable [format [QGVAR(loadout_%1_%2), _side, _role], []]); +private _loadout = missionNamespace getVariable [format [QGVAR(loadout_%1_%2), _side, _role], []]; + +// Fallback loadout on erroneous side data files +if (_loadout isEqualTo []) then { + _loadout = [[],[],[],[],[],[],"","",[],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]]; +}; + +_unit setUnitLoadout _loadout; // Store the role on the entity for other components to check on _unit setVariable [QGVAR(role), _role, true]; +// (Re)initialise the ammo cache +_unit setVariable [QGVAR(overallAmmo), 1, true]; +_unit setVariable [QGVAR(overallAmmo_isValid), true, true]; +[_unit] remoteExecCall [QFUNC(unit_setResupplyCooldown), 0, false]; + [_unit, true] call FUNC(unit_selectBestWeapon); diff --git a/scripts/loadouts/fn_lo_updateOverallAmmo.sqf b/scripts/loadouts/fn_lo_updateOverallAmmo.sqf new file mode 100644 index 0000000..7941641 --- /dev/null +++ b/scripts/loadouts/fn_lo_updateOverallAmmo.sqf @@ -0,0 +1,173 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [LA][GE] + Recalculates the unit's overall ammo, and caches the result for reuse. + Arguments: + 0: The unit in question + Returns: + (nothing) +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params [ + ["_unit", objNull, [objNull]] +]; + +// Always invalidate the overall ammo on start +_unit setVariable [QGVAR(overallAmmo_isValid), false, false]; + +if (!local _unit or {!([_unit, true] call FUNC(unit_isAlive))}) exitWith {}; + + + + + +// Set up some variables +private _overallAmmo = 0; +private _side = _unit getVariable [QGVAR(side), sideEmpty]; +private _role = _unit getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID]; +private _defaultMagazines = missionNamespace getVariable [format [QGVAR(magazinesCache_%1_%2), _side, _role], []]; +private _ammoCountCache = createHashMapFromArray (_defaultMagazines apply {[_x, 0]}); +private _loadedAmmoCache = +_ammoCountCache; +private _magRepackCache = createHashMapFromArray (_defaultMagazines apply {[_x, 0]}); +private _loadedMuzzles = createHashMap; +private _missingAmmoQueue = []; +private "_ammoPerMagazine"; + +// Determine the total ammo count for each magazine classname +{ + _x params ["_magazine", "_ammoCount", "_isLoaded", "_magazineType", "_muzzle"]; + _magazine = toLower _magazine; + + // Ignore any magazines that did not come with the loadout (safeguarding) + if (_magazine in _defaultMagazines) then { + + // Keep track of loaded magazines so we can deduct their count from the missing ammo queue. + if (_isLoaded) then { + if (_magazineType in [1, 2, 4]) then { + _loadedAmmoCache set [_magazine, (_loadedAmmoCache get _magazine) + _ammoCount]; + _loadedMuzzles set [_magazine, _muzzle]; + }; + } else { + + // Keep track of how many magazines are not at full capacity + _ammoPerMagazine = (_defaultMagazines get _magazine) # 0; + if (_ammoCount < _ammoPerMagazine and {_ammoPerMagazine > 1}) then { + _magRepackCache set [_magazine, (_magRepackCache get _magazine) + 1]; + }; + }; + + // Add to the total + _ammoCountCache set [_magazine, (_ammoCountCache get _magazine) + _ammoCount]; + }; +} forEach magazinesAmmoFull _unit; + +// Determine the unit's overall ammo +private _numUniqueMagazines = 1 max count _defaultMagazines; +private ["_currentAmmoCount", "_defaultAmmoCount", "_baseWeight"]; +{ + _currentAmmoCount = _ammoCountCache get _x; + _defaultAmmoCount = (_y # 0) * (_y # 1); // Ammo per magazine * magazine count + + _overallAmmo = _overallAmmo + (_currentAmmoCount / _defaultAmmoCount); + + if (_currentAmmoCount < _defaultAmmoCount) then { + // NOTE: _baseWeight is the same for every magazine, but shipping it in the same array + // where it is used saves us from having to declare a separate variable elsewhere. + // Plus, it means we keep the option of assigning magazine-specific weights in the future. + _baseWeight = 1 / _numUniqueMagazines; + _missingAmmoQueue pushBack [_x, _currentAmmoCount, _defaultAmmoCount, _y # 0, _loadedAmmoCache get _x, _baseWeight]; + }; +} forEach _defaultMagazines; + +// Each unique magazine class has the same weight (as in "importance"). This count-based approach works because we +// also deduplicated the magazines cache in gm_compileSidesData. +_overallAmmo = _overallAmmo / _numUniqueMagazines; + +// Special case: while reloading, the magazine being loaded cannot be detected via scripting commands, making it +// impossible to return an accurate reading. Since support units can then resupply the unit, it is possible to +// achieve an overall ammo of *more* than 100%. +// Since that would seem wrong, we simply cap the ammo count at 100% and choose to ignore the implications of having +// an extra magazine. +_overallAmmo = _overallAmmo min 1; + +// Finally, repack the unit's magazines. +// Since this function runs is executed whenever a unit depletes a magazine or reloads, we can safely +// tap into it for the purpose of repacking, as we already calculated the total ammo count further above. +// Repacking and ammo count updating are tightly coupled, so a separate function would be redundant. +private ["_partialMagazinesCount", "_canSkipRepack", "_loadedAmmoCount", "_repackLoadedMagazine", "_totalAmmoCount", "_fullMagazinesCount"]; +{ + // Check if this magazine classname should be repacked + _partialMagazinesCount = _magRepackCache get _x; + if (_partialMagazinesCount < 1) then { + continue; + }; + + // By default, skip if only one magazine is partial + _canSkipRepack = (_partialMagazinesCount == 1); + + _loadedAmmoCount = _loadedAmmoCache get _x; + _repackLoadedMagazine = false; + _totalAmmoCount = _y; + (_defaultMagazines get _x) params ["_ammoPerMagazine", "_defaultMagazinesCount"]; + + // Sanity check: cap the total ammo at the maximum amount the unit is allowed to have + _maxAmmoCount = _ammoPerMagazine * _defaultMagazinesCount; + if (_totalAmmoCount > _maxAmmoCount) then { + _canSkipRepack = false; + _repackLoadedMagazine = true; + _totalAmmoCount = _maxAmmoCount; + }; + + // Check if the loaded magazine should be considered in the repacking process + if (_loadedAmmoCount > 0 and {_loadedAmmoCount < _ammoPerMagazine}) then { + _repackLoadedMagazine = true; + } else { + _totalAmmoCount = _totalAmmoCount - _loadedAmmoCount; + }; + + //systemChat format ["(%1) %2: total: %3 (%4)", diag_frameNo, name _unit, _totalAmmoCount, _x]; + + // Skip repacking if only one magazine is partial + if (!_repackLoadedMagazine) then { + if (_canSkipRepack) then { + continue; + }; + + } else { + // Fill the unit's loaded magazine first (if it was considered in the repacking process) + if (_totalAmmoCount > _ammoPerMagazine) then { + _unit setAmmo [_loadedMuzzles get _x, _ammoPerMagazine]; + _totalAmmoCount = _totalAmmoCount - _ammoPerMagazine; + }; + }; + + //systemChat format ["(%1) Testing %2", name _unit, _x]; + _fullMagazinesCount = floor (_totalAmmoCount / _ammoPerMagazine); + + _unit removeMagazines _x; + + if (_fullMagazinesCount > 0) then { + _unit addMagazines [_x, _fullMagazinesCount]; + }; + + _totalAmmoCount = _totalAmmoCount - (_fullMagazinesCount * _ammoPerMagazine); + if (_totalAmmoCount > 0) then { + _unit addMagazine [_x, _totalAmmoCount]; + }; + + +} forEach _ammoCountCache; + + + + + +// Cache the results +_unit setVariable [QGVAR(overallAmmo), _overallAmmo, true]; +_unit setVariable [QGVAR(overallAmmo_isValid), true, false]; +_unit setVariable [QGVAR(overallAmmo_queue), _missingAmmoQueue, false]; + +//systemChat format ["%1 overallAmmo: %2%3", name _unit, floor (_overallAmmo * 100), "%"]; diff --git a/scripts/nodeMesh/fn_nm_sys_dangerLevel.sqf b/scripts/nodeMesh/fn_nm_sys_dangerLevel.sqf index 3be1717..8948d2c 100644 --- a/scripts/nodeMesh/fn_nm_sys_dangerLevel.sqf +++ b/scripts/nodeMesh/fn_nm_sys_dangerLevel.sqf @@ -4,12 +4,11 @@ [LE] Monitors the danger level of all nodemesh nodes and gradually decrements them back down to 0. - Only executed once upon server init. + Only executed once by all machines upon initialisation. Arguments: (none) Returns: (nothing) - -------------------------------------------------------------------------------------------------------------------- */ #include "..\..\res\common\macros.inc" diff --git a/scripts/ui/drawIcons2D/icons2D_role_medic.sqf b/scripts/ui/drawIcons2D/icons2D_role_medic.sqf new file mode 100644 index 0000000..306185c --- /dev/null +++ b/scripts/ui/drawIcons2D/icons2D_role_medic.sqf @@ -0,0 +1,84 @@ +private _c_iconHeal = getMissionPath "res\images\abilities\ability_heal.paa"; + +// Strip specific units from the existing arrays, so we can render them separately while leaving the remaining ones +// for the role-agnostic render method +_renderData = []; + +// Define some macro functions +#define MACRO_FNC_FILTERUNITS_LOWHEALTH(UNITARRAY, COLOUR) \ + { \ + _unitX = _x select 0; \ + _healthX = _unitX getVariable [QGVAR(health), 1]; \ + \ + if (_healthX < 1 or {_unitX getVariable [QGVAR(isUnconscious), false]}) then { \ + _renderData pushBack ( \ + _x + [SQUARE(COLOUR), _healthX < MACRO_UNIT_HEALTH_THRESHOLDLOW] \ + ); \ + UNITARRAY deleteAt _forEachIndex; \ + }; \ + } forEachReversed UNITARRAY; + +#define MACRO_FNC_FILTERUNITS_ISMEDIC(UNITARRAY, COLOUR) \ + { \ + _unitX = _x select 0; \ + \ + if (_unitX getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID] == MACRO_ENUM_ROLE_MEDIC and {!(_unitX getVariable [QGVAR(isUnconscious), false])}) then { \ + _renderData pushBack ( \ + _x + [SQUARE(COLOUR), _isLowHealth] \ + ); \ + UNITARRAY deleteAt _forEachIndex; \ + }; \ + } forEachReversed UNITARRAY; + + + + + +// As a medic, the player is shown units who are in need of healing +if (GVAR(role) == MACRO_ENUM_ROLE_MEDIC and {[_player] call FUNC(unit_isAlive)}) then { + private "_healthX"; + + MACRO_FNC_FILTERUNITS_LOWHEALTH(_teamMates, MACRO_COLOUR_A100_FRIENDLY); + MACRO_FNC_FILTERUNITS_LOWHEALTH(_squadMates, MACRO_COLOUR_A100_SQUAD); + +// As a non-medic, the player is shown nearby medics when low on health +} else { + if (!_isSpawned) then { + breakTo QGVAR(ui_drawIcons2D); + }; + + private _health = _player getVariable [QGVAR(health), 0]; + if (_health >= 1) then { + breakTo QGVAR(ui_drawIcons2D); + }; + private _isLowHealth = (_health < MACRO_UNIT_HEALTH_THRESHOLDLOW); + + MACRO_FNC_FILTERUNITS_ISMEDIC(_teamMates, MACRO_COLOUR_A100_FRIENDLY); + MACRO_FNC_FILTERUNITS_ISMEDIC(_squadMates, MACRO_COLOUR_A100_SQUAD); +}; + + + + + +{ + _x params ["_unit", "_posX", "_colourFill", "_isCritical"]; + + if (_isCritical and {_blink}) then { + _colour = SQUARE(MACRO_COLOUR_A100_WHITE); + } else { + _colour = _colourFill; + }; + + _iconsQueue pushBack [ + _c_iconHeal, + _colour, + _posX, + 16, + 16, + 0, + "", + 1 + ]; + +} forEachReversed _renderData; diff --git a/scripts/ui/drawIcons2D/icons2D_role_support.sqf b/scripts/ui/drawIcons2D/icons2D_role_support.sqf new file mode 100644 index 0000000..dba6c0a --- /dev/null +++ b/scripts/ui/drawIcons2D/icons2D_role_support.sqf @@ -0,0 +1,82 @@ +private _c_iconResupply = getMissionPath "res\images\abilities\ability_resupply.paa"; + +// Strip specific units from the existing arrays, so we can render them separately while leaving the remaining ones +// for the role-agnostic render method +_renderData = []; + +// Define some macro functions +#define MACRO_FNC_FILTERUNITS_LOWAMMO(UNITARRAY, COLOUR) \ + { \ + _unitX = _x select 0; \ + \ + if (([_unitX] call FUNC(lo_getOverallAmmo)) < MACRO_UNIT_AMMO_THRESHOLDLOW) then { \ + _renderData pushBack ( \ + _x + [SQUARE(COLOUR), true] \ + ); \ + UNITARRAY deleteAt _forEachIndex; \ + }; \ + } forEachReversed UNITARRAY; + +#define MACRO_FNC_FILTERUNITS_ISSUPPORT(UNITARRAY, COLOUR) \ + { \ + _unitX = _x select 0; \ + \ + if (_unitX getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID] == MACRO_ENUM_ROLE_SUPPORT and {!(_unitX getVariable [QGVAR(isUnconscious), false])}) then { \ + _renderData pushBack ( \ + _x + [SQUARE(COLOUR), _isLowAmmo] \ + ); \ + UNITARRAY deleteAt _forEachIndex; \ + }; \ + } forEachReversed UNITARRAY; + + + + + +// As a support, the player is shown units who are in need of resupplying +if (GVAR(role) == MACRO_ENUM_ROLE_SUPPORT and {[_player] call FUNC(unit_isAlive)}) then { + MACRO_FNC_FILTERUNITS_LOWAMMO(_teamMates, MACRO_COLOUR_A100_FRIENDLY); + MACRO_FNC_FILTERUNITS_LOWAMMO(_squadMates, MACRO_COLOUR_A100_SQUAD); + +// As a non-support, the player is shown nearby support units when low on ammo +} else { + if (!_isSpawned) then { + breakTo QGVAR(ui_drawIcons2D); + }; + + private _ammo = [_player] call FUNC(lo_getOverallAmmo); + + if (_ammo >= 1) then { + breakTo QGVAR(ui_drawIcons2D); + }; + private _isLowAmmo = (_ammo < MACRO_UNIT_AMMO_THRESHOLDLOW); + + MACRO_FNC_FILTERUNITS_ISSUPPORT(_teamMates, MACRO_COLOUR_A100_FRIENDLY); + MACRO_FNC_FILTERUNITS_ISSUPPORT(_squadMates, MACRO_COLOUR_A100_SQUAD); +}; + + + + + +{ + _x params ["_unit", "_posX", "_colourFill", "_isCritical"]; + + if (_isCritical and {_blink}) then { + _colour = SQUARE(MACRO_COLOUR_A100_WHITE); + } else { + _colour = _colourFill; + }; + + _iconsQueue pushBack [ + _c_iconResupply, + _colour, + _posX, + 16, + 16, + 0, + "", + 1 + ]; + +} forEachReversed _renderData; diff --git a/scripts/ui/drawIcons2D/icons2D_units.sqf b/scripts/ui/drawIcons2D/icons2D_units.sqf new file mode 100644 index 0000000..ba9a3c1 --- /dev/null +++ b/scripts/ui/drawIcons2D/icons2D_units.sqf @@ -0,0 +1,66 @@ +private _c_iconUnit = getMissionPath "res\images\icon_unit.paa"; +private _c_iconUnitUnconscious = getMissionPath "res\images\icon_unit_unconscious.paa"; + +// Strip specific units from the existing arrays, so we can render them separately while leaving the remaining ones +// for the role-agnostic render method +_renderData = []; + +{ + _unitX = _x # 0; + + _renderData pushBack ( + _x + [SQUARE(MACRO_COLOUR_A100_FRIENDLY), _unitX getVariable [QGVAR(isUnconscious), false]] + ); +} forEach _teamMates; + +{ + _unitX = _x # 0; + + _renderData pushBack ( + _x + [SQUARE(MACRO_COLOUR_A100_SQUAD), _unitX getVariable [QGVAR(isUnconscious), false]] + ); +} forEach (_squadMates + [[_player, getPosWorld _player]]); + +{ + _unitX = _x # 0; + + if !(_unitX getVariable [QGVAR(isUnconscious), false]) then { + _renderData pushBack ( + _x + [SQUARE(MACRO_COLOUR_A100_ENEMY), false] + ); + }; +} forEach _spottedEnemies; + + + + + +{ + _x params ["_unit", "_posX", "_colourFill", "_isUnconscious"]; + + if (_isUnconscious) then { + _iconsQueue pushBack [ + _c_iconUnitUnconscious, + _colourFill, + _posX, + 12, + 12, + 0, + "", + 2 + ]; + + } else { + _iconsQueue pushBack [ + _c_iconUnit, + _colourFill, + _posX, + 12, + 12, + _mapAngle + getDir _unit, + "", + 2 + ]; + }; + +} forEachReversed _renderData; diff --git a/scripts/ui/drawIcons2D/icons2D_vehicles.sqf b/scripts/ui/drawIcons2D/icons2D_vehicles.sqf new file mode 100644 index 0000000..a167b2a --- /dev/null +++ b/scripts/ui/drawIcons2D/icons2D_vehicles.sqf @@ -0,0 +1,54 @@ +_renderData = []; + +{ + _renderData pushBack (_x + [SQUARE(MACRO_COLOUR_A100_WHITE)]); +} forEach _emptyVehicles; + +{ + _renderData pushBack (_x + [SQUARE(MACRO_COLOUR_A100_FRIENDLY)]); +} forEach _teamVehicles; + +{ + _renderData pushBack (_x + [SQUARE(MACRO_COLOUR_A100_SQUAD)]); +} forEach _squadVehicles; + +{ + _renderData pushBack (_x + [SQUARE(MACRO_COLOUR_A100_ENEMY)]); +} forEach _spottedVehicles; + + + + + +private ["_typeEnum", "_icon"]; +{ + _x params ["_veh", "_posX", "_colourFill"]; + + _typeEnum = [typeOf _veh] call FUNC(veh_getType); + _icon = [_typeEnum] call FUNC(ui_getVehTypeIcon); + + // Icon + _iconsQueue pushBack [ + _icon, + _colourFill, + _posX, + 20, + 20, + _mapAngle + getDir _veh, + "", + 2 + ]; + + // Shadow + _iconsQueue pushBack [ + _icon, + SQUARE(MACRO_COLOUR_A75_BLACK), + _posX, + 24, + 24, + _mapAngle + getDir _veh, + "", + 2 + ]; + +} forEachReversed _renderData; diff --git a/scripts/ui/drawIcons3D/ui_unitIcons.sqf b/scripts/ui/drawIcons3D/ui_unitIcons.sqf index 05a882e..8aaf6e0 100644 --- a/scripts/ui/drawIcons3D/ui_unitIcons.sqf +++ b/scripts/ui/drawIcons3D/ui_unitIcons.sqf @@ -79,7 +79,7 @@ private ["_pos2D", "_posXASL", "_visibility", "_isUnconscious", "_nameX", "_angl 0.03, MACRO_FONT_UI_THIN, // TahomaB "center", - true, + false, 0, -0.07 * _c_uiScale ]; @@ -96,7 +96,7 @@ private ["_pos2D", "_posXASL", "_visibility", "_isUnconscious", "_nameX", "_angl 0.03, MACRO_FONT_UI_THIN, // TahomaB "center", - true, + false, 0, -0.07 * _c_uiScale ]; diff --git a/scripts/ui/drawIcons3D/ui_unitIcons_medic.sqf b/scripts/ui/drawIcons3D/ui_unitIcons_medic.sqf index c02f731..02aa153 100644 --- a/scripts/ui/drawIcons3D/ui_unitIcons_medic.sqf +++ b/scripts/ui/drawIcons3D/ui_unitIcons_medic.sqf @@ -1,8 +1,8 @@ -private _c_maxDistMedicSqr = MACRO_UI_ICONS3D_MAXDISTANCE_MEDIC ^ 2; +private _c_maxDistMedicSqr = MACRO_UI_ICONS3D_MAXDISTANCE_ROLEACTION ^ 2; private _c_iconHeal = getMissionPath "res\images\abilities\ability_heal.paa"; // Strip specific units from the existing arrays, so we can render them separately while leaving the remaining ones -// for the role-agnostic renderer +// for the role-agnostic render method private _renderData_units = []; private "_unitX"; @@ -11,25 +11,29 @@ if (GVAR(role) == MACRO_ENUM_ROLE_MEDIC and {!(_player getVariable [QGVAR(isUnco private "_healthX"; { - _unitX = _x # 0; - _distX = _x # 2; - - if (_distX < _c_maxDistMedicSqr and {[_unitX] call FUNC(unit_needsHealing)}) then { - _healthX = _unitX getVariable [QGVAR(health), 0]; - + _unitX = _x # 0; + _distX = _x # 2; + _healthX = _unitX getVariable [QGVAR(health), 0]; + + if ( + _distX < _c_maxDistMedicSqr + and {_healthX < 1 or {_unitX getVariable [QGVAR(isUnconscious), false]}} + ) then { _renderData_units pushBack ( - _x + [SQUARE(MACRO_COLOUR_A100_FRIENDLY), _freeLook or {_healthX < MACRO_UNIT_HEALTH_THRESHOLDLOW}, _unitX getVariable [QGVAR(health), 0], !(_unitX getVariable [QGVAR(isUnconscious), false])] + _x + [SQUARE(MACRO_COLOUR_A100_FRIENDLY), _freeLook or {_healthX < MACRO_UNIT_HEALTH_THRESHOLDLOW}, _healthX, !(_unitX getVariable [QGVAR(isUnconscious), false])] ); _teamMates deleteAt _forEachIndex; }; } forEachReversed _teamMates; { - _unitX = _x # 0; - _distX = _x # 2; - - if (_distX < _c_maxDistMedicSqr and {[_unitX] call FUNC(unit_needsHealing)}) then { - _healthX = _unitX getVariable [QGVAR(health), 0]; - + _unitX = _x # 0; + _distX = _x # 2; + _healthX = _unitX getVariable [QGVAR(health), 0]; + + if ( + _distX < _c_maxDistMedicSqr + and {_healthX < 1 or {_unitX getVariable [QGVAR(isUnconscious), false]}} + ) then { _renderData_units pushBack ( _x + [SQUARE(MACRO_COLOUR_A100_SQUAD), _freeLook or {_healthX < MACRO_UNIT_HEALTH_THRESHOLDLOW}, _healthX, !(_unitX getVariable [QGVAR(isUnconscious), false])] ); @@ -40,12 +44,11 @@ if (GVAR(role) == MACRO_ENUM_ROLE_MEDIC and {!(_player getVariable [QGVAR(isUnco // As a non-medic, the player is shown nearby medics when low on health } else { - if !([_player] call FUNC(unit_needsHealing)) then { + private _health = _player getVariable [QGVAR(health), 0]; + if (_health >= 1) then { breakTo QGVAR(ui_sys_drawIcons3D); }; - private _health = _player getVariable [QGVAR(health), -1]; - { _unitX = _x # 0; _distX = _x # 2; @@ -68,7 +71,6 @@ if (GVAR(role) == MACRO_ENUM_ROLE_MEDIC and {!(_player getVariable [QGVAR(isUnco _squadMates deleteAt _forEachIndex; }; } forEachReversed _squadMates; - }; diff --git a/scripts/ui/drawIcons3D/ui_unitIcons_support.sqf b/scripts/ui/drawIcons3D/ui_unitIcons_support.sqf index e69de29..e56b511 100644 --- a/scripts/ui/drawIcons3D/ui_unitIcons_support.sqf +++ b/scripts/ui/drawIcons3D/ui_unitIcons_support.sqf @@ -0,0 +1,159 @@ +private _c_maxDistSupportSqr = MACRO_UI_ICONS3D_MAXDISTANCE_ROLEACTION ^ 2; +private _c_iconResupply = getMissionPath "res\images\abilities\ability_resupply.paa"; + +// Strip specific units from the existing arrays, so we can render them separately while leaving the remaining ones +// for the role-agnostic render method +private _renderData_units = []; + +// As a support, the player is shown nearby units who are in need of resupplying +if (GVAR(role) == MACRO_ENUM_ROLE_SUPPORT) then { + private ["_unitX", "_distX", "_ammoX"]; + + { + _unitX = _x # 0; + _distX = _x # 2; + + if ( + _distX < _c_maxDistSupportSqr + and {[_unitX] call FUNC(unit_isAlive)} + ) then { + _ammoX = [_unitX] call FUNC(lo_getOverallAmmo); + + if (_ammoX < 1) then { + _renderData_units pushBack ( + _x + [SQUARE(MACRO_COLOUR_A100_FRIENDLY), _freeLook or {_ammoX < MACRO_UNIT_AMMO_THRESHOLDLOW}, _ammoX, true] + ); + _teamMates deleteAt _forEachIndex; + }; + }; + } forEachReversed _teamMates; + { + _unitX = _x # 0; + _distX = _x # 2; + + if ( + _distX < _c_maxDistSupportSqr + and {[_unitX] call FUNC(unit_isAlive)} + ) then { + _ammoX = [_unitX] call FUNC(lo_getOverallAmmo); + + if (_ammoX < 1) then { + _renderData_units pushBack ( + _x + [SQUARE(MACRO_COLOUR_A100_SQUAD), _freeLook or {_ammoX < MACRO_UNIT_AMMO_THRESHOLDLOW}, _ammoX, true] + ); + _squadMates deleteAt _forEachIndex; + }; + }; + } forEachReversed _squadMates; + +// As a non-support, the player is shown nearby support units when low on ammo +} else { + private _ammo = [_player] call FUNC(lo_getOverallAmmo); + private ["_unitX", "_distX"]; + + if (_ammo >= 1) then { + breakTo QGVAR(ui_sys_drawIcons3D); + }; + + { + _unitX = _x # 0; + _distX = _x # 2; + + if (_distX < _c_maxDistSupportSqr and {_unitX getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID] == MACRO_ENUM_ROLE_SUPPORT} and {[_unitX] call FUNC(unit_isAlive)}) then { + _renderData_units pushBack ( + _x + [SQUARE(MACRO_COLOUR_A100_FRIENDLY), _ammo < MACRO_UNIT_AMMO_THRESHOLDLOW or {_freeLook}, _ammo] + ); + _teamMates deleteAt _forEachIndex; + }; + } forEachReversed _teamMates; + { + _unitX = _x # 0; + _distX = _x # 2; + + if (_distX < _c_maxDistSupportSqr and {_unitX getVariable [QGVAR(role), MACRO_ENUM_ROLE_INVALID] == MACRO_ENUM_ROLE_SUPPORT} and {[_unitX] call FUNC(unit_isAlive)}) then { + _renderData_units pushBack ( + _x + [SQUARE(MACRO_COLOUR_A100_SQUAD), _ammo < MACRO_UNIT_AMMO_THRESHOLDLOW or {_freeLook}, _ammo] + ); + _squadMates deleteAt _forEachIndex; + }; + } forEachReversed _squadMates; +}; + + + + + +private ["_pos2D", "_nameX", "_colour", "_posXASL", "_angle", "_distMul"]; +{ + _x params ["_unit", "_posX", "_dist", "_colourFill", "_alwaysShown", ["_ammo", 0], ["_showAmmo", false]]; + + // Optimisation: don't continue if the position is too far away, or if the icon is off-screem + if (!_alwaysShown) then { + if (_dist > _c_maxDistSqr) then { + continue; + }; + + _pos2D = worldToScreen _posX; + if (_pos2D isEqualTo []) then { + continue; + }; + }; + + _nameX = name _unit; + + if (_blink and {_ammo < MACRO_UNIT_AMMO_THRESHOLDLOW}) then { + _colour = SQUARE(MACRO_COLOUR_A100_WHITE); + } else { + _colour = _colourFill; + }; + + if (!_alwaysShown) then { + _posXASL = AGLtoASL _posX; + _angle = (_posPly vectorFromTo _posXASL) distanceSqr _dirPly; + + if (_angle > _c_maxAngleSqr) then { + _nameX = ""; + }; + + _distMul = 1 - 0.75 * (sqrt _dist / MACRO_UI_ICONS3D_MAXDISTANCE_INF); + _colour set [3, _distMul]; + }; + + drawIcon3D [ + _c_iconResupply, + _colour, + _posX, + 0.7, + 0.7, + 0, + _nameX, + 2, + 0.03, + MACRO_FONT_UI_THIN, + "center", + true, + 0, + -0.09 * _c_uiScale + ]; + + // Ammo bar + if (_showAmmo) then { + drawIcon3D [ + [_ammo] call FUNC(ui_getFillBarIcon), + _colour, + _posX, + 0.7, + 1.4, + 0, + "", + 2, + 0.03, + MACRO_FONT_UI_THIN, // TahomaB + "center", + true, + 0, + -0.09 * _c_uiScale + ]; + }; + +} forEach _renderData_units; diff --git a/scripts/ui/fn_ui_drawIcons2D.sqf b/scripts/ui/fn_ui_drawIcons2D.sqf new file mode 100644 index 0000000..2f5b3c2 --- /dev/null +++ b/scripts/ui/fn_ui_drawIcons2D.sqf @@ -0,0 +1,141 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [LE] + Handles the drawing of unit/vehicle icons on 2D controls, such as the main map. + + Called via the map control's "Draw" EH. + Arguments: + 0: The map control to draw on + Returns: + (nothing) +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params ["_ctrlMap"]; + + + + + +// Define some macros +#define MACRO_BLINK_INTERVAL 0.5 + +// Set up some constants +private _c_spottedTimeVarName = format [QGVAR(spottedTime_%1), GVAR(side)]; + +// Set up some variables +private _time = time; +private _player = player; +private _groupPly = group _player; +private _vehPly = vehicle _player; +private _mapAngle = ctrlMapDir _ctrlMap; +private _blink = ((_time mod (2 * MACRO_BLINK_INTERVAL)) < MACRO_BLINK_INTERVAL); +private _isSpawned = [_player, true] call FUNC(unit_isAlive); +private _iconsQueue = []; + +scopeName QGVAR(ui_drawIcons2D); + + + +// Aggregate the units data +private _teamMates = []; +private _squadMates = []; +private _spottedEnemies = []; +private ["_posX", "_groupX"]; +{ + _posX = getPosWorld _x; + _groupX = group _x; + + if (side _groupX == GVAR(side)) then { + if (_groupX == _groupPly) then { + _squadMates pushBack [_x, _posX]; + } else { + _teamMates pushBack [_x, _posX]; + }; + } else { + if (_time < _x getVariable [_c_spottedTimeVarName, 0] and {!(_x getVariable [QGVAR(isUnconscious), false])}) then { + _spottedEnemies pushBack [_x, _posX]; + }; + }; +} forEach (allUnits select { + _x == vehicle _x + and {[_x, true] call FUNC(unit_isAlive)} + and {_x != _player} +}); + + + +// Aggregate the vehicles data +private _allVehicles = GVAR(allVehicles) select {alive _x}; +private _teamVehicles = []; +private _squadVehicles = []; +private _spottedVehicles = []; +private _emptyVehicles = []; +private ["_crew", "_unitX", "_groupX", "_groupIndex"]; +{ + _posX = getPosWorld _x; + _crew = crew _x select {[_x] call FUNC(unit_isAlive)}; + _unitX = driver _x; + + if (isNull _unitX) then { + _unitX = _crew param [0, objNull]; + }; + + // Empty vehicles + if (isNull _unitX) then { + if (_x getVariable [QGVAR(side), sideEmpty] == GVAR(side)) then { + _emptyVehicles pushBack [_x, _posX]; + }; + continue; + }; + + // Manned vehicles + _groupX = group _unitX; + if (side _groupX == GVAR(side)) then { + + // Edge case: AI drivers are in a separate group. Fetch the original one. + if (_groupX getVariable [QGVAR(isVehicleGroup), false]) then { + _groupIndex = _unitX getVariable [QGVAR(groupIndex), -1]; + _groupX = missionNamespace getVariable [format [QGVAR(AIGroup_%1_%2), GVAR(side), _groupIndex], _groupX]; + }; + + if (_x == _vehPly or {_groupX == _groupPly}) then { + _squadVehicles pushBack [_x, _posX]; + } else { + _teamVehicles pushBack [_x, _posX]; + }; + } else { + _unitX = _crew param [_crew findIf {_time < _x getVariable [_c_spottedTimeVarName, 0]}, objNull]; + + if (!isNull _unitX) then { + _spottedVehicles pushBack [_x, _posX]; + }; + }; +} forEach _allVehicles; + + + +// Handle role-specific icon drawing +private ["_renderData", "_colour"]; + +#include "drawIcons2D\icons2D_role_support.sqf" + +#include "drawIcons2D\icons2D_role_medic.sqf" + +//#include "drawIcons2D\icons2D_role_engineer.sqf" + + + +// Handle role-agnostic unit and vehicle icon drawing +#include "drawIcons2D\icons2D_vehicles.sqf" + +#include "drawIcons2D\icons2D_units.sqf" + + + +// Render the queued icons, in reverse order +{ + _ctrlMap drawIcon _x; +} forEachReversed _iconsQueue; diff --git a/scripts/ui/fn_ui_drawUnitIcons2D.sqf b/scripts/ui/fn_ui_drawUnitIcons2D.sqf deleted file mode 100644 index 4675f6b..0000000 --- a/scripts/ui/fn_ui_drawUnitIcons2D.sqf +++ /dev/null @@ -1,182 +0,0 @@ -/* -------------------------------------------------------------------------------------------------------------------- - Author: Cre8or - Description: - [LE] - Handles the drawing of unit/vehicle icons on 2D controls, such as the main map. - - Called internally via the control's "Draw" EH. - Arguments: - 0: The map control to draw on - Returns: - (nothing) --------------------------------------------------------------------------------------------------------------------- */ - -#include "..\..\res\common\macros.inc" - -// No parameter validation, as this is an internal function -params ["_ctrlMap"]; - - - - - -// Define some macros -#define MACRO_BLINK_INTERVAL 0.5 - -// Set up some constants -private _c_iconUnit = getMissionPath "res\images\icon_unit.paa"; -private _c_iconUnitUnconscious = getMissionPath "res\images\icon_unit_unconscious.paa"; -private _c_iconHeal = getMissionPath "res\images\abilities\ability_heal.paa"; - -// Set up some variables -private _time = time; -private _player = player; -private _groupPly = group _player; -private _spottedTimeVarName = format [QGVAR(spottedTime_%1), GVAR(side)]; -private _mapAngle = ctrlMapDir _ctrlMap; -private _blink = ((_time mod (2 * MACRO_BLINK_INTERVAL)) < MACRO_BLINK_INTERVAL); - -private _allUnits = allUnits select {_x getVariable [QGVAR(isSpawned), false]}; -private _squadMates = units _groupPly select {alive _x and {vehicle _x == _x} and {_x getVariable [QGVAR(isSpawned), false]}}; -private _teamMates = (_allUnits select {side group _x == GVAR(side) and {vehicle _x == _x}}) - _squadMates; -private _spottedEnemies = _allUnits select {side group _x != GVAR(side) and {_time < _x getVariable [_spottedTimeVarName, 0]} and {vehicle _x == _x} and {[_x] call FUNC(unit_isAlive)}}; - -private _allVehicles = GVAR(allVehicles) select {alive _x}; -private _emptyVehicles = []; -private _squadVehicles = []; -private _teamVehicles = []; -private _enemyVehicles = []; - -private ["_crew", "_driver", "_crewUnit", "_groupX", "_groupIndex"]; -{ - _crew = crew _x select {[_x] call FUNC(unit_isAlive)}; - _driver = driver _x; - _crewUnit = [_driver, _crew param [0, objNull]] select (isNull _driver); - - if (isNull _crewUnit) then { - _emptyVehicles pushBack _x; - continue; - }; - - _groupX = group _crewUnit; - if (side _groupX == GVAR(side)) then { - - // Edge case: AI drivers are in a separate group. Fetch the original one. - if (_groupX getVariable [QGVAR(isVehicleGroup), false]) then { - _groupIndex = _crewUnit getVariable [QGVAR(groupIndex), -1]; - _groupX = missionNamespace getVariable [format [QGVAR(AIGroup_%1_%2), GVAR(side), _groupIndex], _groupX]; - }; - - if (_groupX == _groupPly) then { - _squadVehicles pushBack _x; - } else { - _teamVehicles pushBack _x; - }; - } else { - _crewUnit = _crew param [_crew findIf {_time < _x getVariable [_spottedTimeVarName, 0]}, objNull]; - - if (!isNull _crewUnit) then { - _enemyVehicles pushBack _x; - }; - }; -} forEach _allVehicles; - -// Set up some functions -private ["_typeEnum", "_icon"]; -private _fnc_drawUnit = { - params ["_unit", "_colour"]; - - if (_unit getVariable [QGVAR(isUnconscious), false]) then { - - if (GVAR(role) == MACRO_ENUM_ROLE_MEDIC) then { - _ctrlMap drawIcon [ - _c_iconHeal, - [_colour, SQUARE(MACRO_COLOUR_A100_WHITE)] select _blink, - getPosVisual _unit, - 16, - 16, - 0, - "", - 1 - ]; - } else { - _ctrlMap drawIcon [ - _c_iconUnitUnconscious, - _colour, - getPosVisual _unit, - 12, - 12, - 0, - "", - 2 - ]; - }; - - } else { - _ctrlMap drawIcon [ - _c_iconUnit, - _colour, - getPosVisual _unit, - 12, - 12, - _mapAngle + getDir _unit, - "", - 2 - ]; - }; -}; - -private _fnc_drawVehicle = { - params ["_veh", "_colour"]; - - _typeEnum = [typeOf _veh] call FUNC(veh_getType); - _icon = [_typeEnum] call FUNC(ui_getVehTypeIcon); - - _ctrlMap drawIcon [ - _icon, - _colour, - getPosVisual _veh, - 24, - 24, - _mapAngle + getDir _veh, - "", - 1 - ]; -}; - - - - - -// Draw empty vehicles -{ - [_x, SQUARE(MACRO_COLOUR_A100_WHITE)] call _fnc_drawVehicle; -} forEach _emptyVehicles; - - -// Draw the units -{ - [_x, SQUARE(MACRO_COLOUR_A100_FRIENDLY)] call _fnc_drawUnit; -} forEach _teamMates; - -{ - [_x, SQUARE(MACRO_COLOUR_A100_SQUAD)] call _fnc_drawUnit; -} forEach _squadMates; - -{ - [_x, SQUARE(MACRO_COLOUR_A100_ENEMY)] call _fnc_drawUnit; -} forEach _spottedEnemies; - - -// Draw the vehicles -{ - [_x, SQUARE(MACRO_COLOUR_A100_FRIENDLY)] call _fnc_drawVehicle; -} forEach _teamVehicles; - -{ - [_x, SQUARE(MACRO_COLOUR_A100_SQUAD)] call _fnc_drawVehicle; -} forEach _squadVehicles; - -{ - [_x, SQUARE(MACRO_COLOUR_A100_ENEMY)] call _fnc_drawVehicle; -} forEach _enemyVehicles; diff --git a/scripts/ui/fn_ui_focusMap.sqf b/scripts/ui/fn_ui_focusMap.sqf index a0f598c..233ffc8 100644 --- a/scripts/ui/fn_ui_focusMap.sqf +++ b/scripts/ui/fn_ui_focusMap.sqf @@ -49,6 +49,15 @@ _ctrlMap setVariable [QGVAR(EH_focusMap), _ctrlMap ctrlAddEventHandler ["Draw", missionNamespace getVariable [format [QGVAR(CA_%1), GVAR(side)], []] ) ); + + // DEBUG: Use the combat area of all sides (for generating overview images) + #ifdef MACRO_DEBUG_UI_MAP_OVERVIEWMODE + _allPositions = ( + (missionNamespace getVariable [format [QGVAR(CA_%1), east], []]) + + (missionNamespace getVariable [format [QGVAR(CA_%1), west], []]) + ); + #endif + ([_allPositions] call FUNC(math_boundingBox2D)) params ["_posBL", "_posTR"]; // Center the map diff --git a/scripts/ui/fn_ui_processKillFeedEvent.sqf b/scripts/ui/fn_ui_processKillFeedEvent.sqf index 6c3327a..3fd369b 100644 --- a/scripts/ui/fn_ui_processKillFeedEvent.sqf +++ b/scripts/ui/fn_ui_processKillFeedEvent.sqf @@ -30,7 +30,6 @@ if (!hasInterface or {isNull _victim}) exitWith {}; // Set up some macros -#define MACRO_ICON_UNKNOWN "a3\ui_f\data\IGUI\Cfg\simpleTasks\types\unknown_ca.paa" #define MACRO_ICON_SUICIDE getMissionPath "res\images\suicide.paa" // Set up some variables @@ -40,7 +39,7 @@ private _unitsPly = units group player; private _isKillerFriendly = (GVAR(side) == _killer getVariable [QGVAR(side), sideEmpty]); private _isVictimFriendly = (GVAR(side) == _victim getVariable [QGVAR(side), sideEmpty]); private _iconEnum = MACRO_ENUM_KF_ICON_NONE; -private _weaponIcon = MACRO_ICON_UNKNOWN; +private _weaponIcon = MACRO_KF_ICON_UNKNOWN; private ["_colourKiller", "_colourVictim"]; if (_killer in _unitsPly) then { @@ -96,7 +95,7 @@ if (_iconEnum == MACRO_ENUM_KF_ICON_MINE) then { // Fallback - default icon if (_weaponIcon == "") then { - _weaponIcon = MACRO_ICON_UNKNOWN; + _weaponIcon = MACRO_KF_ICON_UNKNOWN; }; @@ -108,10 +107,10 @@ if (!isNull _killer and {_killer != _victim}) then { GVAR(ui_sys_drawKillFeed_data) pushBack [ time + MACRO_UI_KILLFEED_ENTRYLIFETIME, - name _victim, + name _killer, _iconEnum, _weaponIcon, - name _killer, + name _victim, _colourKiller, _colourVictim ]; @@ -120,10 +119,10 @@ if (!isNull _killer and {_killer != _victim}) then { GVAR(ui_sys_drawKillFeed_data) pushBack [ time + MACRO_UI_KILLFEED_ENTRYLIFETIME, - name _victim, + "", _iconEnum, _weaponIcon, - "", + name _victim, _colourKiller, _colourVictim ]; diff --git a/scripts/ui/fn_ui_processScoreEvent.sqf b/scripts/ui/fn_ui_processScoreEvent.sqf index 5befdc3..05e6c7d 100644 --- a/scripts/ui/fn_ui_processScoreEvent.sqf +++ b/scripts/ui/fn_ui_processScoreEvent.sqf @@ -32,9 +32,9 @@ if (!hasInterface or {_enum == MACRO_ENUM_SCORE_INVALID}) exitWith {}; // Set up some variables MACRO_FNC_INITVAR(GVAR(ui_sys_drawScoreFeed_data), []); MACRO_FNC_INITVAR(GVAR(ui_sys_drawScoreFeed_redrawLast), false); -MACRO_FNC_INITVAR(GVAR(ui_processScoreEvent_soundObj), objNull); +MACRO_FNC_INITVAR(GVAR(ui_processScoreEvent_sound), -1); -private _sound = ""; +private _soundData = []; @@ -70,6 +70,18 @@ switch (_enum) do { // -------- + case MACRO_ENUM_SCORE_RESUPPLY: { + if (_arg isEqualType 0 and {_arg > 0}) then { + _eventData = [ + _arg, + "RESUPPLYING" + ]; + }; + _canStack = true; + }; + + // -------- + case MACRO_ENUM_SCORE_HEAL: { if (_arg isEqualType 0 and {_arg > 0}) then { _eventData = [ @@ -138,7 +150,7 @@ switch (_enum) do { name _arg, SQUARE(MACRO_COLOUR_A100_ENEMY) ]; - _sound = QGVAR(EnemyKilled); + _soundData = [QGVAR(EnemyKilled), 4, 1]; }; }; @@ -153,7 +165,7 @@ switch (_enum) do { SQUARE(MACRO_COLOUR_A100_SQUAD) ] select (_arg in units group player) ]; - _sound = QGVAR(FriendlyKilled); + _soundData = [QGVAR(FriendlyKilled), 3, 1]; }; }; @@ -162,6 +174,7 @@ switch (_enum) do { MACRO_SCORE_HEADSHOT, "HEADSHOT BONUS" ]; + _soundData = [QGVAR(Headshot), 5, 1]; }; // -------- @@ -171,7 +184,7 @@ switch (_enum) do { MACRO_SCORE_DESTROYVEHICLE_ENEMY, "VEHICLE DESTROYED" ]; - _sound = QGVAR(EnemyKilled); + _soundData = [QGVAR(EnemyKilled), 4, 1]; }; case MACRO_ENUM_SCORE_DESTROYVEHICLE_FRIENDLY: { @@ -179,7 +192,7 @@ switch (_enum) do { MACRO_SCORE_DESTROYVEHICLE_FRIENDLY, "FRIENDLY VEHICLE DESTROYED" ]; - _sound = QGVAR(FriendlyKilled); + _soundData = [QGVAR(FriendlyKilled), 3, 1]; }; // -------- @@ -189,15 +202,11 @@ switch (_enum) do { _eventData = [ MACRO_SCORE_SIDEDEFEATED, "DEFEATED", - [ - MACRO_SIDE_NAME_EAST, - MACRO_SIDE_NAME_RESISTANCE, - MACRO_SIDE_NAME_WEST - ] param [GVAR(sides) find _arg, "ENEMY FACTION"], + [_arg] call FUNC(gm_getSideName), SQUARE(MACRO_COLOUR_A100_ENEMY) ]; }; - _sound = QGVAR(SideDefeated); + _soundData = [QGVAR(SideDefeated), 4, 1]; }; }; @@ -230,8 +239,8 @@ if (_eventData isNotEqualTo []) then { ); // Play a sound - if (_sound != "") then { - deleteVehicle GVAR(ui_processScoreEvent_soundObj); - GVAR(ui_processScoreEvent_soundObj) = playSound _sound; + if (_soundData isNotEqualTo []) then { + stopSound GVAR(ui_processScoreEvent_sound); + GVAR(ui_processScoreEvent_sound) = playSoundUI (_soundData + [true]); }; }; diff --git a/scripts/ui/fn_ui_sys_drawHitMarkers.sqf b/scripts/ui/fn_ui_sys_drawHitMarkers.sqf index 4d59439..557363b 100644 --- a/scripts/ui/fn_ui_sys_drawHitMarkers.sqf +++ b/scripts/ui/fn_ui_sys_drawHitMarkers.sqf @@ -39,8 +39,7 @@ GVAR(ui_sys_drawHitMarkers_EH) = addMissionEventHandler ["Draw3D", { if (GVAR(ui_sys_drawHitMarkers_trigger)) then { GVAR(ui_sys_drawHitMarkers_trigger) = false; - // Play a sound - playSound QGVAR(HitMarker); + playSoundUI [QGVAR(HitMarker), 3, 1, true]; }; private _time = time; diff --git a/scripts/ui/fn_ui_sys_drawIcons3D.sqf b/scripts/ui/fn_ui_sys_drawIcons3D.sqf index 42216a8..92a5248 100644 --- a/scripts/ui/fn_ui_sys_drawIcons3D.sqf +++ b/scripts/ui/fn_ui_sys_drawIcons3D.sqf @@ -39,12 +39,10 @@ GVAR(ui_sys_drawIcons3D_EH) = addMissionEventHandler ["Draw3D", { if (dialog or {!([_player, true] call FUNC(unit_isAlive))}) exitWith {}; - scopeName QGVAR(ui_sys_drawIcons3D); - // Set up some constants - private _c_maxDistSqr = MACRO_UI_ICONS3D_MAXDISTANCE_INF ^ 2; - private _c_maxAngleSqr = (0.2 * getObjectFOV cameraOn) ^ 2; // Minimum angle within which unit names should be displayed - private _c_uiScale = getResolution # 5; + private _c_maxDistSqr = MACRO_UI_ICONS3D_MAXDISTANCE_INF ^ 2; + private _c_maxAngleSqr = (0.2 * getObjectFOV cameraOn) ^ 2; // Minimum angle within which unit names should be displayed + private _c_uiScale = getResolution # 5; private _c_spottedTimeVarName = format [QGVAR(spottedTime_%1), GVAR(side)]; // Set up some variables @@ -56,6 +54,10 @@ GVAR(ui_sys_drawIcons3D_EH) = addMissionEventHandler ["Draw3D", { private _blink = ((_time mod (2 * MACRO_BLINK_INTERVAL)) < MACRO_BLINK_INTERVAL); private _freeLook = (inputAction "lookAround" > 0); + scopeName QGVAR(ui_sys_drawIcons3D); + + + // Aggregate the units data private _teamMates = []; private _squadMates = []; @@ -73,7 +75,7 @@ GVAR(ui_sys_drawIcons3D_EH) = addMissionEventHandler ["Draw3D", { _teamMates pushBack [_x, _posX, _distX]; }; } else { - if (_time < _x getVariable [_c_spottedTimeVarName, 0]) then { + if (_time < _x getVariable [_c_spottedTimeVarName, 0] and {!(_x getVariable [QGVAR(isUnconscious), false])}) then { _spottedEnemies pushBack [_x, _posX, _distX]; }; }; @@ -86,18 +88,11 @@ GVAR(ui_sys_drawIcons3D_EH) = addMissionEventHandler ["Draw3D", { // Handle role-specific icon drawing - #include "drawIcons3D\ui_unitIcons_medic.sqf" - - switch (GVAR(role)) do { + #include "drawIcons3D\ui_unitIcons_support.sqf" - case MACRO_ENUM_ROLE_SUPPORT: { - #include "drawIcons3D\ui_unitIcons_support.sqf" - }; + #include "drawIcons3D\ui_unitIcons_medic.sqf" - case MACRO_ENUM_ROLE_ENGINEER: { - #include "drawIcons3D\ui_vehicleIcons_engineer.sqf" - }; - }; + #include "drawIcons3D\ui_vehicleIcons_engineer.sqf" diff --git a/scripts/ui/fn_ui_sys_drawKillFeed.sqf b/scripts/ui/fn_ui_sys_drawKillFeed.sqf index 5230651..6993280 100644 --- a/scripts/ui/fn_ui_sys_drawKillFeed.sqf +++ b/scripts/ui/fn_ui_sys_drawKillFeed.sqf @@ -57,10 +57,10 @@ GVAR(ui_sys_drawKillFeed_EH) = addMissionEventHandler ["EachFrame", { (GVAR(ui_sys_drawKillFeed_data) # _index) params [ "_endTime", - "_nameVictim", + "_nameKiller", "_iconEnum", "_weaponIcon", - "_nameKiller", + "_nameVictim", ["_killerColour", SQUARE(MACRO_COLOUR_A100_WHITE)], ["_victimColour", SQUARE(MACRO_COLOUR_A100_WHITE)] ]; @@ -92,7 +92,7 @@ GVAR(ui_sys_drawKillFeed_EH) = addMissionEventHandler ["EachFrame", { _UI setVariable [QGVAR(animEndTime), _animEndTime]; _UI setVariable [QGVAR(animOffset), _animOffset]; - // If no killer is assigned, move the victim's name control to the left (treat the entry as suicide) + // If no killer is assigned, move the victim's name control to the right (treat the entry as suicide) if (_nameKiller == "") then { _nameKillerWidth = 0; } else { @@ -110,28 +110,30 @@ GVAR(ui_sys_drawKillFeed_EH) = addMissionEventHandler ["EachFrame", { }; _iconPosX = (MACRO_POS_KF_WIDTH - _weaponIconWidth) / 2; - // Victim - _ctrlBackgroundVictim = _UI ctrlCreate [QGVAR(RscFrame), -1, _ctrlGrp]; - _ctrlBackgroundVictim ctrlSetPosition [ - (MACRO_POS_KF_WIDTH - _weaponIconWidth) / 2 - _nameVictimWidth, - 0, - _nameVictimWidth, - MACRO_POS_KF_ENTRY_HEIGHT - ]; - _ctrlBackgroundVictim ctrlCommit 0; - _ctrlBackgroundVictim setVariable [QGVAR(fillColour), SQUARE(MACRO_COLOUR_INGAME_BACKGROUND)]; - _ctrlBackgroundVictim ctrlSetPixelPrecision 2; + // Killer + if (_nameKiller != "") then { + _ctrlBackgroundKiller = _UI ctrlCreate [QGVAR(RscFrame), -1, _ctrlGrp]; + _ctrlBackgroundKiller ctrlSetPosition [ + (MACRO_POS_KF_WIDTH - _weaponIconWidth) / 2 - _nameKillerWidth, + 0, + _nameKillerWidth, + MACRO_POS_KF_ENTRY_HEIGHT + ]; + _ctrlBackgroundKiller ctrlCommit 0; + _ctrlBackgroundKiller setVariable [QGVAR(fillColour), SQUARE(MACRO_COLOUR_INGAME_BACKGROUND)]; + _ctrlBackgroundKiller ctrlSetPixelPrecision 2; - _ctrlNameVictim = _UI ctrlCreate [QGVAR(RscKillFeed_Name_Victim), -1, _ctrlGrp]; - _ctrlNameVictim ctrlSetPosition [ - 0, - 0, - (MACRO_POS_KF_WIDTH - _weaponIconWidth) / 2, - MACRO_POS_KF_ENTRY_HEIGHT - ]; - _ctrlNameVictim ctrlCommit 0; - _ctrlNameVictim ctrlSetText _nameVictim; - _ctrlNameVictim setVariable [QGVAR(textColour), _victimColour]; + _ctrlNameKiller = _UI ctrlCreate [QGVAR(RscKillFeed_Name_Killer), -1, _ctrlGrp]; + _ctrlNameKiller ctrlSetPosition [ + 0, + 0, + (MACRO_POS_KF_WIDTH - _weaponIconWidth) / 2, + MACRO_POS_KF_ENTRY_HEIGHT + ]; + _ctrlNameKiller ctrlCommit 0; + _ctrlNameKiller ctrlSetText _nameKiller; + _ctrlNameKiller setVariable [QGVAR(textColour), _killerColour]; + }; // Weapon _ctrlBackgroundWeapon = _UI ctrlCreate [QGVAR(RscFrame), -1, _ctrlGrp]; @@ -145,14 +147,22 @@ GVAR(ui_sys_drawKillFeed_EH) = addMissionEventHandler ["EachFrame", { _ctrlBackgroundWeapon setVariable [QGVAR(fillColour), SQUARE(MACRO_COLOUR_A50_WHITE)]; _ctrlBackgroundWeapon ctrlSetPixelPrecision 2; - // TODO: - // Suicide icon: "\a3\ui_f\data\IGUI\Cfg\HoldActions\holdAction_forceRespawn_ca.paa" + _ctrlWeapon = _UI ctrlCreate [QGVAR(RscPicture), -1, _ctrlGrp]; + _ctrlWeapon ctrlSetPosition [ + _iconPosX, + 0, + MACRO_POS_KF_WEAPON_WIDTH, + MACRO_POS_KF_ENTRY_HEIGHT + ]; + _ctrlWeapon ctrlCommit 0; + _ctrlWeapon ctrlSetText _weaponIcon; + _ctrlWeapon setVariable [QGVAR(textColour), SQUARE(MACRO_COLOUR_A100_BLACK)]; // Special icon if (_iconEnum != MACRO_ENUM_KF_ICON_NONE) then { _ctrlSpecialIcon = _UI ctrlCreate [QGVAR(RscPicture), -1, _ctrlGrp]; _ctrlSpecialIcon ctrlSetPosition [ - _iconPosX, + _iconPosX + MACRO_POS_KF_WEAPON_WIDTH, 0, MACRO_POS_KF_ICON_WIDTH, MACRO_POS_KF_ENTRY_HEIGHT @@ -166,45 +176,30 @@ GVAR(ui_sys_drawKillFeed_EH) = addMissionEventHandler ["EachFrame", { case MACRO_ENUM_KF_ICON_EXPLOSIVE: {MACRO_KF_ICON_EXPLOSIVE}; default {""}; }); - - _iconPosX = _iconPosX + MACRO_POS_KF_ICON_WIDTH; }; - _ctrlWeapon = _UI ctrlCreate [QGVAR(RscPicture), -1, _ctrlGrp]; - _ctrlWeapon ctrlSetPosition [ - _iconPosX, + // Victim + _ctrlBackgroundVictim = _UI ctrlCreate [QGVAR(RscFrame), -1, _ctrlGrp]; + _ctrlBackgroundVictim ctrlSetPosition [ + (MACRO_POS_KF_WIDTH + _weaponIconWidth) / 2, 0, - MACRO_POS_KF_WEAPON_WIDTH, + _nameVictimWidth, MACRO_POS_KF_ENTRY_HEIGHT ]; - _ctrlWeapon ctrlCommit 0; - _ctrlWeapon ctrlSetText _weaponIcon; - _ctrlWeapon setVariable [QGVAR(textColour), SQUARE(MACRO_COLOUR_A100_BLACK)]; - - // Killer - if (_nameKiller != "") then { - _ctrlBackgroundKiller = _UI ctrlCreate [QGVAR(RscFrame), -1, _ctrlGrp]; - _ctrlBackgroundKiller ctrlSetPosition [ - (MACRO_POS_KF_WIDTH + _weaponIconWidth) / 2, - 0, - _nameKillerWidth, - MACRO_POS_KF_ENTRY_HEIGHT - ]; - _ctrlBackgroundKiller ctrlCommit 0; - _ctrlBackgroundKiller setVariable [QGVAR(fillColour), SQUARE(MACRO_COLOUR_INGAME_BACKGROUND)]; - _ctrlBackgroundKiller ctrlSetPixelPrecision 2; + _ctrlBackgroundVictim ctrlCommit 0; + _ctrlBackgroundVictim setVariable [QGVAR(fillColour), SQUARE(MACRO_COLOUR_INGAME_BACKGROUND)]; + _ctrlBackgroundVictim ctrlSetPixelPrecision 2; - _ctrlNameKiller = _UI ctrlCreate [QGVAR(RscKillFeed_Name_Killer), -1, _ctrlGrp]; - _ctrlNameKiller ctrlSetPosition [ - (MACRO_POS_KF_WIDTH + _weaponIconWidth) / 2, - 0, - (MACRO_POS_KF_WIDTH - _weaponIconWidth) / 2, - MACRO_POS_KF_ENTRY_HEIGHT - ]; - _ctrlNameKiller ctrlCommit 0; - _ctrlNameKiller ctrlSetText _nameKiller; - _ctrlNameKiller setVariable [QGVAR(textColour), _killerColour]; - }; + _ctrlNameVictim = _UI ctrlCreate [QGVAR(RscKillFeed_Name_Victim), -1, _ctrlGrp]; + _ctrlNameVictim ctrlSetPosition [ + (MACRO_POS_KF_WIDTH + _weaponIconWidth) / 2, + 0, + (MACRO_POS_KF_WIDTH - _weaponIconWidth) / 2, + MACRO_POS_KF_ENTRY_HEIGHT + ]; + _ctrlNameVictim ctrlCommit 0; + _ctrlNameVictim ctrlSetText _nameVictim; + _ctrlNameVictim setVariable [QGVAR(textColour), _victimColour]; _ctrls = [_ctrlGrp, _ctrlBackgroundVictim, _ctrlNameVictim, _ctrlBackgroundWeapon, _ctrlSpecialIcon, _ctrlWeapon, _ctrlBackgroundKiller, _ctrlNameKiller]; diff --git a/scripts/ui/fn_ui_sys_hookMapCtrls.sqf b/scripts/ui/fn_ui_sys_hookMapCtrls.sqf index d71158d..0fb887f 100644 --- a/scripts/ui/fn_ui_sys_hookMapCtrls.sqf +++ b/scripts/ui/fn_ui_sys_hookMapCtrls.sqf @@ -44,7 +44,7 @@ GVAR(ui_sys_hookMapCtrls_EH) = addMissionEventHandler ["EachFrame", { } forEach (_ctrlMap getVariable [QGVAR(UI_EH_draw), []]); _ctrlMap setVariable [QGVAR(UI_EH_draw), [ - _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawUnitIcons2D)], + _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawIcons2D)], _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawSectorFlags)], _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawSectorLocations)], _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawCombatArea_map)] @@ -95,7 +95,7 @@ GVAR(ui_sys_hookMapCtrls_EH) = addMissionEventHandler ["EachFrame", { } forEach (_ctrlMap getVariable [QGVAR(UI_EH_draw), []]); _ctrlMap setVariable [QGVAR(UI_EH_draw), [ - _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawUnitIcons2D)], + _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawIcons2D)], _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawSectorFlags)], _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawCombatArea_gps)], _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_handleGPSScale)] diff --git a/scripts/ui/spawnMenu/events/ui_button_click.sqf b/scripts/ui/spawnMenu/events/ui_button_click.sqf index 7537d72..0d6024e 100644 --- a/scripts/ui/spawnMenu/events/ui_button_click.sqf +++ b/scripts/ui/spawnMenu/events/ui_button_click.sqf @@ -25,14 +25,14 @@ case "ui_button_click": { }; // Set up some variables - private _player = player; + private _player = player; private _groupPly = group _player; - private _alive = [_player] call FUNC(unit_isAlive); - private _curMenu = _spawnMenu getVariable [QGVAR(currentMenu), 0]; // We use the menu buttons' frame IDCs to identify the menu - 0 is the control group, but this shouldn't interfere with anything - private _newMenu = _curMenu; - private _newSide = GVAR(side); - private _newRole = GVAR(role); + private _alive = [_player, true] call FUNC(unit_isAlive); + private _curMenu = _spawnMenu getVariable [QGVAR(currentMenu), 0];// We use the menu buttons' frame IDCs to identify the menu - 0 is the control group, but this shouldn't interfere with anything + private _newMenu = _curMenu; + private _newSide = GVAR(side); private _newSpawn = GVAR(spawnSector); + private _newRole = GVAR(ui_sm_role); private _clickedOnRolePreview = false; @@ -43,27 +43,27 @@ case "ui_button_click": { switch (_IDC) do { // Menus - case MACRO_IDC_SM_SIDE_BUTTON: {_newMenu = MACRO_IDC_SM_SIDE_FRAME}; - case MACRO_IDC_SM_ROLE_BUTTON: {_newMenu = MACRO_IDC_SM_ROLE_FRAME}; - case MACRO_IDC_SM_DEPLOY_BUTTON: {_newMenu = MACRO_IDC_SM_DEPLOY_FRAME}; + case MACRO_IDC_SM_SIDE_BUTTON: {_newMenu = MACRO_IDC_SM_SIDE_FRAME}; + case MACRO_IDC_SM_ROLE_BUTTON: {_newMenu = MACRO_IDC_SM_ROLE_FRAME}; + case MACRO_IDC_SM_DEPLOY_BUTTON: {_newMenu = MACRO_IDC_SM_DEPLOY_FRAME}; // Side menu - case MACRO_IDC_SM_SIDE_JOIN_LEFT_BUTTON: {_newSide = east}; - case MACRO_IDC_SM_SIDE_JOIN_MIDDLE_BUTTON: {_newSide = resistance}; - case MACRO_IDC_SM_SIDE_JOIN_RIGHT_BUTTON: {_newSide = west}; + case MACRO_IDC_SM_SIDE_JOIN_LEFT_BUTTON: {_newSide = east}; + case MACRO_IDC_SM_SIDE_JOIN_MIDDLE_BUTTON: {_newSide = resistance}; + case MACRO_IDC_SM_SIDE_JOIN_RIGHT_BUTTON: {_newSide = west}; // Role Preview Frame - //case MACRO_IDC_SM_ROLE_PREVIEW_FRAME: {_clickedOnRolePreview = (_button == 1)}; // Only consider right clicks - case MACRO_IDC_SM_ROLE_PREVIEW_FRAME: {_clickedOnRolePreview = true}; // Consider all clicks + //case MACRO_IDC_SM_ROLE_PREVIEW_FRAME: {_clickedOnRolePreview = (_button == 1)}; // Only consider right clicks + case MACRO_IDC_SM_ROLE_PREVIEW_FRAME: {_clickedOnRolePreview = true}; // Consider all clicks // Roles - case MACRO_IDC_SM_ROLE_SPECOPS_BUTTON: {_newRole = MACRO_ENUM_ROLE_SPECOPS}; - case MACRO_IDC_SM_ROLE_SNIPER_BUTTON: {_newRole = MACRO_ENUM_ROLE_SNIPER}; - case MACRO_IDC_SM_ROLE_ASSAULT_BUTTON: {_newRole = MACRO_ENUM_ROLE_ASSAULT}; - case MACRO_IDC_SM_ROLE_SUPPORT_BUTTON: {_newRole = MACRO_ENUM_ROLE_SUPPORT}; - case MACRO_IDC_SM_ROLE_ENGINEER_BUTTON: {_newRole = MACRO_ENUM_ROLE_ENGINEER}; - case MACRO_IDC_SM_ROLE_MEDIC_BUTTON: {_newRole = MACRO_ENUM_ROLE_MEDIC}; - case MACRO_IDC_SM_ROLE_ANTITANK_BUTTON: {_newRole = MACRO_ENUM_ROLE_ANTITANK}; + case MACRO_IDC_SM_ROLE_SPECOPS_BUTTON: {_newRole = MACRO_ENUM_ROLE_SPECOPS}; + case MACRO_IDC_SM_ROLE_SNIPER_BUTTON: {_newRole = MACRO_ENUM_ROLE_SNIPER}; + case MACRO_IDC_SM_ROLE_ASSAULT_BUTTON: {_newRole = MACRO_ENUM_ROLE_ASSAULT}; + case MACRO_IDC_SM_ROLE_SUPPORT_BUTTON: {_newRole = MACRO_ENUM_ROLE_SUPPORT}; + case MACRO_IDC_SM_ROLE_ENGINEER_BUTTON: {_newRole = MACRO_ENUM_ROLE_ENGINEER}; + case MACRO_IDC_SM_ROLE_MEDIC_BUTTON: {_newRole = MACRO_ENUM_ROLE_MEDIC}; + case MACRO_IDC_SM_ROLE_ANTITANK_BUTTON: {_newRole = MACRO_ENUM_ROLE_ANTITANK}; // Groups case MACRO_IDC_SM_GROUP_JOIN_BUTTON: { @@ -261,8 +261,12 @@ case "ui_button_click": { }; // Check if the player selected a new role - if (_newRole != GVAR(role)) then { - GVAR(role) = _newRole; + if (_newRole != GVAR(ui_sm_role)) then { + + GVAR(ui_sm_role) = _newRole; + if (!_alive) then { + GVAR(role) = GVAR(ui_sm_role); + }; // Update the selected role ["ui_update_role", [true]] call FUNC(ui_spawnMenu); diff --git a/scripts/ui/spawnMenu/events/ui_focus_reset.sqf b/scripts/ui/spawnMenu/events/ui_focus_reset.sqf index f11e0e1..189e14c 100644 --- a/scripts/ui/spawnMenu/events/ui_focus_reset.sqf +++ b/scripts/ui/spawnMenu/events/ui_focus_reset.sqf @@ -2,7 +2,7 @@ case "ui_focus_reset": { _eventExists = true; - // First, set the focus onto the role preveiw controls group's focus frame + // First, set the focus onto the role preview controls group's focus frame ctrlSetFocus (_spawnMenu displayCtrl MACRO_IDC_SM_ROLE_PREVIEW_FOCUS_FRAME); // Next, set the focus onto the main controls group's focus frame diff --git a/scripts/ui/spawnMenu/events/ui_init.sqf b/scripts/ui/spawnMenu/events/ui_init.sqf index ecf919e..d2985d1 100644 --- a/scripts/ui/spawnMenu/events/ui_init.sqf +++ b/scripts/ui/spawnMenu/events/ui_init.sqf @@ -13,6 +13,7 @@ case "ui_init": { MACRO_FNC_INITVAR(GVAR(rt_role_wall),objNull); MACRO_FNC_INITVAR(GVAR(rt_role_unit),objNull); MACRO_FNC_INITVAR(GVAR(rt_role_light),objNull); + MACRO_FNC_INITVAR(GVAR(ui_sm_role), MACRO_ENUM_ROLE_INVALID); MACRO_FNC_INITVAR(GVAR(ui_sm_prevMenu), 0); MACRO_FNC_INITVAR(GVAR(ui_sm_EH_eachFrame), 0); @@ -61,9 +62,9 @@ case "ui_init": { and {GVAR(role) != MACRO_ENUM_ROLE_INVALID} ) then { switch (GVAR(ui_sm_prevMenu)) do { - case MACRO_IDC_SM_ROLE_FRAME: {["ui_button_click", [MACRO_IDC_SM_ROLE_BUTTON]] call FUNC(ui_spawnMenu)}; - case MACRO_IDC_SM_DEPLOY_FRAME: {["ui_button_click", [MACRO_IDC_SM_DEPLOY_BUTTON]] call FUNC(ui_spawnMenu)}; - default {["ui_button_click", [MACRO_IDC_SM_SIDE_BUTTON]] call FUNC(ui_spawnMenu)}; + case MACRO_IDC_SM_ROLE_FRAME: {["ui_button_click", [MACRO_IDC_SM_ROLE_BUTTON]] call FUNC(ui_spawnMenu)}; + case MACRO_IDC_SM_DEPLOY_FRAME: {["ui_button_click", [MACRO_IDC_SM_DEPLOY_BUTTON]] call FUNC(ui_spawnMenu)}; + default {["ui_button_click", [MACRO_IDC_SM_SIDE_BUTTON]] call FUNC(ui_spawnMenu)}; }; } else { // Default to the side menu, forcing the player to pick a side @@ -200,7 +201,7 @@ case "ui_init": { private _ctrlMap = _spawnMenu displayCtrl MACRO_IDC_SM_DEPLOY_MAP; _ctrlMap setVariable [QGVAR(isSpawnMenu), true]; _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawSpawnSector)]; - _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawUnitIcons2D)]; + _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawIcons2D)]; _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawSectorFlags)]; _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawSectorLocations)]; _ctrlMap ctrlAddEventHandler ["Draw", FUNC(ui_drawCombatArea_map)]; diff --git a/scripts/ui/spawnMenu/events/ui_key_down.sqf b/scripts/ui/spawnMenu/events/ui_key_down.sqf index a9326f8..3860142 100644 --- a/scripts/ui/spawnMenu/events/ui_key_down.sqf +++ b/scripts/ui/spawnMenu/events/ui_key_down.sqf @@ -14,7 +14,6 @@ case "ui_key_down": { - // If the player is currently naming their new group... if (_isNamingGroup) then { _spawnMenu_return = false; @@ -56,6 +55,11 @@ case "ui_key_down": { } else { + // If the enter key was pressed, simulate clicking the "Spawn" button + if (_key == 28 or {_key == 156}) then { + ["ui_button_click", MACRO_IDC_SM_SPAWN_BUTTON] call FUNC(ui_spawnMenu); + }; + // If the night vision key was pressed... if (inputAction "nightvision" > 0 and {_key in actionKeys "nightvision"}) then { diff --git a/scripts/ui/spawnMenu/events/ui_update_role.sqf b/scripts/ui/spawnMenu/events/ui_update_role.sqf index 1225cc3..32932c6 100644 --- a/scripts/ui/spawnMenu/events/ui_update_role.sqf +++ b/scripts/ui/spawnMenu/events/ui_update_role.sqf @@ -125,8 +125,13 @@ case "ui_update_role": { // Check if the selected role has changed if (_shouldUpdateRole) then { - if (GVAR(role) != MACRO_ENUM_ROLE_INVALID) then { - [GVAR(rt_role_unit), GVAR(side), GVAR(role)] call FUNC(lo_setRoleLoadout); + private _role = GVAR(role); + if ([player, true] call FUNC(unit_isAlive)) then { + _role = GVAR(ui_sm_role); + }; + + if (_role != MACRO_ENUM_ROLE_INVALID) then { + [GVAR(rt_role_unit), GVAR(side), _role] call FUNC(lo_setRoleLoadout); GVAR(rt_role_unit) switchMove selectRandom [ "Acts_AidlPercMstpSloWWpstDnon_warmup_1_loop", "Acts_AidlPercMstpSloWWrflDnon_warmup_3_loop", @@ -144,7 +149,7 @@ case "ui_update_role": { private ["_isSelected"]; { _x params ["_roleX", "_idc_frameX", "_idc_backgroundX"]; - _isSelected = (_roleX == GVAR(role)); + _isSelected = (_roleX == _role); (_spawnMenu displayCtrl _idc_frameX) ctrlSetBackgroundColor ([SQUARE(MACRO_COLOUR_BUTTON_ACTIVE), SQUARE(MACRO_COLOUR_BUTTON_ACTIVE_PRESSED)] select _isSelected); (_spawnMenu displayCtrl _idc_backgroundX) ctrlSetBackgroundColor ([SQUARE(MACRO_COLOUR_A50_WHITE), SQUARE(MACRO_COLOUR_A75_WHITE)] select _isSelected); @@ -216,7 +221,7 @@ case "ui_update_role": { _ctrlLB_members lnbAddRow ["", "", name _x]; _ctrlLB_members lnbSetPicture [[_index, 1], squadParams _x # 0 # 4]; - if (!alive _x) then { + if !([_x] call FUNC(unit_isAlive)) then { for "_i" from 0 to 2 step 2 do { _ctrlLB_members lnbSetColor [[_index, _i], SQUARE(MACRO_COLOUR_A100_GREY)]; }; @@ -232,7 +237,7 @@ case "ui_update_role": { _ctrlLB_members lnbAddRow ["", "AI", GVAR(cl_AIIdentities) # _x # 1]; _ctrlLB_members lnbSetColor [[_index, 1], SQUARE(MACRO_COLOUR_A100_GREY)]; - if (!alive _unit) then { + if !([_unit] call FUNC(unit_isAlive)) then { for "_i" from 0 to 2 step 2 do { _ctrlLB_members lnbSetColor [[_index, _i], SQUARE(MACRO_COLOUR_A100_GREY)]; }; diff --git a/scripts/units/fn_unit_isAlive.sqf b/scripts/units/fn_unit_isAlive.sqf index 48f793f..8380d15 100644 --- a/scripts/units/fn_unit_isAlive.sqf +++ b/scripts/units/fn_unit_isAlive.sqf @@ -7,9 +7,9 @@ combat effective), aswell as scenarios where players have been respawned by the engine, but are still in the spawn menu, or waiting to be respawned by the framework. Arguments: - 0: The unit to be tested - 1: Whether unconsciousness should be considered as being alive (optional, default: - false) + 0: The unit to be tested + 1: Whether unconsciousness should be considered as being alive (optional, default: + false) Returns: True if the unit is alive, otherwise false -------------------------------------------------------------------------------------------------------------------- */ diff --git a/scripts/units/fn_unit_needsHealing.sqf b/scripts/units/fn_unit_needsHealing.sqf deleted file mode 100644 index 76e2c56..0000000 --- a/scripts/units/fn_unit_needsHealing.sqf +++ /dev/null @@ -1,41 +0,0 @@ -/* -------------------------------------------------------------------------------------------------------------------- - Author: Cre8or - Description: - [GA] - Returns whether or not a unit is in need of healing. Any unconscious or alive unit that is not at 100% - health is considered to be in need of healing. Units inside of vehicles may not be healed, and as such - are not considered to need healing, even if they are not at 100% health. - Arguments: - 0: The unit to be tested - Returns: - True if the unit needs healing, otherwise false --------------------------------------------------------------------------------------------------------------------- */ - -#include "..\..\res\common\macros.inc" - -params [ - ["_unit", objNull, [objNull]] -]; - - - - - -// Preliminary tests -if ( - !alive _unit - or {!(_unit getVariable [QGVAR(isSpawned), false])} - or {_unit != vehicle _unit} -) exitWith {false}; - -// Check health and consciousness -if ( - _unit getVariable [QGVAR(health), 0] < 1 - or {_unit getVariable [QGVAR(isUnconscious), false]} -) exitWith {true}; - - - - - -false; diff --git a/scripts/units/fn_unit_onFired.sqf b/scripts/units/fn_unit_onFired.sqf index 639c660..ef0d445 100644 --- a/scripts/units/fn_unit_onFired.sqf +++ b/scripts/units/fn_unit_onFired.sqf @@ -17,7 +17,7 @@ params [ "_unit", "_weapon", - "", + "_muzzle", "", "_ammoType", "_magazine", @@ -83,6 +83,28 @@ if (_unit == player) then { +// Interface with lo_getOverallAmmo +private _curWeapon = currentWeapon _unit; +if (_weapon == _curWeapon) then { + + // To prevent network saturation, only invalidate the cache on the last bullet of the magazine. + private _curAmmo = _unit ammo _muzzle; + if (_curAmmo <= 0) then { + [_unit] call FUNC(lo_updateOverallAmmo); + }; +} else { + // For thrown weapons, the "Reloaded" does not trigger on the last grenade. + // As such, we only need to react to the very last grenade to invalidate the ammo cache. + if (_weapon == "Throw" and {!(_magazine in magazines _unit)}) then { + _unit setVariable [QGVAR(overallAmmo_isValid), false, false]; + [_unit] call FUNC(lo_updateOverallAmmo); + }; +}; + + + + + // If the cached weapon data does not match the computed data, broadcast it if (_ammoData isNotEqualTo _ammoDataLUT) then { _unit setVariable [format [QGVAR(ammoData_%1), _ammoType], _ammoData, true]; diff --git a/scripts/units/fn_unit_onHealUnit.sqf b/scripts/units/fn_unit_onHealUnit.sqf index 121d1f6..5832a72 100644 --- a/scripts/units/fn_unit_onHealUnit.sqf +++ b/scripts/units/fn_unit_onHealUnit.sqf @@ -3,6 +3,7 @@ Description: [LA][GE] Callback function for when a medic has healed (or revived) a local unit. + Handles the actual reviving/healing of the patient. Revival of unconscious units requires the local machine's confirmation, as the bleedout timer is handled locally too (and time is a difficult thing to synchronise in multiplayer). @@ -56,4 +57,4 @@ if (_patient getVariable [QGVAR(isUnconscious), false]) then { }; // Interface with ai_sys_unitControl to make the unit stay put while being healed -_patient setVariable [QGVAR(ai_unitControl_handleMedical_stopTime), _time + MACRO_AI_MEDICAL_PATIENT_STOPDURATIONPERHEAL, false]; +_patient setVariable [QGVAR(ai_unitControl_handleMedical_stopTime), _time + MACRO_AI_ROLEACTION_RECIPIENT_STOPDURATION, false]; diff --git a/scripts/units/fn_unit_onInit.sqf b/scripts/units/fn_unit_onInit.sqf index ed23033..19e7be0 100644 --- a/scripts/units/fn_unit_onInit.sqf +++ b/scripts/units/fn_unit_onInit.sqf @@ -38,12 +38,22 @@ _unit setVariable [QGVAR(EH_unit_onKilled), _unit addEventHandler ["Killed", FUN _unit removeEventHandler ["FiredMan", _unit getVariable [QGVAR(EH_unit_onFired), -1]]; _unit setVariable [QGVAR(EH_unit_onFired), _unit addEventHandler ["FiredMan", FUNC(unit_onFired)], false]; +_unit removeEventHandler ["Reloaded", _unit getVariable [QGVAR(EH_unit_onReloaded), -1]]; +_unit setVariable [QGVAR(EH_unit_onReloaded), _unit addEventHandler ["Reloaded", FUNC(unit_onReloaded)], false]; + private _local = local _unit; _unit setVariable [QGVAR(canCaptureSectors), true, _local]; _unit setVariable [QGVAR(health), 1, _local]; -_unit setVariable [QGVAR(isSpawned), true, _local]; // Interfaces with drawUnitIcons2D and drawIcons3D +_unit setVariable [QGVAR(isSpawned), true, _local]; // Interfaces with drawIcons2D and drawIcons3D + +_unit setVariable [QGVAR(lo_addOverallAmmo_accumulator), 0, false]; // Interfaces with lo_addOverallAmmo +// Disable stamina +if (_local) then { + _unit enableFatigue false; +}; + @@ -78,10 +88,24 @@ if (isPlayer _unit) then { // To fix this, pretend it's a fresh unit. _unit setVariable [QGVAR(gm_sys_removeCorpses_removalTime), -1, false]; + // Add compatibility for ACE's custom events + if (GVAR(hasMod_ace_throwing)) then { + ["ace_firedPlayer", _unit getVariable [QGVAR(EH_unit_ace_firedPlayer), -1]] call CBA_fnc_removeEventHandler; + _unit setVariable [QGVAR(EH_unit_ace_firedPlayer), ["ace_firedPlayer", { + _this params ["", "_weapon"]; + + // We only care about thrown items, to support ACE throwing. + // Everything else is already handled by the default "FiredMan" EH. + if (_weapon == "Throw") then { + _this call FUNC(unit_onFired); + }; + }] call CBA_fnc_addEventHandler, false]; + }; + // AI specific } else { [true, MACRO_ENUM_AI_PRIO_BASESETTINGS, _unit, "AUTOCOMBAT", false] call FUNC(ai_toggleFeature); - +/* // Reset the unit's movement time when firing (prevents it from being flagged as stuck) _unit removeEventHandler ["Fired", _unit getVariable [QGVAR(EH_stuckDetection_fired), -1]]; _unit setVariable [QGVAR(EH_stuckDetection_fired), _unit addEventHandler ["Fired", { @@ -89,8 +113,5 @@ if (isPlayer _unit) then { _unit setVariable [QGVAR(lastMovedTime), time, false]; }], false]; - - // Handle infinite ammo - _unit removeEventHandler ["Reloaded", _unit getVariable [QGVAR(EH_unit_reloaded), -1]]; - _unit setVariable [QGVAR(EH_unit_reloaded), _unit addEventHandler ["Reloaded", FUNC(unit_onReloaded)], false]; +*/ }; diff --git a/scripts/units/fn_unit_onReloaded.sqf b/scripts/units/fn_unit_onReloaded.sqf index 24a05bb..32ee523 100644 --- a/scripts/units/fn_unit_onReloaded.sqf +++ b/scripts/units/fn_unit_onReloaded.sqf @@ -21,14 +21,25 @@ params [ "_oldMagazine" ]; -if (!local _unit or {!([_unit] call FUNC(unit_isAlive))}) exitWith {}; +if (!local _unit) exitWith {}; -// Only refill primary weapon and handgun ammo -if (_weapon == primaryWeapon _unit or {_weapon == handgunWeapon _unit}) then { +// Interface with lo_getOverallAmmo +[_unit] call FUNC(lo_updateOverallAmmo); + +// Interface with subSys_moveToPos +_unit setVariable [QGVAR(ai_unitControl_moveToPos_reloadTime), time + MACRO_AI_CARELESSDURATION, false]; + + + + + +/* +// AI: Only refill primary weapon and handgun ammo +if (!isPlayer _unit and {_weapon == primaryWeapon _unit or {_weapon == handgunWeapon _unit}}) then { // Figure out the magazine classname private _magClass = _oldMagazine param [0, ""]; @@ -36,6 +47,7 @@ if (_weapon == primaryWeapon _unit or {_weapon == handgunWeapon _unit}) then { _magClass = _newMagazine param [0, ""]; }; - // Add a new magazine to the unit + // Infinite ammo, babyyyyy _unit addMagazine _magClass; }; +*/ diff --git a/scripts/units/fn_unit_onResupplyUnit.sqf b/scripts/units/fn_unit_onResupplyUnit.sqf new file mode 100644 index 0000000..0c9927c --- /dev/null +++ b/scripts/units/fn_unit_onResupplyUnit.sqf @@ -0,0 +1,53 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [LA][GE] + Callback function for when a support unit has resupplied a local unit. + Handles the actual resupplying of the recipient. + Arguments: + 0: The support unit + 1: The recipient unit that was resupplied + Returns: + (nothing) +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params [ + ["_support", objNull, [objNull]], + ["_recipient", objNull, [objNull]] +]; + +if (isNull _support or {!local _recipient}) exitWith {}; + + + + + +// Set up some variables +private _time = time; +private _oldAmmo = [_recipient] call FUNC(lo_getOverallAmmo); +private _resupplyCooldown = _recipient getVariable [QGVAR(resupplyCooldown), 0]; + +if (_oldAmmo >= 1 and {_time < _resupplyCooldown}) exitWith {}; + + + + + +// Resupply the recipient +private _ammoAdded = [_recipient, MACRO_ACT_RESUPPLYUNIT_AMOUNT] call FUNC(lo_addOverallAmmo); +private _newAmmo = _oldAmmo + _ammoAdded; + +// Reward the support unit for resupplying +if (_support != _recipient) then { + [_support, MACRO_ENUM_SCORE_RESUPPLY, _ammoAdded] remoteExecCall [QFUNC(gm_addScore), 2, false]; +}; + +// Interface with ai_sys_unitControl to make the unit stay put while being resupplied +_recipient setVariable [QGVAR(ai_unitControl_handleResupply_stopTime), _time + MACRO_ACT_RESUPPLYUNIT_COOLDOWN + MACRO_AI_ROLEACTION_RECIPIENT_STOPDURATION, false]; + +// Enforce a resupply cooldown (to prevent abuse) +if (_newAmmo >= 1) then { + [_recipient] remoteExecCall [QFUNC(unit_setResupplyCooldown), 0, false]; +}; diff --git a/scripts/units/fn_unit_playSound.sqf b/scripts/units/fn_unit_playSound.sqf new file mode 100644 index 0000000..9daa424 --- /dev/null +++ b/scripts/units/fn_unit_playSound.sqf @@ -0,0 +1,87 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [GA][LE] + Plays various 3D sounds on units. + Refer to macros.inc for a list of possible sound enumerations. + Arguments: + 0: The unit in question + 1: The sound enum (optional, default: MACRO_ENUM_SOUND_INVALID) + Returns: + (nothing) +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params [ + ["_unit", objNull, [objNull]], + ["_enum", MACRO_ENUM_SOUND_INVALID, [MACRO_ENUM_SOUND_INVALID]] +]; + + +if (!hasInterface or {!alive _unit}) exitWith {}; + + + + + +private _soundData = []; +private _soundsToStop = []; + +// Fetch the requested sound data +switch (_enum) do { + + case MACRO_ENUM_SOUND_RESUPPLY: { + _soundData = [format [QGVAR(Unit_Resupply_%1), 1 + floor random 2], 20, 1, 0]; + }; + + case MACRO_ENUM_SOUND_HEAL: { + _soundData = selectRandom [ + [QGVAR(Unit_Heal_1), 2.5], + [QGVAR(Unit_Heal_2), 4.15], + [QGVAR(Unit_Heal_3), 4.8], + [QGVAR(Unit_Heal_4), 3.1], + [QGVAR(Unit_Heal_5), 3.6], + [QGVAR(Unit_Heal_6), 0], + [QGVAR(Unit_Heal_7), 0], + [QGVAR(Unit_Heal_8), 0], + [QGVAR(Unit_Heal_9), 0] + ]; + _soundData = [_soundData # 0, 20, 1, 0, _soundData # 1]; + }; + + case MACRO_ENUM_SOUND_VO_DEATH: { + if (0.5 < random 1) then { + _soundData = [format [QGVAR(Unit_VO_Death_Loud_%1), 1 + floor random 16], 200, 1, 0]; + } else { + _soundData = [format [QGVAR(Unit_VO_Death_Quiet_%1), 1 + floor random 16], 100, 1, 0]; + }; + + _soundsToStop = [MACRO_ENUM_SOUND_VO_REVIVE]; + }; + + case MACRO_ENUM_SOUND_VO_REVIVE: { + _soundData = [format [QGVAR(Unit_VO_Revive_%1), 1 + floor random 10], 50, 1, 0]; + }; + +}; + +if (_soundData isEqualTo []) exitWith {}; + + + + + +// Stop any existing sounds for the same enum +private _soundObjName = format [QGVAR(unit_soundObj_%1), _enum]; +deleteVehicle (_unit getVariable [_soundObjName, objNull]); + +// Stop any additional sounds, if requested +{ + deleteVehicle (_unit getVariable [format [QGVAR(unit_soundObj_%1), _x], objNull]); +} forEach _soundsToStop; + +// Play the new sound +private _soundObj = _unit say3D _soundData; + +_unit setVariable [_soundObjName, _soundObj, false]; diff --git a/scripts/units/fn_unit_setIdentityLocal.sqf b/scripts/units/fn_unit_setIdentityLocal.sqf index 51d9943..2866fcd 100644 --- a/scripts/units/fn_unit_setIdentityLocal.sqf +++ b/scripts/units/fn_unit_setIdentityLocal.sqf @@ -14,7 +14,6 @@ #include "..\..\res\common\macros.inc" -// Fetch our params params [ ["_unit", objNull, [objNull]], ["_name", "", [""]], diff --git a/scripts/units/fn_unit_setResupplyCooldown.sqf b/scripts/units/fn_unit_setResupplyCooldown.sqf new file mode 100644 index 0000000..6a486b4 --- /dev/null +++ b/scripts/units/fn_unit_setResupplyCooldown.sqf @@ -0,0 +1,26 @@ +/* -------------------------------------------------------------------------------------------------------------------- + Author: Cre8or + Description: + [GA] + Sets the resupply cooldown of the unit via to the machine's local time. + + Called on every machine via unit_onResupplyUnit. + Arguments: + 0: The unit that was resupplied + Returns: + (nothing) +-------------------------------------------------------------------------------------------------------------------- */ + +#include "..\..\res\common\macros.inc" + +params [ + ["_unit", objNull, [objNull]] +]; + +if (isNull _unit) exitWith {}; + + + + + +_unit setVariable [QGVAR(resupplyCooldown), time + MACRO_UNIT_AMMO_RESUPPLYCOOLDOWN, false]; diff --git a/scripts/units/fn_unit_setUnconscious.sqf b/scripts/units/fn_unit_setUnconscious.sqf index 362c1f0..1b95ed0 100644 --- a/scripts/units/fn_unit_setUnconscious.sqf +++ b/scripts/units/fn_unit_setUnconscious.sqf @@ -39,6 +39,7 @@ _unit setUnconscious _newState; _unit setCaptive _newState; _unit setVariable [QGVAR(isUnconscious), _newState, true]; + // Unconscious if (_newState) then { _unit setVariable [QGVAR(health), 0, true]; @@ -47,6 +48,7 @@ if (_newState) then { moveOut _unit; [_unit] call FUNC(anim_unconscious); + [_unit, MACRO_ENUM_SOUND_VO_DEATH] remoteExecCall [QFUNC(unit_playSound), 0, false]; // Update the respawn time on AI units if (!isPlayer _unit) then { @@ -55,9 +57,14 @@ if (_newState) then { // Revived } else { + [_unit, MACRO_ENUM_SOUND_VO_REVIVE] remoteExecCall [QFUNC(unit_playSound), 0, false]; + // Reset the unit's health to the lowest amount that can be given by a medic _unit setVariable [QGVAR(health), MACRO_ACT_HEALUNIT_AMOUNT, true]; + // Interface with ai_sys_unitControl to make the unit stay put while being healed + _unit setVariable [QGVAR(ai_unitControl_handleMedical_stopTime), _time + MACRO_AI_ROLEACTION_RECIPIENT_STOPDURATION, false]; + [_unit, true] call FUNC(unit_selectBestWeapon); }; diff --git a/scripts/vehicles/fn_veh_getType.sqf b/scripts/vehicles/fn_veh_getType.sqf index 8e06938..9707a1a 100644 --- a/scripts/vehicles/fn_veh_getType.sqf +++ b/scripts/vehicles/fn_veh_getType.sqf @@ -2,7 +2,7 @@ Author: Cre8or Description: [GA] - Returns the objet's vehicle type enum. For a list of possible values, see macros.inc. + Returns the object's vehicle type enum. For a list of possible values, see macros.inc. Arguments: 0: The vehicle to be inspected Returns: