mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Wind Waker: Implement New Game (#4458)
Adds The Legend of Zelda: The Wind Waker as a supported game in Archipelago. The game uses [LagoLunatic's randomizer](https://github.com/LagoLunatic/wwrando) as its base (regarding logic, options, etc.) and builds from there.
This commit is contained in:
205
worlds/tww/randomizers/ItemPool.py
Normal file
205
worlds/tww/randomizers/ItemPool.py
Normal file
@@ -0,0 +1,205 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import ItemClassification as IC
|
||||
from Fill import FillError
|
||||
|
||||
from ..Items import ITEM_TABLE, item_factory
|
||||
from ..Options import DungeonItem
|
||||
from .Dungeons import get_dungeon_item_pool_player
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import TWWWorld
|
||||
|
||||
VANILLA_DUNGEON_ITEM_LOCATIONS: dict[str, list[str]] = {
|
||||
"DRC Small Key": [
|
||||
"Dragon Roost Cavern - First Room",
|
||||
"Dragon Roost Cavern - Boarded Up Chest",
|
||||
"Dragon Roost Cavern - Rat Room Boarded Up Chest",
|
||||
"Dragon Roost Cavern - Bird's Nest",
|
||||
],
|
||||
"FW Small Key": [
|
||||
"Forbidden Woods - Vine Maze Right Chest"
|
||||
],
|
||||
"TotG Small Key": [
|
||||
"Tower of the Gods - Hop Across Floating Boxes",
|
||||
"Tower of the Gods - Floating Platforms Room"
|
||||
],
|
||||
"ET Small Key": [
|
||||
"Earth Temple - Transparent Chest in First Crypt",
|
||||
"Earth Temple - Casket in Second Crypt",
|
||||
"Earth Temple - End of Foggy Room With Floormasters",
|
||||
],
|
||||
"WT Small Key": [
|
||||
"Wind Temple - Spike Wall Room - First Chest",
|
||||
"Wind Temple - Chest Behind Seven Armos"
|
||||
],
|
||||
|
||||
"DRC Big Key": ["Dragon Roost Cavern - Big Key Chest"],
|
||||
"FW Big Key": ["Forbidden Woods - Big Key Chest"],
|
||||
"TotG Big Key": ["Tower of the Gods - Big Key Chest"],
|
||||
"ET Big Key": ["Earth Temple - Big Key Chest"],
|
||||
"WT Big Key": ["Wind Temple - Big Key Chest"],
|
||||
|
||||
"DRC Dungeon Map": ["Dragon Roost Cavern - Alcove With Water Jugs"],
|
||||
"FW Dungeon Map": ["Forbidden Woods - First Room"],
|
||||
"TotG Dungeon Map": ["Tower of the Gods - Chest Behind Bombable Walls"],
|
||||
"FF Dungeon Map": ["Forsaken Fortress - Chest Outside Upper Jail Cell"],
|
||||
"ET Dungeon Map": ["Earth Temple - Transparent Chest In Warp Pot Room"],
|
||||
"WT Dungeon Map": ["Wind Temple - Chest In Many Cyclones Room"],
|
||||
|
||||
"DRC Compass": ["Dragon Roost Cavern - Rat Room"],
|
||||
"FW Compass": ["Forbidden Woods - Vine Maze Left Chest"],
|
||||
"TotG Compass": ["Tower of the Gods - Skulls Room Chest"],
|
||||
"FF Compass": ["Forsaken Fortress - Chest Guarded By Bokoblin"],
|
||||
"ET Compass": ["Earth Temple - Chest In Three Blocks Room"],
|
||||
"WT Compass": ["Wind Temple - Chest In Middle Of Hub Room"],
|
||||
}
|
||||
|
||||
|
||||
def generate_itempool(world: "TWWWorld") -> None:
|
||||
"""
|
||||
Generate the item pool for the world.
|
||||
|
||||
:param world: The Wind Waker game world.
|
||||
"""
|
||||
multiworld = world.multiworld
|
||||
|
||||
# Get the core pool of items.
|
||||
pool, precollected_items = get_pool_core(world)
|
||||
|
||||
# Add precollected items to the multiworld's `precollected_items` list.
|
||||
for item in precollected_items:
|
||||
multiworld.push_precollected(item_factory(item, world))
|
||||
|
||||
# Place a "Victory" item on "Defeat Ganondorf" for the spoiler log.
|
||||
world.get_location("Defeat Ganondorf").place_locked_item(item_factory("Victory", world))
|
||||
|
||||
# Create the pool of the remaining shuffled items.
|
||||
items = item_factory(pool, world)
|
||||
world.random.shuffle(items)
|
||||
|
||||
multiworld.itempool += items
|
||||
|
||||
# Dungeon items should already be created, so handle those separately.
|
||||
handle_dungeon_items(world)
|
||||
|
||||
|
||||
def get_pool_core(world: "TWWWorld") -> tuple[list[str], list[str]]:
|
||||
"""
|
||||
Get the core pool of items and precollected items for the world.
|
||||
|
||||
:param world: The Wind Waker game world.
|
||||
:return: A tuple of the item pool and precollected items.
|
||||
"""
|
||||
pool: list[str] = []
|
||||
precollected_items: list[str] = []
|
||||
|
||||
# Split items into three different pools: progression, useful, and filler.
|
||||
progression_pool: list[str] = []
|
||||
useful_pool: list[str] = []
|
||||
filler_pool: list[str] = []
|
||||
for item, data in ITEM_TABLE.items():
|
||||
if data.type == "Item":
|
||||
adjusted_classification = world.item_classification_overrides.get(item)
|
||||
classification = data.classification if adjusted_classification is None else adjusted_classification
|
||||
|
||||
if classification & IC.progression:
|
||||
progression_pool.extend([item] * data.quantity)
|
||||
elif classification & IC.useful:
|
||||
useful_pool.extend([item] * data.quantity)
|
||||
else:
|
||||
filler_pool.extend([item] * data.quantity)
|
||||
|
||||
# Assign useful and filler items to item pools in the world.
|
||||
world.random.shuffle(useful_pool)
|
||||
world.random.shuffle(filler_pool)
|
||||
world.useful_pool = useful_pool
|
||||
world.filler_pool = filler_pool
|
||||
|
||||
# Add filler items to place into excluded locations.
|
||||
pool.extend([world.get_filler_item_name() for _ in world.options.exclude_locations])
|
||||
|
||||
# The remaining of items left to place should be the same as the number of non-excluded locations in the world.
|
||||
nonexcluded_locations = [
|
||||
location
|
||||
for location in world.multiworld.get_locations(world.player)
|
||||
if location.name not in world.options.exclude_locations
|
||||
]
|
||||
num_items_left_to_place = len(nonexcluded_locations) - 1
|
||||
|
||||
# Account for the dungeon items that have already been created.
|
||||
for dungeon in world.dungeons.values():
|
||||
num_items_left_to_place -= len(dungeon.all_items)
|
||||
|
||||
# All progression items are added to the item pool.
|
||||
if len(progression_pool) > num_items_left_to_place:
|
||||
raise FillError(
|
||||
"There are insufficient locations to place progression items! "
|
||||
f"Trying to place {len(progression_pool)} items in only {num_items_left_to_place} locations."
|
||||
)
|
||||
pool.extend(progression_pool)
|
||||
num_items_left_to_place -= len(progression_pool)
|
||||
|
||||
# If the player starts with a sword, add one to the precollected items list and remove one from the item pool.
|
||||
if world.options.sword_mode == "start_with_sword":
|
||||
precollected_items.append("Progressive Sword")
|
||||
num_items_left_to_place += 1
|
||||
pool.remove("Progressive Sword")
|
||||
# Or, if it's swordless mode, remove all swords from the item pool.
|
||||
elif world.options.sword_mode == "swordless":
|
||||
while "Progressive Sword" in pool:
|
||||
num_items_left_to_place += 1
|
||||
pool.remove("Progressive Sword")
|
||||
|
||||
# Place useful items, then filler items to fill out the remaining locations.
|
||||
pool.extend([world.get_filler_item_name(strict=False) for _ in range(num_items_left_to_place)])
|
||||
|
||||
return pool, precollected_items
|
||||
|
||||
|
||||
def handle_dungeon_items(world: "TWWWorld") -> None:
|
||||
"""
|
||||
Handle the placement of dungeon items in the world.
|
||||
|
||||
:param world: The Wind Waker game world.
|
||||
"""
|
||||
player = world.player
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
|
||||
dungeon_items = [
|
||||
item
|
||||
for item in get_dungeon_item_pool_player(world)
|
||||
if item.name not in multiworld.worlds[player].dungeon_local_item_names
|
||||
]
|
||||
|
||||
for x in range(len(dungeon_items) - 1, -1, -1):
|
||||
item = dungeon_items[x]
|
||||
|
||||
# Consider dungeon items in non-required dungeons as filler.
|
||||
if item.dungeon.name in world.boss_reqs.banned_dungeons:
|
||||
item.classification = IC.filler
|
||||
|
||||
option: DungeonItem
|
||||
if item.type == "Big Key":
|
||||
option = options.randomize_bigkeys
|
||||
elif item.type == "Small Key":
|
||||
option = options.randomize_smallkeys
|
||||
else:
|
||||
option = options.randomize_mapcompass
|
||||
|
||||
if option == "startwith":
|
||||
dungeon_items.pop(x)
|
||||
multiworld.push_precollected(item)
|
||||
multiworld.itempool.append(item_factory(world.get_filler_item_name(), world))
|
||||
elif option == "vanilla":
|
||||
for location_name in VANILLA_DUNGEON_ITEM_LOCATIONS[item.name]:
|
||||
location = world.get_location(location_name)
|
||||
if location.item is None:
|
||||
dungeon_items.pop(x)
|
||||
location.place_locked_item(item)
|
||||
break
|
||||
else:
|
||||
raise FillError(f"Could not place dungeon item in vanilla location: {item}")
|
||||
|
||||
multiworld.itempool.extend([item for item in dungeon_items])
|
||||
Reference in New Issue
Block a user