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:
- Sleep/Berserk requires target to have usable weapon. Silence requires target to have usable staff.
- 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.
- 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:
- Primary AI (AI1) fails. Generally, this happens when the unit has no target for attack, steal, staff. AI1 0x06 always fails.
- 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.
- AI1 cannot be 0x03, 0x04, 0x05 (action without movement) and AI4 cannot be 0x20.
- 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.
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