News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: Final Fantasy 1 Magic Upgrade Idea  (Read 16798 times)

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #40 on: May 22, 2017, 11:07:20 am »
Looks like somebody still uses tab characters instead of using spaces  ;P

-- Because I don't know exactly how the ch_xxx figure out what channel they're applying to. That's the next step.

It's usually stored in the index (X).  Example code:

Code: [Select]
    LDY #0                ; read the pointer from the track's pointer table
    LDA (tmp), Y          ;   and set it as this channel's score pointer
    STA ch_scoreptr, X
    INY
    LDA (tmp), Y
    STA ch_scoreptr+1, X
Here, X would be either CHAN_SQ1 ($B0), CHAN_SQ2 ($C0) or CHAN_TRI ($D0)


Quote
Meanwhile, some tweaks to the loop code:

So... if loop_marker=$A0, and if X=$B0 (for Square 1), then 
Code: [Select]
LDA loop_marker, X will be reading from address $A0+$B0 ... which gets truncated to $50 because it's a zero-page access.

Which means you're reading/writing your loop counter from RAM address $50... which, according to variables.inc is the "entering_shop" flag.  Which is why when you try to loop it's teleporting you into a shop.

If you want just 2 more bytes for each channel, you can do this:

- In constants.inc, change CHAN_START to $A0 instead of $B0  (giving you more space to work with)
- In constants.inc, change CHAN_BYTES to $12 instead of $10  (giving you 2 more bytes for each channel)
- loop_marker should be $10


As for your regeneration code, I've replaced your comments with my own:
Code: [Select]
    LDY #en_hp                  ; This code is correctly loading current HP into mathbuf 0 and 1
    LDA (@ram), Y
    STA btl_mathbuf
    INY
    LDA (@ram), Y
    STA btl_mathbuf+1

    LDA #$00                    ; A is zero here because you're accessing mathbuf+0 and mathbuf+1.
                                ;   If A was 2, you'd be adding to mathbuf+2 and mathbuf+3 which is not what you want
    LDX #$0A                    ; X is the value to add -- in this case 10  (note you don't always have to use hex
                                ;   if you're using an assembler.  This could have been "LDX #10"
    JSR MathBuf_Add             ; regenerative enemies recover 10 HP (Jiggers)

    LDY #ENROMSTAT_HPMAX        ; You could do LDY #ENROMSTAT_HPMAX+1 and get rid of the INY
    INY
    LDA (@rom), Y               ; Correctly gets the high byte of max HP
    CMP btl_mathbuf+1 ; Compares to high byte of current HP
                                ;    CMP works like this:
                                ;   if A < mem:   C=0, Z=0
                                ;   if A = mem:   C=1, Z=1
                                ;   if A > mem:   C=1, Z=0
                                ; Here, A is the high byte of max hp, and mem is the high byte of current HP
                               
    BCC :+ ; So... this will jump ahead if max < current
                                ;    Logically if max < current, you'd probably want to cap the current HP at the max
                               
                                ; This next block of code will run only if max >= current (enemy is not at full life)
        STA btl_mathbuf+1       ; So... if enemy is not at full life, you are refilling it most of the way by setting the high
                                ;    byte of its current HP to its maximum.  This is probably not what you want
        DEY
        LDA (@rom), Y           ; Gets low byte of max HP
        CMP btl_mathbuf+0       ; Compare to low byte of current HP
        BCC @ADD                ; C will be clear when max < current ... which .. you probably don't want to add when that's the case
        JMP :+
        @ADD:
        STA btl_mathbuf+0

  : LDY #en_hp
    ;LDA btl_mathbuf            ;  <- Your comment on this line was incorrect.  A is not necessarily
    STA (@ram), Y               ;    the low byte of current HP at this point (if the first BCC took the
    INY                         ;    jump, A will be the high byte of max HP, which will totally screw you up)
    LDA btl_mathbuf+1
    STA (@ram), Y

So yeah -- this code is not doing what you want.

The logic you'd want is something along these lines:

