[FE7] The Official AI Documentation Thread

Primary-Secondary AI system

AI byte 1 = Primary AI
AI byte 2 = Secondary AI

First, if a unit is in recovery mode (explained more below), it attempts its recovery action. If not, then primary AI is attempted. If primary AI “succeeds” then the unit makes its action and the turn is over. If primary AI “fails” then secondary AI is executed, which can also succeed or fail. If secondary AI fails, then the unit has the chance to use a Door Key/Lockpick/Antitoxin (explained more below). If none of these can be used, then the unit does nothing. Generally, primary AI involves performing an action such as attack, staff or steal. Secondary AI usually involves only movement. Note there isn’t actually a mechanical thing insuring this, just that IntSys has done it this way.

Pointer table for primary AI: B98994 (20 entries)
Pointer table for secondary AI: B98908 (35 entries)
Each entry contains a series of AI commands that are 16 bytes each.

Unit data in RAM
Bytes 64-69: [AI3] [AI4] [AI1] [AI1C] [AI2] [AI2C]
AI1C = AI 1 counter. AI2C = AI 2 counter.
These bytes keep track of the next 16-byte AI command to execute.


Primary AI - AI byte 1

Action priority: staff → steal → attack/ballista
If unit has multiple usable staves, use the staff with the highest weapon rank. If multiple staves share the highest rank, use the staff that is lowest in inventory.

0x00 = Action 100%
0x01 = Action 80%, end turn without moving/acting 20%
0x02 = Action 50%, end turn without moving/acting 50%
0x03 = Action without moving 100%
0x04 = Action without moving 80%, end turn without moving/acting 20%
0x05 = Action without moving 50%, end turn without moving/acting 50%
0x06 = Do Nothing
0x07 = Do not attack character 0x14 (Nino) (B979E8)
0x08 = Do not attack character 0x0A (citizen) (B97A58)
0x09 = Do not attack character 0x00 (B970F6)
0x0A = Only attack character 0x03 (tutorial Lyn) (B97AC0)
0x0B = Same as 0x00
0x0C = If in Movement/2, act.
0x0D = CHAI [0x0, 0x0] if the unit's leader has foe in range.
0x0E = Use healing staves on allies with HP <= 50% (% at 1D3B59)
0x0F = Alternate between AI1 0x0E and "action without moving"
0x10 = Open Locks/Steal, then CHAI [0x6, 0xC] (which is escape)
0x11 = Like 0x10, but doesn't change AI. ("No action untill doors opened"?)
0x12 = Do not attack characters 0x10 (Lucius) and 0x1B (Priscilla) (Characters at B97A0C)
0x13 = Do not attack character 0x04 (Raven) (B97A34)

Next 6 entries are just unused(?) pointers to the primary/secondary AI tables.

Status staff:

  1. Sleep/Berserk requires target to have usable weapon. Silence requires target to have usable staff.
  2. Target needs to meet a minimum hit requirement. The staff-user needs 5+ hit with accuracy being calculated from the staff-user’s start-of-phase position. The initial position is used even if the staff-user must move forward to get the target within staff range. Also, the staff-user will retreat a few spaces if a target is close, so a status staff can be used at 0-4 hit as long as the initial 5+ hit test is passed.
  3. The character furthest down in deployment list who meets the above requirements is targeted.

Healing staff:
Normally, staff-users heal allies in “recovery mode” (see section on AI byte 3 for details).
Healers with 0x0E heal allies with HP <= 50% instead using target’s recovery mode status.


Secondary AI - AI byte 2

