Qhimm.com Forums

Final Fantasy 7 => FF7 Tools => Topic started by: Bosola on 2010-04-30 22:45:44

Title: [FF7] AI Template Project - Welder
Post by: Bosola on 2010-04-30 22:45:44
CAUTION: The templates here are old. For the latest plugins, and an easy-to-use AI building environment, try out my 'parser', which comes with the latest versions of each template.  Click for more info. (http://forums.qhimm.com/index.php?topic=10376)

Edit: Rewritten because it was, well, a bit of a muddle

OVERVIEW

Welder is an AI template based around a 'header', a 'footer' and a series of independent 'plugins'. The header will pick an attack, but x/yths of the time consult a series of plugins, that might decide to choose a different attack if their conditions are matched. In either case, the game will then reach a 'footer', that performs the action either the header or the plugins have stored in memory.

This way, enemies can simulate smart decisions, with different priorities and quirks, but not act 'telepathically'.

Users will need to copy the header, and then copy the plugins they want to use - the lowest priority decisions coming first. Once all the plugins they require have been copied into Proud Clod / Wallmarket, it's then a matter of typing a 'footer', which is just the opcode 92.

The structure of AI scripts produced by Welder might look like the following -

** header : Choose claw attack,
** header : make smart decision instead 2/3rds of the time -
  * Plugin - If enemy is in regen, cast dispell
  * Plugin - If enemy is critical, kill
  * Plugin - If ally is critical, heal
** footer : use the spell or attack that has been chosen

In this case, the monster - let's call him Gordon - will use a claw 1/3rds of the time. But, 2/3rds of the time he'll consider other possibilities - healing allies, snatching opportunities to KO enemies and destroying buffs in that priority, should those conditions be met (otherwise, he'll carry on using his claw).

FOR USERS

Don't care about AI code, just want to be able to easily customize it? Well, that is what Welder is for. You need to copy the HEADER -

Code: [Select]

12 2070
02 20a0
82
90
60 20
61 index of the 'dumb' attack

81
60 X
34
60 Y
43
70 ZZZZ

Where: Y+1 / X is the chance of making a 'smart' decision (you can make different enemies more or less intelligent than one another) and ZZZZ is the address of the 'footer' (see above), which is merely the opcode 92 at the end of the script. Put the index of the attack you'll use by default into the first instance, insert your preferred 'IQ', and then find the footer location, replacing ZZZZ with it.

Between the header and footer, you can paste in 'plugins', which are sections of code I and (I hope) others will post in this thread. Each plugin is self-contained, and caters to a specific circumstance and response - for instance, casting a spell when an ally is below 50% health, or using a particular spell on oneself (say, reflect) if enemies are in a certain status (in this case, the reflect status).

Once you've finished copying in the plugins you want for your monster (and customizing a few values - these will be marked up in the plugins), you need to create an opcode 92, which comes right at the end of the AI script (before 73). Easy, huh?

FOR DEVELOPERS

Ok. Here's the idea:

The header leaves two values on the stack: 20 and the index of the 'default' or 'dumb' attack. Plugins each check for conditions, and if these are met, the index is popped and replaced by the index of a new attack. The target data is changed too. If these conditions are not met, the stack goes on untouched. At the end of each plugin, no matter what happens, there will always be on the stack

*20
*a valid attack index

In summary


The 'rules' of the plugins are:
* Note the length of the plugin, and the amount we will have to add, in hex, to the current address to reach the next plugin (and mark this up clearly within your code)
* Always leave the stack with exactly two values on it - the 20 needed for enemy attacks, and a 0-FFFF attack index.
* You can use as much internal logic as you like, just leave the stack 'clean'.
* Plugins respond to one particular condition with one particular attack. Keep it simple so that people can chop and change to avoid redundant data or alter the priorities an enemy has.
* Try to keep your plugins short and sweet. Space can be important.
* Plugins are self-contained. It's important that this template be orthogonal, and therefore easy to chop and change.

The idea is that newbies can simply take the header, runner, and the plugins that are appropriate to the creature they're thinking of, and still create a smart but not 'telepathic' monster who is fun to play against.

In summary:

Copy the header, modify the attack indexes and target data to your liking
Drop in the plugins
Create a 'runner' which is Opcode 92


So, shall we get posting?

See this page for an excel spreadsheet with templates (http://libre-limitbreakeditorforff7.yolasite.com/modding-efforts.php)
Title: Welder: A little AI Template Project
Post by: gjoerulv on 2010-05-01 01:34:15
Basically what you need is to make 2 variables (at the very least): 1 for attack and 1 for target. If the condition changes you change the value of the variables. You should then end the code by setting the target and the attack, and finally some sort of check if thee code should loop. A really good ff7 AI should have as few random factors as possible. Just random enough to not make it predictable.

Imo a good template.
Title: Welder: A little AI Template Project
Post by: secondadvent on 2010-05-01 02:12:32
...
12 2070
02 20a0
82
90
60 20
61 FFFF

81
60 02
34
70 XXXX

//these following three lines are redundant/useless as the above already chose a random target, and the one
//below doesn't even set the target to the new random target (missing 90... did you just c/p and forget to
//delete that part? :-P).

//12 2070
//02 20A0
//82
91
61 EEEE
...

the way i have been writing the AI lately has made as much use of the stack as possible to reduce the overall size by a good bit the larger the AI is, but it becomes harder to keep track of, especially since the way i have been coding causes PrC to spit out errors and give up disassembling it :-P.

Edit: adding comments within the code may also help it to become clearer, rather than just placing it all and then commenting after, and explains things line-by-line rather than as a whole (you could still keep your summary afterwards, but comments make things easier to understand). not saying that the current layout isn't good as it is, but newcomers to the AI may have more trouble understanding without "holding their hand" through it.
Title: Welder: A little AI Template Project
Post by: Covarr on 2010-05-01 02:18:02
Waitaminnit...

50% chance of a smart attack, 50% chance random?

This is basically assuming that all enemies are the same intelligence level. Wouldn't it make more sense to be able to adjust these odds per enemy? I mean, it seems like Reno would be a hell of a lot more likely to choose an intelligent attack than Hell House.
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-01 11:24:52
Edit: This refers to a slightly different version of the header. I decided to remove randomization out of the header to improve space, and because it's possible to write plugins that randomly choose attacks anyway.

Basically what you need is to make 2 variables (at the very least): 1 for attack and 1 for target. If the condition changes you change the value of the variables. You should then end the code by setting the target and the attack, and finally some sort of check if thee code should loop. A really good ff7 AI should have as few random factors as possible. Just random enough to not make it predictable.

Imo a good template.

Are you suggesting the plugins must push two values to the stack? The 20h needed to define an enemy attack is already present, so yes, you need only use the 91 opcode once, push one 0-FFFF attack index to the stack, and change the data in 2070.

For instance, in pseudo code, a plugin to check for allies without haste:

 IF there are allies with Haste = 0 AND MP Cost of attack X < Current MP then:
    Change 2070 to allies without haste
    Pop one value from the stack
    Push the index of haste or the like to the stack
  ELSE go to next plugin (current address + the length of the plugin from this point)


Quote from: secondadvent
//these following three lines are redundant/useless as the above already chose a random target, and the one
//below doesn't even set the target to the new random target (missing 90... did you just c/p and forget to
//delete that part? tongue).

No. I can't quite understand what you're asking me. It might help if I take you through the way the code works. In turn:
- the header flips a coin
- it sets the default attack on the basis of this coin toss
- it then flips another coin
- and sees whether or not to consult the plugins

A default attack must be set first, because the conditions of the plugins might not be met.

So, what the code does is:

12 2070
02 20a0
82
90 Sets target to random opponent
60 20
61 FFFF Pushes data for primary attack to the stack

Sets the normal default attack


81
60 02
34 creates a random number, sees if it divides by 2
70 XXXX skips the next section (which would write new attack data) if it *does* divide by two

50% of the time, refuses to overwrite it

12 2070
02 20A0
82
90
91
61 EEEE erases data on the stack, and adds data for the secondary attack -assuming this piece of code isn't skipped

Otherwise, it resets the target, pops the previous attack digit, and adds its own. I had accidentally deleted the 90, though, so thanks for pointing that up!

81 <---- XXXX points here
60 02
34
70 YYYY

Now we ask whether or not to proceed with plugins.

Flipping two coins takes less space than rolling a four-sided dice (so to speak), pushing the value to an address, then using comparators.

Quote from: secondadvent
the way i have been writing the AI lately has made as much use of the stack as possible to reduce the overall size by a good bit the larger the AI is, but it becomes harder to keep track of, especially since the way i have been coding causes PrC to spit out errors and give up disassembling it tongue.

Yes, using the stack is always preferable to relying on memory addresses. It's also slightly faster from a technical perspective, as the processor doesn't need to wait on RAM, AFAIK.

Quote from: secondadvent
Edit: adding comments within the code may also help it to become clearer, rather than just placing it all and then commenting after, and explains things line-by-line rather than as a whole (you could still keep your summary afterwards, but comments make things easier to understand). not saying that the current layout isn't good as it is, but newcomers to the AI may have more trouble understanding without "holding their hand" through it.

Maybe. The idea is that newbies should just be able to copy paste, though, and add custom values where told to (digits for certain statuses, and the addresses they'll have to derive themselves by adding a specified digit to the current address). Still, I suppose it can't hurt, can it?

Quote from: Covarr
Waitaminnit...

50% chance of a smart attack, 50% chance random?

This is basically assuming that all enemies are the same intelligence level. Wouldn't it make more sense to be able to adjust these odds per enemy? I mean, it seems like Reno would be a hell of a lot more likely to choose an intelligent attack than Hell House.

Indeed - I'd already pre-empted this. Just change the last pushed digit in the header to alter the chances of an intelligent attack, where the chance is 1 / the digit.

You can choose 'odd' intelligence ratios, like 2/3rds, too, but it's a little more complicated.

Frinstance, for

81
60 02
34
70 YYYY

use

81
60 03
34
60 01
42
70 YYYY

What's happening here is that you're pushing a random digit (81) and 3 (60 03) to the stack, then doing a modulo (34). This will pop the two values, and push the remainder - which in this case can be 0, 1, or 2.

I now push 1 to the stack (60 01) and use opcode 42, which pops both and sees if 1 is greater than / equal to the remainder. If it is (2/3 chance), I now have a number 1 sitting on top of the stack. If not, a number 0. The opcode 70 will pop this figure, and skip the plugins if it's a zero. In this case, I have a 2/3 chance of making a 'smart' decision.

So, you can think of

81
60 X
34
60 Y
42
70 YYYY

Where y+1/x is the chance of a 'smart' decision.
Title: Welder: A little AI Template Project
Post by: gjoerulv on 2010-05-03 03:23:56
Basically what you need is to make 2 variables (at the very least): 1 for attack and 1 for target. If the condition changes you change the value of the variables. You should then end the code by setting the target and the attack, and finally some sort of check if thee code should loop. A really good ff7 AI should have as few random factors as possible. Just random enough to not make it predictable.

Imo a good template.

Are you suggesting the plugins must push two values to the stack? The 20h needed to define an enemy attack is already present, so yes, you need only use the 91 opcode once, push one 0-FFFF attack index to the stack, and change the data in 2070.

For instance, in pseudo code, a plugin to check for allies without haste:

 IF there are allies with Haste = 0 AND MP Cost of attack X < Current MP then:
    Change 2070 to allies without haste
    Pop one value from the stack
    Push the index of haste or the like to the stack
  ELSE go to next plugin (current address + the length of the plugin from this point)


Sorry, I didn't mean to lecture or anything (if you got that impression)...
It's a good template really, though I would make it slightly different. You could change the footer and header in a way you don't have to worry too much 'bout the stack. some pseudo:
Code: [Select]
//header
while(Condition0)
{
   var tempAttack = XXX; //(defaultAttack)
   var tempTarget = YYY; //(defaultTarget)

   //plugins

   if(condition1)
   {
      temAttack = XXY;
      tempTarget = YYZ;
   }


   if(condition1)
   {
      temAttack = XXZ;
      tempTarget = YYX;
   }

   .
   .
   .

   if(conditionN)
   {
      temAttack = XZZ;
      tempTarget = YXX;
   }

   //footer
   target = tempTarget;
   attack = tempAttack;
   PerformAttack();
}


Edit:

Bytecode:

//header

12 0000
02 20A0
82
90 // Set the tempTarget var (0000) to random opponent
12 0010
61 XXXX
90 // Set tempAttack to default

//plugins
...

//footer
12 2070
02 0000
90 // Target = tempTarget
60 20
02 0010
92 // Perform tempAttack on target.

//Then optionally see if the code should loop (counts as plugin).

... (<- condition)
70 0000

73
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-03 12:26:38
No. I had considered this method, but rejected it. Using the localvars takes up more room: you need to call the localvars and use store opcodes - more verbose than just popping values and pushing anew (as NFITC1 will angrily berate you for ; ) ). As space is limited on the PSOne, saving a few bytes here and there is important.

Keeping the stack clean really isn't that difficult, anyway, especially as there should be no real need for using opcodes that don't pop two vals within each plugin (only 'Jump if not equal' comes to mind).
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-05-03 14:04:12
No. I had considered this method, but rejected it. Using the localvars takes up more room: you need to call the localvars and use store opcodes - more verbose than just popping values and pushing anew (as NFITC1 will angrily berate you for ; ) ). As space is limited on the PSOne, saving a few bytes here and there is important.

Very likely, yes. ;) However.....

the way i have been writing the AI lately has made as much use of the stack as possible to reduce the overall size by a good bit the larger the AI is, but it becomes harder to keep track of, especially since the way i have been coding causes PrC to spit out errors and give up disassembling it :-P.

This is a limitation on the way I'm writing PrC. When it's disassembling it is reading it the way the original compiler would have thought it should work. There's no fancy tricks done with it to preserve values on the stack or things. PrC tries to keep track of any values that happen to be on the stack at the time. However it's doing that linearly, ignoring jumps and such. That usually means that there might be things that won't disassemble correctly the would work fine. Take this for example:

Code: [Select]
81
60  01
35
70  0016
12  2070
02  20A0
00  4026
80
60  01
40
82
90
72  XXXX
12  2070
02  20A0
00  4026
80
60  00
40
82
90
72  XXXX
Code: [Select]
If (Random AND 1)
{
TargetMask <- RandomBit( (AllOpponentMask.Flag:BackRow == 1) )
}
Else
{
TargetMask <- RandomBit( (AllOpponentMask.Flag:BackRow == 0) )
}

That gives a 50% chance of picking a target in the backrow vs the frontrow. This will disassemble fine in PrC and is the traditional way for things to happen in FF7 AI.
This does the same thing and will not disassemble fine:

Code: [Select]
12  2070
02  20A0
00  4026
80
81
60  01
35
70  0017
60  00
40
72  001A
60  01
40
82
90
Code: [Select]
TargetMask <- RandomBit( (AllOpponentMask.Flag:BackRow == IIf(RANDOM AND 1 == 0, 0, 1) )

It's several bytes shorter, does the same thing, but will not disassemble correctly in PrC. Look at it linearly and see what the stack is doing:

Code: [Select]
12  2070     Stack: &TargetMask
02  20A0     Stack: &TargetMask, AllActiveEnemies
00  4026     Stack: &TargetMask, AllActiveEnemies, BackRow
80           Stack: &TargetMask, AllActiveEnemies.BackRow
81           Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM
60  01       Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM, 1
35           Stack: &TargetMask, AllActiveEnemies.BackRow, (RANDOM AND 1)
70  0017     Stack: &TargetMask, AllActiveEnemies.BackRow
60  00       Stack: &TargetMask, AllActiveEnemies.BackRow, 0
40           Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)
72  001A     Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)
60  01       Stack: &TargetMask, (AllActiveEnemies.BackRow == 0), 1
40           Stack: &TargetMask, ((AllActiveEnemies.BackRow == 0) == 1)
82           Stack: &TargetMask, RandomBit(((AllActiveEnemies.BackRow == 0) == 1))
90           &TargetMask <- RandomBit(((AllActiveEnemies.BackRow == 0) == 1))