- Add 10 to current HP in math buffer (you're doing this fine)
- Compare high bytes of max & current HPs
- If high_max > high_current, do nothing
- If high_max < high_current, jump to some code that caps current HP at max HP
- If high_max = high_current, you need to check the low bytes
- If low_max >= low_current, do nothing
- If low_max < low_current, jump to code that caps current HP at max HP

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #41 on: May 24, 2017, 05:59:47 pm »
Quote
Looks like somebody still uses tab characters instead of using spaces  ;P
Eek, that's why things get messed up on the forums? Sorry! It's less key strokes!

Quote
Which means you're reading/writing your loop counter from RAM address $50... which, according to variables.inc is the "entering_shop" flag.  Which is why when you try to loop it's teleporting you into a shop.
Oohhh... I get it now! Fun little bug though.

Oaw, man, thank you so much for the logic. That's what I was missing. I couldn't get myself thinking clearly about what I WANTED it to do. Even with that little roadmap, I had to stare for a long while to really figure out what was going on. The > < signs get mixed up... like left and right, and white and yellow, east and west, garlic and gorilla... Coding is trouble when you get stuff mixed around so much.

Quote
Anyways, I like your idea a lot and look foreward to the results it's to bad I feel unconfident in my ability to learn programming.


Aw, thanks! I think you could give it a try, screw confidence. I had none when I started, I was terrified, but everyone's been so helpful and patient, and if you love the project you're working on, you'll push yourself to overcome the complications. My friend's been studying coding in school for over a year now, and she hates it, struggles with things all the time. She keeps saying she wishes she had a project to do that motivates and interests her like this little hack of mine, and I keep telling her the moment she does get that, everything'll start getting better with her coding troubles. When you do what you want instead of what the professor wants, it gets fun.

Quote
So are you planning to like take out the level 5-7 elemental spells as well as NUKE? And instead continually make the Level 1-4 offensive spells get more powerful as more ORBs are lit?

Something like that... I'm actually completely re-writing every spell. Every class gets spells now! My silly idea was to take the text MMO DragonRealms and turn it into a Final Fantasy game. So instead of the Fighter, there's the Cleric, with undead smiting and dead-raising. Thief is still a thief, but has Khri, which are non-magical abilities, meditations--kind of like Monks in other FF games? The Black Belt is now Barbarian, who can use every weapon as well as a physical-based AOE attack. Red Mage is Bard, who focuses on party-boosting abilities and some elemental magics. White Mage is Empath, who focuses on healing and debilitating the enemy. Black Mage/Wizard is split into the Moon Mage, who has time/dark/ice based magic and some utility spells like EXIT/WARP--a little bit like the Time Wizard, I guess? And then there's the Warrior Mage, who has all the heavy-hitting elemental spells, and selfish buffs... So, 7 classes, and 5 of the classes have alternate sprites.

Most spells will be available at the start, with some really doozy ones being available later, or even hidden (the empath has 2 pretty forbidden spells...) Then they'll get stronger as you progress, so there's never going to be a time where you're like, "Oh, CURE is all I have left? Fiddlesticks, I might as well just attack and hope not to die."

Your idea might be difficult to do, to have each orb target different spells... I think its possible to have the game overwrite some of its ROM, though? So if there's just a spot to have a look up table for lit orbs, and then have that go to a bit that overwrites the spell data...
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #42 on: May 24, 2017, 06:22:48 pm »
Eek, that's why things get messed up on the forums? Sorry! It's less key strokes!

Any text editor worth using has an "auto-replace tabs with spaces" option.  It's the first thing I set whenever I use a new editor.   ;)

Quote
Oaw, man, thank you so much for the logic. That's what I was missing. I couldn't get myself thinking clearly about what I WANTED it to do.

No probalo.  And yeah.. isolating the logic and figuring out what you want should be the first thing you nail down, though it can be tricky sometimes.   :thumbsup:

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #43 on: May 24, 2017, 07:30:09 pm »
Quote
Any text editor worth using has an "auto-replace tabs with spaces" option.  It's the first thing I set whenever I use a new editor.
Found it and set it!

I went through Bank C and poked at a ton of stuff, and made up a list. Right now the game is freezing when I attack enemies. It draws the character name, the enemy's name, then instead of damage or "missed!", it freezes and the music drones on the stopped note.

But first, a list of the changes I made/want to make.

MUSIC CHANNEL RESETTING?
Code: [Select]
Music_NewSong:
CMP #$40             ; see if bit 6 is set.  If it's set, we need to halt all channels
BCC @PrimeChannel    ;  and begin priming channels.  If set, we already halted channels
                     ;  and began priming.  So jump ahead to resume priming
AND #$3F             ; here if bit 6 was set.  Isolate the low bits (track number)
STA music_track      ; and write back to the music track

LDA #0
STA mu_chanprimer   ; zero the channel primer

;LDA #0
STA sq2_sfx         ; zero sq2_sfx (seems strange to do here)

;LDA #0
STA $4002           ; then zero each of the channels output freq (including noise)
STA $4003           ; This in effect silences the squares
STA $4006           ;  and SLOPPILY silences the tri (tri will pop)
STA $4007           ;  but does not silence noise -- instead it just makes it crazy high pitched
STA $400A
STA $400B
STA $400E
STA $4010           ; for loop_marker?
* Is it smart to cancel out the extra LDA #0s?
* To reset the new Loop Marker variable between song changes, will adding that last line work? Testing indicates no, it doesn't...

LoadOneCharacterIBStats
Code: [Select]
@ApplyWeapon:
LDY #btlch_critrate
AND #$7F                            ; 127
STA (btl_ib_charstat_ptr), Y        ; BUGGED - this sets the critical rate to the weapon index,
                                    ;  rather than actually fetching the critical rate from the weapon
                                    ;  stats.
Very much stumped on how to fix this. Is it as simple as changing AND, or is more code needed? Maybe... this?
LDY #$02 
LDA (lut_WeaponData), Y
LDY #btlch_critrate
STA (btl_ib_charstat_ptr), Y

On that note...

PlayerAttackEnemy_Physical (I think my battle freezing has to do with this at the moment.)
Code: [Select]
LDY #btlch_category             ; BUGGED - this is pulling a player's category (always 0).  It should be
LDA (btl_ib_charstat_ptr), Y    ;  pulling the category of the weapon they're using.
STA btl_attacker_category
   
LDY #btlch_elemweak             ; BUGGED - uses player elemental weakness as attack element.  This is always 0
LDA (btl_ib_charstat_ptr), Y    ;  this should be pulling their elemental value from their equipped weapon
STA btl_attacker_element

I tried to do this:
LDY #$05   ;; Weapon Category
LDA lut_WeaponData, Y ;; or LDA (lut_WeaponData), Y ? (But using parenthesis here causes a range error when compiling...)
STA btl_attacker_category

LDY #$04   ;; Weapon Element
LDA lut_WeaponData, Y   
STA btl_attacker_element

Multiple Enemy Hits/Misses Having Chances to Cause Ailments
Code: [Select]
LDA battle_hitsconnected
BEQ @NextHitIteration           ; we're done if no hits connected yet
                                    ;  This is BUGGED!  Logically you would only perform this check if
                                    ;  THIS attack was a hit.  But instead, this will perform the check
                                    ;  if ANY attacks up to this point were hits.  This means once an enemy
                                    ;  connects with one of their hits, each swing afterwards will have a chance
                                    ;  to inflict an ailment, even if they miss!
Where should this be placed instead? Or do I have to write more code?[/code]

BtlMag_LoadPlayerDefenderStats -- Variable confusion!
btlmag_defender_hitrate: I added this to fix BtlMag_Effect_AttackUp2, but I don't know what address to give it in variables.inc ... Should I use the address used by btlmag_defender_unknownRawInit0?
I also want to use btlmag_defender_magdef for a spell later. Replace btlmag_defender_morale maybe?

Poisoning!
Code: [Select]
Poisoning:

LDA ch_ailments, X  ; get this character's ailments
AND #(AIL_DEAD | AIL_STONE) ; if dead or stoned, exit
BNE @Exit
AND #AIL_POISON
BEQ @Exit           ; if they're not poisoned, nothing to do here, so exit
   
;; Divide Max XP
LDA ch_maxhp, X     ; move max HP to $68B3,4
STA $68B3
LDA ch_maxhp+1, X
STA $68B4
   
LSR $68B4           ; divide it by 8 (12.5% of max HP)
ROR $68B3
LSR $68B4
ROR $68B3
LSR $68B4           
ROR $68B3
LSR $68B4
ROR $68B3

LDA ch_curhp, X
SBC $68B3
STA btl_mathbuf
LDA ch_curhp+1, X
SBC $68B4
STA btl_mathbuf+1 ; Since it takes from the math buffer at the end anyway...

Problem: In testing, a character has 475 Max HP, but only loses 29 to poison. Shouldn't it be 59? Will the dead/stone check work?
Question: BNE after checking for stone/death first? I figured to use it because it's If YES then exit, while with the poison check, its If NO then exit.
Additional Complication: I'd also like this to pop up a few messages to tell the player how much they've been poisoned for. But that's for after I get it working.

Running!
Running:
LDY #ch_luck - ch_stats         ; get player luck
LDA (btl_ob_charstat_ptr), Y
; At this point, the game could just STX tmp, CMP tmp and be done with it.. but noooo.  Bring the math buffer into it!
;  make it even more complicated!  Waste all sorts of space![/code]
STX tmp
CMP tmp
BEQ @Success
Just this will work?

Doublechecking my Orb Boost Code For Battle!
Just want a second set of eyes on this to make sure it looks good and working before I do the grindy annoying testing stuff all over again.

For Accuracy:
Code: [Select]
BtlMag_PrepHitAndDamage:
JSR OrbCount
LDA lut_OrbAccBoost, X     ; .byte 0, 10, 20, 30, 40
ADC #MATHBUF_HITCHANCE
LDX btlmag_hitrate
JSR MathBuf_Add             ; math_hitchance += spell's base hit rate

For Damage (including main code blocks the others JSR to)
Code: [Select]
PutEffectivityInDamageMathBuf:
JSR OrbCount   
JSR OrbDamageBoost       
STA math_basedamage
LDA #0
STA math_basedamage+1
RTS

OrbCount:
CLC
LDA orb_earth
ADC orb_fire
ADC orb_water
ADC orb_air                 
TAX     
RTS

OrbDamageBoost:
LDA lut_OrbDamageBoost, X ; .byte 0, 20, 35, 55, 65
JMP OrbBoostResume
OrbHealBoost:   
LDA lut_OrbHealBoost, X ; .byte 20, 30, 40, 50, 60
OrbBoostResume:
LDY #ch_level - ch_stats ; Add character level to effectivity for that nice extra dose of power
ADC (btl_entityptr_obrom), Y
ADC btlmag_effectivity
RTS

For Healing:
Code: [Select]
BtlMag_Effect_RecoverHP:
JSR BtlMag_MarkSpellConnected   
   
JSR OrbCount
JSR OrbHealBoost
    TAX
    LDA #0
    JSR RandAX                      ;   random between [0,effectivity]
    CLC
    ADC btlmag_effectivity          ;   random between [effectivity, effectivity*2]
    BCC :+
    LDA #$FF                      ;   (cap at 255)
: TAX

And lastly...

Queston with RandAX: Does this routine pick a number BETWEEN 0 and 200, or is it 0 OR 200? It seems like the game has special code for when it hits 200, and that its used for some spells (putting an enemy to sleep/stunning) which seem to have a fairly high fail rate when actually playing the game. Or its a 50/50 fail rate and RNG hates me to the point where I gave up using status spells in games the last 20 years, because they never freakin' work...
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #44 on: May 24, 2017, 10:00:36 pm »
Wow this is a huge post.

MUSIC CHANNEL RESETTING?
[snip]
* Is it smart to cancel out the extra LDA #0s?

As long as you know for sure that A will be 0 -- then yes, getting rid of those LDAs is fine.

Quote
* To reset the new Loop Marker variable between song changes, will adding that last line work? Testing indicates no, it doesn't...

$4010 is a DMC register and is completely unrelated to anything you might be interested in.  So no, that's not going to do anything.

If you want to zero your loop marker variable, then you would STA (with A=0) to whatever address your loop marker variable(s) are.  Which I assume would be:

Code: [Select]
STA CHAN_SQ1+ch_loop_marker
STA CHAN_SQ1+ch_loop_marker+1
STA CHAN_SQ2+ch_loop_marker
 ...etc

There might be some code that resets all the channel data when a song loads --- and if there is it might be easier to just modify that code a bit to fit in your changes.  But maybe that code doesn't exist... I didn't see it when I glanced at the code (though I didn't look very long).  =/

Quote
LoadOneCharacterIBStats
[snip]
Very much stumped on how to fix this. Is it as simple as changing AND, or is more code needed?

 Maybe... this?
LDY #$02 
LDA (lut_WeaponData), Y
LDY #btlch_critrate
STA (btl_ib_charstat_ptr), Y

More code is definitely needed.  Your code is very close but not quite right.  Remember that the parenthesis in 6502 means indirection, so it's going to try to read a pointer from that address.... and 'lut_WeaponData' is not an address that contains a pointer.

I've added some different comments here, which should help out a bit more:

Code: [Select]
    ;  At this point, A is the ID of the equipped weapon with the high bit set, or $00 if no weapon
    ; is equipped.  Example:
    ; A=$00   no weapon
    ; A=$81   wooden nunchucks
    ; A=$82   small knife
    ;   etc
  @ApplyWeapon:
    LDY #btlch_critrate
    AND #$7F                            ; This "chops off" the high bit if a weapon is equipped.  So $81 becomes $01, etc.
    STA (btl_ib_charstat_ptr), Y        ; This writes that value to the btlch_critrate IB stat, which is not the correct value
                                        ;  Note that you might not want to remove this, though -- as it effectively writes "0"
                                        ;  if no weapon is equipped, which you want.
   
    AND #$FF                        ; This isn't really necessary because the above AND 7F took care of it.  But you can
                                    ;   think of this AND as the same as 'CMP #$00'.  Basically it's just checking to see
                                    ;   if a weapon is equipped.
    BEQ :+
   
        SEC                         ; subtracts 1 from the equipped weapon ID ... so now it's zero-based ($00 = wooden nunchucks)
        SBC #$01
        JSR GetPointerToWeaponData  ; This makes ($88) point to the stats for the equipped weapon.
        STA $88                     ; The layout of the stats is outlined at the very top of bank_0C.asm.  The critical rate
        STX $89                     ;   is index 02, so we'd want to do "LDA ($88),Y" when Y=02 to get the weapon's crit stat
     
        LDY #$07
        LDA ($88), Y                ; <- here, Y=7, which is the palette stat
        LDY #btlch_wepplt
        STA (btl_ib_charstat_ptr), Y    ; <- which it then writes to IB palette data
       
            ;;  <<--- This is where you'll want to put in the Crit stat lookup
            ;;  You have all the info you need.  You can use the above palette code as an example -- the crit stat code
            ;;   will look very similar
       
        LDY #$06
        LDA ($88), Y                ; <- Y=6, which is graphic data, etc

Quote
PlayerAttackEnemy_Physical (I think my battle freezing has to do with this at the moment.)

I'm not going to go in as much detail on this one.

The parens were giving you an error because lut_WeaponData is not a zero page address, and you can only do indirection (parenthesis) on a zero page address.

Without the parens, your code doesn't make much sense:

Code: [Select]
LDY #$05
LDA lut_WeaponData, Y

;; The above is the same as:

LDA lut_WeaponData+5
Which will just get you offset 5 of the weapon data... which is zero:


It seems like you're falling into the trap of trying random code and hoping it works.  Which is almost certain never to work.  If you find yourself doing this, take a step back -- ask yourself what you want to do... and what code you need to do it.  You should have a plan before you write any actual code.

But there's another, more basic question to ask here.  Do you understand the difference between "LDA nn, Y" and "LDA (nn), Y"?  Can you explain what "LDA (nn),Y" does?

PS:  don't feel bad or anything -- I'm not trying to rip on you -- I just want to help you learn  =)

Quote
Multiple Enemy Hits/Misses Having Chances to Cause Ailments

I'll leave this one alone for now.  It's not really a matter of moving any code, it might just be branching at a different time.

But to fix this, you'll need to be able to follow what the code is actually doing in order to figure out how to make it do what you want.  Don't just jump straight to the "BUGGED" line... look at the code that leads up to it and see if you can really understand WHY the code is bugged the way it is.


Quote
BtlMag_LoadPlayerDefenderStats -- Variable confusion!
btlmag_defender_hitrate: I added this to fix BtlMag_Effect_AttackUp2, but I don't know what address to give it in variables.inc ... Should I use the address used by btlmag_defender_unknownRawInit0?

You can give it anything that is RAM ($0000-07FF range, or $6000-7FFF range) that isn't already used by something else.

Or... if it is used by something else ... just make sure it isn't being used at the same time.

'unknown' stuff is a good candidate for free RAM, but you might want to Ctrl+F to find out any code that references it and get rid of it.  Also, you might want to Ctrl+F for the raw address (like $6118 or whatever it is) too, just in case.

Quote
I also want to use btlmag_defender_magdef for a spell later. Replace btlmag_defender_morale maybe?

Wait... defender_magdef isn't already there?  It should be.

Also defender_morale would likely be used for the FEAR family of spells, so I probably wouldn't replace that.  But I wouldn't think you'd need to replace anything in this case.... magdef should definitely be there already.

Quote
Poisoning!
Problem: In testing, a character has 475 Max HP, but only loses 29 to poison. Shouldn't it be 59?

You're dividing by 16, not by 8.  Each shift is a divide by 2, and you're doing 4 of them.  1/2 * 1/2 * 1/2 * 1/2 = 1/16

Also you might want to SEC before the first SBC to prevent the additional -1.  But it'd only make 1 HP difference... so whatever.

Quote
Will the dead/stone check work?   Question: BNE after checking for stone/death first? I figured to use it because it's If YES then exit, while with the poison check, its If NO then exit.

It should.  Looks fine to me.  =)

