Author Topic: [FFVII] Enemy AI Script questions  (Read 29974 times)

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #25 on: 2019-03-09 17:03:53 »
Is there any reason that the Sample:H0512-opt stores the element of thelast performed action (2120)?

If one of the Opt's dies then it sets the main boss's "Angry" var (41A0) to 1, and also stores the "Elements of last performed action" in the main Boss's "AllyAngry" var (41C0). When Sample:H0152' script runs it checks to see if it's Angry Var is 1, which it will be if one of the opt's have died, and then stores the element of the last action performed (by the Opt/on the Opt?) into H0512's 2120, and then does absolutely nothing with it. I'm guessing initially they had intended for his attack pattern to change according to the Element the Player was using...

Also, what is 2120 specifically? The Wiki says"Elements of last performed action." is that the last action performed *by* the actor; last action performed *on* the actor; or the last action in general by any actor?
« Last Edit: 2019-03-09 17:05:46 by DynamixDJ »

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #26 on: 2019-03-10 10:02:24 »
Do we think that the Hundred/Heli Gunner battle should have had the Still More Fighting theme play? It seems a little anti-climatic to have the normal Fighting theme play out.

Also, the EXP AP and GIL for Hundred Gunner are not earned, I'm guessing this is because of the fact that the game uses "Next Arenas" for the battle? Or is it because the battle doesn't end upon killing that enemy, the same as other enemies that "transform"? In fact, thinking on it, what exactly is controlling the deactivation of Spoils earned by Enemies after a transformation? Why exactly is it that the Hell House (for example) does not give spoils for killing both versions?

Bosola

  • Fire hazard!
  • *
  • Posts: 1752
    • View Profile
    • My YouTube Channel
Re: [FFVII] Enemy AI Script questions
« Reply #27 on: 2019-03-11 00:45:59 »
Quote
Do we think that the Hundred/Heli Gunner battle should have had the Still More Fighting theme play?

I think the Cloud / Rufus fight might have lost something if it simply repeated the music from the Heli Gunner.

Quote
Also, the EXP AP and GIL for Hundred Gunner are not earned, I'm guessing this is because of the fact that the game uses "Next Arenas" for the battle? Or is it because the battle doesn't end upon killing that enemy, the same as other enemies that "transform"? In fact, thinking on it, what exactly is controlling the deactivation of Spoils earned by Enemies after a transformation? Why exactly is it that the Hell House (for example) does not give spoils for killing both versions?

There are quite a few questions here. It's been a long time since I looked into FF7's scripts (5+ years), so my memory is quite hazy, but I'll do my best to answer.

Hundred Gunner / Heli Gunner don't work the way Battle Arena does. The arena is implemented via field scripting: field code can start battles and pass a bit flag to enable various battle mechanics features, one of which is a continuous multi-formation battle with player prompts every victory ("Off course!"). By contrast the Gunners (and things like Hell House) are implemented via battle AI scripts which enable / disable units in the same formation and copy over a struct of associated data (so that the game can simulate continuity in terms of statuses, stats multipliers - etc)

I don't remember researching how exp is gathered at the end of a battle. I don't think the Exp screen is actually part of the menu modules, but I may be wrong. If it is not a menu then it will presumably be powered by BATTLE.X. Looking at Proud Clod, there seem to be some actor flags (e.g. 4022/4024) that typically get set when those 'transforming' enemies die - my guess is that the battle engine uses these to determine whether a unit should be included in the exp calculation. I'd suggest should sniff around Battle.x to see what functions get called at the end of a battle.

Curiously enough, you can actually alter exp / ap / gil rewards for an enemy with AI scripts directly, but I'm not sure if any enemies actually do so.
« Last Edit: 2019-03-11 16:40:15 by Bosola »

Bosola

  • Fire hazard!
  • *
  • Posts: 1752
    • View Profile
    • My YouTube Channel
Re: [FFVII] Enemy AI Script questions
« Reply #28 on: 2019-03-11 00:53:41 »
Quote
Also, what is 2120 specifically? The Wiki says"Elements of last performed action." is that the last action performed *by* the actor; last action performed *on* the actor; or the last action in general by any actor?

The latter. I can't remember if it's set by the time the pre-action script runs, or you can only reliably read the element of incoming attacks in counter scripts. It can be handy in mods when implementing things like an 'oil' status.

Quote
In my opinion, having the Soldier:3rd only attack players physically when they do not have sleep, or only attacking them magically when they do have sleep is a clever AI design. It's the type of thing that I want to focus on when I write my mod, instead of just 50% do this 50% do that. The Solder:3rd will never cure a character of sleep; you have to cure them yourself by attacking them or wait it out.

I've done this kind of thing before. It's not that easy to balance. Enemies that hone in your weaknesses and exploit status effects sound interesting but aren't always fun to play against. Remember how FFIX enemies always seemed to target party members who you'd just Phoenix Downed? It's quite cool to see now and again but the player needs options to adapt and counter the strategy mid-battle, or it just feels like the AI is cheesing.
« Last Edit: 2019-03-11 01:00:50 by Bosola »

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #29 on: 2019-03-11 09:49:11 »
Thanks for clearing all that up!

In regards to the flag that disables the EXP etc earned at the end of a battle, I had assumed also that it was something to do with the 4020-4024 flags, seeing as they are present in each instance that we see an enemy remove itself from battle.

So, I've gone back to the Warning Board. With the default vanilla script the machine gun does not pop out once you've killed the Laser Cannon, and vice versa. You do get the 5 EXP for killing one of them, and none of the 4020- 4024 flags get set upon death (although the Warning Board does toggle it's counterparts 4020-4020 when it dies, which is why the battle can end without killing the little ones)

In my custom script I've toggled the 4020 flag (SelfActive) so it sets to 0 as a Death Counter, and doing this enables the Machine Gun to pop out once you've killed the Laser Cannon and vice versa (and it looks kinda cool; I'm convinced this was how it was intended). Anyway, with 4020 set to 0 after death the MG + LC did reward me with5 EXP each.

So, with the 4020 still being set to 0 upon death, I added another line of script, setting 4022 (as well as the 4020) to 0 at the end of battle, so that the "SelfActive" and "NeededToEndBattle" flags were off. I still got the extra 10 EXP.

I then changed the 4022 value to 4024, so instead of disabling the "NeededToEndBattle" flag, I disabled the "AiScriptActive" flag. I still got the extra 10 EXP.

So, I'm left still unsure as to what exactly is disabling the end of battle rewards. I'm aware in the back of my mind that there is an easy fix by just assigning Heli Gunner's EXP AP and GIL value to match that what it should have been if Hundred Gunner did leave rewards, but I'd rather identify the problem instead of creating work-arounds. I'm pretty sure there are a few examples of the Spoil values being toggled manually in-battle; take the Trickplay for example (increases gil earned by 800 with every use of Gold Mountain).

Also, in my post, I mentioned "Next Arenas". I said that by default without thinking (recently been working on Battle Square formations), I meant "Next Battle", which is a different thing used in the Hundred/Heli and Hojo battles only.
« Last Edit: 2019-03-11 10:04:09 by DynamixDJ »

Kaldarasha

  • *
  • Posts: 2449
  • Prince of Model Editing
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #30 on: 2019-03-11 15:04:31 »
Isn't there a command in the field script which disables the battle win?

Bosola

  • Fire hazard!
  • *
  • Posts: 1752
    • View Profile
    • My YouTube Channel
Re: [FFVII] Enemy AI Script questions
« Reply #31 on: 2019-03-11 16:49:57 »
So, I'm left still unsure as to what exactly is disabling the end of battle rewards. I'm aware in the back of my mind that there is an easy fix by just assigning Heli Gunner's EXP AP and GIL value to match that what it should have been if Hundred Gunner did leave rewards, but I'd rather identify the problem instead of creating work-arounds. I'm pretty sure there are a few examples of the Spoil values being toggled manually in-battle; take the Trickplay for example (increases gil earned by 800 with every use of Gold Mountain).

I think your best bet is to just to fire up an emulator with a debugger, replay the end of a battle (e.g. using saved states) and just trace where the exp total comes from.

Personally I'd be tempted to just modify the exp reward values in the unit struct directly from a post-death script, but if you need a solution that's easy to invoke and 'robust' then the ideal would be to find an unused status flag and patch BATTLE.X to use a custom exp calculation function with the help of e.g. ARMIPS. You can then distribute the patch separately as a basis for other mods.
« Last Edit: 2019-03-11 17:06:14 by Bosola »

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #32 on: 2019-03-11 17:30:20 »
Isn't there a command in the field script which disables the battle win?
Yes there is, but that's a separate thing which doesn't get used for enemies such as the Hell House, Aero Combatant, Mighty Grunt, and other rnd encounters where battle spoils are disabled (that opcode can only be used for forced encounters).

I think your best bet is to just to fire up an emulator with a debugger, replay the end of a battle (e.g. using saved states) and just trace where the exp total comes from.

That's a really good idea, I hadn't thought of doing that. I have started mapping out a few thing, such as Current MP of the three 6 potential enemies, so I have an offset to work from, thanks! That's a fantastic suggestion! I did try to find the address for the ATB gauge of the enemy, but I had no success. That would be great to map out...

Anyway, I'm having one of those forget everything you know and start again moments. I was under the distinct impression that an Attack that uses split damage does so based on the formula it uses.

This, apparently, isn't the case.

I've always noted that a Magical Attack that uses the formula 22 (amongst others) will have the "Split Damage" flag ON when attacking all opponents. Nope! I've just learned (and tested) that Motor Ball's Rolling Fire [1AA] Attack attacks all targets, and does NOT use split damage; it was hitting my party for the full 185 - 205, instead of 120-140.

The same for Matra Magic. This uses 22, yet it does not deal split damage. Why is this? Is it gonna be one of those obscure things such as whether an attack is coverable, or visible, or set as a Berserk Attack, or one of the other weird things that I'm still not 100% where to look to find out (AB files I think, although I don't know what/where that is.....)

Bosola

  • Fire hazard!
  • *
  • Posts: 1752
    • View Profile
    • My YouTube Channel
Re: [FFVII] Enemy AI Script questions
« Reply #33 on: 2019-03-11 19:54:22 »
There's a lot of bugs like that. For instance: all enemy attacks do half damage at long range, unless they're actual magic commands. The more one looks into FFVII's battle system the more rushed and incomplete it appears. Fights are fun but are only a shadow of what they could have been, had certain ideas been developed further.

Look at all the obscure status effects and strange damage formulae. Look at how big and complicated Eligor's AI script is - when 80% of players will miss him completely. Read through the nuances of how the MPs react to Cloud's party position. It's clear Square had huge ambitions for VII's battle scenarios but not nearly enough time to realise them.
« Last Edit: 2019-03-11 19:58:20 by Bosola »

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #34 on: 2019-03-11 19:55:05 »
An extract from TFerguson's Mechanics FAQ:

You have a chance at winning an item if an enemy is killed and you finish the
battle successfully.  In addition, the enemy cannot have been stolen from and
must not have been killed by Remove.  Finally, the battle must finish with
the default ending screen that awards EXP/AP/Gil/Items.  Battle Square
battles do not do this, and multi-part battles only count the final battle in
the series.  (Example: The Hundred Gunner and Heli Gunner battle only awards
the Heli Gunner part of the battle


Guess that solves that query then :) Still puddling over the Split Damage thing though

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #35 on: 2019-03-11 19:57:39 »
There's a lot of bugs like that. For instance: all enemy attacks do half damage at long range, unless they're actual magic commands. The more one looks into FFVII's battle system the more rushed and incomplete it appears. Fights are fun but are only a shadow of what they could have been, had certain ideas been developed further.

Look at all the unused statuses and strange one-time attack effects. Look at how big and complicated Eligor's AI script is - when 80% of players will miss him completely. Read through the nuances of how the MPs react to Cloud's party position. It's clear Square had huge ambitions for VII's battle scenarios but not nearly enough time to realise them.

So you're saying it could be a bug then? I'm curious to know what causes it, it's important for my guide that I get everything down right, and not leaving things to guesswork

Bosola

  • Fire hazard!
  • *
  • Posts: 1752
    • View Profile
    • My YouTube Channel
Re: [FFVII] Enemy AI Script questions
« Reply #36 on: 2019-03-11 20:06:22 »
In as much as it was probably a programming oversight. In fairness FFVII's battles are actually quite hard to manually test - would most QAs know that an attack was supposed to hit for 210 damage rather than 290? Or that a section of the AI script never fired? It's fairly opaque.

I'm not sure how modern RPG projects program battle scenarios, but I expect there's much better tooling these days. Even just being able to run the AI scripts in a test framework would have been a massive boon, but automated testing wasn't on the radar of anyone in 1997 outside of finance, defence or academia.

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: [FFVII] Enemy AI Script questions
« Reply #37 on: 2019-03-12 02:41:37 »
2120 is elements in the last performed action. It doesn’t matter who the actor was.

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #38 on: 2019-03-16 16:35:40 »
What does (2040) do? It has something to do with the last Physical Attacker. It's seen on the Elfadunk (and possibly others) as part of the main script, it seems to remove the targets set as the last physical attacker

Also, what does 41E0 do? It seems to be a "GroupFlag" which triggers the same flag for all of the Actor's allies, seen in the Mandragora's script. I've called this "GrpCustomAtt" for now until I've seen it used more.
« Last Edit: 2019-03-17 12:21:50 by DynamixDJ »

Kaldarasha

  • *
  • Posts: 2449
  • Prince of Model Editing
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #39 on: 2019-03-17 21:46:47 »
I dunno if it helps you, but an early psx demo has a slightly different battle module. First thing: the party m members go into a ready-to-execute-comand-position like in ff8. The other thing is that everyone is facing the last attacker or the last character who has done an action on him/her (like casting cure).
It just wouldn't surprise me if there are leftover code or fast solutions like deactivating code.

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #40 on: 2019-03-18 12:48:43 »
Yeah, I've seen quite a lot of instances with dummied out script, particularly in instances where it's obvious that the scrip has been C+Ped from another enemy. The Elfadunk for example checks to see if the Battle Type is type 2 (back attack), but then does nothing with that check. The MP, Guard Hound, Mono Drive and Grunt all have disabled script; I get that it's easier and quicker just to overrite certain script than it is to tidy it, especially with the flevel.lgp, take Sector 7 as an example. Leaving in the dummied out script means that they can easily modify the scene if they wish to change it at a later dare.

@NFITC1 I need your help with this, the qhimm wiki states that formula 22 uses split damage, however, after going through TF's FAQ I can see that the majority of actions that use the regular damage formula 22 all have No Split damage when attacking all targets.

I was ready to change my database so that all 22 attacks use Split Damage, until I came to Motor Ball. Two of his attacks targets all opponents - Twin Burner and Rolling Fire. Both use 22, however, Twin Burner has split damage and Rolling fire has no split damage. Tried and tested.

So yeah, I'm banging my head against the wall with this. Why is it that an "All Opponents" Attack that uses 22 such as Beta will have No Split, and another attack that uses 22 such as Twin Burner will have split damage?

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: [FFVII] Enemy AI Script questions
« Reply #41 on: 2019-03-18 14:54:54 »
I’ll have to double check some of this, but IIRC 41E0 is the actor’s MMP. Maybe the mandragora use it as a sort of shared pool? I dunno.

As for the 22 having split damage, it certainly does. That might be the magic calf for either most magic damage actions the player has (fire, ice, etc) or magic healing (cure, et al). Either way, there is a flag on actions that can set to bypass the split damage calculations. Most likely the ones TF has listed as 22 and non-split have this override enabled.

I’ll have to check on 2040, though. It doesn’t seem familiar.

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #42 on: 2019-03-18 15:24:54 »
Ah, so there's a separate flag that can override the Split Damage, I'm guessing this is something similar to the fact that the M/Barrier check can also be overriden?

I have Actor's MMP as 4150 btw

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: [FFVII] Enemy AI Script questions
« Reply #43 on: 2019-03-18 16:18:00 »
Then 4160 is current HP. I’m transposing numbers in my head... I don’t remember what 41E0 is.

EDIT:
It looks like neither 41E0 or 2040 are set by the battle engine.

For Mandragora's part, it look like it's using 41E0 to direct AI for all the Mandragoras in the battle. If one is attacked with either physical or magical damage, then the next attacking mandragora will counter with a specific action based on the type and then declare to the others "I've avenged us".

As for 2040. The only enemies that use it are Bottomswell, Carry Armor/Left Arm/Right Arm, Elfadunk, Roulette Cannon, SOLDIER:2nd, Turks:Rude. Roulette Cannon and SOLDIER:2nd can appear in the same formation so they might be able to shed some light as to what the intended function is.
« Last Edit: 2019-03-18 22:05:22 by NFITC1 »

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #44 on: 2019-03-19 13:01:27 »
Thanks for looking, I'll keep my eyes peeled when I reach those enemies.

I was having trouble with the Zemzelett's use of [2120] in its general counter script:

If (BattleAddr(&ElementsOfLastAction) AND 3)

I was in the frame of mind that the Element check was for the Index ID, meaning that if 00 Fire, 04 Poison, 08 Holy or 0C Punch was used then the counter would not trigger. I couldn't work out why Fire was triggering the counter....

The qhimm wiki solved the problem. It's because it's looking at the bitmask of the Element, not the index, so only 0x0001 and 0x0002 (Fire and Ice) will trigger the counter script. Tried and tested :) quite glad to move on from that lol

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: [FFVII] Enemy AI Script questions
« Reply #45 on: 2019-03-19 21:03:35 »
I found that 41E0 and 41F0 ARE indeed being set....