0x00 = Move towards opponents
0x01 = Move towards opponents, but do not move towards character 0x00 (B970F2)
0x02 = Move towards opponents, but do not move towards character 0x00 (B970F4)
0x03 = Do nothing
0x04 = Loot villages/chests then change AI1 to 0x00 and AI2 to 0x00 (brigand AI)
0x05 = Loot villages/chests then change AI1 to 0x06 and AI2 to 0x0C (thief AI)
0x06 = Wait. Change AI2 to 0x00 if foe is in expanded range (attack range with double move)
0x07 = Wait. Change AI2 to 0x01 if foe is in expanded range (attack range with double move)
0x08
0x09 = Random movement
0x0A = If AI2 ever used, CHAI [0x0, 0x0]
0x0B = Farina talks to Hector (Character 0x02 at B97FE0)
0x0C = Move towards escape points and exit map
0x0D = Move to character 0x28 (Merlinus)
0x0E = Move to character 0x01 (Eliwood)
0x0F = Move to character 0x02 (Hector)
0x10 = Move to character 0x26 (Nils)
0x11 = Move to character 0x7A (Zephiel)
0x12 = Do something(???) once, then change AI1 to 0x10 and AI2 to 0x05 (thief AI)
0x13 = Move to \[03,13] (B98718)
0x14 = Move to \[18,13] (B98794)
0x15 = Move to \[10,24] (B98810)
0x16 = Move to \[08,02] (B9888C)
0x17 = Move to \[06,02] (X: B97579, Y: B9757B)
0x18 Bugs out. Is not even formatted quite right.
0x19 = Move to \[06,09] (X: B974B9, Y: B974BB)
0x1A = Move to \[06,05] (X: B974F9, Y: B974FB)
0x1B = Break walls/snags
0x1C = Move towards opponents as much as possible when path blocked by walls/terrain
0x1D = Move to \[15,17] (B9869C)
0x1E = Move to \[05,02] (X: B97539, Y: B9753B)
0x1F = Wait 1 turn, then change AI2 to 0x04 (brigand AI)
0x20 = Wait 1 turn, then change AI2 to 0x00
0x21 = Move to character 0x01 (Eliwood)
0x22 = Move to character 0x02 (Hector)

Next entry is the start of the of the primary AI table.

0x00 and 0x1C:
Units with 0x00 will not move if walls/terrain block path to enemies. They will begin to move once a path becomes available (like a door being opened). Units with 0x1C will move up against walls/terrain when path to enemies is blocked.

Move to coordinate AI:
After reaching destination unit will attack enemies in range. Then if unit is lured away from target point and has no enemies in range: 0x13, 0x14, 0x15, 0x16, 0x1D will move back to target point. 0x17, 0x19, 0x1A, 0x1E will not move back to target point.

Move to character AI:
Units with 0x0D-0x11 that have an allied target: move towards target until target is inside range then alternate turns seeking enemies and moving back to target. Units with 0x21/0x22 that have an allied target: move towards target until target is inside range then stay near target if not attacking.


Door Keys, Lockpicks, Antitoxins

There’s a chapter array at 1D3A60 that controls the AI’s ability to use Door Keys, Lockpicks, and Antitoxins.
Bit 01 = Enable Door Key in chapter. Bit 02 = Enable Lockpick in chapter. Bit 04 = Enable Antitoxin in chapter.

All FE7 chapters allow the AI to use Lockpicks and Antitoxins.
Chapters that prohibit AI from using Door Keys: 6, 17, 23x, 27 Jerme, 32x.

Door Key, Lockpick(1), Antitoxin conditions:

  1. Primary AI (AI1) fails. Generally, this happens when the unit has no target for attack, steal, staff. AI1 0x06 always fails.
  2. Secondary AI (AI2) fails or AI2 = 0x04 (brigand AI) or AI2 = 0x05 (thief AI). AI2 0x00 fails when the unit has no available path to foes due to walls/terrain. AI2 0x03 always fails.
  3. AI1 cannot be 0x03, 0x04, 0x05 (action without movement) and AI4 cannot be 0x20.
  4. Item cannot be disabled by the chapter array.

Door Key: Move towards doors and open.
Lockpick(1): Move towards doors/chests and open (requires STEAL ability).
Antitoxin: If poisoned, retreat and cure poison.
If unit has multiple of these items that can be used, priority is given to the lowest in inventory.

Then if AI2 = 0x04 (brigand AI) or 0x05 (thief AI) and none of the above items were used:

Chest Key (1-use): Move towards chests and open.
Lockpick(2): Move towards CHESTS (not doors) and open (requires STEAL ability).
Note: the chapter’s item bans do not apply to the Lockpick here.
Usage priority to the top Lockpick/Chest Key in inventory.
The Chest Key with 5-uses cannot be used by the AI.

Note that there are 2 opportunities to use Lockpicks
Lockpick(1): Can open both doors and chests, but can be blocked by the item ban.
Lockpick(2): Requires brigand/thief AI, can only open chests, and is unaffected by the item ban.

So, a thief carrying a Lockpick in a chapter with Lockpicks disabled = a thief that will only open chests and will ignore all doors.