Quote
Additional Complication: I'd also like this to pop up a few messages to tell the player how much they've been poisoned for. But that's for after I get it working.

Drawing stuff is kind of a nightmare.  I don't remember offhand how it all works... I'd have to look it up, and... blech I don't want to do that now.  :P

There are some routines that deal with drawing "Battle Boxes".  From what I remember, you'll want to format a string to put in RAM somewhere, then call a routine that draws a battle box and give it a pointer to that string.

See if you can find the code that draws the damage text for attacks and use that as a guide.

Quote
Running!

So after the STX.. you know the following:
- 'tmp' contains a random number between [0, level+15]
- 'A' contains the player's luck

So it makes sense to CMP them -- but ask yourself if BEQ is what you really want  ;)

Quote
Doublechecking my Orb Boost Code For Battle!

For accuracy:
When you call 'MathBuf_Add', A is the index of the math buffer you'd want to use.  If you're modifying spell hit rate, you'd ALWAYS want A=#MATHBUF_HITCHANCE, since that's the index of the buffer that holds the hit chance.

X is the value that actually gets added.  The original code is adding btlmag_hitrate.  You'll either want to add your orb boost to that value before adding it to the math buffer, or you'd want to add the orb boost separately.

Your code is modifying A.. and thus you are making it add btlmag_hitrate to some other unrelated buffer.  Definitely not what you want.


