TUNIC: Grass Randomizer (#3913)
* Fix certain items not being added to slot data * Change where items get added to slot data * Add initial grass randomizer stuff * Fix rules * Update grass.py Improve location names * Remove wand and gun from logic * Update __init__.py * Fix logic for two pieces of grass in atoll * Make early bushes only contain grass * Backport changes to grass rando (#20) * Backport changes to grass rando * add_rule instead of set_rule for the special cases, add special cases for back of swamp laurels area cause I should've made a new region for the swamp upper entrance * Remove item name group for grass * Update grass rando option descriptions - Also ignore grass fill for single player games * Ignore grass fill option for solo rando * Update er_rules.py * Fix pre fill issue * Remove duplicate option * Add excluded grass locations back * Hide grass fill option from simple ui options page * Check for start with sword before setting grass rules * Update worlds/tunic/options.py Co-authored-by: Scipio Wright <scipiowright@gmail.com> * Exclude grass from get_filler_item_name - non-grass rando games were accidentally seeing grass items get shuffled in as filler, which is funny but probably shouldn't happen * Update worlds/tunic/__init__.py Co-authored-by: Scipio Wright <scipiowright@gmail.com> * Apply suggestions from code review Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Scipio Wright <scipiowright@gmail.com> * change the rest of grass_fill to local_fill * Filter out grass from filler_items * remove -> discard * Update worlds/tunic/__init__.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * change has_stick to has_melee * Update grass list with combat logic regions * More fixes from combat logic merge * Fix some dumb stuff (#21) * Reorganize pre fill for grass * Update option value passthrough * Update __init__.py * Fix region name * Make separate pools for the grass and non-grass fills (#22) * Make separate pools for the grass and non-grass fills * Update worlds/tunic/__init__.py Co-authored-by: Scipio Wright <scipiowright@gmail.com> * Fix those things in the PR (#23) * Use excludable property Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: Scipio Wright <scipiowright@gmail.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
parent
9dac7d9cc3
commit
b7baaed391
|
@ -1,19 +1,20 @@
|
|||
from typing import Dict, List, Any, Tuple, TypedDict, ClassVar, Union
|
||||
from typing import Dict, List, Any, Tuple, TypedDict, ClassVar, Union, Set
|
||||
from logging import warning
|
||||
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState
|
||||
from .items import (item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names,
|
||||
combat_items)
|
||||
from .locations import location_table, location_name_groups, location_name_to_id, hexagon_locations
|
||||
from .locations import location_table, location_name_groups, standard_location_name_to_id, hexagon_locations, sphere_one
|
||||
from .rules import set_location_rules, set_region_rules, randomize_ability_unlocks, gold_hexagon
|
||||
from .er_rules import set_er_location_rules
|
||||
from .regions import tunic_regions
|
||||
from .er_scripts import create_er_regions
|
||||
from .grass import grass_location_table, grass_location_name_to_id, grass_location_name_groups, excluded_grass_locations
|
||||
from .er_data import portal_mapping, RegionInfo, tunic_er_regions
|
||||
from .options import (TunicOptions, EntranceRando, tunic_option_groups, tunic_option_presets, TunicPlandoConnections,
|
||||
LaurelsLocation, LogicRules, LaurelsZips, IceGrappling, LadderStorage)
|
||||
from .combat_logic import area_data, CombatState
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from Options import PlandoConnection
|
||||
from Options import PlandoConnection, OptionError
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
from settings import Group, Bool
|
||||
|
||||
|
@ -22,7 +23,11 @@ class TunicSettings(Group):
|
|||
class DisableLocalSpoiler(Bool):
|
||||
"""Disallows the TUNIC client from creating a local spoiler log."""
|
||||
|
||||
class LimitGrassRando(Bool):
|
||||
"""Limits the impact of Grass Randomizer on the multiworld by disallowing local_fill percentages below 95."""
|
||||
|
||||
disable_local_spoiler: Union[DisableLocalSpoiler, bool] = False
|
||||
limit_grass_rando: Union[LimitGrassRando, bool] = True
|
||||
|
||||
|
||||
class TunicWeb(WebWorld):
|
||||
|
@ -73,10 +78,13 @@ class TunicWorld(World):
|
|||
settings: ClassVar[TunicSettings]
|
||||
item_name_groups = item_name_groups
|
||||
location_name_groups = location_name_groups
|
||||
location_name_groups.update(grass_location_name_groups)
|
||||
|
||||
item_name_to_id = item_name_to_id
|
||||
location_name_to_id = location_name_to_id
|
||||
location_name_to_id = standard_location_name_to_id.copy()
|
||||
location_name_to_id.update(grass_location_name_to_id)
|
||||
|
||||
player_location_table: Dict[str, int]
|
||||
ability_unlocks: Dict[str, int]
|
||||
slot_data_items: List[TunicItem]
|
||||
tunic_portal_pairs: Dict[str, str]
|
||||
|
@ -85,6 +93,11 @@ class TunicWorld(World):
|
|||
shop_num: int = 1 # need to make it so that you can walk out of shops, but also that they aren't all connected
|
||||
er_regions: Dict[str, RegionInfo] # absolutely needed so outlet regions work
|
||||
|
||||
# for the local_fill option
|
||||
fill_items: List[TunicItem]
|
||||
fill_locations: List[TunicLocation]
|
||||
amount_to_local_fill: int
|
||||
|
||||
# so we only loop the multiworld locations once
|
||||
# if these are locations instead of their info, it gives a memory leak error
|
||||
item_link_locations: Dict[int, Dict[str, List[Tuple[int, str]]]] = {}
|
||||
|
@ -132,6 +145,7 @@ class TunicWorld(World):
|
|||
self.options.hexagon_quest.value = self.passthrough["hexagon_quest"]
|
||||
self.options.entrance_rando.value = self.passthrough["entrance_rando"]
|
||||
self.options.shuffle_ladders.value = self.passthrough["shuffle_ladders"]
|
||||
self.options.grass_randomizer.value = self.passthrough.get("grass_randomizer", 0)
|
||||
self.options.fixed_shop.value = self.options.fixed_shop.option_false
|
||||
self.options.laurels_location.value = self.options.laurels_location.option_anywhere
|
||||
self.options.combat_logic.value = self.passthrough["combat_logic"]
|
||||
|
@ -140,6 +154,22 @@ class TunicWorld(World):
|
|||
else:
|
||||
self.using_ut = False
|
||||
|
||||
self.player_location_table = standard_location_name_to_id.copy()
|
||||
|
||||
if self.options.local_fill == -1:
|
||||
if self.options.grass_randomizer:
|
||||
self.options.local_fill.value = 95
|
||||
else:
|
||||
self.options.local_fill.value = 0
|
||||
|
||||
if self.options.grass_randomizer:
|
||||
if self.settings.limit_grass_rando and self.options.local_fill < 95 and self.multiworld.players > 1:
|
||||
raise OptionError(f"TUNIC: Player {self.player_name} has their Local Fill option set too low. "
|
||||
f"They must either bring it above 95% or the host needs to disable limit_grass_rando "
|
||||
f"in their host.yaml settings")
|
||||
|
||||
self.player_location_table.update(grass_location_name_to_id)
|
||||
|
||||
@classmethod
|
||||
def stage_generate_early(cls, multiworld: MultiWorld) -> None:
|
||||
tunic_worlds: Tuple[TunicWorld] = multiworld.get_game_worlds("TUNIC")
|
||||
|
@ -245,6 +275,14 @@ class TunicWorld(World):
|
|||
self.get_location("Secret Gathering Place - 10 Fairy Reward").place_locked_item(laurels)
|
||||
items_to_create["Hero's Laurels"] = 0
|
||||
|
||||
if self.options.grass_randomizer:
|
||||
items_to_create["Grass"] = len(grass_location_table)
|
||||
tunic_items.append(self.create_item("Glass Cannon", ItemClassification.progression))
|
||||
items_to_create["Glass Cannon"] = 0
|
||||
for grass_location in excluded_grass_locations:
|
||||
self.get_location(grass_location).place_locked_item(self.create_item("Grass"))
|
||||
items_to_create["Grass"] -= len(excluded_grass_locations)
|
||||
|
||||
if self.options.keys_behind_bosses:
|
||||
for rgb_hexagon, location in hexagon_locations.items():
|
||||
hex_item = self.create_item(gold_hexagon if self.options.hexagon_quest else rgb_hexagon)
|
||||
|
@ -332,8 +370,73 @@ class TunicWorld(World):
|
|||
if tunic_item.name in slot_data_item_names:
|
||||
self.slot_data_items.append(tunic_item)
|
||||
|
||||
# pull out the filler so that we can place it manually during pre_fill
|
||||
self.fill_items = []
|
||||
if self.options.local_fill > 0 and self.multiworld.players > 1:
|
||||
# skip items marked local or non-local, let fill deal with them in its own way
|
||||
# discard grass from non_local if it's meant to be limited
|
||||
if self.settings.limit_grass_rando:
|
||||
self.options.non_local_items.value.discard("Grass")
|
||||
all_filler: List[TunicItem] = []
|
||||
non_filler: List[TunicItem] = []
|
||||
for tunic_item in tunic_items:
|
||||
if (tunic_item.excludable
|
||||
and tunic_item.name not in self.options.local_items
|
||||
and tunic_item.name not in self.options.non_local_items):
|
||||
all_filler.append(tunic_item)
|
||||
else:
|
||||
non_filler.append(tunic_item)
|
||||
self.amount_to_local_fill = int(self.options.local_fill.value * len(all_filler) / 100)
|
||||
self.fill_items += all_filler[:self.amount_to_local_fill]
|
||||
del all_filler[:self.amount_to_local_fill]
|
||||
tunic_items = all_filler + non_filler
|
||||
|
||||
self.multiworld.itempool += tunic_items
|
||||
|
||||
def pre_fill(self) -> None:
|
||||
self.fill_locations = []
|
||||
|
||||
if self.options.local_fill > 0 and self.multiworld.players > 1:
|
||||
# we need to reserve a couple locations so that we don't fill up every sphere 1 location
|
||||
reserved_locations: Set[str] = set(self.random.sample(sphere_one, 2))
|
||||
viable_locations = [loc for loc in self.multiworld.get_unfilled_locations(self.player)
|
||||
if loc.name not in reserved_locations
|
||||
and loc.name not in self.options.priority_locations.value]
|
||||
|
||||
if len(viable_locations) < self.amount_to_local_fill:
|
||||
raise OptionError(f"TUNIC: Not enough locations for local_fill option for {self.player_name}. "
|
||||
f"This is likely due to excess plando or priority locations.")
|
||||
|
||||
self.fill_locations += viable_locations
|
||||
|
||||
@classmethod
|
||||
def stage_pre_fill(cls, multiworld: MultiWorld) -> None:
|
||||
tunic_fill_worlds: List[TunicWorld] = [world for world in multiworld.get_game_worlds("TUNIC")
|
||||
if world.options.local_fill.value > 0]
|
||||
if tunic_fill_worlds:
|
||||
grass_fill: List[TunicItem] = []
|
||||
non_grass_fill: List[TunicItem] = []
|
||||
grass_fill_locations: List[Location] = []
|
||||
non_grass_fill_locations: List[Location] = []
|
||||
for world in tunic_fill_worlds:
|
||||
if world.options.grass_randomizer:
|
||||
grass_fill.extend(world.fill_items)
|
||||
grass_fill_locations.extend(world.fill_locations)
|
||||
else:
|
||||
non_grass_fill.extend(world.fill_items)
|
||||
non_grass_fill_locations.extend(world.fill_locations)
|
||||
|
||||
multiworld.random.shuffle(grass_fill)
|
||||
multiworld.random.shuffle(non_grass_fill)
|
||||
multiworld.random.shuffle(grass_fill_locations)
|
||||
multiworld.random.shuffle(non_grass_fill_locations)
|
||||
|
||||
for filler_item in grass_fill:
|
||||
multiworld.push_item(grass_fill_locations.pop(), filler_item, collect=False)
|
||||
|
||||
for filler_item in non_grass_fill:
|
||||
multiworld.push_item(non_grass_fill_locations.pop(), filler_item, collect=False)
|
||||
|
||||
def create_regions(self) -> None:
|
||||
self.tunic_portal_pairs = {}
|
||||
self.er_portal_hints = {}
|
||||
|
@ -346,7 +449,8 @@ class TunicWorld(World):
|
|||
self.ability_unlocks["Pages 52-53 (Icebolt)"] = self.passthrough["Hexagon Quest Icebolt"]
|
||||
|
||||
# Ladders and Combat Logic uses ER rules with vanilla connections for easier maintenance
|
||||
if self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic:
|
||||
if (self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic
|
||||
or self.options.grass_randomizer):
|
||||
portal_pairs = create_er_regions(self)
|
||||
if self.options.entrance_rando:
|
||||
# these get interpreted by the game to tell it which entrances to connect
|
||||
|
@ -362,7 +466,7 @@ class TunicWorld(World):
|
|||
region = self.get_region(region_name)
|
||||
region.add_exits(exits)
|
||||
|
||||
for location_name, location_id in self.location_name_to_id.items():
|
||||
for location_name, location_id in self.player_location_table.items():
|
||||
region = self.get_region(location_table[location_name].region)
|
||||
location = TunicLocation(self.player, location_name, location_id, region)
|
||||
region.locations.append(location)
|
||||
|
@ -375,7 +479,8 @@ class TunicWorld(World):
|
|||
|
||||
def set_rules(self) -> None:
|
||||
# same reason as in create_regions, could probably be put into create_regions
|
||||
if self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic:
|
||||
if (self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic
|
||||
or self.options.grass_randomizer):
|
||||
set_er_location_rules(self)
|
||||
else:
|
||||
set_region_rules(self)
|
||||
|
@ -463,6 +568,7 @@ class TunicWorld(World):
|
|||
"maskless": self.options.maskless.value,
|
||||
"entrance_rando": int(bool(self.options.entrance_rando.value)),
|
||||
"shuffle_ladders": self.options.shuffle_ladders.value,
|
||||
"grass_randomizer": self.options.grass_randomizer.value,
|
||||
"combat_logic": self.options.combat_logic.value,
|
||||
"Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"],
|
||||
"Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"],
|
||||
|
|
|
@ -629,14 +629,16 @@ tunic_er_regions: Dict[str, RegionInfo] = {
|
|||
"Beneath the Well Back": RegionInfo("Sewer"), # the back two portals, and all 4 upper chests
|
||||
"West Garden before Terry": RegionInfo("Archipelagos Redux"), # the lower entry point, near hero grave
|
||||
"West Garden after Terry": RegionInfo("Archipelagos Redux"), # after Terry, up until next chompignons
|
||||
"West Garden West Combat": RegionInfo("Archipelagos Redux"), # for grass rando basically
|
||||
"West Garden at Dagger House": RegionInfo("Archipelagos Redux"), # just outside magic dagger house
|
||||
"West Garden South Checkpoint": RegionInfo("Archipelagos Redux"),
|
||||
"West Garden South Checkpoint": RegionInfo("Archipelagos Redux"), # the checkpoint and the blue lines area
|
||||
"Magic Dagger House": RegionInfo("archipelagos_house", dead_end=DeadEnd.all_cats),
|
||||
"West Garden Portal": RegionInfo("Archipelagos Redux", dead_end=DeadEnd.restricted, outlet_region="West Garden by Portal"),
|
||||
"West Garden Portal": RegionInfo("Archipelagos Redux", dead_end=DeadEnd.restricted,
|
||||
outlet_region="West Garden by Portal"),
|
||||
"West Garden by Portal": RegionInfo("Archipelagos Redux", dead_end=DeadEnd.restricted),
|
||||
"West Garden Portal Item": RegionInfo("Archipelagos Redux", dead_end=DeadEnd.restricted),
|
||||
"West Garden Laurels Exit Region": RegionInfo("Archipelagos Redux"),
|
||||
"West Garden before Boss": RegionInfo("Archipelagos Redux"), # main west garden
|
||||
"West Garden before Boss": RegionInfo("Archipelagos Redux"), # up the ladder before garden knight
|
||||
"West Garden after Boss": RegionInfo("Archipelagos Redux"),
|
||||
"West Garden Hero's Grave Region": RegionInfo("Archipelagos Redux", outlet_region="West Garden before Terry"),
|
||||
"Ruined Atoll": RegionInfo("Atoll Redux"),
|
||||
|
@ -1165,8 +1167,10 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
|
|||
"West Garden after Terry": {
|
||||
"West Garden before Terry":
|
||||
[],
|
||||
"West Garden South Checkpoint":
|
||||
"West Garden West Combat":
|
||||
[],
|
||||
"West Garden South Checkpoint":
|
||||
[["Hyperdash"]],
|
||||
"West Garden Laurels Exit Region":
|
||||
[["LS1"]],
|
||||
},
|
||||
|
@ -1176,6 +1180,8 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
|
|||
"West Garden at Dagger House":
|
||||
[],
|
||||
"West Garden after Terry":
|
||||
[["Hyperdash"]],
|
||||
"West Garden West Combat":
|
||||
[],
|
||||
},
|
||||
"West Garden before Boss": {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from typing import Dict, FrozenSet, Tuple, TYPE_CHECKING
|
||||
from worlds.generic.Rules import set_rule, add_rule, forbid_item
|
||||
from BaseClasses import Region, CollectionState
|
||||
from .options import IceGrappling, LadderStorage, CombatLogic
|
||||
from .rules import (has_ability, has_sword, has_melee, has_ice_grapple_logic, has_lantern, has_mask, can_ladder_storage,
|
||||
laurels_zip, bomb_walls)
|
||||
from .er_data import Portal, get_portal_outlet_region
|
||||
from .ladder_storage_data import ow_ladder_groups, region_ladders, easy_ls, medium_ls, hard_ls
|
||||
from .combat_logic import has_combat_reqs
|
||||
from BaseClasses import Region, CollectionState
|
||||
from .grass import set_grass_location_rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import TunicWorld
|
||||
|
@ -555,7 +556,6 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
|
|||
regions["Dark Tomb Upper"].connect(
|
||||
connecting_region=regions["Dark Tomb Entry Point"])
|
||||
|
||||
# ice grapple through the wall, get the little secret sound to trigger
|
||||
regions["Dark Tomb Upper"].connect(
|
||||
connecting_region=regions["Dark Tomb Main"],
|
||||
rule=lambda state: has_ladder("Ladder in Dark Tomb", state, world)
|
||||
|
@ -577,11 +577,24 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
|
|||
wg_after_to_before_terry = regions["West Garden after Terry"].connect(
|
||||
connecting_region=regions["West Garden before Terry"])
|
||||
|
||||
regions["West Garden after Terry"].connect(
|
||||
connecting_region=regions["West Garden South Checkpoint"])
|
||||
wg_checkpoint_to_after_terry = regions["West Garden South Checkpoint"].connect(
|
||||
wg_after_terry_to_west_combat = regions["West Garden after Terry"].connect(
|
||||
connecting_region=regions["West Garden West Combat"])
|
||||
regions["West Garden West Combat"].connect(
|
||||
connecting_region=regions["West Garden after Terry"])
|
||||
|
||||
wg_checkpoint_to_west_combat = regions["West Garden South Checkpoint"].connect(
|
||||
connecting_region=regions["West Garden West Combat"])
|
||||
regions["West Garden West Combat"].connect(
|
||||
connecting_region=regions["West Garden South Checkpoint"])
|
||||
|
||||
# if not laurels, it goes through the west combat region instead
|
||||
regions["West Garden after Terry"].connect(
|
||||
connecting_region=regions["West Garden South Checkpoint"],
|
||||
rule=lambda state: state.has(laurels, player))
|
||||
regions["West Garden South Checkpoint"].connect(
|
||||
connecting_region=regions["West Garden after Terry"],
|
||||
rule=lambda state: state.has(laurels, player))
|
||||
|
||||
wg_checkpoint_to_dagger = regions["West Garden South Checkpoint"].connect(
|
||||
connecting_region=regions["West Garden at Dagger House"])
|
||||
regions["West Garden at Dagger House"].connect(
|
||||
|
@ -1402,11 +1415,15 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
|
|||
set_rule(wg_after_to_before_terry,
|
||||
lambda state: state.has_any({laurels, ice_dagger}, player)
|
||||
or has_combat_reqs("West Garden", state, player))
|
||||
# laurels through, probably to the checkpoint, or just fight
|
||||
set_rule(wg_checkpoint_to_after_terry,
|
||||
lambda state: state.has(laurels, player) or has_combat_reqs("West Garden", state, player))
|
||||
set_rule(wg_checkpoint_to_before_boss,
|
||||
|
||||
set_rule(wg_after_terry_to_west_combat,
|
||||
lambda state: has_combat_reqs("West Garden", state, player))
|
||||
set_rule(wg_checkpoint_to_west_combat,
|
||||
lambda state: has_combat_reqs("West Garden", state, player))
|
||||
|
||||
# maybe a little too generous? probably fine though
|
||||
set_rule(wg_checkpoint_to_before_boss,
|
||||
lambda state: state.has(laurels, player) or has_combat_reqs("West Garden", state, player))
|
||||
|
||||
add_rule(btv_front_to_main,
|
||||
lambda state: has_combat_reqs("Beneath the Vault", state, player))
|
||||
|
@ -1528,6 +1545,9 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
|
|||
def set_er_location_rules(world: "TunicWorld") -> None:
|
||||
player = world.player
|
||||
|
||||
if world.options.grass_randomizer:
|
||||
set_grass_location_rules(world)
|
||||
|
||||
forbid_item(world.get_location("Secret Gathering Place - 20 Fairy Reward"), fairies, player)
|
||||
|
||||
# Ability Shuffle Exclusive Rules
|
||||
|
@ -1852,6 +1872,8 @@ def set_er_location_rules(world: "TunicWorld") -> None:
|
|||
combat_logic_to_loc("West Garden - [Central Lowlands] Chest Beneath Faeries", "West Garden")
|
||||
combat_logic_to_loc("West Garden - [Central Lowlands] Chest Beneath Save Point", "West Garden")
|
||||
combat_logic_to_loc("West Garden - [West Highlands] Upper Left Walkway", "West Garden")
|
||||
combat_logic_to_loc("West Garden - [Central Highlands] Holy Cross (Blue Lines)", "West Garden")
|
||||
combat_logic_to_loc("West Garden - [Central Highlands] Behind Guard Captain", "West Garden")
|
||||
|
||||
# with combat logic on, I presume the player will want to be able to see to avoid the spiders
|
||||
set_rule(world.get_location("Beneath the Fortress - Bridge"),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Dict, List, Set, Tuple, TYPE_CHECKING
|
||||
from BaseClasses import Region, ItemClassification, Item, Location
|
||||
from .locations import location_table
|
||||
from .locations import all_locations
|
||||
from .er_data import Portal, portal_mapping, traversal_requirements, DeadEnd, RegionInfo
|
||||
from .er_rules import set_er_region_rules
|
||||
from Options import PlandoConnection
|
||||
|
@ -53,8 +53,8 @@ def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]:
|
|||
|
||||
set_er_region_rules(world, regions, portal_pairs)
|
||||
|
||||
for location_name, location_id in world.location_name_to_id.items():
|
||||
region = regions[location_table[location_name].er_region]
|
||||
for location_name, location_id in world.player_location_table.items():
|
||||
region = regions[all_locations[location_name].er_region]
|
||||
location = TunicERLocation(world.player, location_name, location_id, region)
|
||||
region.locations.append(location)
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -166,6 +166,7 @@ item_table: Dict[str, TunicItemData] = {
|
|||
"Ladders in Library": TunicItemData(IC.progression, 0, 148, "Ladders"),
|
||||
"Ladders in Lower Quarry": TunicItemData(IC.progression, 0, 149, "Ladders"),
|
||||
"Ladders in Swamp": TunicItemData(IC.progression, 0, 150, "Ladders"),
|
||||
"Grass": TunicItemData(IC.filler, 0, 151),
|
||||
}
|
||||
|
||||
# items to be replaced by fool traps
|
||||
|
@ -214,7 +215,7 @@ combat_items.extend(["Stick", "Sword", "Sword Upgrade", "Magic Wand", "Hero's La
|
|||
|
||||
item_name_to_id: Dict[str, int] = {name: item_base_id + data.item_id_offset for name, data in item_table.items()}
|
||||
|
||||
filler_items: List[str] = [name for name, data in item_table.items() if data.classification == IC.filler]
|
||||
filler_items: List[str] = [name for name, data in item_table.items() if data.classification == IC.filler and name != "Grass"]
|
||||
|
||||
|
||||
def get_item_group(item_name: str) -> str:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Dict, NamedTuple, Set, Optional
|
||||
from typing import Dict, NamedTuple, Set, Optional, List
|
||||
from .grass import grass_location_table
|
||||
|
||||
|
||||
class TunicLocationData(NamedTuple):
|
||||
|
@ -320,7 +321,27 @@ hexagon_locations: Dict[str, str] = {
|
|||
"Blue Questagon": "Rooted Ziggurat Lower - Hexagon Blue",
|
||||
}
|
||||
|
||||
location_name_to_id: Dict[str, int] = {name: location_base_id + index for index, name in enumerate(location_table)}
|
||||
sphere_one: List[str] = [
|
||||
"Overworld - [Central] Chest Across From Well",
|
||||
"Overworld - [Northwest] Chest Near Quarry Gate",
|
||||
"Overworld - [Northwest] Shadowy Corner Chest",
|
||||
"Overworld - [Southwest] Chest Guarded By Turret",
|
||||
"Overworld - [Southwest] South Chest Near Guard",
|
||||
"Overworld - [Southwest] Obscured in Tunnel to Beach",
|
||||
"Overworld - [Northwest] Chest Near Turret",
|
||||
"Overworld - [Northwest] Page By Well",
|
||||
"Overworld - [West] Chest Behind Moss Wall",
|
||||
"Overworld - [Southwest] Key Pickup",
|
||||
"Overworld - [West] Key Pickup",
|
||||
"Overworld - [West] Obscured Behind Windmill",
|
||||
"Overworld - [West] Obscured Near Well",
|
||||
"Overworld - [West] Page On Teleporter"
|
||||
]
|
||||
|
||||
standard_location_name_to_id: Dict[str, int] = {name: location_base_id + index for index, name in enumerate(location_table)}
|
||||
|
||||
all_locations = location_table.copy()
|
||||
all_locations.update(grass_location_table)
|
||||
|
||||
location_name_groups: Dict[str, Set[str]] = {}
|
||||
for loc_name, loc_data in location_table.items():
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Dict, Any
|
||||
from Options import (DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PlandoConnections,
|
||||
PerGameCommonOptions, OptionGroup, Visibility)
|
||||
PerGameCommonOptions, OptionGroup, Visibility, NamedRange)
|
||||
from .er_data import portal_mapping
|
||||
|
||||
|
||||
|
@ -154,6 +154,33 @@ class ShuffleLadders(Toggle):
|
|||
display_name = "Shuffle Ladders"
|
||||
|
||||
|
||||
class GrassRandomizer(Toggle):
|
||||
"""
|
||||
Turns over 6,000 blades of grass and bushes in the game into checks.
|
||||
"""
|
||||
internal_name = "grass_randomizer"
|
||||
display_name = "Grass Randomizer"
|
||||
|
||||
|
||||
class LocalFill(NamedRange):
|
||||
"""
|
||||
Choose the percentage of your filler/trap items that will be kept local or distributed to other TUNIC players with this option enabled.
|
||||
If you have Grass Randomizer enabled, this option must be set to 95% or higher to avoid flooding the item pool. The host can remove this restriction by turning off the limit_grass_rando setting in host.yaml.
|
||||
This option defaults to 95% if you have Grass Randomizer enabled, and to 0% otherwise.
|
||||
This option ignores items placed in your local_items or non_local_items.
|
||||
This option does nothing in single player games.
|
||||
"""
|
||||
internal_name = "local_fill"
|
||||
display_name = "Local Fill Percent"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
special_range_names = {
|
||||
"default": -1
|
||||
}
|
||||
default = -1
|
||||
visibility = Visibility.template | Visibility.complex_ui | Visibility.spoiler
|
||||
|
||||
|
||||
class TunicPlandoConnections(PlandoConnections):
|
||||
"""
|
||||
Generic connection plando. Format is:
|
||||
|
@ -278,12 +305,13 @@ class TunicOptions(PerGameCommonOptions):
|
|||
combat_logic: CombatLogic
|
||||
lanternless: Lanternless
|
||||
maskless: Maskless
|
||||
grass_randomizer: GrassRandomizer
|
||||
local_fill: LocalFill
|
||||
laurels_zips: LaurelsZips
|
||||
ice_grappling: IceGrappling
|
||||
ladder_storage: LadderStorage
|
||||
ladder_storage_without_items: LadderStorageWithoutItems
|
||||
plando_connections: TunicPlandoConnections
|
||||
|
||||
logic_rules: LogicRules
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue