[FE8] Proper enemy AI for maps with a defense point?

I thought I had this figured out, but it turns out that it didn’t work completely the way I wanted to.

I have a chapter where one of the objectives is to prevent an enemy from taking the gate. In order for this to be free of dumb exploits, the enemy AI should move in the general direction of the gate, move onto the gate if the path is open, and otherwise attacks units if they can’t reach the gate.

My initial thought was to set an enemy escape point to the coordinate of the gate and then to set enemies to have AI byte 1 = 0x0 and AI byte 2 = 0xC. Here’s what happens:

  • If path to gate is open and no targets in range, enemy moves towards gate. This is good.
  • If enemy can reach gate, enemy takes gate. This is good.
  • If path to gate is open and targets are in range, enemy attacks target. This is okay.
  • If path to gate is blocked and no targets in range, enemy does nothing. This is bad.
  • If path to gate is blocked and targets are in range, enemy attacks target. This is good.

So the problem right now is that if you block the path to the gate and stay out of enemy range, then the enemy does nothing, thus rendering the defense sub-objective pointless. How do I solve this problem in a non-exploitable way? I read the FE7 AI documentation and saw that there are some AI2 bytes that force enemies to prioritize a pre-determined coordinate. Does FE8 have this as well?

EDIT: AI2 0x10 sets enemies to target a coordinate specified at 0x5A90C0. However, this doesn’t solve my problem. With AI2 0x10, the enemy will continue to match towards the coordinate if the coordinate is blocked, which takes care of the issue specified above, but this creates new issues.

If AI1 is 0x0, the enemy fails to take the goal if it has a target in range, and if AI1 is 0x6, the enemy will steadfastedly go for the goal but never attack. Neither solution is satisfactory. AI1 0x3 is interesting - the enemy will usually go for the goal without attacking, but it will prioritize attacking over goal-taking if you leave a unit within its immediate attack range (i.e., as long as it doesn’t have to move).

Yeah, making better seize AI has been something on my to-do list.
You already figured out why the current AI1 options don’t work well with “move towards target point” AI2. Using escape points is also problematic and couldn’t be used in something like FE7 ch15/31 where you want both “seize throne” enemies and “loot-n-escape” thieves.

I’m going to have to make a custom AI1. Something like:
if (target point within movement range) {move to target point}
else {try to attack}

Then AI2 = move towards target point

1 Like

How soon do you think you can get this working? We were discussing this in the Discord last night and if left with no other recourse, I’ll probably just go for a workaround.

My current thought is to load goal-seeking units with AI [0x0, 0x10] such that they will at least head towards the goal if undistracted. This is not perfect because they can be kited, but it’s the best option. I will then set up an AREA event that covers a region within a certain radius of the goal. The AREA event will check for enemy phase, and then if enemy phase, when activated, the event checks for the activating unit’s coordinates using GET_COORDS, shifts the stored value from 0xC to 0xB, and then CHAI2s the unit’s AI to [0x0, 0xC].

Therefore, enemies will have acceptable goal-seeking behavior outside of goal range and desired goal-seeking behavior inside goal range. If enemy is inside goal range, he must be able to either seize the goal or attack someone (because there must be a unit blocking the goal), so enemy will never stand still.

It’s still possible for an enemy to change AI to [0x0, 0xC] and then be kited out of goal range, at which point he will be susceptible to standing still if the player runs away quickly. This is convoluted and unlikely to happen, but I can possibly circumvent this by setting up an AREA event of even larger radius and CHAI2’ing enemies to [0x0, 0x10] within that radius. This way, enemies within goal-range will always have [0x0, 0xC], while enemies 2 turns removed from goal will always have [0x0, 0x10].

FE8 only seems to have AI byte 2 0x10 for specific point targeting, which is a problem because the coordinates are hardcoded into the ROM. I’m looking for a way to set a coordinate via script and have the AI use the newly set coordinates instead.

EDIT: for the last paragraph, changing the pointer at 0x5A90F4 to an offset in RAM corresponding to a register and then setting the register via SVAL to 0x0000YYXX will allow AI2 0x10 to read a dynamic coordinate instead of a static one. I don’t know how regularly registers besides r1-r3 and r11-r13 are used, but currently I have it pointing to r10 and the game seems to be sending enemies to the right place.

I got the AI working in FE7 (with only 2 game crashes!)
On chapter 15, the enemy chose to take the throne instead of attacking 1 HP Serra.
Now I need to find the relevant FE8 RAM offsets and routine addresses so I can port it.

2 Likes

This AI1 is intended to be used with an area event that triggers a game over.
If seize point within movement range, move to seize point
Else, try to attack

FE7 seize AI dump
30 B5 83 B0 05 1C 0F 48 00 68 0F 4C 00 F0 22 F8 28 78 69 78 8B 00 0D 4A 12 68 D2 18 12 68 12 18 12 78 78 2A 09 DC 00 22 00 92 01 92 02 92 00 23 07 4C 00 F0 0F F8 01 20 00 E0 00 20 03 B0 30 BC 02 BC 08 47 90 46 00 03 A1 9B 01 08 E4 E3 02 02 51 4E 03 08 20 47 C0 46