For Damage:
Assuming math_basedamage is the correct buffer (I'm too lazy to check), that all looks good.

The only thing to worry about here is overflow.  If the character is at level 50 and has all 4 orbs lit, you'll be adding 105 to the effectivity -- which means you can't have any spell that has base effectivity over 150 or else it will overflow.


For Healing:
Looks good... just with same overflow caveats.

Quote
Queston with RandAX: Does this routine pick a number BETWEEN 0 and 200, or is it 0 OR 200?

Between and including whatever values are in A and X.  RandAX with A=4 and X=7 will give you either 4, 5, 6, or 7.

Quote
It seems like the game has special code for when it hits 200, and that its used for some spells (putting an enemy to sleep/stunning) which seem to have a fairly high fail rate when actually playing the game. Or its a 50/50 fail rate and RNG hates me to the point where I gave up using status spells in games the last 20 years, because they never freakin' work...

IIRC 200 is a "critical miss".... like rolling a natural 1 in D&D.  No matter what your stats, if you get that roll, it misses.

But even if you don't get 200, you might still miss.  It will then depend on whatever other logic the spell does to determine hit/miss. 

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #45 on: May 25, 2017, 04:40:46 pm »
Woo!!

Quote
There might be some code that resets all the channel data when a song loads --- and if there is it might be easier to just modify that code a bit to fit in your changes.  But maybe that code doesn't exist... I didn't see it when I glanced at the code (though I didn't look very long).  =/

Yep... I've been up and down Bank D looking for it, and I thought that was it. Priming stuff for a new song, sounds reasonable it'd clear out junk data, and that saving 0 to all those 4000 addresses was how it was doing it... I just went and slipped in my loop marker stuff in there, see if that works next time I test it. Thanks!

Code: [Select]
@ApplyWeapon:
    AND #$7F
                                    ;  rather than actually fetching the critical rate from the weapon
                                    ;  stats.
   
    AND #$FF                        ; update Z flag
    BEQ :+                          ; only do this next block if a weapon is actually equipped
   
        SEC                         ; weapon index is 1-based (convert to 0 based)
        SBC #$01
        JSR GetPointerToWeaponData  ; get a pointer to the weapon data
        STA $88                     ; put the pointer at $88
        STX $89
     
        LDY #$07
        LDA ($88), Y
        LDY #btlch_wepplt           ; get palette
        STA (btl_ib_charstat_ptr), Y
        LDY #$02
        LDA ($88), Y
        LDY #btlch_critrate
        STA (btl_ib_charstat_ptr), Y
        LDY #$05
        LDA ($88), Y
        LDY #btlch_category         ; weapon catagory
        STA (btl_ib_charstat_ptr), Y
        LDY #$04
        LDA ($88), Y
        LDY #btlch_elemweak         ; Weapon element
        STA (btl_ib_charstat_ptr), Y
        LDY #$06
        LDA ($88), Y                ; get the weapon graphic
So with this, the PlayerAttackEnemy_Physical stuff shouldn't need changing.

Quote
It seems like you're falling into the trap of trying random code and hoping it works.  Which is almost certain never to work.  If you find yourself doing this, take a step back -- ask yourself what you want to do... and what code you need to do it.  You should have a plan before you write any actual code.

But there's another, more basic question to ask here.  Do you understand the difference between "LDA nn, Y" and "LDA (nn), Y"?  Can you explain what "LDA (nn),Y" does?
It wasn't entirely random! ...to me. I just forgot a few important facts about how that particular set up works... I think you explained it back around Page 1 of the thread, and I was assuming I remembered the lesson correctly.

Since I can't explain the difference between the two off the top of my head, though... next time I'll go back and try to understand it a second time. I'm making a little document with all your lessons in it that the other ASM help files don't explain very well. XD

Quote
Wait... defender_magdef isn't already there?  It should be.

Also defender_morale would likely be used for the FEAR family of spells, so I probably wouldn't replace that.  But I wouldn't think you'd need to replace anything in this case.... magdef should definitely be there already.

Oh, oh! Its BtlMag_SavePlayerDefenderStats that doesn't save magdef.

    LDA btlmag_defender_magdef      ;
    LDY #ch_magdef - ch_stats       ;
    STA (btl_entityptr_ibrom), Y    ; Error: Symbol 'btl_entityptr_ibrom' is undefined.

... and now I realise why it didn't work the first time and why I brought it up. :I "ibrom" silly me... sorry!

Quote
You're dividing by 16, not by 8.  Each shift is a divide by 2, and you're doing 4 of them.  1/2 * 1/2 * 1/2 * 1/2 = 1/16
Ahhhhh. Another brain fail. Forgetting that division stacks. I saw it done twice to get 4 so I was like, double it for 8! Woo! ...no Jiggers, no.

Quote
So it makes sense to CMP them -- but ask yourself if BEQ is what you really want  ;)
At the time, I thought it made sense. Let's see...

if A is 15 and random value is 10 then A > R and Z = 0 and C = 1
a = luck
x = random value
cmp : a minus x
fail if luck <= random
success if luck > random
if A < mem:   C=0, Z=0
if A = mem:   C=1, Z=1
if A > mem:   C=1, Z=0
BNE ... branch if Z = 0
BEQ ... branch if Z = 1
BCC ... branch if C = 0
BCS ... branch if C = 1

... wait ... I wanted BCS, I think. So that A >= R is success, instead of A <= R being fail. So only A < R is fail. So running is a tad bit easier... But no other set of Cs or Zs looked appropriate without having to use a second one? The original used BNE, but that could have gone either way, with < or >, but not = ...

*drool*

Why did I go with BEQ? Or... or do I want to stick with BNE...?????

Quote
Your code is modifying A.. and thus you are making it add btlmag_hitrate to some other unrelated buffer.  Definitely not what you want.

Gotcha. This is why I asked before testing. I reckon I might have got it eventually, after debugging and going "huh... what?" and then spending another week pouring over the entire thing to figure out what all the values are and how they're being put together, but this is so much easier and faster... thank you!
This should do it right, then...

Code: [Select]
BtlMag_PrepHitAndDamage:
JSR OrbCount
LDA lut_OrbAccBoost, X
ADC btlmag_hitrate
TAX
LDA #MATHBUF_HITCHANCE
JSR MathBuf_Add             ; math_hitchance += spell's base hit rate

Quote
But to fix this, you'll need to be able to follow what the code is actually doing in order to figure out how to make it do what you want.  Don't just jump straight to the "BUGGED" line... look at the code that leads up to it and see if you can really understand WHY the code is bugged the way it is.

*drool* Alright... yeah, I was looking for an easy out with asking, but... I think I found it...? (Edit: ... did not work as intended. Heehee. Now it should.)

Code: [Select]
DoPhysicalAttack_Exit:
    JSR EnemyDoAilment
    JMP RespondDelay_ClearCombatBoxes

And these...

LDY math_dmgcalc   
LDX math_dmgcalc+1
JSR ZeroXYIfNegative
STY math_dmgcalc
STX math_dmgcalc+1 -- All these can be commented out pretty much?

Ahhh, so excited now. So close to being done with this stuff so I can go back to designing actual content...
« Last Edit: May 25, 2017, 06:26:10 pm by Jiggers »
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #46 on: May 25, 2017, 07:41:35 pm »
So with this, the PlayerAttackEnemy_Physical stuff shouldn't need changing.

Well... you've introduced a new problem.  'btlch_elemweak' is the elements a character is weak to.  By assigning the weapon's element to that, you've effectively made the player weak against whatever element their weapon is strong against.

Though, since players never should be weak to any elements, you can repurpose this variable to be their attack element.  To do this, you'll have to find out where it's used to determine their weakness and change that.

Quote
... wait ... I wanted BCS, I think. So that A >= R is success

BCS is what you want.  If Luck is higher than the random number, you would want to run successfully.  This rewards higher luck values.

BNE is not what you want... as luck would be mostly irrelevant and you'd succeed almost every time (you'd only fail if the random number happens to be equal to exactly your current luck)

Quote
Gotcha. This is why I asked before testing. I reckon I might have got it eventually, after debugging and going "huh... what?" and then spending another week pouring over the entire thing to figure out what all the values are and how they're being put together, but this is so much easier and faster... thank you!
This should do it right, then...

Looks good.  Though in general you want to put a CLC before your ADC to avoid the possible +1.  Unless you don't mind occasionally getting a +1 boost.


Quote
All these can be commented out pretty much?

I believe so.  If I mentioned in my notes that they're unnecessary, they probably are.  But it doesn't hurt to double-check -- even I make mistakes sometimes  ;)

Quote
So close to being done with this stuff so I can go back to designing actual content...

You're nuts!  This is the fun stuff!   :D :D


Anyway -- keep up the great work.  Considering this is in the "newbie" forum, you're actually doing moderately advanced stuff.   :beer:

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #47 on: May 25, 2017, 08:14:59 pm »
I think I took care of that new problem before I even made it a new problem...

Code: [Select]
   ;;;;;;;;;;;;;;;;;;;;;
    ; Defender PLAYER CHARACTER stats
    ;LDY #btlch_category                 ; IB:  assigned category.  Always zero?  Players don't have categories.
    ;LDA (btl_ib_charstat_ptr), Y
    LDA #$00
    STA btl_defender_category
   
    ;LDY #btlch_elemweak                 ; IB:  elemental weakness.  (Always zero?)
    ;LDA (btl_ib_charstat_ptr), Y
    LDA #$00
    STA btl_defender_elemweakness
And the same in BtlMag_LoadPlayerDefenderStats.

