Users browsing this thread: 1 Guest(s)
Expansion of Treasure Chest subroutine

#1
Posts: 48
Threads: 7
Thanks Received: 0
Thanks Given: 0
Joined: Dec 2017
Reputation: 0
Status
None
Hello,

I started to learn ASM and I'm having lots of fun. I have an idea to expand the treasure chest subroutine. Currently, it offers the following possibilities:
  • Granting an item.
  • Granting a multiple of 100 gold, up to 25500 gold.
  • Starting a Monster-in-a-box encounter with a monster pack between #256 and #511.
  • Being empty.

I want to change it so it would also be possible to:
  • Grant a rare item. [DONE]
  • Grant a summon. [DONE]
  • Grant between 1 and 99 items of one type. [DONE]
  • Grant a multiple of 10 gold, up to 655350 gold (the value of 10 is arbitrary, of course). [DONE]
  • Start a Monster-in-a-box encounter with any encounter pack.
  • Start a MiaB encounter with a monster pack between #256 and #511 then grant an item, some gold, a rare item or a summon upon winning the battle.

While the first two are technically already possible (as a small test, I changed the JSR branching of getting an item to the one that gives a summon and it worked. The message box was wrong, but I got the summon.), the other ones require an additional byte in the chest information. The Treasure Chest data is composed of 5 bytes starting at $ED/8634.
  • Coordinates of the chest (X, Y)
  • Encoding byte and bit (byte is added to $1E40)
  • Treasure type (0x08: empty, 0x20: Monster-in-a-box, 0x40: Item, 0x80: Gold)
  • Treasure value (MiaB encounter pack, item type or gold quantity)

Here is the beginning of the subroutine:
Code:
C0/4C06:    C220        REP #$20          (16 bit accum./memory)
C0/4C08:    BF3886ED    LDA $ED8638,X   (Load chest item index / gold / encounter # in A)
C0/4C0C:    851A        STA $1A         (Store A in $1A)
C0/4C0E:    BF3686ED    LDA $ED8636,X   (Load relative encoding byte/bit and treasure type in A)
C0/4C12:    851E        STA $1E         (Store A in $1E)
C0/4C14:    290700      AND #$0007      (A = A AND #$0007 => Load encoding bit in A)
C0/4C17:    AA          TAX             (Transfer A to X)
C0/4C18:    A51E        LDA $1E         (Load relative encoding byte/bit and treasure type in A from $1E)
C0/4C1A:    29FF01      AND #$01FF      (A = A AND #$01FF => Load encoding byte/bit in A)
C0/4C1D:    4A          LSR A
C0/4C1E:    4A          LSR A
C0/4C1F:    4A          LSR A           (Getting rid of encoding bit, keeping relative encoding byte in A)
C0/4C20:    A8          TAY             (Transfer A to Y)


The thing is that the X register contains the relative value of the first byte of the current treasure chest. It is 5 times larger than the chest "ID". So, it gave me an idea. There is absolutely no need the keep the encoding byte/bit as hard-coded information if it can be found by a simple division (I know it costs many cycles, so it is not that simple! Tongue ). In that way, you ensure that every chest has its own encoding bit.

Personally, I never liked the whole "not opening treasure chests so they offer more powerful items in the future" thing. I want to open ALL the chests. Additionally, it frees a whole byte.

Code:
C0/4C06    C2    20    --    --    REP #$20    
C0/4C08    20    13    D6    --    JSR $D613    (Jumping to subroutine at $D613)
C0/4C0B    BF    36    86    ED    LDA $ED8636,X    (Loading encoding byte/bit and treasure chest in A)
C0/4C0F    85    1E    --    --    STA $1E        (Storing A in $1E, item type / gold quantity / encounter number is in $1F, just as before)
C0/4C11    AD    16    42    --    LDA $4216    (Loading quotient in A)
C0/4C14    29    07    00    --    AND #$0007    (A = A AND 0x0007, keeping encoding bit)
C0/4C17    AA    --    --    --    TAX        (Transfer A to X)
C0/4C18    AD    16    42    --    LDA $4216    (Reloading quotient in A)
C0/4C1B    4A    --    --    --    LSR A    
C0/4C1C    4A    --    --    --    LSR A    
C0/4C1D    4A    --    --    --    LSR A        (Shifting data three bits to the right, keeping only encoding byte in A)
C0/4C1E    A8    --    --    --    TAY        (Transfer A to Y)
C0/4C1F    EA    --    --    --    NOP            
C0/4C20    EA    --    --    --    NOP        (Chilling for 4 cycles, then resuming original subroutine)


C0/D613    8E    04    42    --    STX $4204    (Storing X as dividend)
C0/D616    A9    00    05    --    LDA #$0500    (Loading 0x0500 in A)
C0/D619    8D    06    42    --    STA $4206    (Storing 0x05 as divisor)
C0/D61C    BF    38    86    ED    LDA $ED8638,X    (Putting item type / gold quantity / encounter number in A)
C0/D620    85    1A    --    --    STA $1A        (Storing A in $1A)
C0/D622    60    --    --    --    RTS

Please note this is only a proof-of-concept. I know the code is really not optimized. I just wanted to move as less stuff as possible around. Now, this works. The chest is obtainable once and no longer available after that. But when the map is reloaded (after opening/closing the menu, finishing a battle or leaving the map and coming back), the chest is closed once more (its content can't be retaken though). My first question would be: Where in the map generation code the subroutine that decides if a chest is open or closed? Thanks in advance!
  Find
Quote  
[-] The following 1 user says Thank You to NPCnextdoor for this post:
  • madsiur (07-01-2018)

#2
Posts: 3,970
Threads: 279
Thanks Received: 236
Thanks Given: 58
Joined: Oct 2011
Reputation: 65
Status
Tissue-aware
The treasure chest bits are at $7E1E40-$7E1E7F. The code is inevitably in bank $C0. Make a search for "$1E40", $C01614, $C04C24 (chest opening) and $C0BB14 (RAM / SRAM init) will come up. You answer should be $C01614, but I have not really looked closely at that code.

BTW, this is a cool work. Nothing stops you once you found all relevant code to expand a data structure with any mean of programming, python or C family languages. A quick script can automate the data shuffling and the rest is regular ASM work and data relocation in some other ROM spot (expanding usually mean it won't fit in original ROM place unless you cut back on an amount of entries).
  Find
Quote  

#3
Posts: 48
Threads: 7
Thanks Received: 0
Thanks Given: 0
Joined: Dec 2017
Reputation: 0
Status
None
Found it! Thanks Madsiur!

Code:
Checking chest coordinates and event bit
15F1    20    13    D6    --    JSR $D613            Preparing the division
15F4    BF    34    86    ED    LDA $ED8634,X            Load (X, Y) coordinates of chest into A
15F8    85    2A    --    --    STA $2A                Store X coordinate in $2A, Y coordinate into $2B
15FA    DA    --    --    --    PHX                Push X to stack
15FB    EA    EA    EA    --    NOP x 3                Being haunted by the ghosts of your past for 6 cycles.
15FE    20    1D    D6    --    JSR $D61D    
1601    4C    11    16    --    JMP $1611    
1604    FF    FF    FF    FF                
1608    FF    FF    FF    FF                
160C    FF    FF    FF    FF                
1610    FF    --    --    --                

Opening a treasure chest
C0/4C06    C2    20    --    --    REP #$20
C0/4C08    20    13    D6    --    JSR $D613
C0/4C0B    BF    38    86    ED    LDA $ED8638,X
C0/4C0F    85    1A    --    --    STA $1A
C0/4C11    BF    36    86    ED    LDA $ED8636,X
C0/4C15    85    1E    --    --    STA $1E
C0/4C17    20    1D    D6    --    JSR $D61D
C0/4C1A    4C    21    4C    --    JMP $4C21
C0/4C1D    FF    FF    FF    FF            

Division & Retrieving event byte/bit
C0/D613    8E    04    42    --    STX $4204            Store X as dividend
C0/D616    A9    00    05    --    LDA #$0500    
C0/D619    8D    06    42    --    STA $4206            Store 0x05 as divisor
C0/D61C    60    --    --    --    RTS
C0/D61D    AD    16    42    --    LDA $4216            Load quotient into A
C0/D620    29    07    00    --    AND #$0007            A = A AND 0x07, keeping encoding bit
C0/D623    AA    --    --    --    TAX                Transfer A to X
C0/D624    AD    16    42    --    LDA $4216            Reload quotient into A
C0/D627    4A    --    --    --    LSR A    
C0/D628    4A    --    --    --    LSR A    
C0/D629    4A    --    --    --    LSR A                Shift 3 bits to the right, keeping encoding byte
C0/D62A    A8    --    --    --    TAY                Transfer A to Y
C0/D62B    60    --    --    --    RTS

And yes, I already made a script to randomize the treasure chests. It will only be a matter of fooling around with the function to write information back to the ROM.
  Find
Quote  
[-] The following 1 user says Thank You to NPCnextdoor for this post:
  • madsiur (07-01-2018)

#4
Posts: 3,970
Threads: 279
Thanks Received: 236
Thanks Given: 58
Joined: Oct 2011
Reputation: 65
Status
Tissue-aware
(07-01-2018, 11:20 PM)NPCnextdoor Wrote: And yes, I already made a script to randomize the treasure chests. It will only be a matter of fooling around with the function to write information back to the ROM.

I was more referring to expand a structure in a general way, like from 5 bytes to 6 bytes in this case (though it is not needed in this hack). Biggest obstacles when this is wanted is that people don't have the tools to reorganize and edit the data. This is what is cool about being able to code in higher languages :)

Is it a total chest randomization or are you keeping some structure for increase in loot types? As an example, preventing the paladin shield or top tier treasure to be found at beginning?
  Find
Quote  

#5
Posts: 48
Threads: 7
Thanks Received: 0
Thanks Given: 0
Joined: Dec 2017
Reputation: 0
Status
None
I thought about using 6 bytes if I was not going to be successful in eliminating the need for the event byte. But thanks to you, I found a way to do it! Laugh

Regarding chest randomization, I have three options in mind:
  • Shuffling every chest among themselves. (Kind of boring)
  • Randomizing chest contents but offering items or gold from the same tier as the original item, as you mentioned. (Mirage Vest in the Cave to the Sealed Gate, perhaps?)
  • Anything goes on! (My personal favorite)
But doing so will open the possibility to have better equipment in an early chest, hence the need to be able to open every chest.

I also want to randomize secret item locations, e.g. the clock, the armor, the fireplace and wherever Arvis checks when he takes the Slave Crown in his house could potentially hold an item. On top of that, since the game currently has a little more than 270 chests, there is still place for at least 200 other secret items. I want to see how many more of these could be added before it starts to feel ridiculous.
  Find
Quote  

#6
Posts: 48
Threads: 7
Thanks Received: 0
Thanks Given: 0
Joined: Dec 2017
Reputation: 0
Status
None
Update: I started the expansion with gold treasure chests.

[Image: new_chest_subroutine_00000.png]

When I asked about how Final Fantasy VI handled 16 by 8-bit multiplication, Assassin wrote to look at C2/47B7. I must admit, I did not fully understand how it worked. Also, I noticed that the division process I used to get the chest ID did not work properly. By the time I arrive at the Returner's Hideout, most of the chests and pots are already empty. So, I decided to wrote my own subroutines instead of relying of the special registers that may or may not be used by something else. I have yet to write the long division, but here is my multiplication subroutine. (I love to say subroutine, I feel like I'm in Star Trek! Tongue )

I first experimented with hardcoding the multiplier, 10 in this case.

Code:
C2    21    REP    #$21    16-bit mode + clear Carry bit
64    24    STZ    $24    Store zeroes in bytes 4 ($24) and 3 ($25)
A5    1A    LDA    $1A    Load multiplicand (base GP value) in A
0A    --    ASL    --    Left-shift A, therefore doubling base GP value
90    02    BCC    2ND_MULT    If the multiplication doesn't set the Carry bit (or, does the multiplication of the first two-bytes smear into byte 3?), skip next operation
E6    25    INC    $25    Add one to byte 3
06    25    ASL    $25    -=2ND_MULT=- Left-shift byte 3 (No left-shift of byte 4 is made, because there is no need when multiplying a 16-bit value by 10.)
0A    --    ASL    --    Left-shift A, multiplication factor is now 4.
90    02    BCC    ADDITION    If the multiplication doesn't set the Carry bit, skip next operation
E6    25    INC    $25    Add one to byte 3
C2    01    REP    #$01    -=ADDITION=- Clear Carry bit, making sure it doesn't mess the next operation, an addition
65    1A    ADC    $1A    Add base GP value to A, multiplication factor is now 5.
90    02    BCC    3RD_MULT    If the addition doesn't set the Carry bit, skip next operation
E6    25    INC    $25    Add one to byte 3
06    25    ASL    $25    -=3RD_MULT=- Left-shift byte 3
0A    --    ASL    --    Left-shift A, multiplication factor is now 10.
90    02    BCC    END    If multiplication doesn't set the Carry bit, skip next operation
E6    25    INC    $25    Add one to byte 3
85    22    STA    $22    Store A back into $22
60    --    RTS    --    -=END=-

So, of course, I wanted to automate the process for any 16-bit value so that I can reuse this subroutine. Here is what I got:

Code:
C0/D700    C2    31    --    REP    #$31    16-bit accumulator mode, 16-bit X and Y registers and clear Carry bit
C0/D702    64    22    --    STZ    $22    Store zeroes in byte 2 and 1
C0/D704    64    24    --    STZ    $24    Store zeroes in byte 4 and 3
C0/D706    A4    1A    --    LDY    $1A    Load base GP value (multiplicand) into Y
C0/D708    A6    1C    --    LDX    $1C    Load multiplier in X
C0/D70A    F0    37    --    BEQ    END    If X = 0, skip to the end
C0/D70C    A9    10    00    LDA    #$000F    Load (15) into A, the maximum amount of left-shifts possible of a 16-bit value minus 1 before is it guaranteed to become 0
C0/D70F    85    1C    --    STA    $1C    Store A into $1C
C0/D711    8A    --    --    TXA    --    Transfer X to A

C0/D712    0A    --    --    ASL    --    -=FIRST_LOOP_START=- Left-shift A
C0/D713    B0    04    --    BCS    ADD_ONCE    If the Carry bit is set (or, was the previous bit we lost due to left-shifting an 1?), continue into the subroutine
C0/D715    C6    1C    --    DEC    $1C    Decrement $1C, the number of left-shifts remaining.
C0/D717    80    F9    --    BRA    FIRST_LOOP_START    Go back to the start of the loop.

C0/D719    C6    1C    --    DEC    $1C    -=ADD_ONCE=- Decrement $1C.
C0/D71B    84    22    --    STY    $22    Store base GP value into $22, the multiplication factor is now 1.
C0/D71D    AA    --    --    TAX    --    Transfer A to X

C0/D71E    A5    1C    --    LDA    $1C    -=SECOND_LOOP_START=- Load remaining number of left-shifts into A, setting or clearing the Zero bit
C0/D720    F0    21    --    BEQ    END    If A = 0, skip to the end

###MULTIPLICATION BY 2##
C0/D722    A5    24    --    LDA    $24    Load bytes 4 and 3 (high bytes) into A
C0/D724    0A    --    --    ASL    --    Left-shift the high bytes
C0/D725    85    24    --    STA    $24    Store the high bytes back into $24
C0/D727    A5    22    --    LDA    $22    Load bytes 2 and 1 (low bytes) into A
C0/D729    0A    --    --    ASL    --    Left-shift the low bytes
C0/D72A    85    22    --    STA    $22    Store the low bytes back into $22
########################

C0/D72C    B0    16    --    BCS    INC_HIGH_BYTE1    If the Carry bit is set (or was the high bit of the low bytes before the left-shifting an 1?), increment the high bytes
C0/D72E    8A    --    --    TXA    --    -=LSHIFT=- Transfer X to A
C0/D72F    0A    --    --    ASL    --    Left-shift the multiplier, doubling the multiplication factor
C0/D730    B0    03    --    BCS    ADD_MULTIPLICAND    If the Carry bit is set, add the multiplicand, incrementing the multiplication factor by 1
C0/D732    AA    --    --    TAX    --    Transfer A back to X
C0/D733    80    0A    --    BRA    SECOND_LOOP_END    Skip the addition of the multiplicand

###ADDING THE MULTIPLICAND###
C0/D735    AA    --    --    TAX    --    -=ADD MULTIPLICAND=- Transfer A back to X
C0/D736    98    --    --    TYA    --    Transfer the base GP value to A
C0/D737    C2    01    --    REP    #$01    Clear the Carry bit
C0/D739    65    22    --    ADC    $22    Add to the base GP value the current GP value
C0/D73B    85    22    --    STA    $22    Store the sum back into $22
C0/D73D    B0    0A    --    BCS    INC_HIGH_BYTE2    If the Carry bit is set, increment the high bytes
#############################

C0/D73F    C6    1C    --    DEC    $1C    -=SECOND_LOOP_END=- Decrement the number of remaining left-shifts
C0/D741    80    DB    --    BRA    SECOND_LOOP_START    Go back to the start of the loop
C0/D743    60    --    --    RTS    --    -=END=-

C0/D744    20    4E    D7    JSR    INC_HIGH_BYTE_START    -=INC_HIGH_BYTE1=- Jump to the "Increment the high bytes subroutine"
C0/D747    80    E5    --    BRA    LSHIFT    Goes back to where we were in the code
C0/D749    20    4E    D7    JSR    INC_HIGH_BYTE_START    -=INC_HIGH_BYTE2=- Jump to the "Increment the high bytes subroutine"
C0/D74C    80    F1    --    BRA    SECOND_LOOP_END    Goes back to where we were in the code
C0/D74E    E6    25    --    INC    $25    -=INC_HIGH_BYTE_START=- Increment byte 3
C0/D750    90    02    --    BCC    INC_HIGH_BYTE_END    If the Carry bit is clear, skip the next operation
C0/D752    E6    24    --    INC    $24    Increment byte 4
C0/D754    60    --    --    RTS    --    -=INC_HIGH_BYTE_END=-

I still don't understand why DEC $1C seems to be a 16-bit operation but INC $24 is only a 8-bit one. Does JSR has something to do with it? Feel free to propose code optimization. This is the result of moving pieces of code back and forth, hence the messy look. Also, is it possible to insert tables? I spend so much time trying to get the formatting right, but the tabulation in the editor doesn't act the same once it's published! Tongue
  Find
Quote  

#7
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
C0/D722:

for accuracy: switch the order of the $22 and $24 operations, so you can then rotate the Carry from the low bytes into the high.
for speed and size: also, there's no need to load them into A.

Code:
ASL $22
ROL $24

----

C0/D737: unless you plan to alter other CPU flags here, "CLC" is smaller.

----

for a similar but smaller way to do your non-special-register multiplication, look at C1/1867 and C1/18F4.  here's the former since Slick is down for an eternity now:
Code:
(Multiply 16-bit $2E by 8-bit $2C, and put result in 32-bit $30-$33.
Wow, this and C1/18F4 are total clones.)

C1/1867: 64 30        STZ $30
C1/1869: 64 32        STZ $32
C1/186B: 64 34        STZ $34
C1/186D: A2 08 00     LDX #$0008
C1/1870: 46 2C        LSR $2C
C1/1872: 90 0D        BCC $1881
C1/1874: A5 30        LDA $30
C1/1876: 18           CLC
C1/1877: 65 2E        ADC $2E
C1/1879: 85 30        STA $30
C1/187B: A5 32        LDA $32
C1/187D: 65 34        ADC $34
C1/187F: 85 32        STA $32
C1/1881: 06 2E        ASL $2E
C1/1883: 26 34        ROL $34
C1/1885: CA           DEX
C1/1886: D0 E8        BNE $1870
C1/1888: 60           RTS
Quote  
[-] The following 1 user says Thank You to assassin for this post:
  • NPCnextdoor (07-15-2018)

#8
Posts: 48
Threads: 7
Thanks Received: 0
Thanks Given: 0
Joined: Dec 2017
Reputation: 0
Status
None
Thanks!

I made the modifications. However, with

ASL $22
ROL $24
(And also removing the BCS INC_HIGH_BYTE1 operation)

It didn't work. The result was way too low. So I thought that perhaps ROL was 8-bit some how, even though the M flag is 0 (16-bit mode). So, I did

ASL $22
ROL $25
ROL $24

And the result was way too high. However,

ASL $22
ROL $25

Works perfectly! And I don't understand why. Sad

I did try first this approach (well, when I learnt eventually about the ROL opcode Tongue ) and because of this weird behavior, I took another route that I knew would work until I understood it better. I concluded that if some operations directly on the data were somehow always 8-bit, I would prefer to put the values in the A, X or Y registers first and put the results back.
  Find
Quote  

#9
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
Quote:I still don't understand why DEC $1C seems to be a 16-bit operation but INC $24 is only a 8-bit one. Does JSR has something to do with it?

what makes you think this?  i never see your code change register size mid-function.  so i think it's all 16-bits.

------

re C0/D744:

1) branching to this, and then returning by branching out to different places depending on the caller, is a mess.  let's get rid of that.  so instead of "BCS function", then a horrible mess inside "function", we'd do "BCC no_call_function /  JSR function / no_call_function:".  and inside of "function", we'd always return with an "RTS"; no spaghetti code branching.

2) you're in 16-bit mode, so there's no reason to increment both $24 and $25.  just do $24.  and chuck the "BCC", because the callers already limited when we'd reach here by usage of "BCS".

