Qhimm.com Forums

Final Fantasy Forums => Scripting and Reverse Engineering => Topic started by: DynamixDJ on 2019-03-03 05:52:57

Title: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-03 05:52:57
Hi Y'all! I'm currently working on the Enemy Module of my walkthrough (https://faqs.neoseeker.com/Games/PS4/final_fantasy_vii_dynamixdj.txt), and I've decided that I want to break the AI script down for myself, instead of just blindly following the work the TFergusson has done in his Enemy Mechanics FAQ (https://gamefaqs.gamespot.com/ps/197341-final-fantasy-vii/faqs/31903) (the Eligor caused me quite a few problems).

Anyway, this is a fun project, and this webpage (http://wiki.ffrtt.ru/index.php/FF7/Battle/Battle_Scenes/Battle_AI_Addresses) from the qhuimm wiki is the thing that I needed to fill in the blanks. I have had a few queries along the way though, so I've decided to ask them here.

1st Question:

Code: [Select]
If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
Is this a check to see if ALL Players are in the front row, or if ANY of the Players are in the front row?

I had assumed it was ALL Players when I was looking at the script for MP/Guard Hound/Mono Drive/Grunt. All of these enemies uses AI script that's supposed to look at which row the player is in to choose their Attacks accordingly, but the script is illogical.

I've tried to work out what their patterns were supposed to be. Take the MP as an example, here is the opening setup for the AI Script:

Code: [Select]
0x000LovalVar:PlayerRow <- 0
0x006LovalVar:EnemyRow <- 0
0x00CLovalVar:SelfRow <- 0
0x012 If ( (BattleAddr(&AllEnemies).BattleAddr(&Row) == 0) )
0x012 {
0x01F LovalVar:EnemyRow <- LovalVar:EnemyRow AND 1
0x029 If ( (BattleAddr(&AllEnemies).BattleAddr(&Row) == 1) )
0x029 {
0x036 LovalVar:EnemyRow <- LovalVar:EnemyRow AND 2
0x040 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x040 {
0x04D LovalVar:PlayerRow <- LovalVar:PlayerRow AND 1
0x057 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x057 {
0x064 LovalVar:PlayerRow <- LovalVar:PlayerRow AND 2
0x06E If ( (BattleAddr(&Self).BattleAddr(&Row) == 0) )
0x06E {
0x07B LovalVar:SelfRow <- 0
0x081 }
0x081 Else
0x081 {
0x084 LovalVar:SelfRow <- 1

So, seeing as TF has reported that flaws in the script prevent it from targetting th Players wors correctly, I wanted to see if I could fix the error, which is the AND function returning 0 for every check. If you swap the AND X functions with  <- X, then it would make sense to me. If All Players are in the front row; LovalVar:PlayerRow <- 1, and if All Players are in the Back Row, then LovalVar:PlayerRow <- 2, and if the Player Row values are mixed, then it stays as 0. That works.

The thing is, I've just started looking at the Grashtrike, and the first line reads:

Code: [Select]
If ( ( (BattleAddr(&AllPlayers).BattleAddr(&SlowStatus) == 0)
I had assumed that this meant that it was performing Silk ONLY if all players do not have Slow, however, after testing I can see that this actually means that it will perform Silk if ANY Player does not have Slow.

So, how does that translate to the MP? If at least one player is in the front, the var gets set to 1, and if any player is in the back, the var gets set to 2... that would mean that it can never be 0, and would bypass some of the script....

Here is the MP's full AI script for reference:

Code: [Select]
MAIN:

0x000LovalVar:PlayerRow <- 0
0x006LovalVar:EnemyRow <- 0
0x00CLovalVar:SelfRow <- 0
0x012 If ( (BattleAddr(&AllEnemies).BattleAddr(&Row) == 0) )
0x012 {
0x01F LovalVar:EnemyRow <- LovalVar:EnemyRow AND 1
0x029 If ( (BattleAddr(&AllEnemies).BattleAddr(&Row) == 1) )
0x029 {
0x036 LovalVar:EnemyRow <- LovalVar:EnemyRow AND 2
0x040 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x040 {
0x04D LovalVar:PlayerRow <- LovalVar:PlayerRow AND 1
0x057 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x057 {
0x064 LovalVar:PlayerRow <- LovalVar:PlayerRow AND 2
0x06E If ( (BattleAddr(&Self).BattleAddr(&Row) == 0) )
0x06E {
0x07B LovalVar:SelfRow <- 0
0x081 }
0x081 Else
0x081 {
0x084 LovalVar:SelfRow <- 1
0x08A If (Not  (LovalVar:SelfRow) )
0x08A {
0x091 If (LovalVar:PlayerRow == 1)
0x091 {
0x099 LocalVar:Random <- 4
0x09F If (Not  (Random MOD LocalVar:Random) )
0x09F {
0x0A8 If ( (LovalVar:PlayerRow AND 2 == 2) )
0x0A8 {
0x0B4 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x0C3 }
0x0C3 Else
0x0C3 {
0x0C6 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x0CE LocalVar:EnemyAttack <- 272 (Machine Gun)
0x0D5 }
0x0D5 Else
0x0D5 {
0x0D8 If ( (LovalVar:PlayerRow AND 1 == 1) )
0x0D8 {
0x0E4 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x0F3 }
0x0F3 Else
0x0F3 {
0x0F6 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x0FE LocalVar:EnemyAttack <- 273 (Tonfa)
0x105 }
0x105 Else
0x105 {
0x108 }
0x108 Else
0x108 {
0x10B If (LovalVar:PlayerRow == 2)
0x10B {
0x110 LocalVar:Random <- 8
0x116 If (Not  (Random MOD LocalVar:Random) )
0x116 {
0x11F If ( (LovalVar:PlayerRow AND 1 == 1) )
0x11F {
0x12B BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x13A }
0x13A Else
0x13A {
0x13D BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x145 LocalVar:EnemyAttack <- 273 (Tonfa)
0x14C }
0x14C Else
0x14C {
0x14F If ( (LovalVar:PlayerRow AND 2 == 2) )
0x14F {
0x15B BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x16A }
0x16A Else
0x16A {
0x16D BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x175 LocalVar:EnemyAttack <- 272 (Machine Gun)
0x17C }
0x17C Else
0x17C {
0x17F }
0x17F Else
0x17F {
0x182 LocalVar:Random <- 2
0x188 If (Not  (Random MOD LocalVar:Random) )
0x188 {
0x191 If ( (LovalVar:PlayerRow AND 2 == 2) )
0x191 {
0x19D BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x1AC }
0x1AC Else
0x1AC {
0x1AF BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x1B7 LocalVar:EnemyAttack <- 272 (Machine Gun)
0x1BE }
0x1BE Else
0x1BE {
0x1C1 If ( (LovalVar:PlayerRow AND 1 == 1) )
0x1C1 {
0x1CD BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x1DC }
0x1DC Else
0x1DC {
0x1DF BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x1E7 LocalVar:EnemyAttack <- 273 (Tonfa)
0x1EE }
0x1EE Else
0x1EE {
0x1F1 POP(LovalVar:PlayerRow)
0x1F2 }
0x1F2 Else
0x1F2 {
0x1F5 If (LovalVar:PlayerRow == 1)
0x1F5 {
0x1FD LocalVar:Random <- 4
0x203 If (Not  (Random MOD LocalVar:Random) )
0x203 {
0x20C If ( (LovalVar:PlayerRow AND 1 == 1) )
0x20C {
0x218 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x227 }
0x227 Else
0x227 {
0x22A BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x232 LocalVar:EnemyAttack <- 273 (Tonfa)
0x239 }
0x239 Else
0x239 {
0x23C If ( (LovalVar:PlayerRow AND 2 == 2) )
0x23C {
0x248 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x257 }
0x257 Else
0x257 {
0x25A BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x262 LocalVar:EnemyAttack <- 272 (Machine Gun)
0x269 }
0x269 Else
0x269 {
0x26C }
0x26C Else
0x26C {
0x26F If (LovalVar:PlayerRow == 2)
0x26F {
0x274 LocalVar:Random <- 8
0x27A If (Not  (Random MOD LocalVar:Random) )
0x27A {
0x283 If ( (LovalVar:PlayerRow AND 1 == 1) )
0x283 {
0x28F BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x29E }
0x29E Else
0x29E {
0x2A1 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x2A9 LocalVar:EnemyAttack <- 273 (Tonfa)
0x2B0 }
0x2B0 Else
0x2B0 {
0x2B3 If ( (LovalVar:PlayerRow AND 2 == 2) )
0x2B3 {
0x2BF BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x2CE }
0x2CE Else
0x2CE {
0x2D1 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x2D9 LocalVar:EnemyAttack <- 272 (Machine Gun)
0x2E0 }
0x2E0 Else
0x2E0 {
0x2E3 }
0x2E3 Else
0x2E3 {
0x2E6 LocalVar:Random <- 6
0x2EC If (Not  (Random MOD LocalVar:Random) )
0x2EC {
0x2F5 If ( (LovalVar:PlayerRow AND 1 == 1) )
0x2F5 {
0x301 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x310 }
0x310 Else
0x310 {
0x313 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x31B LocalVar:EnemyAttack <- 273 (Tonfa)
0x322 }
0x322 Else
0x322 {
0x325 If ( (LovalVar:PlayerRow AND 2 == 2) )
0x325 {
0x331 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x340 }
0x340 Else
0x340 {
0x343 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x34B LocalVar:EnemyAttack <- 272 (Machine Gun)
0x352 }
0x352 Else
0x352 {
0x355 POP(LovalVar:PlayerRow)
0x356Perform(LocalVar:EnemyAttack, EnemyAttack)
0x35CSCRIPT END

Here's how I have deciphered that, assuming that the AND functions are actually PUSH functions, and that the check in question is looking for ALL players in Front/Back Row (as opposed to any player):


Code: [Select]
/----------------------------------------------------------------------------\
|   BATTLE PLAN: (Unused)                                                    |
|                                                                            |
|  If All Players are in Back Row: 1/4 Chance use Machine Gun on Rnd Target  |
|                                  3/4 Chance use <Tonfa> on Rnd Target      |
|                                                                            |
|  If All Players are in Front Row: 1/8 Chance use <Tonfa> on Rnd Target     |
|                                   7/8 Chance use Machine Gun on Rnd Target |
|                                                                            |
|  If the Players Rows are mixed:                                            |
|   If MP is in the Front Row: 1/2 Chance use Machine Gun on Rnd Target      |
|                              1/2 Chance use <Tonfa> on Rnd Target          |
|                                                                            |
|   If MP is in the Back Row:  1/6 Chance use <Tonfa> on Rnd Target          |
|                              5/6 Chance use Machine Gun on Rnd Target      |
\----------------------------------------------------------------------------/


I'm not 100% sure this is correct now. If anyone could clarify for me how the MP's correct AI script is supposed to work, that would be great!
Title: Re: [FFVII] Enemy AI Script questions
Post by: codemann8 on 2019-03-03 09:53:45
The first question I'd have is where you're getting the text version of these scripts from? I'm assuming these are probably disassembled from ProudClod. The word of caution there is to worry about the lesser known opcodes that have a higher chance of inaccuracies, although i don't know if any of these lesser known opcodes are used for MP, not only lesser known opcodes but also special cases that maybe weren't considered.  It's possible that some of these outputs could have errors, but NFITC would be the one to comment on how confident he is with the accuracy of that output.

It also looks like there are several layers of IFs, but I don't see any closing brackets, which is important because that will determine whether they are nested or sequential.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-03 10:35:42
I'm first disassembling it via Proud Clod, then using the Find-All > Replace-All feature in my .txt editor to assign the argument values to the variable mapping as you see. This is where I'm getting the info for the mapping:

http://wiki.ffrtt.ru/index.php/FF7/Battle/Battle_Scenes/Battle_AI_Addresses (http://wiki.ffrtt.ru/index.php/FF7/Battle/Battle_Scenes/Battle_AI_Addresses)

Here is a list of variables that I've been working with, I'm adding to the list as and when I need to:

Code: [Select]
BattleAddr(&2050) = BattleAddr(&ActiveActors)
BattleAddr(&2060) = BattleAddr(&Self)
BattleAddr(&2070) = BattleAddr(&TargetSelected)
BattleAddr(&2080) = BattleAddr(&AllEnemies)
BattleAddr(&2090) = BattleAddr(&ActiveEnemies)
BattleAddr(&20A0) = BattleAddr(&AllPlayers)
BattleAddr(&20B0) = BattleAddr(&ActivePlayers)



BattleAddr(&4000) = BattleAddr(&DeathStatus)
BattleAddr(&4001) = BattleAddr(&NearDeathStatus)
BattleAddr(&4002) = BattleAddr(&SleepStatus)
BattleAddr(&4003) = BattleAddr(&PoisonStatus)
BattleAddr(&4004) = BattleAddr(&SadnessStatus)
BattleAddr(&4005) = BattleAddr(&FuryStatus)
BattleAddr(&4006) = BattleAddr(&ConfusionStatus)
BattleAddr(&4007) = BattleAddr(&SilenceStatus)
BattleAddr(&4008) = BattleAddr(&HasteStatus)
BattleAddr(&4009) = BattleAddr(&SlowStatus)
BattleAddr(&400A) = BattleAddr(&StopStatus)
BattleAddr(&400B) = BattleAddr(&FrogStatus)
BattleAddr(&400C) = BattleAddr(&SmallStatus)
BattleAddr(&400D) = BattleAddr(&SlowNumbStatus)
BattleAddr(&400E) = BattleAddr(&PetrifyStatus)
BattleAddr(&400F) = BattleAddr(&RegenStatus)
BattleAddr(&4010) = BattleAddr(&BarrierStatus)
BattleAddr(&4011) = BattleAddr(&MBarrierStatus)
BattleAddr(&4012) = BattleAddr(&ReflectStatus)
BattleAddr(&4013) = BattleAddr(&DualStatus)
BattleAddr(&4014) = BattleAddr(&ShieldStatus)
BattleAddr(&4015) = BattleAddr(&DSentenceStatus)
BattleAddr(&4016) = BattleAddr(&ManipulateStatus)
BattleAddr(&4017) = BattleAddr(&BerserkStatus)
BattleAddr(&4018) = BattleAddr(&PeerlessStatus)
BattleAddr(&4019) = BattleAddr(&ParalyzedStatus)
BattleAddr(&401A) = BattleAddr(&DarknessStatus)
BattleAddr(&401B) = BattleAddr(&DualDrainStatus)
BattleAddr(&401C) = BattleAddr(&DeathForceStatus)
BattleAddr(&401D) = BattleAddr(&ResistStatus)
BattleAddr(&401E) = BattleAddr(&LuckyGirlStatus)
BattleAddr(&401F) = BattleAddr(&ImprisonedStatus)


BattleAddr(&4026) = BattleAddr(&Row)
BattleAddr(&4060) = BattleAddr(&PlayerID)
BattleAddr(&4080) = BattleAddr(&IdleAnimID)
BattleAddr(&4088) = BattleAddr(&DamagedAnimID)
BattleAddr(&40D0) = BattleAddr(&LastAttacker)
BattleAddr(&4100) = BattleAddr(&Def)
BattleAddr(&4110) = BattleAddr(&MDef)
BattleAddr(&4140) = BattleAddr(&CurrentMP)
BattleAddr(&4150) = BattleAddr(&MaxMP)
BattleAddr(&4160) = BattleAddr(&CurrentHP)
BattleAddr(&4180) = BattleAddr(&MaxHP)
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-04 00:55:50
BattleAddr(&4020)
BattleAddr(&4022)
BattleAddr(&4023)
BattleAddr(&4024)

What do these do? They can be found after the Action 0138 Escape is performed, which is used by Prowler, Bandit and Vice:


Code: [Select]
BattleAddr(&TargetSelected) <- BattleAddr(&Self)
Perform("Escape"[0138], EnemyAttack)
BattleAddr(&Self).BattleAddr(&4020) <- 0
BattleAddr(&Self).BattleAddr(&4023) <- 0
BattleAddr(&Self).BattleAddr(&4022) <- 0
BattleAddr(&Self).BattleAddr(&4024) <- 0

Edit - Same vars are found on the Hell House [2C] (1st Form). I guess this has something to do with stat transfers when an enemy leaves the battle

Double edit - Perhaps.... Exp, Gil, AP and....... ???? - Another edit - Nope, AP is 0x42BO, Gil is 0x42C0, and EXP is 0x42E0. The mystery remains...

Another edit - OK, I have a suspicion that they could be setting the main enemy battle flags:

BattleAddr(&4020) = 0x0001 OFF = Invisible
BattleAddr(&4021) = 0x0002 OFF = Groups the enemies together during a PINCER ATTACK.
BattleAddr(&4022) = 0x0004 OFF = ???? (Bad Rap Sample and Poodler Sample only)
BattleAddr(&4023) = 0x0008 OFF = Non-Targetable
BattleAddr(&4024) =  0x0010 OFF = Disables Main AI Script

This would fit, seeing as the four variables that are used are 4020 4022 4023 & 4024, meaning the 0x0002 flag (4021) wouldn't be set during the instances in which an enemy removes itself from battle, as it wouldn't need to. Edit - the quimm wiki's description for 4021 is "Ally of current actor", which could fit....

Bear in mind that with the Hell House and Aero Combatant are turning the same vars ON for the subsequent enemy that takes it's place:

Aero Combatant (1st form):
Code: [Select]
PRE-BATTLE:

0x000LocalVar:TransformID <-  (BitCount(BattleAddr(&Self).BattleAddr(&PlayerID)) == BattleAddr(&ActiveActors).BattleAddr(&PlayerID))
0x014LocalVar:TransformID <-  (LocalVar:TransformID.BattleAddr(&4120) == 47)
0x022SCRIPT END


MAIN:

0x000BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x008 If (Not  (Random MOD 3) )
0x008 {
0x010 Perform("Kick"[0158], EnemyAttack)
0x016 }
0x016 Else
0x016 {
0x019 Perform("Propeller"[0159], EnemyAttack)
0x01FSCRIPT END


GENERAL COUNTER:

0x000 If (Not  (Random MOD 2) )
0x000 {
0x008 BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x00F Perform([011F], EnemyAttack)
0x015 BattleAddr(&Self).BattleAddr(&4020) <- 0
0x01F BattleAddr(&Self).BattleAddr(&4023) <- 0
0x029 LocalVar:TransformID.BattleAddr(&4020) <- 1
0x033 LocalVar:TransformID.BattleAddr(&4023) <- 1
0x03D LocalVar:TransformID.BattleAddr(&4022) <- 1
0x047 LocalVar:TransformID.BattleAddr(&4024) <- 1
0x051 TransferStatus(BattleAddr(&Self), LocalVar:TransformID)
0x058 BattleAddr(&Self).BattleAddr(&4020) <- 0
0x062 BattleAddr(&Self).BattleAddr(&4023) <- 0
0x06C BattleAddr(&Self).BattleAddr(&4022) <- 0
0x076 BattleAddr(&Self).BattleAddr(&4024) <- 0
0x080SCRIPT END


DEATH COUNTER:

0x000LocalVar:TransformID.BattleAddr(&4020) <- 0
0x00ALocalVar:TransformID.BattleAddr(&4023) <- 0
0x014LocalVar:TransformID.BattleAddr(&4022) <- 0
0x01ELocalVar:TransformID.BattleAddr(&4024) <- 0
0x028SCRIPT END

I can't see anywhere else in the script that handles enabling of the AI script/visibility/targetability of the new enemy.

On a side note, that 0x0004 Enemy Battle Flag that I'm unsure of could be disabling battle rewards, seeing as Bad Rap Sample and Poodler Sample do not give any rewards....
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-04 16:01:08
Oh this is the thread for me. :D

Regarding to the (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) conundrum. This, as you noticed, translates to if there are NO players in the back row. This has to do with the data type that is returned by the AllPlayers address. It's a 2X type variable meaning it returns a list (of 3 in this case) values with the state of each player. When that is masked by row, it changes those 3 values to each players' row values. In this case, if ANY of them are zero then the condition passes. That's just the nature of the 2X variable type and conditional checks. I wish there were a non-ugly way to indicate that in the AI editor.

Now to the 4022 variable, I believe this unsetting this value indicates the enemy does not need to be defeated in order for the battle to end. I think some set pieces set this in their init scripts. Check the helicopter in the Rufus battle and the truck and tiny bronco in the Palmer battle. Just going by memory here, but I'm pretty sure they unset the 4022 flag. A few bosses do as well IIRC. Check Mystery Ninja Custom 7 script and Diamond Weapon's Death script.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-04 23:43:40
Ah, so it's not "All players in the Front row", it's  "No Players in the Back Row", which is the same thing when it comes to rows. With the Slow check, it's not as I initially thought "All Players without Slow", but rather "No Player with Slow". This fits, thanks!

In regards to 4023 and 4024, I'm 99% certain that I'm correct in thinking that 4023 is Actor's Targetability, and 4024 is Actor's AI Script enabled/disabled, after having dissected Reno:Turks I and his Pyramids.

If I'm right in thinking that, then that would mean that 4020 - 4024 does indeed reflect the Actor's Battle Flags as mentioned. And, if I'm right about that. then that means that you have solved the mystery of 0x0004 Enemy Battle Flag; as mentioned, Poodler Sample and Bad Rap sample are the only enemies in the game that go into the fight with this flag disabled. Your description of the actor needeing to be defetaed for the Battle to end fits perfectly, not just with the P/BR Sample, but also with the instances in which I've witnessed the 4023 flag be enabled/disabled via the AI script.

I'm going to jot down 4023 as you've described, and I'm gonna keep going through each enemy sequentially keeping my eyes open for consistency, but I'm fairly happy the shoe fits. Thanks!

I must say, this is quite a fun project to do, seeing as I don't think anyone has worked with the AI sctipt down in the way I currently am, which is to try to break each enemy's "Battle Plan" down into something the layman can understand. TF did a fantastic job, but I'm trying to do one better.

Take the Eligor, for example. I've converted this:

Code: [Select]
40 - [28] Eligor



LocalVar:0000 = LocalVar:OpenAttack
LocalVar:0020 = LocalVar:Stage
LocalVar:0040 = LocalVar:1stChr
LocalVar:0060 = LocalVar:2ndChr
LocalVar:0080 = LovalVar:3rdChr

BattleAddr(&2060) = BattleAddr(&Self)
BattleAddr(&2070) = BattleAddr(&TargetSelected)
BattleAddr(&20A0) = BattleAddr(&AllPlayers)
BattleAddr(&4000) = BattleAddr(&DeathStatus)
BattleAddr(&4002) = BattleAddr(&SleepStatus)
BattleAddr(&4007) = BattleAddr(&SilenceStatus)
BattleAddr(&4026) = BattleAddr(&Row)
BattleAddr(&4140) = BattleAddr(&CurrentMP)
BattleAddr(&4160) = BattleAddr(&CurrentHP)
BattleAddr(&4180) = BattleAddr(&MaxHP)



MAIN:

0x000 If (Not  (LocalVar:OpenAttack) )
0x000 {
0x007 If (Random MOD 3 == 0)
0x007 {
0x010 If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Silence [000D])) )
0x010 {
0x01E BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x02D Perform("Silence"[000D], EnemyAttack)
0x032 }
0x032 Else
0x032 {
0x035 If (Not  (LocalVar:Stage) )
0x035 {
0x03C If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x03C {
0x049 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x058 }
0x058 Else
0x058 {
0x05B BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x063 Perform("Sword Attack"[014A], EnemyAttack)
0x069 }
0x069 Else
0x069 {
0x06C LocalVar:1stChr <- FlagBit(0)
0x073 LocalVar:2ndChr <- FlagBit(1)
0x07A LovalVar:3rdChr <- FlagBit(2)
0x081 If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x081 {
0x08E BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x095 Perform("Mono Laser"[014B], EnemyAttack)
0x09B If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x09B {
0x0A8 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x0AF Perform("Mono Laser"[014B], EnemyAttack)
0x0B5 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x0B5 {
0x0C2 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x0C9 Perform("Mono Laser"[014B], EnemyAttack)
0x0CF }
0x0CF Else
0x0CF {
0x0D2 }
0x0D2 Else
0x0D2 {
0x0D5 If (Random MOD 3 == 1)
0x0D5 {
0x0DA If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Sleepel [000B])) )
0x0DA {
0x0E8 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x0F7 Perform("Sleepel"[000B], EnemyAttack)
0x0FC }
0x0FC Else
0x0FC {
0x0FF If (Not  (LocalVar:Stage) )
0x0FF {
0x106 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x106 {
0x113 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x122 }
0x122 Else
0x122 {
0x125 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x12D Perform("Sword Attack"[014A], EnemyAttack)
0x133 }
0x133 Else
0x133 {
0x136 LocalVar:1stChr <- FlagBit(0)
0x13D LocalVar:2ndChr <- FlagBit(1)
0x144 LovalVar:3rdChr <- FlagBit(2)
0x14B If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x14B {
0x158 BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x15F Perform("Mono Laser"[014B], EnemyAttack)
0x165 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x165 {
0x172 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x179 Perform("Mono Laser"[014B], EnemyAttack)
0x17F If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x17F {
0x18C BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x193 Perform("Mono Laser"[014B], EnemyAttack)
0x199 }
0x199 Else
0x199 {
0x19C }
0x19C Else
0x19C {
0x19F }
0x19F Else
0x19F {
0x1A2 POP(Random MOD 3)
0x1A3 LocalVar:OpenAttack <- 1
0x1A9 }
0x1A9 Else
0x1A9 {
0x1AC If (Not  (LocalVar:Stage) )
0x1AC {
0x1B3 If (Random MOD 12 == 0)
0x1B3 {
0x1BC }
0x1BC Else
0x1BC {
0x1BF If (Random MOD 12 == 1)
0x1BF {
0x1C4 If ( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x1C4 {
0x1D1 If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Silence [000D])) )
0x1D1 {
0x1DF BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x1EE Perform("Silence"[000D], EnemyAttack)
0x1F3 }
0x1F3 Else
0x1F3 {
0x1F6 If (Not  (LocalVar:Stage) )
0x1F6 {
0x1FD If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x1FD {
0x20A BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x219 }
0x219 Else
0x219 {
0x21C BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x224 Perform("Sword Attack"[014A], EnemyAttack)
0x22A }
0x22A Else
0x22A {
0x22D LocalVar:1stChr <- FlagBit(0)
0x234 LocalVar:2ndChr <- FlagBit(1)
0x23B LovalVar:3rdChr <- FlagBit(2)
0x242 If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x242 {
0x24F BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x256 Perform("Mono Laser"[014B], EnemyAttack)
0x25C If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x25C {
0x269 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x270 Perform("Mono Laser"[014B], EnemyAttack)
0x276 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x276 {
0x283 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x28A Perform("Mono Laser"[014B], EnemyAttack)
0x290 }
0x290 Else
0x290 {
0x293 If ( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x293 {
0x2A0 If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Sleepel [000B])) )
0x2A0 {
0x2AE BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x2BD Perform("Sleepel"[000B], EnemyAttack)
0x2C2 }
0x2C2 Else
0x2C2 {
0x2C5 If (Not  (LocalVar:Stage) )
0x2C5 {
0x2CC If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x2CC {
0x2D9 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x2E8 }
0x2E8 Else
0x2E8 {
0x2EB BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x2F3 Perform("Sword Attack"[014A], EnemyAttack)
0x2F9 }
0x2F9 Else
0x2F9 {
0x2FC LocalVar:1stChr <- FlagBit(0)
0x303 LocalVar:2ndChr <- FlagBit(1)
0x30A LovalVar:3rdChr <- FlagBit(2)
0x311 If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x311 {
0x31E BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x325 Perform("Mono Laser"[014B], EnemyAttack)
0x32B If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x32B {
0x338 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x33F Perform("Mono Laser"[014B], EnemyAttack)
0x345 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x345 {
0x352 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x359 Perform("Mono Laser"[014B], EnemyAttack)
0x35F }
0x35F Else
0x35F {
0x362 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x362 {
0x36F BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x37E }
0x37E Else
0x37E {
0x381 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x389 Perform("Sword Attack"[014A], EnemyAttack)
0x38F }
0x38F Else
0x38F {
0x392 }
0x392 Else
0x392 {
0x395 If (Random MOD 12 == 2)
0x395 {
0x39A }
0x39A Else
0x39A {
0x39D If (Random MOD 12 == 3)
0x39D {
0x3A2 If ( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x3A2 {
0x3AF If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Sleepel [000B])) )
0x3AF {
0x3BD BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x3CC Perform("Sleepel"[000B], EnemyAttack)
0x3D1 }
0x3D1 Else
0x3D1 {
0x3D4 If (Not  (LocalVar:Stage) )
0x3D4 {
0x3DB If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x3DB {
0x3E8 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x3F7 }
0x3F7 Else
0x3F7 {
0x3FA BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x402 Perform("Sword Attack"[014A], EnemyAttack)
0x408 }
0x408 Else
0x408 {
0x40B LocalVar:1stChr <- FlagBit(0)
0x412 LocalVar:2ndChr <- FlagBit(1)
0x419 LovalVar:3rdChr <- FlagBit(2)
0x420 If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x420 {
0x42D BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x434 Perform("Mono Laser"[014B], EnemyAttack)
0x43A If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x43A {
0x447 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x44E Perform("Mono Laser"[014B], EnemyAttack)
0x454 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x454 {
0x461 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x468 Perform("Mono Laser"[014B], EnemyAttack)
0x46E }
0x46E Else
0x46E {
0x471 If ( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x471 {
0x47E If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Silence [000D])) )
0x47E {
0x48C BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x49B Perform("Silence"[000D], EnemyAttack)
0x4A0 }
0x4A0 Else
0x4A0 {
0x4A3 If (Not  (LocalVar:Stage) )
0x4A3 {
0x4AA If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x4AA {
0x4B7 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x4C6 }
0x4C6 Else
0x4C6 {
0x4C9 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x4D1 Perform("Sword Attack"[014A], EnemyAttack)
0x4D7 }
0x4D7 Else
0x4D7 {
0x4DA LocalVar:1stChr <- FlagBit(0)
0x4E1 LocalVar:2ndChr <- FlagBit(1)
0x4E8 LovalVar:3rdChr <- FlagBit(2)
0x4EF If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x4EF {
0x4FC BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x503 Perform("Mono Laser"[014B], EnemyAttack)
0x509 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x509 {
0x516 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x51D Perform("Mono Laser"[014B], EnemyAttack)
0x523 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x523 {
0x530 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x537 Perform("Mono Laser"[014B], EnemyAttack)
0x53D }
0x53D Else
0x53D {
0x540 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x540 {
0x54D BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x55C }
0x55C Else
0x55C {
0x55F BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x567 Perform("Sword Attack"[014A], EnemyAttack)
0x56D }
0x56D Else
0x56D {
0x570 }
0x570 Else
0x570 {
0x573 If (Random MOD 12 == 4)
0x573 {
0x578 }
0x578 Else
0x578 {
0x57B If (Random MOD 12 == 5)
0x57B {
0x580 }
0x580 Else
0x580 {
0x583 If (Random MOD 12 == 6)
0x583 {
0x588 }
0x588 Else
0x588 {
0x58B If (Random MOD 12 == 7)
0x58B {
0x590 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x590 {
0x59D BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 1) )
0x5AC }
0x5AC Else
0x5AC {
0x5AF BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x5B7 Perform("Mono Laser"[014B], EnemyAttack)
0x5BD }
0x5BD Else
0x5BD {
0x5C0 }
0x5C0 Else
0x5C0 {
0x5C3 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x5C3 {
0x5D0 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x5DF }
0x5DF Else
0x5DF {
0x5E2 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x5EA Perform("Sword Attack"[014A], EnemyAttack)
0x5F0 }
0x5F0 Else
0x5F0 {
0x5F3 POP(Random MOD 12)
0x5F4 }
0x5F4 Else
0x5F4 {
0x5F7 If (Random MOD 5 == 0)
0x5F7 {
0x600 If ( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x600 {
0x60D If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Silence [000D])) )
0x60D {
0x61B BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x62A Perform("Silence"[000D], EnemyAttack)
0x62F }
0x62F Else
0x62F {
0x632 If (Not  (LocalVar:Stage) )
0x632 {
0x639 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x639 {
0x646 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x655 }
0x655 Else
0x655 {
0x658 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x660 Perform("Sword Attack"[014A], EnemyAttack)
0x666 }
0x666 Else
0x666 {
0x669 LocalVar:1stChr <- FlagBit(0)
0x670 LocalVar:2ndChr <- FlagBit(1)
0x677 LovalVar:3rdChr <- FlagBit(2)
0x67E If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x67E {
0x68B BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x692 Perform("Mono Laser"[014B], EnemyAttack)
0x698 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x698 {
0x6A5 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x6AC Perform("Mono Laser"[014B], EnemyAttack)
0x6B2 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x6B2 {
0x6BF BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x6C6 Perform("Mono Laser"[014B], EnemyAttack)
0x6CC }
0x6CC Else
0x6CC {
0x6CF If ( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x6CF {
0x6DC If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Sleepel [000B])) )
0x6DC {
0x6EA BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x6F9 Perform("Sleepel"[000B], EnemyAttack)
0x6FE }
0x6FE Else
0x6FE {
0x701 If (Not  (LocalVar:Stage) )
0x701 {
0x708 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x708 {
0x715 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x724 }
0x724 Else
0x724 {
0x727 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x72F Perform("Sword Attack"[014A], EnemyAttack)
0x735 }
0x735 Else
0x735 {
0x738 LocalVar:1stChr <- FlagBit(0)
0x73F LocalVar:2ndChr <- FlagBit(1)
0x746 LovalVar:3rdChr <- FlagBit(2)
0x74D If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x74D {
0x75A BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x761 Perform("Mono Laser"[014B], EnemyAttack)
0x767 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x767 {
0x774 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x77B Perform("Mono Laser"[014B], EnemyAttack)
0x781 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x781 {
0x78E BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x795 Perform("Mono Laser"[014B], EnemyAttack)
0x79B }
0x79B Else
0x79B {
0x79E LocalVar:1stChr <- FlagBit(0)
0x7A5 LocalVar:2ndChr <- FlagBit(1)
0x7AC LovalVar:3rdChr <- FlagBit(2)
0x7B3 If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x7B3 {
0x7C0 BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x7C7 Perform("Mono Laser"[014B], EnemyAttack)
0x7CD If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x7CD {
0x7DA BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x7E1 Perform("Mono Laser"[014B], EnemyAttack)
0x7E7 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x7E7 {
0x7F4 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x7FB Perform("Mono Laser"[014B], EnemyAttack)
0x801 }
0x801 Else
0x801 {
0x804 }
0x804 Else
0x804 {
0x807 If (Random MOD 5 == 1)
0x807 {
0x80C If ( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x80C {
0x819 If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Sleepel [000B])) )
0x819 {
0x827 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SleepStatus) == 0) )
0x836 Perform("Sleepel"[000B], EnemyAttack)
0x83B }
0x83B Else
0x83B {
0x83E If (Not  (LocalVar:Stage) )
0x83E {
0x845 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x845 {
0x852 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x861 }
0x861 Else
0x861 {
0x864 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x86C Perform("Sword Attack"[014A], EnemyAttack)
0x872 }
0x872 Else
0x872 {
0x875 LocalVar:1stChr <- FlagBit(0)
0x87C LocalVar:2ndChr <- FlagBit(1)
0x883 LovalVar:3rdChr <- FlagBit(2)
0x88A If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x88A {
0x897 BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x89E Perform("Mono Laser"[014B], EnemyAttack)
0x8A4 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x8A4 {
0x8B1 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x8B8 Perform("Mono Laser"[014B], EnemyAttack)
0x8BE If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x8BE {
0x8CB BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x8D2 Perform("Mono Laser"[014B], EnemyAttack)
0x8D8 }
0x8D8 Else
0x8D8 {
0x8DB If ( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x8DB {
0x8E8 If ( (BattleAddr(&Self).BattleAddr(&CurrentMP) >= MPCost(Silence [000D])) )
0x8E8 {
0x8F6 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&SilenceStatus) == 0) )
0x905 Perform("Silence"[000D], EnemyAttack)
0x90A }
0x90A Else
0x90A {
0x90D If (Not  (LocalVar:Stage) )
0x90D {
0x914 If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x914 {
0x921 BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
0x930 }
0x930 Else
0x930 {
0x933 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x93B Perform("Sword Attack"[014A], EnemyAttack)
0x941 }
0x941 Else
0x941 {
0x944 LocalVar:1stChr <- FlagBit(0)
0x94B LocalVar:2ndChr <- FlagBit(1)
0x952 LovalVar:3rdChr <- FlagBit(2)
0x959 If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x959 {
0x966 BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x96D Perform("Mono Laser"[014B], EnemyAttack)
0x973 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x973 {
0x980 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x987 Perform("Mono Laser"[014B], EnemyAttack)
0x98D If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x98D {
0x99A BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0x9A1 Perform("Mono Laser"[014B], EnemyAttack)
0x9A7 }
0x9A7 Else
0x9A7 {
0x9AA LocalVar:1stChr <- FlagBit(0)
0x9B1 LocalVar:2ndChr <- FlagBit(1)
0x9B8 LovalVar:3rdChr <- FlagBit(2)
0x9BF If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0x9BF {
0x9CC BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0x9D3 Perform("Mono Laser"[014B], EnemyAttack)
0x9D9 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0x9D9 {
0x9E6 BattleAddr(&TargetSelected) <- LocalVar:1stChr
0x9ED Perform("Mono Laser"[014B], EnemyAttack)
0x9F3 If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0x9F3 {
0xA00 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0xA07 Perform("Mono Laser"[014B], EnemyAttack)
0xA0D }
0xA0D Else
0xA0D {
0xA10 }
0xA10 Else
0xA10 {
0xA13 LocalVar:1stChr <- FlagBit(0)
0xA1A LocalVar:2ndChr <- FlagBit(1)
0xA21 LovalVar:3rdChr <- FlagBit(2)
0xA28 If ( (LocalVar:2ndChr.BattleAddr(&DeathStatus) == 0) )
0xA28 {
0xA35 BattleAddr(&TargetSelected) <- LocalVar:2ndChr
0xA3C Perform("Mono Laser"[014B], EnemyAttack)
0xA42 If ( (LocalVar:1stChr.BattleAddr(&DeathStatus) == 0) )
0xA42 {
0xA4F BattleAddr(&TargetSelected) <- LocalVar:1stChr
0xA56 Perform("Mono Laser"[014B], EnemyAttack)
0xA5C If ( (LovalVar:3rdChr.BattleAddr(&DeathStatus) == 0) )
0xA5C {
0xA69 BattleAddr(&TargetSelected) <- LovalVar:3rdChr
0xA70 Perform("Mono Laser"[014B], EnemyAttack)
0xA76 }
0xA76 Else
0xA76 {
0xA79 POP(Random MOD 5)
0xA7ASCRIPT END


GENERAL COUNTER:

0x000 If ( (BattleAddr(&Self).BattleAddr(&CurrentHP) <= BattleAddr(&Self).BattleAddr(&MaxHP) / 2) )
0x000 {
0x015 LocalVar:Stage <- 1
0x01B }
0x01B Else
0x01B {
0x01E LocalVar:Stage <- 0
0x024SCRIPT END

Into this:

Code: [Select]
/----------------------------------------------------------------------------\
|   BATTLE PLAN:                                                             |
|                                                                            |
|  Opening AttacK: 1/3 Chance use Silence on Rnd Target without Silence;     |
|                       If Eligor's MP < 24:                                 |
|                        Eligor's HP  > 50%: Use Phase 1 Attack              |
|                        Eligor's HP <= 50%: Use Phase 2 Attack              |
|                                                                            |
|                  1/3 Chance use Sleepel on Rnd Target without Sleep,       |
|                        If Eligor's MP < 8:                                 |
|                         Eligor's HP  > 50%: Use Phase 1 Attack             |
|                         Eligor's HP <= 50%: Use Phase 2 Attack             |
|                                                                            |
|                  1/3 Chance skip a turn                                    |
|                                                                            |
|                                                                            |
|  Phase 1 Attack: Use <Sword Attack> on Rnd Target in Front Row; If All     |
|                   Targets are in Back Row, select Rnd Target               |
|                                                                            |
|  Phase 2 Attack: Use Mono Laser on 2nd Target; If 2nd Target has Death, do |
|                   nothing                                                  |
|                  Use Mono Laser on 1st Target; If 1st Target has Death, do |
|                   nothing (Follow-Up Attack)                               |
|                  Use Mono Laser on 3rd Target; If 3rd Target has Death, do |
|                   nothing (Follow-Up Attack)                               |
|                                                                            |
|                                                                            |
|  Phase 1: (Repeats)                                                        |
|                                                                            |
|   Eligor's HP > 50%:                                                       |
|                                                                            |
|    1/6 Chance: Use Silence on Rnd Target without Silence; If Eligor's      |
|                 MP < 24, use Phase 1 Attack                                |
|                                                                            |
|                If All Targets have Silence, use Sleepel on Rnd Target; If  |
|                 Eligor's MP < 8, use Phase 1 Attack                        |
|                                                                            |
|                If All Targets have Sleep, use Phase 1 Attack               |
|                                                                            |
|    1/6 Chance: Use Sleepel on Rnd Target without Sleep; If Eligor's        |
|                 MP < 8, use Phase 1 Attack                                 |
|                                                                            |
|                If All Targets have Sleep, use Silence on Rnd Target; If    |
|                 Eligor's MP < 24, use Phase 1 Attack                       |
|                                                                            |
|                If All Targets have Silence, use Phase 1 Attack             |
|                                                                            |
|    2/6 Chance: Use Mono Laser on Rnd Target in Back Row; If All Targets    |
|                 are in Front Row, select Rnd Target                        |
|                                                                            |
|    2/6 Chance: Use Phase 1 Attack                                          |
|                                                                            |
|                                                                            |
|  Phase 2: (Repeats)                                                        |
|                                                                            |
|   Eligor's HP <= 50%:                                                      |
|                                                                            |
|    1/5 Chance: Use Silence on Rnd Target without Silence; If Eligor's      |
|                 MP < 24, use Phase 2 Attack                                |
|                                                                            |
|                If All Targets have Silence, use Sleepel on Rnd Target; If  |
|                 Eligor's MP < 8, use Phase 2 Attack                        |
|                                                                            |
|                If All Targets have Sleep, use Phase 2 Attack               |
|                                                                            |
|    1/5 Chance: Use Sleepel on Rnd Target without Sleep; If Eligor's        |
|                 MP < 8, use Phase 2 Attack                                 |
|                                                                            |
|                If All Targets have Sleep, use Silence on Rnd Target; If    |
|                 Eligor's MP < 24, use Phase 2 Attack                       |
|                                                                            |
|                If All Targets have Silence, use Phase 2 Attack             |
|                                                                            |
|    3/5 Chance: Use Phase 2 Attack                                          |
\----------------------------------------------------------------------------/

Once I'm done I'm gonna upload each individual enemy to Paste Bin. I'm also gonna C+P the raw AI Script as opcodes+arguments, so that when the time finally comes to writing my own AI script I'll be able to take snippets from each enemy to build my own custom script. It's a really fun project! I'm sure I'll be back  soon with my next question :)

On a side note NFI, do you use Discord much? I've sent you a PM in the past but you never got...
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-05 03:49:53
Eligor made me rage quit. I understand what it was doing, but he got WAY more of a detailed script than he needed. It's main script is longer and convoluted than Ruby Weapon's; And HE controls his tentacles!

You have the 4020, 4021, 4023 and 4024 addresses correct to my notes. I haven't tested 4022 very much. It's used in 59 different enemies (none of which are the Hojo samples. Not sure where you get that they are) and they are typically enemies that have multiple parts and are controlling certain parts. Some exceptions are enemies like Chocobos (which is why I think this flag is "not necessary to defeat to win").

I used to check Discord a lot. I haven't been on lately due to lack of time. I'll give it a check now though. I did get that PM, but I didn't have anything constructive to add. I'm almost daily checking this site though.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-05 04:58:09
The reason I know that Hojo's samples use that flag is that I spotted it when working on my Enemy Formation Charts (https://pastebin.com/raw/MUcyFwwi).

Look at offset 0x0C in the Battle Formation Data (http://wiki.ffrtt.ru/index.php/FF7/Battle/Battle_Scenes#Battle_Formation_Data).These four bytes only consider the last 5 bits, and these 5 bits are what the 4020 - 4024 addresses pull.
0x01 I think isn't the Actor's visibility, but rather its "presence" in the battle. 0x02 is more accurately "Enemy grouping during a pincer attack" 0 = group one, and 1= group two (or the other way round, I can't remember). 0x04 is unknown, but you've allocated that as "Indicates if Actor needs to die for Battle to end".

The only Formation to use the "FFE3" Battle Formation Flag (Non-Targetable, Main AI script disabled and 0x04 OFF) is formation 358h, specifically the BR Sample and P Sample enemies. These are the only two enemies in the game that enter the battle with the 0x04 flag off; all other instances of that flag being toggled occur via in-battle AI script.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-05 05:13:07
When you mention enemies with multiple limbs, are you sure that you're not confusing it wih the 0x0010 offset of the Battle Setup formate (http://wiki.ffrtt.ru/index.php/FF7/Battle/Battle_Scenes#Battle_Setup_1_format)? I remember we discussed before about the fact that bit 1 was a bit of a mystery to me, and I recall you saying then that that flag has something to do with enemies with multiple limbs. In fact, I made a list of enemies which have this flag ON for the pre-battle setup flag ([FF], [FB], [EF], [F3], [EB] & [E3]):

 - Ghirofelgo / Chain
 - Mirage
 - Dorky Face
 - Jersey
 - Ying, Yang, Ying/Yang
 - Ancient Dragon
 - Schizo Left / Schizo Right
 - Guardian (+ Guardian Left and Guardian Right)
 - Carry Armour / Left Arm / Right Arm
 - Proud Clod / Jamar Armour
 - Helletic Hojo / Right Arm / Left Arm
 - Mover
 - Jenova Synthesis
 - Bizarro Sephiroth
 - Roulette Cannon / Pedestal
 - Diamond Weapon
 - Ruby Weapon / Right + left Tentacle
 - Emerald Weapon / Leg / Eye

Anyway, I don't think this is the same as the 4022 address. We see the Vice and Hell House use 4022, and they don't have multiple limbs
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-05 14:54:49
Ack! I was only looking at the AI. I know those flags pull into the 402X memory region, but I always forget to look in form data.

I didn't say multiple limbs, I said multiple parts. This includes Hell House as it essentially has two "parts". One being the thing you initially encounter and the other being the possessed thing after the initial house thing is defeated.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-06 09:06:33
Ah, my bad. I'm only assuming that 4022 is pulling the 0x04 flag in the enemy battle setup, but I could be wrong.

Anyway, in regards to the Setup Flag used (0x0010 offset in the Battle Setup Format), I still have Bit1 0x02 jotted down as ?'s, it would be good to give it a clear description as to what that flag does. I recall you saying it has something do with the animations when an enemy does have multiple limbs (not parts; I get what you're saying now about the other flag). Or, if an enemy interacts with another enemy during one of their animations (like the Movers).

A concise explanation to this flag would be really useful :). It's the 2 bytes that's usually set to FFFD to indicate a normal battle (0x02 OFF), or FFED to indicate Pre-Emp disabled.
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-06 15:06:39
I'm not going to say I'm lazy, but can you provide a link to where I discussed that. :P I'm not doubting that I said this, but I probably said other things around that and the context would make more sense to me and be able to explain it.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-06 20:22:20
Haha lol. I know it's somewhere in the PMs that we've sent each other, or possibly in a topic I posted a few years back (I'm not saying I'm lazy either.......)

Anyway, just a quickie regarding the MP again, we can see that the script doesn't work properly, I'm compiling a list of errors for DLPB and I would like to offer working fixes.

The fix that I have suggested for the MP (and if this works then the same thing can be applied to the Grunt/Guard Hound/Mono Drive also):

change:

Code: [Select]
0x040 If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 0) )
0x040 {
0x04D LovalVar:PlayerRow <- LovalVar:PlayerRow AND 1
0x057 If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 1) )
0x057 {
0x064 LovalVar:PlayerRow <- LovalVar:PlayerRow AND 2
0x06E If ( (BattleAddr(&Self).BattleAddr(&FrontRow) == 0) )

TO:

Code: [Select]
0x040 If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 0) )
0x040 {
0x04D LovalVar:PlayerRow <- LovalVar:PlayerRow <- 1
0x057 If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 1) )
0x057 {
0x064 LovalVar:PlayerRow <- LovalVar:PlayerRow <- 2
0x06E If ( (BattleAddr(&Self).BattleAddr(&FrontRow) == 0) )

This should fix the subsequent AND checks to make it work properly... I think. NFI?

Edit - actually, thinking on it, it could be a fun exercise just to tidy up the whole script, it'll put everything I've learned to the test. It's obvious that the opening bombing mission was a "trial and error" task for the coders, they probably had a cool off period after the demo was finished to restructure the way they compile the script (Enemy AI and field script; why on earth are they using [2][0] $GM to signify that the Restore Materia has been obtained haha lol??)

I'll have a go at that once I've escaped Midgar, which I honestly thought I would have done by now lol
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-06 21:10:54
Once you enter Midgar, you never leave.

Though...no. those changes don't make sense. I'm guessing you meant this:
Code: [Select]
0x040 If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 0) )
0x040 {
0x04D LocalVar:PlayerRow <- 1
0x057 If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 1) )
0x057 {
0x064 LocalVar:PlayerRow <- 2
0x06E If ( (BattleAddr(&Self).BattleAddr(&FrontRow) == 0) )
THAT would make sense. :)
I just took a look at it and at first glance the vanilla script is totally stupid. Trying to AND a value that you just set to 0 will always result in 0. However, maybe they intended this. It appears that the intended function is if all playable characters are in the back row then prioritize the machine gun action.

There is a 1:4 chance of Tonfa in the event that the LocalVar:PlayerRow = 1
There is a 1:8 chance of Tonfa if LocalVar:PlayerRow = 2
However, since neither of those are possible, it defaults to a 1:6 chance of a Tonfa

This idea probably got scrapped since both Machine Gun and Tonfa are long range attacks and their damage output is not reduced due to target's row. Rather than remove the code that would allow for this chance behavior, it is simply mitigated by not allowing it to logically happen. Then, should the need arise in the future to change it back, it's a simple fix. A SIMPLER fix is to change the initial assignment at position 0x004 to be a 3. Then these values will be correctly AND'd and produce a non-zero result. The script is still stupid for other reasons (obvious copy-pasted code blocks) but it has more function again. If I wanted to take the time I could make a much cleaner code. I'm just not sure it's worth it on such a simple enemy. Although it might provide some good practice for other scripts... I might change my mind if I find myself bored later. Actually, it is CERTAINLY not worth it because the entire script is based around the possibility that Tonfa is a short range action. Since it's not there is no need for 90% of the script. It can be as simple as
Code: [Select]
If (Not (Random MOD 6))
{
   LocalVar:0080 <- 272 (Machine Gun)
} Else {
   LocalVar:0080 <- 273 (Tonfa)
}
TargetMask <- RandomBit( AllOpponentMask )
Perform(LocalVar:0080, EnemyAttack)
SCRIPT END
That is literally all it needs to be. This prioritizes Tonfa (since it's a more damaging action) at a 5:6 ratio.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-06 22:10:32
Quote
Though...no. those changes don't make sense. I'm guessing you meant this:
Code: [Select]
0x040   If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 0) )
0x040   {
0x04D   LocalVar:PlayerRow <- 1
0x057   If ( (BattleAddr(&AllPlayers).BattleAddr(&FrontRow) == 1) )
0x057   {
0x064   LocalVar:PlayerRow <- 2
0x06E   If ( (BattleAddr(&Self).BattleAddr(&FrontRow) == 0) )
THAT would make sense. :)

Yes, indeed, that's exactly what I meant lol.

I agree that it looks quite a lot like they had left this script in as it was to give them the option to invoke the Front/Back row behaviour at a later date, or they decided that it wasn't needed so they simply overwrote it. I didn't clock that changing the initial value of LocalVar:0020 from 0 to 3 would also neatly fix the issue, kudos (kind of annoyed I didn't see that myself :))

In your custom script you've missed out the part that does work, which is the check to see if the MP is in the Front Row or back row, and we do see an instance on the Sector 8 Bridge with MP's in the back row. Here is how (I think) it should look:

Code: [Select]
MAIN:

     LovalVar:SelfRow <- 0
      If ( (BattleAddr(&Self).BattleAddr(&Row) == 0) )
      {
      LovalVar:SelfRow <- 0
      }
      Else
      {
      LovalVar:SelfRow <- 1
      If (Not  (LovalVar:SelfRow) )
      {
      LocalVar:Random <- 2
      If (Not  (Random MOD LocalVar:Random) )
      {
      BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
      LocalVar:EnemyAttack <- 272 (Machine Gun)
      }
      Else
      {
      BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
      LocalVar:EnemyAttack <- 273 (Tonfa)
      }
      Else
      {
      LocalVar:Random <- 6
      If (Not  (Random MOD LocalVar:Random) )
      {
      BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
      LocalVar:EnemyAttack <- 273 (Tonfa)
      }
      Else
      {
      BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
      LocalVar:EnemyAttack <- 272 (Machine Gun)
      }
      Else
      {
      POP(LovalVar:SelfROw)
     Perform(LocalVar:EnemyAttack, EnemyAttack)
     SCRIPT END

As mentioned, this weirdness in the script occurs in the first four enemies 10h - 13h, and all of them can have their script completely simplified in a similar way.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-07 00:47:12
Remind me again what happens to enemies that take double damage from Statuses (Sword Dance, Soldier:3rd, Corvette take double from Confusion; Palmer takes double from Sleep).

I know that they would take double damage from Added Effect+Confusion on your weapon, but does it also mean that the Confusion/Sleep infliction chance would now be 100%?
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-07 02:54:00
It just affects damage done by actions that inflict the status
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-07 07:41:04
Man the Warning Board + Machine Gun + Laser Cannon are cheesing me off.

I don't get what's going on at 0x130 here with the Machine Gun:

Code: [Select]
PRE-BATTLE:

0x000LocalVar:WarningBoard <-  (BitCount(BattleAddr(&Self).BattleAddr(&PlayerID)) == BattleAddr(&ActiveActors).BattleAddr(&PlayerID))
0x014LocalVar:WarningBoard <-  (LocalVar:WarningBoard.BattleAddr(&SelfID) == 50)
0x022LocalVar:LaserCannon <-  (BitCount(BattleAddr(&Self).BattleAddr(&PlayerID)) == BattleAddr(&ActiveActors).BattleAddr(&PlayerID))
0x036LocalVar:LaserCannon <-  (LocalVar:LaserCannon.BattleAddr(&SelfID) == 52)
0x044SCRIPT END


MAIN:

0x000LocalVar:SelfActive <- 0
0x006 If (LocalVar:WarningBoard.BattleAddr(&SelfActive))
0x006 {
0x010 LocalVar:SelfActive <- LocalVar:SelfActive OR 1
0x01A If (BattleAddr(&Self).BattleAddr(&SelfActive))
0x01A {
0x024 LocalVar:SelfActive <- LocalVar:SelfActive OR 2
0x02E If (LocalVar:LaserCannon.BattleAddr(&SelfActive))
0x02E {
0x038 LocalVar:SelfActive <- LocalVar:SelfActive OR 4
0x042 If ( (BattleAddr(&Self).BattleAddr(&AllyAngry) == 1) )
0x042 {
0x04F If (LocalVar:SelfActive == 1)
0x04F {
0x057 If ( (BattleAddr(&Self).BattleAddr(&Angry) == 0) )
0x057 {
0x064 If ( (BattleAddr(&AllPlayers).BattleAddr(&ParalyzedStatus) == 0) )
0x064 {
0x071 If (Not  (Random MOD 2) )
0x071 {
0x079 BattleAddr(&Self).BattleAddr(&SelfActive) <- 1
0x083 BattleAddr(&Self).BattleAddr(&Targetable) <- 1
0x08D BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x094 Perform("Appear"[014F], EnemyAttack)
0x09A BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x0A2 Perform("Machine Gun"[0110], EnemyAttack)
0x0A8 }
0x0A8 Else
0x0A8 {
0x0AB If (Random MOD 4)
0x0AB {
0x0B2 BattleAddr(&Self).BattleAddr(&SelfActive) <- 1
0x0BC BattleAddr(&Self).BattleAddr(&Targetable) <- 1
0x0C6 BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x0CD Perform("Appear"[014F], EnemyAttack)
0x0D3 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x0DB Perform("Machine Gun"[0110], EnemyAttack)
0x0E1 }
0x0E1 Else
0x0E1 {
0x0E4 BattleAddr(&Self).BattleAddr(&SelfActive) <- 1
0x0EE BattleAddr(&Self).BattleAddr(&Targetable) <- 1
0x0F8 BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x0FF Perform("Appear"[014F], EnemyAttack)
0x105 BattleAddr(&Self).BattleAddr(&Angry) <- 0
0x10F If (Not  (Random MOD 2) )
0x10F {
0x117 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x11F Perform("Machine Gun"[0110], EnemyAttack)
0x125 }
0x125 Else
0x125 {
0x128 }
0x128 Else
0x128 {
0x12B If (LocalVar:SelfActive == 3)
0x12B {
0x130 If ( (LocalVar:LaserCannon.BattleAddr(&DeathStatus) == 0) )
0x130 {
0x13D If (Random MOD 4 == 0)
0x13D {
0x146 BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x14D Perform("Vanish"[014E], EnemyAttack)
0x153 BattleAddr(&Self).BattleAddr(&SelfActive) <- 0
0x15D BattleAddr(&Self).BattleAddr(&Targetable) <- 0
0x167 LocalVar:LaserCannon.BattleAddr(&Angry) <- 1
0x171 }
0x171 Else
0x171 {
0x174 }
0x174 Else
0x174 {
0x177 If (Random MOD 4 == 1)
0x177 {
0x17C BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x183 Perform("Vanish"[014E], EnemyAttack)
0x189 BattleAddr(&Self).BattleAddr(&SelfActive) <- 0
0x193 BattleAddr(&Self).BattleAddr(&Targetable) <- 0
0x19D }
0x19D Else
0x19D {
0x1A0 }
0x1A0 Else
0x1A0 {
0x1A3 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x1AB Perform("Machine Gun"[0110], EnemyAttack)
0x1B1 }
0x1B1 Else
0x1B1 {
0x1B4 POP(Random MOD 4)
0x1B5 }
0x1B5 Else
0x1B5 {
0x1B8 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x1C0 Perform("Machine Gun"[0110], EnemyAttack)
0x1C6 }
0x1C6 Else
0x1C6 {
0x1C9 }
0x1C9 Else
0x1C9 {
0x1CC If (LocalVar:SelfActive == 5)
0x1CC {
0x1D1 }
0x1D1 Else
0x1D1 {
0x1D4 }
0x1D4 Else
0x1D4 {
0x1D7 POP(LocalVar:SelfActive)
0x1D8SCRIPT END

It's checking to see if Laser Cannon doesn't have the Death Status, which it always will pass seeing as the Machine Gun cannot spawn in if you've killed the Laser Cannon.

Am I missing something, or is the check at 0x130 always going to pass, meaning that use of Machine Gun at 0x1B5 will never occur?
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-07 08:13:41
OK, I think I understand it now. It's a flaw in the scripting, there should be a Death Counter.

When you kill either the Mchn Gun or Lsr Can, there is no script to toggle the 4020 "SelfActive" var from 1 to 0 (like we see when one of the counterparts disappears). Because of this, once you've killed the Laser Cannon, the next time the Machine Gun's script runs, the check at 0x02E passes and the final value that is returned is always 5, which bypasses all of the scripts and acts as though the Laser Cannon was still alive.

I think this is an error. When you kill either the MG or LC another enemy will not spawn in, and the Warning Board will just sit there till you kill it. It seems to me as though the Laser Cannon should have spawned in again once you kill the Machine Gun and vice-versa (unless the other one has been killed obv).

If you add a Death Counter script for the MG and LC that looks like this, I think that will solve the problem:

Code: [Select]
DEATH COUNTER:

0x000BattleAddr(&Self).BattleAddr(&SelfActive) <- 0
0x0OASCRIPT END

or:

DEATH COUNTER:

0x000BattleAddr(&2060).BattleAddr(&4020) <- 0
0x0OASCRIPT END

Thing is, this flag may automatically get set to 0 upon playing the normal Death script for an enemy, in which case I don't know what's going on...
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-07 09:35:53
YES! The last two hours haven't been wasted :) :)

My first bit of AI script editing was a success. It took me a while, Pr Cl didn't like it when I tried to copy and paste and delete large chunks of AI script, but I finally got the script I wanted which was this:
Code: [Select]
12 2060
10 4020
80
60 00
90

This is the death counter script that I've added for the Machine Gun and Laser Cannon, which disassembles to:

Code: [Select]
0x000: PUSH (2060) Type (12)
0x003: PUSH (4020) Type (10)
0x006: MASK
0x007: PUSH (00) Type (01)
0x009: STRA
0x00A: END

0x000BattleAddr(&2060).BattleAddr(&4020) <- 0
0x00ASCRIPT END

I know it doesn't look like much, but I'm only a beginner lol. Anyway, after trying to mess around with Steam and getting it to not delete my save, then working out which bloody scene.bin the Steam version pulls from, I finally managed to battle the Warning Board, and that script did exactly what I had intended it to do.

It has flagged the killed enemy as non-active. So, let's say I've killed the Machine Gun; normally the MG will still be flagged as active, so when the LC takes it turn it does nothing and does not spawn in. With the added Death Counter script the MG is now flagged as non-active, so the LC re-spawns in like it was supposed to.

Again, I'm only assuming that it was supposed to respawn in givent the fact that both the MG and LC have redundant script that searches for the Death status of its counterpart. And I've fixed it :) Now I can move on :)

One for DLPB
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-07 11:07:51
Need some clarification on what's going on here with the Laser Cannon:


Code: [Select]
0x161 }
0x161 Else
0x161 {
0x164 If (LocalVar:SelfActive == 3)
0x164 {
0x169 If ( (LocalVar:MachineGun.BattleAddr(&DeathStatus) == 0) )
0x169 {
0x176 If (Random MOD 4 == 0)
0x176 {
0x17F BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x186 Perform("Vanish"[014E], EnemyAttack)
0x18C BattleAddr(&Self).BattleAddr(&SelfActive) <- 0
0x196 BattleAddr(&Self).BattleAddr(&Targetable) <- 0
0x1A0 LocalVar:MachineGun.BattleAddr(&Angry) <- 1
0x1AA }
0x1AA Else
0x1AA {
0x1AD }
0x1AD Else
0x1AD {
0x1B0 If (Random MOD 4 == 1)
0x1B0 {
0x1B5 BattleAddr(&TargetSelected) <- BattleAddr(&Self)
0x1BC Perform("Vanish"[014E], EnemyAttack)
0x1C2 BattleAddr(&Self).BattleAddr(&SelfActive) <- 0
0x1CC BattleAddr(&Self).BattleAddr(&Targetable) <- 0
0x1D6 }
0x1D6 Else
0x1D6 {
0x1D9 }
0x1D9 Else
0x1D9 {
0x1DC If ( (BattleAddr(&AllPlayers).BattleAddr(&ParalyzedStatus) == 0) )
0x1DC {
0x1E9 If (Not  (Random MOD 4) )
0x1E9 {
0x1F1 If ( (BattleAddr(&AllPlayers).BattleAddr(&ParalyzedStatus) == 0) )
0x1F1 {
0x1FE BattleAddr(&TargetSelected) <- RandomBit( (BattleAddr(&AllPlayers).BattleAddr(&ParalyzedStatus) == 0) )
0x20D }
0x20D Else
0x20D {
0x210 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x218 Perform("Paralaser"[0169], EnemyAttack)
0x21E }
0x21E Else
0x21E {
0x221 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x229 Perform("Laser Cannon"[0117], EnemyAttack)
0x22F }
0x22F Else
0x22F {
0x232 BattleAddr(&TargetSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x23A Perform("Laser Cannon"[0117], EnemyAttack)
0x240 }
0x240 Else
0x240 {
0x243 POP(Random MOD 4)
0x244 }
0x244 Else
0x244 {

I'm reading this as:

1/4 chance use Vanish and set Machine Gun's "Angry" TempVar to 1
1/4 chance use vanish
2/4 chance: 1/4 chance use paralyser on rnd target without Paralyse
                  3/4 chance use Laser Cannon

which actually makes it:

2/8 chance use Vanish and set Machine Gun's "Angry" TempVar to 1
2/8 chance use vanish
1/8 chance use paralyser on rnd target without Paralyse
3/8 chance use Laser Cannon

Am I correct with this?

edit no I'm not, I'm completely wrong....
It goes:

1/4 chance use Vanish on Self and set Machine Gun's "Angry" TempVar to 1
1/4 chance use vanish on Self
2/4 chance: At least one Target without Paralyzed:
                   1/4 chance use paralyser on rnd target without Paralyse
                   3/4 chance use Laser Cannon on Rnd Target
                  All Targets with Paralyzed:
                   Use Laser Cannon on Rnd Target


Damn, the Machine Gun and Laser Cannon have been the most long winded enemies to try to decipher so far, even more so than the Eligor! Time for a break :)
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-07 16:06:58
There's a bug with the Soldier:3rd. It checks for the MP cost when using Sleepel, Ice2 and Bolt2, nut not Flying Sickle which costs 8MP. When it tries to use Flying Sickle with less than 8 MP it displays the message: Soldier:3rd's skill power is all used up
Title: Re: [FFVII] Enemy AI Script questions
Post by: Kaldarasha on 2019-03-07 17:04:27
Sounds correct to me. I guess it's like a free turn for the player and shows that the enemy has run out of MP.
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-07 18:24:08
Yeah. While it is an odd choice to check for some actions and exclude one, this is still essentially intended behavior. Soldier:3rd only has 40 MP so it can only do Ice2 or Bolt2 once without the player somehow interfering (giving an ether by mistake or something). Personally, I think most human enemies should be allowed to utilize items once or twice if the need arises. Say they can use an ether (rather, an action that imitates the ether item) if their AI selects something they don't have MP for. That makes them "waste" their turn on a utility action which makes the experience of fighting "intelligent" characters more believable. This is during the ShinRa HQ raid so you'd expect company guards would have access to such supplies. But I digress...

This is definitely one of those AIs that could be simplified.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-08 06:46:17
I can understand the want to have a few Enemies that will run out of MP, the main issue that I have with the Soldier:3rd is the fact that it will call on Flying Sickle very often once it has run out of MP, which means that after just a few uses of Flying Sickle/Sleepel (8mp) and Ice2/Bolt2 (22mp), it will spend approximately 2/3 of its turn just doing nothing, with the text string appearing at the top.

Take a look at the Soldier:3rd's simplified AI script:

Code: [Select]
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  |
|   SOLDIER:3rd's BATTLE PLAN:                                               |
|                                                                            |
|  OPENING ATTACK: 1/4 Chance start from 1st Attack                          |
|                  1/4 Chance start from 2nd Attack                          |
|                  1/4 Chance start from 3rd Attack                          |
|                  1/4 Chance start from 5th Attack                          |
|                                                                            |
|    *If All Targets have Sleep, select Rnd Target                           |
|                                                                            |
|                                                                            |
|  1ST ATTACK: (<Split>)                                                     |
|                                                                            |
|   Use <Split> on Rnd Target without without Sleep*                         |
|                                                                            |
|    FOLLOW-UP: SOLDIER:3rd's HP > 75%                                       |
|                1/4 Chance: Use <Split> on Rnd Target without Sleep*        |
|                3/4 Chance: Do Nothing                                      |
|                                                                            |
|               SOLDIER:3rd's HP <= 75%:                                     |
|                1/3 Chance: Use <Split> on Rnd Target without Sleep*        |
|                2/3 Chance: Do Nothing                                      |
|                                                                            |
|               SOLDIER:3rd's HP <= 50%:                                     |
|                1/2 Chance: Use <Split> on Rnd Target without Sleep*        |
|                1/2 Chance: Do Nothing                                      |
|                                                                            |
|               SOLDIER:3rd's HP <= 25%:                                     |
|                Use <Split> on Rnd Target without Sleep*                    |
|                                                                            |
|                                                                            |
|  2ND ATTACK: (Sleepel)                                                     |
|                                                                            |
|   SOLDIER:3rd's HP > 75%:                                                  |
|                                                                            |
|    2/8 Chance: Use Sleepel on Rnd Target without Sleep; If All Targets     |
|                 have Sleep OR SOLDIER:3rd's MP < 8, move onto remaining    |
|                 6/8 chance                                                 |
|    3/8 Chance: Use <Split> on Rnd Target without Sleep*                    |
|    3/8 Chance: Use Flying Sickle on Rnd Target without Sleep*              |
|                                                                            |
|   SOLDIER:3rd's HP <= 75%:                                                 |
|                                                                            |
|    2/4 Chance: Use Sleepel on Rnd Target without Sleep; If All Targets     |
|                 have Sleep OR SOLDIER:3rd's MP < 8, move onto remaining    |
|                 2/4 chance                                                 |
|    1/4 Chance: Use <Split> on Rnd Target without Sleep*                    |
|    1/4 Chance: Use Flying Sickle on Rnd Target without Sleep*              |
|                                                                            |
|   SOLDIER:3rd's HP <= 50%:                                                 |
|                                                                            |
|    Use Sleepel on Rnd Target without Sleep; If All Targets have Sleep OR   |
|     SOLDIER:3rd's MP < 8:                                                  |
|      1/2 Chance use <Split> on Rnd Target without Sleep*                   |
|      1/2 Chance use Flying Sickle on Rnd Target without Sleep*             |
|                                                                            |
|                                                                            |
|    After 2ND ATTACK: 1/2 Chance jump to 3rd Attack for next turn           |
|                      1/2 Chance jump to 4th Attack for next turn           |
|                                                                            |
|                                                                            |
|  3RD ATTACK: (Bolt2)                                                       |
|                                                                            |
|   SOLDIER:3rd's HP > 75%:                                                  |
|                                                                            |
|    2/8 Chance: Use Bolt2 on Rnd Target with Sleep; If no Targets have      |
|                 Sleep, select Rnd Target; If SOLDIER:3rd's MP < 22, move   |
|                 onto remaining 6/8 chance                                  |
|    3/8 Chance: Use <Split> on Rnd Target without Sleep*                    |
|    3/8 Chance: Use Flying Sickle on Rnd Target without Sleep*              |
|                                                                            |
|   SOLDIER:3rd's HP <= 75%:                                                 |
|                                                                            |
|    2/4 Chance: Use Bolt2 on Rnd Target with Sleep; If no Targets have      |
|                 Sleep, select Rnd Target; If SOLDIER:3rd's MP < 22, move   |
|                 onto remaining 2/4 chance                                  |
|    1/4 Chance: Use <Split> on Rnd Target without Sleep*                    |
|    1/4 Chance: Use Flying Sickle on Rnd Target without Sleep*              |
|                                                                            |
|   SOLDIER:3rd's HP <= 50%:                                                 |
|                                                                            |
|    Use Bolt2 on Rnd Target with Sleep; If no Targets have Sleep, select    |
|     Rnd Target; If SOLDIER:3rd's MP < 22:                                  |
|      1/2 Chance use <Split> on Rnd Target without Sleep*                   |
|      1/2 Chance use Flying Sickle on Rnd Target without Sleep*             |
|                                                                            |
|                                                                            |
|    After 3RD ATTACK: 1/2 Chance jump to 1st Attack for next turn           |
|                      1/2 Chance jump to 5th Attack for next turn           |
|                                                                            |
|                                                                            |
|  4TH ATTACK: (Ice2)                                                        |
|                                                                            |
|   SOLDIER:3rd's HP > 75%:                                                  |
|                                                                            |
|    2/8 Chance: Use Ice2 on Rnd Target with Sleep; If no Targets have       |
|                 Sleep, select Rnd Target; If SOLDIER:3rd's MP < 22, move   |
|                 onto remaining 6/8 chance                                  |
|    3/8 Chance: Use <Split> on Rnd Target without Sleep*                    |
|    3/8 Chance: Use Flying Sickle on Rnd Target without Sleep*              |
|                                                                            |
|   SOLDIER:3rd's HP <= 75%:                                                 |
|                                                                            |
|    2/4 Chance: Use Ice2 on Rnd Target with Sleep; If no Targets have       |
|                 Sleep, select Rnd Target; If SOLDIER:3rd's MP < 22, move   |
|                 onto remaining 4/4 chance                                  |
|    1/4 Chance: Use <Split> on Rnd Target without Sleep*                    |
|    1/4 Chance: Use Flying Sickle on Rnd Target without Sleep*              |
|                                                                            |
|   SOLDIER:3rd's HP <= 50%:                                                 |
|                                                                            |
|    Use Ice2 on Rnd Target with Sleep; If no Targets have Sleep, select Rnd |
|     Target; If SOLDIER:3rd's MP < 22:                                      |
|      1/2 Chance use <Split> on Rnd Target without Sleep*                   |
|      1/2 Chance use Flying Sickle on Rnd Target without Sleep*             |
|                                                                            |
|                                                                            |
|    After 4TH ATTACK: 1/2 Chance jump to 1st Attack for next turn           |
|                      1/2 Chance jump to 5th Attack for next turn           |
|                                                                            |
|                                                                            |
|  5TH ATTACK: (Flying Sickle)                                               |
|                                                                            |
|   Use Flying Sickle on Rnd Target without Sleep*                           |
|                                                                            |
|  FOLLOW-UP: SOLDIER:3rd's HP > 75%                                         |
|              1/4 Chance: Use Flying Sickle on Rnd Target without Sleep*    |
|              3/4 Chance: Do Nothing                                        |
|                                                                            |
|             SOLDIER:3rd's HP <= 75%:                                       |
|              1/3 Chance: Use Flying Sickle on Rnd Target without Sleep*    |
|              2/3 Chance: Do Nothing                                        |
|                                                                            |
|             SOLDIER:3rd's HP <= 50%:                                       |
|              1/2 Chance: Use Flying Sickle on Rnd Target without Sleep*    |
|              1/2 Chance: Do Nothing                                        |
|                                                                            |
|             SOLDIER:3rd's HP <= 25%:                                       |
|              Use Flying Sickle on Rnd Target without Sleep*                |
|                                                                            |
|                                                                            |
|    After 5TH ATTACK: Jump to 2nd Attack for next turn                      |
|>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o~~~~~~~~~~~~~~~~~~~~~~~~~~~~<|

So, once it has less than 8MP, it will:

 - Use Split on rnd target (sometimes twice)
 - 1/2 Chance use Flying Sickle
 - 1/2 Chance use Flying Sickle
 - 1/2 Chance use Flying Sickle
 - Use Flying Sickle

so of its 5 Attacks, it could spend four of those doing nothing, and seeing as the attack sequence can jump around quite a lot you could find yourself not being attacked at all by an enemy that quite frankly, shouldn't be this underwhelming.

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.

The Soldier:3rd goes from being a well thought out enemy to an enemy that poses very little risk to the Player. If anything, it would make more sense to balance it out a little bit by increasing the chance of using Split, and decreasing the chances of using Flying Sickle for 3 of the 5 attacks.

The Flapbeat only have a 1/4 chance of using Flying Sickle, so when they run out of MP at least 3/4 of the time they will still attack the player and the same thing with the Frozen Nail.

Also, I hadn't thought of the idea of having an Ether simulated attack, that's a good one, deffo gonna consider that for my mod!
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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?
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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?
Title: Re: [FFVII] Enemy AI Script questions
Post by: Bosola 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: Bosola 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: Kaldarasha on 2019-03-11 15:04:31
Isn't there a command in the field script which disables the battle win?
Title: Re: [FFVII] Enemy AI Script questions
Post by: Bosola 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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.....)
Title: Re: [FFVII] Enemy AI Script questions
Post by: Bosola 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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
Title: Re: [FFVII] Enemy AI Script questions
Post by: Bosola 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-12 02:41:37
2120 is elements in the last performed action. It doesn’t matter who the actor was.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: Kaldarasha 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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?
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 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.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ 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).
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-22 15:27:25
What's going on here with the Grangalan Jr.Jr.?

Code: [Select]
MAIN:

0x000 If (Random MOD 4 == 0)
0x000 {
0x009 }
0x009 Else
0x009 {
0x00C }
0x00C Else
0x00C {
0x00F BattleAddr(&TarSelected) <- RandomBit(BattleAddr(&AllPlayers))
0x017 Perform("Silver Wheel"[01DC], EnemyAttack)
0x01D }
0x01D Else
0x01D {
0x020POP(Random MOD 4)
0x021SCRIPT END

Is this a 1/4 chance of using silver wheel or a 3/4 chance?

the Jr.Jr. has an unused Attack (Stop Eye 01DB) which has a 71% chance of casting Stop on the target. It's likely that they decided this was too OP and removed the attack from the script (imagine how hard it would be in the battle square).

It looks to me as though they have removed the Stop Eye attack from the script but didn't clean it properly. I can't tell if this is running sequentially or not. I think it should have been initially a 1/4 chance use Stop Eye and a 3/4 chance use Silver Wheel.

Terrence Ferguson has deciphered it as a 1/4 chance of using Silver Wheel, but I suspect that it's wrong, it's actually a 3/4 chance and a 1/4 chance it'll do nothing. Testing against them seems to suggest so, anyway. NF?
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-22 20:27:41
When does the "Custom Event" script get played? On the Mystery Ninja, we can see script which disables the mystery ninja's AI script and target-ability. Does this get played upon the Enemy's death?

Also, what does "RunScript(15)" do?
Title: Re: [FFVII] Enemy AI Script questions
Post by: JBedford128 on 2019-03-22 23:49:21
RunScript(15) runs the enemy's sixteenth script: What Proud Clod calls "Custom Event 8". All custom scripts are executed in this way.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-23 02:07:07
Ah OK, cool. That answers both questions :)

The wiki marks 2018 as "DUMMY. Used in one script in a test enemy." I suspect this is accessing Memory Bank 7/F (not that it does anything with it)
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-23 15:25:00
I made an interesting discovery yesterday. I was making a list of all of the test formations, and the backgrounds that they appear on. I didn't include any duplicate formations, only unique ones, and take note of the test Encounters found on the Debug Background:

Code: [Select]
  DEBUG TEST AREA BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 1000      1st Row: 1x êúô0(äñ)                  |
|                      AP  - 100                                             |
|                      GIL - 10,000                                          |
|                                                                            |
|   Test Encounter #2  EXP - 500,000   1st Row: 2x êúô2(ñ)                   |
|                      AP  - 10,000    2nd Row: 2x êúô2(ñ)                   |
|                      GIL - 500,000   3rd Row: 1x êúô2(ñ)                   |
|                                                                            |
|   Test Encounter #4  EXP - 16        1st Row: 1x êúô7(úñA), 1x êúô8(úñB)   |
|                      AP  - 2                                               |
|                      GIL - 10                                              |
|                                                                            |
|   Test Encounter #5  EXP - 24        1st Row: 1x êúô7(úñA), 1x êúô8(úñB),  |
|                      AP  - 3                                  1x êúô9(úñC) |
|                      GIL - 15                                              |
|                                                                            |
|   Test Encounter #6  EXP - 32        1st Row: 1x êúô7(úñA), 2x êúô8(úñB),  |
|                      AP  - 4                                  1x êúô9(úñC) |
|                      GIL - 20                                              |
|                                                                            |
|   Test Encounter #7  EXP - 1000      1st Row: 1x êúô0(äñ)                  |
|      Back Attack     AP  - 100                                             |
|          Run = 2     GIL - 10,000                                          |
| Battle Won't End                                                           |
|                                                                            |
|   Test Encounter #8  EXP - 1000      1st Row: 1x êúô0(äñ)                  |
|      Side Attack     AP  - 100                                             |
|                      GIL - 10,000                                          |
|                                                                            |
|   Test Encounter #9  EXP - 2000      1st Grp: 1x êúô0(äñ)                  |
|      Pinc Attack     AP  - 200       2nd Grp: 1x êúô0(äñ)                  |
| Battle Won't End     GIL - 20,000                                          |
'----------------------------------------------------------------------------'

  FOREST BACKGROUND:
 NOTE - The first encounter could be found in the Well in Corel Prison in the
         JORG version as a 12.5% encounter chance.
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 4000      1st Row: 4x êúô0(äñ)                  |
|                      AP  - 400                                             |
|                      GIL - 40,000                                          |
|                                                                            |
|   Test Encounter #2  EXP - 96        1st Row: 1x êúô4(ëöù)                 |
|                      AP  - 6         2nd Row: 2x êúô4(ëöù)                 |
|                      GIL - 240                                             |
|                                                                            |
|   Test Encounter #3  EXP - 80        1st Row: 1x êúô4(ëöù), 1x êúô5(áñô),  |
|                      AP  - 5                                   1x êúô6(ñâ) |
|                      GIL - 100                                             |
|                                                                            |
|   Test Encounter #4  EXP - 6000      1st Row: 6x êúô0(äñ)                  |
|                      AP  - 600                                             |
|                      GIL - 60,000                                          |
'----------------------------------------------------------------------------'

  TRAIN GRAVEYARD BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 101,012   1st Row: 1x êúô0(äñ)                  |
|                      AP  - 2101      2nd Row: 1x êúô1(), 1x êúô2(ñ)        |
|                      GIL - 110,025                                         |
'----------------------------------------------------------------------------'

  DIRT WASTELAND BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 72        1st Row: 2x êúô4(ëöù), 1x êúô6(ñâ)    |
|      Back-Attack     AP  - 5                                               |
|          Run = 2     GIL - 165                                             |
'----------------------------------------------------------------------------'

  SNOW BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 40        1st Row: 1x êúô6(ñâ), 1x êúô4(ëöù)    |
|      Side-Attack     AP  - 3                                               |
|                      GIL - 85                                              |
'----------------------------------------------------------------------------'

  SEWERS BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 128       1st Grp: 2x êúô4(ëöù)                 |
|      Pinc Attack     AP  - 8         2nd Grp: 2x êúô4(ëöù)                 |
| Battle Won't End     GIL - 320                                             |
'----------------------------------------------------------------------------'

  COREL VALLEY BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 6000      1st Row: 2x êúô0(äñ) <C>              |
|  No Victory Pose     AP  - 600       2nd Row: 1x êúô0(äñ) <C>              |
| Battle Won't End     GIL - 60,000    3rd Row: 3x êúô0(äñ)                  |
|                                                                            |
|   Test Encounter #2  EXP - 72        1st Row: 2x êúô1() <C>                |
|  No Victory Pose     AP  - 6         2nd Row: 1x êúô1() <C>                |
|                      GIL - 150       3rd Row: 3x êúô1()                    |
'----------------------------------------------------------------------------'

  BLANK BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 100,000   1st Row: 1x êúô2(ñ)                   |
|                      AP  - 2000                                            |
|                      GIL - 100,000                                         |
'----------------------------------------------------------------------------'

  SISTER RAY BASE BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 100,000   1st Row: 1x êúô2(ñ)                   |
|                      AP  - 2000                                            |
|                      GIL - 100,000                                         |
'----------------------------------------------------------------------------'

  KALM FLASHBACK DRAGON BATTLE BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 100       1st Row: 2x Mighty Grunt              |
|     Auto Pre-Emp     AP  - 10                                              |
|                      GIL - 196                                             |
'----------------------------------------------------------------------------'

  COREL TRAIN BATTLE BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 100       1st Row: 2x Mighty Grunt              |
|                      AP  - 10                                              |
|                      GIL - 196                                             |
'----------------------------------------------------------------------------'

  REACTOR 5 BACKGROUND:
.----------------------------------------------------------------------------.
|   Test Encounter #1  EXP - 4320      1st Row: 3x Adamantaimai              |
|                      AP  - 600       2nd Row: 3x Adamantaimai              |
|                      GIL - 12,000                                          |
'----------------------------------------------------------------------------'

There are exactly enough formations on the Debug Background to fill out each slot on a field map! It's likely that these formations were once used on one of the blackbg maps to test encounters (the Back Attack would have filled both back-attack slots with a probable rating of 2 or 4 for each)
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-23 21:33:28
There's more debug info in the Japanese PSX edition. There is no scene.bin as that is unique to the PC edition, but the test enemies and formations were way different in the initial release. Every other edition after the first Japan release had their test enemies replaced with those pyramids.

As for 2018, this is only used in one of (all of?) the debug enemies and doesn't seem to be assigned anything.

What I'll say abou Grangalan Jr. Jr. is that PrC's AI disassembler isn't perfect. That's because the AI scripts themselves aren't perfect. Sometimes jumps will jump to jumps and other times jumps won't be reachable at all. This always throws off the disassemly. I think the Kalm wolf has some behavior like this, but it has a very simple script. Anyway, when this happens you'll have to diagnose the script by hand/sight.
TFergusson is incorrect and the script indicates a 3/4 chance of silver wheel. Take a look at the binary script:
Code: [Select]
81
60  04
34
60  00
71  000F
72  0020
72  000F
...
This is the relevant part at least. The code 71 will jump if the top two pops ARE NOT EQUAL. So the (Random Mod 4) will NOT trigger this if it is equal 0 and it will jump to 0020 at the next step. The confusing bit is the unreachable 72  000F that is located at 0x00C. This is confusing the disassembly and causing the weird output. This actually may be an artifact of the removed Stop Eye action.
Title: Re: [FFVII] Enemy AI Script questions
Post by: JBedford128 on 2019-03-23 23:25:53
There is no scene.bin as that is unique to the PC edition
The PlayStation versions use a scene.bin. Same format too-- except the original Japanese releases which had some additional padding. I think the only difference between the original English PS and PC scene.bins is the AI for the Chocobo enemy.
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-24 00:01:37
Ahh, shoot! You’re right. I was thinking about the battle.lgp that contain the enemy model data. I’ve gotten less smart lately, apparently. :P
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-24 19:52:21
Thanks for clearing that up about the Grangalan, I had a feeling it had something to do with the way the jumps were working. I've spent a little bit of time looking at the non-disassembled version and things start to make a bit more sense. Also, whenever LMSK or HMSK is used to select the target with the highest/lowest X (HP/MP/Def etc), Pr Cl displays "Bade Code" and the address of the LMSK/HMSK.

The more I learn the easier this becomes. Note to self for my mod: c+p Bagnadrana's script as a template:

Code: [Select]
0x000: PUSH (2070) Type (12)
0x003: PSHA (20A0) Type (02)
0x006: RBYT
0x007: STRA
0x008: RWRD
0x009: PUSH (04) Type (01)
0x00B: MOD
0x00C: PUSH (00) Type (01)
0x00E: JNEQ (001D)
0x011: PUSH (20) Type (01)
0x013: PUSH (0189) Type (02)
0x016: ATTK
0x017: JUMP (0048)
0x01A: JUMP (0022)
0x01D: PUSH (01) Type (01)
0x01F: JNEQ (002E)
0x022: PUSH (20) Type (01)
0x024: PUSH (01BE) Type (02)
0x027: ATTK
0x028: JUMP (0048)
0x02B: JUMP (0033)
0x02E: PUSH (02) Type (01)
0x030: JNEQ (003F)
0x033: PUSH (20) Type (01)
0x035: PUSH (01E5) Type (02)
0x038: ATTK
0x039: JUMP (0048)
0x03C: JUMP (003F)
0x03F: PUSH (20) Type (01)
0x041: PUSH (01E6) Type (02)
0x044: ATTK
0x045: JUMP (0048)
0x048: POP
0x049: END

produces an even 1/4 chance of using one of four attacks. Lovely and simple :). Only use attacks that select a single target.
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-03-24 19:54:37
Does it actually say “bade code”? I wouldn’t put it past me back then. :P
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-25 02:57:28
lol, no it doesn't, that was a runscript error in my head lol

What's going on 'ere with the 2-Faced? It seems as though the 2-Faced should have a 100% chance of countering with one of its two attacks if it is the last enemy alive, however, after testing, that's not the case:

Code: [Select]
0x000LocalVar:0000 <-  (BitCount(BattleAddr(&2060).BattleAddr(&4060)) == BattleAddr(&2050).BattleAddr(&4060))
0x014LocalVar:0000 <- LocalVar:0000 AND  NOT BattleAddr(&2060)
0x020LocalVar:0020 <-  (LocalVar:0000.BattleAddr(&4120) == 107)
0x02E If ( ( (LocalVar:0000.BattleAddr(&4000) == 1)  And  (LocalVar:0020.BattleAddr(&4000) == 0) ) )
0x02E {
0x046 If (Not  (Random MOD 2) )
0x046 {
0x04E BattleAddr(&2070) <- RandomBit(BattleAddr(&20A0))
0x056 Perform("Self-destruct"[01ED], EnemyAttack)
0x05C BattleAddr(&2060).BattleAddr(&4020) <- 0
0x066 BattleAddr(&2060).BattleAddr(&4023) <- 0
0x070 BattleAddr(&2060).BattleAddr(&4022) <- 0
0x07A BattleAddr(&2060).BattleAddr(&4024) <- 0
0x084 }
0x084 Else
0x084 {
0x087 BattleAddr(&2070) <- BattleAddr(&20A0)
0x08E Perform("Cure3"[0002], EnemyAttack)
0x093 BattleAddr(&2060).BattleAddr(&4020) <- 0
0x09D BattleAddr(&2060).BattleAddr(&4023) <- 0
0x0A7 BattleAddr(&2060).BattleAddr(&4022) <- 0
0x0B1 BattleAddr(&2060).BattleAddr(&4024) <- 0
0x0BB }
0x0BB Else
0x0BB {
0x0BE If (Random MOD 8 == 0)
0x0BE {
0x0C7 BattleAddr(&2070) <- RandomBit(BattleAddr(&20A0))
0x0CF Perform("Self-destruct"[01ED], EnemyAttack)
0x0D5 BattleAddr(&2060).BattleAddr(&4020) <- 0
0x0DF BattleAddr(&2060).BattleAddr(&4023) <- 0
0x0E9 BattleAddr(&2060).BattleAddr(&4022) <- 0
0x0F3 BattleAddr(&2060).BattleAddr(&4024) <- 0
0x0FD }
0x0FD Else
0x0FD {
0x100 }
0x100 Else
0x100 {
0x103 If (Random MOD 8 == 1)
0x103 {
0x108 BattleAddr(&2070) <- BattleAddr(&20A0)
0x10F Perform("Cure3"[0002], EnemyAttack)
0x114 BattleAddr(&2060).BattleAddr(&4020) <- 0
0x11E BattleAddr(&2060).BattleAddr(&4023) <- 0
0x128 BattleAddr(&2060).BattleAddr(&4022) <- 0
0x132 BattleAddr(&2060).BattleAddr(&4024) <- 0
0x13C }
0x13C Else
0x13C {
0x13F }
0x13F Else
0x13F {
0x142 }
0x142 Else
0x142 {
0x145 POP(Random MOD 8)
0x146SCRIPT END

I *think* the problem lies with the use of 2050, which is "Active Actors". This means that the initial script for the first check only runs if the 2-Faced is the last actor alive, which abviously can't happen becuase you'd get a game over before that.

If 2050 was changed to 2090 ("Active Enemies") would that fix the problem? I'm gonna test to find out. I've noted down 2090 as active enemies, although technically 2090 is "all actors that the current actor regards as an ally", but to make it easier in my mind I've just jotted it as active enemies (and 20B0 as Active Players).

What one earth?? Why is the vanilla steam version giving my formation IDs that are 4 out? I'm fighting the Mt. Corel Bridge enemies and the forced Cokatolis battle in Corel Prison, all the ID#s are -4. The Formation in the well was #516 1x 2-Face and 2x Bandit instead of #520 4- 2 Faced Pnicer Attack.

What's up with that? I've checked the flevel.lgp in C:\Steam Library\steamapps\common\FINAL FANTASY VII\data\field and the formation IDs are correct.

Regardless, chenging 2050 to 2090 did not work, so I've no clue what's going on here....
Ah, maybe it is correct but it's only triggering the script if it's the last *2-Faced* alive, not the last enemy. I guess I'll have to fight the Land Worm #524 to get the 4x 2-Faced #520 lol
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-25 03:59:03
OK I'd somehow corrupted the scene.bin, I've sorted that now, but the corrected script did not work. Changing it to 2050 to 2080 didn't work either.

How di I make it so the 2-Faced passes the check at 0x02E and has a 100% chance of countering when it is the last 2 Faced alive as intended?

edit - I've been trying for hours to get the script working properly. I've tried changing:

0x014LocalVar:OtherActors <- LocalVar:OtherActors AND  NOT BattleAddr(&2060)

to

0x014LocalVar:OtherActors <- LocalVar:OtherActors AND  NOT BattleAddr(&2060).BattleAddr(&4060)

That took me a while to work out that the MASK opcode was needed. that didn't work.

Then I tried changing

If ( ( (LocalVar:0000.BattleAddr(&4000) == 1)  And  (LocalVar:0020.BattleAddr(&4000) == 0) ) )

to

If ( ( (LocalVar:0000.BattleAddr(&4000) == 1)  And  (LocalVar:0020.BattleAddr(&4000) == 1) ) )

that didn't work. Then I tried the same thing but the 2050 to 2080. Still didn't work. Then I tried a combination of all different things, and now I give up and I'm going to bed.

I just wanna make sure I understand this correctly

0x000LocalVar:0000 <-  (BitCount(BattleAddr(&2060).BattleAddr(&4060)) == BattleAddr(&2050).BattleAddr(&4060))

First this looks at the bit of the current character ID, and changes it to the bit of all active character iDs. So if there are four enemies (in the well) then there are 7 actors, and LocalVar:0000 to this point will be 127, or [0111 1111].

Then, the LovalVar:000 AND NOT 2060 toggles the bit of the current actor, so let's say we are dealing with the 2-Faced A (bit 3), then 0x04 gets toggled so we end up with 119, or [0111 0111].

The final bit that sets LocalVar:0020 sets the bitmask of 0000 to match any other actors that have the ID of 6B (107) so LocalVar 0020 becomes 112, or [0111 0000].

As it stands, it makes no sense because the check is to see if 6 other actors are dead, and the other 2-faced enemies are still alive. It's nonsensical, assuming I'm reading correctly. The point is, how do I fix it? The things I've tried should have worked but for now I can't see straight....

Lol, so much for blasting through all of the Corel Prison enemies in one night, I didn't even get past the first enemy haha. Been staring at the 2-faced for about 6 hours lol
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-27 18:35:55
OK, I've been completely misreading what 4060 is. TF has cleared it up (the wiki could do with being a little clearer):

  Formation ID
    Used to determine whether multiple monsters are part of the same 'group'.
    A Formation ID is visible in battles with multiple monsters of the
    same type by looking at the 'A'/'B'/'C' after the monster's name.
    So, "Hammer Blaster A" has a Formation ID of 0, while "Hammer Blaster B"
    has a Formation ID of 1.

I think I've analysed the problem correctly. This is what I've written up on the 2-Faced:

A flaw exists with the 2-Faced's AI Script. If the other (non 2-Faced) enemies that share the same 'Group ID' as the 2-Faced have been killed (2-Faced A = Group 0; 2-Faced B = 1 etc), then the 2-Faced *should* have had a 100% chance of activating its Counter Script, with a 50/50 chance of using either Attack. As it stands, the Counter script will check for the Death Status of the other 2-Faced in the same group, which is illogical due to the fact there can only be one "2-Faced A" (or "B" or "C").
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-03-27 23:29:52
What's going on with the Harpy's general counter script? It changes the player flag of the Chocobo the lower its HP. The only way the script runs as it should is if LocalVar:0020 is set to 3, seeing as the Chocobo is is the 4th Actor (1st Enemy). This only happens when the Harpy's HP is <= 25%.

This doesn't appear to be a glitch either. It seems to be intentional. God only knows what they were trying to do there...

If you manage to kill the Harpy while its HP is above 25% then the player flag for the Chocobo is changed, and the Chocobo only exists in the 1st enemy slot, slot "3" so the default Chocobo Capturing script is *not* executed from the harpy. Instead you have to wait for the Chocobo's ATB guage to fill, at which point the Chocobo will be captured. The Chocobo may say "Wark! Wark!" as if it is about to run away (instead of displaying "Captured a Chocobo"), but the script still ends the battle and gives you the Chocobo if this happens. TF reported that the Chocobo would flee the battle in this instance, but I haven't seen that happen yet.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-05 01:11:33
I'm pretty convinced that 42B0 is not the Actor's AP value. I think it has something to do with the evade animation used.....
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-05 04:08:54
What is the following doing, found on Yang's Physical and Magical counter script?

PUSH (24) Type (01)
PUSH (00) Type (01)
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-04-05 14:18:28
I'm pretty convinced that 42B0 is not the Actor's AP value. I think it has something to do with the evade animation used.....

Yeah, totally NOT AP. 42E0 is EXP and 42C0 is Gil so I assumed that 42B0 was AP. It looks like it's a damage reaction animation. When a player misses the enemy just restarts their idle animation. There is no special "dodge" animation on enemies.

What is the following doing, found on Yang's Physical and Magical counter script?

PUSH (24) Type (01)
PUSH (00) Type (01)

That's actually queuing an action of command index 24. I have no idea what its affects are. If a flag is set, it sets some other flags. ??? TF's FAQ doesn't have a clue what it does either. It's not a graphical thing...

If BF2E08 bit 1 is active, set 9AEA78 to 1. That just skips a few steps in the command flow. I can't tell that it actually does anything.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-07 04:19:30
Ah, I thought I was connecting some dots. The Black Bat has the action 'Dodge' [023A] which is not used via the AI script. I noticed that the Animation value was 4, so I made the assumption that the 42B0 assignment to 4 meant that it was using that animation.

A quick question regarding Added Effect: I'm fairly certain that you get a *fresh* 19% chance for each Status you have on your weapon (Hades) when you attack, as opposed to a single 19% chance to inflict every status (correct me if I'm wrong).

My question is this - if you pass the chance to inflict Stop on an enemy that already has Stop (or Paralyzed on an enemy that is Paralyzed etc), will the 'Stop Timer' reset to 0, or will it remain unaffected?
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-04-08 17:34:18
A quick question regarding Added Effect: I'm fairly certain that you get a *fresh* 19% chance for each Status you have on your weapon (Hades) when you attack, as opposed to a single 19% chance to inflict every status (correct me if I'm wrong).

That would make sense, except..... It looks like a few fuzzy logics are in play here. First, it would appear that multiple strikes would make multiple attempts to inflict. Second, it seems like there's a logical oddity that means you can only inflict, remove, or toggle effects, but it's not possible to do all three with vanilla data. Third, the code assumes that there is single inflict chance for all statuses. It's flat for all added-effect statuses. There's just as much chance that you can cause Death and Sleep on the same action.

My question is this - if you pass the chance to inflict Stop on an enemy that already has Stop (or Paralyzed on an enemy that is Paralyzed etc), will the 'Stop Timer' reset to 0, or will it remain unaffected?

Looks like the timer is only set when the status actually changes. I believe the logic of the battle is that infliction will miss if attempting to inflict a timed status on a target that already has that status. I just tested with Barrier and it missed an already barrier'd target.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-11 19:07:04
What happens if a target's condition changes after the actor's ATB gauge has filled, but before its turn?

Let's say that the actor will use Attack A on a rnd target without Frog. Does the script run as soon as its ATB is filled, or does it run when it is about to perform its Attack? If it selects its target as the ATB is filled, and the target that is chosen becomes inflicted with Frog after the script is run, will it still try to use the attack on the target, even though the target has now become inflicted with Frog (where the check in the script targets an enemy without Frog)?
Title: Re: [FFVII] Enemy AI Script questions
Post by: NxK on 2019-04-11 20:40:17
What happens if a target's condition changes after the actor's ATB gauge has filled, but before its turn?

Let's say that the actor will use Attack A on a rnd target without Frog. Does the script run as soon as its ATB is filled, or does it run when it is about to perform its Attack? If it selects its target as the ATB is filled, and the target that is chosen becomes inflicted with Frog after the script is run, will it still try to use the attack on the target, even though the target has now become inflicted with Frog (where the check in the script targets an enemy without Frog)?

Scripts are only run when they are about to be executed, i.e. the moment they are to be run, which is generally quite a while after the respective ATB gauge has been filled up. This makes redundancy scenarios like the one you envision impossible.

Let's say you are to fight six Touch Me in a pincer attack with just Cloud and these six Touch Me had their AI altered to use Frog Song every turn if at least one character is not a Frog and Frog Jab otherwise. Now, since it's a pincer attack, all of these six Touch Me start with their ATB gauges filled at the beginning of the battle. If scripts being run as soon as ATB gauges are filled were the case, these six Touch Me should all cast Frog Song on Cloud. However, that is not what would happen. The first Touch Me to act would cast Frog Song, turning Cloud into a Frog (let's say all attacks cannot miss), the second one would use Frog Jab, removing the Frog status from Cloud, then the third Touch Me would cast Frog Song again, the fourth one Frog Jab, the fifth one Frog Song and the sixth one Frog Jab again.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-11 21:34:52
Thanks for clearing that up :) I suspected as much, I just needed to be sure
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-14 23:14:20
I'm a little bit confused what this script is doing with the Jumping:

Code: [Select]
0x008: PSHA (20A0) Type (02)
0x00B: PSHA (4000) Type (00)
0x00E: MASK
0x00F: PUSH (01) Type (01)
0x011: EQU
0x012: CNTB
0x013: PUSH (01) Type (01)

0x008 If (BitCount( (BattleAddr(&20A0).BattleAddr(&4000) == 1) ) == 1)

It seems to be checking to see if exactly one player has Death, but after testing this check didn't seem to do anything; the Jumping appeared to have a 1/2 chance of using Club Sword regardless of how many players were dead (it's supposed to have a 1/4 chance of using the attack if the check passes).

TF seemed to think this check will always fail, aswell
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-22 00:29:16
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.

The script with the Roulette Cannon is basically a copy and paste of part of Bottomswell's script. It makes sure that it will only use the attack if two or more players are alive; if only one player is alive, it will not use the attack, and target selected will be 2040.

It looks like it's just a coincidence that the Soldier:2nd and roulette cannon appear together and both use 2040. Until now I've been able to dismiss the var, as it doesn't seem to be doing much. With the Soldier:2nd, however, there's quite a lot going on with 2040. I kind of need to know what's going on with it before I can move on....

Just decided to have a quick look at Carry Armor. Any idea what 4300 is doing?

2090. There's another new one that I don't know what it's doing
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-22 03:13:45
OK, after testing it seems that 4300 is used when a playable character is imprisoned within Carry Armor's Arms, and is used to damage the imprisoned actor, amongst other things. I'll look into it more thoroughly when I actually get to Carr Armor.

For now though, I've spent quite a lot of time with Cheat Engine, and 2040 never seems to be any value other than 0. I think I was correct in my original assumption that this will reset the var to 0, as that's exactly what it seems to be doing.

-edit- The more I look at it, the more I'm sure that 2040 is used to clear the last attacker that is set as the SOLDIER:2nd's target. The same thing is used for the Elfadunk.
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-04-22 18:40:07
I'm a little bit confused what this script is doing with the Jumping:

Code: [Select]
0x008: PSHA (20A0) Type (02)
0x00B: PSHA (4000) Type (00)
0x00E: MASK
0x00F: PUSH (01) Type (01)
0x011: EQU
0x012: CNTB
0x013: PUSH (01) Type (01)

0x008 If (BitCount( (BattleAddr(&20A0).BattleAddr(&4000) == 1) ) == 1)

It seems to be checking to see if exactly one player has Death, but after testing this check didn't seem to do anything; the Jumping appeared to have a 1/2 chance of using Club Sword regardless of how many players were dead (it's supposed to have a 1/4 chance of using the attack if the check passes).

TF seemed to think this check will always fail, aswell

I have been trying and trying to figure this one out. It seems that the 4X family SKIPS character 0. I'm still looking into this, but in my test character 0 was definitely dead and the count of successes was still 0.

EDIT:
I got it! 20A0 is the LIVING enemy actors. So testing the death status against living targets will always result in a 0.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-22 20:18:04
^^ Awesomness dude, that one was bugging me for a while. It did inspire me to work with Cheat Engine a bit more closely. I've mapped out all of the six enemy slots LocalVar addresses, which helps to see what is going on in real-time. I've also found the address that deals with the next Formation ID, so I can do tests on any formation I want without having to load save files or mess around with MR lol.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-24 02:22:07
OK, after staring at Ultimate Weapon for hours, I now know precisely what's going on. The A0 opcode was throwing me off for a bit, but I figured it all out in the end (without having to ask for help lol). It's interesting to note that a small bug occurs once its HP has dropped to less than 65,536; it won't store the lower byte's value and will undercut the stored HP by up to 255.

Anyway, I've observed that the 0x004 flag in 0x0C1F of the save map is not a flag indicating that Ultimate has been killed; it's a flag that signifies that its HP has reached 'critical' and is ready to move onto the Cosmo Canyon phase. 0x001 is used to indicate that it has been killed, which is also used by the Battle Square, but technically 0x001 should be "Ultimate killed", and 0x004 should be "Ultimate HP Critical" or something to that effect.

edit I didn't realise how easy it was to make an account and edit the wiki (at http://wiki.ffrtt.ru). I'm gonna start updating the save map as and when.

double edit - there is no glitch with Ultimate Weapon; I had the value stored as 2 bytes, not 3 (dur). Cheat Engine doesn't allow for three bytes, just 2 or 4, which means if I set it to 4 bytes, the value overspills into the next byte. I've had to separate out its HP into three separate bytes, which isn't too hard to manage: (Byte3 * 65,536) + (Byte2 * 256) + (Byte1).
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-04-30 15:06:14
Any notion as to why Diamond Weapon is trying to "RunScript(15)", when there is no script for Custom Event 8? I wonder what the intention was there...
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-04-30 18:35:07
Dunno. The only other enemy that I can think that uses RunScript is Mystery Ninja. However, she uses it to end the battle after 10 turns without triggering the death script that the WM scripts are checking for. Maybe Diamond was going to do something similar (or a placeholder was added in case something came up). As it is the WM script to trigger the battle just engages the battle and begins an FMV after the battle is over. Maybe there could have been other outcomes to the battle. That would have been neat.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-02 15:58:09
21A0 "During Emerald Weapon battle, keeps track of how many eyes are active. (Possible use in other battles, too)"

It's also used by the Manhole to identify which Manhole is currently active. I can't think of a global name for 21A0, so I've just called it "ManholePosition" for the MH and "EyesAlive" for EW.

-edit- it's also used by the final Turks Battle to indicate that the Battle has ended once one of the Turks has been killed
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-04 15:11:52
It's peculiar that Helletic Hojo opts to uses 402D to lift the Death Status of the Right Arm..

I see; it's because it has to use "Right Arm Revive" on itself as an animation; it does not target the Right Arm.

If the script is setting the 4000 (death status) of the Right Arm to 0, why is it necessary to also toggle the 402D flag? Is it necessary? The Nibel Wolf do not need to use this flag.

I'm going to assume that any ability that removes the Death Status also toggles the 402D flag in the background. I know that 4000 is used for the 'Death' checks, but what does 402D actually do? When Carry Armor imprisons you, it also flags you as dead for the subsequent checks, but, the character's HP does not drop to 0, and, it will not show in red. Is this due to the fact that 402D isn't being toggled? If you try to use a battle item such as a Megalixir, the imprisoned character's HP *will* show in red.

4000 = death flag for AI Script checks?
402D = red hp, non-targetable, battle ends if all three 402D flags toggled?

Just guessing here; I'm curious to know what's going on with 402D. So far, only the Pyramid and Waterpolo have made use of 402D, so I haven't looked too deeply into it till now.

In fact, while on the subject of Hojo, I'd like to raise the question of the test formations soft locking for instances where the "Next Battle" flags are used. Instead of spawning in the subsequent test formation, the game will freeze, I've always been curious as to why this is.

Looking at Lifeform Hojo, I can see that three will spawn in, two being invisible and non targetable. The AI script for Lifeform even goes out of its way to ensure that these enemies have 0 HP, and are flagged as dead. TF has documented that the reason for this is to fill the two slots left over from the Right Arm and Left Arm, to stop the game from trying to access values from the left over templates, which makes sense.

Would this be the reason the test enemy formations crash when trying to make use of the Next Battles; because the enemy slots that contained enemy details are not being set to 0, so the game tries to load enemies that are not part of the new formation being loaded? Obv Heli/Hundred uses one enemy in each battle, so the problem will not occur there.

Even more things to consider if/when I ever start working on a mod...

Oooh, just spotted a flaw in the Right Arm's script that needs raising. It tries to use an attack that is not assigned to it's Attack list; it tries to use the 11F no-named attack instead of the 120 no-named attack that is assigned to it. Doesn't this create a bug of sorts? I recall another instance where an enemy tries  to use an attack it doesn't have, was it the Admanataimai? I think it is...... why doesn't the Right Arm fall victim to a similar bug? Is it due to the fact that the action 11F is loaded into the scene?

Nuts, I've just spent the last hour scratching my head as to why Helletic will never set its anim ID to 14 - it's because I'd copied the same script for the Right Arm onto the file I'm working on for the Left Arm LOL, no wonder things weren't making sense.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-08 15:55:42
Any idea what 4278 does?

Jen Synty A's 4278 = 3
Jen Synty B's 4278 = 10h
Jen Synty C's 4278 = 14h

Could this be something to do with the positioning of the "Target" icons that appears when you press R1 (or L1)? I can't really move on from Jen Synth until I've solved the 4278 mystery. It's possible that it could be a dummied out function; an educated assumption suggests that the N Crater was designed independently from the rest of the game, as opposed to sequentially; the $GM jump supports this.

It's also curious that the Jen Synth battle overwrites the same addresses in the save map used for Ultimate's HP (and Pandora's Box, seeing as P Box uses the fourth unused byte for U Weapon's HP). I'll look more into 005B - 005E when I get to Biz Seph, as I think that's what they are controlling. Obv the developers didn't account for the Yuffie warping glitch, so overwriting the UWeapon and PBox vars makes sense, as they won't be needed again for anything else.

I wonder what would happen if you Yuffie warped from the Jen fight to any point after the first UWeapon encounter, but before defeating it properly? My guess is that that the vars would be set to 0, so when UW spawns in at Junon it'll have 0 HP; one hit would trigger the death counter to set its HP to 20,000 and fly to Cosmo.

Also, what's the difference between 2008 and 2150? They both seem to have the same function (index of the last action performed)
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-08 19:05:07
4278 definitely has nothing to do with the Target icon

What if it's a bitmask for something?

Jen Synth A = 3 = 0x01 and 0x02 ON
Jen Synth B = 10 = 0x10 ON
Jen Synth C = 14 = 0x10 and 0x40 ON

What could it be toggling? It's not Elemental resistances, I've tested for that....

All other battles have these values set to FF, so maybe it's not that it's toggling the bits ON, but rather, it's toggling all of the other bits OFF. Or maybe it's just a dummied function that does nothing at all. I'd very much like to know.

-edit- Wow, there's quite a lot going on with the final bosses. I'm gonna leave 4278 until I've looked as Biz Seph, Safer and Emerald, as they all have something going on with 4278

2180 is another mystery only seen thus far with Carry Armor.

2165 is definitely 'disable rewards screen', as I tested it on a random encounter. I didn't clock the Formation Pre-Battle Script with Jen Synth; I'm glad I found it, I was banging my head against the wall trying to find the field script that disabled the Rewards flag (I was convinced it was set via the same BTLMD opcdode used in the Flashback lol).
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-09 04:47:23
I've sussed it !! :)

4278 is controlling which body part of the enemy is being selected.

It's not a bit-flag at all. The value is indicating which section of the actor's body to select The penny dropped while battling the Zemzelett. Toggling 4278 mid-battle had no effect, however, setting it to 1 pre-battle meant that it would instead target the tail.
I went through fifteen battles incrementing the value by one each time, and each time the finger icon would target one of various parts; its back, its beak, its right shoulder, its right wing, the base of its tail, the tip of its tail......

So that's it! 4278 = Body part of the actor selected! I've spent about 8 hours on that today. I gave up and watched three hours of TV then came back to it with a fresh perspective, and solved it within an hour :) Now I can go to bed :) Soooo glad to put that one behind me

Lol. If you set Jenova Synthesis 'B' 4278 to 10 instead of 16 (decimal), then it will instead target the actual tentacle, and not the part of the body where the tentacle stems out of. The finger icon even sways with the tentacle, it looks cooler than the default setting
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-18 10:24:57
Does anyone know how Cloud's Limit is being set to lv 4 for the final Sephiroth encounter?

I can see that Safer uses 2160, and toggles 0x10. After testing, I can see that this will allow Cloud to learn all Limits following the battle; however, it does not automatically set the available Limit ability to Omnislash.

Sephiroth toggles 0x08 of 2160, which does a number of things, one of which *isn't* setting Cloud's Limit to Lv 4. After testing against Rnd Encounters, Cloud's Limit Level will say Lv 4 in the command window, but the previous abilities will be available from the level that was selected prior (Meteorain and Finishing Touch). This is after having done a battle with 0x10 ON, then doing another battle with 0x08 ON.

Also, I can see that there are a few additional animations used for the final battle. Firstly, the intro to the battle does a little "face flash" to Cloud then Seph a few times; Cloud's battle model uses a different model; Omnislash uses a slightly different animation (does a quick "face flash" on the final blow). Would it be safe to assume these are being handled via the AB files in the battle.lgp? Would it be possible that Cloud's Limit ability being set to Omnislash is also being handled by the same script?
Title: Re: [FFVII] Enemy AI Script questions
Post by: Sega Chief on 2019-05-18 14:02:16
That top row used in the camera data of the formation seems to be involved in some way, I think it triggers the dynamic camera flashes, etc and they won't play if you change it to something else. You also probably already know that if the battlebg is used Cloud's hi-res model will be called for it in place of his regular one.

That entire battleBG must have scripts running in the back of it though; odd things will happen if it's used for a normal encounter, such as looping animations, moving models, replaying the wrong sound-effects, etc.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-19 12:49:42
That entire battleBG must have scripts running in the back of it though;

Yeah, I agree, there's definitely stuff going on in the background. I was testing with 0x08 Flag ON for 2160 on some random encounters, and a few things were constant every battle, such as the Limit command being available even without the Limit gauge being filled. I would also lose control of the characters after the first action performed by the enemy.

The thing that changed every battle is the use of the other command; sometimes I would only have the Limit Command available with no other option, and sometimes the normal command window would open. Also, sometimes the player's ATB would start filled, and sometimes they would start at random positions. There's definitely stuff going on in the background somewhere that makes sure this battle plays out properly.

Most of it I'm not too bothered about - the main query is exactly how Omnislash is becoming available as the Limit ability. I can't see anywhere that toggles this.
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-05-19 14:11:13
Have you checked the formation AI?
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-19 14:48:46
Yup! Pre-Battle formation script for Sephrioth:
Code: [Select]
0x000BattleAddr(&2160) <- BattleAddr(&2160) OR 8
0x00ABattleAddr(&2160) <- BattleAddr(&2160) OR 32
0x014BattleAddr(&2180) <- 222
0x01ASCRIPT END

0x20 is toggling the custom "No Rewards Screen" (which is also toggled via 2110 OR 8 as part of the "Custom Event 8").

0x08 is toggling the "Final Battle Paramaters" which sets up the unique conditions for the final battle (Limits always filled, lose command window after one action by the enemy, ATB starts filled etc; it also works in conjunction with 2180 to allow the Limit to fill at a specific rate of 222).

Here is the Pre-Battle Formation AI Script for Safer:

Code: [Select]
0x000BattleAddr(&2160) <- BattleAddr(&2160) OR 48
0x00ASCRIPT END

0x20 again toggles the no rewards screen

0x10 allows Cloud to learn all limit breaks following the battle; however, Omnislash is *not* automatically set as the available ability to use (after testing against random encounters). The "Limit Level" will say Lv 4 when bringing up the in-battle Limit menu, but the abilities available will be the ones set previously (after doing one battle with 0x10 toggled then a second with 0x08 toggled):

(https://i.imgtc.ws/rA5NSb7.png)

It doesn't matter what condition I set up, Cloud never has Omnislash ready by default. The *only* time that Omnislash is available when recreating the steps used for the final two battles is during the final two battles; somewhere after defeating Safer and battling Sephiroth Cloud's Limit ability is set to Omni. I've scoured the field scripts thoroughly; it's not there (95% sure). I can only assume that there is some special script running somewhere for this battle....
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-05-20 00:59:41
I promise I am a smart person. :P

Check the formation type. I believe it's type 8. That's a special unique type for that battle that might be the cause.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-21 13:57:50
Ah, that could be it. I've tried changing 2140 as the battle begins, but that's not the address I need to edit. I'll have a play around with Cheat Engine at some point. I tried activating the final battle flag for the Pagoda enemies with Yuffie, as they also make use of Battle Type 8; obviously, the final battle flag is for Cloud only, so I couldn't really test there. I guess I could just edit the scene.bin, but it would be quicker to find the appropriate addr in CE.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-24 10:16:21
Lol, just when I thought I had no more questions...

It seems Ruby makes use of a special "Pincer Attack" (Formation Type 4). All characters will be front-facing for this battle, but they will all still take double damage from the first use of Big Swing.

Also, if you command a character to feed another character a healing item such as an X-Potion, they will then face the opposite direction to then take double damage from Big Swing (Ruby's only Physical attack).

Also, the default "Attack from both sides"! prompt will not be displayed. Any idea what's controlling all of this?

Looking at the WM script:

Called by:
Model($UnknownModel29).Function($UnknownFunc28)
Addr   Code
2f65   SetControlsEnabled(#0000)
2f69   Op31c(#0001)
2f6d   CALL Model(GetSpecial(0008)).Function(0017)
2f6e   Op334()
2f6f   WaitForWindowReady()
2f79   SetWindowDimensions(#0080, #00a0, #0040, #0019)
2f7f   SetWindowParams(#0000, #0000)
2f80   WaitForWindowReady()
2f84   SetWindowMessage(#0022)
2f85   WaitForMessageAcknowledge()
2f89   SetCameraShakingEnabled(#0001)
2f8f   Op333(GetSpecial(0008), #0000)
2f93   PlaySoundEffect(#017f)
2f97   Op31f(#001e)
2f9e   IF ((GetSpecial(0008) == #0003)) {
2fa3     SetNumWaitFrames(#0019)
2fa4     Wait()
2fa5     GOTO 2fb4
}
2fad   IF ((GetSpecial(0008) == #0013)) {
2fb2     SetNumWaitFrames(#000f)
2fb3     Wait()
}
2fb7   Op31f(#0000)
2fbb   SetNumWaitFrames(#0019)
2fbc   Wait()
2fc4   Op3fd(#0001, #0006, #000a)
2fca   ActiveEntity.PlayAnimation(#0002, #0000)
2fce   PlaySoundEffect(#01b1)
2fd2   SetNumWaitFrames(#003c)
2fd3   Wait()
2fd7   Op3fe(#0001)
2fdb   Op34a(#0006)
2fdf   SetCameraShakingEnabled(#0000)
2fe5   ActiveEntity.PlayAnimation(#0000, #0001)
2fe9   SetNumWaitFrames(#0028)
2fea   Wait()
2ff0   WRITE Bank0[0387].Bit(5) = #0001
2ff4   TriggerBattle(SceneId=#03d6)
2ff8   Op31c(#0000)
2ff9   RETURN
Function 2ffa

I know the Save Map has this down as "0x20: World map Ruby Weapon form. bit=0: Small Form (before first encounter). bit=1: Big Form (after first encounter).", but, is there any chance that this could also be triggering some special battle flag?

Also, I can't work out exactly how to get the characters to face the tentacles when they are not buried; some weirdness is definitely going on here...
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-24 11:09:39
According to the Fandom Wikia:

"The PC version has no restrictions on the use of Whirlsand and it will use it often at the start until only one character is alive or remaining. Ruby Weapon is invulnerable to everything, even defense-ignoring attacks or status effects until its tentacles are exposed.

I've had a look at the '97 PC version scene.bin and the Steam version, I can't see any difference in the AI script compared the PSX Pal version, or any other version for that matter (excluding the JORG and JINT versions).

Am I mistaken? Is there a difference in Ruby's use of Whirlsand in the PC version(s)?
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-29 14:34:38
Any idea what 2018 does?

Cloud's AI Script:

Code: [Select]
PHYSICAL COUNTER:

0x000LocalVar:BattleLovePointsOffset <- 255
0x006 If (BattleAddr(&Self).BattleAddr(&ChrCovered) == 19)
0x006 {
0x012 LocalVar:BattleLovePointsOffset <- 80
0x018 }
0x018 Else
0x018 {
0x01B }
0x01B Else
0x01B {
0x01E If (BattleAddr(&Self).BattleAddr(&ChrCovered) == 18)
0x01E {
0x023 LocalVar:BattleLovePointsOffset <- 81
0x029 }
0x029 Else
0x029 {
0x02C }
0x02C Else
0x02C {
0x02F If (BattleAddr(&Self).BattleAddr(&ChrCovered) == 21)
0x02F {
0x034 LocalVar:BattleLovePointsOffset <- 82
0x03A }
0x03A Else
0x03A {
0x03D }
0x03D Else
0x03D {
0x040 If (BattleAddr(&Self).BattleAddr(&ChrCovered) == 17)
0x040 {
0x045 LocalVar:BattleLovePointsOffset <- 83
0x04BPOP(BattleAddr(&Self).BattleAddr(&ChrCovered))
0x04C If ( (LocalVar:BattleLovePointsOffset != 255) )
0x04C {
0x055 TempGlobal <- &GlobalVar(0000)
0x05B BattleAddr(&2018) <- BattleAddr(&MemBank1/2)
0x062 BattleAddr(&MemBank1/2) <- BattleAddr(&MemBank1/2) + 3
0x06C If ( (BattleAddr(&MemBank1/2) > 200) )
0x06C {
0x075 BattleAddr(&MemBank1/2) <- 200
0x07B GlobalVar(0000) <- &TempGlobal)
0x081 Debug.Print: "LOVEPARA CHR:%d, %d->%ds" ; BattleAddr(&Self).BattleAddr(&ChrCovered) - 16; BattleAddr(&2018); BattleAddr(&MemBank1/2)
0x081 }
0x0ABSCRIPT END


ALLY DEATH:

0x000LocalVar:ChrID <- 0
0x006 If ( (LocalVar:ChrID < 3) )
0x006 {
0x00F BattleAddr(&TarSelected) <- FlagBit(LocalVar:ChrID)
0x017 If (BattleAddr(&TarSelected).BattleAddr(&DeathStatus))
0x017 {
0x021 LocalVar:BattleLovePointsOffset <- 255
0x027 If (BattleAddr(&TarSelected).BattleAddr(&GrpFormID) == 19)
0x027 {
0x033 LocalVar:BattleLovePointsOffset <- 80
0x039 }
0x039 Else
0x039 {
0x03C }
0x03C Else
0x03C {
0x03F If (BattleAddr(&TarSelected).BattleAddr(&GrpFormID) == 18)
0x03F {
0x044 LocalVar:BattleLovePointsOffset <- 81
0x04A }
0x04A Else
0x04A {
0x04D }
0x04D Else
0x04D {
0x050 If (BattleAddr(&TarSelected).BattleAddr(&GrpFormID) == 21)
0x050 {
0x055 LocalVar:BattleLovePointsOffset <- 82
0x05B }
0x05B Else
0x05B {
0x05E }
0x05E Else
0x05E {
0x061 If (BattleAddr(&TarSelected).BattleAddr(&GrpFormID) == 17)
0x061 {
0x066 LocalVar:BattleLovePointsOffset <- 83
0x06C POP(BattleAddr(&TarSelected).BattleAddr(&GrpFormID))
0x06D If ( (LocalVar:BattleLovePointsOffset != 255) )
0x06D {
0x076 TempGlobal <- &GlobalVar(0000)
0x07C BattleAddr(&2018) <- BattleAddr(&MemBank1/2)
0x083 BattleAddr(&MemBank1/2) <- BattleAddr(&MemBank1/2) - 5
0x08D If ( (BattleAddr(&MemBank1/2) < 50) )
0x08D {
0x096 BattleAddr(&MemBank1/2) <- 50
0x09C GlobalVar(0000) <- &TempGlobal)
0x0A2 Debug.Print: "LOVEPARA CHR:%d, %d->%d" ; BattleAddr(&TarSelected).BattleAddr(&GrpFormID) - 16; BattleAddr(&2018); BattleAddr(&MemBank1/2)
0x0CC LocalVar:ChrID <- LocalVar:ChrID + 1
0x0D6 LOOP 0x006
0x0D9 SCRIPT END

The only logical thing it can be doing is setting the offset value to the Mem Bank access value. It's obvious that the script is not adjusting $GameMoment (GlobalVar 0000). 2018 must be using the value of LocalVar:0000 (BattleLovePointsOffset) to set the membank value to record to.

?
Title: Re: [FFVII] Enemy AI Script questions
Post by: NFITC1 on 2019-05-29 16:40:36
2018 is the address that the "write global var" function reads to know where in the savemap the value is to be written/read from.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-29 20:11:33
Thanks, thought as much.

Think I'm done with my AI Script questions now, thanks for all the help!
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-30 14:31:00
OK, I lied, one last question before I can wrap this thing up :)

What's 402A used by Sephiroth only (Kalm Flashback version)? Status immunity?

edit - Nope, not Status immunity....

edit - The 402A flag prevents attacks from consuming MP.
Title: Re: [FFVII] Enemy AI Script questions
Post by: DynamixDJ on 2019-05-31 03:03:01
OK then, here's a rough finalised version of the complete Enemy AI Script. I plan to work on it some more in the near future:

https://pastebin.com/raw/mjfRFNsZ (https://pastebin.com/raw/mjfRFNsZ)