Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport changes to grass rando #20

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 56 additions & 14 deletions worlds/tunic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from typing import Dict, List, Any, Tuple, TypedDict, ClassVar, Union
from logging import warning
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld, LocationProgressType
from .items import item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names
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
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 .er_data import portal_mapping
from .options import TunicOptions, EntranceRando, tunic_option_groups, tunic_option_presets, TunicPlandoConnections
from .grass import grass_location_table, grass_location_name_to_id, excluded_grass_locations
from .grass import grass_location_table, grass_location_name_to_id, grass_location_name_groups
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

Expand All @@ -20,7 +20,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 grass_fill percentages below 95."""

disable_local_spoiler: Union[DisableLocalSpoiler, bool] = False
limit_grass_rando: Union[LimitGrassRando, bool] = True


class TunicWeb(WebWorld):
Expand Down Expand Up @@ -69,9 +73,10 @@ 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.copy()
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]
Expand All @@ -81,6 +86,8 @@ class TunicWorld(World):
er_portal_hints: Dict[int, str]
seed_groups: Dict[str, SeedGroup] = {}

local_filler: List[TunicItem]

def generate_early(self) -> None:
if self.options.plando_connections:
for index, cxn in enumerate(self.options.plando_connections):
Expand Down Expand Up @@ -112,10 +119,14 @@ def generate_early(self) -> None:
self.options.fixed_shop.value = self.options.fixed_shop.option_false
self.options.laurels_location.value = self.options.laurels_location.option_anywhere

self.player_location_table = locations.location_name_to_id.copy()
self.player_location_table = standard_location_name_to_id.copy()

if self.options.grass_randomizer:
self.player_location_table.update(grass_location_name_to_id.copy())
if self.settings.limit_grass_rando and self.options.grass_fill < 95:
raise OptionError(f"TUNIC: Player {self.player_name} has their Grass 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:
Expand Down Expand Up @@ -173,7 +184,6 @@ def create_item(self, name: str, classification: ItemClassification = None) -> T
return TunicItem(name, classification or item_data.classification, self.item_name_to_id[name], self.player)

def create_items(self) -> None:

tunic_items: List[TunicItem] = []
self.slot_data_items = []

Expand Down Expand Up @@ -206,11 +216,6 @@ def create_items(self) -> None:
items_to_create["Grass"] = len(grass_location_table)
tunic_items.append(self.create_item("Glass Cannon", ItemClassification.progression))
items_to_create["Glass Cannon"] = 0
tunic_items.append(self.create_item("Gun", ItemClassification.progression))
items_to_create["Gun"] = 0
for grass_location in excluded_grass_locations:
self.multiworld.get_location(grass_location, self.player).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():
Expand Down Expand Up @@ -286,8 +291,45 @@ def remove_filler(amount: int) -> None:
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.local_filler = []
if self.options.grass_randomizer and self.options.grass_fill > 0:
# skip items marked local or non-local, let fill deal with them in its own way
all_filler = []
non_filler = []
for item in tunic_items:
if item.classification in [ItemClassification.filler, ItemClassification.trap] and item.name not in self.options.local_items and item.name not in self.options.non_local_items:
all_filler.append(item)
else:
non_filler.append(item)
amount_to_local_fill = int(self.options.grass_fill.value * len(all_filler) / 100)
for _ in range(amount_to_local_fill):
self.local_filler.append(all_filler.pop())
tunic_items = all_filler + non_filler

self.multiworld.itempool += tunic_items

@classmethod
def stage_pre_fill(cls, multiworld: MultiWorld) -> None:
tunic_grass_worlds: List[TunicWorld] = [world for world in multiworld.get_game_worlds("TUNIC")
if world.options.grass_randomizer]
tunic_players_with_grass: List[int] = [world.player for world in tunic_grass_worlds]
unfilled_locations = [loc for loc in multiworld.get_unfilled_locations_for_players(
location_names=[], players=tunic_players_with_grass) if loc.progress_type != LocationProgressType.PRIORITY]
grass_fill: List[TunicItem] = []
for world in tunic_grass_worlds:
grass_fill.extend(world.local_filler)

grass_filler_count = len(grass_fill)
# in case you plando or priority a bunch of locations
if len(unfilled_locations) < grass_filler_count:
raise Exception("Not enough locations for TUNIC grass randomizer players to place grass fill into TUNIC "
"locations. This is likely due to excessive priority locations or plando.")

locations_to_grass_fill = multiworld.random.sample(unfilled_locations, grass_filler_count)
for loc in locations_to_grass_fill:
multiworld.push_item(loc, grass_fill.pop(), collect=False)

def create_regions(self) -> None:
self.tunic_portal_pairs = {}
self.er_portal_hints = {}
Expand Down Expand Up @@ -318,7 +360,7 @@ def create_regions(self) -> None:
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)
Expand Down
Loading
Loading