• Welcome to Final Fantasy Hacktics. Please login or sign up.
 
March 28, 2024, 08:38:56 am

News:

Don't be hasty to start your own mod; all our FFT modding projects are greatly understaffed! Find out how you can help in the Recruitment section or our Discord!


(ASM) Gear level - Now can use story progression instead of level/0x0169

Started by Glain, March 27, 2011, 12:57:58 pm

Glain


EDIT: I'm putting the Gear Level ASM patches here for convenience. Also, I'm attaching them as an XML at the bottom of this post.


  <Patch name="Random unit gear based on story progression">
    <Description>
      Random unit gear based on story progression
    </Description>
    <Location file="BATTLE_BIN" offset="F59BC"> 
      FF00A530
      E4FFBD27
      1400BFAF
      0400ADAF
      0800A5AF
      0C00A6AF
      1000A7AF
      64ED040C
      6F000434
      0100452C
      21704500
      1400BF8F
      0400AD8F
      0800A58F
      0C00A68F
      1000A78F
      1C00BD27
      20000234
      0800E003
      00000000
    </Location>
    <Location file="SCUS_942_21" offset="4D49C">
      9400BFAF
      21688000
      6F72050C
      20000234
    </Location>
    <Location file="SCUS_942_21" offset="4D52C">
      00000000
    </Location>
    <Location file="SCUS_942_21" offset="4D5D8">
      0A00A390
    </Location>
  </Patch>



  <Patch name="Random unit equipment more selective">
    <Description>
   Random unit equipment will now be more selective
        of secondary item types. (You shouldn't see
        knights in Chapter 2 using linen robes)
    </Description>
    <Location file="SCUS_942_21" offset="4D53C">
      00000F34
    </Location>
    <Location file="SCUS_942_21" offset="4D5F0">
      03000214
      01000F34
      01002925
      00000000
      21606000
      21182001
    </Location>
    <Location file="SCUS_942_21" offset="4D628">
      01003025
      0C000F10
    </Location>
  </Patch>


-----

Recently I've been thinking about an idea for enemy unit levels scaling with the party and how that affects random equipment. By default, that means enemy gear will scale up with the player's level, meaning that after a certain point, the player is going to be outmatched by equipment he can't obtain except through stealing. On the other side of the coin, if the player manages to level very little and/or use degenerators, he can keep the shop equipment ahead of the enemy equipment and gain an advantage that way. My objective: fix the gear of enemies at a certain tier, depending on the battle... without fixing the equipment itself.

Using the pSX debugger, I was able to find where random equipment was being determined. It happens while you're placing your units before the battle, and there is a subroutine at 0x05cc98 in RAM (0x04d498 in SCUS_942.21) that chooses random equipment. It appears to loop through a list of items and check them against the current unit's (r4,r13) level based on the given equipment type (r5). The unit offset relating to the current unit is loaded into the register r13, and sure enough, we find this:


0x0005cd2c: lbu r14,0x0022(r13)


Loading the current unit's level into a register (r14)! r14 is then used for a check in the loop, so this is the number being used to determine the quality of enemy equipment.

My first thought was that this could be replaced with something relating to the shop availability for items, using some kind of multiplier. ([Shop availability variable] * 2? 3? 2.5?) Once again, I used the pSX debugger and went into a shop, to trace to where a check is made against this number. At first, I find this:


0x001251c0: lw r8,0x0028(r29)


I'm testing at the start of chapter 4, and r8 here is loaded with 0x0d (13). That would be... correct! That's the number it'd check against the item's shop availability, and I can see it doing this in the code:


0x001251bc: lbu r2,0x2ec2(r1)     Load in the item's shop availability number... (pSX debugger breakpoint)
0x001251c0: lw r8,0x0028(r29)               Load the player's shop availability (based on story progression) from the stack
0x001251c4: nop
0x001251c8: slt r2,r8,r2     If the player's number is less than the item's, the item is too high quality, so...
0x001251cc: bne r2,r0,0x00125238            ...skip adding it to the shop item list for purchase.


But, well, we loaded r8's value (the player's number) from the stack somewhere, it looks like. How did it get there?


