diff --git a/worlds/lufia2ac/Options.py b/worlds/lufia2ac/Options.py index 5f33d0bd..1b3a39dd 100644 --- a/worlds/lufia2ac/Options.py +++ b/worlds/lufia2ac/Options.py @@ -593,6 +593,20 @@ class HealingFloorChance(Range): default = 16 +class InactiveExpGain(Choice): + """The rate at which characters not currently in the active party gain EXP. + + Supported values: disabled, half, full + Default value: disabled (same as in an unmodified game) + """ + + display_name = "Inactive character EXP gain" + option_disabled = 0 + option_half = 50 + option_full = 100 + default = option_disabled + + class InitialFloor(Range): """The initial floor, where you begin your journey. @@ -805,7 +819,7 @@ class ShufflePartyMembers(Toggle): false — all 6 optional party members are present in the cafe and can be recruited right away true — only Maxim is available from the start; 6 new "items" are added to your pool and shuffled into the multiworld; when one of these items is found, the corresponding party member is unlocked for you to use. - While cave diving, you can add newly unlocked ones to your party by using the character items from the inventory + While cave diving, you can add or remove unlocked party members by using the character items from the inventory Default value: false (same as in an unmodified game) """ @@ -838,6 +852,7 @@ class L2ACOptions(PerGameCommonOptions): goal: Goal gold_modifier: GoldModifier healing_floor_chance: HealingFloorChance + inactive_exp_gain: InactiveExpGain initial_floor: InitialFloor iris_floor_chance: IrisFloorChance iris_treasures_required: IrisTreasuresRequired diff --git a/worlds/lufia2ac/__init__.py b/worlds/lufia2ac/__init__.py index 9bd436fa..561429c8 100644 --- a/worlds/lufia2ac/__init__.py +++ b/worlds/lufia2ac/__init__.py @@ -232,6 +232,7 @@ class L2ACWorld(World): rom_bytearray[0x280018:0x280018 + 1] = self.o.shuffle_party_members.unlock.to_bytes(1, "little") rom_bytearray[0x280019:0x280019 + 1] = self.o.shuffle_capsule_monsters.unlock.to_bytes(1, "little") rom_bytearray[0x28001A:0x28001A + 1] = self.o.shop_interval.value.to_bytes(1, "little") + rom_bytearray[0x28001B:0x28001B + 1] = self.o.inactive_exp_gain.value.to_bytes(1, "little") rom_bytearray[0x280030:0x280030 + 1] = self.o.goal.value.to_bytes(1, "little") rom_bytearray[0x28003D:0x28003D + 1] = self.o.death_link.value.to_bytes(1, "little") rom_bytearray[0x281200:0x281200 + 470] = self.get_capsule_cravings_table() diff --git a/worlds/lufia2ac/basepatch/basepatch.asm b/worlds/lufia2ac/basepatch/basepatch.asm index f9c48a5f..f25d4dea 100644 --- a/worlds/lufia2ac/basepatch/basepatch.asm +++ b/worlds/lufia2ac/basepatch/basepatch.asm @@ -309,6 +309,12 @@ org $8EFD2E ; unused region at the end of bank $8E DB $1E,$0B,$01,$2B,$05,$1A,$05,$00 ; add dekar DB $1E,$0B,$01,$2B,$04,$1A,$06,$00 ; add tia DB $1E,$0B,$01,$2B,$06,$1A,$07,$00 ; add lexis + DB $1F,$0B,$01,$2C,$01,$1B,$02,$00 ; remove selan + DB $1F,$0B,$01,$2C,$02,$1B,$03,$00 ; remove guy + DB $1F,$0B,$01,$2C,$03,$1B,$04,$00 ; remove arty + DB $1F,$0B,$01,$2C,$05,$1B,$05,$00 ; remove dekar + DB $1F,$0B,$01,$2C,$04,$1B,$06,$00 ; remove tia + DB $1F,$0B,$01,$2C,$06,$1B,$07,$00 ; remove lexis pullpc SpecialItemUse: @@ -328,11 +334,15 @@ SpecialItemUse: SEP #$20 LDA $8ED8C7,X ; load predefined bitmask with a single bit set BIT $077E ; check against EV flags $02 to $07 (party member flags) - BNE + ; abort if character already present - LDA $07A9 ; load EV register $11 (party counter) + BEQ ++ + LDA.b #$30 ; character already present; modify pointer to point to L2SASM leave script + ADC $09B7 + STA $09B7 + BRA +++ +++: LDA $07A9 ; character not present; load EV register $0B (party counter) CMP.b #$03 BPL + ; abort if party full - LDA.b #$8E ++++ LDA.b #$8E STA $09B9 PHK PEA ++ @@ -340,7 +350,6 @@ SpecialItemUse: JML $83BB76 ; initialize parser variables ++: NOP JSL $809CB8 ; call L2SASM parser - JSL $81F034 ; consume the item TSX INX #13 TXS @@ -490,6 +499,73 @@ pullpc +; allow inactive characters to gain exp +pushpc +org $81DADD + ; DB=$81, x=0, m=1 + NOP ; overwrites BNE $81DAE2 : JMP $DBED + JML HandleActiveExp +AwardExp: + ; isolate exp distribution into a subroutine, to be reused for both active party members and inactive characters +org $81DAE9 + NOP #2 ; overwrites JMP $DBBD + RTL +org $81DB42 + NOP #2 ; overwrites JMP $DBBD + RTL +org $81DD11 + ; DB=$81, x=0, m=1 + JSL HandleInactiveExp ; overwrites LDA $0A8A : CLC +pullpc + +HandleActiveExp: + BNE + ; (overwritten instruction; modified) check if statblock not empty + JML $81DBED ; (overwritten instruction; modified) abort ++: JSL AwardExp ; award exp (X=statblock pointer, Y=position in battle order, $00=position in menu order) + JML $81DBBD ; (overwritten instruction; modified) continue to next level text + +HandleInactiveExp: + LDA $F0201B ; load inactive exp gain rate + BEQ + ; zero gain; skip everything + CMP.b #$64 + BCS ++ ; full gain + LSR $1607 + ROR $1606 ; half gain + ROR $1605 +++: LDY.w #$0000 ; start looping through all characters +-: TDC + TYA + LDX.w #$0003 ; start looping through active party +--: CMP $0A7B,X + BEQ ++ ; skip if character in active party + DEX + BPL -- ; continue looping through active party + STA $153D ; inactive character detected; overwrite character index of 1st slot in party battle order + ASL + TAX + REP #$20 + LDA $859EBA,X ; convert character index to statblock pointer + SEP #$20 + TAX + PHY ; stash character loop index + LDY $0A80 + PHY ; stash 1st (in menu order) party member statblock pointer + STX $0A80 ; overwrite 1st (in menu order) party member statblock pointer + LDY.w #$0000 ; set to use 1st position (in battle order) + STY $00 ; set to use 1st position (in menu order) + JSL AwardExp ; award exp (X=statblock pointer, Y=position in battle order, $00=position in menu order) + PLY ; restore 1st (in menu order) party member statblock pointer + STY $0A80 + PLY ; restore character loop index +++: INY + CPY.w #$0007 + BCC - ; continue looping through all characters ++: LDA $0A8A ; (overwritten instruction) load current gold + CLC ; (overwritten instruction) + RTL + + + ; receive death link pushpc org $83BC91 @@ -1226,6 +1302,7 @@ pullpc ; $F02018 1 party members available ; $F02019 1 capsule monsters available ; $F0201A 1 shop interval +; $F0201B 1 inactive exp gain rate ; $F02030 1 selected goal ; $F02031 1 goal completion: boss ; $F02032 1 goal completion: iris_treasure_hunt diff --git a/worlds/lufia2ac/basepatch/basepatch.bsdiff4 b/worlds/lufia2ac/basepatch/basepatch.bsdiff4 index 664e197c..1dfade44 100644 Binary files a/worlds/lufia2ac/basepatch/basepatch.bsdiff4 and b/worlds/lufia2ac/basepatch/basepatch.bsdiff4 differ diff --git a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md index 1080a77d..4b5bf3f3 100644 --- a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md +++ b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md @@ -53,8 +53,9 @@ Your Party Leader will hold up the item they received when not in a fight or in - Randomize enemy movement patterns, enemy sprites, and which enemy types can appear at which floor numbers - Option to make shops appear in the cave so that you have a way to spend your hard-earned gold - Option to shuffle your party members and/or capsule monsters into the multiworld, meaning that someone will have to - find them in order to unlock them for you to use. While cave diving, you can add newly unlocked members to your party - by using the character items from your inventory + find them in order to unlock them for you to use. While cave diving, you can add or remove unlocked party members by + using the character items from your inventory. There's also an option to allow inactive characters to gain some EXP, + so that new party members added during a run don't have to start off at a low level ###### Quality of life: