• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[General] AI does not only upgrade tier buildings (custom town halls) but builds a second one

Status
Not open for further replies.
Level 25
Joined
Feb 2, 2006
Messages
1,669
I have created custom campain AI scripts for custom races in my map like: https://raw.githubusercontent.com/tdauth/wowtsr/master/ai/BloodElf.ai

As you can see I use three different custom town hall buildings:

Code:
    constant integer BLOOD_ELF_CASTLE             = 'h00B' // castle
    constant integer BLOOD_ELF_KEEP               = 'h00A' // keep
    constant integer BLOOD_ELF_LUMBER_MILL        = 'h00F' // lumber mill
    constant integer BLOOD_ELF_TOWN_HALL          = 'h009' // town hall

However, instead of only upgrading the one keep they build with tier 1, they build a second castle and maybe a third one (not sure). They still upgrade one tier to the last one.
This does NEVER happen with my custom AI for existing races like undead: https://raw.githubusercontent.com/tdauth/wowtsr/master/ai/Undead.ai
Although the script is almost the same but only with different units and upgrades.

I have also classified my custom buildings as town halls and tier 2 and 3 in the gameplay constants. Does it have something to do with some hardcoded stuff or maybe in the common.ai file?

I found a similar issue here: [Solved] - AI Builds Multiple Halls
He solved it by adding some conditions to the object editor but I cant see them anymore.

Here it wasn't solved: AI town hall spam
This wasn't solved either: AI doesn't recognise tier halls

I could try to limit the unit type or add some conditions but I don't know which.
Maybe it has something to do with SetReplacements?
 
So I'm not sure whether this is the problem or not but when setting the SetBuildUnit or whatever it's called you only need to put the highest tier building the AI is supposed to build.

This is for a campaign AI so I'm assuming they have pre-placed buildings you simply want them to replace. In the case of Human AI youd just have the single JASS line for them to build a Castle and they will then in game build a town Hall and upgrade it as necessary without you needing to specify that they build a town Hall and keep first; if you do then they'll build three town halls with one upgraded to castle, one to keep and one one left as a town Hall. Does that make sense?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,178
Sounds like you have not set the dependency equivalence for the buildings. The AI is not aware that the upgraded town hall counts as its non-upgraded version so instead it makes a second, or third.

The object editor has a field for this, but it is very prone to cyclic loop crashes if set incorrectly. Additionally the gameplay constants also has a field for this, but that might only count for generic "town hall" classification for the AI and not that they are all the same building.

The upgraded town halls have to count as their downgraded version otherwise the AI will order a new replacement downgraded version when upgrading one.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,178
It is possible this behaviour is hard coded. However try setting the Techtree - Dependency Equivalents field for the base town hall building to include its intermediate and final tier buildings and see if that makes a difference. If the AI stops making one of the multiple town halls then the solution is likely to add the final tier to that field of the middle tier. Otherwise this is clearly not helping so something else must be the cause.

If the map starts crashing then these field must be set incorrectly. I have little to no experience with the field so it is possible that my suggestion might not work despite seeming logical.
 
Level 20
Joined
Apr 12, 2018
Messages
494
Adding a conditional check to the AI script seems to work.



If all else fails, you can just give them the lesser buildings and hide them.

I've never seen the Dependency Equivalents solution work for town halls.
 
Level 25
Joined
Feb 2, 2006
Messages
1,669
sry for the double post but I have added:

Code:
call SetReplacements( 1, 2, 3 )
if (((GetUnitCount(BLOOD_ELF_KEEP) == 0) and (GetUnitCount(BLOOD_ELF_CASTLE) == 0))) then
    call SetBuildUnit( 1, BLOOD_ELF_TOWN_HALL )
endif

...

if (((GetUnitCount(BLOOD_ELF_CASTLE) == 0))) then
    call SetBuildUnit( 1, BLOOD_ELF_KEEP )
endif

...

call SetBuildUnit( 1, BLOOD_ELF_CASTLE )

and it did NOT work. They still kept the town hall and the keep. I guess it would only work when the initial buildings get destroyed. One workaround would be to destroy the town hall when they build a keep and give them back the resources for it. I can still try the Techtree - Dependency Equivalents solution.
 
