From 1c4303cce6918d235bbb7235731392ca31dba22c Mon Sep 17 00:00:00 2001 From: el-u <109771707+el-u@users.noreply.github.com> Date: Sat, 21 Oct 2023 23:27:30 +0200 Subject: [PATCH] lufia2ac: add shops to the cave (#2103) This PR adds a new, optional aspect to the Ancient Cave experience: During their run, players can have the opportunity to purchase some additional items or spells to improve their party. If enabled, a shop will appear everytime a certain (configurable) number of floors in the dungeon has been completed. The shop inventories are generated randomly (taking into account player preference as well as a system to ensure that more expensive items can only become available deeper into the run). For customization, 3 new options are introduced: - `shop_interval`: Determines by how many floors the shops are separated (or keeps them turned off entirely) - `shop_inventory`: Determines what's possible to be for sale. (Players can specify weights for general categories of things such as "weapon" or "spell" or even adjust the probabilities of individual items) - `gold_modifier`: Determines how much gold is dropped by enemies. This is the player's only source of income and thus controls how much money they will have available to spend in shops --- worlds/lufia2ac/Items.py | 5 +- worlds/lufia2ac/Options.py | 128 +++++++++++++++++- worlds/lufia2ac/Utils.py | 11 +- worlds/lufia2ac/__init__.py | 93 ++++++++++++- worlds/lufia2ac/basepatch/basepatch.asm | 119 ++++++++++++++++ worlds/lufia2ac/basepatch/basepatch.bsdiff4 | Bin 8365 -> 8555 bytes .../lufia2ac/docs/en_Lufia II Ancient Cave.md | 3 +- 7 files changed, 349 insertions(+), 10 deletions(-) diff --git a/worlds/lufia2ac/Items.py b/worlds/lufia2ac/Items.py index 20159f48..190b913c 100644 --- a/worlds/lufia2ac/Items.py +++ b/worlds/lufia2ac/Items.py @@ -2,9 +2,8 @@ from enum import auto, Enum from typing import Dict, NamedTuple, Optional from BaseClasses import Item, ItemClassification -from . import Locations -start_id: int = Locations.start_id +start_id: int = 0xAC0000 class ItemType(Enum): @@ -500,7 +499,7 @@ l2ac_item_table: Dict[str, ItemData] = { # 0x01C8: "Key28" # 0x01C9: "Key29" # 0x01CA: "AP item" # replaces "Key30" - # 0x01CB: "Crown" + # 0x01CB: "SOLD OUT" # replaces "Crown" # 0x01CC: "Ruby apple" # 0x01CD: "PURIFIA" # 0x01CE: "Tag ring" diff --git a/worlds/lufia2ac/Options.py b/worlds/lufia2ac/Options.py index 3f1c58f9..783da8e4 100644 --- a/worlds/lufia2ac/Options.py +++ b/worlds/lufia2ac/Options.py @@ -1,13 +1,16 @@ from __future__ import annotations +import functools +import numbers import random from dataclasses import dataclass from itertools import accumulate, chain, combinations from typing import Any, cast, Dict, Iterator, List, Mapping, Optional, Set, Tuple, Type, TYPE_CHECKING, Union -from Options import AssembleOptions, Choice, DeathLink, ItemDict, PerGameCommonOptions, Range, SpecialRange, \ - TextChoice, Toggle +from Options import AssembleOptions, Choice, DeathLink, ItemDict, OptionDict, PerGameCommonOptions, Range, \ + SpecialRange, TextChoice, Toggle from .Enemies import enemy_name_to_sprite +from .Items import ItemType, l2ac_item_table if TYPE_CHECKING: from BaseClasses import PlandoOptions @@ -558,6 +561,25 @@ class Goal(Choice): default = option_boss +class GoldModifier(Range): + """Percentage modifier for gold gained from enemies. + + Supported values: 25 – 400 + Default value: 100 (same as in an unmodified game) + """ + + display_name = "Gold modifier" + range_start = 25 + range_end = 400 + default = 100 + + def __call__(self, gold: bytes) -> bytes: + try: + return (int.from_bytes(gold, "little") * self.value // 100).to_bytes(2, "little") + except OverflowError: + return b"\xFF\xFF" + + class HealingFloorChance(Range): """The chance of a floor having a healing tile hidden under a bush. @@ -662,6 +684,105 @@ class RunSpeed(Choice): default = option_disabled +class ShopInterval(SpecialRange): + """Place shops after a certain number of floors. + + E.g., if you set this to 5, then you will be given the opportunity to shop after completing B5, B10, B15, etc., + whereas if you set it to 1, then there will be a shop after every single completed floor. + Shops will offer a random selection of wares; on deeper floors, more expensive items might appear. + You can customize the stock that can appear in shops using the shop_inventory option. + You can control how much gold you will be obtaining from enemies using the gold_multiplier option. + Supported values: disabled, 1 – 10 + Default value: disabled (same as in an unmodified game) + """ + + display_name = "Shop interval" + range_start = 0 + range_end = 10 + default = 0 + special_range_cutoff = 1 + special_range_names = { + "disabled": 0, + } + + +class ShopInventory(OptionDict): + """Determine the item types that can appear in shops. + + The value of this option should be a mapping of item categories (or individual items) to weights (non-negative + integers), which are used as relative probabilities when it comes to including these things in shops. (The actual + contents of the generated shops are selected randomly and are subject to additional constraints such as more + expensive things being allowed only on later floors.) + Supported keys: + non_restorative — a selection of mostly non-restorative red chest consumables + restorative — all HP- or MP-restoring red chest consumables + blue_chest — all blue chest items + spell — all red chest spells + gear — all red chest armors, shields, headgear, rings, and rocks (this respects the gear_variety_after_b9 option, + meaning that you will not encounter any shields, headgear, rings, or rocks in shops from B10 onward unless you + also enabled that option) + weapon — all red chest weapons + Additionally, you can also add extra weights for any specific cave item. If you want your shops to have a + higher than normal chance of selling a Dekar blade, you can, e.g., add "Dekar blade: 5". + You can even forego the predefined categories entirely and design a custom shop pool from scratch by providing + separate weights for each item you want to include. + (Spells, however, cannot be weighted individually and are only available as part of the "spell" category.) + Default value: {spell: 30, gear: 45, weapon: 82} + """ + + display_name = "Shop inventory" + _special_keys = {"non_restorative", "restorative", "blue_chest", "spell", "gear", "weapon"} + valid_keys = _special_keys | {item for item, data in l2ac_item_table.items() + if data.type in {ItemType.BLUE_CHEST, ItemType.ENEMY_DROP, ItemType.ENTRANCE_CHEST, + ItemType.RED_CHEST, ItemType.RED_CHEST_PATCH}} + default: Dict[str, int] = { + "spell": 30, + "gear": 45, + "weapon": 82, + } + value: Dict[str, int] + + def verify(self, world: Type[World], player_name: str, plando_options: PlandoOptions) -> None: + super().verify(world, player_name, plando_options) + for item, weight in self.value.items(): + if not isinstance(weight, numbers.Integral) or weight < 0: + raise Exception(f"Weight for item \"{item}\" from option {self} must be a non-negative integer, " + f"but was \"{weight}\".") + + @property + def total(self) -> int: + return sum(self.value.values()) + + @property + def non_restorative(self) -> int: + return self.value.get("non_restorative", 0) + + @property + def restorative(self) -> int: + return self.value.get("restorative", 0) + + @property + def blue_chest(self) -> int: + return self.value.get("blue_chest", 0) + + @property + def spell(self) -> int: + return self.value.get("spell", 0) + + @property + def gear(self) -> int: + return self.value.get("gear", 0) + + @property + def weapon(self) -> int: + return self.value.get("weapon", 0) + + @functools.cached_property + def custom(self) -> Dict[int, int]: + return {l2ac_item_table[item].code & 0x01FF: weight for item, weight in self.value.items() + if item not in self._special_keys} + + class ShuffleCapsuleMonsters(Toggle): """Shuffle the capsule monsters into the multiworld. @@ -717,6 +838,7 @@ class L2ACOptions(PerGameCommonOptions): final_floor: FinalFloor gear_variety_after_b9: GearVarietyAfterB9 goal: Goal + gold_modifier: GoldModifier healing_floor_chance: HealingFloorChance initial_floor: InitialFloor iris_floor_chance: IrisFloorChance @@ -724,5 +846,7 @@ class L2ACOptions(PerGameCommonOptions): master_hp: MasterHp party_starting_level: PartyStartingLevel run_speed: RunSpeed + shop_interval: ShopInterval + shop_inventory: ShopInventory shuffle_capsule_monsters: ShuffleCapsuleMonsters shuffle_party_members: ShufflePartyMembers diff --git a/worlds/lufia2ac/Utils.py b/worlds/lufia2ac/Utils.py index 6c2e28d1..1fd7e0e1 100644 --- a/worlds/lufia2ac/Utils.py +++ b/worlds/lufia2ac/Utils.py @@ -1,5 +1,7 @@ +import itertools +from operator import itemgetter from random import Random -from typing import Dict, List, MutableSequence, Sequence, Set, Tuple +from typing import Dict, Iterable, List, MutableSequence, Sequence, Set, Tuple def constrained_choices(population: Sequence[int], d: int, *, k: int, random: Random) -> List[int]: @@ -19,3 +21,10 @@ def constrained_shuffle(x: MutableSequence[int], d: int, random: Random) -> None i, j = random.randrange(n), random.randrange(n) if x[i] in constraints[j] and x[j] in constraints[i]: x[i], x[j] = x[j], x[i] + + +def weighted_sample(population: Iterable[int], weights: Iterable[float], k: int, *, random: Random) -> List[int]: + population, keys = zip(*((item, pow(random.random(), 1 / group_weight)) + for item, group in itertools.groupby(sorted(zip(population, weights)), key=itemgetter(0)) + if (group_weight := sum(weight for _, weight in group)))) + return sorted(population, key=dict(zip(population, keys)).__getitem__)[-k:] diff --git a/worlds/lufia2ac/__init__.py b/worlds/lufia2ac/__init__.py index fad7109a..acb988da 100644 --- a/worlds/lufia2ac/__init__.py +++ b/worlds/lufia2ac/__init__.py @@ -14,9 +14,9 @@ from .Client import L2ACSNIClient # noqa: F401 from .Items import ItemData, ItemType, l2ac_item_name_to_id, l2ac_item_table, L2ACItem, start_id as items_start_id from .Locations import l2ac_location_name_to_id, L2ACLocation from .Options import CapsuleStartingLevel, DefaultParty, EnemyFloorNumbers, EnemyMovementPatterns, EnemySprites, \ - ExpModifier, Goal, L2ACOptions + Goal, L2ACOptions from .Rom import get_base_rom_bytes, get_base_rom_path, L2ACDeltaPatch -from .Utils import constrained_choices, constrained_shuffle +from .Utils import constrained_choices, constrained_shuffle, weighted_sample from .basepatch import apply_basepatch CHESTS_PER_SPHERE: int = 5 @@ -222,6 +222,7 @@ class L2ACWorld(World): rom_bytearray[0x09D59B:0x09D59B + 256] = self.get_node_connection_table() rom_bytearray[0x0B05C0:0x0B05C0 + 18843] = self.get_enemy_stats() rom_bytearray[0x0B4F02:0x0B4F02 + 2] = self.o.master_hp.value.to_bytes(2, "little") + rom_bytearray[0x0BEE9F:0x0BEE9F + 1948] = self.get_shops() rom_bytearray[0x280010:0x280010 + 2] = self.o.blue_chest_count.value.to_bytes(2, "little") rom_bytearray[0x280012:0x280012 + 3] = self.o.capsule_starting_level.xp.to_bytes(3, "little") rom_bytearray[0x280015:0x280015 + 1] = self.o.initial_floor.value.to_bytes(1, "little") @@ -229,6 +230,7 @@ class L2ACWorld(World): rom_bytearray[0x280017:0x280017 + 1] = self.o.iris_treasures_required.value.to_bytes(1, "little") 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[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() @@ -357,7 +359,7 @@ class L2ACWorld(World): def get_enemy_stats(self) -> bytes: rom: bytes = get_base_rom_bytes() - if self.o.exp_modifier == ExpModifier.default: + if self.o.exp_modifier == 100 and self.o.gold_modifier == 100: return rom[0x0B05C0:0x0B05C0 + 18843] number_of_enemies: int = 224 @@ -366,6 +368,7 @@ class L2ACWorld(World): for enemy_id in range(number_of_enemies): pointer: int = int.from_bytes(enemy_stats[2 * enemy_id:2 * enemy_id + 2], "little") enemy_stats[pointer + 29:pointer + 31] = self.o.exp_modifier(enemy_stats[pointer + 29:pointer + 31]) + enemy_stats[pointer + 31:pointer + 33] = self.o.gold_modifier(enemy_stats[pointer + 31:pointer + 33]) return enemy_stats def get_goal_text_bytes(self) -> bytes: @@ -383,6 +386,90 @@ class L2ACWorld(World): goal_text_bytes = bytes((0x08, *b"\x03".join(line.encode("ascii") for line in goal_text), 0x00)) return goal_text_bytes + b"\x00" * (147 - len(goal_text_bytes)) + def get_shops(self) -> bytes: + rom: bytes = get_base_rom_bytes() + + if not self.o.shop_interval: + return rom[0x0BEE9F:0x0BEE9F + 1948] + + non_restorative_ids = {int.from_bytes(rom[0x0A713D + 2 * i:0x0A713D + 2 * i + 2], "little") for i in range(31)} + restorative_ids = {int.from_bytes(rom[0x08FFDC + 2 * i:0x08FFDC + 2 * i + 2], "little") for i in range(9)} + blue_ids = {int.from_bytes(rom[0x0A6EA0 + 2 * i:0x0A6EA0 + 2 * i + 2], "little") for i in range(41)} + number_of_spells: int = 35 + number_of_items: int = 467 + spells_offset: int = 0x0AFA5B + items_offset: int = 0x0B4F69 + non_restorative_list: List[List[int]] = [list() for _ in range(99)] + restorative_list: List[List[int]] = [list() for _ in range(99)] + blue_list: List[List[int]] = [list() for _ in range(99)] + spell_list: List[List[int]] = [list() for _ in range(99)] + gear_list: List[List[int]] = [list() for _ in range(99)] + weapon_list: List[List[int]] = [list() for _ in range(99)] + custom_list: List[List[int]] = [list() for _ in range(99)] + + for spell_id in range(number_of_spells): + pointer: int = int.from_bytes(rom[spells_offset + 2 * spell_id:spells_offset + 2 * spell_id + 2], "little") + value: int = int.from_bytes(rom[spells_offset + pointer + 15:spells_offset + pointer + 17], "little") + for f in range(value // 1000, 99): + spell_list[f].append(spell_id) + for item_id in range(number_of_items): + pointer = int.from_bytes(rom[items_offset + 2 * item_id:items_offset + 2 * item_id + 2], "little") + buckets: List[List[List[int]]] = list() + if item_id in non_restorative_ids: + buckets.append(non_restorative_list) + if item_id in restorative_ids: + buckets.append(restorative_list) + if item_id in blue_ids: + buckets.append(blue_list) + if not rom[items_offset + pointer] & 0x20 and not rom[items_offset + pointer + 1] & 0x20: + category: int = rom[items_offset + pointer + 7] + if category >= 0x02: + buckets.append(gear_list) + elif category == 0x01: + buckets.append(weapon_list) + if item_id in self.o.shop_inventory.custom: + buckets.append(custom_list) + value = int.from_bytes(rom[items_offset + pointer + 5:items_offset + pointer + 7], "little") + for bucket in buckets: + for f in range(value // 1000, 99): + bucket[f].append(item_id) + + if not self.o.gear_variety_after_b9: + for f in range(99): + del gear_list[f][len(gear_list[f]) % 128:] + + def create_shop(floor: int) -> Tuple[int, ...]: + if self.random.randrange(self.o.shop_inventory.total) < self.o.shop_inventory.spell: + return create_spell_shop(floor) + else: + return create_item_shop(floor) + + def create_spell_shop(floor: int) -> Tuple[int, ...]: + spells = self.random.sample(spell_list[floor], 3) + return 0x03, 0x20, 0x00, *spells, 0xFF + + def create_item_shop(floor: int) -> Tuple[int, ...]: + population = non_restorative_list[floor] + restorative_list[floor] + blue_list[floor] \ + + gear_list[floor] + weapon_list[floor] + custom_list[floor] + weights = itertools.chain(*([weight / len_] * len_ if (len_ := len(list_)) else [] for weight, list_ in + [(self.o.shop_inventory.non_restorative, non_restorative_list[floor]), + (self.o.shop_inventory.restorative, restorative_list[floor]), + (self.o.shop_inventory.blue_chest, blue_list[floor]), + (self.o.shop_inventory.gear, gear_list[floor]), + (self.o.shop_inventory.weapon, weapon_list[floor])]), + (self.o.shop_inventory.custom[item] for item in custom_list[floor])) + items = weighted_sample(population, weights, 5, random=self.random) + return 0x01, 0x04, 0x00, *(b for item in items for b in item.to_bytes(2, "little")), 0x00, 0x00 + + shops = [create_shop(floor) + for floor in range(self.o.shop_interval, 99, self.o.shop_interval) + for _ in range(self.o.shop_interval)] + shop_pointers = itertools.accumulate((len(shop) for shop in shops[:-1]), initial=2 * len(shops)) + shop_bytes = bytes(itertools.chain(*(p.to_bytes(2, "little") for p in shop_pointers), *shops)) + + assert len(shop_bytes) <= 1948, shop_bytes + return shop_bytes.ljust(1948, b"\x00") + @staticmethod def get_node_connection_table() -> bytes: class Connect(IntFlag): diff --git a/worlds/lufia2ac/basepatch/basepatch.asm b/worlds/lufia2ac/basepatch/basepatch.asm index aeae6846..923ee6a2 100644 --- a/worlds/lufia2ac/basepatch/basepatch.asm +++ b/worlds/lufia2ac/basepatch/basepatch.asm @@ -71,6 +71,11 @@ org $9EDD60 ; name org $9FA900 ; sprite incbin "ap_logo/ap_logo.bin" warnpc $9FA980 +; sold out item +org $96F9BA ; properties + DB $00,$00,$00,$10,$00,$00,$00,$00,$00,$00,$00,$00,$00 +org $9EDD6C ; name + DB "SOLD OUT " ; overwrites "Crown " org $D08000 ; signature, start of expanded data area @@ -825,6 +830,119 @@ SpellRNG: +; shops +pushpc +org $83B442 + ; DB=$83, x=1, m=1 + JSL Shop ; overwrites STA $7FD0BF +pullpc + +Shop: + STA $7FD0BF ; (overwritten instruction) + LDY $05AC ; load map number + CPY.b #$F0 ; check if ancient cave + BCC + + LDA $05B4 ; check if going to ancient cave entrance + BEQ + + LDA $7FE696 ; load next to next floor number + DEC + CPY.b #$F1 ; check if going to final floor + BCS ++ ; skip a decrement because next floor number is not incremented on final floor + DEC +++: CMP $D08015 ; check if past initial floor + BCC + + STA $4204 ; WRDIVL; dividend = floor number + STZ $4205 ; WRDIVH + TAX + LDA $D0801A + STA $4206 ; WRDIVB; divisor = shop_interval + STA $211C ; M7B; second factor = shop_interval + JSL $8082C7 ; advance RNG (while waiting for division to complete) + LDY $4216 ; RDMPYL; skip if remainder (i.e., floor number mod shop_interval) is not 0 + BNE + + STA $211B + STZ $211B ; M7A; first factor = random number from 0 to 255 + TXA + CLC + SBC $2135 ; MPYM; calculate (floor number) - (random number from 0 to shop_interval-1) - 1 + STA $30 ; set shop id + STZ $05A8 ; initialize variable for sold out item tracking + STZ $05A9 + PHB + PHP + JML $80A33A ; open shop menu ++: RTL + +; shop item select +pushpc +org $82DF50 + ; DB=$83, x=0, m=1 + JML ShopItemSelected ; overwrites JSR $8B08 : CMP.b #$01 +pullpc + +ShopItemSelected: + LDA $1548 ; check inventory free space + BEQ + + JSR LoadShopSlotAsFlag + BIT $05A8 ; test item not already sold + BNE + + JML $82DF79 ; skip quantity selection and go directly to buy/equip ++: JML $82DF80 ; abort and go back to item selection + +; track bought shop items +pushpc +org $82E084 + ; DB=$83, x=0, m=1 + JSL ShopBuy ; overwrites LDA.b #$05 : LDX.w #$0007 + NOP +org $82E10E + ; DB=$83, x=0, m=1 + JSL ShopEquip ; overwrites SEP #$10 : LDX $14DC + NOP +pullpc + +ShopBuy: + JSR LoadShopSlotAsFlag + TSB $05A8 ; mark item as sold + LDA.b #$05 ; (overwritten instruction) + LDX.w #$0007 ; (overwritten instruction) + RTL + +ShopEquip: + JSR LoadShopSlotAsFlag + TSB $05A8 ; mark item as sold + SEP #$10 ; (overwritten instruction) + LDX $14DC ; (overwritten instruction) + RTL + +LoadShopSlotAsFlag: + TDC + LDA $14EC ; load currently selected shop slot number + ASL + TAX + LDA $8ED8C3,X ; load predefined bitmask with a single bit set + RTS + +; mark bought items as sold out +pushpc +org $8285EA + ; DB=$83, x=0, m=0 + JSL SoldOut ; overwrites LDA [$FC],Y : AND #$01FF + NOP +pullpc + +SoldOut: + LDA $8ED8C3,X ; load predefined bitmask with a single bit set + BIT $05A8 ; test sold items + BEQ + + LDA.w #$01CB ; load sold out item id + BRA ++ ++: LDA [$FC],Y ; (overwritten instruction) + AND #$01FF ; (overwritten instruction) +++: RTL + + + ; increase variety of red chest gear after B9 pushpc org $839176 @@ -1054,6 +1172,7 @@ pullpc ; $F02017 1 iris treasures required ; $F02018 1 party members available ; $F02019 1 capsule monsters available +; $F0201A 1 shop interval ; $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 51478e5d525636d62f8a4d5755a2c1d36905fb2a..aee1c7125ddac1ca5ccd03a8caf5fa563f2ccee9 100644 GIT binary patch literal 8555 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 literal 8365 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 diff --git a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md index 64658a7d..849a9f9c 100644 --- a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md +++ b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md @@ -49,8 +49,9 @@ Your Party Leader will hold up the item they received when not in a fight or in - Customize the multiworld item pool. (By default, your pool is filled with random blue chest items, but you can place any cave item you want instead) - Customize start inventory, i.e., begin every run with certain items or spells of your choice -- Adjust how much EXP is gained from enemies +- Adjust how much EXP and gold is gained from enemies - 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