0x00125128: jal 0x000ef1a8
(...)
0x00125134: sw r2,0x0028(r29)


r2 changed to the correct value (0x0d) after the call to subroutine 0x0ef1a8.

At least under some circumstances, this subroutine at 0x0ef1a8 seems to load r2 with the variable I wanted... but unfortunately, we're in a shop, so probably we're in WORLD.BIN. When I tried to call this subroutine in a battle, the memory at 0x0ef1a8 looked completely different... the subroutine wasn't even loaded into memory anymore! (Leading to fantastic results). Besides, I wasn't sure of the circumstances under which that subroutine would return the storyline variable (it uses a billion registers and is also recursive, so it's a barrel of fun to try to trace through).

So my next thought... forgetting subroutine 0x0ef1a8... was I overcomplicating things? We already have an offset to the current unit in subroutine 0x05cc98 (where the random equipment is loaded). So what other offsets can I use? Is there some other property of the current unit I can use to check for what the equipment quality should be? Looking at the formula hacking reference, there's basically nothing. I can't use HP, MP, Brave, Faith, etc. So what to do?

Venture into the unknown. In the ENTD in FFTPatcher, there are unknown fields for each unit, in that little box down in the bottom left. Some of them seem to be AI flags, but what of the one down in the very bottom left (last row, leftmost)? I couldn't find a single instance of it being anything other than zero (though I only checked the story battles). So if it's unused... why not use it? This number could function as a "give the enemy gear as if his level was X" variable, without actually changing the unit's level. I located it in RAM at unit offset 0x0169.



This is the number I'm talking about. In my patch, I've made Gafgarion here Party Level + 5, but his "gear level" is 0x12 = 18, which is his default level for this battle. (Gate of Lionel)

We could simply change the offset checked by subroutine 0x05cc98 to use 0x0169 instead of 0x0022 (level). While that's viable, we'd have to make sure we changed every single instance of this variable in the ENTD. What I really want is for it use 0x0169 unless it's zero, and otherwise use 0x0022.

Well...okay, I can write a block of ASM that does that. Here is the biggest oustanding problem with this whole thing though... where to put it? It just so happened that in my patch, I had ASM hacked formula 41 into something else, and I happened to have a bunch of nop's at the end of it. I used that and wrote my own subroutine at 0x0018a164. (I'll get to this shortly)

If we look at subroutine 0x05cc98, we have to rewrite a little bit to make our own subroutine call work. I want to make the call to my subroutine near the beginning of this one, but the return address wasn't saved yet. Originally, we have:


0x0005cc9c: 00806821 addu r13,r4,r0 r13 = r4 (reference to unit)
0x0005cca0: 30a500ff andi r5,r5,0xff r5 = (Lowest byte of r5)
0x0005cca4: 34020020 ori r2,r0,0x0020 r2 = 0x0020
0x0005cca8: afbf0094 sw r31,0x0094(r29)         (Save return address)


I changed mine to this:


0x0005cc9c: afbf0094 sw r31,0x0094(r29)         (Save return address)
0x0005cca0: 00806821 addu r13,r4,r0 r13 = r4 (reference to unit)
0x0005cca4: 0c062859 jal 0x0018a164 Call my subroutine!
0x0005cca8: 34020020 ori r2,r0,0x0020 r2 = 0x0020


I had to cut out the andi statement, but I execute that in my subroutine, as shown here:


0x0018a164: 91ae0169 lbu r14,0x0169(r13)      Load in unit offset 0x0169
0x0018a168: 140e0002 bne r14,r0,0x0018a174      If it's not zero, use it, so jump to the return statement
0x0018a16c: 30a500ff andi r5,r5,0xff      (Statement from calling subroutine that I cut and wanted to execute here)
0x0018a170: 91ae0022 lbu r14,0x0022(r13)      Otherwise, load in the unit's level, and
0x0018a174: 03e00008 jr r31      Return
0x0018a178: 00000000 nop


Now r14 (the number used to check quality of equipment) already has what I wanted in it. The only thing left to do is stop it from trying to load in the unit offset later in the subroutine. In other words, changing this:


0x0005cd2c: 91ae0022 lbu r14,0x0022(r13)


to this:


0x0005cd2c: 00000000 nop


And we've done it! Random enemy equipment would now load based on the "gear level" you give them at unit offset 0x0169, analogous to what their gear would be if they were at that level... without changing the actual level. Thus, the level can scale with the party, but the gear can remain fixed at a certain tier.

I implemented this in my own patch swimmingly enough (after lots of debugging, of course)! It's pretty cool, but making it into a general patch anyone can use presents some difficulties. The biggest problems at this point, as I see it, are:

(1) Where do I put the new subroutine? (I can't just assume everyone has a bunch of nop's at the end of their formula 41)
(2) What if unit offset 0x0169 is used by something and now it won't work the same way? (Doubtful, they're all zero, and my testing doesn't show any problems here)

Honestly, the simplest answer to (1) may be to just go with the first approach, just straight changing the instruction at 0x05cd2c to load offset 0x0169 instead of 0x0022, beause that would be a one-line ASM hack, and it would work... it's just that you'd have to change that 0x0169 number in the ENTD for every single unit that loads random equipment, or they'd have none at all (I presume). It's just that I'd prefer the (Use 0x0169 if it's there, otherwise the level) approach, but am not sure where I could put the subroutine that does that.

Thoughts? Concerns? Ideas on other ways to make this happen? Different offsets to use? Random comments?
  • Modding version: Other/Unknown

Darkholme

That's really cool.

So if you wanted to say: work this into FFT 1.3; you'd have to go through the battles, look at what level each NPC should be by default; and put that number in the box you noticed as unused?

I'm impressed; and interested to hear the results of anyone who tries this hack out.

Glain

Quote from: Darkholme on March 27, 2011, 02:26:42 pm
That's really cool.

So if you wanted to say: work this into FFT 1.3; you'd have to go through the battles, look at what level each NPC should be by default; and put that number in the box you noticed as unused?

I'm impressed; and interested to hear the results of anyone who tries this hack out.


Yeah, that would be the idea. If we used the "writing a new subroutine" approach, you'd only have to change the unused box if you didn't want it to load the random gear based on level (so for the main story battles at least, maybe not the randoms, I dunno). If we used the "change one instruction and nothing else" approach, then yeah, it would have to be changed for every unit that has random equipment or they'd have no gear in any slots marked "<Random>".

Of course, you wouldn't have to use the default levels if you didn't want to, and you could change it for each unit separately. If all the enemies have similar default levels, it might be easier to just pick one number for the whole battle and give that to everyone. You'd just have to remember they're hex numbers, so if the default level is 11 and you wanted to use it for the gear level, you'd have to input B (for 11) instead of 11 (for 17).

Glad to hear the concept sounds useful!
  • Modding version: Other/Unknown

Pride

SCUS is infamous for having no room for codes within it. If you wanted to place your code in it, you'd have to rewrite large sections of squares awful coding.
  • Modding version: PSX
Check out my ASM thread. Who doesn't like hax?

pokeytax

Impressive how far you got on your own! That's awfully ingenious. Please feel free to ask questions as I know from experience it's a pain to figure this stuff out.

There are two main places the FFH community puts hacks: WORLD.BIN, and BATTLE.BIN. SCUS_942.21 is just too cramped; adding a routine of any size requires jumping to WORLD.BIN or BATTLE.BIN, as you discovered. Conveniently, one of these is loaded pretty much all of the time.

The best place to put codes in each of these is the kanji table, since obviously it's unnecessary in the English release and takes up a lot of space. The place various hackers have called dibs on chunks of that table is here, so feel free to claim some space. There's no shortage but you don't want your code to conflict with someone else's.

I suggest you edit the wiki to claim BATTLE.BIN 0xF59BC - 0xF67F3 and start your code at BATTLE.BIN 0xF59BC, which works out to RAM 0x15C9BC. To get more detail on what RAM maps to which files, look at http://www.ffhacktics.com/wiki/Offset_Conversion.

As far as I know, 0x169 is unused; since you're only using it to load equipment, you should be covered even if it's temporary read/write during battle.
  • Modding version: PSX

Glain

Awesome feedback, guys, thanks.

I went ahead and reserved the block from 0xF59BC - 0xF67F3 (BATTLE.BIN) in the wiki as pokeytax suggested. I'm also now putting the subroutine I created in BATTLE.BIN at 0xF59BC instead of formula 41. There didn't seem to be any issues with moving the subroutine to the new location.

So with that said, looks like I can make it into a general patch!

Patch file/location/hex format:

BATTLE.BIN

0xF59BC
6901AE91
00000000
03000E14
FF00A530
2200AE91
00000000
0800E003
00000000

SCUS_942.21

0x4D49C
9400BFAF
21688000
6F72050C
20000234

SCUS_942.21

0x4D52C
00000000


XML format (FFTorgASM)

 <Patch name="Random unit equipment based on unit flag 0x0169">
   <Description>
   Random gear in ENTD will now be based on unit flag 0x0169
       (in ENTD, unknown section, last row, leftmost box)
       instead of unit level. If unit flag 0x0169 is 0, unit level
       will be used as normal.
   </Description>
   <Location file="BATTLE_BIN" offset="F59BC">
      6901AE91
      00000000
      03000E14
      FF00A530
      2200AE91
      00000000
      0800E003
      00000000
   </Location>
   <Location file="SCUS_942_21" offset="4D49C">
     9400BFAF
     21688000
     6F72050C
     20000234
   </Location>
   <Location file="SCUS_942_21" offset="4D52C">
     00000000
   </Location>
 </Patch>


(Edit: Updated patch to play nice with the load delay)
  • Modding version: Other/Unknown

Darkholme

Hey glain; I have a possible expansion idea for this.

Could you maybe set it up so that instead of defaulting to the level of the NPC, it defaults to some int that gets saved and updated as the game progresses? so for instance; You get to dorter. The equipment levels of the story NPCs have been manually set in the slot where you chose them. It cycles through the characters; and finds the highest Equipment Level value on the map, and saves it in memory (or the lowest non-0 value, if you want the gear to be on the low side).

Then say you go to a random battle. The NPCs in the battle will never have better gear than the story battles.

Additionally; it means you don't need to set every NPC in the story battle's gear level. If you don't assign them a value; they'll get the same level gear as the last story battle you did.

Just an idea.
[When I say int; of course I mean a byte used as an unsigned int, and not an actual int; though I imagine you guessed that.]

Loving this function though.

Glain

It's a cool idea, and it's similar to what I thought about doing when I set out to do this... I wanted to use the variable that tracked the story progression and compare that against the item levels... but unfortunately, didn't know where to find it. I knew it was being checked in WORLD.BIN, but the subroutine that retrieved it didn't exist during battles, and I couldn't tell where it was getting the variable from in the first place.

The idea of permanently storing a number in memory is interesting... I don't know enough about the memory blocks FFT uses to know if there are any addresses I could do that with. One of the big problems here is that the game likes to load things into memory, use them somehow, then discard them when done... For example, It might be difficult to find a memory location where a value could survive both in the world map and in battle, because often times I'll find that a memory location will have one value when WORLD.BIN is loaded (on the world map), and then be overwritten when BATTLE.BIN is loaded (in battle), or vice versa.

Apart from that, I'd think to store something permanently, I'd have to find a way to make FFT save it to the memory card when you save your game... Otherwise, when you saved and quit, it would be removed from memory. I'm also unfamiliar with the way that works.

A good expansion idea, though, to be sure. Glad to hear you like the function, Darkholme.

  • Modding version: Other/Unknown

Eternal

Very interesting ASM. I'd love to see this put in practice sometime.
  • Modding version: PSX & WotL
"You, no less human than we? Ha! Now there's a beastly thought. You've been less than we from the moment your baseborn father fell upon your mother in whatever gutter saw you sired! You've been chattel since you came into the world drenched in common blood!"
  • Discord username: eternal248#1817

Darkholme

I added it to 1.3 easytype today and goofed around a little bit.

I like the non-infinitely scaling gear. :)

Celdia

This is an amazing idea. I need to implement it into CCP somehow...
  • Modding version: PSX
  • Discord username: Celdia#0

Darkholme

From goofing around with it in my emulator (I'm running emulators again instead of playing it on my PSP! See what you people made me do! :P)
I like it.

All it's lacking is a way to scale gear in random battles the same way (while still having the option for "scale by level" if someone wants it to). - I proposed one method above; but the question is where we're going to store the "current gear level" so that it can access it when it needs to. Maybe leaving it at 0 could be "use current gear level" and setting it above 99 means "Use characer level." And instead of just checking for the lowest non-0 value, you'd need to check for the lowest non-0 value below 100.

Does that variable you're changing stay on party members? Could you say; save the value on Ramza and load it later?

Glain

So I'm looking at the ASM I wrote a bit more, and from looking at a few resouces on MIPS, I'm a bit unclear on whether I needed to honor the load delay. If I did, I'm technically causing a potential problem by executing the lbu and bne instructions right next to each other in the third-to-last block in my original post, because I can't be sure r14's value will be ready yet for me to check on the bne. Hrm. I didn't notice any problems when I tested it though (Other than that it loves linen robes for some reason, but that might just be a peculiarity of loading in random gear in general). So maybe it's not coming into play? Not sure... is the PSX MIPS processor affected by the load delay? Some of the ASM seems to be coded as if it is, but I never noticed this happening when I was debugging with pSX.

In particular, this:

91ae0169 lbu r14,0x0169(r13)             Load in unit offset 0x0169
140e0002 bne r14,r0,0x0018a174             (Do I know r14 actually has the value I loaded in yet?)


Shoving a nop between the two statements would fix the potential problem, I believe. I should probably do it to be on the safe side...

Darkholme:

That's a clever idea, storing the variable on Ramza at the same offset... it might work! Still no clue if it would work between saving/loading the game, but it would make sense that Ramza's data would be saved with the save file, so that's got a chance of working.

Having the codes is an interesting idea too. Something like you said:
0x00 = (Load from Ramza's offset, the 'default')
0xFF = (Load from level)

I'm thinking as I type here, but maybe the process would be something like this for each unit:
(1) Load from unit's offset (0x0169)
(2) If result = 0, load from Ramza's 0x0169 (the default)
(3) If result still = 0, make result = 255
(4) If result = 255 (0xFF), load from level (0x0022) and jump to (6)
(5) Save to Ramza's 0x0169 offset
(6) Return

The reason for (3) being that we don't want to get caught in a loop where battles load Ramza's offset, get 0, then save back the 0, forever, resulting in enemies with no gear for the rest of the game...

The only thing I don't really like about all this is that if you were to just apply the ASM patch without changing anything in FFTPatcher, enemies would have level 1 gear for the entire game. That's why I went with the level as the default if you didn't enter anything, so nothing would be changed until you changed it in the ENTD. Not sure, maybe we should swap the 0x00 and 0xFF codes for Ramza's offset.

Still highly theoretical, of course. I'd have to play around with this for a while before doing it. Seems viable though.

Edit: Okay, thanks pokeytax, good to know. I'm modifying my post with the patch in it with the updated version that takes the load delay into account.

Edit: Changed the process to include Darkholme's idea of using the unit level if Ramza's offset was 0. We should probably also not save it back in that case.
  • Modding version: Other/Unknown

pokeytax

Quote from: Glain on March 29, 2011, 10:38:13 pm
So I'm looking at the ASM I wrote a bit more, and from looking at a few resouces on MIPS, I'm a bit unclear on whether I needed to honor the load delay.


I have definitely had code fail solely because I did not honor the load delay, as near as I could tell. It's not 100% predictable but it's not worth any amount of debugging headache when you can often rearrange code to make use of the delay slot anyway.

I have also had code fail because I put a load instruction in a branch delay slot, in pSX; writing for emulators can be inconsistent. ePSXe is more forgiving of erroneous code, I think. Sometimes code will function as intended on stepthrough, when running from a breakpoint, or even just when an untripped "execute" breakpoint is set, but crash when run without the debugger open.
  • Modding version: PSX

Darkholme

For Number 3: I agree that you don't want to set it to 0; but you probably don't want to set it to 1 either (Too Low).
I'd suggest you have it default to the NPC's level as you mentioned in step 4.

Glain

I just discovered something very interesting about random equipment!

I made a passing reference to this, but I couldn't understand why the game was loading in certain items that were much lower quality than the level they were being checked against. For example, knights with Linen Robes at Lionel Castle.

The item selection algorithm loads in a beginning item and an end item to check for the current slot. For armor, it starts at 0xAC (Leather Armor) and goes until it reaches 0xD0 (one beyond Robe of Lords). These item codes are the same as the ones in the Gameshark Handbook.

There are several factors that disqualify an item from consideration (the unit can't equip it, the item has the rare flag checked, etc). Any item that doesn't pass the checks will be skipped, but the routine will go through the whole list of items for each slot. Ultimately, it will create a list of items in memory that are eligible, then choose a random index in the list and return the item at that index.

The algorithm also keeps track of what the previous item's level was. If it finds another eligible item that is a higher level, it will overwrite that slot in the list with the new one. It will go through the armor this way... for example:

0xAC: Leather Armor: Save to list in slot 0
0xAD: Linen Cuirass: Save to list in slot 0, because this item's level is higher than the previous
0xAE: Bronze Armor: Save to list in slot 0, because this item's level is higher than the previous

Et cetera. In my test at Lionel Castle, we'll get to 0xB2 (Gold Armor) before the level check will stop any higher quality armor from being considered. This is the same every time. The item list now looks like this:

List = [B2]

Fairly simple list at this point. It just contains Gold Armor.

The algorithm assumes that item levels are in order, so if it runs into an item that is a lower level than the previous one, it considers this a new item type and goes to the next index in the list. When we get to robes, the Linen Robe (0xC8) is of a lower level than Gold Armor, so it gets a new index in the list. Our list becomes:

List = [B2, C8]

So far, so good! We just need FFT to remember the previous item level for the robes and it'll just start overwriting them as it gets to higher quality ones! Next in the list are Silk Robe (0xC9) and Wizard Robe (0xCA), after which it will stop because the item's level will be too high.

Let's see what happens!

List = [B2, C8, C9]

Oh no FFT what are you doing? This isn't a new item type at all! I really don't think...

List = [B2, C8, C9, CA]

...


Why would you do this, FFT?

The algorithm doesn't remember the previous item level here! When it adds a new element to the list, it still thinks the previous item was Gold Armor... and keeps adding new slots to the list for every robe!

This is the final list that will return here. As a result, we've got a 25% chance of any of [Gold Armor, Linen Robe, Silk Robe, Wizard Robe]!

Let's see what's going on:


0005cdf0: beq r2,r0,0x0005ce00 If (Item level < Previous item level), skip the next 3 lines
0005cdf4: nop

0005cdf8: addu r12,r3,r0 r12 = (Item level)
0005cdfc: addu r9,r0,r0 List index (r9) = 0

0005ce00: addu r3,r9,r0 r3 = r9
0005ce04: addiu r9,r9,0x0001 Increment list index (r9)


r12 is where it's storing the previous item level, and r9 is our list index. It's a little weird in that it always increments the list index, but sets it to 0 beforehand if it didn't really want to increment it. What we can see here, though, is that the update to the previous item level (at 0x0005cdf8) is skipped in this case! (r9 will actually be set to one beyond the list index at the end here, but we'll get to that in a minute...)

Going into ASM coding mode, I thought of a way to save the previous item level...


0005cdf0: 14020003 bne r2,r0,0x0005ce00 If (Item level >= Previous item level), skip the next 3 lines (This check is reversed)
0005cdf4: 00000000 nop

0005cdf8: 25290001 addiu r9,r9,0x0001 Increment list index (r9)
0005cdfc: 00000000 nop

0005ce00: 00606021 addu r12,r3,r0 r12 = (Item level)
0005ce04: 01201821 addu r3,r9,r0 r3 = r9


I'm not doing the funky incrementing-but-not-really logic here: The list index increments only when we're going to add another slot to the list.

The fact that the list index (r9) gets set to one beyond its value only comes into play once we've exited the loop, but it means I've got to remember to add one to the value of r9 when I put it into the next register.


0005ce28: andi r16,r9,0x00ff r16 = r9 (r9 always < 0xFF)


becomes


0005ce28: addiu r16,r9,0x0001 r16 = r9 + 1


...And now, trying things out, at the same battle. What do I see?


Success!

50% chance for any of: [Gold Armor, Wizard Robe]. It picked the highest quality armor and highest quality robe that still fit within the level check, then randomly picked one of the two! All the knights in the battle had one of these two items in the armor slot.

EDIT: Apparently there's something wrong with this patch. I was getting some pretty funky stuff in tests in the deep dungeon (items equipped in the wrong slots), so I'm redacting it for now.

EDIT 2: Back in business! It seemed monks were problem children, but what was really happening was that it couldn't distinguish between "I chose from one item type, without ever increasing my list index" and "I couldn't find any items at all to equip for this gear slot". In the latter case, it was trying to load whatever happened to be in that memory location on the last run (D:). It led to some interesting results. Wasn't aware you could equip katanas on your head... seems dangerous! Anyhow, here's the new patch with that fixed.

It was checking the list index to see if we had found an item, but that wasn't going to work anymore. I needed another register. r15 wasn't being used, and it's a temp register, so I used it.

Here I'm setting up r15 at the top of the loop. I'm able to replace this line because grabbing only the lowest byte of r7 was just extraneous... it was always <= 0xff anyway.


0005cd3c: andi r7,r7,0x00ff r7 = (lowest byte of r7) (redundant)


became


0005cd3c: ori r15,r0,0x0000                  r15 = 0 (Start out false... haven't found anything yet)


I fortuitously had a nop where I needed to set r15 = 1 (when we were going to choose an item), so I was able to replace it with this:


0005cdf4: ori r15,r0,0x0001                  r15 = 1 (We found an item)


And then the check at the end became this:


0005ce2c: beq r15,r0,0x0005ce60     Skip loading the item if we couldn't find one (Used to be r16 instead of r15)


Anyhow, here it is in XML format.


  <Patch name="Random unit equipment more selective">
    <Description>
   Random unit equipment will now be more selective
        of secondary item types. (You shouldn't see
        knights in Chapter 2 using linen robes)
    </Description>
    <Location file="SCUS_942_21" offset="4D53C">
      00000F34
    </Location>
    <Location file="SCUS_942_21" offset="4D5F0">
      03000214
      01000F34
      01002925
      00000000
      21606000
      21182001
    </Location>
    <Location file="SCUS_942_21" offset="4D628">
      01003025
      0C000F10
    </Location>
  </Patch>
  • Modding version: Other/Unknown

Darkholme

*clapping*

Though I don't like that it only looks at the highest one for the level. :/

I was always under the impression that the game had a random chance of choosing a slightly lower level item, or a slightly higher level item. It's disappointing that it's actually supposed to be far less random than I'd like, but I like that you fixed the bug.

So.. Flipping through the items in FFTPatcher: Stone Gun is level 95, but the blaze gun one slot later is level 91. This means it will think they're in separate categories ans start counting again? So a level 95 enemy Mustadio would be choosing randomly between a lv 95 Stone Gun and a lv 93 Blast gun? that seems to not fit in well with the algorithm you just described...
Congrats on finding the bug though. :) there are some problems with even a working one, as I just pointed out, as not all the items are sorted from lowest to highest level within an item category. It would work if the items were better sorted though.

I could write a new algorithm to do what I think would make random gear a bit more suitable, but my ASM is fuzzy, and my hex editing is worse for being out of practice. Perhaps I'll do it in pseudocode, or fake c-like syntax, which would be readable enough to know how to implement it if you're familiar with the appropriate ASM, and then when I get the chance to brush up on my ASM I might implement it here. It would be nice if it doesn't always give them the optimum gear, but instead has a random chance of going slightly higher or lower as well.

Did the idea of storing the most recent NPC levels on Ramza pan out?

pokeytax

Wow, that's a really awesome bugfix and should go in every patch. Nice job! (And nice explanation - gotta say I could learn something from the way you documented it.)

Both Blast Gun and Stone Gun are Rare and will be skipped by this algorithm regardless. There are a few quiddities with item levels not monotonically increasing in vanilla, but an individual patch can fix those.
  • Modding version: PSX

Pickle Girl Fanboy

So does this mean I'll never again fight level 60 Knights at Mandalia Plains who have Small Mantles equipped?