[FF7] Enemy AI main script template

  • Thread starter Thread starter ff7rules
  • Start date Start date
Status
Not open for further replies.
F

ff7rules

Guest
After a long time away i have decided to post this its helped speed my project up so much that its just a crime to keep to myself anymore.

Delete the old Main part of the script and replace it with this. Yes i know its alot of work to put it all in by hand but its gonna be worth it


12 2070
02 20a0
82
91
81
60 02
34
52
70 0019
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 0024
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 0032
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 004e
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 005c
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 006a
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 0078
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 0086
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 0094
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 00a2
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 00b0
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 00be
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 00cc
60 20
61 XXXX any attack you want
92
81
60 05
70 00da
60 20
61 XXXX any attack you want
92
81
60 05
34
52
70 00e8
60 20
61 XXXX any attack you want
92
73

Ok here is the template which will do whatever attacks you choose at random. How do you add attacks youre asking well heres how.

1. First find an attack you want in PRC and copy it
2. Paste it into the scene you want to edit the AI for.
3. Look at the Attack ID for the attack thats what the XXXX is going to be in the script template.
4. Once you have inserted all the attacks and put them into the script you need to give them an animation
5. To do this go to the scene you want an click Animations/formations.
6. Find your enemy you have edited the AI for.
7. Look at the attacks it has and use whatever animation you think is best for that attack they can be all the same if you want.
8. Don't forget to save your scene!
9. Test it! if you have done everything right itll be like a whole new battle.
 
Last edited:
hm, this seems to work pretty well, it makes the enemy's attacks almost unpredictable and still provides a interesting array of attacks depending on your own personal tastes of how 'complicated' a enemy should be with the number of attacks.
 
hm, this seems to work pretty well, it makes the enemy's attacks almost unpredictable and still provides a interesting array of attacks depending on your own personal tastes of how 'complicated' a enemy should be with the number of attacks.
That was the aim, you can also repeat attacks so you have more chance of that one attack.
 
This is really helpful. This means plenty more enemy modding should be possible for those of us a bit intimidated by the prospect of learning assembler!
 
Also, if space were short, could I cut down the length of the script? As I understand it, the '70' opcodes are a sort of 'if popped value isn't true, go to...' command. Presumably I'd have to make sure that the 70 XXXX never pointed at a part of the script that didn't exist, am I correct? Would that be enough if I wanted to shorten the script?

I assume that I'd have to end on

61 [attack]
92
73

To finish it, right?

Or would that leave a possibility of the enemy not attacking? Could I use

92
81
60 05
34
52
72 00YY [just the next line]
60 20
61 XXXX any attack you want
92
73

Would that work? I imagine that I could delete the 72 line altogether, but that might make the 70 XXXX lines earlier on point to the wrong places in the script.
 
Last edited:
Now that I'm actually looking at your script I have issues with it.

1:
12 2070
02 20a0
82
91
This translates into:
Code: [Select]
Code:
POP(RandomBit(AllOpponentMask))
while the TargetMask address is still loaded. Should be:

Code: [Select]
Code:
12 207002 20a08290TargetMask <- RandomBit(AllOpponentMask)
Otherwise target could be anything. I've never tested what would happen in this case. Likely a copy error, I'll forgive it. ;)

