HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

TownThreatened() or something

09-15-2003, 02:10 PM#1
Zycat
Well, do you guys have some workarounds for this nasty bug -

When I playtested my A.I vs my A.I, almost all of the game-deciding factor is who attacks who's base first, since then the defending team will send all of their units to help the attacked player, and continue to send units to the attacked base, one by one as they're created. It doesn't matter if the attacked base survives, or gone, they still send their newly created units there :bgrun: and suicides them :nono: . Often if the attack is repelled, a player or two still do that and just clump there doing nothing.

This has to be the most :nono: bug in the A.I system.
09-16-2003, 09:58 AM#2
Zalamander
Sounds like the "Kemikaze bug" we sometimes happens to trigger with AMAI.
Sadly I have no idea on how we solved it, I guess AIAndy can help you with that.
09-16-2003, 11:39 AM#3
AIAndy
It is similar to the Kamikaze bug. That was when they would send all their units directly after they are created to attack the enemy who attacked them before. It is as if they think that that player still attacks them although he fled.
Now in the current version of TFT I think the Kamikaze bug is gone because I have not seen it in this form since then.
But who knows, it might not have been properly fixed and something you did triggered it. Or it is something that is inert to the way the hard coded AI is implemented.
You mention TownThreatened() . Have you checked if that is true or false when the bug happens?
09-16-2003, 01:05 PM#4
Zycat
This is what I found so far...

There is probably a bug in the standard AI Code (NOT Hardcoded :ggani:) on attacking thread. Since my A.I's attacking 'thread' (and probably yours as well) is based on the original AI, it contains this bug also.

Let's see... how to explain this... I'll just copy-paste important codes first.

Functions taken from standard, unaltered A.I files - common.ai and elf.ai

common.ai's SingleMeleeAttack... the core attacking function.
Code:
//============================================================================
//  SingleMeleeAttack
//============================================================================
function SingleMeleeAttack takes boolean needs_exp, boolean has_siege, boolean major_ok, boolean air_units returns nothing
    local boolean   can_siege
    local real      daytime 
    local unit      hall
    local unit      mega
    local unit      creep
    local unit      common
    local integer   minimum
    local boolean   allies

    call Trace("===SingleMeleeAttack===\n") //xxx

    if TownThreatened() then
        call Trace("sleep 2, town threatened\n") //xxx
        call Sleep(2)
        return
    endif

    // purchase zeppelins
    //
    if get_zeppelin and GetGold() > 300 and GetWood() > 100 then
        call Trace("purchase zep\n") //xxx
        call PurchaseZeppelin()
        set get_zeppelin = false
        set ready_for_zeppelin = false
        return
    endif
    set ready_for_zeppelin = true

    // coordinate with allies
    //
    set allies = GetAllyCount(ai_player) > 0
    if allies and MeleeDifficulty() != MELEE_NEWBIE then
        set common = GetAllianceTarget()
        if common != null then
            call Trace("join ally force\n") //xxx
            if GetMegaTarget() != null then
                call AddSiege()
            endif
            call FormGroup(3,true)
            call AttackMoveKillA(common)
            call SetAllianceTarget(null)
            return
        endif
    endif

    // take expansions as needed
    //
    if needs_exp then
        call Trace("needs exp\n") //xxx
        set creep = GetExpansionFoe()
        if creep != null then
            call Trace("attack exp\n") //xxx
            call SetAllianceTarget(creep)
            call FormGroup(3,true)
            call AttackMoveKillA(creep)
            call Sleep(20)
            set take_exp = false
            return
        endif
    endif

    // all-out attack if the player is weak
    //
    if MeleeDifficulty() != MELEE_NEWBIE then
        set mega = GetMegaTarget()
        if mega != null then
            call Trace("MEGA TARGET!!!\n") //xxx
            call AddSiege()
            call FormGroup(3,true)
            call AttackMoveKillA(mega)
            return
        endif
    endif

    // deny player an expansion
    //
    set hall = GetEnemyExpansion()
    set daytime = GetFloatGameState(GAME_STATE_TIME_OF_DAY)
    set can_siege = has_siege and (air_units or (daytime>=4 and daytime<=12))

    if hall!=null and (can_siege or not IsTowered(hall)) then

        call Trace("test player town attack\n") //xxx

        if MeleeDifficulty() == MELEE_NEWBIE then
            set minimum = 3
        elseif allies and MeleeDifficulty() == MELEE_NORMAL then
            set minimum = 1
        else
            set minimum = 0 // HARD, INSANE, and NORMAL with no allies
        endif

        if exp_seen >= minimum then
            call Trace("do player town attack\n") //xxx
            set exp_seen = 0
            call AddSiege()
            call SetAllianceTarget(hall)
            call FormGroup(3,true)
            call AttackMoveKillA(hall)
            return
        endif

        set exp_seen = exp_seen + 1
    endif

    // attack player's main base when siege is available
    //
    if can_siege then
        call Trace("attack player's town\n") //xxx
        call AddSiege()
        call AnyPlayerAttack()
        return
    endif

    // extended, more specific method of determining creep levels
    //
    if min_creeps != -1 then
        call TraceI("custom creep attack %d\n",max_creeps) //xxx
        call CreepAttackEx()
        return
    endif

    // nothing better to do, so kill a creep camp
    //
    if major_ok then
        call Trace("major creep attack\n") //xxx
        call MajorCreepAttack()
        return
    endif

    call Trace("minor creep attack\n") //xxx
    call MinorCreepAttack()
endfunction

elf.ai's attack_sequence
Code:
//--------------------------------------------------------------------------------------------------
//  attack_sequence
//--------------------------------------------------------------------------------------------------
function attack_sequence takes nothing returns nothing
    local boolean needs_exp
    local boolean has_siege
    local boolean air_units
    local integer level

    loop
        exitwhen c_hero1_done > 0 and c_archer_done >= 2
        call Sleep(2)
    endloop

    if MeleeDifficulty() == MELEE_NEWBIE then
        call Sleep(240)
    endif

    call StaggerSleep(0,2)
    loop
        loop
            exitwhen not CaptainRetreating()
            call Sleep(2)
        endloop

        set wave = wave + 1
        if wave == 2 then
            loop
                exitwhen c_archer_done >= 4
                call Sleep(2)
            endloop
        endif

        call setup_force()

        set level = force_level()
        set max_creeps = level * 4 / 5
        set min_creeps = max_creeps - 10
        if min_creeps < 0 then
            set min_creeps = 0
        endif

        set needs_exp        = take_exp and (level >= 9 or c_gold_owned < 2000)
        set has_siege        = level >= 40 or c_ballista_done > 0 or c_chimaera_done > 0 or c_mtn_giant_done > 0
        set air_units        = c_chimaera_done > 0 or c_dragon_done > 0
        set allow_air_creeps = air_units or c_archer_done > 3

        call SingleMeleeAttack(needs_exp,has_siege,false,air_units)

        if MeleeDifficulty() == MELEE_NEWBIE then
            call Sleep(60)
        endif
    endloop
endfunction

elf.ai's setup_force
Code:
//--------------------------------------------------------------------------------------------------
//  setup_force
//--------------------------------------------------------------------------------------------------
function setup_force takes nothing returns nothing
    call AwaitMeleeHeroes()
    call InitMeleeGroup()

    call SetMeleeGroup( hero_id         )
    call SetMeleeGroup( hero_id2        )
    call SetMeleeGroup( hero_id3        )
    call SetMeleeGroup( ARCHER          )
    call SetMeleeGroup( HUNTRESS        )
    call SetMeleeGroup( DRUID_TALON     )
    call SetMeleeGroup( DRUID_CLAW      )
    call SetMeleeGroup( DRYAD           )
    call SetMeleeGroup( CHIMAERA        )
    call SetMeleeGroup( MOUNTAIN_GIANT  )
    call SetMeleeGroup( FAERIE_DRAGON   )

    if GetUnitCountDone(HIPPO) > 0 then
        call SetMeleeGroup( HIPPO )
    endif

    if GetUnitCountDone(HIPPO_RIDER) > 0 then
        call SetMeleeGroup( HIPPO_RIDER )
    endif

    call SetInitialWave(10)
endfunction

At the start of each wave, attack_sequence calls setup_force and adds each military unit into the attacking force. Then after that it calls SingleMeleeAttack.

Look at the TownThreatened() code in the SingleMeleeAttack. It returns when Town is threatened or captain is in combat.

Now it returns to the attack_sequence and loops once agains, calling setup_force again. When it calls SingleMeleeAttack again, it will probably also return because Town is still threatened or captain is still in combat. Now, if a unit has been created between the first SingleMeleeAttack and the last setup_force, it will call up the unit and send it to the fray because the new unit is also a part of the captain group.

My guess is that either TownThreatened or CaptainInCombat has some timeout value to consider it is not threatened/in combat, so the A.I will send the units one by one as they created, then dying (and probably setting CaptainInCombat's timer for timeout to zero again), then sending new units, dying, new units, dying, ........ blah blah blah.

My solution for now is changing this inside SingleMeleeAttack...
Code:
    if TownThreatened() then
        call Trace("sleep 2, town threatened\n") //xxx
        call Sleep(2)
        return
    endif

with this...
Code:
    loop
        exitwhen not TownThreatened() and not CaptainInCombat(true)
        call Sleep(2)
    endloop

This may be true... or not, I'm not too sure about this and still testing.
09-16-2003, 02:17 PM#5
AIAndy
This would be true if setup_force would actually add the units to the attack group but actually it only adds them to an array. Only when FormGroup is called, the units in the array are actually added to the attack group.