*investigation music intensifies!*
*investigation music abruptly ends*

Well that took, literally 10 seconds. 41E0 is Initial HP and 41F0 is Initial MP...but only for playable characters. It also doesn't seem to ever be used this way. Mandragora is literally the only enemy that uses it. Possibly it was a convenient dummy value for enemies and the AI programmer just thought "I'll use this value that is never used to communicate between enemies of the same type". While a great idea, it is sadly underused even as that.

2040 is still unset by the battle engine. It's a convenient global value marker, however, since it will not be automatically changed/unset during the battle except through scripting.

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #46 on: 2019-03-20 01:22:27 »
OK cool, thanks for clearing that up (I actually read that as 41E0 being *investigation music intensifies!* and 41F0 being *investigation music abruptly ends* for a moment there lol).

Damn, my head is absolutely spinning. I've been working on Bottomswell for hours now, and I'm still not 100% sure I have it deciphered right. I could do with a fresh pair of eyes to check my work:


Code: [Select]
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  |
|   Bottomswell's BATTLE PLAN:                                               |
|                                                                            |
|  SETUP: Disable default Death handling, sets Row value to 16 (Flying)      |
|                                                                            |
|                                                                            |
|  PHASE 1: (Repeats)                                                        |
|                                                                            |
|   Bottomswell's HP > 75% AND been attacked less than 6 times               |
|                                                                            |
|    1ST ATTACK: Use Tail Attack on Rnd Target                               |
|    2ND ATTACK: Use Tail Attack on Rnd Target                               |
|    3RD ATTACK: Use Tail Attack on Rnd Target                               |
|    4TH ATTACK:                                                             |
|                                                                            |
|     1/3 Chance: Use Tail Attack on 2nd Target; If 2nd Target has Death, do |
|                  nothing                                                   |
|      Follow-Up: Use Tail Attack on 1st Target; If 1st Target has Death, do |
|                  nothing                                                   |
|      Follow-Up: Use Tail Attack on 3rd Target; If 3rd Target has Death, do |
|                  nothing                                                   |
|                                                                            |
|     2/3 Chance: Use <Bodyblow> on Target with Highest HP                   |
|                                                                            |
|                                                                            |
|  PHASE 2:                                                                  |
|                                                                            |
|   Bottomswell's HP <=75% OR been attacked 6 times while in Phase 1         |
|                                                                            |
|    1ST ATTCK: Use Moonstrike on 2nd Target; If 2nd Target has Death, do    |
|                nothing                                                     |
|    FOLLOW-UP: Use Moonstrike on 1st Target; If 1st Target has Death, do    |
|                nothing                                                     |
|    FOLLOW-UP: Use Moonstrike on 3rd Target; If 3rd Target has Death, do    |
|                nothing                                                     |
|                                                                            |
|    2ND ATTACK: Use <Bodyblow> on Target with Highest HP                    |
|    3RD ATTACK: Use Moonstrike on Rnd Target                                |
|    4TH ATTACK:                                                             |
|                                                                            |
|      Bottomswell's HP  > 75%: Use <Fury Blast> on Self, transformation to  |
|                                Phase 1, sets Attack Count to 0             |
|                                                                            |
|      Bottomswell's HP <= 75%: Use Moonstrike on Rnd Target                 |
|                    Follow-Up: 1/2 Chance jump to 1st Attack for next turn  |
|                               1/2 Chance jump to 2nd Attack for next turn  |
|                                                                            |
|                                                                            |
|  PHASE 3:                                                                  |
|                                                                            |
|   Bottomswell's HP <=50% OR been attacked 6 times while in Phase 2         |
|                                                                            |
|    1ST ATTCK: Use <Waterball> on Rnd Target without Seizure                |
|    2ND ATTCK: Skip a turn                                                  |
|    3RD ATTCK: Skip a turn                                                  |
|    4TH ATTCK:                                                              |
|                                                                            |
|      Bottomswell's HP  > 50%: Use <Chill> on self, transformation to Phase |
|                                1, sets Attack Count to 0                   |
|                                                                            |
|      Bottomswell's HP <= 50%: Big Wave on All Targets                      |
|                                                                            |
|    5TH ATTACK: Skip a turn                                                 |
|                                                                            |
|      After 5th Attack: Jump to 2nd Attack for next turn, If no Targets     |
|                         have Imprisoned, jump to 1st Attack for next turn  |
|                                                                            |
|                                                                            |
|  DEATH: Use Big Wave on All Targets, use <Vanish> on Self                  |
|>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o~~~~~~~~~~~~~~~~~~~~~~~~~~~~<|