2:
Your blocks of:
81
60 02
34
52
70 0019
60 20
61 XXXX any attack you want
92
are too long.
Try this:
Code: [Select]
Code:
8160 XX (however many attacks said enemy has34
at the beginning of the script then multiple:
Code: [Select]
Code:
60 WW <- sequential71 XXXX <- next block like this60 2061 YYYY <- attack9272 ZZZZ <- To End of Script
blocks followed by:
Code: [Select]
Code:
91 <- to pop the (Random MOD 16) value73
3:
Your jumps are WAY off. There are whole blocks that don't get used at all. Your script goes to D8, but you have two jumps beyond that. This is likely just your copying of them, so I'll excuse it.

4:
The way you have it now, an enemy could perform any number of their attacks in one turn. If that's your intention then fine. It's creating a new random number each time 81 is called. A better practice is to put a random number into a variable so it keeps its value:
Code: [Select]
Code:
12 00008190LocalVar:0000 <- Random
As yours is:
If (Not Random MOD 2)
{
}
If (Not Random MOD 5)
{
}
If (Not Random MOD 5)
{
}
...
Each one of those randoms is a different value. Again, if that's the effect you're going for then great. However this means that all 15 of your attacks can be called in one round. Since they're NOT-ed that raises the chance of EACH attack to be used to 80%. There's a 2.2% chance that said enemy will use ALL their attacks in one round without giving the player a chance to retaliate. I wouldn't really call that good practice, but it's different.

Something else to keep in mind:
COUNTER ATTACKS DON'T OCCUR UNTIL THE SCRIPT CURRENTLY EXECUTING IS COMPLETED.
I'm not yelling at you, Just trying to impress it upon people.

Let me propose this script to you all:


12   2070
02   20A0
82   
90   
81   
60   10
34   
60   00
71   001A
60   20
61   0100   *
92   
72   00E4
60   01
71   0028
60   20
61   0101   *
92   
72   00E4
60   02
71   0036
60   20
61   0102   *
92   
72   00E4
60   03
71   0044
60   20
61   0103   *
92   
72   00E4
60   04
71   0052
60   20
61   0104   *
92   
72   00E4
60   05
71   0060
60   20
61   0105   *
92   
72   00E4
60   06
71   006E
60   20
61   0106   *
92   
72   00E4
60   07
71   007C
60   20
61   0107   *
92   
72   00E4
60   08
71   008A
60   20
61   0108   *
92   
72   00E4
60   09
71   0098
60   20
61   0109   *
92   
72   00E4
60   0A
71   00A6
60   20
61   010A   *
92   
72   00E4
60   0B
71   00B4
60   20
61   010B   *
92   
72   00E4
60   0C
71   00C2
60   20
61   010C   *
92   
72   00E4
60   0D
71   00D0
60   20
61   010D   *
92   
72   00E4
60   0E
71   00DE
60   20
61   010E   *
92   
72   00E4
60   20
61   010F   *
92
91
73

That will target one random enemy with any of 16 different attacks with index 0100 - 010F (marked by '*'). Also, using the 71 jump won't pop the (Random MOD 16) value so you can compare it multiple times. It also jumps to the end after one attack is performed. It's slightly larger in size, but fewer lines. This disassembles into:

Code: [Select]
Code:
TargetMask <- RandomBit(AllOpponentMask)If (Random MOD 16 == 0){ Perform([0100], EnemyAttack)}ElseIf (Random MOD 16 == 1){ Perform([0101], EnemyAttack)}ElseIf (Random MOD 16 == 2){ Perform([0102], EnemyAttack)}ElseIf (Random MOD 16 == 3){ Perform([0103], EnemyAttack)}ElseIf (Random MOD 16 == 4){ Perform([0104], EnemyAttack)}ElseIf (Random MOD 16 == 5){ Perform([0105], EnemyAttack)}ElseIf (Random MOD 16 == 6){ Perform([0106], EnemyAttack)}ElseIf (Random MOD 16 == 7){ Perform([0107], EnemyAttack)}ElseIf (Random MOD 16 == 8){ Perform([0108], EnemyAttack)}ElseIf (Random MOD 16 == 9){ Perform([0109], EnemyAttack)}ElseIf (Random MOD 16 == 10){ Perform([010A], EnemyAttack)}ElseIf (Random MOD 16 == 11){ Perform([010B], EnemyAttack)}ElseIf (Random MOD 16 == 12){ Perform([010C], EnemyAttack)}ElseIf (Random MOD 16 == 13){ Perform([010D], EnemyAttack)}ElseIf (Random MOD 16 == 14){ Perform([010E], EnemyAttack)}Else{ Perform([010F], EnemyAttack)}POP(Random MOD 16)SCRIPT END
More traditional and has no errors. ;) If you want fewer attacks, knock it out in chunks of
Code: [Select]
Code:
60   0071   001A60   2061   0100   *92   72   00E4
.
Changing probabilities is fun too, but that's more complicated.

Sorry this post and that script are so long. I'm going to try to make it possible for copy-pasting from the clipboard into the next PrC/WM versions. I've fixed the length issue, but won't release it until the clipboard thing is working. Until then, keep the KERNEL.BIN script length in the triple digits at least.
 
Thanks for that very comprehensive post. Not that this is the first time you've offered something so useful to the modding community.

I have just a couple of questions:

1. You say that for a shorter script, we should 'knock it out in chunks of [...]'. Your wording leaves me a little unsure - do you mean we should excise portions of

60   00
71   001A
60   20
61   0100   *
92  
72   00E4

