MrBill Posted April 9, 2023 Posted April 9, 2023 Hi Guys, I too have been playing with Chat GPT... As a test, I asked it to write some LUA code for a mission I'd like to have. Simple requirements Pick a random group from a pre-defined list and activate it I kill it Pick another one and activate it... Check to see if all are dead then print a message After a couple days of messing around and flying the mission, I've got most of it working except the part that checks for all the dead groups and prints the message. I was flying and shooting missiles to test it...and looking for a faster way. So, I figured I'll just set a timer and destroy the groups after they are activated. None of my event code gets hit on the destroy. So, first question: is there a different event fired on Group.destroy()? I see NO events. Second question: can you review my code and help me out here? I'm truly at a loss. BTW, I'm experienced in SW and coding, just not LUA or DCS coding. Last question: Is there a better way to test this stuff without having to fly it? Are there any inspection tools to debug DCS lua? Thanks in advance! local unitList = {"Bandit1", "Bandit3"} local activatedUnits = {} local function activateUnit() local unit repeat unit = unitList[math.random(#unitList)] until not activatedUnits[unit] activatedUnits[unit] = true local group = Group.getByName(unit) trigger.action.activateGroup(group) trigger.action.outTextForCoalition(coalition.side.BLUE, unit .. " has been activated!", 20) return group end local function allUnitsDead() for num, unitName in pairs(unitList) do local group = Group.getByName(unit) local unit = Unit.getByName(unit) if group then trigger.action.outTextForCoalition(coalition.side.BLUE, unitName .. " FOUND therefore Still alive", 20) return false else trigger.action.outTextForCoalition(coalition.side.BLUE, unitName .. " not found therefore dead", 20) end end return true end function dumpEventInfo(event) local m = {} m[#m+1] = "Event ID: " m[#m+1] = event.id if event.initiator then m[#m+1] = "\nInitiator : " m[#m+1] = event.initiator:getName() end if event.weapon then m[#m+1] = "\nWeapon : " m[#m+1] = event.weapon :getTypeName() end if event.target then m[#m+1] = "\nTarget : " m[#m+1] = event.target :getName() end trigger.action.outText(table.concat(m), 20) end local e = {} function e:onEvent(event) local m = {} if event.id == world.event.S_EVENT_KILL then trigger.action.outText(event.target:getName() .. " Killed by " .. event.initiator:getName(), 20) if allUnitsDead() then trigger.action.outTextForCoalition(coalition.side.BLUE, "All units have been destroyed!", 20) else local delay = math.random(10, 15) timer.scheduleFunction(activateUnit, {}, timer.getTime() + delay) end end if event.id == world.event.S_EVENT_DEAD then trigger.action.outText(event.target:getName() .. " UNIT DEAD Event " .. event.initiator:getName(), 20) end dumpEventInfo(event) end world.addEventHandler(e) activateUnit() function testDestroy() Group.destroy(Group.getByName(currentGroup)) trigger.action.outTextForCoalition(coalition.side.BLUE, "Destroying Current Group: " .. currentGroup, 20) end timer.scheduleFunction(testDestroy, {}, timer.getTime() + 35)
cfrag Posted April 9, 2023 Posted April 9, 2023 24 minutes ago, MrBill said: first question: is there a different event fired on Group.destroy()? I see NO events. Hmmm... What makes you think that groups create an event when they are destroyed? You may want to re-read the documentation on that. 25 minutes ago, MrBill said: Second question: can you review my code and help me out here? I'm truly at a loss. You are not saying what's wrong, except that it doesn't work, so I can't help. Worse, you are basing on AI-generated code, and that is a no-no for me. If you wrote your code yourself, I'd be happy to help. If you use AI to write your code, you don't know what it does, hence I suggest that you use AI to have it corrected. 28 minutes ago, MrBill said: Last question: Is there a better way to test this stuff without having to fly it? Unfortunately no. We've been wishing for something resembling an IDE, but so far we have nothing. It would help tremendously if we could at least run ME in parallel to the mission, but not even that. 2
Kanelbolle Posted April 9, 2023 Posted April 9, 2023 (edited) Try running the function trigger.action.explosion(table vec3 , number power ) on each location of units you want to destroy. If the units are placed in the Mission Editor and not spawned by script you can use the same Trigger action in the Editor called "EXPLODE UNIT" Might need to use "S_EVENT_UNIT_LOST" to get the AI units that are killed. Also can't see any use for the following, since it does not have any group. function testDestroy() Group.destroy(Group.getByName(currentGroup)) trigger.action.outTextForCoalition(coalition.side.BLUE, "Destroying Current Group: " .. currentGroup, 20) end timer.scheduleFunction(testDestroy, {}, timer.getTime() + 35) Edited April 9, 2023 by Kanelbolle WARGAMES Servers: https://forum.dcs.world/topic/301046-wargames-server-pvp-and-pve-campaign-servers/ Scripts: RGC & SPGG https://github.com/AGluttonForPunishment/
cfrag Posted April 9, 2023 Posted April 9, 2023 2 hours ago, Kanelbolle said: Also can't see any use for the following, since it does not have any group. That's pretty much my issue. OP's code doesn't make any sense (see the random mix of group and unit as in Group.getByName(unit) ), and appears to be low-effort. I'm usually happy to help, but I'd expect a modicum of investment. Copying code from some AI Chat does not meet that requirement for me. 4
MrBill Posted April 10, 2023 Author Posted April 10, 2023 Thanks to all for the help. First point. I started with AI code and modified it through testing and learning. If it's crap code to you, that's because I wrote crap code. You should have seen the original code...nowhere close to actually working. As to investment, I've got nearly 16 hours invested in trying to figure this out. So, please don't think I'm just handing a pile of AI code to the group and saying "fix it for me"...not how I roll. As to the specific point about events on unit.destroy(). I DID read the docs such as they are. And YES, I assumed there would be some kind of event. Obviously not. Found that out by testing. Puzzling to me as I was just trying to shorten the test cycles to get the code to work. So, in the larger scheme of things, forget the destroy logic. And if you look at: function e:onEvent(event) you'll see that it should dump EVERY event (as I understand the example code I used for this). Second point: Sorry for my lack of clarity. I want help with the logic behind how to tell if all my Groups (comprised of only one unit) are dead. Third point: All of this works fine except for the logic to tell when ALL of my units or groups are dead. So, maybe the ask should have been: given a table of groups, can you help me check when they are all dead? And when that test is true, print a message to coalition (which I already have done successfully). Finally: I've gone through all the docs I can find and I'm still not clear on whether this logic should be based at the group level or the unit level? Or, to check for group dead, do I need to loop through each unit in the group and check for IT being dead first? I can find nowhere that this is explained clearly. Seems pretty basic as there is the group dead logic in the mission triggers (if my memory serves).
Chump Posted April 10, 2023 Posted April 10, 2023 (edited) On 4/10/2023 at 8:23 AM, MrBill said: Second point: Sorry for my lack of clarity. I want help with the logic behind how to tell if all my Groups (comprised of only one unit) are dead. Third point: All of this works fine except for the logic to tell when ALL of my units or groups are dead. So, maybe the ask should have been: given a table of groups, can you help me check when they are all dead? And when that test is true, print a message to coalition (which I already have done successfully). Finally: I've gone through all the docs I can find and I'm still not clear on whether this logic should be based at the group level or the unit level? Or, to check for group dead, do I need to loop through each unit in the group and check for IT being dead first? I can find nowhere that this is explained clearly. Seems pretty basic as there is the group dead logic in the mission triggers (if my memory serves). Here is some code to tell if units or groups are still alive: -- for units local unitList = {"Bandit1", "Bandit3"} local function allUnitsDead() for _, unitName in pairs(unitList) do local unit = Unit.getByName(unitName) if not unit or not unit:isExist() or unit:getLife() < 1 then return false end end return true end if allUnitsDead() then trigger.action.outTextForCoalition(coalition.side.BLUE, "All units dead!", 10) else trigger.action.outTextForCoalition(coalition.side.BLUE, "At least one unit alive!", 10) end -- for groups local groupList = {"myGroup1"} local function allGroupsDead() for _, groupName in pairs(groupList) do local group = Group.getByName(groupName) if group and group:getSize() > 0 then return false end end return true end if allGroupsDead() then trigger.action.outTextForCoalition(coalition.side.BLUE, "All groups dead!", 10) else trigger.action.outTextForCoalition(coalition.side.BLUE, "At least one group alive!", 10) end To answer your last question, if you only know the groups, then you need to evaluate each unit in each group. If you only know the units, then you can evaluate each of them. Edited April 11, 2023 by Chump fixed LUA
MrBill Posted April 10, 2023 Author Posted April 10, 2023 Thanks Chump! I truly appreciate the help. I'll test it out and reply when it works. For clarification purposes...if I have a group with one unit in it, should I work at the group or the unit level?
Chump Posted April 10, 2023 Posted April 10, 2023 @MrBill If you know for a fact that there is only a single unit in a group that you want to keep track of, I'd suggest working at the unit level. Working at the group level is mainly for when you have an unknown number of units in groups. Keep it simple. If you know the unit names, as in your unitList variable, there is no need to use Group functions (unless you are doing something more complex). It seems like the question that you are trying to answer is, "are these two units still alive?" event.initiator is at the Unit level also, making it easy to determine what is going on in this case. 1
cfrag Posted April 11, 2023 Posted April 11, 2023 (edited) 13 hours ago, MrBill said: For clarification purposes...if I have a group with one unit in it, should I work at the group or the unit level? Groups and units have different scopes, although DCS's lack of good reference material does not help, and neither does the fact that DCS breaks it's own rule of unique naming for groups/units. So should you work on group or unit level? It depends what your scope is. Groups are mainly in DCS (which, we should remember is positively ancient and even if it once was engineered, today is a hodgepodge of organic extensions, and bolted-on features) to organize multiple units: orders and pathing is done on group level, while life and ammo is individual unit level. So if you are trying to find out if all of your units are dead, you have exactly hit the middle. Since you can organize units in groups, and you may want to re-use your code in later missions and then forego the single-unit-group concept, I'd go with group level, and mix in some unit checking when you feel it appropriate, or just to be sure. 17 hours ago, MrBill said: Or, to check for group dead, do I need to loop through each unit in the group and check for IT being dead first? That would be the safest approach, although Group.getSize() may give you a lift. Given the state of DCS's documentation, I'd not entirely trust this, so add guards with "if group then" and "if Group.isExist(group) then"; the docs aren't clear if getSize() can reliably return a zero size count, or if the game returns a null group in that case. Also remember that you are using inactive groups, and accessing inactive groups/units may give unexpected results. 17 hours ago, MrBill said: As to the specific point about events on unit.destroy(). I DID read the docs such as they are. And YES, I assumed there would be some kind of event. Obviously not. Ah, I think there is the mix-up. While there is no event when unit.destroy is invoked, the similar dead event (which has been messed with by ED and has become unreliable for reasons beyond this thread) would be usable, so something like that does exist. The issue was that there is no "dead" event for groups - which is tedious, and an omission indeed. But DCS's event handling is in dire need of overhaul anyway, so let's be happy that there is something at all. 17 hours ago, MrBill said: given a table of groups, can you help me check when they are all dead? @Chump has already kindly supplied a possible solution in allGroupsDead(). Note the clever inversion of logic: it checks if at least one unit is still alive. There are a couple of lines that look suspicious to me, so maybe @Chump wants to jump in, since I feel uncomfortable commenting on their code (but will do it anyway ) : if not group then return false end this looks wrong to me, as it returns false (not all groups dead = some groups are still alive) when group returns null (i.e. is destroyed). I would have thought the following was better: if group and Group.isExist(group) and group:getSize() > 0 then return false -- not all dead, at least one live group found end similarly, the unit-checking part (which is not required since the prior code would already return 'false' when a group has a size count > 0; but it is a good example on how to iterate the group. Still, I think the code would better look like this to abort when the first living unit was found for _, unit in pairs(units) do if unit and Unit.isExist(unit) and unit:getLife() > 1 then return false -- we got a live one, not all dead! end end Note my somewhat precarious jumping between syntactic quasi-oop sugar (with colons ':') and old-school dereferencing with period '.' - it' sure to trip you up, especially if you take advantage of Lua's case sensitivity and use 'unit' (an instance) with 'Unit' (a class, or rather, global table in Lua). I recommend prepending 'the' whenever you are using something that may be mistaken for the class/table, i.e. 'theUnit' vs 'unit'. But that's my silly personal beef with Lua and programming practice Hope this helps, -ch Edited April 11, 2023 by cfrag 1
Chump Posted April 11, 2023 Posted April 11, 2023 @cfrag I appreciate you catching my mistake (not enough coffee yet?) before I confused more than I helped! Maybe it should look more like the following, for simplicity: local function allGroupsDead() for _, groupName in pairs(groupList) do local group = Group.getByName(groupName) if group and group:getSize() > 0 then return false end end return true end 1
Solution MrBill Posted April 14, 2023 Author Solution Posted April 14, 2023 Thanks to all for weighing in on this topic. I found some of the negative issues in Chump's original code and kept trying to get this all to work. Here are some key points that I learned: This death of a group thing is a LOT more involved than the simple Group Dead flag available in the editor. And I'm only talking about airborne groups here. The checking logic needs to check multiple times and the Bandits are going down in flames. Testing this code took forever as I had to fly to do it...(boo hoo!) I had a GREAT time working through all this and now have a working script to share. Here's the working script. Assumes groups are single unit are defined and late activated. Hope this helps and have fun! local groupList = {"Bandit1", "Bandit2", "Bandit3"} local activatedGroups = {} local function allGroupsDead() for _, groupName in pairs(activatedGroups) do trigger.action.outTextForCoalition(coalition.side.BLUE, groupName .. " Being checked for death status...", 20) if Group.getByName(groupName) then return false end end return true end local function checkAllDead() if allGroupsDead() then trigger.action.outTextForCoalition(coalition.side.BLUE, "All targets (Groups) have been destroyed!", 20) else trigger.action.outTextForCoalition(coalition.side.BLUE, "Some Groups still Alive, Checking again soon...", 20) timer.scheduleFunction(checkAllDead, {}, timer.getTime() + 30) end end local function activateGroup() local groupName local counter = 0 repeat groupName = groupList[math.random(#groupList)] counter = counter + 1 until not activatedGroups[groupName] or counter > 100 activatedGroups[groupName] = groupName -- if we don't use the counter this locks up in an infinite loop if counter > 100 then trigger.action.outTextForCoalition(coalition.side.BLUE, groupName .. " No more groups to activate, waiting for death!", 20) checkAllDead() return end local group = Group.getByName(groupName) trigger.action.activateGroup(group) trigger.action.outTextForCoalition(coalition.side.BLUE, groupName .. " has been activated!", 20) return group end local function dumpEventInfo(event) local m = {} m[#m+1] = "Event ID: " m[#m+1] = event.id if event.initiator then m[#m+1] = "\nInitiator : " m[#m+1] = event.initiator:getName() end if event.weapon then m[#m+1] = "\nWeapon : " m[#m+1] = event.weapon :getTypeName() end if event.target then m[#m+1] = "\nTarget : " m[#m+1] = event.target :getName() end trigger.action.outText(table.concat(m), 20) end local e = {} function e:onEvent(event) local m = {} if event.id == world.event.S_EVENT_KILL then trigger.action.outText(event.target:getName() .. " Killed by (KILL EVENT )" .. event.initiator:getName(), 20) if allGroupsDead() then trigger.action.outTextForCoalition(coalition.side.BLUE, "All targets (Groups) have been destroyed!", 20) else local delay = math.random(1 * 60, 3 * 60) timer.scheduleFunction(activateGroup, {}, timer.getTime() + delay) end end --dumpEventInfo(event) end world.addEventHandler(e) activateGroup()
cfrag Posted April 15, 2023 Posted April 15, 2023 13 hours ago, MrBill said: Here's the working script. I'm glad you could make it work. I think that the activateGroup() method can take advantage from table.remove(). That way your can forego the counter/randomized pick&match loops that make the code a bit unwieldly. Instead of checking if the item that has been picked was picked before, simply remove it from the list of possible items to pick, and abort if there are no items to pick upon entry. This has the advantage that the code is faster, and more robust (i.e. keeps working if groupList contains more then 100 items, in which case it has a greater than 50/50 chance to fail after you have picked 50 or more from the total units in groupList. Now, that's an edge case, but it points to a possible design improvement to the way objects are picked. Here's a possible improvement. function activateGroup() if #groupList < 1 then -- list is empty, abort trigger.action.outTextForCoalition(coalition.side.BLUE, groupName .. " No more groups to activate, waiting for death!", 20) checkAllDead() return end local index = math.random(#groupList) -- choose a random index groupName = groupList[index] table.remove(groupList, index) -- remove from table local group = Group.getByName(groupName) trigger.action.activateGroup(group) trigger.action.outTextForCoalition(coalition.side.BLUE, groupName .. " has been activated!", 20) return group end Note the line table.remove(groupList, index) -- remove from table which does the magic of removing the item that we just randomly selected. Since we aren't iterating groupList at the same time, it works well, and makes the code more compact. This in turns enables us to establish the guard if #groupList < 1 then at the top of the method. Note also that your code relies on world.event.kill to regularly check up on your units. I think it could be easier if you just scheduled a "check" function that regularly keeps scheduling itself every 10 seconds or so until all groups are destroyed
MrBill Posted April 15, 2023 Author Posted April 15, 2023 CFrag, Thanks for your help. I'll make the changes you suggest and test it out. You kind of read my mind a bit as I'm about to expand the list of possible groups a lot and make it less predictable. I'm going to set an objective of killing 3 groups before success is declared. That way, the script can choose from a wider set of possible targets.
Recommended Posts