diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py index 38a3e47809..b4d14555e3 100644 --- a/worlds/kdl3/__init__.py +++ b/worlds/kdl3/__init__.py @@ -85,6 +85,12 @@ class KDL3World(World): create_regions = create_levels + def generate_early(self) -> None: + if self.options.total_heart_stars != -1: + logger.warning(f"Kirby's Dream Land 3 ({self.player_name}): Use of \"total_heart_stars\" is deprecated. " + f"Please use \"max_heart_stars\" instead.") + self.options.max_heart_stars.value = self.options.total_heart_stars.value + def create_item(self, name: str, force_non_progression: bool = False) -> KDL3Item: item = item_table[name] classification = ItemClassification.filler @@ -245,21 +251,20 @@ class KDL3World(World): remaining_items = len(location_table) - len(itempool) if not self.options.consumables: remaining_items -= len(consumable_locations) - remaining_items -= len(star_locations) - if self.options.starsanity: - # star fill, keep consumable pool locked to consumable and fill 767 stars specifically - star_items = list(star_item_weights.keys()) - star_weights = list(star_item_weights.values()) - itempool.extend([self.create_item(item) for item in self.random.choices(star_items, weights=star_weights, - k=767)]) - total_heart_stars = self.options.total_heart_stars + if not self.options.starsanity: + remaining_items -= len(star_locations) + max_heart_stars = self.options.max_heart_stars.value + if max_heart_stars > remaining_items: + max_heart_stars = remaining_items # ensure at least 1 heart star required per world - required_heart_stars = max(int(total_heart_stars * required_percentage), 5) - filler_items = total_heart_stars - required_heart_stars - filler_amount = math.floor(filler_items * (self.options.filler_percentage / 100.0)) - trap_amount = math.floor(filler_amount * (self.options.trap_percentage / 100.0)) - filler_amount -= trap_amount - non_required_heart_stars = filler_items - filler_amount - trap_amount + required_heart_stars = min(max(int(max_heart_stars * required_percentage), 5), 99) + filler_items = remaining_items - required_heart_stars + converted_heart_stars = math.floor((max_heart_stars - required_heart_stars) * (self.options.filler_percentage / 100.0)) + non_required_heart_stars = max_heart_stars - converted_heart_stars - required_heart_stars + filler_items -= non_required_heart_stars + trap_amount = math.floor(filler_items * (self.options.trap_percentage / 100.0)) + + filler_items -= trap_amount self.required_heart_stars = required_heart_stars # handle boss requirements here requirements = [required_heart_stars] @@ -281,8 +286,8 @@ class KDL3World(World): requirements.insert(i - 1, quotient * i) self.boss_requirements = requirements itempool.extend([self.create_item("Heart Star") for _ in range(required_heart_stars)]) - itempool.extend([self.create_item(self.get_filler_item_name(False)) - for _ in range(filler_amount + (remaining_items - total_heart_stars))]) + itempool.extend([self.create_item(self.get_filler_item_name(bool(self.options.starsanity.value))) + for _ in range(filler_items)]) itempool.extend([self.create_item(self.get_trap_item_name()) for _ in range(trap_amount)]) itempool.extend([self.create_item("Heart Star", True) for _ in range(non_required_heart_stars)]) diff --git a/worlds/kdl3/items.py b/worlds/kdl3/items.py index 1c7f9dc12c..72687a6065 100644 --- a/worlds/kdl3/items.py +++ b/worlds/kdl3/items.py @@ -77,9 +77,9 @@ filler_item_weights = { } star_item_weights = { - "Little Star": 4, - "Medium Star": 2, - "Big Star": 1 + "Little Star": 16, + "Medium Star": 8, + "Big Star": 4 } total_filler_weights = { diff --git a/worlds/kdl3/options.py b/worlds/kdl3/options.py index f4f3aa2cb9..538c73b786 100644 --- a/worlds/kdl3/options.py +++ b/worlds/kdl3/options.py @@ -4,7 +4,7 @@ from typing import List from BaseClasses import OptionGroup from Options import DeathLinkMixin, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \ - PerGameCommonOptions + PerGameCommonOptions, Visibility from .names import location_name @@ -46,13 +46,14 @@ class GoalSpeed(Choice): option_fast = 1 -class TotalHeartStars(Range): +class MaxHeartStars(Range): """ Maximum number of heart stars to include in the pool of items. + If fewer available locations exist in the pool than this number, the number of available locations will be used instead. """ display_name = "Max Heart Stars" range_start = 5 # set to 5 so strict bosses does not degrade - range_end = 50 # 30 default locations + 30 stage clears + 5 bosses - 14 progression items = 51, so round down + range_end = 99 # previously set to 50, set to highest it can be should there be less locations than heart stars default = 30 @@ -320,6 +321,7 @@ class KirbyFlavor(OptionDict): "14": "F8F8F8", "15": "B03830", } + visibility = Visibility.template | Visibility.spoiler # likely never supported on guis class GooeyFlavorPreset(Choice): @@ -371,6 +373,7 @@ class GooeyFlavor(OptionDict): "8": "D0C0C0", "9": "F8F8F8", } + visibility = Visibility.template | Visibility.spoiler # likely never supported on guis class MusicShuffle(Choice): @@ -410,13 +413,23 @@ class Gifting(Toggle): display_name = "Gifting" +class TotalHeartStars(Range): + """ + Deprecated. Use max_heart_stars instead. Supported for only one version. + """ + default = -1 + range_start = 5 + range_end = 99 + visibility = Visibility.none + + @dataclass class KDL3Options(PerGameCommonOptions, DeathLinkMixin): remote_items: RemoteItems game_language: GameLanguage goal: Goal goal_speed: GoalSpeed - total_heart_stars: TotalHeartStars + max_heart_stars: MaxHeartStars heart_stars_required: HeartStarsRequired filler_percentage: FillerPercentage trap_percentage: TrapPercentage @@ -443,9 +456,11 @@ class KDL3Options(PerGameCommonOptions, DeathLinkMixin): music_shuffle: MusicShuffle virtual_console: VirtualConsoleChanges + total_heart_stars: TotalHeartStars # remove in 2 versions + kdl3_option_groups: List[OptionGroup] = [ - OptionGroup("Goal Options", [Goal, GoalSpeed, TotalHeartStars, HeartStarsRequired, JumpingTarget, ]), + OptionGroup("Goal Options", [Goal, GoalSpeed, MaxHeartStars, HeartStarsRequired, JumpingTarget, ]), OptionGroup("World Options", [RemoteItems, StrictBosses, OpenWorld, OpenWorldBossRequirement, ConsumableChecks, StarChecks, FillerPercentage, TrapPercentage, GooeyTrapPercentage, SlowTrapPercentage, AbilityTrapPercentage, LevelShuffle, BossShuffle, diff --git a/worlds/kdl3/rom.py b/worlds/kdl3/rom.py index aa08cfc644..ed68d0c13a 100644 --- a/worlds/kdl3/rom.py +++ b/worlds/kdl3/rom.py @@ -550,10 +550,11 @@ def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch) -> None: patch.write_token(APTokenTypes.WRITE, 0x944E, struct.pack("H", world.options.jumping_target)) from Utils import __version__ - patch.name = bytearray( + patch_name = bytearray( f'KDL3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] - patch.name.extend([0] * (21 - len(patch.name))) - patch.write_token(APTokenTypes.WRITE, 0x3C000, bytes(patch.name)) + patch_name.extend([0] * (21 - len(patch_name))) + patch.name = bytes(patch_name) + patch.write_token(APTokenTypes.WRITE, 0x3C000, patch.name) patch.write_token(APTokenTypes.WRITE, 0x3C020, world.options.game_language.value.to_bytes(1, "little")) patch.write_token(APTokenTypes.COPY, 0x7FC0, (21, 0x3C000))