Miscellaneous Forums > Scripting and Reverse Engineering

[FFVII] Enemy AI Script questions

(1/20) > >>

DynamixDJ:
Hi Y'all! I'm currently working on the Enemy Module of my walkthrough, 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 (the Eligor caused me quite a few problems).

Anyway, this is a fun project, and this webpage 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: ---If ( (BattleAddr(&AllPlayers).BattleAddr(&Row) == 0) )
--- End code ---

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: ---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
--- End code ---

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: ---If ( ( (BattleAddr(&AllPlayers).BattleAddr(&SlowStatus) == 0)
--- End code ---

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: ---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
--- End code ---

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: ---/----------------------------------------------------------------------------\
|   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      |
\----------------------------------------------------------------------------/
--- End code ---


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!

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

DynamixDJ:
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

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: ---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)
--- End code ---

DynamixDJ:
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: ---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
--- End code ---

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: ---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
--- End code ---

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

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

Navigation

[0] Message Index

[#] Next page

Go to full version