[FF7] AI Template Project - Welder

  • Thread starter Thread starter Bosola
  • Start date Start date
Status
Not open for further replies.
B

Bosola

Guest
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.

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]
Code:
12 207002 20a0829060 2061 index of the 'dumb' attack8160 X3460 Y4370 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

  • Header writes data for primary attack
  • Rolls a dice to decide whether it will use this attack, or consult the plugins - which might but don't have to change the attack to something more 'tactically' suitable
  • Performs whatever attack is left on the stack


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
 
Last edited:
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.
 
...
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.
 
Last edited:
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.
 
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)


//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.

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.

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?

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.
 
Last edited:
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]
Code:
//headerwhile(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
 
Last edited:
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).
 
Last edited:
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]
Code:
8160  013570  001612  207002  20A000  40268060  0140829072  XXXX12  207002  20A000  40268060  0040829072  XXXX
Code: [Select]
Code:
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]
Code:
12  207002  20A000  4026808160  013570  001760  004072  001A60  01408290
Code: [Select]
Code:
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]
Code:
12  2070     Stack: &TargetMask02  20A0     Stack: &TargetMask, AllActiveEnemies00  4026     Stack: &TargetMask, AllActiveEnemies, BackRow80           Stack: &TargetMask, AllActiveEnemies.BackRow81           Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM60  01       Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM, 135           Stack: &TargetMask, AllActiveEnemies.BackRow, (RANDOM AND 1)70  0017     Stack: &TargetMask, AllActiveEnemies.BackRow60  00       Stack: &TargetMask, AllActiveEnemies.BackRow, 040           Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)72  001A     Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)60  01       Stack: &TargetMask, (AllActiveEnemies.BackRow == 0), 140           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]
Code:
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. ;)
 
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]
Code:
8160  013570  001612  207002  20A000  40268060  0140829072  XXXX12  207002  20A000  40268060  0040829072  XXXX
Code: [Select]
Code:
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]
Code:
12  207002  20A000  4026808160  013570  001760  004072  001A60  01408290
Code: [Select]
Code:
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]
Code:
12  2070     Stack: &TargetMask02  20A0     Stack: &TargetMask, AllActiveEnemies00  4026     Stack: &TargetMask, AllActiveEnemies, BackRow80           Stack: &TargetMask, AllActiveEnemies.BackRow81           Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM60  01       Stack: &TargetMask, AllActiveEnemies.BackRow, RANDOM, 135           Stack: &TargetMask, AllActiveEnemies.BackRow, (RANDOM AND 1)70  0017     Stack: &TargetMask, AllActiveEnemies.BackRow60  00       Stack: &TargetMask, AllActiveEnemies.BackRow, 040           Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)72  001A     Stack: &TargetMask, (AllActiveEnemies.BackRow == 0)60  01       Stack: &TargetMask, (AllActiveEnemies.BackRow == 0), 140           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]
Code:
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?
 
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]
Code:
12 207002 20a0829060 2061 index of the 'dumb' attack8160 X3460 Y4270 ZZZZ9191   <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]
Code:
9200  100070  XXXX (skip the next byte to the ending)9173
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
 
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'.

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:
 
Last edited:
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]
Code:
12 207002 20a0829060 2061 index of the 'dumb' attack8101  10203401  10404270 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.
 
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
 
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.

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

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.
 
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).

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.
 
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.
 
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.

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.
 
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
 
To be honest, you could probably do it with just an excel spreadsheet and a macro.
 
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. :(
 
Status
Not open for further replies.
Back
Top