mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
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:
@@ -7,23 +7,12 @@ from ..roomEditor import RoomEditor
|
||||
|
||||
|
||||
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
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(0x2A3)
|
||||
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):
|
||||
assert multiworld is None
|
||||
|
||||
|
@@ -527,6 +527,20 @@ class InGameHints(DefaultOnToggle):
|
||||
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):
|
||||
"""
|
||||
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", [
|
||||
TradeQuest,
|
||||
Rooster,
|
||||
TarinsGift,
|
||||
Overworld,
|
||||
TrendyGame,
|
||||
InGameHints,
|
||||
@@ -638,6 +653,7 @@ class LinksAwakeningOptions(PerGameCommonOptions):
|
||||
text_mode: TextMode
|
||||
no_flash: NoFlash
|
||||
in_game_hints: InGameHints
|
||||
tarins_gift: TarinsGift
|
||||
overworld: Overworld
|
||||
stabilize_item_pool: StabilizeItemPool
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import os
|
||||
import pkgutil
|
||||
import tempfile
|
||||
import typing
|
||||
import logging
|
||||
import re
|
||||
|
||||
import bsdiff4
|
||||
@@ -178,10 +179,10 @@ class LinksAwakeningWorld(World):
|
||||
|
||||
assert(start)
|
||||
|
||||
menu_region = LinksAwakeningRegion("Menu", None, "Menu", self.player, self.multiworld)
|
||||
menu_region = LinksAwakeningRegion("Menu", None, "Menu", self.player, self.multiworld)
|
||||
menu_region.exits = [Entrance(self.player, "Start Game", menu_region)]
|
||||
menu_region.exits[0].connect(start)
|
||||
|
||||
|
||||
self.multiworld.regions.append(menu_region)
|
||||
|
||||
# Place RAFT, other access events
|
||||
@@ -189,14 +190,14 @@ class LinksAwakeningWorld(World):
|
||||
for loc in region.locations:
|
||||
if loc.address is None:
|
||||
loc.place_locked_item(self.create_event(loc.ladxr_item.event))
|
||||
|
||||
|
||||
# Connect Windfish -> Victory
|
||||
windfish = self.multiworld.get_region("Windfish", self.player)
|
||||
l = Location(self.player, "Windfish", parent=windfish)
|
||||
windfish.locations = [l]
|
||||
|
||||
|
||||
l.place_locked_item(self.create_event("An Alarm Clock"))
|
||||
|
||||
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("An Alarm Clock", player=self.player)
|
||||
|
||||
def create_item(self, item_name: str):
|
||||
@@ -206,6 +207,8 @@ class LinksAwakeningWorld(World):
|
||||
return Item(event, ItemClassification.progression, None, self.player)
|
||||
|
||||
def create_items(self) -> None:
|
||||
itempool = []
|
||||
|
||||
exclude = [item.name for item in self.multiworld.precollected_items[self.player]]
|
||||
|
||||
self.prefill_original_dungeon = [ [], [], [], [], [], [], [], [], [] ]
|
||||
@@ -265,9 +268,9 @@ class LinksAwakeningWorld(World):
|
||||
self.prefill_own_dungeons.append(item)
|
||||
self.pre_fill_items.append(item)
|
||||
else:
|
||||
self.multiworld.itempool.append(item)
|
||||
itempool.append(item)
|
||||
else:
|
||||
self.multiworld.itempool.append(item)
|
||||
itempool.append(item)
|
||||
|
||||
self.multi_key = self.generate_multi_key()
|
||||
|
||||
@@ -276,8 +279,8 @@ class LinksAwakeningWorld(World):
|
||||
event_location = Location(self.player, "Can Play Trendy Game", parent=trendy_region)
|
||||
trendy_region.locations.insert(0, event_location)
|
||||
event_location.place_locked_item(self.create_event("Can Play Trendy Game"))
|
||||
|
||||
self.dungeon_locations_by_dungeon = [[], [], [], [], [], [], [], [], []]
|
||||
|
||||
self.dungeon_locations_by_dungeon = [[], [], [], [], [], [], [], [], []]
|
||||
for r in self.multiworld.get_regions(self.player):
|
||||
# Set aside dungeon locations
|
||||
if r.dungeon_index:
|
||||
@@ -290,21 +293,52 @@ class LinksAwakeningWorld(World):
|
||||
# Properly fill locations within dungeon
|
||||
location.dungeon = r.dungeon_index
|
||||
|
||||
# For now, special case first item
|
||||
FORCE_START_ITEM = True
|
||||
if FORCE_START_ITEM:
|
||||
self.force_start_item()
|
||||
if self.options.tarins_gift != "any_item":
|
||||
self.force_start_item(itempool)
|
||||
|
||||
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)
|
||||
if not start_loc.item:
|
||||
possible_start_items = [index for index, item in enumerate(self.multiworld.itempool)
|
||||
if item.player == self.player
|
||||
and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location]
|
||||
if possible_start_items:
|
||||
index = self.random.choice(possible_start_items)
|
||||
start_item = self.multiworld.itempool.pop(index)
|
||||
"""
|
||||
Find an item that forces progression or a bush breaker for the player, depending on settings.
|
||||
"""
|
||||
def is_possible_start_item(item):
|
||||
return item.advancement and item.name not in self.options.non_local_items
|
||||
|
||||
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)
|
||||
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):
|
||||
return self.pre_fill_items
|
||||
@@ -317,7 +351,7 @@ class LinksAwakeningWorld(World):
|
||||
|
||||
# set containing the list of all possible dungeon locations for the player
|
||||
all_dungeon_locs = set()
|
||||
|
||||
|
||||
# Do dungeon specific things
|
||||
for dungeon_index in range(0, 9):
|
||||
# set up allow-list for dungeon specific items
|
||||
@@ -330,7 +364,7 @@ class LinksAwakeningWorld(World):
|
||||
# ...also set the rules for the dungeon
|
||||
for location in locs:
|
||||
orig_rule = location.item_rule
|
||||
# If an item is about to be placed on a dungeon location, it can go there iff
|
||||
# If an item is about to be placed on a dungeon location, it can go there iff
|
||||
# 1. it fits the general rules for that location (probably 'return True' for most places)
|
||||
# 2. Either
|
||||
# 2a. it's not a restricted dungeon item
|
||||
@@ -382,7 +416,7 @@ class LinksAwakeningWorld(World):
|
||||
|
||||
# Sweep to pick up already placed items that are reachable with everything but the dungeon items.
|
||||
partial_all_state.sweep_for_advancements()
|
||||
|
||||
|
||||
fill_restrictive(self.multiworld, partial_all_state, all_dungeon_locs_to_fill, all_dungeon_items_to_fill, lock=True, single_player_placement=True, allow_partial=False)
|
||||
|
||||
|
||||
@@ -421,7 +455,7 @@ class LinksAwakeningWorld(World):
|
||||
for name in possibles:
|
||||
if name in self.name_cache:
|
||||
return self.name_cache[name]
|
||||
|
||||
|
||||
return "TRADING_ITEM_LETTER"
|
||||
|
||||
@classmethod
|
||||
@@ -436,7 +470,7 @@ class LinksAwakeningWorld(World):
|
||||
for loc in r.locations:
|
||||
if isinstance(loc, LinksAwakeningLocation):
|
||||
assert(loc.item)
|
||||
|
||||
|
||||
# If we're a links awakening item, just use the item
|
||||
if isinstance(loc.item, LinksAwakeningItem):
|
||||
loc.ladxr_item.item = loc.item.item_data.ladxr_id
|
||||
@@ -470,7 +504,7 @@ class LinksAwakeningWorld(World):
|
||||
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
||||
|
||||
rom = generator.generateRom(args, self)
|
||||
|
||||
|
||||
with open(out_path, "wb") as handle:
|
||||
rom.save(handle, name="LADXR")
|
||||
|
||||
@@ -478,7 +512,7 @@ class LinksAwakeningWorld(World):
|
||||
if self.options.ap_title_screen:
|
||||
with tempfile.NamedTemporaryFile(delete=False) as title_patch:
|
||||
title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
|
||||
|
||||
|
||||
bsdiff4.file_patch_inplace(out_path, title_patch.name)
|
||||
os.unlink(title_patch.name)
|
||||
|
||||
|
Reference in New Issue
Block a user