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:
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: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
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.
Using the output from the above, a quick explanation is as follows. Should make sense later on with the example AI code.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
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.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
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:
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.Code:function Allied_SpecialUnitAbilityDemand( abilityPBG, currentDemand ) function Axis_SpecialUnitAbilityDemand( abilityPBG, currentDemand ) function AlliedCW_SpecialUnitAbilityDemand( abilityPBG, currentDemand ) function AxisPE_SpecialUnitAbilityDemand( abilityPBG, currentDemand )
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:
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.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
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.
Notice the change in the Get_demand() function. The 4th ranger is unlikely to show up, and the 5th ranger will NEVER show up.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
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:
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.5Code: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
--
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