FE8 seize AI dump
30 B5 83 B0 05 1C 0F 48 00 68 0F 4C 00 F0 22 F8 28 78 69 78 8B 00 0D 4A 12 68 D2 18 12 68 12 18 12 78 78 2A 09 DC 00 22 00 92 01 92 02 92 00 23 07 4C 00 F0 0F F8 01 20 00 E0 00 20 03 B0 30 BC 02 BC 08 47 50 4E 00 03 8D A3 01 08 E0 E4 02 02 21 9C 03 08 20 47 C0 46

AI instructions:

01 00 FF 00 00 00 00 00 RR RR RR 08 PP PP PP 08
05 64 FF 00 00 00 00 00 00 00 00 00 00 00 00 00
03 00 FF 00 00 00 00 00 00 00 00 00 00 00 00 00

First pointer = where you wrote the routine PLUS ONE (I always forget this part)
Second pointer = seize coordinate, format: X Y

  1. Paste the seize AI dump somewhere in freespace.
  2. Set the routine and parameter pointers in the above AI instructions.
  3. Write the AI instructions and set the AI1 pointer. FE8 has some unused zombie slots from FE7, so we can use one of those to make this easier. Let’s go with AI1 0x08. For AI1 0x08, write the AI instructions to 5A8B40.
  4. In enemy data, set AI1 and use a “move towards seize point” AI2.
3 Likes

After implementation and quick testing, I’m reasonably confident that this satisfies all of my criteria. I took the liberty of doing what I did with AI2 0x10 above and set the seize coordinate pointer to RAM, thereby letting me set a different seize coordinate depending on the chapter. So currently a unit with AI [0x8, 0x10] will go to the coordinate that I set at r10.

Thank you!

Also tagging @Onmi because he might be interested in this AI for his project.

Is that “If able to seize the location, move onto it, otherwise do nothing (and thus the next AI command would take effect)?”

Also I might write an AI Assembly installer for this sometime.

Esp since it’s great for automatic pointers.

Yeah.
if coordinate within movement range: write move command and return 0x01 (stop-n-execute)
else, return 0 (continue to next command)

I took shortcuts by going with the assumption that a game over would be immediately triggered by moving onto seize point, so I didn’t care about the enemy’s AI state afterwards. It’s not really intended for other situations.

The “proper” way to make this more general would have a condition checker then branch to the different possible actions. Something like:

01: routine that checks if unit on seize point or seize point within movement range
00: branch to label 1 if on seize point, branch to label 2 if can move to seize point
05: attack (can’t get to seize point)
03: jump to top
1B: label 1 (on seize point)
07: attack without moving
03: jump to top
1B: label 2 (can reach seize point)
01: routine that moves unit onto seize point
03: jump to top

Or alternatively, make a new opcode that does all the above.

Sorry for necro, but if anyone knows, could I ask what this second pointer contains exactly? I’ve tried multiple things trying to figure out what this format is (e.g. BYTE X Y, pointing to the AI2) but haven’t gotten this to work. (FE8 EA btw lol)

EDIT: Solved on my own! I had a chapter with a wide area where the game over could be, and set the coordinate to be too far away. Just for anyone wanting to know, for EA your AI should look something like this:

warning: event assembler ahead
ALIGN 16
SeizeAIDump:
BYTE 0x30 0xB5 0x83 0xB0 0x05 0x1C 0x0F 0x48 0x00 0x68 0x0F 0x4C 0x00 0xF0 0x22 0xF8
BYTE 0x28 0x78 0x69 0x78 0x8B 0x00 0x0D 0x4A 0x12 0x68 0xD2 0x18 0x12 0x68 0x12 0x18
BYTE 0x12 0x78 0x78 0x2A 0x09 0xDC 0x00 0x22 0x00 0x92 0x01 0x92 0x02 0x92 0x00 0x23
BYTE 0x07 0x4C 0x00 0xF0 0x0F 0xF8 0x01 0x20 0x00 0xE0 0x00 0x20 0x03 0xB0 0x30 0xBC
BYTE 0x02 0xBC 0x08 0x47 0x50 0x4E 0x00 0x03 0x8D 0xA3 0x01 0x08 0xE0 0xE4 0x02 0x02
BYTE 0x21 0x9C 0x03 0x08 0x20 0x47 0xC0 0x46

ALIGN 16
SeizeAI1:
BYTE 0x01 0x00 0xFF 0x00 0x00 0x00 0x00 0x00 
POIN SeizeAIDump+1
POIN SeizePoint
BYTE 0x05 100 0xFF 0
WORD 0 0 0
BYTE 0x03 0 0xFF 0
WORD 0 0 0

ALIGN 4
SeizePoint:
BYTE X Y 0 0

ALIGN 16
SeizeAI2:
BYTE 0xC X 0xFF Y
WORD 0 0 0
BYTE 0x03 0 0xFF 0
WORD 0 0 0

The best way to actually get the AI to work is to include Stan’s AI Scr Definitions to inject the AI into the table, found here under AIPerformExtension! You could also use it to make the code look prettier :OOO
[FE8U] Stan's ASM Stuff

1 Like