[FF7] Enemy AI main script template

  • Thread starter Thread starter ff7rules
  • Start date Start date
Status
Not open for further replies.
I'm not sure if this is right, but I think I've written a template to insert at the opening of a script, that activates whenever the creature is in a certain status.

Here it is, in this case checking for Barrier.

02 2060
01 4010 <- This is Barrier
80
60 00 <- I'm having the enemy respond to *not* having Barrier, so this is 0. If you want an enemy to cure itself, choose 1 instead
40
70 [points to the start of the normal script, usually 0019]
12 2070
02 2060 <- Target self
90
60 20
60 X <- Attack / spell index
92
72 [point to position of the END opcode]
{Normal script starts here...}

It seems to work, although I haven't thoroughly tested it yet.
This is the pre battle script you are trying to do yeah? looks right apart from i always thought the pointer was to020 not 019 but im proberbly wrong.
 
No. This is to be inserted at the start of a Main script.

It means that an enemy will, as its first priority, buff / heal itself when necessary (at least that's the theory).

I'm pretty sure the pointer is 19, though I've been wrong plenty of times before. At any rate, I'd recommend checking before using it.
 
Edit: See the message below for a better script

[size=8pt]Simple Alternating Script

This AI script simply has an enemy swap between two attacks. This won't be as useful as a normal 'random' script, but may be appropriate in specific circumstances.

You need to use the following Pre-Battle code:

10 0020
60 00
90
73

In formations where LocalVar20 (0020) is otherwise being used, use an address that isn't being taken (find the highest address of any LocalVar used in the battle, and add 20). Obviously, if several enemies will use this script, you'll have to find another address to replace 0020.

Assuming you're using 0020 as the LocalVar for this, use the following for the Main / Counter / Ally Death script:

00 0020
60 0
71 001E
12 2070
02 20A0
82
90
60 20
61 XXXX
92
10 0020
60 01
90
72 0032
10 0020
60 00
90
12 2070
02 20A0
82
90
60 20
61 YYYY
92
73

This is for targeting. Change it if you don't want to target a random single enemy

This is where you insert the indexes of the attacks you want to use.

This disassembles to:

If (LocalVar:0020 == 0)
{
   TargetMask <- RandomBit(AllOpponentMask)
   Perform("whatever"[xxxx], EnemyAttack)
   LocalVar:0020 <- 1

}

Else
{
   LocalVar:0020 <- 0
   TargetMask <- RandomBit(AllOpponentMask)
   Perform("etc"[yyyy], EnemyAttack)
}
SCRIPT END
[/size]

'Whatever' is performed first; then 'etc', then 'whatever', then 'etc'...

Tested and (seems) to work.
 
Last edited:
Bosola: Good script and nice concept, but let me see if I can "upgrade it". You forgot to pop the LocalVar(0020) at the end of your script, but it's likely not going to cause a problem on "easy" monsters that only take a few rounds to defeat. I don't know the size of the stack, but I can't say whether it gets cleared completely after the enemies turn or not. Best err on the side of caution, ne? ;)

Here's my version:

11   0020
01   0020
60   00
71   0015
12   0040
61   0100
90
72   001C
12   0040
61   0101
90
12   2070
02   20A0
82
90
60   20
02   0040
92
60   01
30
60   01
34
90
73

translates to:
Code: [Select]
Code:
If (LocalVar:0020 == 0){ LocalVar:0040 <- 256   *attack index of attack 1}Else{ LocalVar:0040 <- 257   *attack index of attack 2}TargetMask <- RandomBit(AllOpponentMask)Perform(LocalVar:0040, EnemyAttack)LocalVar:0020 <- LocalVar:0020 + 1 MOD 1SCRIPT END
More tightly weaved, but same function and it's two bytes shorter. This also makes it easier to add additional attacks:

60   01
71   AAAA <- address of next test of LocalVar(0020)
12   0040
61   XXXX <- next attack
90
72   BBBB <- address of target assignment

disassembles:

Code: [Select]
Code:
ElseIf (LocalVar:0020 == 1){ LocalVar:0040 <- 258}
Downsides? This assumes all attacks will be single-target attacks. Easily fixed, but it becomes a little larger:

12   2070
02   20A0
82
90
11   0020
01   0020
60   00
71   001D
12   0040
61   0100
90
72   0049
60   01
71   002C
12   0040
61   0102
90
72   0049
12   2070
02   20A0
90
60   02
71   0042
12   0040
61   0101
90
72   0049
12   0040
61   0103
90
60   20
02   0040
92
60   01
30
60   01
34
90
73

Disassembles into:

Code: [Select]
Code:
TargetMask <- RandomBit(AllOpponentMask)If (LocalVar:0020 == 0){ LocalVar:0040 <- 256   *attack index of attack 1 that targets one}ElseIf (LocalVar:0020 == 1){ LocalVar:0040 <- 258   *attack index of attack 2 that targets one}Else{ TargetMask <- AllOpponentMask If (LocalVar:0020 == 2) {  LocalVar:0040 <- 257   *attack index of attack 1 that targets all enemies } Else {  LocalVar:0040 <- 259   *attack index of attack 2 that targets all enemies }}Perform(LocalVar:0040, EnemyAttack)LocalVar:0020 <- LocalVar:0020 + 1 MOD 4   *value between 0-3SCRIPT END
This is more of a cycle than an alternate, but it has very basic conditions.

I'm not sure about the PSX version, but all "varibles" in the PC version are initialized at 0. That means that the Pre-battle assigning of LocalVar(0020) <- 00 isn't necessary on the PC version.
 
yeah, i had stopped doing that after some time as well, after having seen a few AI sections, since some enemies do not initialize the variable at all and read it in later to see if it is 0 (for first attack). I think this is the same for the psx version as well, since i had originally used some of my AI changes on it and everything worked fine, but i cannot say for sure right now :-P. i like these layouts, they are all nice for a quick and easy setup for an enemy, but i still like to do all of mine manually (and happen to have a thing for row checking :wink:).
 
Here's another template. It looks for targets without or with a particular status, then chooses to buff or debuff them. Let's say that our monster wants to haste everyone on its team that it can.

02 2080 [friends - 20A0 for opponents]
00 4008 [haste]
80 [creates a list of friends and their hasted / un-hasted statuses]
60 0 [haste not active]
40 [compare with value '0' - returns a 1 if there are un-hasted friends]
02 2060
02 4140
80 [pushes own MP]
61 [index of spell to use - likely haste]
86
42 [sees if the spell costs less MP than available]
50 [IF both are TRUE...]
70 yy [don't jump to yy (the ELSE action)]
12 2070
02 2080 [friends]
00 4008 [haste]
80
60 0 [haste not active]
40
82
90
60 20 [use if an enemy specific attack; use 01 if player-usable magic]
61 XX [index of attack]
92
72 LLLL  [last line of script]
[This next line is YY, would be 32 if nothing preceded the script]
Etc. for else...


This would disassemble to:

If ( ( (AllAllyMask.Status:Haste == 0)  And  (Self.MP >= MPCost(of attack)) ) )
{
   TargetMask <- RandomBit( (AllAllyMask.Status:Haste == 0) )
   Perform("Whatever"[XXXX], EnemyAttack)

}

Else...

================
For direct copy-pasting...
================

02 2080
00 4008
80
60 0
40
02 2060
02 4140
80
61 19
86
42
50
70 32
12 2070
02 2080
00 4008
80
60 0
40
82
90
60 01
61 19
92
72 LLLL
[This next line is YY, would be 32 if nothing preceded the script]
 
Last edited:
So, in the above example, you would have a monster looking at its friends. If friends don't all have haste, then it randomly selects one and casts some haste-conferring move on it (could be the spell, or some other buffing move). You could make a small set of changes to have it look at the party, choose members who don't have poison and cast bio on them.

Of course, this would mean that should the party have ribbons, the enemy will spend the entire battle bio-ing fruitlessly. There are ways around this, such as including a counter, so that the monster doesn't do it too many times, and eventually gives up.
 
QUADRA-POST

I've been looking into how H0512 and H0512-opt 'work', hoping for code that can tell me how to make one creature support a specific peer, but not itself.

Here's how it does it:

1. In H0512's Pre-Battle script, there is a set of opcodes:

12 0020
02 2050
02 4120
80
60 41
40
90
73

This pushes the address of localvar20 to the stack. Then, it uses a mask to create a list of players who are both active, and have enemy ID (address 4120) $41 (65 - that's H0512-opt's ID), and stores it at 0020.

2. Now, one way it can interact with its peers is to directly change the statuses of them in a script. One example appears in the Death script, where H0512-opt is made un-targetable:

12 0020
10 4023
80
60 00
90

This uses the mask, taking the list of active players with ID 65, then specifying the target-able flag. Basically, you use 12 0020 as you would use 12 2060 if you wanted to change the stats of the script owner, or 12 20A0 if you wanted to change the stats of all opponents.

3. What if you want to, say, target a peer with a particular ID? Well, you can probably guess from above that you might substitute 02 0020 for a 'all opponent mask', 'all allies mask' etc. etc. You're right. H0512's code for targeting H0512-opt is:

12 2070
02 0020
90

You can also mask the list provided by 0020. H0512 masks the list, targetting Opt-units which are dead:

12 2070
02 0020
00 4000
80
60 01
40
90

Address 4000 is 'dead'. A list of Opt-units and their death-statuses is created, then the opcode 40 compares the value of each entry with the 'True' condition. You could add an 82 before the 'store' opcode (90) of that if you wanted to pick a random dead H0512-opt unit.

You could easily adapt this code to have a monster buff another monster in certain conditions. I think, though, that you'd have to change the way that LocalVar0020 is set, instead setting it at the start of the script and having it choose from *alive* units, or else I think you might get your monster trying to cast haste on something that's dead. Of course, you could just set a condition saying that the ability is only used when monster X is both unbuffed and alive, and only target those living, but I think changing the way LocalVar0020 is set would be easier.

Also, I was wondering if one of our veterans could tell me: what if I wanted to use multiple conditions in targeting? What if I wanted to choose targets who were both asleep and slowed?

Edit: Found it. I looked at how Helletic Hojo locates targets for Pile Banger:

12 2070 [pushes address of 'target mask']
02 20A0 [all opps]
00 4006 [confu]
80 [mask - all opponents and their confu statuses]
60 00
40 [compare - creates list of opponents without confu]
90 [store this to target mask]
12 2070
02 2070 [loads the list of enemies from the target mask]
00 4002 [sleep]
80
60 00
40
90 [so, takes the list from the target mask, then sees the sleep status of these targets, and returns to the target mask only party members in neither confu nor sleep]
12 2070
02 2070 [again]
82
90 [and a random target from that list]

Ta-da!
 
Last edited:
Edit: This is a chunky and unnecessarily complicated script. I've no idea what I was trying to do when designing it, when I could just have a healer's main script look for allies with a certain ID whose HP was less than their MaxHP. I must have been trying to achieve something else, for some reason or other, but I'm not sure what.

I've been posting consecutively; I hope no-one thinks of this as spamming. However, there has been a fair bit of interest in these topics, so I'm going to post another template, one that I've disassembled but not yet tried out.

It's similar to the ideas above, where one creature heals its friends. In this case, though, we're looking to restore HP rather than remove debuffs.

So, the monster that's going to be healed needs the counter-script:

12 0020

02 2060
03 4160
80
02 2060
03 4180
80
41

90

[This stores a non-zero at LocalVar0020 if MaxHP != CurrentHP]

It also needs a death-script that clears the value of 0020, so that our 'healer' won't target dead allies

12 0020
60 0
90


The friend needs to be able to read this state, then act accordingly;

02 0020
70 [address of the creature's other move]
12 2070
02 2080
02 4120
80
60 [ID of peer]
40
90
60 20
60 [atk number]
92
12 0020
60 0
90
72 [send to end of script]

This 'main' template can be used as a part of a larger AI structure. It could be followed by a simple attack instruction (choose random enemy, hit with a stick) or it could be part of something far more complicated, like

Create random number
If odd,
 - see if an enemy needs help, use atk1 if so, or go to next section if not
If even,
 - use atk2
End script.

I'm going to test this out with MPs and guard hounds, seeing if I can get the MPs to heal their dogs half the time when injured.
 
Last edited:
Status
Not open for further replies.
Back
Top