Results 1 to 11 of 11

[How-to] Totally fix and control unit call-in spam. End of Spam.

  1. #1

    [How-to] Totally fix and control unit call-in spam. End of Spam.

    Moderators: Please move this to an appropriate section.


    HOW-TO: Totally fix and control unit call-in spam. End of Spam.

    Since I don't have the time, this isn't going to be a traditional how-to. Actually, I kinda suck at writing documentation, but it should be useful to anyone maintaining a mod or starting a new one. I might also mention that this technique was implemented in the Modern Combat mod, and recently within the Back-To-Basics mod. This works and it works effectively. Proof for sure is that it made it past the mighty Agameanx radar.

    What you'll need is of course the Mod Studio, a decent text editor, possibly a lua interpreter, and just a minor knowledge of AI scripting. I'd suggest a recent clean install of cheatmod for testing purposes.

    STEP ONE:

    Run the following script over the abilities in a mod, answer 'yes' to any permission questions, then double check that the changes were made. All this script does is make sure two simple values in the rgd files are set correctly. Note that any call-in with rgd based hard caps override everything. You might now consider removing certain rgd hard caps as they may not be needed anymore. For instance, something like the hetzer that I believe is hard capped at three.

    As you can see, the macro is pretty straight forward and can be easily modified for a custom mod. Just make sure you already have the proper ability definitions in luaconst.lua and you should be good to go. The latter part of this how-to (mostly) needs a proper luaconst file.

    Code:
    -- README:
    -- Load and run this macro over the abilities from within mod studio. Answer yes to any permission questions.
    --
    -- These two values should help to stop or at least contain the AI spamming of the following call-ins.
    -- In order for this work properly, the AI WILL need rules as in RUP.ai. Otherwise some form of spamming can
    -- still occur, although much slower then default. Check any RGD based hard caps, as these may or may not be
    -- needed anymore.
    
    MainFilePath = "attrib\\attrib\\abilities\\"
    
    -- *-------------------------*
    -- *  Change ability values  *
    -- *-------------------------*
    
    Abilities = {"ally_paradrop_ability.rgd",
                "ally_paradrop_57mm_at.rgd",
                "ally_reinforcements_ability_rangers.rgd",
                "reinforcements_ability.rgd",
                "calliope_reinforce.rgd",
                "pershing_reinforce.rgd",
                "axis_reinforcements_ability_tiger_ace.rgd",
                "axis_stormtrooper_ability.rgd",
                "axis_stormtrooper_ability_with_stug.rgd",
                "axis_stormtrooper_ability_with_tiger.rgd",
                "commonwealth_priest_reinforcement.rgd",
                "glider_troops.rgd",
                "glider_tetrarch.rgd",
                "glider_hq_ability.rgd",
                "commonwealth_churchill_tank_reinforcement.rgd",
                "commonwealth_churchill_avre_reinforcement.rgd",
                "commonwealth_churchill_crocodile_reinforcement.rgd",
                "panzer_elite_luftwaffe_ability.rgd",
                "panzer_elite_infiltration_fallshirmjager_ability.rgd",
                "panzer_elite_wirblewind_ability.rgd",
                "panzer_elite_bergetiger_ability.rgd",
                "panzer_elite_reinforcements_jagdpanther.rgd",
                "panzer_elite_reinforcements_hetzer.rgd",
                "panzer_elite_reinforcements_panther_battlegroup.rgd",
                "panzer_grenadier_reinforcements_ability_small_squads.rgd",
                "panzer_grenadier_reinforcements_ability_large_squads.rgd",
                "panzer_elite_hummel_artillery.rgd"
    }
    
    for x = 1,table.getn(Abilities) do
    
        rgd = loadRgd(MainFilePath..Abilities[x])
    
        print (rgd.name.." ..Done!")
    
        -- Change and save the following
        rgd.GameData.ability_bag.disable_when_active = true
        rgd.GameData.ability_bag.duration_time = 10
        rgd:save()
    
    end
    
    -- *-------------------------*
    -- *                         *
    -- *-------------------------*

    STEP TWO:

    This is the fun part and probably the most complex. To save time I'm going to avoid adding any explanations as to how the AI does what it does and instead drop into the coding examples. What we need to do here is implement the same exact idea for call-ins that relic did for the regular units (now that call-ins won't magically spam). That is, to reduce the demand for a call-in unit based on how many units already exist on the battlefield at any point in time. The more the AI has, the less likely it will build that unit again. Simple right? Not really. I'm choosing to use 'rules_unit_purchase.ai' as the file to work on as its commonly known, and allows dynamic modification instead of mucking around with a few other core strategy files. KISS! Keep It Simple Stupid!!!

    In the file "MODNAME/Data/ai/rules_unit-purchase.ai" you should paste the following function at the very bottom of the file:

    Code:
    function Get_Demand( curDemand, Multiplier, unitcount, initDemand)
    
    if (s_buildorder_phase == false and cache.spawner_count > 0) then
    
        --strategy_unit_purchase.trace("**CI-Demand**: curDemand "..curDemand.." Mutliplier: "..Multiplier.." unitcount: "..unitcount)
    
        if (unitcount == 0 and initDemand > 0) then
            --strategy_unit_purchase.trace("**CI-Demand**: Zero units, Initial demand set: "..initDemand)
            return initDemand
        end
    
        local demandmult = (unitcount * Multiplier + 1)
        local demand = curDemand * demandmult
    
        --strategy_unit_purchase.trace("**CI-Demand**: FINAL MODIFIER: "..-(curDemand - demand).."demandmult: "..demandmult)
        return -(curDemand - demand)
    
    end
    
    --strategy_unit_purchase.trace("**NO SPAWNERS**: Removing Call-In demand restrictions!!!")
    return 0
    
    end
    What this function does is return a negative demand value to Strat_Unit_purchase in the exact same way relic does for regular units, except you have a bit more control here. The parameters are as follows:

    curDemand = Already supplied by another function, but useful if you need to fake a demand value from abilities that use funky scar calls to create call-ins.

    Multiplier = Value used to determine how the demand drops based on unit count. Also determines exactly how many call-in units the AI will try and build. Sort of serves as a pseudo soft cap which is why hard caps may not be needed.

    unitcount = Duh?

    initDemand = This is a bonus to the initial demand value when the AI has zero units on the field. Good for pushing out a single call-in unit faster then regular units. Setting this value to 50 for a STUGH pretty much guarantees the AI will build that call-in before any other regular unit --> But only if it doesn't already have a STUGH running around. IMHO, this value should normally be set as call-ins are much stronger then the regular units, so getting at least one on the field ASAP is acceptable. See the AI examples below.

    *-----*

    Before continuing, here is a tiny piece of code that might be useful for determining/testing various demand multipliers, unit counts, and demand values that apply to my method. Simply plug in some values and review the output. This can be used in some sort of interactive lua setup or it can be run directly within the mod studio as a macro.

    Code:
    SUPAItest.lua
    -- For testing SUP.ai spam reduction code
    
    for UnitCount=0, 6 do
    
        demand = 12            -- Enter any test demand value. Call-ins tend to have much higher demand values
                            -- than regular units.
        demandmult = 1
        unitcap = 99        -- Enter a pretend unit cap. Not Used.
    
        TestValue = -0.40    -- Enter a test value. Must be negative. Controls how the demand value decreases
                            -- per unit currently on field. Can also be used to soft cap call-ins -> Demand
                            -- values of zero or less means the call-in will be ignored.
    
        if (UnitCount >= unitcap) then
            demandmult = 0.01
        end
    
    
        demandmult = demandmult * ( UnitCount * TestValue + 1 )
        demand = demand * demandmult
    
        print ("Current Unit Count: ".. UnitCount.." Demand: "..demand.." DemandMult: "..demandmult)
    
    end
    Using the output from the above, a quick explanation is as follows. Should make sense later on with the example AI code.

    Code:
    Current Unit Count: 0 Demand: 12 DemandMult: 1
    Current Unit Count: 1 Demand: 7.2 DemandMult: 0.6
    Current Unit Count: 2 Demand: 2.4 DemandMult: 0.2
    Current Unit Count: 3 Demand: -2.4 DemandMult: -0.2
    Current Unit Count: 4 Demand: -7.2 DemandMult: -0.6
    Current Unit Count: 5 Demand: -12 DemandMult: -1
    Current Unit Count: 6 Demand: -16.8 DemandMult: -1.4
    When no call-in is on the field, the initial demand for that call-in is at 12. As each call-in ability is used the demand drops even further until it reaches a zero or negative demand value. By the time the AI tries to build the 3rd unit, the demand is significantly low (2.4) which means it isn't likely to overpower the regular units unless the AI already has a good sized army of regular units. Should the AI call in the third unit, the demand now sits in the negative (-2.4) which means the AI will ignore the call-in from that point on. With this example, the AI will normally call in about 2 units with a possible third unit showing up in extreme situations. It will never try to call in a fourth unit (soft cap -0.2). And remember, zero counts as a number.

    BTW - A units class value is a good starting point for a demand value.

    *-----*


    CODING EXAMPLES:

    First off, there are four functions in the default RUP.AI responsible for setting up the rules/demands for call-ins as follows:

    Code:
    function Allied_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    function Axis_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    function AlliedCW_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    function AxisPE_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    Each of these *MUST* have rules to control the spam-able call-ins. No getting by this. Really. Also, some call-ins like the Pershing or Jagdpanther won't need this method at all. Basically, anything that has no hard cap in the RGD file and continuously spams itself will need some kind of control rules. Tis' up to you to figure out what needs this stuff or not.

    Lets start with the axis stormtrooper and the stugh. We've all seen it when either of these two can dominate in late skirmish game to the exclusion of all other units. Once the AI calls them in, they never seem to stop -> not ever. My method fixes this. Here's how:

    Code:
    function Axis_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    
    ..
    ..
        if (abilityPBG == ABILITY.COMMANDER_TREE.AXIS.RECRUIT_STORMTROOPERS) then
    
            local Ucount = UtilPBG_CountTotal( SBP.AXIS.STORMTROOPER )
            --strategy_unit_purchase.trace("**STORMTROOPER_COUNT**:"..Ucount)
    
            return Get_Demand( currentDemand, -0.40, Ucount, 5)
        end
    
        if (abilityPBG == ABILITY.COMMANDER_TREE.AXIS.RECRUIT_STUG) then
    
            local Ucount = UtilPBG_CountTotal( SBP.AXIS.STUGH )
            --strategy_unit_purchase.trace("**STUGH_COUNT**:"..Ucount)
    
            return Get_Demand( currentDemand, -0.55, Ucount, 1)
        end
    ..
    ..
    end
    In the case of the Stormtrooper, we want only 2 squads as normal, with a third possible showing up later. We never want more than 3 on field so the AI will ignore a call to the fourth unit. See my example script output above. For the stugh (be sure to define this in luaconst.scar cuz relic didn't) we only want 1 as normal with a second possible showing up later. We don't want more than 2 on the field at any point in time. Notice the change in the Get_demand() function. Run my nifty little helper macro above to see it for yourself. If the AI goes after the third stugh, it gets ignored. Hence, a soft cap that doesn't need to be in an RGD file at all.

    And what about the allied rangers that populate a map to the point of disgust? Heres how to fix it. In this case we really want at least 4 ranger units with the fourth unit showing up later on (assume the AI is winning). At most, the AI should normally push out about 2-3 rangers. A fourth might show up, but never anything over 4.

    Code:
    function Allied_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    
    ..
    ..
        if (abilityPBG == ABILITY.COMMANDER_TREE.ALLIES.RECRUIT_RANGERS) then
    
            local Ucount = UtilPBG_CountTotal( SBP.ALLIES.RANGER )
            --strategy_unit_purchase.trace("**RANGER_COUNT**:"..Ucount)
    
            return Get_Demand( currentDemand, -0.23, Ucount, 5)
        end
    ..
    ..
    end
    Notice the change in the Get_demand() function. The 4th ranger is unlikely to show up, and the 5th ranger will NEVER show up.

    So lets take a chance on the PE luftwaffe units (cheap and simple) and allow the AI to spam at least 5 with a possible sixth showing up later. Heres how:

    Code:
        if (abilityPBG == ABILITY.COMMANDER_TREE.ELITE.RECRUIT_LUFTWAFFE) then
            local Ucount = UtilPBG_CountTotal( SBP.ELITE.LUFTWAFFE )
            --strategy_unit_purchase.trace("**LUFTWAFFE_COUNT**:"..Ucount)
    
            return Get_Demand( currentDemand + 3.5, -0.155, Ucount, 20)
        end
    Again, notice the change in the Get_demand() function. Also notice the 'initDemand' parameter is at 20 --> We really want at least one luftwaffe squad around at all times. Mind you, we fake an increase to the demand for the squads by 3.5

    --

    By this time, you should see a bit of a pattern here. Most if not all call-ins can be manipulated using these simple code templates. But what about more direct rules that use DEMAND_NeverBuild?. Not to worry, DEMAND_NeverBuild should work as intended. You could, if you want, allow the AI to call in only a single stugh and use DEMAND_NeverBuild to stop building anything else. As stated, a soft cap that needs no RGD file at all. Always better, smaller, and faster to code this then deal with the mod studio and RGD editing.

    No doubt there are better ways to code this stuff (BTB Mod?), but I wanted to keep things basic and simple. Best of luck. Relic should have handled this in the first place! --> Years --> ago?

    I've included a proof of concept file to illustrate this method does indeed work. It's actually cheatmod with only changes to the stugh and the hetzer (removed max squad cap so as to allow spamming). Included are instructions to quickly test this method and a few screen shots. All it needs is to move the *.module files where they normally go and a shortcut to make it happen.

    HTH
    Attached Files
    Last edited by Nurbsico; 9th Aug 12 at 3:56 PM. Reason: Added Attachment

  2. Modding Senior Member Company of Heroes Senior Member  #2
    Celéstial by heart Celution's Avatar
    Join Date
    Apr 2008
    Location
    Sweden
    Very nice to have put this in a tutorial. Me and AGameAnx never got around to writing the whole thing down, and this does save a whole lot of work, thank you!

  3. Technical Help Senior Member  #3

  4. #4
    Is anyone else having problems getting this to work? I've followed the above to the letter but the AI still spams the map to death with Stuh42s and Hetzers etc. Each of the abilities has been editied to amend the disable_when_active and duration_time properties to true and 10 respectively, I have the Get_Demand function copied exactly from above in my rules_unit_purchase.ai file, i've amended each of the special ability demand functions to include the definitions, e.g. in Axis_SpecialUnitAbilityDemand I have the following for the Stuh42:
    Code:
    	if (abilityPBG == ABILITY.COMMANDER_TREE.AXIS.RECRUIT_STUG) then
    
    		local Ucount = UtilPBG_CountTotal( SBP.AXIS.STUGH )
    		--strategy_unit_purchase.trace("**STUGH_COUNT**:"..Ucount)
    
    		return Get_Demand( currentDemand, -0.55, Ucount, 1)
    	end
    I have also added the definition for SBP.AXIS.STUGH in my luaconsts.scar file. I have also tried this with the Hetzer and several other units but the AI still spams the map with them. Is there something i've missed?

  5. Modding Senior Member Company of Heroes Senior Member  #5
    Celéstial by heart Celution's Avatar
    Join Date
    Apr 2008
    Location
    Sweden
    It is because Nurbsico didn't create a shared_timer through his macro, which it the final step to stop AI spam. Actually, to make AI stop spamming the call-ins you simply have to give it a 5 second duration_time, set disable_when_active and shared_timer_player_wide to true and finally creating a unique shared_timer for each ability in Attrib\attrib\shared_timers and set the name of that new timer in your ability under shared_timer..

    The whole AI thing he explained is only to change the demands where AI will want to call in these units.

  6. #6
    Ahh i see, ok cheers, going to try this out.
    Last edited by acid369; 27th Jul 12 at 6:27 AM.

  7. #7
    UPDATE: OK I've created unique shared timers for each of the call in abilities, amended each of the abilities to set the duration_time, disable_when_active and shared_timer_player_wide properties and shared timer names, i have done everything mentioned and the AI is STILL spamming the map with Stuh 42s and Hetzers. Anything else i've missed?

  8. #8
    acid369,

    Is anyone else having problems getting this to work? I've followed the above to the letter but
    Okay now, lets not make this more difficult. The reason my macro doesn't have any sort of shared timer, as well as the fact I never mentioned shared timers is because I never used a shared timer of any sort. They are NOT necessary. But be my guest if anyone wants to do all that extra work. Since you didn't mention a Fatal AI error, my guess is you've made a minor mistake somewhere.

    Are you working on the vanilla 'rules_unit_purchase.ai' file? Did ya know Relic flip-flopped on the call-in function parameters? For example, in the stock RUP.AI (2.6.02) you'd see these function parameters:

    Code:
    function Allied_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    function Axis_SpecialUnitAbilityDemand( unitPBG, currentDemand )
    function AlliedCW_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    function AxisPE_SpecialUnitAbilityDemand( unitPBG, currentDemand )
    ALL of my examples assume the following parameter names. See the difference?

    Code:
    function Allied_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    function Axis_SpecialUnitAbilityDemand( abilityPBG, currentDemand ) --<--
    function AlliedCW_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
    function AxisPE_SpecialUnitAbilityDemand( abilityPBG, currentDemand ) --<--
    Double check you have function parameter names corrected. Since you mentioned the stugH (axis) and the Hetzer (pe), I've the feeling thats the minor mistake. You might still be trying to use 'unitPBG' instead of 'abilityPBG'. Not a big deal really, but one of those variables points to nothing and will fail the if/then statements therefore never reducing demand properly. Like I said, without proper rules, you can still get some form of spamming. Also be sure you actually have the StugH properly defined in luaconst.scar as well. If you don't, it can't be counted and the code will always see zero units on the field even if there are 20+.

    This method works dude, for sure. Hopefully, thats all there is to this, but if worst comes to worst, I can PM you a full version of cheatmod with only changes to the stugH, Hetzer, the needed rgd files, and of course the AI. This way, you can test it to see that it works and compare the changes.

    BTW - I'd ditch the shared timer changes. Thats way more work then you need to do. Believe me, I would have mentioned that fact in the How-To.

    HTH,

  9. #9
    Yeah I checked that, the function parameters were all defined as abilityPBG as they should be, when I read that message I removed the shared timer changes from my abilities and started again doing direct copy / pasting from the tutorial examples but the AI still spammed the map with Hetzers etc, after combining your examples with the shared timer changes it now seems to work fine, with the one exception of the StuH42 - The AI still spams the map with them.

    Here's what i've got defined:

    rules_unit.purchase.ai (within Axis_SpecialUnitAbilityDemand)
    Code:
    	if (abilityPBG == ABILITY.COMMANDER_TREE.AXIS.RECRUIT_STUG) then
    		local Ucount = UtilPBG_CountTotal( SBP.AXIS.STUGH )
    		return Get_Demand( currentDemand, -0.65, Ucount, 1)
    	end
    luaconsts.lua (STUGH definitions)
    Code:
    SBP = {
    
    	AXIS = {
    
    		STUGH = BP_GetSquadBlueprint("sbps/races/axis/vehicles/stug_iv_squad_upgrade.lua"),
    
    	},
    
    }
    
    EBP = {
    
    	WRECKS = {
    
    		STUGH = BP_GetEntityBlueprint("ebps/environment/art_ambient/objects/vehicles/wrecked_vehicles/wrecked_stugiv_upgrade.lua"),
    
    	},
    
    }
    Checked through the entire file and I can't find anything else where i could define the StuH42, just the above which looks right to me

  10. #10
    acid369,

    Of course it wouldn't be a simple thing. Never is. I've used this method in my last AI mod, my current personal AI mod, and the Modern Combat mod was set up using the same thing. Seems to work for me. I've never seen, or even heard of anyone complaining the AI spammed the call-ins after putting my method in place.

    One quick thing you can try for the stugH is this in RUP.ai:

    Code:
    if (abilityPBG == ABILITY.COMMANDER_TREE.AXIS.RECRUIT_STUG) then
          local Ucount = UtilPBG_CountTotal( SBP.AXIS.STUGH )
          if (Ucount >= 1) then
                return DEMAND_NeverBuild
          end
    end
    If that fails, then you have something funky going on, since this would totally cap the stugh to one call-in at a time. Unfortunately, I can't easily tell you how I debug the ai (the old way and perl scripts), but I'd know immediately what was going on from the output. All I can suggest is you double check all your spelling. If the call-in unit can't be counted, then this method won't work, so check the rgd files. Could it be possible you arent working with vanilla COH/TOV?

    I have a proof kit made up of cheatmod that needs nothing more then a shortcut to run. It contains changes for the stugh/hetzer rgd files, modified AI, support files, and screenshots pointing out why this works. Too bad I can't attach that to my How-to. Can't seem to attach it to a pm either. At least you could see exactly the changes I made to only two measly units. The proof kit 'cannot' fail.

  11. #11
    Yeah I tried the DEMAND_NeverBuild with this too, think as you say i just have something funky going on. I'm using the latest 2.602 patched version of the game and my mod is an extension of Spectres Battle of Legends mod, which i converted to work for OF and ToV then extended to include things like the anti spam, return of the real tiger ace etc.

    If you could tell me how you debug the AI i can try that, i'm a php programmer and have worked with perl / cgi etc, also setup a php test script of the demands at http://www.sc-media.co.uk/test.php which i've used to test the call in demands, i dont seem to get any fatal errors occuring in the dev console

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

     

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •