Users browsing this thread: 2 Guest(s)
Condensing Spell List in Battle

#61
Posts: 2,548
Threads: 98
Thanks Received: 147
Thanks Given: 156
Joined: Aug 2009
Reputation: 52
Status
Nattak\'d
(10-05-2017, 09:51 PM)assassin Wrote: as mentioned a few pages back, my "Control attacks ignore MP cost fix" edits this function.  no idea what "my ROM" consists of here.

Ah yes, that be it!


We are born, live, die and then do the same thing over again.
Quote  

#62
Posts: 175
Threads: 11
Thanks Received: 10
Thanks Given: 8
Joined: May 2013
Reputation: 13
Status
Well-Fed
Yeah, I think I can make this compatible without actually using any additional extra space, thanks to the optimisations that assassin's already done on the code for the Control MP fix. Working on it now.

Try this -- it seems to be working on my end, everything's still all deducting properly on the party side. I didn't bother tracking down the monster MP in RAM to see if it deducted properly, but Control is still working properly with nothing crashing, so... Here's hoping?

Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Condensed Spell Lists v1.5 -- Control MP Fix Variant
; Author: Gray Shadows (in.the.afterlight@gmail.com
; Applies to Final Fantasy 3/6us v1.0
;
; Urgency: Low - QoL enhancement
;
; Contributors: assassin (code support and optimisation)
;               seibaby (testing and problem solving)
;               Warrax (testing and bug identification)
;
; In vanilla FF6, because of the way the game builds spell lists in
; battle, if one character in battle knows a lot of spells and another
; character doesn't, that second character will have a lot of blank
; space in their Magic menu. Condensed Spell Lists adds some additional
; code to battle initialisation that takes a character's spell list
; and 'shuffles' it up, so that all of the blank spots are at the end.
; It also resorts the Lore list in the same manner.
;
; Version 1.5 update includes: bugfixes, including an error introduced
; wherein monster abilities were incorrectly costing MP, as well as a
; fatal crashing error caused by Interceptor counter-attacks. The hack
; should now properly account for all abilities that use the Magic
; command but do not cast from a character's spell list. (This should
; only be Interceptor counter-attacks, and possibly Desperation Attacks.)
;
; The Control MP Fix variant incorporates code from assassin's "Control
; Attacks ignore MP cost fix" patch, including some optimisations that
; let me fit both his code and mine into the vanila C2/4F08 function
; without taking up any more space than the base version of the patch does.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;

hirom
;header

print " "

!freespace_C2_0 = $C2A65A        ; 77 bytes needed, used through $C2A6A6
!freespace_C2_1 = $C2FAB0        ; 64 bytes needed, used through $C2FAEF

org $C2256D                
JSR condenseSpellLists
; This was originally a JSR to modify available commands; we'll be JMPing to
; that at the end of the modified code so that RTS comes back to the right spot.

org $C24EDF
loop:

org $C24EE3
; FF6j = org $C24ECB
print "Replacement Function updated_4F08 starts at: ",pc
reset bytes

BEQ skip
LDA $3184,Y
BPL loop           ; oops!  i wrongly had "F0 F5  BEQ $4EDF" in the first
                  ;  release of the patch, even though this was one of the
                  ;  simpler optimizations.. and even though I had an email
                  ;  from Imzogelmo containing the correct instruction, as
                  ;  he'd come up with same optimization on his own.

; ---- Rest of function is unchanged from original game, aside from the ----
; ---- JSR destination, but just shifted upwards. ------------

skip:
PLA
STA $3184,Y
ASL
TAY
JSR GetMPCost      ; Determine MP cost of a spell/attack
STA $3620,Y        ; save MP cost
REP #$20           ; Set 16-bit Accumulator
LDA $3A7A
STA $3420,Y        ; save command #
LDA $B8
STA $3520,Y
PLP
PLY
RTS

;org $C24F24
GetMPCost:

PHX
PHP
TDC                     ; 16-bit A = 0.  this means the returned spell cost will
                       ;  default to zero.
LDA #$40
TRB $B1                 ; clear Bit 6 of $B1
BNE KeepZero            ; branch if it was set, meaning we're on second Gem Box
                       ;  spell for Mimic, and return 0 as cost.  no precaution
                       ;  needed for first Mimicked Gem Box spell, as we reach
                       ;  this function with $3A7A as 12h [Mimic] then.

REP #$10                ; Set 16-bit X and Y

LDA $3A7A               ; get command #
CMP #$19
BEQ CommandUsesMP       ; branch if it's Summon
CMP #$0C
BEQ CommandUsesMP       ; branch if it's Lore
CMP #$02
BEQ CommandUsesMP       ; branch if it's Magic
CMP #$17
BEQ CommandUsesMP       ; branch if it's X-Magic

CMP #$0E
BNE KeepZero            ; branch if it's not Control

ControlCheck:
LDA $3EF9,X             ; status byte 4
BIT #$10
BEQ KeepZero            ; branch if Controller not in "spell chant" yet.  the
                       ;  first turn of Control just involves mentally violating
                       ;  the monster, so there won't be any MP cost [and the
                       ;  attack # should be undefined].
BRA GetAttack           ; always get the MP cost from the spell data.
                       ;  while Control attacks may be launched *from* a
                       ;  character menu [which will make X < 8 in this function],
                       ;  they're performed *by* the monster, so that menu doesn't
                       ;  even have MP data.
                       ;
                       ; Why the dreadful obfuscation, you ask?  If the command
                       ;  is Control, Carry will always be set by the "CMP #$0E"
                       ;  above, and the next branch (at C2/4F37) will ALWAYS be
                       ;  taken.  Butt-ugly, but I really didn't feel like having
                       ;  a "LDA $3A7B" in two separate places.
CommandUsesMP:
CPX #$0008
GetAttack:
XBA                        ; [High byte is now command, low byte will be attack]
LDA $3A7B               ; get attack #
BCS UseSpellData        ; branch if it's a monster attacker.  they don't have
                       ;  menus containing MP data, nor relics that can
                       ;  alter MP costs.  OR always branch if command is Control.
XBA                ; high is spell, low is command
CMP #$19
BEQ .summon
JMP calculateMPDeduction

.summon
CLC
TDC
REP #$20
ADC $302C,X             ; add starting address of character's Esper menu
TAX

LDA $0002,X             ; A.bottom = spell aiming from character's menu data.
                       ; A.top = MP cost from character's menu data.  it usually
                       ;  matches the spell data, but Gold Hairpin, Economizer,
                       ;  and Step Mine's special formula can make it vary.
BRA KeepZero            ; put MP cost in bottom of A, clean up stack, and exit

UseSpellData:
; C2/4F52: 20 D8 51     JSR $51D8   (A = MP cost, read from spell data)
; C2/4F55: EB           XBA
JSR $5723               ; From spell data, put MP cost in top of A, and aiming
                       ;  byte in bottom of A
; FF6j = JSR $570B

KeepZero:
XBA                     ; bottom of A = MP cost
ExitWithMP:
PLP
PLX
RTS

print "Replacement Function updated_4F08 ends at: ",pc," and used ",bytes," bytes of space."
print " "

;org $C24F47
;fka_4F47:

; This jumps in the middle of a function at C2/4F08 that grabs the MP cost for
; a spell. A character's individual spell list in battle stores a modified MP
; cost based on modifying relics, and as we're reshuffling the list, we need to
; add additional code to make sure that the game looks for MP costs in the right
; location.



                            
org !freespace_C2_0
print "New function CondenseSpellLists starts at: ",pc
reset bytes

condenseSpellLists:
PHX                ;
PHP
LDY #$04        ; The 0 index in the list holds the equipped esper, which we're not touching.
                ; Each entry is four bytes long, so the spell list starts at Y = 4.
.noMoreSpells    ; We'll be branching back here to execute our Lore list
TYX                ; X is going to be our 'write-space' index, whereas Y is our 'read-space' index.
REP #$10

    .checkSpellLoop
    LDA ($F2),Y
    INC
    BNE .checkNextSpell

        .findNextSpell
        INY #4
        CPY #$00DC            ; If we've hit the first Lore slot, there are no more spells to copy back,
        BEQ .noMoreSpells    ; so jump out, reset X to start sorting the Lore list instead.
        CPY #$013C            ; This is after the last Lore slot, so if we've gone that far, there are
        BEQ .noMoreLores    ; no more spells to copy back and we can exit the function entirely.
        LDA ($F2),Y
        INC
        BEQ .findNextSpell

    PHY            ; we'll be pushing and pulling within the loop, but we need to know
                ; where it started so we can blank out the slot we copied from
    CLC
    REP #$20
    
        .copyNextSpell
        LDA ($F2),Y    
        PHY            ; This stores our Y location, i.e. the next slot with a spell learned
        TXY            ; and pulls our X, or the blank slot we're writing to.
        STA ($F2),Y
        PLY            ; back to our 'write from' location
        INY    #2        ; and gets the next bytes
        INX #2
        BCS .doneCopy
        SEC
        BPL .copyNextSpell    ; if we haven't done four bytes, loop back and grab the next
        .doneCopy
        
    SEP #$20    
    PLY            ; this is the first byte of the slot we copied from
    TDC
    STA ($F4),Y    ; this zeroes out the MP cost
    DEC
    STA ($F2),Y    ; and blanks out the spell we copied from
        
    BRA .weCopiedASpell
    .checkNextSpell
    INX #4
    .weCopiedASpell
    TXY                ; and then copy it over to Y for our next loop through
    CPY #$0138
    BNE .checkSpellLoop
    
.noMoreLores
PLP    
PLX
JMP $532C

print "CondenseSpellLists ends at: ",pc," and used ",bytes," bytes of space."
print " "



org !freespace_C2_1
calculateMPDeduction:

print "New function CalculateMPDeduction starts at: ",pc
reset bytes

CMP #$0C                ; coming in, low byte is command and high byte is spell ID
BEQ .calculateLore        ; (branch if it's Lore)

.calculateMagic
XBA                        ; (get attack #), high byte is now command and low byte is attack ID
CMP #$F0                ; is it a Desperation Attack or an Interceptor counter?
BCS .returnZero            ; if so, exit
STA $F0                    ; save our spell ID in scratch memory
TDC
LDA #$04                ; four bytes per index, and we're starting at the second index in
                        ; the list (i.e. the first Magic spell)

.loreEntersHere
REP #$20
CLC
ADC $302C,X                 ; get the start of our character's magic list (index #0 is esper)
STA $F2                        ; this points out our first Magic slot
INC #3
STA $F4                        ; and this points at our first MP cost slot
SEP #$20
PHY
LDY $00

    .findSpell
    LDA ($F2),Y
    CMP $F0
    BEQ .getMPCost
    INY #4
    BRA .findSpell
    
.getMPCost
LDA ($F4),Y
PLY
BRA .exitWithMP

.calculateLore
XBA                            ; (get attack #), high byte is command and low byte is attack ID
SEC
SBC #$8B                    ; turn our raw spell ID into a 0-23 Lore ID
STA $F0
TDC
LDA #$DC                    ; this is our first Lore slot in the character's spell list
BRA .loreEntersHere

.returnZero
TDC
.exitWithMP
JMP ExitWithMP            ; (clean up stack and exit)


print "CalculateMPDeduction ends at: ",pc," and used ",bytes," bytes of space."
print " "

You should be fine to patch it right over the existing rom, including the one you patched with the last version of this code.


Current Project: FF6: Tensei | Discord ID: TristanGrayse
  Find
Quote  
[-] The following 1 user says Thank You to GrayShadows for this post:
  • Gi Nattak (10-05-2017)

#63
Posts: 2,548
Threads: 98
Thanks Received: 147
Thanks Given: 156
Joined: Aug 2009
Reputation: 52
Status
Nattak\'d
All seems good and working nicely now, good stuff!


We are born, live, die and then do the same thing over again.
Quote  

#64
Posts: 175
Threads: 11
Thanks Received: 10
Thanks Given: 8
Joined: May 2013
Reputation: 13
Status
Well-Fed
Woo, glad it's working. Laugh Let me know if you run into any other bugs with it.


Current Project: FF6: Tensei | Discord ID: TristanGrayse
  Find
Quote  

#65
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
various observations and realizations that might be old news to you, and mostly don't go anywhere, but sort of do:

- loading $3084,X for the Esper position in C2/4F08 should still work.  the Esper slot is one of the few things that ISN'T condensed, so most of the $3084 list being worthless doesn't matter.  now, there's not much point to using the variable, when C2/5554 just sets all in the range to 0 anyway, but we can.

- earlier in the thread, i'd said:
Quote:re C2/4F08 remedy: under .calculateSummon, i'd tend to do the Esper=>Spell ID conversion and verify the desired spell is in that first slot before proceeding, just to be robust.  extra space be damned.  but your assumption regarding Summons seems fairly sound.

to be sure..  this would match your patch's level of robustness used for spells.  but vanilla is less robust than i was probably thinking when making the post.  because there's only a single $3084 list (which is why you had to edit C2/4F08 in the first place), vanilla's not actually verifying that the current character has the spell in their menu before fetching its MP cost; it's just verifying that at least one character does.

- but what dawned on me when you relayed issues with Interceptor and Desperation Attacks is that the $3084 list is a substitute for hardcoded bounds checking.  the whole damn thing up through $3183 must be filled with null FFs to start, which are used when Function C2/4F08 is called in those special cases.  i must've thought that many bytes ending with $3183 were simply never used.  the $3084 approach means if C2/5551 had been modified to put higher-numbered attacks in a spell list, C2/4F08 could handle them (possibly with a couple added instructions if these attacks are associated with a different command).

--------

weird how i can write working code without knowing all these things.

putting all of this together, i believe that if you are willing to do stricter bounds checking (you already introduced some with your >= F0h check) and forgo some of the $3084 flexibility, we can construct separate, shortened $3084 lists for every character, thus letting C2/4F08 run faster.  the C2/561E loop is easy enough to paste and tweak.

(78 * 4) = 312 > 256 , so no go.

but in vanilla, there's only a single Lore list, so the real equation is:
(54 * 4) + 24 = 240 < 256

for that matter, is the $3034 list ever used after Function C2/5551 completes?

C2/4F08 would need a command check to choose a personalized list or a generic list, depending on whether Magic/X-Magic or Lore is being used.

and whatever strict Attack ID bounds checking was put in place would need to be applied only to certain commands -- not to Control, for instance.

hardcoding is something i usually regard as lame -- just watch me tirelessly spew venom at the Zelda 3 assholes who built most of their game that way -- but the Attack ID ranges for different menus are pretty well-established.  (anybody who's editing C2/5551 to change them should be capable of editing your patch if they want coexistence.)  and the speed recovery might be enough to endure such lameness.

hell, if you're not concerned with regaining space along with speed, you can keep a fallback loop in calculateMPDeduction to manually track down exotic Spell IDs to accommodate hackers and hypotheticals.
Quote  

#66
Posts: 175
Threads: 11
Thanks Received: 10
Thanks Given: 8
Joined: May 2013
Reputation: 13
Status
Well-Fed
Quote:loading $3084,X for the Esper position in C2/4F08 should still work.  the Esper slot is one of the few things that ISN'T condensed, so most of the $3084 list being worthless doesn't matter.  now, there's not much point to using the variable, when C2/5554 just sets all in the range to 0 anyway, but we can.

While it would still work, I don't know that it's worth using the additional space. I could make it fit in the standard version if I borrow some of the optimisations from the Control MP fix, but as it is there's only one extra byte in the combined version, so I'd have to off-load it to freespace, and there's little enough of that as it is. 


Quote:to be sure..  this would match your patch's level of robustness used for spells.  but vanilla is less robust than i was probably thinking when making the post.  because there's only a single $3084 list (which is why you had to edit C2/4F08 in the first place), vanilla's not actually verifying that the current character has the spell in their menu before fetching its MP cost; it's just verifying that at least one character does.



Huh, yeah, you're right. I suppose the thinking there is that there "shouldn't" be a way to reach this point with a character unless they do know the spell, because the menu is drawn from their personal list, but you're that it lacks a certain robustness.

It's interesting, because I'm not actually finding anywhere that sets $3084 through $3183 to null/FFh. Espers (starting at $30BA) are zeroed out at the start of C2/5551, whether or not anyone has it equipped, because they're always at position 0 on the list, and then Lore locations are stored directly to an array at $310F. but there doesn't seem to be anything that nulls the rest of the list that I can see.

That said, looking at this C2/5551, I definitely see some bits that can be removed in the interest of both saving space and speeding things up; while we still need the temporary list at $11A0 for reordering White/Black/Effect, we don't need to section at C2/55ED that deletes lines from the list of 'everybody knows' spells. (Even if that were properly changed for the US version with two spells per line.) You'd need to replace later instances of reading from $3034 with $11A0 instead, that's all. (Or write the initial 'everyone knows' to $11A0 and then the reordered list to $3034, but either way.)

On an even better note, my project doesn't need to use $11A0 at all, since I'm not reordering spells/each character with magic only HAS one type. (Which reminds me that I need to disable that in the Config menu. I should find something to replace it with.)

Quote:weird how i can write working code without knowing all these things.
Ohgod, tell me about it, I don't know how I manage 90% of the things I do with this code.
Quote:but in vanilla, there's only a single Lore list, so the real equation is:

(54 * 4) + 24 = 240 < 256


Realistically, the bounds check necessary for this would actually be really simple, at least with vanilla commands. Summon's already taken care of by hardcoding the 0-entry with the Summon command; you could hardcode Lore to pull from a specific section of the list. If it's Magic/X-Magic, do a bounds check to make sure it's <=#$35, since nothing else using the Magic command should be using MP, exit if it's greater. Coming into this, X is our actor ID (0, 2, 4, or 6 for PCs), so if A is our attack ID:

Code:
PHX
.loop
DEX #2
BMI .exit
CLC
ADC #$36
BRA .loop
.exit
TAX
LDA $3084,X
PLX

 then this should exit with A = position in character's individual spell list, assuming that we manage to turn $3084 into four individual 'position in list' lists.
The problem there is I have no idea where the best place to BUILD those individual $3084 lists would be. We can't do it at C2/5551 because we don't have the individual spell lists yet, and after that we're dealing with the longer 4-byte entry lists which I can only think makes it much more complicated.


Current Project: FF6: Tensei | Discord ID: TristanGrayse
  Find
Quote  

#67
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
re the nulling: it's done at C2/23FF.

re code snippet: or add a few to several bytes (depending on whether we want the option of going >= 256d past 3084h; i.e. setting A to 16-bit) but gain some speed with a 4-entry pointer table.

Quote:you could hardcode Lore to pull from a specific section of the list.

that'd avoid doing the Actor ID check, but what's the best or quickest way to make sure the index _into_ the list is a proper value?

re building the individual $3084 lists: huh, guess i did underestimate it some.
Quote  

#68
Posts: 175
Threads: 11
Thanks Received: 10
Thanks Given: 8
Joined: May 2013
Reputation: 13
Status
Well-Fed
(10-07-2017, 01:08 AM)assassin Wrote: re code snippet: or add a few to several bytes (depending on whether we want the option of going >= 256d past 3084h; i.e. setting A to 16-bit) but gain some speed with a 4-entry pointer table.
... Yeah, that would work, too. XD Which is exactly what the game already does for the start of the spell list.

Quote:that'd avoid doing the Actor ID check, but what's the best or quickest way to make sure the index _into_ the list is a proper value?
After you subtract 8Bh from A to convert the attack ID into a Lore ID, do a quick CMP to see if it's >23h? That's the first thing that comes to mind.

Quote:re building the individual $3084 lists: huh, guess i did underestimate it some.
I mean, complicated definitely doesn't mean impossible. Maybe something like...

Code:
LDY #$04      ; we don't need a 16-bit index, because Y will max out at D8
LDX #$35

.loop
LDA ($F2),Y   ;
BMI .exit     ; BMI works here to exit because if the list is already shuffled, hitting FFh means the rest of that list is null
PHX : TAX
PHY : TYA
LSR #2
TAY
STY ($F0),X   ; assuming that we save the start of the character's individual $3084 list in scratch memory at, uh, some point.
PLY          
INY #4
PLX : DEX
BPL .loop
.exit

I'm also wondering if it's worth shuffling Lores BEFORE they're copied over to the individual spell lists; for one, that means we can shuffle the magic lists in 8-bit rather than 16-bit X/Y, since our index would never go above D8h, AND we could create the Lore Position list at the end of $3084 at the same time, since it doesn't need to be done per character.

ETA: For that matter, Lores would be shuffled in 8-bit as well, since we could just do a quick 24-entry loop.


Current Project: FF6: Tensei | Discord ID: TristanGrayse
  Find
Quote  

#69
Posts: 2,548
Threads: 98
Thanks Received: 147
Thanks Given: 156
Joined: Aug 2009
Reputation: 52
Status
Nattak\'d
There's a bit of a bug/strange side effect with this patch, Grayshadow's and I have been beating our heads against the wall trying to fix it, let alone figure out wth is going on. Hoping @assassin or anyone else who might know or can spot what is going on can enlighten us about this issue.

The issue is, is for those caam (character acting as monster) battles, where it loads a character (graphics, properties, name etc.) and puts them on the monster side and gives them a monster battle script AI to use -- think Kefka in the Imperial Camp 'battle', or uncontrollable Cyan shortly after that. Normally, without this patch implemented, those caam battles the caam will have infinite MP at their disposal, or rather it's coded to not have any MP deducted when they cast spells. With this patch, however, when a caam casts a spell that costs MP, the 'Need Mp' message pops up and the spell does not cast. Normally in the vanilla game this might not be an issue, because there are very few caam battles and I believe none of them ever cast any spells that cost MP. But with my hack for instance, I have about 5-6 caam battles and they all have spell casting going on, so it's a huge problem for me Laugh

So the question is, how is this condensed spell patch causing this issue, and hopefully how to 'fix' it! While the caam does load properties of a character (HP/MP), the MP doesn't seem to be loading and they appear to only have 1 MP when healing with an Ether, so whatever causes the caam to not draw MP when casting a spell might just be broken and not a case of them just not having any MP. Making a spell cost just 1 MP the message still pops up, so I'm guessing it's become broken.

Any help greatly appreciated! I'd really love to use this patch as it saves time scrolling around in the spell menu like a fool and looks much nicer, but as it is I simply could not live with this strange issue.

I should also note that we've run the part of the code we think is responsible for this through the debugger and for all intents it looks like it should NOT be using any MP still, yet the issue persists... I should also note that this issue happens on a clean ROM with this patch, so it's not some issue or confliction just with my hack. I should also note that it doesn't matter what side the caam is on, player or monster.


We are born, live, die and then do the same thing over again.
Quote  

#70
Posts: 281
Threads: 18
Thanks Received: 12
Thanks Given: 8
Joined: Mar 2014
Reputation: 8
Status
None
In vanilla, characters that don't have a Magic command don't get in-battle MP. Could that be the case here? It isn't.

If so, Lenophis' patch Summon This! contains a fix for that.
http://slickproductions.org/ff6patch.php...mon%20This

However, since that patch contains a lot more, here's the code that's responsible for this:
Code:
C2/53F9: 46 F8        LSR $F8        (Carry gets set if character has Magic/X-Magic command
                                     and at least one spell known, Magic/X-Magic and an
                                     Esper equipped, or if they simply have Lore.)
C2/53FB: B0 0B        BCS $5408
C2/53FD: A3 02        LDA $02,S      (retrieve the X value originally passed to the function.
                                     iow, the index of the party member whose menu is being
                                     examined)
C2/53FF: AA           TAX
C2/5400: C2 20        REP #$20       (Set 16-bit A)
C2/5402: 9E 08 3C     STZ $3C08,X    (zero Current MP)
C2/5405: 9E 30 3C     STZ $3C30,X    (zero Max MP)
C2/5408: 28           PLP
C2/5409: FA           PLX
C2/540A: 60           RTS

If this is the issue, a solution to Nattak's problem could be to give those CAAMs a Magic command, or to kill that branch.
  Find
Quote  



Forum Jump:

Users browsing this thread: 2 Guest(s)


Theme by Madsiur2017Custom Graphics by JamesWhite