2. You mention using attacks with indexes from 0100 to 010F. Why is this? Looking at the code, and with the info on opcodes, I can't see an obvious reason for this besides making it convenient for the writer to run through the text (but my understanding of assembler is rubbish). I haven't checked this, but I always believed that for an enemy to use a learnable enemy skill, it had to have a very specific attack index. Could I write a script using this template that allowed an enemy to use a learnable enemy skill?
 
Last edited:
1 - removing the sections similar to the one you quoted will remove excess, unneeded script once you have all of the attacks you wanted in, so for example, you only wanted to use two attacks, your final script would look something like this:

Code: [Select]
Code:
12   207002   20A082   90   81   60   02 //only two attacks are to be used, if it was kept at 10 (16 decimal), it would only have a 2/16 or 1/8 chance to use one of the attacks, or else it'd do nothing.34   60   0071   001A60   2061   0100   *92   72   0025 // i hope these two jumps are pointing to the correct spot :P60   0171   002560   2061   0101   *92   //no 72 (absolute) jump is needed to go to the end, because the end is right after this9173
you just remove the extra attack checks, and update the amount of attacks at the beginning, and you have a working script.

2 - those attacks, when called by the enemy AI, refer to (mostly) unused attack indexes, so i am guessing he used it as a sort of placeholder for attacks, since you will be changing them all more than likely anyway. enemy skills are from index 0048 to 005f, and are not called in any special ways for enemies, at least as far as i can tell, so you shuld easily make an enemy use an enemy skill using this template.
 
Thanks. I had sorta guessed that leaving the line as [60 10] rather than [60 02] would mean that the first attacks would still only have a 1/16 chance of occuring each, but that the last attack would take up the 'remainder' of the values. Just removing the extra sections, I think, would make a script that did

- random no. 1-16
- if RND = 1, do alpha
- if RND = 2, do beta
- ELSE do gamma

(I know that's not strictly what the code does, I'm just outlining its operation)

So alpha and beta would only have a 1/16 chance of occurring each, but gamma has a 14/16 chance instead.

Thanks again for the help.
 
Last edited:
ok, the way i had written it, you would not have gotten the remaining attack chance used, and it would just skip it's turn if none of the checks activated, but if you just remove the check from the final attack, it would absorb the rest of the chances to go. i just didn't notice that NFITC1 had removed the check from the final attack, so if you want less than 16 attacks, remove all that are not used, and also remove the final attack's check if you want to make the last attack possibly have a higher chance to activate by increasing the random at the start.