PrC's disassembly looks like this:
Code: [Select]
If (Random AND 1)
{

}

}

Else
{
TargetMask <- RandomBit( ( (AllOpponentMask.Flag:BackRow == 0)  == 1) )

Mostly unreadable and completely incorrect. The code doesn't even use any fuzzy logic.

I suppose the moral is, if space isn't an issue then making the code readable should take precedence over making it shorter. If you're good enough to keep track of what you're doing then great! But since you can't make comments in your code it will confuse anyone that might want to use it for something else later if they're not familiar with little tricks like this.
I'm a programmer and writing code that will likely be used by people in the future. That's why I think like this. ;)
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-03 14:27:15
This is a limitation on the way I'm writing PrC. When it's disassembling it is reading it the way the original compiler would have thought it should work. There's no fancy tricks done with it to preserve values on the stack or things. PrC tries to keep track of any values that happen to be on the stack at the time. However it's doing that linearly, ignoring jumps and such. That usually means that there might be things that won't disassemble correctly the would work fine. Take this for example:

Code: [Select]
81
60  01
35
70  0016
12  2070
02  20A0
00  4026
80
60  01
40
82
90
72  XXXX
12  2070
02  20A0
00  4026
80
60  00
40
82
90
72  XXXX
Code: [Select]
If (Random AND 1)
{
TargetMask <- RandomBit( (AllOpponentMask.Flag:BackRow == 1) )
}
Else
{
TargetMask <- RandomBit( (AllOpponentMask.Flag:BackRow == 0) )
}

That gives a 50% chance of picking a target in the backrow vs the frontrow. This will disassemble fine in PrC and is the traditional way for things to happen in FF7 AI.
This does the same thing and will not disassemble fine:

Code: [Select]
12  2070
02  20A0
00  4026
80
81
60  01
35
70  0017
60  00
40
72  001A
60  01
40
82
90
Code: [Select]
TargetMask <- RandomBit( (AllOpponentMask.Flag:BackRow == IIf(RANDOM AND 1 == 0, 0, 1) )

It's several bytes shorter, does the same thing, but will not disassemble correctly in PrC. Look at it linearly and see what the stack is doing:

Code: [Select]
12  2070     Stack: &TargetMask
02  20A0     Stack: &TargetMask, AllActiveEnemies
00  4026     Stack: &TargetMask, AllActiveEnemies, BackRow
80           Stack: &TargetMask, AllActiveEnemies.BackRow
81           Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM
60  01       Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM, 1
35           Stack: &TargetMask, AllActiveEnemies.BackRow, (RANDOM AND 1)
70  0017     Stack: &TargetMask, AllActiveEnemies.BackRow
60  00       Stack: &TargetMask, AllActiveEnemies.BackRow, 0
40           Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)
72  001A     Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)
60  01       Stack: &TargetMask, (AllActiveEnemies.BackRow == 0), 1
40           Stack: &TargetMask, ((AllActiveEnemies.BackRow == 0) == 1)
82           Stack: &TargetMask, RandomBit(((AllActiveEnemies.BackRow == 0) == 1))
90           &TargetMask <- RandomBit(((AllActiveEnemies.BackRow == 0) == 1))

PrC's disassembly looks like this:
Code: [Select]
If (Random AND 1)
{

}

}

Else
{
TargetMask <- RandomBit( ( (AllOpponentMask.Flag:BackRow == 0)  == 1) )

Mostly unreadable and completely incorrect. The code doesn't even use any fuzzy logic.

I suppose the moral is, if space isn't an issue then making the code readable should take precedence over making it shorter. If you're good enough to keep track of what you're doing then great! But since you can't make comments in your code it will confuse anyone that might want to use it for something else later if they're not familiar with little tricks like this.
I'm a programmer and writing code that will likely be used by people in the future. That's why I think like this. ;)