This has been deciphered from the following heavily modified version of the AI script:

Code: [Select]
PRE-BATTLE:

(Self) | (DeathImmunity) = 1
LocalVar:1stChr = FlagBit(Player1)
LocalVar:2ndChr = FlagBit(Player2)
LocalVar:3rdChr = FlagBit(Player3)
LocalVar:Waterpolo =  ((ActiveActors) | (SelfID) == 92)
LocalVar:1stChrWaterpolo =  (LocalVar:Waterpolo.(PlayerID) == 0)
LocalVar:2ndChrWaterpolo =  (LocalVar:Waterpolo.(PlayerID) == 1)
LocalVar:3rdChrWaterpolo =  (LocalVar:Waterpolo.(PlayerID) == 2)
LocalVar:HitCount = 6
(Self) | (Row) = 16
SCRIPT END


MAIN:

If ((Self) | (IdleAnimID) == 0)
{
If (LocalVar:Count == 0)
{
}
Else
{
If (LocalVar:Count == 1)
{
}
Else
{
If (LocalVar:Count == 2)
{
(TarSelected) = Random (AllPlayers)
Perform ("Tail Attack" [0142])
LocalVar:Count = LocalVar:Count + 1
}
Else
{
}
Else
{
If (Rnd(0..2) == 0)
{
If ( (LocalVar:2ndChr) | (DeathStatus) == 0)
{
(TarSelected) = LocalVar:2ndChr
Perform ("Tail Attack" [0142])
If ( (LocalVar:1stChr) | (DeathStatus) == 0)
{
(TarSelected) = LocalVar:1stChr
Perform ("Tail Attack" [0142])
If ( (LocalVar:3rdChr) | (DeathStatus) == 0)
{
(TarSelected) = LocalVar:3rdChr
Perform ("Tail Attack" [0142])
}
Else
{
(TarSelected) = Random ((AllPlayers) | (HighestCurrentHP))
Perform ("Bodyblow" [012D])
LocalVar:Count = 0
}
Else
{
POP(LocalVar:Count)
}
Else
{
}
Else
{
If ((Self) | (IdleAnimID) == 6)
{
If (LocalVar:Count == 0)
{
If ( (LocalVar:2ndChr) | (DeathStatus) == 0)
{
(TarSelected) = LocalVar:2ndChr
Perform ("Moonstrike" [01CE])
If ( (LocalVar:1stChr) | (DeathStatus) == 0)
{
(TarSelected) = LocalVar:1stChr
Perform ("Moonstrike" [01CE])
If ( (LocalVar:3rdChr) | (DeathStatus) == 0)
{
(TarSelected) = LocalVar:3rdChr
Perform ("Moonstrike" [01CE])
LocalVar:Count = 1
}
Else
{
}
Else
{
If (LocalVar:Count == 1)
{
(TarSelected) = Random ((AllPlayers) | (HighestCurrentHP))
Perform ("Bodyblow" [01A9])
LocalVar:Count = 2
}
Else
{
}
Else
{
If (LocalVar:Count == 2)
{
(TarSelected) = Random (AllPlayers)
Perform ("Moonstrike" [01CE])
LocalVar:Count = 3
}
Else
{
}
Else
{
If ( (LocalVar:Stage < 1) )
{
(TarSelected) = (Self)
Perform ("Fury Blast" [01D0])
(Self) | (IdleAnimID) = 0
LocalVar:Count = 0
LocalVar:HitCount = 6
}
Else
{
(TarSelected) = Random (AllPlayers)
Perform ("Moonstrike" [01CE])
If (Rnd(0..1) == 0)
{
LocalVar:Count = 0
}
Else
{
LocalVar:Count = 1
}
Else
{
POP(LocalVar:Count)
}
Else
{
}
Else
{
If (LocalVar:Count == 0)
{
LocalVar:EnemyGrp = 0
If ( ( (LocalVar:1stChr) | (DeathStatus) == 0)  And  ( (LocalVar:1stChr) | (ImprisonedStatus) == 0) )
{
LocalVar:EnemyGrp = LocalVar:EnemyGrp OR 1
If ( ( (LocalVar:2ndChr) | (DeathStatus) == 0)  And  ( (LocalVar:2ndChr) | (ImprisonedStatus) == 0) )
{
LocalVar:EnemyGrp = LocalVar:EnemyGrp OR 2
If ( ( (LocalVar:3rdChr) | (DeathStatus) == 0)  And  ( (LocalVar:3rdChr) | (ImprisonedStatus) == 0) )
{
LocalVar:EnemyGrp = LocalVar:EnemyGrp OR 4
If ( (LocalVar:EnemyGrp AND 7 == 7) )
{
If (Rnd(0..2) == 0)
{
LocalVar:TarSelected = LocalVar:1stChr
}
Else
{
}
Else
{
If (Rnd(0..2) == 1)
{
LocalVar:TarSelected = LocalVar:2ndChr
}
Else
{
}
Else
{
LocalVar:TarSelected = LocalVar:3rdChr
}
Else
{
POP(Rnd(0..2))
}
Else
{
If ( (LocalVar:EnemyGrp AND 5 == 5) )
{
If (Rnd(0..1) == 0)
{
LocalVar:TarSelected = LocalVar:1stChr
}
Else
{
LocalVar:TarSelected = LocalVar:3rdChr
}
Else
{
If ( (LocalVar:EnemyGrp AND 3 == 3) )
{
If (Rnd(0..1) == 0)
{
LocalVar:TarSelected = LocalVar:1stChr
}
Else
{
LocalVar:TarSelected = LocalVar:2ndChr
}
Else
{
If ( (LocalVar:EnemyGrp AND 6 == 6) )
{
If (Rnd(0..1) == 0)
{
LocalVar:TarSelected = LocalVar:2ndChr
}
Else
{
LocalVar:TarSelected = LocalVar:3rdChr
}
Else
{
LocalVar:TarSelected = (ClearToZero)
If ( (LocalVar:TarSelected != (ClearToZero)) )
{
(TarSelected) = LocalVar:TarSelected
If ( (LocalVar:TarSelected == LocalVar:1stChr) )
{
Perform ("Waterball" [01D2])
(TarSelected) = LocalVar:1stChrWaterpolo
}
Else
{
If ( (LocalVar:TarSelected == LocalVar:2ndChr) )
{
Perform ("Waterball" [01D3])
(TarSelected) = LocalVar:2ndChrWaterpolo
}
Else
{
Perform ("Waterball" [01D4])
(TarSelected) = LocalVar:3rdChrWaterpolo
(TarSelected) | (DeathStatus) = 0
(TarSelected) | (SelfActive) = 1
(TarSelected) | (Targetable) = 1
(TarSelected) | (Needed2EndBattle) = 1
(TarSelected) | (AIScriptActive) = 1
(TarSelected) | (CurrentHP) = (TarSelected) | (MaxHP)
(TarSelected) | (PhysImmunity) = 1
LocalVar:Count = 1
}
Else
{
}
Else
{
If (LocalVar:Count == 1)
{
}
Else
{
If (LocalVar:Count == 2)
{
LocalVar:Count = LocalVar:Count + 1
}
Else
{
}
Else
{
If (LocalVar:Count == 3)
{
If ( (LocalVar:Stage < 2) )
{
(TarSelected) = (Self)
Perform ("Chill" [01D6])
(Self) | (IdleAnimID) = 0
LocalVar:Count = 0
LocalVar:HitCount = 6
}
Else
{
(TarSelected) = (AllPlayers)
Perform ("Big Wave" [01D5])
LocalVar:Count = 4
}
Else
{
}
Else
{
LocalVar:PlayerImprisoned = BitCount( ((AllPlayers) | (ImprisonedStatus) == 1) )
LocalVar:PlayerDeath = BitCount( ((AllPlayers) | (DeathStatus) == 1) )
If ( ( (LocalVar:PlayerDeath >= 1)  And  (LocalVar:PlayerImprisoned >= 1) ) )
{
LocalVar:Count = 1
}
Else
{
LocalVar:Count = 0
}
Else
{
POP(LocalVar:Count)
}
Else
{
POP((Self) | (IdleAnimID))
SCRIPT END


GENERAL COUNTER:

If ( ((Self) | (CurrentHP) <= (Self) | (MaxHP) / 4 * 2) )
{
LocalVar:Stage = 2
LocalVar:HitCount = 0
}
Else
{
If ( ((Self) | (CurrentHP) <= (Self) | (MaxHP) / 4 * 3) )
{
LocalVar:Stage = 1
LocalVar:HitCount = 0
}
Else
{
LocalVar:Stage = 0
If ( ((Self) | (IdleAnimID) == 0) )
{
(Self) | (DamagedAnimID) = 1
If (Not  (LocalVar:HitCount) )
{
(TarSelected) = (Self)
Perform ("Fury Blast" [01CF])
(Self) | (IdleAnimID) = 6
LocalVar:Count = 0
LocalVar:HitCount = 6
If ( (LocalVar:Stage == 2) )
{
(TarSelected) = (Self)
Perform ("Fury Blast" [01D1])
(Self) | (IdleAnimID) = 12
LocalVar:Count = 0
LocalVar:HitCount = 3
}
Else
{
LocalVar:HitCount = LocalVar:HitCount - 1
}
Else
{
If ( ((Self) | (IdleAnimID) == 6) )
{
(Self) | (DamagedAnimID) = 7
If (Not  (LocalVar:HitCount) )
{
(TarSelected) = (Self)
Perform ("Fury Blast" [01D1])
(Self) | (IdleAnimID) = 12
LocalVar:Count = 0
LocalVar:HitCount = 3
}
Else
{
LocalVar:HitCount = LocalVar:HitCount - 1
}
Else
{
(Self) | (DamagedAnimID) = 13
SCRIPT END


DEATH COUNTER:

(TarSelected) = (AllPlayers)
Perform ("Big Wave" [01D5])
If ( ((ActiveActors) | (ImprisonedStatus) == 1) )
{
(TarSelected) =  ((ActiveActors) | (ImprisonedStatus) == 1)
(TarSelected) | (ImprisonedStatus) = 0
(TarSelected) = (Self)
Perform ("Vanish" [014E])
LocalVar:Waterpolo.(SelfActive) = 0
LocalVar:Waterpolo.(Targetable) = 0
LocalVar:Waterpolo.(Needed2EndBattle) = 0
LocalVar:Waterpolo.(AIScriptActive) = 0
SCRIPT END

What's bugging me is "HitCount" being set to 3 once it reaches it's third 'phase' (once IdleAnimID gets set to 12 during the counter scruipt). I think there may have been something I've overlooked. These notes may help:


Code: [Select]
|                                                                            |
|  COUNTER: [All Physical and Magical Attacks]                               |
|                                                                            |
|   PHASE 1: 1st Attack: DamagedAnimID = 1                                   |
|            6th Attack: Use <Fury Blast> on Self, transformation to Phase 2 |
|                                                   IdleAnimID = 6           |
|                                                   Count = 0                |
|                                                   HitCount = 6             |
|                                                                            |
|             HP <= 50%: Use <Fury Blast> on Self, transformation to Phase 3?|
|                                                   IdleAnimID = 12          |
|                                                   Count = 0                |
|                                                   HitCount = 3             |
|                                                                            |
|                                                                            |
|   PHASE 2: 1st Attack: DamagedAnimID = 7                                   |
|            6th Attack: Use <Fury Blast> on Self, transformation to Phase 3 |
|                                                   IdleAnimID = 12          |
|                                                   Count = 0                |
|                                                   HitCount = 3             |
|                                                                            |
|                                                                            |
|   PHASE 3: 1st Attack: DamagedAnimID = 13                                  |
|                                                                            |
|                                                                            |
|  Phase 1 = IdleAnim 0                                                      |
|  Phase 2 = IdleAnim 6                                                      |
|  Phase 3 = IdleAnim 12 ?                                                   |
|                                                                            |
|                                                                            |
|   Bottomswell' HP > 75%          Stage = 0                                 |
|                                                                            |
|   Bottomswell' HP <= 75%         Stage = 1                                 |
|                                  Hit Count = 0                             |
|                                                                            |
|   Bottomswell' HP <= 50%         Stage = 2                                 |
|                                  Hit Count = 0                             |
|                                                                            |
|  Fury Blast 1CF = To Phase 2                                               |
|  Fury Blast 1D0 = To Phase 1                                               |
|  Fury Blast 1D1 = To Phase 3                                               |


This has definitely been one of the more complicated scripts to work out

edit- i have overlooked something. After testing, Bottomswell will use Waterball once it reaches stage 2 (HP <= 75%). I'm not sure why this is.
« Last Edit: 2019-03-20 01:53:59 by DynamixDJ »

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #47 on: 2019-03-20 12:25:47 »
I've just noticed that the Formation itself for Bottomswell has Death Counter script, which I nearly missed. I believe it deals with removing the Waterpolo once the player has died. I'm guessing this script just loops repeatedly in the background?

NFI, how many formations are there that I need to be aware of that contains AI script?

Also the wiki has 2180 dow as "Unknown (divisor of some sort related to limits)". It looks like 2180 is used as a "Character Flagbit", used for checking which target to look at.

This is the death counter script for the Bottomswell formation:

Code: [Select]
DEATH COUNTER:

0x000BattleAddr(&2180) <- 0
0x006 If ( (BattleAddr(&2180) < 3) )
0x006 {
0x00F BattleAddr(&TarSelected) <- FlagBit(BattleAddr(&2180))
0x017 If ( (BattleAddr(&TarSelected).BattleAddr(&DeathStatus) == 1) )
0x017 {
0x024 If ( (BattleAddr(&TarSelected).BattleAddr(&ImprisonedStatus) == 1) )
0x024 {
0x031 BattleAddr(&Self) <-  (BattleAddr(&ActiveActors).BattleAddr(&SelfID) == 92)
0x03F BattleAddr(&Self) <-  (BattleAddr(&Self).BattleAddr(&PlayerID) == BattleAddr(&2180))
0x04E BattleAddr(&TarSelected).BattleAddr(&ImprisonedStatus) <- 0
0x058 BattleAddr(&TarSelected) <- BattleAddr(&Self)
0x05F Perform("Vanish"[014E], EnemyAttack)
0x065 BattleAddr(&Self).BattleAddr(&DeathImmunity) <- 1
0x06F BattleAddr(&Self).BattleAddr(&DeathStatus) <- 0
0x079 BattleAddr(&Self).BattleAddr(&SelfDead) <- 0
0x083 BattleAddr(&Self).BattleAddr(&Targetable) <- 0
0x08D BattleAddr(&2180) <- BattleAddr(&2180) + 1
0x097 LOOP 0x006
0x09A SCRIPT END
« Last Edit: 2019-03-20 12:42:01 by DynamixDJ »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: [FFVII] Enemy AI Script questions
« Reply #48 on: 2019-03-20 13:18:44 »
NFI, how many formations are there that I need to be aware of that contains AI script?

Not as many as there should be. I can say that for certain. Most bosses that have "containers" like Rude's pyramids or Bottomswell's bubbles have them I think. Carry Armor might too. I only added editing them to PrC because I was told they exist. I never did much digging into them.

2180 might be another of those absconded memory addresses that had a function, but no implementation. I'm not sure why it's listed what it is in the wiki (I wrote it, I'm sure). I probably noticed it during a reversing session and just labeled it with the intention of revisiting it and just never did.

DynamixDJ

  • *
  • Posts: 240
  • 1111 1111 0111
    • View Profile
Re: [FFVII] Enemy AI Script questions
« Reply #49 on: 2019-03-21 15:19:01 »
The Grangalan looks at 402B, which the wiki describes as "Was covered / Defers damage".

In what context could the Grangalan be "covered"? I have to assume that this refers to the binary cover flag, and in the instance of the Grangalan, it will be covered if the Granagalan Jr. is present, OR the Grangalan Jr.Jr. A is present.

I'm gonna go ahead and mark it down as such because I don't know what else it could be for now.

Edit - tried and tested 402B is definitely the cover flag (The Grangalan is only covered if the Grangalan Jr. or rightmost Grangalan Jr.Jr is present; kill those two and the Grangalan is no longer covered and moves into the other part of its script).
« Last Edit: 2019-03-21 16:17:35 by DynamixDJ »