there is a lot to learn about AI editing, but once you get the hang of it, everything becomes much easier, and you also notice how badly the original AI was for most things (if you couldn't tell already :roll:). it has been a while since i have done anything for ffvii, so i am kind of rusty, but if you have any questions, i will try to answer them the best that i can  :wink:.
 
@Bosola:

Yes, removing pieces of it like that will increase the chances of the final attack being executed. It's using fuzzy/implicit logic to always do SOMETHING. Secondadvent's code works, but that last check is superfluous, albeit direct/explicit logic. If you change the first 60 10 to something else, then it keeps the probabilities equal. If you get rid of seven of those blocks, subtract 7 from that number (in hex, of course) and everything will then be a 1/9 chance, rather than 8 1/16 chance attacks and one 1/2 chance attack. If that's what you want then great! You could even extend this to allow for all 32 attacks an enemy can have.
You can also play around with probabilities too. If you want three attacks to have a 1/16 chance each and four attacks to have a 1/8 chance each then one that catches the rest you can do this:

(pseudocode)
If (Random MOD 0)
{
     Perform(Attackthefirst, EnemyAttack)
}
ElseIf (Random MOD 1)
{
     Perform(Attackthesecond, EnemyAttack)
}
ElseIf (Random MOD 2)
{
     Perform(Attackthethird, EnemyAttack)
}
ElseIf ((Random MOD 3) or (Random MOD 4))
{
     Perform(Attackthefourth, EnemyAttack)
}
ElseIf ((Random MOD 5) or (Random MOD 6))
{
     Perform(Attackthefifth, EnemyAttack)
}
ElseIf ((Random MOD 7) or (Random MOD 8))
{
     Perform(Attackthesixth, EnemyAttack)
}
ElseIf ((Random MOD 9) or (Random MOD 10))
{
     Perform(Attacktheseventh, EnemyAttack)
}
Else
{
     Perform(FinalAttack, EnemyAttack)
}
Pop (Random)
END SCRIPT

I leave you to figure out the exact logic, but you can achieve this with that template if you know what to delete. Keep in mind, if you change the template, you'll have to watch your jumps. This is also the same Random number each time if you use the 71 jumps instead of the 70 jumps.
 
Thanks for that.

I've been doing some experimentation, trying to find a script that would allow me to alter statuses. By varying the figures in Mirage's script I've made a little headway, and I thought (apparently incorrectly) that it worked. Here it is:

12 2060
10 XXXX
80
60 Y
90

Where XXXX is the status in question and Y is the value to set it to.

What to use for XXXX? Well, I found that 4220 referred to the death status, 4230 to barrier and 4225 to fury, etc. as per mentioned in the status effects section of the wiki.

So, if you want perma-regen on a monster, I thought I'd use

12 2060
10 422F
80
60 1
90

And if we enter this into a blank section, and disassemble...

regen.png



...But, in the field, these scripts don't seem to work. I tried setting the [AI setup] of Midgar Zolom to grant it perma regen, but it doesn't seem to work. I then tried setting a physical counter that set HP to 1, but that didn't work either. Any ideas on where I could be going wrong?
 
Last edited:
Few problems with this.

#1. This kind of discussion needs its own thread or forum. Not a criticism on you, there just needs to be a better place for it.

#2. Your address is wrong. Don't use initial status. That's loaded before the battle is and then ignored. Use address 400Fh instead. That sets the status directly.

#3. You won't know if it even has regen unless you sense it. The status will be there, but it won't glow like a regen'd character will. Don't know if that's how you checked or not. Hopefully so.

#4. The Pre-battle isn't the best place to set these things up. The game kinda disregards time-dependent statuses set here. Set it in the Post-Attack and it will work fine.

12 2060
10 400F
80
60 1
90

Putting that in Post-Attack will make sure it's in a virtually-constant Regen state. Regen runs out after a time, that can't be stopped. Something has to start the timer for it to work. Pre-Battle apparently can't do this.
 
Few problems with this.

#1. This kind of discussion needs its own thread or forum. Not a criticism on you, there just needs to be a better place for it.
Yes, I do see what you mean. I suppose I just didn't want to open an entirely new thread on the matter. I do think that dedicated threads would be a good idea. Especially as otherwise there's a real risk of the ProudClod and WallMarket threads being derailed.

As for the rest - yes, it seems to work. I gave Jenova-DEATH the post-attack regen status, and she started to glow the moment she was hit by any 'attack' (damaging or not). Didn't work on Chekov - caused a freeze for some reason that I'm not quite certain of. In the previous case, the enemy didn't even show evidence of regeneration when I traced its HP with Sense. At any rate, this is all very useful.
 
OK, so get this. I got PrC to, as far as I can tell, use the windows clipboard for copy-pasting. So if you copy things like

60 20
12 2070
10 4000
60 01
81
56
93 "What's happening?!!?1?"
91
A0 01 Just popped something (%d)
91
73

as straight text, you'll be able to paste it into the AI editing window. It's a pretty flexible little format nazi. It should read

60 01

the same way it reads these:

60               01  <- spaces
60   01   <- tab
6001
60mary had a little lamb01
60543201

611234431234 will be read as "61 1234". But 61ittybitty321 will throw an error that PrC catches and disposes of. 61 on a line by itself is read as "61 0061". A little odd, but at least it doesn't crash.

I just copy-pasted my script up there through the clipboard and it pasted (sans asterisks) and disassembled exactly like I thought it would. You can inject it into a script like before, or you can paste it into a blank section and it'll make that into the script and add the end script byte if there isn't one in the script you copied. Pretty cool, huh?

Does anyone want this feature on WM? It's easy enough to inject that ability, but there's not much room for the copy paste buttons so they'd likely be a right-click menu.
 
you wouldn't happen to be planning on having a way to dump single enemy AI sections, would you? that would be a very nice thing to have (especially if it can import them as well :-P), and is something i have been wanting for a while :wink:.
 
you wouldn't happen to be planning on having a way to dump single enemy AI sections, would you? that would be a very nice thing to have (especially if it can import them as well :-P), and is something i have been wanting for a while :wink:.
That's a good point, but you could just copy-paste them yourselves if you only want one enemy's worth.
 
wow once again nfitc1 you have outdone me, i propsed this template purely to help noobs and yeah i have written it out wrong its meant to perform 16 attacks on one random enemy so thanks for correcting that. The new addition of been able to copy and paste into PRc would be a godsend! i am so sick of typing this template in again and again and again lol. thanks for helping people out on this and you also tought me something new so thanks.
 
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.
 
Status
Not open for further replies.
Back
Top