3) there's no point for a function anymore, anyway.  just do the necessary incrementing inline.  at C0/D737, have this:

Code:
CLC
ADC    $22    ; Add to the base GP value the current GP value
STA    $22    ; Store the sum back into $22
BCC    no_overflow
INC    $24    ; propagate any carry into the top word
no_overflow:
DEC    $1C
[...]

this change #3 supersedes the suggestions in #1 and #2.  but i kept those anyway so you can see the motivation and process for refining or fixing code.
Quote  
[-] The following 1 user says Thank You to assassin for this post:
  • NPCnextdoor (07-18-2018)

#10
Posts: 48
Threads: 7
Thanks Received: 0
Thanks Given: 0
Joined: Dec 2017
Reputation: 0
Status
None
(07-15-2018, 05:44 PM)assassin Wrote:
Quote:I still don't understand why DEC $1C seems to be a 16-bit operation but INC $24 is only a 8-bit one. Does JSR has something to do with it?

what makes you think this?  i never see your code change register size mid-function.  so i think it's all 16-bits.

Nevermind, I messed up the endianness in my head so I uselessly added operations to put $25 into $24 later in the code... and I just forgot about it a week later! Hmm

I applied your suggestions. They work perfectly and the code is now 24 bytes smaller!  Victory
  Find
Quote  



Forum Jump:

Users browsing this thread: 1 Guest(s)


Theme by Madsiur2017Custom Graphics by JamesWhite