A thief with Door Key + Chest Key will be absolutely determined to use the Door Key before opening any chests. This is due to the Door Key check always occurring before the Chest Key check.

Why did I say Lockpick AI requires the steal ability?
Class/char ability 0x08 (Lockpick): Determines whether to gray out Lockpick in inventory and if the player units can use it. For AI-controlled units, the game mistakenly checks class/char ability 0x04 (steal) for Lockpick functionality. This means enemy/NPC Assassins cannot use Lockpicks by default. (IntSys plz)

Quicker explanation:
If enemies aren’t using Door Keys at all, check the chapter’s item ban list. If you want attack-in-range enemies to hold/drop Door Keys without using them, you will need to disable Door Keys.

Door Keys with common AI:
Enemies with “aggressive AI” (00/00) should never use Door Keys unless they are put in a locked room away from the player units.
Enemies with “attack-in-range AI” (00/03) will actually leave their positions
and move towards doors if Door Keys aren’t disabled for the chapter.
Enemies with “stand still AI” (03/03) should never use Door Keys regardless of the item settings for chapter.
Enemies with “thief AI” (06/05) and carrying a combo of Door Keys + Chest Keys
will seek to use Door Keys before using Chest Keys.

AI Command Reference

First, the AI tries to run AI1. If it doesn’t get a command out of that, labelled as a command failing, it tries AI2. If that also fails, the unit does not move. Note that recovery mode and Door Key usage take place before AI1.

The actual routines performed for each command are in a jump table at 0x81D3678

AI Commands are done in order until 030013B0 is a 1.
The loop automatically sets this to a 1, so it’s the routine’s responsibility to change it to a 0 to continue.
Most actions will end execution. Exceptions are noted.

30013B8 is the command currently being processed.

AI Command Format:

[byte][byte][byte][byte][word][void*][void*] 

That usually take on the roles of:

[opcode][chance(For some commands)][0xFF][label/goto][word of data][void*(routine in 01, data in 00)][data*(parameter to the routine)]

Opcodes:

0x00 - Conditional Branch based on RAM location. Usually 0203A972
    The 0x1 byte tells the mode to operate. The 0x3 byte tells where to GOTO.
    The word(at 0x4) tells what it should compare against
        00 : if word at 0x04 < memory then branch.
        01 : if word at 0x04 <= memory then branch.
        02 : if word at 0x04 == memory then branch.
        03 : if word at 0x04 >= memory then branch.
        04 : if word at 0x04 > memory then branch.
        05 : if word at 0x04 != memory then branch.
0x01 - Execute given routine (Might do an AI, might check a condition?)
    @param: r0 = &Data
    @return: 00 = continue AI execution 01 = execute AI
