LADX: tarins gift improvement (#3970)

* add groups and a preset

* formatting

* pull zig's tarin's gift improvements

* typing

* alias groups for progressive items

* change tarins gift option a bit

* add bush breakers item group

* fix typo

* bush_breaker option, respect non_local_items

* review suggestions

* cleaner
thx exempt

* Update worlds/ladx/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* fix gen failures for dungeon shuffle

* exclude shovel based on entrance mapping

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
threeandthreee
2025-03-07 19:24:58 -05:00
committed by GitHub
parent bb9a6bcd2e
commit 2f0b81e12c
3 changed files with 77 additions and 38 deletions

View File

@@ -7,23 +7,12 @@ from ..roomEditor import RoomEditor
class StartItem(DroppedKey): class StartItem(DroppedKey):
# We need to give something here that we can use to progress.
# FEATHER
OPTIONS = [SWORD, SHIELD, POWER_BRACELET, OCARINA, BOOMERANG, MAGIC_ROD, TAIL_KEY, SHOVEL, HOOKSHOT, PEGASUS_BOOTS, MAGIC_POWDER, BOMB]
MULTIWORLD = False MULTIWORLD = False
def __init__(self): def __init__(self):
super().__init__(0x2A3) super().__init__(0x2A3)
self.give_bowwow = False self.give_bowwow = False
def configure(self, options):
if options.bowwow != 'normal':
# When we have bowwow mode, we pretend to be a sword for logic reasons
self.OPTIONS = [SWORD]
self.give_bowwow = True
if options.randomstartlocation and options.entranceshuffle != 'none':
self.OPTIONS.append(FLIPPERS)
def patch(self, rom, option, *, multiworld=None): def patch(self, rom, option, *, multiworld=None):
assert multiworld is None assert multiworld is None

View File

@@ -527,6 +527,20 @@ class InGameHints(DefaultOnToggle):
display_name = "In-game Hints" display_name = "In-game Hints"
class TarinsGift(Choice):
"""
[Local Progression] Forces Tarin's gift to be an item that immediately opens up local checks.
Has little effect in single player games, and isn't always necessary with randomized entrances.
[Bush Breaker] Forces Tarin's gift to be an item that can destroy bushes.
[Any Item] Tarin's gift can be any item for any world
"""
display_name = "Tarin's Gift"
option_local_progression = 0
option_bush_breaker = 1
option_any_item = 2
default = option_local_progression
class StabilizeItemPool(DefaultOffToggle): class StabilizeItemPool(DefaultOffToggle):
""" """
By default, rupees in the item pool may be randomly swapped with bombs, arrows, powders, or capacity upgrades. This option disables that swapping, which is useful for plando. By default, rupees in the item pool may be randomly swapped with bombs, arrows, powders, or capacity upgrades. This option disables that swapping, which is useful for plando.
@@ -565,6 +579,7 @@ ladx_option_groups = [
OptionGroup("Miscellaneous", [ OptionGroup("Miscellaneous", [
TradeQuest, TradeQuest,
Rooster, Rooster,
TarinsGift,
Overworld, Overworld,
TrendyGame, TrendyGame,
InGameHints, InGameHints,
@@ -638,6 +653,7 @@ class LinksAwakeningOptions(PerGameCommonOptions):
text_mode: TextMode text_mode: TextMode
no_flash: NoFlash no_flash: NoFlash
in_game_hints: InGameHints in_game_hints: InGameHints
tarins_gift: TarinsGift
overworld: Overworld overworld: Overworld
stabilize_item_pool: StabilizeItemPool stabilize_item_pool: StabilizeItemPool

View File

@@ -4,6 +4,7 @@ import os
import pkgutil import pkgutil
import tempfile import tempfile
import typing import typing
import logging
import re import re
import bsdiff4 import bsdiff4
@@ -206,6 +207,8 @@ class LinksAwakeningWorld(World):
return Item(event, ItemClassification.progression, None, self.player) return Item(event, ItemClassification.progression, None, self.player)
def create_items(self) -> None: def create_items(self) -> None:
itempool = []
exclude = [item.name for item in self.multiworld.precollected_items[self.player]] exclude = [item.name for item in self.multiworld.precollected_items[self.player]]
self.prefill_original_dungeon = [ [], [], [], [], [], [], [], [], [] ] self.prefill_original_dungeon = [ [], [], [], [], [], [], [], [], [] ]
@@ -265,9 +268,9 @@ class LinksAwakeningWorld(World):
self.prefill_own_dungeons.append(item) self.prefill_own_dungeons.append(item)
self.pre_fill_items.append(item) self.pre_fill_items.append(item)
else: else:
self.multiworld.itempool.append(item) itempool.append(item)
else: else:
self.multiworld.itempool.append(item) itempool.append(item)
self.multi_key = self.generate_multi_key() self.multi_key = self.generate_multi_key()
@@ -290,21 +293,52 @@ class LinksAwakeningWorld(World):
# Properly fill locations within dungeon # Properly fill locations within dungeon
location.dungeon = r.dungeon_index location.dungeon = r.dungeon_index
# For now, special case first item if self.options.tarins_gift != "any_item":
FORCE_START_ITEM = True self.force_start_item(itempool)
if FORCE_START_ITEM:
self.force_start_item()
def force_start_item(self):
self.multiworld.itempool += itempool
def force_start_item(self, itempool):
start_loc = self.multiworld.get_location("Tarin's Gift (Mabe Village)", self.player) start_loc = self.multiworld.get_location("Tarin's Gift (Mabe Village)", self.player)
if not start_loc.item: if not start_loc.item:
possible_start_items = [index for index, item in enumerate(self.multiworld.itempool) """
if item.player == self.player Find an item that forces progression or a bush breaker for the player, depending on settings.
and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location] """
if possible_start_items: def is_possible_start_item(item):
index = self.random.choice(possible_start_items) return item.advancement and item.name not in self.options.non_local_items
start_item = self.multiworld.itempool.pop(index)
def opens_new_regions(item):
collection_state = base_collection_state.copy()
collection_state.collect(item)
return len(collection_state.reachable_regions[self.player]) > reachable_count
start_items = [item for item in itempool if is_possible_start_item(item)]
self.random.shuffle(start_items)
if self.options.tarins_gift == "bush_breaker":
start_item = next((item for item in start_items if item.name in links_awakening_item_name_groups["Bush Breakers"]), None)
else: # local_progression
entrance_mapping = self.ladxr_logic.world_setup.entrance_mapping
# Tail key opens a region but not a location if d1 entrance is not mapped to d1 or d4
# exclude it in these cases to avoid fill errors
if entrance_mapping['d1'] not in ['d1', 'd4']:
start_items = [item for item in start_items if item.name != 'Tail Key']
# Exclude shovel unless starting in Mabe Village
if entrance_mapping['start_house'] not in ['start_house', 'shop']:
start_items = [item for item in start_items if item.name != 'Shovel']
base_collection_state = CollectionState(self.multiworld)
base_collection_state.update_reachable_regions(self.player)
reachable_count = len(base_collection_state.reachable_regions[self.player])
start_item = next((item for item in start_items if opens_new_regions(item)), None)
if start_item:
itempool.remove(start_item)
start_loc.place_locked_item(start_item) start_loc.place_locked_item(start_item)
else:
logging.getLogger("Link's Awakening Logger").warning(f"No {self.options.tarins_gift.current_option_name} available for Tarin's Gift.")
def get_pre_fill_items(self): def get_pre_fill_items(self):
return self.pre_fill_items return self.pre_fill_items