Skip to content

aowlspt_botai.h

Source: abi/aowlspt_botai.h — 273 lines, 12 file-scope functions.

What this header owns

Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.

text
 aowlspt_botai.h -- BOT AI ACTIVATION RESCUE for the post-1.0 EFT host.

One Unity-thread bool field poke that releases the gate which keeps our
offline scavs standing still at their spawn with a knife in their hands and
their primary slung on their back. Same proven, reflection-free shape as
`aowlspt_botcap.h` (static-RVA detour, prologue byte-verified, VirtualQuery
guarded raw hops, guarded write, flag-gated, fail-safe, whole body under the
VEH/SEH guard). No runtime_invoke, no reflection -- both are DEAD on this
build.

## The activation chain (RE, byte-verified against build 1.1.0.1.46777)

  BotSpawner::ActivateBots            0x2565970
    -> IBotCreator.ActivateBot        0x1E0F6E0 / 0x1E0FA60  (tail-jumps to
       BotCreatorClient::SpawnBot     0x1E0EFE0, with a Comfort Callback)
    -> <>c__DisplayClass1{6,7}_0::<ActivateBot>b__0  (the callback)
    -> BotCreatorClient::ActivationFaze          0x1E0FDB0
         (the ONLY caller of PreActivate: E8 at +0x1E0FFFD)
    -> EFT.BotOwner::PreActivate                 0x813F60
         ... builds BotMover / BotWeaponPresetCollection / BotMemory ...
         calls BotWeaponSelector::TakeMainWeapon 0xD40100  (at +0x8142DE)
         and FINALLY, at +0x8145C4:  mov dword ptr [BotOwner+0x30], 1
                                     i.e. `_botState = 1` (PreActivated)

  Then, EVERY FRAME:
    BotsList::UpdateByUnity           0x1BD6510
      -> EFT.BotOwner::UpdateManual   0x81B7C0   (the ONLY caller, E8 @0x1BD6656)

  `BotOwner::UpdateManual` is a three-way switch on `_botState`:
    +0x81B82A  cmp dword ptr [rbx+0x30], 2 ; jne 0x81C440   <- 2 = ACTIVE: the
               whole brain runs (BotStandBy::Update, CalcGoal, ShootData,
               BotDogFight, BotHeadData, ... the bot thinks and moves)
    +0x81C440  cmp dword ptr [rbx+0x30], 1 ; jne <return>   <- 1 = PreActivated
    +0x81C44A  mov rax, [rbx+0x308]                         <- WeaponManager
    +0x81C45A  cmp byte ptr [rax+0x80], 0 ; je <return>     <- **THE GATE**
               (then a NavMesh.SamplePosition; on failure it waits out a
                cooldown and teleports the bot to a BotZone spawn point)
    +0x81C65F  call 0x818F50                                <- BotOwner::Activate
               which at +0x81A855 does `mov dword ptr [rsi+0x30], 2`.

  So a bot only ever leaves state 1 for state 2 -- only ever starts thinking --
  if `BotOwner.WeaponManager.IsReady` is true.

## The gate field (byte-verified accessors)

  EFT.BotOwner.WeaponManager    = pointer at offset **+0x308**
    get_WeaponManager @0x80F040 = `48 8B 81 08 03 00 00 C3`
                                  (mov rax,[rcx+0x308]; ret)
    set_WeaponManager @0x80F057 writes the same slot; the only other writer is
    EFT.BotOwner::Create @0x812825 -- which runs BEFORE PreActivate, so the
    slot is already populated at our hook point.
  BotWeaponManager.IsReady      = bool at offset **+0x80**
    get_IsReady @0xA003D0 = `0F B6 81 80 00 00 00 C3` (movzx eax,byte[rcx+0x80])
    set_IsReady @0xA003E0 = `88 91 80 00 00 00 C3`    (mov byte[rcx+0x80],dl)
  EFT.BotOwner._botState        = int32 at offset **+0x30**
    get_BotState @0x6D2DD0 = `8B 41 30 C3`
    set_BotState @0x80FD40 = `... 89 51 30 ...`