0x02 - Change AI + Execute (Does not take up the unit's turn, that is)
0x03 - Unconditional GOTO
0x04 - If word2's character is deployed, attack them if they're in range (don't attack anyone else even if they are the only ones in range). If the character is not deployed, attack anyone in range.
0x05 - Attack in range (takes a probability). Fails if it cannot take an action in range.
0x06 - Nop (always fails)
0x07 - Execute action without moving. Fails if it cannot take an action without moving.
0x08 - Does nothing? Not seen in default AI codes?
0x09 - Does nothing? Not seen in default AI codes?
0x0A - Does nothing? Not seen in default AI codes?
0x0B - Does nothing? Not seen in default AI codes?
0x0C - Move towards [Byte 0x1, Byte 0x3]. Takes the action and does not advance the AI-PC until the tile is stepped on.
0x0D - Move towards character in 2nd word if not in range
    Used in conjunction with []#define InitiateTalk(charsPointer) "RoutineAI(0x0803A58D, charsPointer)"]
    Sets memory to: 
    0x2 if target is in range (this itself does no action)
    0x4 if target is not deployed
    0x3 or 0x1 otherwise(?)
    If target is in range, seems to indicate the location of the target for a subsequent InitateTalk?
0x0E - Nop (always fails)
0x0F - Does nothing? Not seen in default AI codes?
0x10 - Loot/destroy villages. Doesn't proceed the AI-PC until the unit is unable to loot/destroy anymore.
0x11 - Run away from opponents?
0x12 - Move towards opponents, but not those in *(3rd word). Ignore when blocked
0x13 - Move towards opponents. Move as close as possible when blocked.
0x14 - Does nothing? Not seen in default AI codes?
0x15 - Does nothing? Not seen in default AI codes?
0x16 - Random Movement
0x17 - Escape
0x18 - Move towards and attack walls. If no more exist, turns into AttackInRange? Fails if no more walls and no enemy in range.
0x19 - Attack in range?
0x1A - Lags a lot, then moves randomly?
0x1B - LABEL

Healing AI + Targeting AI - AI Byte 3

Bits 1-3 of AI byte 3 are used for healing AI. Bits 4-8 of AI byte 3 are used for targeting AI.
Add healing and targeting settings together to form third AI byte (AI3 0x09 = Healing 0x01 + Targeting 0x08)


Healing

When a unit’s HP falls below a certain percentage it enters “recovery mode”
Being in recovery mode has these effects:

  • If a vulnerary or elixir is in inventory, retreat from enemies and use item
  • Unit is healed by allied staff-users (Fortify requires at least 3 units in recovery mode?)
  • Retreat towards an allied healer (only if unit can stay out of enemy range)
  • Retreat to nearest fort (only if unit can stay out of enemy range)

Recovery Mode thresholds:
0x00 = Enter recovery mode when HP is less than 50%. Exit when HP is 100%
0x01 = Enter recovery mode when HP is less than 30%. Exit when HP is 80% or greater
0x02 = Enter recovery mode when HP is less than 10%. Exit when HP is 50% or greater
0x03 = Enter recovery mode when HP is less than 80%. Exit when HP is 100%
0x04 = Unable to enter recovery mode

0x00 thresholds: B9727C (64 32)
0x01 thresholds: B97280 (50 1E)
0x02 thresholds: B97284 (32 0A)
0x03 thresholds: B97288 (64 50)

Byte 0x0A of unit data in RAM:
0x00 = recovery mode OFF
0x01 = recovery mode ON


Targeting

Attack using the weapon-target option that results in the highest TP (target points).

For each weapon in inventory (top to bottom)
{
        For each unit in range (deployment order)
        {
               Determine attack position
               Calculate TP for weapon-target option
               If TP >= highest TP seen so far, set this weapon-target option as highest.
        }
} 

In event of TP tie, inventory order and deployment order are the tiebreakers. Select weapon-target option with lower weapon in inventory then unit further down in deployment list.


TP Modification Table

TP calculations are modified by the value of the third AI byte. The table is located at 1D36F4.
The table has 32 entries. Here are the first six rows:

       00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13
==================================================================
0x00 = 02 01 01 01 01 01 01 01 0A 0F 00 00 05 00 00 00 00 00 00 00 
0x14 = 01 02 02 02 00 00 00 00 0A 0F 00 00 05 00 00 00 00 00 00 00 
0x28 = 01 02 02 02 02 02 01 02 0A 0F 00 00 05 00 00 00 00 00 00 00 
0x3C = 02 02 02 02 00 01 01 01 0A 0F 00 00 05 00 00 00 00 00 00 00 
0x50 = 01 00 00 00 02 00 00 00 0A 0F 00 00 05 00 00 00 00 00 00 00 
0x64 = 02 01 01 01 01 00 00 00 0A 0F 00 00 05 28 00 00 00 00 00 00 

Columns 0x00-0x07 : Used as multipliers to various TP bonuses and penalties. Summary below:

0x0: Damage
0x1: Remaining HP
0x2: Near Allied Units
0x3: Defender's Class (unused in FE7/8)
0x4: Turn Count
0x5: Uncounterable Bonus/Defender's Expected Damage Penalty
0x6: Strongest Weapon of Defender Penalty
0x7: Own Remaining HP Penalty

Columns 0x08-0x11 : TP bonus values based on defender’s class (only used in FE6)
Columns 0x12-0x13 : Unused. Always zero

The majority of FE6/7/8 enemies use either 0x00 or 0x08 for targeting setting.


TP Calculations

Bonuses add TP to TP total. Penalties subtract TP from TP total


[0] Bonus: Lethal damage + attacker’s expected damage (08039010)

If damage to defender will be lethal: TP bonus = 50. Move to step 1

If damage is not lethal:
TP bonus = (Attacker’s battle hit * Attacker’s battle damage)/100
Note: ability to double strike does not affect the above calculation
Multiply bonus by TP_Modifier[0x00]
Cap bonus to 40 TP


[1] Bonus: Defender’s remaining HP (08039070)

TP bonus = 20 - Defender’s remaining HP. Assume all attacks will hit. Set to 0 if result less than 0
Example: If defender has 30 HP and the attack would do 25 damage, then TP bonus = 15
Multiply bonus by TP_Modifier[0x01]


[2] Bonus: Near allied units (08039094)

TP bonus for each allied unit in a 3 space radius around attack position
The attacker will receive a bonus from itself if its pre-attack position is inside the bonus area
1 space away = 3 TP, 2 spaces away = 2 TP, 3 spaces away = 1 TP

      1       
      2 1     
  1 2 3 2 1   
1 2 3   3 2 1 
  1 2 3 2 1   
    1 2 1     
      1 

The gap in the outer layer is due to a bug
Multiply bonus by TP_Modifier[0x02]
Cap bonus to 10 TP


[3] Bonus: Defender’s class (08039138)

Columns 0x08-0x10 in TP Modification Table contain TP bonuses for defender’s class
B970C8: contains 9 pointers to class sets
First pointer → column 0x08, second pointer → column 0x09 … last pointer → column 0x10
If the defender doesn’t belong to any class set, then use the value in column 0x11 (zero).

In FE7/8 the class set pointers all point to 00 (nothing) by default. Class sets only defined in FE6.
The TP bonuses already present in the FE7/8 table are just leftovers from FE6.

TP bonus = value from TP modification table that corresponds to defender’s class
Multiply bonus by TP_Modifier[0x03]
Cap bonus to 20 TP


[4] Bonus: Turn count (0803916C)

TP bonus = current turn number
Multiply bonus by TP_Modifier[0x04]


[5] Bonus/Penalty: Defender can’t counter + defender’s expected damage (08039184)

If defender cannot counterattack
OR if the defender’s weapon has the potential to break (assume all attacks will hit)
TP bonus = 10. Move to step [6]

Otherwise…
TP penalty = (Defender’s battle hit * Defender’s battle damage)/100
Note: ability to double strike does not affect the above calculation
Multiply penalty by TP_Modifier[0x05]
Cap penalty to 40 TP


[6] Penalty: Range and attack power of opponents (080391E4)

TP penalty for attack position being inside range of opponents’ strongest weapons
TP penalty from each foe = (attack power with strongest weapon)/2
Add up penalties from each foe then divide total by 8

Example:
Lyn with 8 strength and 5 mt iron sword: (8 + 5)/2 = 6
Sain with 13 strength, 6 mt javelin, and 10 mt steel lance: (13 + 10)/2 = 11
If attack position is only in Lyn’s range: TP penalty = 6/8 = 0
If attack position is only in Sain’s range (steel lance): TP penalty = 11/8 = 1
If attack position is in both Lyn’s and Sain’s range: TP penalty = (6 + 11)/8 = 2

Multiply penalty by TP_Modifier[0x06]
Cap penalty to 20 TP


[7] Penalty: Attacker’s remaining HP (0803921C)

TP penalty = 20 - Attacker’s remaining HP. Assume all attacks will hit. Set to 0 if result less than 0
Multiply penalty by TP_Modifier[0x07]


Final TP Adjustment (08039288)

if (TP total <= 0) then TP total = result from step [0]
else TP total = TP total x 40

TP from each weapon-target option gets compared at 080386F0


AI Byte 4

AI4 = 0x20:
Unit in recovery mode will not retreat before using a vulnerary/elixir or retreat to a healer/fort
Seems to apply the “action without moving” effect to unit’s AI

6 Likes

[Edit: I think this is where the game is writing suspend data for units.][1]
[1]: https://dl.dropboxusercontent.com/u/92273434/FE%20Hacking%2C%20Public%20Files/AI%20Analysis/Write%20AI%20Processing%20Data.txt
@r0 = &unit being processed
@r1 = &the structure (the struct is 0x34 bytes, each unit uses one slot, starts at 0x2020140)

Yeah, there seems to be some kind of structure in the RAM at 0x2020140, with each slot being 0x34 bytes. The first byte is the character number of the unit being processed. I’m investigating what the rest are.
(It’s written to at 080A13EC)

This code is run at the start of the enemy phase and before each unit’s move…

EDIT: Wait, no, I think this routine is writing the data the game uses to suspend, as it writes after each battle, phase change, etc… still an interesting find though…

0x203A85C is showing up a lot… I think it’s some kind of buffer(like the battle stats data) that tells the game how to move its units?

Edit: To avoid quadruple posting…
Break-on-read points for the enemy boss’s AI bytes.

(7:20:24 PM) Crazycolorz5: 080375C8
(7:20:39 PM) Crazycolorz5: Also, 080378C4…
(7:21:01 PM) Crazycolorz5: 080375C8, twice?

1 Like

you can create an fe10-style battle save function by screwing with that suspend data routine (alternatively just edit the caller)

FEGirls did this; there was an option to turn off automatic suspend state saving

Yeah! I thought about doing that. This is just a location in ram though, and it doesn’t write to save or anything… I might mess around with it some other time.

I did some experimenting with FE7 and learned some stuff about second AI byte 0x06 and 0x1C.

Second AI byte = 0x06
Begin to move when an unit enters activation area (attack range calculated with move x 2)
Example: a stationary knight will move when a unit waits in a space that could be attacked if the knight had 8 move instead of 4. Also, unlike 0x03, the enemy will continue moving even if units move out of range.

Second AI byte = 0x1C
In FE7 this AI is assigned to enemies (usually archers/mages) who are isolated from the player units due to walls and closed doors. If the enemy has no units in range, move towards the nearest player unit as much as possible.

In small enclosed areas that can be covered entirely by an enemy’s move, this AI is kind of pointless. But this can have use in larger enclosed areas. It allows the enemy to “follow” player units rather than have range locked to starting/current position.

Found by looking at 080375B8

Look at 0x8B989F0. You’ll see a pointer to 0x08B98994. There, it’s a table of… pointers… I assume, to functions(Haven’t investigated that yet; will update when I figure it out)?

Anyway, based on whatever the 1st AI Byte is, it load that-th pointer in the table.

Also, this is just a hunch, but
30013B8 – 4 bytes for attack/action/or movement AI, 4 bytes of 00, 4 bytes of something else(Attack/Action?) AI… Again, just a hunch, haven’t investigated it yet.

So the AI bytes are a jump table, huh? That’s interesting.

An address in IRAM like that is most likely SP-based and won’t be constant (i’d love to be proven wrong, though)

Oh, nice! I have been wanting something like this since forever.

So if I’m understanding this correctly, that means that a 0x06 for an AI byte would correspond to the 0x06th pointer in that table…?

Yes. But it’s more complicated from that. Based on the code, it seems that each entry in that table(specified by AI 1) is a pointer to a table… with a real routine specified by AI 2. I think.
In other words

byte** AITable = new byte*[# of AI1]
AITable[0] = new byte[# of 1stAIData]
byte AIToExecute = AITable[AI1][AI2]

I think.

It’s not a jump table actually… the table points to 1-byte entries in-rom… it’s weird.

Found the jump table:
0x81D3678

Basically, the AI Bytes map to a table that, after some processing, tell which jump table element to use.

After I uncover the arguments and returns for these routines, the bulk of our research will be figuring out which jump table elements do what, I guess.

  • They all take &(1stAIData) as their parameter.
  • r0 = &1stAIData
  • r1 = location of branch
  • r2 = &AI’s Data
  • They all have no return value.

Calling it a day today, but before I go, dropping some links to annotated routines

(Edit: I removed and broke the links. Check later in the post for a rar of all deocs)

(@CT075, I think you should take a look at these.)

oh, that’s a find

not entirely sure what you want me to do other than go debug the actual table entries (ha)

does anyone have that old post where icecube made ai check its might properly

Moved to first post

6 Likes

Yeah! This helps a lot. I haven’t seen any unified-all-information-here about the AI bytes like this before.

@Arch can you make that post a wiki post pretty please?

Bada bam. Great stuff here from everybody!

I’ll be cheerleading from the sidelines.

Just quickly, if you follow the pointer trails based on the table that @Crazycolorz5 found, you’ll find the addresses that specify which characters these will be.

0x8B97A0C - character corresponding to 0x12 (Lucius/0x10)
0x8B97A34 - character corresponding to 0x13 (Raven/0x04)

Edit: Just wanted to confirm that changing these bytes does indeed work.

I imagine that there’s similar sorts of things for other AI bytes–might be worth checking out later.

1 Like

Uhm, I myself am confused as to how you got those offsets. Care to explain in a bit more detail?