In this case, though, I think space really is an issue, unless we find out more about haxxoring the SCUS_blahblahblah filetable (I think Gemini has some info on this, which I'd like to see more of). Whilst it's true that readability is nice, the real purpose is to open up easy AI editing to those who wouldn't be interested in learning assembler etc. anyway.

At any rate, authors are quite free to write 'plugins' however they wish - internally handling localvars and what have you is absolutely fine, you just need to remember to end the plugin with the right values on the stack. Hell, you could even do it with a pen and paper - write a list of things on the stack, crossing them out as they get popped. Some writers might not create very efficient plugins, but users can choose between competing plugins, that offer different lengths, features, and customization. And hey, it's a forum, they're free to ask if they so choose.

Oh, and I'd forgot about the 'Random, push X, bitwise AND' trick. Nice.

What are your thoughts on Welder otherwise, though?
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-05-03 15:37:49
In this case, though, I think space really is an issue, unless we find out more about haxxoring the SCUS_blahblahblah filetable (I think Gemini has some info on this, which I'd like to see more of). Whilst it's true that readability is nice, the real purpose is to open up easy AI editing to those who wouldn't be interested in learning assembler etc. anyway.

True enough. There are other ways to decrease size besides making AI code a few bytes shorter. Deleting entire scenes works well because 7808 bytes of the same value compress into about 16 bytes or so. :)

At any rate, authors are quite free to write 'plugins' however they wish - internally handling localvars and what have you is absolutely fine, you just need to remember to end the plugin with the right values on the stack. Hell, you could even do it with a pen and paper - write a list of things on the stack, crossing them out as they get popped. Some writers might not create very efficient plugins, but users can choose between competing plugins, that offer different lengths, features, and customization. And hey, it's a forum, they're free to ask if they so choose.

Oh, and I'd forgot about the 'Random, push X, bitwise AND' trick. Nice.

What are your thoughts on Welder otherwise, though?

The idea is great, but you'll need to change your header slightly. It needs to pop the values of the header's base attack

Code: [Select]
12 2070
02 20a0
82
90
60 20
61 index of the 'dumb' attack

81
60 X
34
60 Y
42
70 ZZZZ
91
91   <if probability passes to execute "intelligent attack", remove the pushed attack/command indexes>

Also, consider the following "plugin":

<header>      Default attack are set here so there are two values on the stack
<given a probablility, jump to footer>
81
60  17
34
60  00
71  XXXX
<set target and push attacks>
<jump to footer>
<more options based on value of (Random MOD 23)>
...
<footer>      Now there are three values on the stack!

71 doesn't pop both values it compares so there would still be a value at the back of the stack. When you'd get to the end of the footer you'd still have the (Random MOD 23) on the bottom of the stack. I don't know what consequences this has. All the scripts are written in the way that the stack is empty when the 73 is "executed". I believe this a safe practice as I'm not sure if a new stack is created each time a script runs or if there's one stack at a time. So I say there should be a reserved intermediate "need pop" localvar (say LocalVar:1000) that indicates if there needs to be a pop at the end.

10  1000
60  01
90      These values are popped so essentially clean stack still. Not part of the header since 71 might not be used in plugin
81
60  17
34
60  00
71  XXXX
<set target and push attacks>
<jump to footer>
<more options based on value of (Random MOD 23)>
...

Then the footer should be:

Code: [Select]
92
00  1000
70  XXXX (skip the next byte to the ending)
91
73

Since PrC updates jumps (you're welcome ;) ) then the footer doesn't have to be rewritten. This ensures that the stack is empty by the end and the pushed attack data isn't lost.

Also, the RANDOM AND XXX would only work with powers of 2. Since RANDOM creates a Word, then it would give chances from 1:2^-1 to 1:2^-16 in powers of 2. Some things could be pretty rare, but if that's what you're going for then great. :D
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-03 15:48:09
True enough. There are other ways to decrease size besides making AI code a few bytes shorter. Deleting entire scenes works well because 7808 bytes of the same value compress into about 16 bytes or so. :)

Too true.

The idea is great, but you'll need to change your header slightly. It needs to pop the values of the header's base attack

No, I can see why you think that, but you've misunderstood - plugins can alter the attacks, but don't have to - they might keep the default attack. Consider the following

Default attack: Hit random with hammer
1/2 chance of plugins:
 - if enemy is weakened, finish them off
 - if in critical, change 'hit with hammer' to 'cast cure on self'

Here, the monster (let's call him Alan) will by default hit things with hammers. Sometimes, he'll make smarter decisions - but there are still circumstances where hitting a random enemy with a hammer is the 'smart' choice for Alan to make. The default choice remains in these circumstances. Plus, plugin authors and users don't need to worry about always maintaining a 'plan B' attack index at the end of each plugin.

tl;dr - Plugins check for conditions, but you don't *have* to have any certain to come 'true'.

Quote
Also, the RANDOM AND XXX would only work with powers of 2. Since RANDOM creates a Word, then it would give chances from 1:2^-1 to 1:2^-16 in powers of 2. Some things could be pretty rare, but if that's what you're going for then great.

Yes. I already understood that.  :wink:
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-05-03 16:02:38
No, I can see why you think that, but you've misunderstood - plugins can alter the attacks, but don't have to - they might keep the default attack. Consider the following

Default attack: Hit random with hammer
1/2 chance of plugins:
 - if enemy is weakened, finish them off
 - if in critical, change 'hit with hammer' to 'cast cure on self'

Here, the monster (let's call him Alan) will by default hit things with hammers. Sometimes, he'll make smarter decisions - but there are still circumstances where hitting a random enemy with a hammer is the 'smart' choice for Alan to make. The default choice remains. Plus, plugin authors and users don't need to worry about always maintaining a 'plan B' attack index at the end of each plugin.

tl;dr - Plugins check for conditions, but you don't *have* to have any certain to come 'true'.

The pops don't need to be in the header, but they do need to be somewhere before changing the attack or else the stack wouldn't be empty by the end of the script. I believe it's safer to empty the stack since I'm not quite sure what happens at the end. Does the stack get cleared by the 73 or does it get reinitialized at the beginning of a new script? I'm not sure so I'm going to be more cautious with it. All the scripts execute and leave a clean stack in their wake. Otherwise you'll have some leftovers when scripts get run again. So I guess that falls upon the plugin author to account for, doesn't it?

One more thing; The header should be changed to:

Code: [Select]
12 2070
02 20a0
82
90
60 20
61 index of the 'dumb' attack

81
01  1020
34
01  1040
42
70 ZZZZ

This way, the probabilities can be changed mid-battle based on counter attacks and such and be initialized in the Pre-Battle scripts.

11  1040
60  3
90
11  1020
60  7
90              Roughly 42% chance of doing something "smart"

That's personal preference of course. If you don't have any desire to change probabilities then you could leave them as static values.
Title: Welder: A little AI Template Project
Post by: gjoerulv on 2010-05-03 16:11:44
No. I had considered this method, but rejected it. Using the localvars takes up more room: you need to call the localvars and use store opcodes - more verbose than just popping values and pushing anew (as NFITC1 will angrily berate you for ; ) ). As space is limited on the PSOne, saving a few bytes here and there is important.

Keeping the stack clean really isn't that difficult, anyway, especially as there should be no real need for using opcodes that don't pop two vals within each plugin (only 'Jump if not equal' comes to mind).

It's takes (not really that much) more space, yes, but imo it looks more sexier if you do it this way. For a programmer, easy readable code is important. But I'm fully aware the PS1 has space issues, and taken the space problem into consideration, people would probably benefit more from your method. I'm just saying how I would've done it.

Personally, I wouldn't use this template simply 'cause it's limited. That doesn't mean it's not a good template to work with. It would be fun to make some plugins.

...
I suppose the moral is, if space isn't an issue then making the code readable should take precedence over making it shorter. If you're good enough to keep track of what you're doing then great! But since you can't make comments in your code it will confuse anyone that might want to use it for something else later if they're not familiar with little tricks like this.
I'm a programmer and writing code that will likely be used by people in the future. That's why I think like this. ;)

Yup, that's a good morale. I'm a programmer myself, and some of my algorithms are out there in the world lol. And one of the things I'm most concerned with is that people easily understands the code. But I must admit I'm a bit lazy when it comes to progs I have no intention releasing a source on. :P
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-03 16:15:40
Quote
The pops don't need to be in the header, but they do need to be somewhere before changing the attack or else the stack wouldn't be empty by the end of the script.

Yes. In the first draft of the opening post, I made that a lot clearer, saying 'you need to leave two values on the stack, one being the 20, the other being a valid attack index'. The idea I proposed involved clearing the stack in the plugins. I think that having each plugin use a single 91 opcode will be shorter overall than popping the values in the header and lengthening a plugin such that an attack will *always* be performed in any circumstance.

Quote
Does the stack get cleared by the 73 or does it get reinitialized at the beginning of a new script?

I believe not. See a thread I posted on GameFAQs here: http://www.gamefaqs.com/boards/genmessage.php?board=197341&topic=52751515

Quote
One more thing; The header should be changed to:

Code:
12 2070
02 20a0
82
90
60 20
61 index of the 'dumb' attack

81
01  1020
34
01  1040
42
70 ZZZZ

This way, the probabilities can be changed mid-battle based on counter attacks and such and be initialized in the Pre-Battle scripts.

11  1040
60  3
90
11  1020
60  7
90              Roughly 42% chance of doing something "smart"

That's personal preference of course. If you don't have any desire to change probabilities then you could leave them as static values.

That's a pretty cool idea - enemies that get smarter (or dumber) under certain conditions.

I can already imagine an application: an enemy and his pets. The pets become less intelligent on the death of their master, using less pointed, and maybe less 'efficient' (ie less powerful) attacks. All it would take is a change to var 1020 / 1040 on the 'master's' death.
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-03 16:25:59
It's takes (not really that much) more space, yes, but imo it looks more sexier if you do it this way. For a programmer, easy readable code is important.

On a tangent, but I have to object to this, with respect.

Yes, readability is important when space and speed aren't issues, but it's not true to claim all programmers always favour this sort of philosophy. Granted, using less-than-readable code, compiler exploits for space, etc. etc. often gets derided as 'cowboy coding', but different coding strategies suit different circumstances IMHO.

Consider Micro-Soft's famous BASIC interpreter (one of their first commercial applications - actually written by Gates!) - they actually have the application jump to the mid-way part of a three-byte instruction (so it starts reading from the second byte). This data is actually intelligible and works as intended - that's pretty tight coding! Why go to all this trouble? To make a BASIC interpreter that actually ran and left headspace on the Altair Personal Computer - giving MS a massive commercial advantage. Of course, for modern organizations, a few bytes give them virtually no commercial advantage - being able to reduce costs in making code 'portable', on the other hand...

It all matters on the circumstance. The idea for me is to produce a header that doesn't really need to be read, but does need to be fairly compact, because space is still an issue with the PSX (YAMADA.BIN alterations not withstanding).

Quote
Personally, I wouldn't use this template simply 'cause it's limited. That doesn't mean it's not a good template to work with. It would be fun to make some plugins.

Hey, that's fine. There are quite a few enemies this wouldn't really fit anyway, like most bosses.
Title: Welder: A little AI Template Project
Post by: gjoerulv on 2010-05-03 16:56:22
It's takes (not really that much) more space, yes, but imo it looks more sexier if you do it this way. For a programmer, easy readable code is important.

On a tangent, but I have to object to this, with respect.

Yes, readability is important when space and speed aren't issues, but it's not true to claim all programmers always favour this sort of philosophy. Granted, using less-than-readable code, compiler exploits for space, etc. etc. often gets derided as 'cowboy coding', but different coding strategies suit different circumstances IMHO.
...

Well, yeah, I agree. That's why I said people would probably benefit more from your method when it comes to space.
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-05-03 17:26:33
Quote
The pops don't need to be in the header, but they do need to be somewhere before changing the attack or else the stack wouldn't be empty by the end of the script.

Yes. In the first draft of the opening post, I made that a lot clearer, saying 'you need to leave two values on the stack, one being the 20, the other being a valid attack index'. The idea I proposed involved clearing the stack in the plugins. I think that having each plugin use a single 91 opcode will be shorter overall than popping the values in the header and lengthening a plugin such that an attack will *always* be performed in any circumstance.

Oh, sorry. I must have glanced over this. :) Good point.