Why are there two Load Player Defender Stat places? I wonder if they could be put into one big one... eh.

The poison has been being a bother. I had to add another "LDA ch_ailmentx, X" or else the dead/stone ailment flags were messing with the BEQ for the poison check.

I've figured out the boxes I want to draw, I just have no idea how to make them draw what I want them to.

JSR DrawCombatBox_Attacker   ; This should say "Poison" or "Poisoned", whatever I can get away with.
(there's a monster attack name I can use!)
JSR DrawCombatBox_Defender ; This should have the character's name.
JSR DrawDamageCombatBox     ; This should have the damage the poison does.
JSR RespondDelay 
JSR ClearAllCombatBoxes

The whole system is just a convoluted mess, I can't tell where things are written to that these routines actually read off. The only time I got it working was for the enemy regeneration code, but we're not even using an enemy this time... so again, I'm stumped and a bit frustrated. Back-tracking things to their source hasn't made any sense so far, and trying random stuff is... well, not a good idea either.

I think for the Attacker box I just need to set btl_attacker, and same for btl_defender. But how to load the information to save to them? And I have NO idea how DrawDamage is picking what to display...

Quote
You're nuts!  This is the fun stuff!

Yeah, but...! ... but ... maybe the other stuff is MORE fun! ...maybe...
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #48 on: May 25, 2017, 10:35:21 pm »
I think I took care of that new problem before I even made it a new problem...

Nice.  Well done  =)

Quote
Why are there two Load Player Defender Stat places? I wonder if they could be put into one big one... eh.

One for physical attacks and one for magical.  IIRC you can't really combine them into one because magic spells use different sets of vars ("btlmag_xxx") from physical attack ("btl_xxx").  Well... I guess you COULD, but it'd be more work than it's probably worth.

Quote
The poison has been being a bother. I had to add another "LDA ch_ailmentx, X" or else the dead/stone ailment flags were messing with the BEQ for the poison check.

I thought you were skipping all of the poison code if they were dead/stone?


Quote
I've figured out the boxes I want to draw, I just have no idea how to make them draw what I want them to.

You don't want DrawCombatBox_Attacker, since that computes the attacker name based whoever's turn it is.  Since you want your "attacker" to be a phony string ("poison"), you'll probably need to call the generic routine (DrawCombatBox) directly.

The outline for DrawCombatBox is in bank F:
Code: [Select]
;;  DrawCombatBox  [$F71C :: 0x3F72C]
;;
;;  input:  A = ID of box to draw (0-4)
;;         YX = pointer to text data to put in that box
;;
;;  Combat boxes are the boxes that pop up during combat that show attackers/damage/etc.
;;  See lut_CombatBoxes for more.

.. and it refers you to 'lut_CombatBoxes'... which tells you what the IDs are

Code: [Select]
lut_CombatBoxes:
;             BOX                      TEXT
;       hdr    X    Y   wd   ht     hdr    X    Y
  .BYTE $00, $01, $01, $0A, $04,    $01, $02, $02       ; attacker name
  .BYTE $00, $0B, $01, $0C, $04,    $01, $0C, $02       ; their attack ("FROST", "2Hits!" etc)
  .BYTE $00, $01, $04, $0A, $04,    $01, $02, $05       ; defender name
  .BYTE $00, $0B, $04, $0B, $04,    $01, $0C, $05       ; damage
  .BYTE $00, $01, $07, $18, $04,    $01, $02, $08       ; bottom message ("Terminated", "Critical Hit", etc)

So to draw the "Poison" string as the attacker name, you'll want to use box ID 0.  Note that the text is drawn using FormatBattleString (bank F), so you don't actually have to spell out "Poison" if it's an enemy attack... you can use the special format codes (See that routine for details).  Though it might be easier to just spell out "Poison"  =x

Something like this:
Code: [Select]
LDY #>Somewhere_that_has_to_the_POISON_string
LDX #<Somewhere_that_has_to_the_POISON_string
LDA #0    ; draw it as the Attacker
JSR DrawCombatBox_L
INC btl_combatboxcount_alt   ; count this box so it can be properly undrawn later

For defender name and damage you should be able to use those routines directly.


Quote
The whole system is just a convoluted mess, I can't tell where things are written to that these routines actually read off. The only time I got it working was for the enemy regeneration code, but we're not even using an enemy this time... so again, I'm stumped and a bit frustrated. Back-tracking things to their source hasn't made any sense so far, and trying random stuff is... well, not a good idea either.

Yeah... drawing stuff is a beast.  :P


Quote
I think for the Attacker box I just need to set btl_attacker, and same for btl_defender.

I believe you are correct... but "Poison" isn't an attacker -- an attacker is just going to be an enemy or player name.

Quote
But how to load the information to save to them?

btl_defender should be either 80, 81, 82, or 83 depending on which player is currently being damaged by poison.  This might take some bit twiddling to get from whatever code you're working with.

Quote
And I have NO idea how DrawDamage is picking what to display...

Look at the '@Data' section of DrawDamageCombatBox.  The string it prints is heavily formatted, but it's ultimately drawing from 'math_basedamage' (presumably 2 bytes since it's a math buffer).  So just make sure your poison damage is at that spot.

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #49 on: May 26, 2017, 12:05:52 am »
Eee! Your post is like a treasure map. Delicious crumb trails to follow into the forbidden woods... to get eaten by witches! :D Thanks! I'll take a look at all that more thoroughly... tomorrow. Or the next day, if I take a break tomorrow.

Quote
I thought you were skipping all of the poison code if they were dead/stone?

I am, but for some reason the check skips applying poison to someone who is poisoned.

Code: [Select]
LDA ch_ailments, X  ; get this character's ailments
AND #(AIL_DEAD | AIL_STONE) 
BNE @Exit
LDA ch_ailments, X  ; without this, poisoning gets skipped entirely
AND #AIL_POISON
BEQ @Exit

So that's weird, a bit.

