Mahagar 1 Posted September 27, 2023 Hi, this is my first post here so I apologize in advance if I placed this under the wrong thread. I am currently developing a coop MP scenario where you have to take over the city from enemy and kill the main enemy commander. I have the basics up and running, but what I would like to achieve next is that at the end of the mission, ideally on debrief screen, it will display the name of the BLUFOR character with most kills (whether it was a player or AI). There are other stats I would like to show, too, such as name of the one who killed the enemy commander, the one who died first etc, but I think that as long as I get the first stat working I could figure out the rest. I am admittedly not very strong at scripting so I asked chatGPT (dont hate me lol) for help on this and I think it gave me some good pointers, however there are some errors I cant get through. Here is my "setup": init.sqf: // Function to check if a unit is BLUFOR _isBLUFOR = { side _this == west; // Check if the unit's side is BLUFOR }; // Get a list of all units in the mission _allUnits = allUnits; // Execute the kill tracking script for each BLUFOR unit { if (_isBLUFOR _x) then { [_x] execVM "killTracking.sqf"; } } forEach _allUnits; killTracking.sqf: _characterKills = []; _unit addEventHandler ["killed", { private ["_killer"]; _killer = _this select 1; if (side _killer == west && side _this == east) then { _killerName = name _killer; if (!isNil {_characterKills select _killerName}) then { _characterKills select _killerName = _characterKills select _killerName + 1; } else { _characterKills set [_killerName, 1]; } } }]; missionDebriefing.sqf (called directly from editor when trigger's condition of all opfor units being dead is met): // Find the character with the most kills _mostKills = 0; _mostKillsCharacter = ""; { private ["_characterName", "_killCount"]; _characterName = _x; _killCount = _characterKills select _x; if (_killCount > _mostKills) then { _mostKills = _killCount; _mostKillsCharacter = _characterName; } } forEach keys _characterKills; // Display the information in the debriefing screen ["Most kills were achieved by %1", _mostKillsCharacter] call BIS_fnc_dynamictext; by looking at the code I think I understand roughly what are most of the lines supposed to do. However, when I try to run the game with these scripts, it points to an error within init.sqf - something about missing ). Checking the code in Notepad++ I cant see any missing brackets or anything. What am I missing here? Is this a smart way of going about the kill counter or it can be done in easier way ? Any help would be appreciated ^^ Thanks Share this post Link to post Share on other sites
mrcurry 511 Posted September 27, 2023 4 hours ago, Mahagar said: when I try to run the game with these scripts, it points to an error I'm not surprised, it's a mess... like it's probably the worst attempt from chatGPT I've seen so far on this forum, but I'm not here to dunk on language models. I'm deffo more of a "teach a man to fish" kinda guy so here's the gist of what would be my approach: 1. Use initServer.sqf to setup a hashmap to store kills by name and make sure it's accessible globally (in missionNamespace). The kills value should start at 0. 2. Use initServer.sqf to add a EntityKilled mission event handler (MEH). 3. In the MEH check if the killed entity is of side opfor (or w/e side you have as the enemy), then check if the instigator is of side blufor (see above link for example how to correctly get the instigator), if both are true then a. Fetch the instigating unit's name b. Get the kills to that name from the hashmap we created in step 1, remember to default to 0 if not found. c. Add 1 to kills and set the new value into the hashmap, remember to again use the name as key. 4. At mission end trigger a script that: a. Create an empty array for sorting b. Loop over each name in the hashmap and forEach name makes a pair of the kills value and the name with the number of kills being the first of the pair. The pair is then added to the sorting array. c. Sort the array in descending order. d. Select the first pair in the array. e. Format the pair into a string of text. f. Hint the text for all players by using remoteExec. I've marked relevant commands and terms for each step. You can search on the biki for more info if you feel like writing it yourself or, if you are feeling lazy or get stuck, I can write up an example for you. Share this post Link to post Share on other sites
Mahagar 1 Posted September 28, 2023 2 hours ago, mrcurry said: I'm not surprised, it's a mess... like it's probably the worst attempt from chatGPT I've seen so far on this forum, but I'm not here to dunk on language models. I'm deffo more of a "teach a man to fish" kinda guy so here's the gist of what would be my approach: 1. Use initServer.sqf to setup a hashmap to store kills by name and make sure it's accessible globally (in missionNamespace). The kills value should start at 0. 2. Use initServer.sqf to add a EntityKilled mission event handler (MEH). 3. In the MEH check if the killed entity is of side opfor (or w/e side you have as the enemy), then check if the instigator is of side blufor (see above link for example how to correctly get the instigator), if both are true then a. Fetch the instigating unit's name b. Get the kills to that name from the hashmap we created in step 1, remember to default to 0 if not found. c. Add 1 to kills and set the new value into the hashmap, remember to again use the name as key. 4. At mission end trigger a script that: a. Create an empty array for sorting b. Loop over each name in the hashmap and forEach name makes a pair of the kills value and the name with the number of kills being the first of the pair. The pair is then added to the sorting array. c. Sort the array in descending order. d. Select the first pair in the array. e. Format the pair into a string of text. f. Hint the text for all players by using remoteExec. I've marked relevant commands and terms for each step. You can search on the biki for more info if you feel like writing it yourself or, if you are feeling lazy or get stuck, I can write up an example for you. hey, thanks for your reply! not gonna lie, that seems little bit too overwhelming for my capabilities. I was hoping it could be done in bit of easier fashion. Not only I would have to read up on each and every single step, but attempting to do so Im lost right away at hashmap... Share this post Link to post Share on other sites
mrcurry 511 Posted September 28, 2023 5 hours ago, Mahagar said: hey, thanks for your reply! not gonna lie, that seems little bit too overwhelming for my capabilities. I was hoping it could be done in bit of easier fashion. Not only I would have to read up on each and every single step, but attempting to do so Im lost right away at hashmap... No worries, MP compatible code is seldom straightforward. Here's what should be a working example (don't have access to game right now so untested). If any questions then ask. In your initServer.sqf: Spoiler // Define hashmap MAH_killsByName = createHashMap; // Add MEH addMissionEventHandler ["EntityKilled", { params ["_killed", "_killer", "_instigator"]; if (isNull _instigator) then { _instigator = UAVControl vehicle _killer select 0 }; // UAV/UGV player operated road kill if (isNull _instigator) then { _instigator = _killer }; // player driven vehicle road kill //check side of killed if( side group _killed isEqualTo OPFOR ) then { // check side of instigator if( side group _instigator isEqualTo BLUFOR ) then { // get name private _name = name _instigator; // get kills from hashmap private _kills = MAH_killsByName getOrDefault [ _name, 0 ]; // increment by 1 and set to hashmap MAH_killsByName set [ _name, _kills + 1 ]; }; }; }]; In your missionDebriefing.sqf clear the chatGPT garbage and put this: Spoiler // Define an empty array for sorting private _toSort = []; // Loop over the entire hashmap { // Get the name and kills private _name = _x; private _kills = _y; // Put them in a pair, kills first private _pair = [ _kills, _name ]; // Add to _toSort array _toSort pushBack _pair; } forEach MAH_killsByName; // Sort descending based on kills first then name _toSort sort false; // Select the first entry in the now sorted private _firstPair = _toSort select 0; // Make into a string private _string = format ( ["Best killer: %2 Kills: %1"] + _firstPair ); // Send the and text hint to all clients using remoteExec _string remoteExec ["hint", 0]; 1 Share this post Link to post Share on other sites
Joshua9797 38 Posted September 28, 2023 21 minutes ago, mrcurry said: addMissionEventHandler ["EntityKilled", { Oh, the idea of using a Mission Event Handler is brilliant. I was trying to find a solution yesterday and attempted to use a regular event handler ("Killed"). However, I couldn't come up with a good solution for the problem when an enemy is spawned mid mission because the "Killed" EH needs to be associated with a specific unit. Now I need to take a look at some of my scripts and see if I can improve them with a Mission Event Handler. 😄 Share this post Link to post Share on other sites
Mahagar 1 Posted September 28, 2023 damn it worked like a charm, thanks!!! hats off to you for being able to code it from top of your head without access to the game haha! Is there a way how to keep characters name even if it is a player ? I noticed that if AI becomes the one with most kills, his name is displayed properly, but if its me (player), it will override the characters name to mine. Is there a way to keep the original name ? btw just a side question, what's the difference between _killer and _instigator? Share this post Link to post Share on other sites
mrcurry 511 Posted September 28, 2023 4 hours ago, Mahagar said: Is there a way to keep the original name ? Sort of, while AI are given a randomly generated names player controlled units are named after their controlling player's identity a.k.a. the profile name. If an AI controlled unit is taken over by a player the unit's identity shifts to the player's identity, so what you see is your unit's 'original' name. You could fake it by generating a random name if the _instigator is a player but I don't see why you would want to. Surely the point of displaying highest kills is for people to know who it is. A random name will just hide who scored highest and make it seem like an AI always does best. 4 hours ago, Mahagar said: what's the difference between _killer and _instigator? Often they are the same but in certain cases they differ. For example if the shooter is in a vehicle the instigator is the one who pulled the trigger while the _killer is usually the vehicle commander if I remember correctly. Generally _instigator is better at saying who should be awarded the kill (from a gaming perspective). Share this post Link to post Share on other sites
Mahagar 1 Posted September 28, 2023 22 minutes ago, mrcurry said: You could fake it by generating a random name if the _instigator is a player but I don't see why you would want to. Surely the point of displaying highest kills is for people to know who it is. A random name will just hide who scored highest and make it seem like an AI always does best. I see your point. The reason I am asking is because, in this specific MP scenario, you will most likely play for multiple characters throughout the game (unless you are so good you dont get killed even once, which is highly unlikely given the circumstances), so if it returns your player name, say "johnny", you wont know which johnny it is referring to - johnny when he played as infantry machinegunner, or johnny when he was a tank gunner etc. Thats why I was intending to keep the characters original name instead. The way I understand it, the moment you take over an AI unit, its name (John Doe) gets overriden with your player name (Johnny). Is that correct? Quote Often they are the same but in certain cases they differ. For example if the shooter is in a vehicle the instigator is the one who pulled the trigger while the _killer is usually the vehicle commander if I remember correctly. Generally _instigator is better at saying who should be awarded the kill (from a gaming perspective). yeah thats kinda what I thought but wasnt sure. Moreover, if you dont mind helping me with one last thing? (sorry if Im being pain in the ass) Im trying to repurpose your scripts to also include the name of the character (just like in previous case) who killed specific enemy unit, unit named "elPresidente". The way I go about this, is in initServer, I added this (note I changed _name for _nameAssassin to avoid any clashing): addMissionEventHandler ["EntityKilled", { params ["_killed", "_killer", "_instigator"]; if (isNull _instigator) then { _instigator = UAVControl vehicle _killer select 0 }; if (isNull _instigator) then { _instigator = _killer }; if (_this select 0 == elPresidente) then { if(side group _instigator isEqualTo BLUFOR) then { private _nameAssassin = name _instigator; }; }; }]; and in debriefing.sqf I added this: // Make into a string private _stringAssassin = format ( ["%1 receives the Pineapple Pizza Annihilator's Disctinction Medal for eliminating El Presidente.", _nameAssassin] ); // Send the and text hint to all clients using remoteExec _stringAssassin remoteExec ["hint", 0]; What happens is that ingame I get a hint saying "any receives the Pineapple Pizza Annihilator's Disctinction Medal for eliminating El Presidente." But the error Im getting upon executing this last part (your part still works just fine) is _stringAssassin remo> 15:24:29 Error position: <_nameAssassin] ); _stringAssassin remo> 15:24:29 Error Undefined variable in expression: _nameassassin 15:24:29 File C:\Users\adria\Documents\Arma 3\mpmissions\killCounterTest_v01.Altis\missionDebriefing.sqf..., line 37 not sure if I understand it correctly, but it seems like it considers _nameAssassin as undefined variable, but I thought I defined it in initServer? Am I missing something? Share this post Link to post Share on other sites
mrcurry 511 Posted September 29, 2023 On 9/28/2023 at 9:31 PM, Mahagar said: if it returns your player name, say "johnny", you wont know which johnny it is referring to - johnny when he played as infantry machinegunner, or johnny when he was a tank gunner etc. A way you could get around this: Display the name of the player and the type of unit they occupied at the time e.g. "Johnny (Rifleman) - 69 kills" or "Johnny (Machinegunner) - 420 kills" or even "Johnny (T100 Varsuk)" if in a vehicle. To get this working you just need to change how the name is fetched in the MEH to one of these: // Retrieve the name of the unit and the unit type private _name = format [ "%1 (%2)", name _instigator, getText( configFile >> "CfgVehicles" >> typeOf _instigator >> "displayName") ]; // Retrieve the name of the unit and the unit type if unit is on foot or vehicle type if unit is in vehicle. private _name = format [ "%1 (%2)", name _instigator, getText( configFile >> "CfgVehicles" >> typeOf vehicle _instigator >> "displayName" ) ]; As for "elPresidente" you could just handle him in the original MEH as well like so: initServer.sqf - Note the extra global variable at the top. Spoiler // Define hashmap MAH_killsByName = createHashMap; MAH_assassinsName = ""; // Add MEH addMissionEventHandler ["EntityKilled", { params ["_killed", "_killer", "_instigator"]; if (isNull _instigator) then { _instigator = UAVControl vehicle _killer select 0 }; // UAV/UGV player operated road kill if (isNull _instigator) then { _instigator = _killer }; // player driven vehicle road kill //check side of killed if( side group _killed isEqualTo OPFOR ) then { // check side of instigator if( side group _instigator isEqualTo BLUFOR ) then { // get name private _name = name _instigator; // get kills from hashmap private _kills = MAH_killsByName getOrDefault [ _name, 0 ]; // increment by 1 and set to hashmap MAH_killsByName set [ _name, _kills + 1 ]; //check if elPresidente was killed if( _killed isEqualTo elPresidente ) then { MAH_assassinsName = _name; }; }; }; }]; missionDebriefing.sqf: Spoiler // Define an empty array for sorting private _toSort = []; // Loop over the entire hashmap { // Get the name and kills private _name = _x; private _kills = _y; // Put them in a pair, kills first private _pair = [ _kills, _name ]; // Add to _toSort array _toSort pushBack _pair; } forEach MAH_killsByName; // Sort descending based on kills first then name _toSort sort false; // Select the first entry in the now sorted private _firstPair = _toSort select 0; // Make into a string private _string = format ( ["Best killer: %2 Kills: %1"] + _firstPair ); // NEW: Add string of assassin's name if( MAH_assassinsName != "" ) then { _string = _string + format ["\n\n%1 receives the Pineapple Pizza Annihilator's Disctinction Medal for eliminating El Presidente.", MAH_assassinsName]; }; // Send the and text hint to all clients using remoteExec _string remoteExec ["hint", 0]; On 9/28/2023 at 9:31 PM, Mahagar said: Am I missing something? Yes, the variable is declared as a local variable and is only available within the same scope (curly brackets) it was declared, hence we have to move it out into a global variable like I did above. Share this post Link to post Share on other sites
Mahagar 1 Posted September 29, 2023 Quote As for "elPresidente" you could just handle him in the original MEH as well like so: Right, handling it within the same MEH makes sense. However it returns error: 10:58:45 Error in expression < = createHashMap; MAH_assassinsName = ""; addMissionEventHandler ["Entit> 10:58:45 Error position: <; addMissionEventHandler ["Entit> 10:58:45 Error Missing ; 10:58:45 File C:\Users\adria\Documents\Arma 3\mpmissions\killCounterTest_v01.Altis\initServer.sqf..., line 3 if I understand correctly, there is something wrong with declaring the MAH_assassinsName? I tried replacing "" with [], or inserting some placeholder value but nothing 😕 Im not sure how to read these error logs really - almost every time it says "missing ;" but in reality it is rarely the case Share this post Link to post Share on other sites
mrcurry 511 Posted September 30, 2023 On 9/29/2023 at 5:07 PM, Mahagar said: Right, handling it within the same MEH makes sense. However it returns error: 10:58:45 Error in expression < = createHashMap; MAH_assassinsName = ""; addMissionEventHandler ["Entit> 10:58:45 Error position: <; addMissionEventHandler ["Entit> 10:58:45 Error Missing ; 10:58:45 File C:\Users\adria\Documents\Arma 3\mpmissions\killCounterTest_v01.Altis\initServer.sqf..., line 3 if I understand correctly, there is something wrong with declaring the MAH_assassinsName? I tried replacing "" with [], or inserting some placeholder value but nothing 😕 Im not sure how to read these error logs really - almost every time it says "missing ;" but in reality it is rarely the case Looks like copying between forum posts added a few "ghost" characters in my last edit, I've updated the code in the previous post so either remove those spaces or copy the code again. Share this post Link to post Share on other sites
Mahagar 1 Posted October 2, 2023 On 9/30/2023 at 3:55 PM, mrcurry said: Looks like copying between forum posts added a few "ghost" characters in my last edit, I've updated the code in the previous post so either remove those spaces or copy the code again. thats super weird, but thanks for checking it. Now I copied your codes again (deleted the old ones to start fresh) and checked it for any hidden characters in Notepad++ and apart from CR and LF there doesnt seem to be anything malicious but still the code wont run 😕 Now it returns error right upon the start of the game Quote 16:12:51 Error in expression <= createHashMap; MAH_assassinsName = ""; addMissionEventHandler ["EntityKil> 16:12:51 Error position: < addMissionEventHandler ["EntityKil> 16:12:51 Error Invalid number in expression 16:12:51 File C:\Users\adria\Documents\Arma 3\mpmissions\killCounterTest_v01.Altis\initServer.sqf..., line 3 and one upon the end when all enemies are eliminated Quote 16:16:45 Error in expression <\missionDebriefing.sqf" private _toSort = []; { private _name = _x; pr> 16:16:45 Error position: < = []; { private _name = _x; pr> 16:16:45 Error Missing ; 16:16:45 File C:\Users\adria\Documents\Arma 3\mpmissions\killCounterTest_v01.Altis\missionDebriefing.sqf..., line 2 do you think there are still some hidden characters ? How do you check for those ? I went to View -> Show Symbol -> Show All Symbols Share this post Link to post Share on other sites
mrcurry 511 Posted October 2, 2023 55 minutes ago, Mahagar said: 😕do you think there are still some hidden characters ? How do you check for those ? I went to View -> Show Symbol -> Show All Symbols That would be my guess, the lines the error messages are referring to are syntactically correct and should work. Try just manually deleting everything that seems empty and adding your own spaces, tabs and newlines. It may just be easier to rewrite the offending sections from scratch. Share this post Link to post Share on other sites
Harzach 2518 Posted October 2, 2023 2 hours ago, Mahagar said: How do you check for those Here on the forums, they will appear as red dots when pasted into a code block - but only while editing: Offline editors might display them as you see in @mrcurry's post. Share this post Link to post Share on other sites
Mahagar 1 Posted October 3, 2023 seems like I have finally got it all working with your help. Thank you gentlemen! Now I can finally wrap the whole mission up! I really appreciate your support. I will credit you with help when I release it if you dont mind 🙂 Cheers 1 Share this post Link to post Share on other sites