Quote
Does the stack get cleared by the 73 or does it get reinitialized at the beginning of a new script?

I believe not. See a thread I posted on GameFAQs here: http://www.gamefaqs.com/boards/genmessage.php?board=197341&topic=52751515

Should be easy enough to test, actually. Find some enemy that has a basic attack and see if you can get it to perform that attack based on stack leftovers. I'd suggest using the Kalm Fang. It's got probably the simplest AI of all of them. Re write it to do this:

Pre-Battle
60  20
61  0179 (I think this is its bodyblow)

Main
12  2070
02  20A0
80
82
90
92
60  20
61  0179    (for next round)
73

If that doesn't crash the game then the stack doesn't reset between script calls.
Title: Welder: A little AI Template Project
Post by: Armorvil on 2010-05-04 19:11:14
Coming from someone without programming notions, this is indeed a great project. To be quite honest, I'm still struggling to understand all those push, stack and pop talk :P

In the past, I used FF3usME quite a lot (multi-editor for FFVI), and I'd love that one day, editing AI for FFVII would be as simple. Maybe these templates and plugins could be implemented to Proud Clod, somehow ? Like, by clicking on boxes, in the AI editor, which would lead to a list of the monster's abilities... ...Selecting one would add the AI script so the enemy casts the selected spell. Then, you click the next box to choose the chance for it to cast it, and the conditions. Another box would be to decide the target, etc...

Actually, that sounds like too much work for NFITC1 :P
Never mind about this message  :-X
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-05 19:30:50
To be honest, you could probably do it with just an excel spreadsheet and a macro.
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-05-05 21:11:24
Coming from someone without programming notions, this is indeed a great project. To be quite honest, I'm still struggling to understand all those push, stack and pop talk :P

In the past, I used FF3usME quite a lot (multi-editor for FFVI), and I'd love that one day, editing AI for FFVII would be as simple. Maybe these templates and plugins could be implemented to Proud Clod, somehow ? Like, by clicking on boxes, in the AI editor, which would lead to a list of the monster's abilities... ...Selecting one would add the AI script so the enemy casts the selected spell. Then, you click the next box to choose the chance for it to cast it, and the conditions. Another box would be to decide the target, etc...

Actually, that sounds like too much work for NFITC1 :P
Never mind about this message  :-X

None of that is actually hard. I eventually plan to allow for some simple templates like that, but the truth is that writing AI for FFVII is hundreds times more difficult than it is for FFVI. Yes, I used FF3usME back when it was still being developed and that's part of what made me want to make PrC do what it does. For FFVI though it's a lot more simplistic. "Pick random target" is only one or two bytes where doing it in FFVII takes no fewer than 9. Attacks are also more complicated, blah, blah, blah, etc.

I guess there could be a menu where it inserts certain things like "random target" or "perform attack (x)". For now unfortunately, it'll stay the way it is. Sorry. :(
Title: Welder: A little AI Template Project
Post by: Idec Sdawkminn on 2010-05-06 23:41:12
From what I can understand, this sounds good.

Not entirely on topic, but I was thinking... Materia is readily available to anyone who goes to a town. It would be cool to have enemies, maybe just humanoid enemies, have some materia combinations. Some even Final Attack+Revive or something. You could have plugins for materia combinations, most just using the ones from the town they are near, and have it randomly assign those to the enemies at the start of the battle. The same enemy can have different attacks. Each materia plugin could have a code, like All+Cure will be assigned the restorative code, or Fire would be given the magic attack code.
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-08 16:19:51
This plugin heals allies when they're in low health:

Code: [Select]
02 2060
02 4140
80
61 <healing spell index>
86
42
70 <address of next plugin - should be current + 31h, but do check>
12 1000
02 2080
03 4160
80
02 2080
03 4180
80
60 Y
33
60 X
32
43
90
02 1000
60 1
42
70 <address of next plugin - should be current + Fh, but do check>
12 2070
02 1000
82
90
91
61 <healing spell index>