Level 25
Joined
Feb 2, 2006
Messages
1,669
Hey, sry for double post but I found the issue. It is this function from common.ai:
Code:
//============================================================================
function TownCountEx takes integer unitid, boolean only_done, integer townid returns integer

    local integer have_qty = GetUnitCountEx(unitid,only_done,townid)

    if unitid == TOWN_HALL then
        set have_qty = have_qty + GetUnitCountEx(KEEP,false,townid) + GetUnitCountEx(CASTLE,false,townid)
    elseif unitid == KEEP then
        set have_qty = have_qty  + GetUnitCountEx(CASTLE,false,townid)

    elseif unitid == WATCH_TOWER then
        set have_qty = have_qty + GetUnitCountEx(GUARD_TOWER,false,townid) + GetUnitCountEx(CANNON_TOWER,false,townid) + GetUnitCountEx(ARCANE_TOWER,false,townid)

    elseif unitid == PEASANT then
        set have_qty = have_qty + GetUnitCountEx(MILITIA,false,townid)

    elseif unitid == GREAT_HALL then
        set have_qty = have_qty + GetUnitCountEx(STRONGHOLD,false,townid) + GetUnitCountEx(FORTRESS,false,townid)
    elseif unitid == STRONGHOLD then
        set have_qty = have_qty + GetUnitCountEx(FORTRESS,false,townid)

    elseif unitid == HEAD_HUNTER then
        set have_qty = have_qty + GetUnitCountEx(BERSERKER,false,townid)

    elseif unitid == SPIRIT_WALKER then
        set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER_M,false,townid)
    elseif unitid == SPIRIT_WALKER_M then
        set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER,only_done,townid)

    elseif unitid == NECROPOLIS_1 then
        set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_2,false,townid) + GetUnitCountEx(NECROPOLIS_3,false,townid)
    elseif unitid == NECROPOLIS_2 then
        set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_3,false,townid)

    elseif unitid == ZIGGURAT_1 then
        set have_qty = have_qty + GetUnitCountEx(ZIGGURAT_2,false,townid) + GetUnitCountEx(ZIGGURAT_FROST,false,townid)

    elseif unitid == GARGOYLE then
        set have_qty = have_qty + GetUnitCountEx(GARGOYLE_MORPH,false,townid)

    elseif unitid == TREE_LIFE then
        set have_qty = have_qty + GetUnitCountEx(TREE_AGES,false,townid) + GetUnitCountEx(TREE_ETERNITY,false,townid)
    elseif unitid == TREE_AGES then
        set have_qty = have_qty + GetUnitCountEx(TREE_ETERNITY,false,townid)

    elseif unitid == DRUID_TALON then
        set have_qty = have_qty + GetUnitCountEx(DRUID_TALON_M,false,townid)
    elseif unitid == DRUID_TALON_M then
        set have_qty = have_qty + GetUnitCountEx(DRUID_TALON,only_done,townid)

    elseif unitid == DRUID_CLAW then
        set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW_M,false,townid)
    elseif unitid == DRUID_CLAW_M then
        set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW,only_done,townid)

    elseif unitid == ILLIDAN then
        set have_qty = have_qty + GetUnitCountEx(ILLIDAN_DEMON,false,townid)

    endif
    return have_qty
endfunction

This function is called by the build loop and used by TownCount which is used by StartUnit to check if the unit should be build at all. As you can see it clearly has some hardcoded unit types for which it counts the upgraded versions.

Some possible solutions:
  • Use a custom modified common.ai file.
  • Use a custom function instead of SetBuildUnit.
 
Hey, sry for double post but I found the issue. It is this function from common.ai:
Code:
//============================================================================
function TownCountEx takes integer unitid, boolean only_done, integer townid returns integer

    local integer have_qty = GetUnitCountEx(unitid,only_done,townid)

    if unitid == TOWN_HALL then
        set have_qty = have_qty + GetUnitCountEx(KEEP,false,townid) + GetUnitCountEx(CASTLE,false,townid)
    elseif unitid == KEEP then
        set have_qty = have_qty  + GetUnitCountEx(CASTLE,false,townid)

    elseif unitid == WATCH_TOWER then
        set have_qty = have_qty + GetUnitCountEx(GUARD_TOWER,false,townid) + GetUnitCountEx(CANNON_TOWER,false,townid) + GetUnitCountEx(ARCANE_TOWER,false,townid)

    elseif unitid == PEASANT then
        set have_qty = have_qty + GetUnitCountEx(MILITIA,false,townid)

    elseif unitid == GREAT_HALL then
        set have_qty = have_qty + GetUnitCountEx(STRONGHOLD,false,townid) + GetUnitCountEx(FORTRESS,false,townid)
    elseif unitid == STRONGHOLD then
        set have_qty = have_qty + GetUnitCountEx(FORTRESS,false,townid)

    elseif unitid == HEAD_HUNTER then
        set have_qty = have_qty + GetUnitCountEx(BERSERKER,false,townid)

    elseif unitid == SPIRIT_WALKER then
        set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER_M,false,townid)
    elseif unitid == SPIRIT_WALKER_M then
        set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER,only_done,townid)

    elseif unitid == NECROPOLIS_1 then
        set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_2,false,townid) + GetUnitCountEx(NECROPOLIS_3,false,townid)
    elseif unitid == NECROPOLIS_2 then
        set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_3,false,townid)

    elseif unitid == ZIGGURAT_1 then
        set have_qty = have_qty + GetUnitCountEx(ZIGGURAT_2,false,townid) + GetUnitCountEx(ZIGGURAT_FROST,false,townid)

    elseif unitid == GARGOYLE then
        set have_qty = have_qty + GetUnitCountEx(GARGOYLE_MORPH,false,townid)

    elseif unitid == TREE_LIFE then
        set have_qty = have_qty + GetUnitCountEx(TREE_AGES,false,townid) + GetUnitCountEx(TREE_ETERNITY,false,townid)
    elseif unitid == TREE_AGES then
        set have_qty = have_qty + GetUnitCountEx(TREE_ETERNITY,false,townid)

    elseif unitid == DRUID_TALON then
        set have_qty = have_qty + GetUnitCountEx(DRUID_TALON_M,false,townid)
    elseif unitid == DRUID_TALON_M then
        set have_qty = have_qty + GetUnitCountEx(DRUID_TALON,only_done,townid)

    elseif unitid == DRUID_CLAW then
        set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW_M,false,townid)
    elseif unitid == DRUID_CLAW_M then
        set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW,only_done,townid)

    elseif unitid == ILLIDAN then
        set have_qty = have_qty + GetUnitCountEx(ILLIDAN_DEMON,false,townid)

    endif
    return have_qty
endfunction

This function is called by the build loop and used by TownCount which is used by StartUnit to check if the unit should be build at all. As you can see it clearly has some hardcoded unit types for which it counts the upgraded versions.

Some possible solutions:
  • Use a custom modified common.ai file.
  • Use a custom function instead of SetBuildUnit.

Your best solution is to replace default hardcoded buildings of a certain faction with your own and it should get stuff done. Quick and dirty, unless you need ALL the default races together in the map with your Blood Elves.
 
Status
Not open for further replies.
Top