## Who sets IsReady, and why ours never do

  The ONLY writer of `IsReady = true` in the image is inside
  `BotWeaponManager::UpdateFirearmsController` @0xD3CFE0, at +0xD3D45F
  (`mov byte ptr [rbp+0x80], r15b`, r15b == 1, immediately after
  `LookSensor::Init`). Its callers are, exhaustively:
    BotWeaponManager::CheckCurMainWeapon    0xD3C939
    BotWeaponManager::UpdateHandsController 0xD3CF48
    BotStationaryWeaponData::ImplementStationary
  and `UpdateHandsController` is reached ONLY from
    BotWeaponSelector::OnWeaponTaken        0xD41073
  -- the completion callback of the weapon change that PreActivate kicks off
  with `TakeMainWeapon`. If that weapon change never completes, `OnWeaponTaken`
  never fires, `IsReady` stays false, and the bot is pinned in state 1 forever:
  it stands at its spawn, it never thinks, and its hands still hold the default
  Scabbard item (the knife) while the primary stays slung. That is EXACTLY the
  reported symptom, and it is one gate, not two.

## What this does

  Hooks `EFT.BotOwner::PreActivate` (once per bot, on the Unity thread, RCX =
  the BotOwner) and sets `WeaponManager.IsReady = 1`. It does NOT call
  Activate, does NOT touch `_botState`, and does NOT skip the original: the
  game's own `UpdateManual` still runs its NavMesh check and its own
  `Activate()` on its own schedule. All this removes is the weapon-readiness
  veto. For a bot whose weapon change would have completed normally the poke
  is a no-op in effect (OnWeaponTaken sets the same byte to the same value a
  frame or two later); for a bot whose weapon change never completes it is the
  difference between a statue and a working scav.

## Hook target: EFT.BotOwner::PreActivate @ RVA 0x813F60

  Prologue (byte-verified) -- 16 bytes of clean, whole instructions, no
  RIP-relative operand and no branch in the stolen region, comfortably more
  than the engine's 14-byte `jmp [rip+0]`:
    48 89 5C 24 10    mov [rsp+0x10], rbx   (5)
    48 89 6C 24 18    mov [rsp+0x18], rbp   (5)
    48 89 74 24 20    mov [rsp+0x20], rsi   (5)
    57                push rdi              (1)
    48 83 EC 40       sub  rsp, 0x40        (4)
    80 3D 4D 47 ..    cmp  byte [rip+..],0  (disp32 pins the build; NOT stolen)
  On any other build the bytes differ, the guard fails, NULL is returned -- a
  missed bind, never a corrupted game.

Constants

  • AOWLSPT_BOTAI_H
  • AOWL_BA_BOTSTATE_OFF
  • AOWL_BA_ISREADY_OFF
  • AOWL_BA_WEAPONMGR_OFF
  • AOWL_BOTAI_TARGET_COUNT

Types

  • struct AowlBotAiTarget

Functions

SignatureLine
int32_t aowl_ba_off_botstate(void)116
int32_t aowl_ba_off_weaponmgr(void)117
int32_t aowl_ba_off_isready(void)118
void aowl_botai_target_at(int32_t i)145
char aowl_botai_target_name(int32_t i)171
int32_t aowl_botai_target_count(void)176
int32_t aowl_ba_slot_readable(void* at, size_t n)180
int32_t aowl_ba_slot_writable(void* at, size_t n)200
int32_t aowl_botai_read_i32(void* p, int32_t off, int32_t* ok)221
void aowl_botai_read_ptr(void* p, int32_t off, int32_t* ok)234
int32_t aowl_botai_read_u8(void* p, int32_t off, int32_t* ok)247
int32_t aowl_botai_write_u8(void* p, int32_t off, int32_t value)262

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.