Replace 'healing spell index' with the attack index of Cure, Cure2, etc, and put in the address of the next plugin where told to (if no other plugins come afterwards, you'd put the address of the footer, obviously). X/Y is the fraction of HP a creature must be at or below to be considered for healing. If several creatures meet this requirement, a random one will be selected. This plugin checks for MP, and will not try and cast a spell beyond its MP pool.

I have tested this, and it seems to work. I would appreciate any feedback from other testers, though.

Specifically, I tried it on a Nerosuferoth, turning heatwing into a curative 'spell'. The code is just the header and plugin:

Code: [Select]
12 2070
02 20A0
82
90
60 20
61 01C5 <- Default attack, which I named 'Standard'
81
60 04
34
60 02 <- 'IQ' is 3/4
42
70 0054 <- Goes to footer
***** THE FOOTER HAS ENDED *****
***** NOW THE PLUGIN STARTS *****
02 2060
02 2140
80
61 01C6 <- The index of the healing attack (which I named 'Healwing'), checks MP requirements
86
42
70 0054 <- Goes to next plugin, or in this case, the footer (because I'm not using any other plugins)
13 1000 <- I'm using this to hold lists of creatures with HP below X/Y
02 2080
03 4160
80
02 2080
03 4180
80
60 03
33
60 02 <- In this case, X/Y = 2/3rds, so a creature with 66% or less HP will qualify
32
43
90
03 1000
60 01 <- Checks to see if any such creatures actually meet these requirements
42
70 0054 <- Goes to next plugin, or in this case, the footer (because I'm not using any other plugins)
12 2070
03 1000
82
90
91
61 01C6 <- We've just popped the default attack, and we're now pushing the 'healwing' index
***** THE PLUGIN ENDS HERE *****
< The next plugin would start here - if the above was not the last. >
< In this case, there are no other plugins to include, so we proceed directly to the footer >
***** THE FOOTER: *****
92 <- This is the footer, or opcode 92. In this case, it's at address 54h.

Quote
You could have plugins for materia combinations, most just using the ones from the town they are near, and have it randomly assign those to the enemies at the start of the battle. The same enemy can have different attacks. Each materia plugin could have a code, like All+Cure will be assigned the restorative code, or Fire would be given the magic attack code.

This would be sort of doable, yes. You'd need to put random values into certain variables at the battle start, and then create plugins that only 'activate' when those variables say so. The AI would end up quite bulky this way, however.

EDIT: Addendum

You should be able to modify this plugin to target only particular creatures. This is useful when one species 'looks after' another, or when a creature's friend absorbs an elemental attack (and can thus be healed with non-curative spell). You would do it like

Code: [Select]
12 1000
02 2080
02 4120
80
60 <species index - see indeces listed in formation>
40
90
02 2060
02 4140
80
61 <heal/ elemental attack index>
86
42
70 <next plugin>
12 1000
02 1000
03 4160
80
02 2080
03 4180
80
60 Y
33
60 X
32
43
90
02 1000
60 1
42
70 <next plugin>
12 2070
02 1000
82
90
91
61 <heal/ elemental attack index>

All this does is make a list of allies with ID X and store it in localvar1000. It will then consult with these to check for HP / target critical allies. As a rule, if using both plugins, you should insert this one second to give it higher priority.

Of course, you could always achieve this by creating a list of creatures of species X in the pre-battle script, but that might complicate things too much for the target audience (people who want to at most cut and paste, and maybe tweak the odd variable).

EDIT 2: Another Plugin, this time using attack X on random opp with status Y

If correctly written (and I've leave others to say so or not), this will be an important plugin - useful for not only buffing and debuffing, but things like "Take out opps. in critical", etc.

Here's the essential template

Code: [Select]
OPTIONAL : CHECK MP

02 2060
02 4140
80
61 <index of spell / attack>
86
42
70 <address of next plugin - MP fail>

MAIN: IS OPP IN STATUS --?

13 1000
02 20a0 <2080 for allies>
00 <status flag address in memory - 4001 for Critical>
80
90
<this has just created a list of enemies and their critical statuses>
03 1000
60 01
42 <make 45 to look for enemies WITHOUT the status>
70 <address in AI of next plugin / footer>
12 2070
03 1000
82
90
91
61 <index of attack>

So, the monster can attack a low-HP party member to KO them.

This could have several other implementations. Frinstance, a simple debuff plugin:

Code: [Select]
02 2060
02 4140
80
61 [index of Silence]
86
42
70 <address of next plugin - MP fail>
13 1000
02 20a0
00 400a <stop>
80
90
<this has just created a list of enemies and their stop statuses>
03 1000
60 01
45 <not stop>
70 <address in AI of next plugin / footer>
12 2070
03 1000
82
90
91
61 <index of silence>

The only issue with this is that it would keep using debuff spells on party members immune to them, unless we were using the default attack (chance depends on IQ) / higher-priority events came up. Most battles don't last all that long, though, so it wouldn't be that noticeable. You could get around it by adding a 'if Mod of Random and X > Y, go and do something else' piece to the start of the plugin.

More interesting are implementations like

Code: [Select]
02 2060
02 4140
80
61 <index of deathblow like attack>
86
42
70 <address of next plugin - MP fail>
13 1000
02 20a0
00 400a <stop>
80
90
<this has just created a list of enemies and their stop statuses>
03 1000
60 01
42
70 <address in AI of next plugin / footer>
12 2070
03 1000
82
90
91
61 <index of deathblow-like attack, that obviously cannot miss an enemy in stop>

With the two variants on this plugin, you've now got a monster who casts stop and then exploits your lack of evasion to rain down a powerful but inaccurate attack.
Likewise with casting only magic on sleeping party members.

MULTIEDIT: BOARD SOFTWARE IS BORKED, WILL NOT LET ME DELETE CODE TAGS FROM POAST. RECTIFY PLZ.
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-05-09 01:53:10
MULTIEDIT: BOARD SOFTWARE IS BORKED, WILL NOT LET ME DELETE CODE TAGS FROM POAST. RECTIFY PLZ.

You've got two [cod e] tags in your first code block. Get rid of those and you can get rid of those ending terminators.
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-09 10:51:01
Aha, never saw those. Guess I should re-edit that to

Quote
BOARD SOFTWARE IS NOT BORKED, MY GLASSES ARE

Anyways, here's an idea of the plugins I think need to be sorted out:

- header / footer (done)
- heal /hurt ally / opp with less/more than X/Y HP (done)
- cast X on ally / opp with status X (done)
- cast X on ally/opp with status X AND Y
- cast X on ally/opp with status X OR Y
- cast X on ally/opp with status X AND NOT Y
- cast X on self when opps have status X [eg reflect]
- cast X when in status X
- cast X ally / opp with greatest / least STAT (Magic, Dexterity, Strength, Vitality, HP, MP - whatever)
- cast X on random when a LOCALVAR-a is at 1
- cast X on LOCALVAR-a when LOCALVAR-b is at 1
- random chance of other attack
- escape when all allies inc. self dead OR critical
- cast X when below/above X/Y HP [kamikaze, darkside, etc]

I think with those the vast majority of operations will be covered. Can you think up any more?

Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-05-10 16:38:50

- cast X on ally/opp with status X AND Y
- cast X on ally/opp with status X OR Y
- cast X on ally/opp with status X AND NOT Y
- cast X on self when opps have status X [eg reflect]
- cast X when in status X
- cast X ally / opp with greatest / least STAT (Magic, Dexterity, Strength, Vitality, HP, MP - whatever)
- cast X on random when a LOCALVAR-a is at 1
- cast X on LOCALVAR-a when LOCALVAR-b is at 1
- random chance of other attack
- escape when all allies inc. self dead OR critical
- cast X when below/above X/Y HP [kamikaze, darkside, etc]

Very easy. Some enemies do these kinds of things.

- cast X on ally/opp with status X AND/OR/AND NOT Y
Code: [Select]

Most of the other cases you listed are just as easy. Check out Eligor's Main for some ideas.
12  2070
02  20A0  <- enemies, change to 20B0 for allies
00  40XX  <- change XX to status1
80
60  01    <- or 00 for NOT having status
40
02  20A0
00  40XX  <- status2
80
60  01
40
35        <- this is status1 AND status2, 36 is (status1 OR status2), 37 before the 35 is (status1 AND NOT status2)
82        <- random target that fits the above restrictions (optional)
90        <- store target
60  20
61  YYYY  <- attack
92

Translates:
TargetMask <- RandomBit( (AllOpponentMask.Status:status1 == 1)  AND  (AllOpponentMask.Status:status2 == 1) )
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-22 19:03:56
Thank you, NFITC1. I think we now have enough data to cover most functions. A provisional set of plugins can now be bandied about.

NEW PLUGINS

**********************************
* ALTERNATIVE HEADER WITH VARIABLE IQ *
**********************************

This is an alternative to the standard header. It uses LOCALVAR 0200 to determine intelligence.

The purpose of this is for enemies to get smarter and more efficient under certain circumstances. I'm sure we've all played games where one leader unit grants new abilities or better stats to his peers - or where a group will become more aggressive and vengeful on the death of certain allies. This header allows you to implement such behaviour.

Code: [Select]
12 2070
02 20a0
82
90
60 20
61 index of the 'dumb' attack

81
60 FF
34
00 0200 <- reads one byte from LOCALVAR 0200
43
70 ZZZZ <- address of the footer

To use this, you will need to set a default intelligence level in at least one of the active Pre-battle scripts for each formation. This should be a value from 0 to FF (one byte). This value over 255 will be the 'IQ' - the proportion of attacks that are 'smart'.

Establish this with the following code

Code: [Select]
10 0200
60 FF <- this is the default intel level, from 0h to FFh - 80h is about 'half' intelligence
90

To alter IQ on certain events, you will need to write counter scripts, or counter-death scripts. These will take the form of the above code for Pre-Battle scripts, but contain the 'new' intelligence level.

So, to make things clear, let's imagine a soldier and his two dogs

*Dog IQ = 2/3rds by default. 'Normal' attack is just the usual, but the 'smart' attack will be 'fang' (more potent), used against the weakest opp
*Soldier's counter-death script sets IQ to zero
*Dog now acts like a wild animal without a trainer

****************************
* THE TWO-STATUS CHECK PLUGIN *
****************************

This is very close to the code NFITC1 contributed. It looks for enemies with either two specified status conditions, or looks for enemies n one condition *or* another. By 'condition', I mean being either in or *out* of a status.

For instance, you can check for
* Opponents in both SLEEP and MINI
* Opponents both ASLEEP and NOT MINI'D
* Opponents either ASLEEP or MINI'D
* Opponents either ASLEEP or NOT MINI'D

This is useful when wanting to take heed of things like Resist or Reflect. It also allows you to compound effects, or limit difficulty by stopping monsters over-loading the party with nasty statuses. Want to cast debarrier on opponents with either protect or shell? Want to cast despell on enemies with regen but not slow? Fancy being able to haste allies in berserk but not confusion? This is the script to use.

Code: [Select]
<<OPTIONAL MP CHECKING SECTION START>>
02 2060
02 4140
80
61 INDEXOFATTACK
86
42
70 ADDRESS OF NEXTPLUGIN
<<OPTIONAL MP CHECKING SECTION END>>
12 0400
02 20A0
00 40XX
80
60 01
40
02 20A0
00 40YY
80
60 01
40
35
82
90
02 0400
70 ADDRESS OF NEXTPLUGIN
02 2070
02 0400
90
91
61 FFFF
90

Let's go over that again to explain how it works, and what you should change to make the plugin suit your purposes:

--optional MP checking section--
02 2060
02 4140
80
61 INDEXOFATTACK
86 <- checks if user has MP for the above attack
42
70 ADDRESS OF NEXT PLUGIN
--end of optional MP checking section--
12 0400
02 20A0 <- active opponents (2080 for allies)
00 40XX <- the first status to check, for instance, 4006 for confusion
80
60 01 <- are we looking for opponents who have the above status? If so, this should be 1. If we're looking for opponents who *don't*, then 0
40 <- create list of opponents with/without the status in question
02 20A0
00 40YY <- second status to check
80
60 01 <- again, 01 if we want opponents WITH the status, 00 if we want units WITHOUT the status.
40 <- we now have two lists: one of opps. with status X (in)active, and one of opps. with status Y (in)active. We're now going to compare them.
35 <- bitwise AND: creates a list of opps with both status X and status Y active. We use the opcode 36 instead if we're wanting an OR.
82 <- a random member of the resultant list (optional)
90 <- stores in 0400
02 0400 <- loads list from 0400
70 ADDRESS OF NEXT PLUGIN <- goes to next plugin if this list is empty
12 2070 <- tar address
02 0400 <- list data
90 <- uses list as target
91 <- POPS off default attack / prior set attacks
61 FFFF <- pushes data of new attack

***************************
* The Kamikaze / Darkside script   *
***************************

This script has a monster look at its own HP, and use a certain attack if its HP is above or below a certain fraction. This is useful for darkside-like attacks/ souleater moves and blowup attacks. You don't want to use darkside (sacrifice a little HP for a powerful attack) when in low HP; nor do you want to explode when well-healed. So that's where this plugin comes in.

Code: [Select]
02 2060
02 4160
80
02 2060
02 4160
80
60 FF
33
60 X
32
42/43
70 ADDRESS OF NEXT PLUGIN
12 2070
02 20a0
82
90
91
61 INDEX OF ATTACK

Explained:

02 2060
02 4160
80 <-own HP
02 2060
02 4160
80
60 FF
33 <- own HP over 255
60 X <- this is our multiplier. This over 255 makes the fraction we're going to compare our HP with. 80h makes for half HP.
32
42/43 <- if HP below this fraction (42)/if HP above this fraction (43) ...then leave a TRUE on the stack
70 ADDRESS OF NEXT PLUGIN <- if there's a FALSE on the stack, go to next plugin
12 2070
02 20a0 <- so, random opponent
82
90
91
61 INDEX OF ATTACK

************************************************
* Use Attack A on LOCALVAR 0300 when LOCALVAR 0400 == 1 *
************************************************

This is a small script whose purpose may seem obscure at first. Basically, this allows for decisions to be set in counter scripts and the like. For instance, an ally might have a counter-death script that sets LVAR 0400 to 1 and puts the identity of its killer in LVAR0300. This script means that the downed monster's friends avenge its death.

Alternatively, a powerful fire attack might cause creatures to catch fire. A monster will, as a counter, flash, "The woods caught fire!" - from that point, attack A gets used, a fire-like attack called "Fire's outta control!". Think the Soulcage battle from FFIX.

You can replace the LocalVar 0300 with 20a0 (opponent list general) if you like. Here's the code:

Code: [Select]
00 0400
70 ADDRESS OF NEXT PLUGIN
12 2070
02 0300
(81)
90
91
61 INDEX OF ATTACK

So

00 0400
70 ADDRESS OF NEXT PLUGIN <- jumps if 0400 is FALSE
12 2070
02 0300 <- replace with 20a0 if you just want to target opponents as per usual
(81) <- optional: choose a random target of the above
90
91
61 INDEX OF ATTACK

***************************************
* ATTACK A on opp/ally with greatest/ least STAT *
***************************************

This is an attack that you may have seen a couple of times in vanilla FF7. It allows you to target the enemy with, say, the least HP or the greatest Magic stat; likewise you can target an ally with the highest Dex (taking advantage of haste, anyone?) or the lowest Defence (Barrier, for instance).

As always, this can be preceded with the 'MP checking section' seen elsewhere.

Code: [Select]
12 2070
02 20a0*
02 4100**
80
85***
82
90
91
61 INDEX OF ATTACK

With notes:

* Use 20a0 for opponents, 2080 for allies
** - This is the stat that you're interested in. Values include
HP - 4160   
MP  -  4140
MaxHP - 4180   
MaxMP  - 4150
STR  - 4068   
MAG  - 4070
DEF - 4100   
MDEF  - 4110
DEX - 40a0   
LUCK  - 40a8
LEVEL  - 4048   
EVADE  - 4078
MagEVADE - 4268

*** - 85 looks for the target with the smallest value of the stat we're interested in; 84 looks for the target with the largest.

I think that with these, we more or less have the basic plugins for just about every function. Now the issue is to refine what we have (if you want), mark up the typical values for 'address of next plugin' in each plugin, and maybe add a few specialist plugins for specific circumstances / consider writing counter and death scripts.
Title: Welder: A little AI Template Project
Post by: nikfrozty on 2010-05-23 12:40:45
Sorry for butting in your topic but really curious in how you mod the PSX version. What do you actually mod in the PSX version?? The files inside the cd?? And after modding it can you still play it on the PlayStation or only in the emulator??
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-05-23 13:04:39
Quote
Insert Quote
Sorry for butting in your topic but really curios in how you mod the PSX version. What do you actually mod in the PSX version?? The files inside the cd?? And after modding it can you still play it on the PlayStation or only in the emulator??

Use CD Mage or similar software to extract files from ISO. Use CD Mage or similar software to reinsert without trying to reauthor ISO. Done.

Will play on original equipment if modified to run 'backup' CDs (a 'chipped' playstation).
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-06-06 17:12:02
Just had a thought - is my 'find enemies withOUT status Y' plugin broken? Think about it:

Code: [Select]
MAIN: IS OPP IN STATUS --?

13 1000
02 20a0 <2080 for allies>
00 <status flag address in memory - 4001 for Critical>
80
90
<this has just created a list of enemies and their critical statuses>
03 1000
60 01
45 <makes game look for enemies WITHOUT the status>
70 <address in AI of next plugin / footer>
12 2070
03 1000
82
90
91
61 <index of attack>

Looks fine, but the target is wrong. This will still find opponents who HAVE the status as a target, won't it? Would using the target code

12 2070
03 1000
37
82
90
91
61 <attack index...>

work?

Edit: Tried it, makes enemy hit everyone EXCEPT those in localvar. Back to the drawing board.

--

Use this plugin, then, for RandomOpp/AllyWith(Out)X

12    0300
02    20A0
00    40XX
80
60    01
40
82
90
02    0300
70    next
12    2070
02    0300
90
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-06-07 16:42:00
Looks like your logic is a bit backwards:

Just had a thought - is my 'find enemies withOUT status Y' plugin broken? Think about it:

Code: [Select]
MAIN: IS OPP IN STATUS --?

13 1000
02 20a0 <2080 for allies>
00 <status flag address in memory - 4001 for Critical>
80
90
<this has just created a list of enemies and their critical statuses>
03 1000
60 01
45 <makes game look for enemies WITHOUT the status>
70 <address in AI of next plugin / footer>
12 2070
03 1000
82
90
91
61 <index of attack>

Looks fine, but the target is wrong. This will still find opponents who HAVE the status as a target, won't it? Would using the target code

When you're using the 45, that is only going to check if there IS a target in critical condition. Look at the logic:

Code: [Select]
LocalVar:1000 <- AllOpponentMask.Status:NearDeath
If ( (LocalVar:1000 < 1) )
{
TargetMask <- RandomBit(LocalVar:1000)
POP(Previously loaded attack index)
...

Oops. This is only going to work if NO target has NearDeath status and you want to know if there ARE targets with this status. NOTing it seems like it would work. Indeed the If statement triggers since it's less than 0 (I guess variables are signed), but then the mask is backwards and indicating the ones that are not NearDeath.

Try this instead:

Code: [Select]
12  1000  <- don't make this a DWord, Target can only be a Word
02  20A0
00  4001  <- whatever status
80
60  00    <- does not have, make '1' to retrieve those that have the status
40
82        <- leave out to target all
90
02  1000
70  <end> <- if no such target   (If LocalVar:1000 <> 0)
12  2070
02  1000
90

Code: [Select]
LocalVar:1000 <- RandomBit( (AllOpponentMask.Status:NearDeath == 0) )
If (LocalVar:1000)
{
TargetMask <- LocalVar:1000
...

When in doubt with regards to targets with status, ask Prof. Eligor. He knows lots about it. ;)
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-06-07 20:21:13
Bit of a tangent, but I had another, similar logic issue with an original version of the header, through a misunderstanding of opcode documentation:

Quote
Popped:  top two values on stack
Pushed:  non-zero value if first pop is less than second pop.

I assumed the 'first pop' was pop1, the second pop2, imagining the stack was:

<top>
pop1
pop2
<bottom>

Actually, 'first pop' refers to pop2, 'second' to pop1.

The difference is important. Imagine

PUSH 0300
PUSH 0001
45 LESSTHAN

I assumed this would ask if 1 < 0300. It doesn't. It does the opposite.

Anyway.

Code: [Select]
12  1000  <- don't make this a DWord, Target can only be a Word
02  20A0
00  4001  <- whatever status
80
60  00    <- does not have, make '1' to retrieve those that have the status
40
82        <- leave out to target all
90
02  1000
70  <end> <- if no such target   (If LocalVar:1000 <> 0)
12  2070
02  1000
90

Resembles my revision:

Code: [Select]
02 2060
02 2140
80
61 AAAA
86
42
70 FFFF
12 1000
02 20A0
00 EEEE
80
90
03 1000
60 01
42
70 FFFF
12 2070
03 1000
82
90
91
61 AAAA

Why do I take the extra step? No idea. I can cut it.

Another thing I'd like to know is: exactly WHAT is on the stack when I push target data? What does this data look like? How is it structured?

I'm going to guess that, at the start of a battle, a 'nomenclature' is formed. This assigns a unit to each bit in a word, so there can be up to sixteen targets. Target data gives the bits of all targets relevant to a particular search. So,

If we have our bits referring to

Cloud / Tifa / JenovaSYNTH-Body / Cait Sith / JenovaSYNTH-LeftArm / JenovaSYNTH-RightArm

a request for 'all allies' (from Jenova's point of view) pushes

0 - 0 - 1 - 0 - 1 - 1

a request for 'all opps'

1 - 1 - 0 - 1 - 0 - 0

all active

1 - 1 - 1 - 1 - 1 - 1

etc.

Am I right?

Also, I believe Sir Eligor is well educated in 'elimination' too - targeting each member of the party, never hitting one more than others with his laser attack. I may have to ask him for a demonstration of this unusual skill.

My question is, though - why are the enemies so inconsistently written? Some are wonderfully complex and interesting bouts that last 30 seconds due to some local balance issue, others are little more than RAN MOD2 - if zero, use X, if one, use Y, where X and Y are only marginally different to one another anyhow. I wonder who wrote them - and how.
Title: Welder: A little AI Template Project
Post by: nfitc1 on 2010-06-08 13:54:35
Bit of a tangent, but I had another, similar logic issue with an original version of the header, through a misunderstanding of opcode documentation:

Quote
Popped:  top two values on stack
Pushed:  non-zero value if first pop is less than second pop.

I assumed the 'first pop' was pop1, the second pop2, imagining the stack was:

<top>
pop1
pop2
<bottom>

Actually, 'first pop' refers to pop2, 'second' to pop1.

The difference is important. Imagine

PUSH 0300
PUSH 0001
45 LESSTHAN

I assumed this would ask if 1 < 0300. It doesn't. It does the opposite.

I may have written it wrong in the documentation. I don't really know how it compares, but I'd assume it's top of the stack < next value in stack. If that's not the way it's working then it might be mislabeled and really be greater than or equal to or I got the pops out of order.

Anyway.

Code: [Select]
12  1000  <- don't make this a DWord, Target can only be a Word
02  20A0
00  4001  <- whatever status
80
60  00    <- does not have, make '1' to retrieve those that have the status
40
82        <- leave out to target all
90
02  1000
70  <end> <- if no such target   (If LocalVar:1000 <> 0)
12  2070
02  1000
90

Resembles my revision:

Code: [Select]
02 2060
02 2140
80
61 AAAA
86
42
70 FFFF
12 1000
02 20A0
00 EEEE
80
90
03 1000
60 01
42
70 FFFF
12 2070
03 1000
82
90
91
61 AAAA

Why do I take the extra step? No idea. I can cut it.

It looks good, but you don't need LocalVar:1000 to be a DWord. Just a Word is fine. Since it won't save space either way it doesn't matter.

Another thing I'd like to know is: exactly WHAT is on the stack when I push target data? What does this data look like? How is it structured?

I'm going to guess that, at the start of a battle, a 'nomenclature' is formed. This assigns a unit to each bit in a word, so there can be up to sixteen targets. Target data gives the bits of all targets relevant to a particular search. So,

If we have our bits referring to

Cloud / Tifa / JenovaSYNTH-Body / Cait Sith / JenovaSYNTH-LeftArm / JenovaSYNTH-RightArm

a request for 'all allies' (from Jenova's point of view) pushes

0 - 0 - 1 - 0 - 1 - 1

a request for 'all opps'

1 - 1 - 0 - 1 - 0 - 0

all active

1 - 1 - 1 - 1 - 1 - 1

etc.

Am I right?

Essentially yes. Looking through several of the enemy's AI (Eligor's and Emerald Weapon's in particular) you notice that they just push flagged bits into variables and treat those as pointers to actors (this is how I'm going to refer to "battle participants" from now on, that was a clunky way of saying it in the first place). When getting a mask of actors, there's just one Word associated with the formation that indicates if there's a participant or not. Bits 0-2 refer to player characters while bits 4-9 refer to enemies (which furthers my theory that there used to be four characters allowed in a battle. If you look at the formation data, the first enemy listed is bit 3, the second is bit 4, etc.

Code: [Select]
Bit     Actor
0       Player Character Position 1
1       Player Character Position 2
2       Player Character Position 3
3       Not Used
4       Enemy in slot 1
5       Enemy in slot 2
6       Enemy in slot 3
7       Enemy in slot 4
8       Enemy in slot 5
9       Enemy in slot 6
A       Not Used
B       Not Used
C       Not Used
D       Not Used
E       Not Used
F       Not Used

Also, I believe Sir Eligor is well educated in 'elimination' too - targeting each member of the party, never hitting one more than others with his laser attack. I may have to ask him for a demonstration of this unusual skill.

Looks to me that Eligor has a tendency to hit the middle Player Character more than the wingmates. I believe at this point in the game, unless you messed with the formation, Cloud would be in the center.

My question is, though - why are the enemies so inconsistently written? Some are wonderfully complex and interesting bouts that last 30 seconds due to some local balance issue, others are little more than RAN MOD2 - if zero, use X, if one, use Y, where X and Y are only marginally different to one another anyhow. I wonder who wrote them - and how.

I think it's obvious that you would want bosses and rare encounters to have more challenging and complex behaviors. Certain special battles like Grangalan and Midgar Zolom would need some complexity to them too. Other than that most enemies are only vicious based on their stats. It's better to make many simple enemies than to make fewer "intelligent" enemies. Gives the game more variety and the player rarely knows the difference in how complex the code is. I was shocked to see how complicated Eligor was. He didn't seem to me to be a very complicated enemy. In fact, his code could be dramatically reduced in size and still retain all its functionality. I don't know who wrote it, but I'd kind of like to slap him (or her or them) in the face(s) for being so complex about it.
Title: Welder: A little AI Template Project
Post by: Bosola on 2010-06-08 18:45:39
Quote
I think it's obvious that you would want bosses and rare encounters to have more challenging and complex behaviors. Certain special battles like Grangalan and Midgar Zolom would need some complexity to them too. Other than that most enemies are only vicious based on their stats. It's better to make many simple enemies than to make fewer "intelligent" enemies. Gives the game more variety and the player rarely knows the difference in how complex the code is. I was shocked to see how complicated Eligor was. He didn't seem to me to be a very complicated enemy. In fact, his code could be dramatically reduced in size and still retain all its functionality. I don't know who wrote it, but I'd kind of like to slap him (or her or them) in the face(s) for being so complex about it.

I disagree there, actually. One of my aims in modifying enemies for Rebirth has been to create a smaller number of longer, more interesting and varied fights. As is, you end up with lots of different enemies that play almost exactly the same. Compare with something like X, which cuts numbers and gives enemy differences far more impact. Actually, that seems to underline X full stop: pruning statuses, making those remaining more problematic; cutting elements, but putting them at the front of combat. In VIII, by contrast, there's a whole plethora of statuses that are each hardly ever invoked, and many not worth taking note of. Even IX has this problem (honestly, who actually cared about Cold and Trouble?).
Title: Re: Welder: A little AI Template Project
Post by: Akari on 2010-06-11 11:55:19
Bit of a tangent, but I had another, similar logic issue with an original version of the header, through a misunderstanding of opcode documentation:

Quote
Popped:  top two values on stack
Pushed:  non-zero value if first pop is less than second pop.

I assumed the 'first pop' was pop1, the second pop2, imagining the stack was:

<top>
pop1
pop2
<bottom>

Actually, 'first pop' refers to pop2, 'second' to pop1.

The difference is important. Imagine

PUSH 0300
PUSH 0001
45 LESSTHAN

I assumed this would ask if 1 < 0300. It doesn't. It does the opposite.

I may have written it wrong in the documentation. I don't really know how it compares, but I'd assume it's top of the stack < next value in stack. If that's not the way it's working then it might be mislabeled and really be greater than or equal to or I got the pops out of order.

Everything is right. 0x4X command work as follows:
1) Pop value from stack and store it to slot1
2) Pop value from stack and store it to slot0
3) Call function that does comparsion depending on X in 0x4X
Code: [Select]
switch ()
{
    case 0: return (slot0_value == slot1_value) ? 1 : 0;
    case 1: return (slot0_value != slot1_value) ? 1 : 0;
    case 2: return (slot0_value >= slot1_value) ? 1 : 0;
    case 3: return (slot0_value <= slot1_value) ? 1 : 0;
    case 4: return (slot0_value >  slot1_value) ? 1 : 0;
    case 5: return (slot0_value <  slot1_value) ? 1 : 0;
}
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-12 20:59:37
Thanks for the input, Akari.

This confirms my suspicions, then. Namely, my belief that opcode descriptions in PC's help are open to misreading. Consider the following case.

Quote

12   2070
02   20A0
82
90
60   20
61   0112 'Bite'
60   80
60   FF
42     'Greater than'
70   0021
12   2070
02   20A0
82
90
91
61   0113 'Tentacle'
92

The help file says that 42 returns a 1 if "the first value popped is greater than or equal to the second value popped". What is the 'first value'?

Convention suggests that the term 'first pop' refers to the FF, the 'second pop' to the 80. This would suggest that in this case, 42 would always yield a '1', prompting the game to pop the attack data and replace it with the index for 'Tentacle'. In battle, the enemy (actually a guard dog) always bites.

In practice, the help file uses 'first pop' to refer to the 'earliest', as it were. This creates ambiguity.

I think, as a rule, it's *always* better to refer to 'top' - 'bottom' / 'upper' - 'lower' when talking about stacks. Still, no harm done.


Edit:

I just don't know any more. I can't deal with this asm. It's impossible to follow, labarynthine and ambiguous. Why is my creature not acting in the way I anticipate? What keeps going wrong?
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-12 21:59:46
Double-post - some more investigation has suggested something else, which may be the issue.

Essentially, I've being trying to get my 'variable header' working - that's the header that uses data set by 'Leaders' to determine intelligence.

The issue seems to be that 'Leaders' aren't changing data in LocalVars as part of their Counter:Death scripts.

I had a guard hound use the following AI script:

00   0000
70   0024 (points to second 93 opcode)
93   "There is a ONE on stack"
72   0041 (jumps to 73 opcode)
93   "There was a ZERO on stack"
73

I then had an MP use the following INIT (pre-battle) script:

10   0000
60   00
90
73

And the following Counter-Death script:

12   0000
60   01
90
73

The hound, however, keeps shouting out "There was a ZERO on stack". This hints that though logic may be an issue, the real problem has to do with this Counter script.

I may have to re-write the variable header to actively look out for allies of a certain 'index', and then alter a variable 'IQ' internally. I wish someone had mentioned this issue beforehand.
Title: Re: Welder: A little AI Template Project
Post by: Terence Fergusson on 2010-06-13 23:25:01
The issue seems to be that 'Leaders' aren't changing data in LocalVars as part of their Counter:Death scripts.

No, the real problem is that LocalVars are *specific to the object that uses them*.  You can't change Object 1's LocalVar:0000, and expect to read the changed value out of Object 2's LocalVar:0000.  They're two completely different variables.

Look at Warning Board's script for examples of how to use the Battle Player Block to use the specially set aside variables in the Battle Player Block to change the behavior of other enemies ([41A0], [41C0], [41E0] and [4200] at least are set aside for this).

Also look at Manhole's script on how to use *Battle* Variables for storing information ([2180]/[2190]/[21A0] at least are set aside for this), which any enemy in battle can use.

Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-14 17:28:47
This is very new information to me. And, I think, to other board members - more than once, I've heard people warn each other not to let different creatures use the same LocalVar numbers. I suppose the assumption was that 'Local' referred to a battle-only namespace, as opposed to global vars.

I'll play with the battlevars right away. I think this is the 643,543rd time I've found your apparently limitless knowledge of VII useful, so thanks... again.

Oh, I'll have to properly confirm it, but testing so far suggests that the stack is cleared between script calls. For instance,

INIT SCRIPT:
60 20 : PUSH byte 20
60 22 : PUSH byte 22
73 : end

MAIN:
92 : attack
73 : end

...does not force Unknown3 to use Bolt2. Or anything, for that matter. Pretty sure you or Akari wanted confirmation on it.
Title: Re: Welder: A little AI Template Project
Post by: Akari on 2010-06-14 19:41:37
This is very new information to me. And, I think, to other board members - more than once, I've heard people warn each other not to let different creatures use the same LocalVar numbers. I suppose the assumption was that 'Local' referred to a battle-only namespace, as opposed to global vars.

I'll play with the battlevars right away. I think this is the 643,543rd time I've found your apparently limitless knowledge of VII useful, so thanks... again.

Oh, I'll have to properly confirm it, but testing so far suggests that the stack is cleared between script calls. For instance,

INIT SCRIPT:
60 20 : PUSH byte 20
60 22 : PUSH byte 22
73 : end

MAIN:
92 : attack
73 : end

...does not force Unknown3 to use Bolt2. Or anything, for that matter. Pretty sure you or Akari wanted confirmation on it.

Stack is reseted before script call. If you want to store value you need to do this in some variable.
Why do you even think about stack. It's now that hard to create some abstraction to be closer to original code.

Example output from my dumper:

Code: [Select]
script 1:
0x0000 [0x0004 + 0x00] = b(0x00);
0x0006 [0x0008 + 0x00] = b(0x00);
0x000c [0x0000 + 0x00] = b(0x00);
0x0012 JumpIfNotAllFriendsInFrontRow(0x0029);
0x001f [0x0008 + 0x00] = b(b[0x0008 + 0x00] & 0x01);
0x0029 JumpIfNotAllFriendsInBackRow(0x0040);
0x0036 [0x0008 + 0x00] = b(b[0x0008 + 0x00] & 0x02);
0x0040 JumpIfNotAllOpponentsInFrontRow(0x0057);
0x004d [0x0004 + 0x00] = b(b[0x0004 + 0x00] & 0x01);
0x0057 JumpIfNotAllOpponentsInBackRow(0x006e);
0x0064 [0x0004 + 0x00] = b(b[0x0004 + 0x00] & 0x02);
0x006e JumpIfSelfNotInFrontRow(0x0084);
0x007b [0x0000 + 0x00] = b(0x00);
0x0081 0x72 JumpTo(0x008a)
0x0084 [0x0000 + 0x00] = b(0x01);
0x008a JumpIfRandomNot(1/0x03)(0x00c7);
0x0092 JumpIf((b[0x0004 + 0x00] & 0x02) != 0x02)(0x00b0);
0x009e SetRandomOpponentInBackRowToAttack();
0x00ad 0x72 JumpTo(0x00bd)
0x00b0 SetRandomOpponentWithLowestCurrentHP();
0x00bd [0x000c + 0x00] = h(0x0113);
0x00c4 0x72 JumpTo(0x00f9)
0x00c7 JumpIf((b[0x0004 + 0x00] & 0x01) != 0x01)(0x00e5);
0x00d3 SetRandomOpponentInFrontRowToAttack();
0x00e2 0x72 JumpTo(0x00f2)
0x00e5 SetRandomOpponentWithLowestCurrentHP();
0x00f2 [0x000c + 0x00] = h(0x0112);
0x00f9 RunCommand(0x20, h[0x000c + 0x00]);
0x00ff 0x73 FinishScript()
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-14 20:23:29
Stack is reseted before script call. If you want to store value you need to do this in some variable.

Yeah, but there was some uncertainty over whether or not the stack got wiped. I just wanted to confirm the assumption that it gets cleared.

Why do you even think about stack. It's now that hard to create some abstraction to be closer to original code.

Example output from my dumper:

Code: [Select]
script 1:
0x0000 [0x0004 + 0x00] = b(0x00);
0x0006 [0x0008 + 0x00] = b(0x00);
0x000c [0x0000 + 0x00] = b(0x00);
0x0012 JumpIfNotAllFriendsInFrontRow(0x0029);
0x001f [0x0008 + 0x00] = b(b[0x0008 + 0x00] & 0x01);
0x0029 JumpIfNotAllFriendsInBackRow(0x0040);
0x0036 [0x0008 + 0x00] = b(b[0x0008 + 0x00] & 0x02);
0x0040 JumpIfNotAllOpponentsInFrontRow(0x0057);
0x004d [0x0004 + 0x00] = b(b[0x0004 + 0x00] & 0x01);
0x0057 JumpIfNotAllOpponentsInBackRow(0x006e);
0x0064 [0x0004 + 0x00] = b(b[0x0004 + 0x00] & 0x02);
0x006e JumpIfSelfNotInFrontRow(0x0084);
0x007b [0x0000 + 0x00] = b(0x00);
0x0081 0x72 JumpTo(0x008a)
0x0084 [0x0000 + 0x00] = b(0x01);
0x008a JumpIfRandomNot(1/0x03)(0x00c7);
0x0092 JumpIf((b[0x0004 + 0x00] & 0x02) != 0x02)(0x00b0);
0x009e SetRandomOpponentInBackRowToAttack();
0x00ad 0x72 JumpTo(0x00bd)
0x00b0 SetRandomOpponentWithLowestCurrentHP();
0x00bd [0x000c + 0x00] = h(0x0113);
0x00c4 0x72 JumpTo(0x00f9)
0x00c7 JumpIf((b[0x0004 + 0x00] & 0x01) != 0x01)(0x00e5);
0x00d3 SetRandomOpponentInFrontRowToAttack();
0x00e2 0x72 JumpTo(0x00f2)
0x00e5 SetRandomOpponentWithLowestCurrentHP();
0x00f2 [0x000c + 0x00] = h(0x0112);
0x00f9 RunCommand(0x20, h[0x000c + 0x00]);
0x00ff 0x73 FinishScript()

...This is a good opportunity to ask more about the AI code and the battle engine. You seem to be suggesting that the AI code is not just some assembly-like scripting language, but was compiled from some other source. What sort of source? If compiled, could some of the 'oddities' of certain scripts (see Ultimate Weapon's HP code) be down to compiler issues? And what *actually* happens to the AI code every frame? Does it just push the battle engine around as a scripting language, or does it get transformed into some intermediate form, like bytecode?
Title: Re: Welder: A little AI Template Project
Post by: Akari on 2010-06-15 07:32:41
Why do you even think about stack. It's now that hard to create some abstraction to be closer to original code.

Example output from my dumper:

Code: [Select]
script 1:
0x0000 [0x0004 + 0x00] = b(0x00);
0x0006 [0x0008 + 0x00] = b(0x00);
0x000c [0x0000 + 0x00] = b(0x00);
0x0012 JumpIfNotAllFriendsInFrontRow(0x0029);
0x001f [0x0008 + 0x00] = b(b[0x0008 + 0x00] & 0x01);
0x0029 JumpIfNotAllFriendsInBackRow(0x0040);
0x0036 [0x0008 + 0x00] = b(b[0x0008 + 0x00] & 0x02);
0x0040 JumpIfNotAllOpponentsInFrontRow(0x0057);
0x004d [0x0004 + 0x00] = b(b[0x0004 + 0x00] & 0x01);
0x0057 JumpIfNotAllOpponentsInBackRow(0x006e);
0x0064 [0x0004 + 0x00] = b(b[0x0004 + 0x00] & 0x02);
0x006e JumpIfSelfNotInFrontRow(0x0084);
0x007b [0x0000 + 0x00] = b(0x00);
0x0081 0x72 JumpTo(0x008a)
0x0084 [0x0000 + 0x00] = b(0x01);
0x008a JumpIfRandomNot(1/0x03)(0x00c7);
0x0092 JumpIf((b[0x0004 + 0x00] & 0x02) != 0x02)(0x00b0);
0x009e SetRandomOpponentInBackRowToAttack();
0x00ad 0x72 JumpTo(0x00bd)
0x00b0 SetRandomOpponentWithLowestCurrentHP();
0x00bd [0x000c + 0x00] = h(0x0113);
0x00c4 0x72 JumpTo(0x00f9)
0x00c7 JumpIf((b[0x0004 + 0x00] & 0x01) != 0x01)(0x00e5);
0x00d3 SetRandomOpponentInFrontRowToAttack();
0x00e2 0x72 JumpTo(0x00f2)
0x00e5 SetRandomOpponentWithLowestCurrentHP();
0x00f2 [0x000c + 0x00] = h(0x0112);
0x00f9 RunCommand(0x20, h[0x000c + 0x00]);
0x00ff 0x73 FinishScript()

...This is a good opportunity to ask more about the AI code and the battle engine. You seem to be suggesting that the AI code is not just some assembly-like scripting language, but was compiled from some other source. What sort of source? If compiled, could some of the 'oddities' of certain scripts (see Ultimate Weapon's HP code) be down to compiler issues? And what *actually* happens to the AI code every frame? Does it just push the battle engine around as a scripting language, or does it get transformed into some intermediate form, like bytecode?

Of course it was normal script that was later compiled to bytecode. Using this instructions you can implement almost all known constructions in languages. For example Lua language compile


Code: [Select]
y = 5
print(y)

into

Code: [Select]
LOADK           0 1     ; 5
SETGLOBAL       0 0     ; y
GETGLOBAL       0 2     ; print
GETGLOBAL       1 0     ; y
CALL            0 2 1
RETURN          0 1 0

Let's write decompiler already =)

And what stranges in Ultima Weapon's script you talking about?

Ai code already bytecode that executed when it's called (when timer filled or when some conditions met).
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-15 14:53:21
Of course it was normal script that was later compiled to bytecode.

So it's bytecode, huh? Do portions of the AI scripting code have 1:1 relations to the binary they eventually yield? Is it bytecode in the sense of 'generalized, partially abstracted ASM'? That surprises me, actually. Especially considering how complex things like calling for targets would be on the machine level.

Quote
And what stranges in Ultima Weapon's script you talking about?

Ai code already bytecode that executed when it's called (when timer filled or when some conditions met).

I don't have access to the code right now, but IIRC, Ultima Weapon uses a very verbose method of storing its HP in a GlobalVar.
Title: Re: Welder: A little AI Template Project
Post by: Terence Fergusson on 2010-06-16 01:40:53
This is very new information to me. And, I think, to other board members - more than once, I've heard people warn each other not to let different creatures use the same LocalVar numbers. I suppose the assumption was that 'Local' referred to a battle-only namespace, as opposed to global vars.

Well, my very first post on the subject of AI scripting all those years ago did show where each set of variables were mapped to: such that 0000-1FFF and 4000+ were mapped to blocks that were unique to each object, but any call to 4000 would retrieve *all* blocks rather than just the object's own block.

Which brings up another thing I've noticed in this thread which is ill-advised: don't use LocalVar 0400 or higher.  Only 128 bytes are given to each enemy, so anything higher than that will start overwriting other data.

So it's bytecode, huh? Do portions of the AI scripting code have 1:1 relations to the binary they eventually yield? Is it bytecode in the sense of 'generalized, partially abstracted ASM'? That surprises me, actually. Especially considering how complex things like calling for targets would be on the machine level.

Just because something is bytecode doesn't mean it's machine code.  It's just a form of code that can be quickly read by an interpreter (in that the operands are all single bytes).  Game designers don't program enemies, rooms or other scripts in bytecode: they use some higher level custom language (usually designed specifically for the game), which they then compile down into an easier format for the game to read.  The resulting code doesn't take up very much memory and is relatively fast to run compared to translating human readable code in real-time (which would be both slower and take up more space).

I don't have access to the code right now, but IIRC, Ultima Weapon uses a very verbose method of storing its HP in a GlobalVar.

It's only verbose because AccessGlobalVar can only read and write Bytes.  So storing what requires at least 3 bytes is going to require a bit of maths.  There's obviously going to be some compiler artifacts around in the code, which is why a lot of the enemy scripts can be rewritten to be more efficient in bytecode, but some of it is just working around the limitations of the system.
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-16 09:42:35
Ah, so it's a bytecode in the strict sense of 'a code with one-byte opcodes'. I was thinking in terms of the 'bytecode' people refer to when talking about Java etc. It is, technically speaking, the same principle - something between human-readable code and actual machine action that can easily be dealt with by an interpreter.

Quote
Which brings up another thing I've noticed in this thread which is ill-advised: don't use LocalVar 0400 or higher.  Only 128 bytes are given to each enemy, so anything higher than that will start overwriting other data.

Asking a bit much, but does anyone actually have a complete memory map? I'm wondering what we get after 0400.

I think at some point I'd like to get into this further, perhaps by using disassemblers. How did you get into reverse engineering yourself?
Title: Re: Welder: A little AI Template Project
Post by: Akari on 2010-06-16 12:38:36
Asking a bit much, but does anyone actually have a complete memory map? I'm wondering what we get after 0400.

http://q-gears.svn.sourceforge.net/viewvc/q-gears/trunk/reversing/ffvii/ffvii_battle/ffvii_address.txt?revision=505&view=markup

What are you looking is at

800f87f0 temp variable unit data structure. (size 0x80). (address 0x0000).
800f83a4 battle global structure. (address 0x2000).
800f83e0 unit data structure. (size 0x68). (address 0x4000).
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-16 18:22:25
Interesting stuff. What's the actual range of values that can be written from within the AI script? Could AI code, for instance, clear the battle queue (which seems to come shortly after the 2000 addresses)?
Title: Re: Welder: A little AI Template Project
Post by: Akari on 2010-06-16 18:32:48
Interesting stuff. What's the actual range of values that can be written from within the AI script? Could AI code, for instance, clear the battle queue (which seems to come shortly after the 2000 addresses)?

Even if it can - doesn't mean you should.
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-06-16 18:56:04
Of course! I'm just curious how values 0-FFFF are assigned in the memory map.
Title: Re: Welder: A little AI Template Project
Post by: Bosola on 2010-07-07 21:10:43
To make a little clearer: if you want variable 'IQ' levels, you need to write the custom IQ in AI setups / death counters to address 21A0.

It looks to me as though the following addresses can be used for BattleVars, though I may need to test further:

21a0
21a8
21b0
21b8
21c0-c8

When I'm a little more inclined, I'll probably clean all the material up and link to it from the initial post.