One of my other ideas that I've just laid out a basic plan for:
The Empath spell "Inno" (for Innocence), when used in battle, boosts evasion by... a lot. The concept of the spell is that the Empath manipulates the enemy into thinking the Empath is of no harm, and therefore not worth attacking. (I don't think I'm capable of writing special spell code to make enemies not ACTUALLY target the character...) (Also the spell should have the opposite effect against undead, but I'm not yet sure how that would be implemented. So for now, huge evasion boost. Simple.)

When used OUTSIDE of battle, it basically acts as a random battle deterrent. Starts out with 101 steps of battle-free map exploration, then fades.

The Logic:
* Casting Innocence sets a variable to Spell's Effectivity (default 100)
* Taking a step decrements this variable by 1
* If this variable is above 0, then overwrite the output of BattleStepRNG so that the random number can never generate a random battle.

The Code:
Code: [Select]
Variables:
Innocence = $6FFF          ; I so hope this is safe...

[b]Bank F stuff[/b]
BattleStepRNG:
    BIT battlestep_sign    ; see if sign indicates a decrement
    BMI @Negative

  @Positive:
    INC battlestep         ; increment battlestep
    BNE @Done              ; if it hasn't expired, finish up
    BEQ @Expired           ; otherwise, it has  (always branches)

  @Negative:
    DEC battlestep         ; if negative, decrement battlestep
    BNE @Done              ; if it hasn't expired, finish up

  @Expired:                ; if battlestep has expired (= zero)
    LDA battlestep_sign    ; add $A0 to the battlestep_sign
    CLC                    ;   this may potentially change directions
    ADC #$A0
    STA battlestep_sign

  @Done:
    LDX battlestep         ; put battlestep in X
    LDA lut_RNG, X         ; use it to get a random number from the LUT
    STA tmp                ; store in tmp
    LDA Innocence          ; load Innocence variable
    DEC Innocence          ; decrease it by 1?
    CMP #$00               ; if Innocence = 0
    BEQ :+            ; then reload tmp and exit
        LDA #$00           ; otherwise, overwrite output with 0
        RTS            
   
:   LDA tmp
    RTS                    ; and exit

[b]Bank E stuff[/b]
UseMagic_INNO:
    LDA #$XX       ; code for description text ... to be worked out later
    JSR DrawItemDescBox       ; draw description text
    LDX mp_required
    DEC ch_magicdata, X       ; decrement MP
    JSR MenuWaitForBtn_SFX    ; wait for button press

DoMagic_Inno:
   JSR OrbCount               ; Count the orbs lit
   LDA lut_OrbDamageBoost, X  ; .byte 0, 20, 35, 55, 65
   LDY #ch_level - ch_stats
   ADC (btl_entityptr_obrom), Y ;; Need to find out how to get Character level from the menu...
   ADC #$100 ; Spell's base effectivity
   STA Innocence              ; Save Variable
   RTS

Watcha think?
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #50 on: May 26, 2017, 12:33:44 am »
I am, but for some reason the check skips applying poison to someone who is poisoned.

You shouldn't have to LDA a second time.  Something is fishy here.... I wonder if X isn't set properly and you're reading the wrong thing.  Might be worth it to step through it in FCEUX.

EDIT:  Whoops!  Yes, you DO need to LDA a second time, because the poison bit is being cleared with the AND instruction.  Sorry for the confusion.



Regarding Inno:

The $6Fxx page is used for on-screen sprites.  I would not recommend using that address.  I don't think anything on the $6Exx page is used, though... so that should be safe.  And I know for a fact that the $00Ex range isn't used for anything (unless you expanded your music RAM)


Your code in BattleStepRNG looks mostly good.  A few tips:
1:  It looks like for the output of this function... higher numbers mean you DON'T battle.  So you probably want to exit with FF instead of 00.  00 is going to trigger a battle immediately.
2:  You can optimize that a bit.  Instead of reading the RNG, storing it in tmp, then reading tmp later -- get rid of the tmp business and read it directly from the RNG later.
3:  DEC will update Z, so you don't need the CMP #0
4:  DEC also does not use A, so you don't need the LDA Innocence.  'DEC Innocence' will modify memory directly without using any regs.


The Bank E stuff looks pretty good too.  You *might* need to slip in a call to ClearOAM otherwise it might be drawing a stray cursor somewhere.  I'm not entirely sure -- I'm just comparing your code to UseMagic_HealFamily.

To get the character's level, you'll want to do "LDA ch_level, X" where X is either 00, 40, 80, or C0 depending on who the caster is.  I would guess you can get the caster by isolating the high 2 bits of mp_required:

Code: [Select]
LDA mp_required
AND #$C0        ; isolate the high 2 bits
TAX
LDA ch_level, X ; should get you the caster's level

As always, very nice!  =D
« Last Edit: May 26, 2017, 03:35:12 pm by Disch »

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #51 on: May 29, 2017, 11:40:39 pm »
No worries on the confusion part!

I bring you a gift of further confusion.

Code: [Select]
ApplyPoisonToPlayer:
            @id    = $685A  ; local - temp to hold character ID
            @index = $6BAD  ; local - temp to hold OB stat index (00, 40, 80, C0)
                   
    STA @id             ; record character ID
ASL A
    ASL A
    ASL A
    ASL A
    ASL A
    ASL A               ; left shift 6 to convert to index
    TAX                 ; put index in X
    STA @index          ; and back it up for later
     
    LDA ch_ailments, X  ; get this character's ailments
AND #(AIL_DEAD | AIL_STONE) ; if dead or stoned, then Z = 0
BNE @Exit
    LDA ch_ailments, X  ; get this character's ailments
    AND #AIL_POISON
    BEQ @Exit           ; if poison = no, then z = 1
   
LDA ch_maxhp, X     ; move max HP to math_basedamage
    STA math_basedamage
    LDA ch_maxhp+1, X
    STA math_basedamage+1
   
    LSR math_basedamage+1   ; divide it by 8 (12.5% of max HP)
    ROR math_basedamage
    LSR math_basedamage+1
    ROR math_basedamage
    LSR math_basedamage+1
    ROR math_basedamage
   
    LDA ch_curhp, X
    SEC
    SBC math_basedamage
    STA ch_curhp, X
    LDA ch_curhp+1, X
    SBC math_basedamage+1
    STA ch_curhp+1, X     

    LDA ch_maxhp+1, X
    CMP ch_curhp+1, X ; if poison causes the HP to wrap or become equal...
    BCS @DrawPoisonAsAttack
       
      LDA #$00              ; clip their HP at zero... (for the firs time!)
      STA ch_curhp         
      STA ch_curhp+1       
     
      LDX @index
      LDA #AIL_DEAD
      STA ch_ailments, X        ; give them the DEAD ailment
   
    @Exit:
    JSR EmpathHealing
    RTS

    @DrawPoisonAsAttack:            ; Who is getting poisoned
    LDA @id
    ORA #$80
    STA btl_attacker
    JSR DrawCombatBox_Attacker
       
    LDA #$02                        ; print "Poisoned"
    LDY #>POISONTEXT
    LDX #<POISONTEXT
    JSR DrawCombatBox_L
    INC btl_combatboxcount_alt
       
JSR DrawDamageCombatBox         ; somehow print damage eventually   
    LDA btlmag_playerhitsfx
    JSR PlayBattleSFX   
    JSR BattleScreenShake_L
    JSR RespondDelay 
    JSR ClearAllCombatBoxes
RTS

    POISONTEXT:
    .byte $0F,$14,$00 ; FormatBattleString: Battlemessage "Poisoned"

    EmpathHealing:
            @id    = $685A 
            @index = $6BAD
   
    LDA ch_class, X
    CMP #$04
    BEQ @Empath
    CMP #$0A
    BNE @Done                       
   
    @Empath:
    LDA ch_ailments, X  ;
AND #(AIL_DEAD | AIL_STONE) ; if dead or stoned, don't regen
BNE @Done
       
    LDA @id
    STA battle_defender_index     
    ORA #$80
    STA btl_defender               
    LDA #$01
    STA battle_defenderisplayer
           
    LDA ch_level, X                 ; Character level = amount of HP to restore
    TAX
    LDA #MATHBUF_MAGDEFENDERHP       
    JSR MathBuf_Add                 
       
    LDX #MATHBUF_MAGDEFENDERMAXHP
    LDY #MATHBUF_MAGDEFENDERHP
    JSR MathBuf_Compare             ; Compare current HP with max HP
    BCC @Done                       ; same as when casting healing magic
    LDX #MATHBUF_MAGDEFENDERMAXHP   
    LDY #MATHBUF_MAGDEFENDERHP
    JSR MathBuf_CopyXToY
       
    @Done:
    RTS

So: Poison does its thing. Attacker Battle box is drawn with the character's name, then "Poisoned" is drawn below it, and to the right, the damage dealt. Everything's working good now! Thanks to your treasure map!

I am a LITTLE unsure if my method of killing someone via poison is good. It WORKS, but it feels iffy.

Then, since we have the character ID and all that, I wanted to add a little something extra. The healing class recovers HP=their level every turn (for reasons I'll further explain.) The method of regeneration, however... does not work. I thought I'd save a ton of space by copying over the healing magic routine (which caps the HP, which was the problem I first had; I didn't know how to do that. Then I ran into the negative HP problem with the poison, so I think I have the answer now.)

The question is: Do I figure out how to make this #MATHBUF_MAGDEFENDERHP stuff work, or do I go back to my old routine and just work on capping the regeneration to max HP?

Additionally: Character in slot 4 (or 3, I guess it doesn't matter) died of poison. Slot 1 character's HP goes to 0 too. Why? They don't die... at first. Just have their HP set to 0. In my most recent test, Character 1 was also poisoned, and was going to die at the end of the turn. Then the enemy attacked, missed, and they died from ... the miss? Heh...
Edit: I think because I forgot the , X this time.

Additionally: Currently, when HP is re-drawn for poisoned characters, its done all together, but I'd like to change that so its re-drawn between characters getting poisoned. I have not yet looked into this, just making a note of it. That was easy.

On the Empath/healer class regenerating small bits of HP: I want to make it so when they cast a healing spell on someone else, their own HP is decremented by the same amount. Or possibly use my lovely OrbCount routine to make it so every lit orb shaves off a percentage of how much HP they themselves lose. For instance, at the start, the HP healed on another is = to the HP the healer loses. With 1 orb lit, the empath only loses 90% of the HP healed on another. 2 orbs, %80, etc. Too complex?

More I want to ramble on, but I think this is enough to work on for now...
« Last Edit: May 30, 2017, 12:21:16 am by Jiggers »
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #52 on: May 31, 2017, 03:47:04 pm »
So I swear I replied to this thread last night.  But apparently I didn't.  I must have just previewed and never submitted.  Argh.  I'll try to remember everything I said before.

Notes on your code:

- Your check to see if their HP went below zero is weird, but should work for BELOW zero, but will not work if their HP becomes EXACTLY zero.  An easier way to check this is to see if there was borrow after the subtraction (C flag).  When you ADC two values together, C will be set if there was overflow.  Likewise, when you subtract 2 bytes, C will be clear when there's overflow (or underflow, I guess).

Code:
Code: [Select]
    ; ... subtraction code here ...
    SBC math_basedamage+1
    STA ch_curhp+1, X           ; subtraction code ends with this
                ;  At this point, C is clear if the subtraction resulted in a value less than zero
             
    BCC @Dead       ; if C is clear, they're dead
   
                ; Otherwise, still need to check for exactly zero.  High byte of HP is still in A
    ORA ch_curhp, X ; OR with low byte.  Now, A will be zero IFF both HP bytes are zero
    BEQ @Dead   ; If A is zero, their HP is zero, so they're dead
   
                ; Otherwise, they survived

- If poison kills the player, you will not be drawing the poison text message... as your code only jumps to that if the player survives.

- If an empath is poisoned, their HP will not be recovered, as you jump over that code to draw the poison text.  Was that intentional?

- The empath healing code is doing lots of stuff with the math buffer, but you never actually put anything in the math buffer for it to use.  Basically you're doing a bunch of calculations with random nonsense, and then you're never actually doing anything with the result of that calculation.



The question is: Do I figure out how to make this #MATHBUF_MAGDEFENDERHP stuff work, or do I go back to my old routine and just work on capping the regeneration to max HP?

My personal opinion is that the math buffer is overly confusing and kind of dumb.  I think the idea behind it was because 16-bit math requires several instructions, the dev wanted to have a set of routines he could call to just do it in one place, and then he wouldn't have to do the math multiple times in the code.

The problem is... you still have to copy the 16-bit values to/from the math buffer to actually make those routines useful... and copying them back and forth isn't any less work than just doing the math directly.  In fact... it's often more work.

So no... I personally would not bother trying to understand the math buffer.  But that's just me.


Quote
Additionally: Character in slot 4 (or 3, I guess it doesn't matter) died of poison. Slot 1 character's HP goes to 0 too. Why?

By your edit I assume you figured this out?

Quote
Too complex?

It's only too complex if you can't figure out how to do it or you don't have the space to do it  ;P

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #53 on: May 31, 2017, 05:00:31 pm »
Oop, thanks! I'll go check that stuff out.

I think I might wanna do some experimenting without worrying about space for a while... how might I configure the disassembly to build a double-size rom? Hrm... Well, would making it double-size make every bank double-size, or just double the amount of banks? If it doubles the amount of banks, that's too complicated... Also, would it double the amount of RAM available to use, or no?

__________________

Quote
- The empath healing code is doing lots of stuff with the math buffer, but you never actually put anything in the math buffer for it to use.  Basically you're doing a bunch of calculations with random nonsense, and then you're never actually doing anything with the result of that calculation.
Yeah... trying to figure out how to load the right stuff into those variables. For some reason I thought it might do it automatically if btl_defender is set. I tried to JSR to BtlMag_LoadPlayerDefenderStats, but that just plays a sound effect and flashes the character, doesn't seem to help the variables point to the right data. I just don't get it, so I guess I'll go with what I had before.

Quote
By your edit I assume you figured this out?
Yep! I made a mistake and caught it. And you caught my other ones! It's fixin' time.
« Last Edit: May 31, 2017, 05:43:33 pm by Jiggers »
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #54 on: May 31, 2017, 05:29:12 pm »
Quote
I think I might wanna do some experimenting without worrying about space for a while... how might I configure the disassembly to build a double-size rom? Hrm... Well, would making it double-size make every bank double-size, or just double the amount of banks?   If it doubles the amount of banks, that's too complicated...

You'd have to double the number of banks.  Also you'd have to switch to SUROM which would require tweaking some mapper startup code.  Either that or switch to a different mapper entirely, which would be more work.


Quote
Also, would it double the amount of RAM available to use, or no?

The short answer is "no".

Technically if you have SXROM you can get more RAM, but there's no reliable way to tell the emulator you want SXROM... so support would be spotty.  To reliably get more RAM you'd have to switch to something like MMC5.



Note that mapper hacks aren't really all THAT difficult.  Especially in FF's case where it hardly uses any mapper functionality and only touches mapper regs in two spots (startup and bankswitching).  Switching to MMC5 just for extra ROM and RAM wouldn't be the most difficult thing ever.

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #55 on: June 01, 2017, 12:38:09 am »
I'll have to come back to that... Sounds like a good idea. I'm again running short of space on C. 12 bytes left. I'll need to either drop some of my ideas, or do something drastic and difficult, like... consolidate all the different box drawing routines or simplify everything that uses the math buffer or something insane. Ugh.

I accidentally deleted my good empath regenerate code, then realised I had regenerate code already written up for enemies. Big, bulky, confusing code. I forget if I even updated that last time you helped with it.  :-[ Gotta go back and doublecheck.

I spent an hour staring at > and < signs trying to remember what they stand for. Today is just not my day.

Here's what I got done so far. Last I tested, it still doesn't heal. But it did draw the name and message. It should RTS at the end, to do a JSR DrawCharacterStatus...

Code: [Select]
EmpathHealing: ; should make this @ ? Then just LDA @id and @index?
            @id    = $685A 
      STA @id        ; A is tmp is old id from poison routine
    LDA tmp+1    ; tmp+1 is index
    TAX                 ; put index in X
       
    LDA ch_class, X
    CMP #$04
    BEQ @Empath
    CMP #$0A
    BNE @Exit
       
    @Empath:
    LDA ch_ailments, X  ;
AND #(AIL_DEAD | AIL_STONE) ; if dead or stoned, don't regen
BNE @Exit
   
    LDA ch_level, X
    STA math_basedamage ;; kept math_basedamage in case I wanted to use the damage box to show healing...
    ; LDA #$00
    ; STA math_basedamage+1
    ; Necessary?

     
    LDA ch_curhp, X     ; Load current HP low byte
    CMP ch_maxhp, X         ; Compare to max HP low byte
    ; continue if current HP is less than max HP
    ; A   <  V
    ;450 < 453 : if A < value:  C=0, Z=0
    BCS @Exit ; branch if C = 1
           
    ;LDA ch_maxhp+1, X                   ; Load HP Max high byte
    ;CMP ch_curhp+1, X     ; Compare to high byte of current HP
    ;BEQ @Exit ; if Z = 1, exit (current HP = max HP, no need to regen)
    ; I can't wrap my head around this at the moment, does adding the high bytes matter if we're never adding more than 50?
   
    ; A is still ch_curhp, X
    CLC
    ADC math_basedamage    ; add regen amount to current HP
    CMP ch_maxhp, X             ; check if that went over...
    ; say we're adding 10 ...
    ; A     V
    ;460 > 453 : if A > value:  C=1, Z=0
    ; if it never goes over, or =, then C=0
    ; BCC : branch if C = 0
    BCC @DrawRegenBoxes         ; if it did go over, then C = 1, we need to cap it
                                ; if it didn't go over, then print regen message...
    LDA ch_maxhp, X             ; cap by loading max and saving as current...
    STA ch_curhp, X       
    ;LDA #$00
    ;STA ch_curhp+1, X ; necessary?

    @DrawRegenBoxes:   
    LDA tmp
    ORA #$80
    STA btl_attacker
    JSR DrawCombatBox_Attacker
    LDA #$04                        ; print "Regenerated HP!"
    LDY #>REGENTEXT
    LDX #<REGENTEXT
    JSR DrawCombatBox_L
    INC btl_combatboxcount_alt
    JSR RespondDelay 
    JSR ClearAllCombatBoxes
   
    @Exit:
RTS

    REGENTEXT:
    .byte $0F,$16,$00 ; FormatBattleString: Battlemessage "Regenerated HP!"
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #56 on: June 01, 2017, 01:37:23 am »
Quote
; should make this @ ? Then just LDA @id and @index?

That's entirely up to you.  There's no "wrong" way to do it.... it's just whatever is easier for you to use.

The way I tried to divide up when I did the disassembly was to find isolated routines.  Code that could be called from elsewhere in the program (even if it wasn't -- as long as it COULD have been).  Those got "top level" labels (no @ symbol)... and anything else I considered local (with a @ symbol).

But it's completely up to you.

Quote
    LDA ch_curhp, X             ; Load current HP low byte
    CMP ch_maxhp, X              ; Compare to max HP low byte
    ; continue if current HP is less than max HP

Comparing just the low bytes is no good.  Let's say you are comparing 300 ($012C) to 100 ($0064).  By just comparing the low bytes ($2C < $64), you have concluded that 300 is less than 100, which clearly is wrong.

Compare high bytes first.  Only compare the low bytes if the high bytes are equal.

But really, you probably shouldn't be comparing here anyway.  I would add HP regardless of whether or not they're at full HP -- and then simply cap them at their max if they spill over.  You have to do the capping anyway, so checking to see if they're at max before hand is unnecessary.

Quote
; I can't wrap my head around this at the moment, does adding the high bytes matter if we're never adding more than 50?

You need to include any carry that happens from the low byte addition.  IE, if you have 255 ($00FF) HP and you add 50... you'll need to increment that high byte.

You can do this with another ADC (ADC #0) on the high byte, or you can branch over an INC:

Code: [Select]
; pseudo-code
CLC
LDA low_result
ADC low_add
STA low_result

BCC :+  ; if there was no carry, skip over this INC
   INC high_result   ; if there was carry, increment the high byte

:

Quote
    LDA ch_maxhp, X             ; cap by loading max and saving as current...
    STA ch_curhp, X       
    ;LDA #$00
    ;STA ch_curhp+1, X ; necessary?

You probably do not want to zero the high byte, as that would potentially take away a LOT of HP.  Example... if they have 300 ($012C) HP and you zero the high byte, they'll end up with 44 ($002C) HP.

If you're capping at max HP, you'll want to copy both maxhp bytes to curhp.

Lenophis

  • Discord Staff
  • Hero Member
  • *****
  • Posts: 971
  • The return of the sombrero!
    • View Profile
    • Slick Productions
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #57 on: June 01, 2017, 01:05:30 pm »
If you are in need of free space, this patch may be able to help. It frees up 0x23 bytes in bank F, as well as adding in DTE support in battles.


https://ff6randomizer.codeplex.com/ - Randomize your FF6 experience!

Jiggers

  • Sr. Member
  • ****
  • Posts: 422
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #58 on: June 01, 2017, 09:45:59 pm »
If you are in need of free space, this patch may be able to help. It frees up 0x23 bytes in bank F, as well as adding in DTE support in battles.

Ooh, thanks, I'll take a look at that!

I think I'm just going to give up on this poison/regen thing and swap back the old code. Nothing's working anymore and I've been over it again and again, missing whatever it is that's going wonky.

Currently in test, as soon as poison knocks a character down from 475 HP to 255 HP, it rolls them back up to Max HP + some extra. And the empath regenerates HP again, but again, won't cap it. I suspect the regen problem is something to do with adding the high byte... I'll try to debug it again, I guess, even though I hate FCEUX's stupid debugger. (Why does it always move away from where you're looking, and why does it show random lines when you press UP to see what your code just did, since it doesn't update until it's off-screen???)

I did manage to streamline the code a little bit; I have 7 bytes left.

Code: [Select]
ApplyPoisonToPlayer:
            @id    = $685A  ; local - temp to hold character ID
            @index = $6BAD  ; local - temp to hold OB stat index (00, 40, 80, C0)
                   
    STA @id             ; record character ID
    ASL A
    ASL A
    ASL A
    ASL A
    ASL A
    ASL A               ; left shift 6 to convert to index
    TAX                 ; put index in X
    STA @index          ; and back it up for later
     
    LDA ch_ailments, X  ; get this character's ailments
    AND #(AIL_DEAD | AIL_STONE) ; if dead or stoned, then Z = 0
    BNE @Exit
    LDA ch_ailments, X  ; get this character's ailments
    AND #AIL_POISON
    BEQ @Exit           ; if poison = no, then z = 1
   
    LDA ch_maxhp, X     ; move max HP to math_basedamage
    STA math_basedamage
    LDA ch_maxhp+1, X
    STA math_basedamage+1
   
    LSR math_basedamage+1   ; divide it by 8 (12.5% of max HP)
    ROR math_basedamage
    LSR math_basedamage+1
    ROR math_basedamage
    LSR math_basedamage+1
    ROR math_basedamage
   
    LDA ch_curhp, X
    SEC
    SBC math_basedamage
    STA ch_curhp, X
    LDA ch_curhp+1, X
    SEC
    SBC math_basedamage+1
    STA ch_curhp+1, X     
   
    BCC @Dead       ; if C is clear (HP is negative), they're dead
    ORA ch_curhp, X ; if high and low are 0
    BNE @DrawPoisonAsAttack
       
    @Dead:
    LDA #0              ; clip their HP at zero...
    STA ch_curhp, X         
    STA ch_curhp+1, X                 
    LDA #AIL_DEAD
    STA ch_ailments, X        ; give them the DEAD ailment
               
    @DrawPoisonAsAttack:            ; Who is getting poisoned
    LDA @id
    ORA #$80
    STA btl_attacker
    JSR DrawCombatBox_Attacker
       
    LDA #$02                        ; print "Poisoned"
    LDY #>POISONTEXT
    LDX #<POISONTEXT
    JSR DrawCombatBox_L
    INC btl_combatboxcount_alt
       
    JSR DrawDamageCombatBox         ; print damage
    LDA btlmag_playerhitsfx
    JSR PlayBattleSFX   
    JSR BattleScreenShake_L
    JSR RespondDelay 
    JSR ClearAllCombatBoxes
   
    @Exit:
    JSR @EmpathHealing
    JSR DrawCharacterStatus     ; redraw character stats to reflect post-poison HP
    RTS

    @EmpathHealing:
    LDA @index
    TAX   
       
    LDA ch_class, X
    CMP #$04
    BEQ @Empath
    CMP #$0A
    BNE @EmpathDone
       
    @Empath:
    LDA ch_ailments, X  ;
    AND #(AIL_DEAD | AIL_STONE) ; if dead or stoned, don't regen
    BNE @EmpathDone
   
    LDA ch_level, X
    STA math_basedamage
    LDA #$00
    STA math_basedamage+1
           
    LDA ch_curhp, X     ; Load current HP low byte
    CLC
    ADC math_basedamage
    STA ch_curhp, X
    LDA ch_curhp+1, X
    ADC math_basedamage+1
    STA ch_curhp+1, X
    BCC @DrawRegenBoxes         ; if it did go over, then C = 1, we need to cap it
                                                   ; if it didn't go over, then print regen message...
    LDA ch_maxhp, X                    ; cap by loading max and saving as current...
    STA ch_curhp, X   
    LDA ch_maxhp+1, X           
    STA ch_curhp+1, X   
       
    @DrawRegenBoxes:   
    LDA @id
    ORA #$80
    STA btl_attacker
    JSR DrawCombatBox_Attacker
    LDA #$04                        ; print "Regenerated HP!"
    LDY #>REGENTEXT
    LDX #<REGENTEXT
    JSR DrawCombatBox_L
    INC btl_combatboxcount_alt
    JSR RespondDelay 
    JSR ClearAllCombatBoxes
   
    @EmpathDone:
RTS
   
    POISONTEXT:
    .byte $0F,$14,$00 ; FormatBattleString: Battlemessage "Poisoned"
    REGENTEXT:
    .byte $0F,$16,$00 ; FormatBattleString: Battlemessage "Regenerated HP!"
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Final Fantasy 1 Magic Upgrade Idea
« Reply #59 on: June 01, 2017, 09:56:40 pm »
In your poison code, you are SEC'ing between lo and high SBCs.  You don't want to do that.  You want to preserve the C flag between subtraction so the borrow gets pulled from the high byte when needed (this would explain why it's rolling them back and giving them a bunch of HP when they cross the 256 line)

Your empath healing code also never checks to see if you exceeded their max HP.  By checking C after addition, you are only checking to see if the HP wrapped (IE, you're not letting them exceed $FFFF HP).