Compare commits

...

10 Commits

Author SHA1 Message Date
NewSoupVi
77e6fa0010 The Witness: Move the Easter Egg Hunt option group lower so that the tooltip isn't cut off 2025-04-01 21:09:59 +02:00
LiquidCat64
d26db6f213 CV64: Fix some unrandomized locations containing unintended items on specific settings (#4728)
* Fix some unrandomized locations on specific settings.

* Remove now-unnecessary comment
2025-04-01 12:37:49 -04:00
Fabian Dill
bb6c753583 FFMQ: fix remote code execution (#4786) 2025-04-01 18:19:07 +02:00
Mysteryem
ca08e4b950 Super Metroid: Replace random module with world random in variaRandomizer (#4429) 2025-04-01 18:14:47 +02:00
Bryce Wilson
5a6b02dbd3 Pokemon Emerald: Fix pre-fill problems (#4686)
Co-authored-by: Mysteryem <Mysteryem@users.noreply.github.com>
2025-04-01 18:12:43 +02:00
jamesbrq
14416b1050 MLSS: Fix issue with door opening earlier than intended (#4737) 2025-04-01 18:10:51 +02:00
Carter Hesterman
da4e6fc532 Civ6: Sanitize player/item values before they go in the XML (#4755) 2025-04-01 18:09:59 +02:00
Justus Lind
57d8b69a6d Muse Dash: Update Song List to Muse Dash Legend. (#4775)
* Add Muse Dash Legend songs.

* Add a new SFX trap
2025-04-01 18:08:09 +02:00
Silvris
c9d8a8661c kvui: Fix hint tab formatting regression (#4778)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2025-04-01 18:06:49 +02:00
Fabian Dill
4a3d23e0e6 Core: update cx-Freeze to 8.0.0 & Worlds: fix packages missing __init__.py (#4773) 2025-04-01 16:29:32 +02:00
45 changed files with 292 additions and 226 deletions

View File

@@ -817,6 +817,12 @@ class HintLayout(BoxLayout):
boxlayout.add_widget(AutocompleteHintInput()) boxlayout.add_widget(AutocompleteHintInput())
self.add_widget(boxlayout) self.add_widget(boxlayout)
def fix_heights(self):
for child in self.children:
fix_func = getattr(child, "fix_heights", None)
if fix_func:
fix_func()
status_names: typing.Dict[HintStatus, str] = { status_names: typing.Dict[HintStatus, str] = {
HintStatus.HINT_FOUND: "Found", HintStatus.HINT_FOUND: "Found",

View File

@@ -19,7 +19,7 @@ from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it # This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
requirement = 'cx-Freeze==7.2.0' requirement = 'cx-Freeze==8.0.0'
try: try:
import pkg_resources import pkg_resources
try: try:

View File

@@ -0,0 +1,14 @@
import unittest
import os
class TestPackages(unittest.TestCase):
def test_packages_have_init(self):
"""Test that all world folders containing .py files also have a __init__.py file,
to indicate full package rather than namespace package."""
import Utils
worlds_path = Utils.local_path("worlds")
for dirpath, dirnames, filenames in os.walk(worlds_path):
with self.subTest(directory=dirpath):
self.assertEqual("__init__.py" in filenames, any(file.endswith(".py") for file in filenames))

View File

@@ -48,6 +48,10 @@ class CivVIContainer(APContainer, metaclass=AutoPatchRegister):
opened_zipfile.writestr(filename, yml) opened_zipfile.writestr(filename, yml)
super().write_contents(opened_zipfile) super().write_contents(opened_zipfile)
def sanitize_value(value: str) -> str:
"""Removes values that can cause issues in XML"""
return value.replace('"', "'").replace('&', 'and')
def get_cost(world: 'CivVIWorld', location: CivVILocationData) -> int: def get_cost(world: 'CivVIWorld', location: CivVILocationData) -> int:
""" """
@@ -63,7 +67,7 @@ def get_formatted_player_name(world: 'CivVIWorld', player: int) -> str:
Returns the name of the player in the world Returns the name of the player in the world
""" """
if player != world.player: if player != world.player:
return f"{world.multiworld.player_name[player]}{apo}s" return sanitize_value(f"{world.multiworld.player_name[player]}{apo}s")
return "Your" return "Your"
@@ -106,7 +110,7 @@ def generate_new_items(world: 'CivVIWorld') -> str:
<Row TechnologyType="TECH_BLOCKER" Name="TECH_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Tech created to prevent players from researching their own tech. If you can read this, then congrats you have reached the end of your tree before beating the game!"/> <Row TechnologyType="TECH_BLOCKER" Name="TECH_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Tech created to prevent players from researching their own tech. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row TechnologyType="{location.name}" ' {"".join([f'{tab}<Row TechnologyType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} ' f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{location.item.name}" ' f'{sanitize_value(location.item.name)}" '
f'EraType="{world.location_table[location.name].era_type}" ' f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" ' f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" ' f'Cost="{get_cost(world, world.location_table[location.name])}" '
@@ -122,7 +126,7 @@ def generate_new_items(world: 'CivVIWorld') -> str:
<Row CivicType="CIVIC_BLOCKER" Name="CIVIC_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Civic created to prevent players from researching their own civics. If you can read this, then congrats you have reached the end of your tree before beating the game!"/> <Row CivicType="CIVIC_BLOCKER" Name="CIVIC_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Civic created to prevent players from researching their own civics. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row CivicType="{location.name}" ' {"".join([f'{tab}<Row CivicType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} ' f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{location.item.name}" ' f'{sanitize_value(location.item.name)}" '
f'EraType="{world.location_table[location.name].era_type}" ' f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" ' f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" ' f'Cost="{get_cost(world, world.location_table[location.name])}" '

View File

View File

View File

@@ -644,6 +644,9 @@ class CV64PatchExtensions(APPatchExtension):
# Replace the PowerUp in the Forest Special1 Bridge 3HB rock with an L jewel if 3HBs aren't randomized # Replace the PowerUp in the Forest Special1 Bridge 3HB rock with an L jewel if 3HBs aren't randomized
if not options["multi_hit_breakables"]: if not options["multi_hit_breakables"]:
rom_data.write_byte(0x10C7A1, 0x03) rom_data.write_byte(0x10C7A1, 0x03)
# Replace the PowerUp in one of the lizard lockers if the lizard locker items aren't randomized.
if not options["lizard_locker_items"]:
rom_data.write_byte(0xBFCA07, 0x03)
# Change the appearance of the Pot-Pourri to that of a larger PowerUp regardless of the above setting, so other # Change the appearance of the Pot-Pourri to that of a larger PowerUp regardless of the above setting, so other
# game PermaUps are distinguishable. # game PermaUps are distinguishable.
rom_data.write_int32s(0xEE558, [0x06005F08, 0x3FB00000, 0xFFFFFF00]) rom_data.write_int32s(0xEE558, [0x06005F08, 0x3FB00000, 0xFFFFFF00])
@@ -714,7 +717,11 @@ class CV64PatchExtensions(APPatchExtension):
rom_data.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab rom_data.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab
rom_data.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab rom_data.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab
rom_data.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier rom_data.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier
rom_data.write_byte(0x10CF37, 0x04) # pointer for CT final room door slab item data
# Change the pointer to the Clock Tower final room 3HB door slab drops to not share its values with those of the
# 3HB slab near Renon at the top of the room.
if options["multi_hit_breakables"]:
rom_data.write_byte(0x10CF37, 0x04)
# Once-per-frame gameplay checks # Once-per-frame gameplay checks
rom_data.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034 rom_data.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034
@@ -1000,6 +1007,7 @@ def write_patch(world: "CV64World", patch: CV64ProcedurePatch, offset_data: Dict
"multi_hit_breakables": world.options.multi_hit_breakables.value, "multi_hit_breakables": world.options.multi_hit_breakables.value,
"drop_previous_sub_weapon": world.options.drop_previous_sub_weapon.value, "drop_previous_sub_weapon": world.options.drop_previous_sub_weapon.value,
"countdown": world.options.countdown.value, "countdown": world.options.countdown.value,
"lizard_locker_items": world.options.lizard_locker_items.value,
"shopsanity": world.options.shopsanity.value, "shopsanity": world.options.shopsanity.value,
"panther_dash": world.options.panther_dash.value, "panther_dash": world.options.panther_dash.value,
"big_toss": world.options.big_toss.value, "big_toss": world.options.big_toss.value,

View File

View File

@@ -3,7 +3,6 @@ import settings
import base64 import base64
import threading import threading
import requests import requests
import yaml
from worlds.AutoWorld import World, WebWorld from worlds.AutoWorld import World, WebWorld
from BaseClasses import Tutorial from BaseClasses import Tutorial
from .Regions import create_regions, location_table, set_rules, stage_set_rules, rooms, non_dead_end_crest_rooms,\ from .Regions import create_regions, location_table, set_rules, stage_set_rules, rooms, non_dead_end_crest_rooms,\
@@ -134,7 +133,7 @@ class FFMQWorld(World):
errors.append([api_url, err]) errors.append([api_url, err])
else: else:
if response.ok: if response.ok:
world.rooms = rooms_data[query] = yaml.load(response.text, yaml.Loader) world.rooms = rooms_data[query] = Utils.parse_yaml(response.text)
break break
else: else:
api_urls.remove(api_url) api_urls.remove(api_url)

View File

View File

View File

View File

View File

View File

View File

Binary file not shown.

View File

View File

@@ -52,11 +52,13 @@ class MuseDashCollections:
"Nyaa SFX Trap": STARTING_CODE + 8, "Nyaa SFX Trap": STARTING_CODE + 8,
"Error SFX Trap": STARTING_CODE + 9, "Error SFX Trap": STARTING_CODE + 9,
"Focus Line Trap": STARTING_CODE + 10, "Focus Line Trap": STARTING_CODE + 10,
"Beefcake SFX Trap": STARTING_CODE + 11,
} }
sfx_trap_items: List[str] = [ sfx_trap_items: List[str] = [
"Nyaa SFX Trap", "Nyaa SFX Trap",
"Error SFX Trap", "Error SFX Trap",
"Beefcake SFX Trap",
] ]
filler_items: Dict[str, int] = { filler_items: Dict[str, int] = {

View File

@@ -627,4 +627,10 @@ SONG_DATA: Dict[str, SongData] = {
"Sharp Bubbles": SongData(2900751, "83-3", "Cosmic Radio 2024", True, 7, 9, 11), "Sharp Bubbles": SongData(2900751, "83-3", "Cosmic Radio 2024", True, 7, 9, 11),
"Replay": SongData(2900752, "83-4", "Cosmic Radio 2024", True, 5, 7, 9), "Replay": SongData(2900752, "83-4", "Cosmic Radio 2024", True, 5, 7, 9),
"Cosmic Dusty Girl": SongData(2900753, "83-5", "Cosmic Radio 2024", True, 5, 7, 9), "Cosmic Dusty Girl": SongData(2900753, "83-5", "Cosmic Radio 2024", True, 5, 7, 9),
"Meow Rock feat. Chun Ge, Yuan Shen": SongData(2900754, "84-0", "Muse Dash Legend", True, None, None, None),
"Even if you make an old radio song with AI": SongData(2900755, "84-1", "Muse Dash Legend", False, 3, 6, 8),
"Unusual Sketchbook": SongData(2900756, "84-2", "Muse Dash Legend", True, 6, 8, 11),
"TransientTears": SongData(2900757, "84-3", "Muse Dash Legend", True, 6, 8, 11),
"SHOOTING*STAR": SongData(2900758, "84-4", "Muse Dash Legend", False, 5, 7, 9),
"But the Blue Bird is Already Dead": SongData(2900759, "84-5", "Muse Dash Legend", False, 6, 8, 10),
} }

View File

View File

@@ -14,6 +14,9 @@ _not_ used for logical access (the seed will never require you to catch somethin
- Now excludes the location "Navel Rock Top - Hidden Item Sacred Ash" if your goal is Champion and you didn't randomize - Now excludes the location "Navel Rock Top - Hidden Item Sacred Ash" if your goal is Champion and you didn't randomize
event tickets. event tickets.
- Fixed handling of shuffle option for badges/HMs in the case that the player sets those items to nonlocal or uses
plando to put an item in one of those locations, or in the case that fill gets itself stuck on these items and has to
retry.
# 2.3.0 # 2.3.0

View File

@@ -8,7 +8,7 @@ import os
import pkgutil import pkgutil
from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union
from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType from BaseClasses import CollectionState, ItemClassification, MultiWorld, Tutorial, LocationProgressType
from Fill import FillError, fill_restrictive from Fill import FillError, fill_restrictive
from Options import OptionError, Toggle from Options import OptionError, Toggle
import settings import settings
@@ -100,6 +100,7 @@ class PokemonEmeraldWorld(World):
required_client_version = (0, 4, 6) required_client_version = (0, 4, 6)
item_pool: List[PokemonEmeraldItem]
badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
free_fly_location_id: int free_fly_location_id: int
@@ -185,7 +186,7 @@ class PokemonEmeraldWorld(World):
# In race mode we don't patch any item location information into the ROM # In race mode we don't patch any item location information into the ROM
if self.multiworld.is_race and not self.options.remote_items: if self.multiworld.is_race and not self.options.remote_items:
logging.warning("Pokemon Emerald: Forcing Player %s (%s) to use remote items due to race mode.", logging.warning("Pokemon Emerald: Forcing player %s (%s) to use remote items due to race mode.",
self.player, self.player_name) self.player, self.player_name)
self.options.remote_items.value = Toggle.option_true self.options.remote_items.value = Toggle.option_true
@@ -197,7 +198,7 @@ class PokemonEmeraldWorld(World):
# Prevent setting the number of required legendaries higher than the number of enabled legendaries # Prevent setting the number of required legendaries higher than the number of enabled legendaries
if self.options.legendary_hunt_count.value > len(self.options.allowed_legendary_hunt_encounters.value): if self.options.legendary_hunt_count.value > len(self.options.allowed_legendary_hunt_encounters.value):
logging.warning("Pokemon Emerald: Legendary hunt count for Player %s (%s) higher than number of allowed " logging.warning("Pokemon Emerald: Legendary hunt count for player %s (%s) higher than number of allowed "
"legendary encounters. Reducing to number of allowed encounters.", self.player, "legendary encounters. Reducing to number of allowed encounters.", self.player,
self.player_name) self.player_name)
self.options.legendary_hunt_count.value = len(self.options.allowed_legendary_hunt_encounters.value) self.options.legendary_hunt_count.value = len(self.options.allowed_legendary_hunt_encounters.value)
@@ -234,10 +235,17 @@ class PokemonEmeraldWorld(World):
max_norman_count = 4 max_norman_count = 4
if self.options.norman_count.value > max_norman_count: if self.options.norman_count.value > max_norman_count:
logging.warning("Pokemon Emerald: Norman requirements for Player %s (%s) are unsafe in combination with " logging.warning("Pokemon Emerald: Norman requirements for player %s (%s) are unsafe in combination with "
"other settings. Reducing to 4.", self.player, self.player_name) "other settings. Reducing to 4.", self.player, self.player_name)
self.options.norman_count.value = max_norman_count self.options.norman_count.value = max_norman_count
# Shuffled badges/hms will always be placed locally, so add them to local_items
if self.options.badges == RandomizeBadges.option_shuffle:
self.options.local_items.value.update(self.item_name_groups["Badge"])
if self.options.hms == RandomizeHms.option_shuffle:
self.options.local_items.value.update(self.item_name_groups["HM"])
def create_regions(self) -> None: def create_regions(self) -> None:
from .regions import create_regions from .regions import create_regions
all_regions = create_regions(self) all_regions = create_regions(self)
@@ -377,12 +385,11 @@ class PokemonEmeraldWorld(World):
item_locations = [location for location in item_locations if emerald_data.locations[location.key].category not in filter_categories] item_locations = [location for location in item_locations if emerald_data.locations[location.key].category not in filter_categories]
default_itempool = [self.create_item_by_code(location.default_item_code) for location in item_locations] default_itempool = [self.create_item_by_code(location.default_item_code) for location in item_locations]
# Take the itempool as-is
if self.options.item_pool_type == ItemPoolType.option_shuffled: if self.options.item_pool_type == ItemPoolType.option_shuffled:
self.multiworld.itempool += default_itempool # Take the itempool as-is
self.item_pool = default_itempool
# Recreate the itempool from random items
elif self.options.item_pool_type in (ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced): elif self.options.item_pool_type in (ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced):
# Recreate the itempool from random items
item_categories = ["Ball", "Healing", "Rare Candy", "Vitamin", "Evolution Stone", item_categories = ["Ball", "Healing", "Rare Candy", "Vitamin", "Evolution Stone",
"Money", "TM", "Held", "Misc", "Berry"] "Money", "TM", "Held", "Misc", "Berry"]
@@ -392,6 +399,7 @@ class PokemonEmeraldWorld(World):
if not item.advancement: if not item.advancement:
item_category_counter.update([tag for tag in item.tags if tag in item_categories]) item_category_counter.update([tag for tag in item.tags if tag in item_categories])
self.item_pool = []
item_category_weights = [item_category_counter.get(category) for category in item_categories] item_category_weights = [item_category_counter.get(category) for category in item_categories]
item_category_weights = [weight if weight is not None else 0 for weight in item_category_weights] item_category_weights = [weight if weight is not None else 0 for weight in item_category_weights]
@@ -436,19 +444,10 @@ class PokemonEmeraldWorld(World):
item_code = self.random.choice(fill_item_candidates_by_category[category]) item_code = self.random.choice(fill_item_candidates_by_category[category])
item = self.create_item_by_code(item_code) item = self.create_item_by_code(item_code)
self.multiworld.itempool.append(item) self.item_pool.append(item)
def set_rules(self) -> None: self.multiworld.itempool += self.item_pool
from .rules import set_rules
set_rules(self)
def generate_basic(self) -> None:
# Create auth
# self.auth = self.random.randbytes(16) # Requires >=3.9
self.auth = self.random.getrandbits(16 * 8).to_bytes(16, "little")
randomize_types(self)
randomize_wild_encounters(self)
set_free_fly(self) set_free_fly(self)
set_legendary_cave_entrances(self) set_legendary_cave_entrances(self)
@@ -475,9 +474,20 @@ class PokemonEmeraldWorld(World):
if not self.options.key_items: if not self.options.key_items:
convert_unrandomized_items_to_events(LocationCategory.KEY) convert_unrandomized_items_to_events(LocationCategory.KEY)
def pre_fill(self) -> None: def set_rules(self):
# Badges and HMs that are set to shuffle need to be placed at from .rules import set_rules
# their own subset of locations set_rules(self)
def connect_entrances(self):
randomize_wild_encounters(self)
self.shuffle_badges_hms()
# For entrance randomization, disconnect entrances here, randomize map, then
# undo badge/HM placement and re-shuffle them in the new map.
def shuffle_badges_hms(self) -> None:
my_progression_items = [item for item in self.item_pool if item.advancement]
my_locations = list(self.get_locations())
if self.options.badges == RandomizeBadges.option_shuffle: if self.options.badges == RandomizeBadges.option_shuffle:
badge_locations: List[PokemonEmeraldLocation] badge_locations: List[PokemonEmeraldLocation]
badge_items: List[PokemonEmeraldItem] badge_items: List[PokemonEmeraldItem]
@@ -502,41 +512,20 @@ class PokemonEmeraldWorld(World):
badge_priority["Knuckle Badge"] = 0 badge_priority["Knuckle Badge"] = 0
badge_items.sort(key=lambda item: badge_priority.get(item.name, 0)) badge_items.sort(key=lambda item: badge_priority.get(item.name, 0))
# Un-exclude badge locations, since we need to put progression items on them # Build state
for location in badge_locations: state = CollectionState(self.multiworld)
location.progress_type = LocationProgressType.DEFAULT \ for item in my_progression_items:
if location.progress_type == LocationProgressType.EXCLUDED \ state.collect(item, True)
else location.progress_type # If HM shuffle is on, HMs are neither placed in locations nor in
# the item pool, so we also need to collect them.
collection_state = self.multiworld.get_all_state(False)
# If HM shuffle is on, HMs are not placed and not in the pool, so
# `get_all_state` did not contain them. Collect them manually for
# this fill. We know that they will be included in all state after
# this stage.
if self.hm_shuffle_info is not None: if self.hm_shuffle_info is not None:
for _, item in self.hm_shuffle_info: for _, item in self.hm_shuffle_info:
collection_state.collect(item) state.collect(item, True)
state.sweep_for_advancements(my_locations)
# In specific very constrained conditions, fill_restrictive may run # Shuffle badges
# out of swaps before it finds a valid solution if it gets unlucky. self.fill_subset_with_retries(badge_items, badge_locations, state)
# This is a band-aid until fill/swap can reliably find those solutions.
attempts_remaining = 2
while attempts_remaining > 0:
attempts_remaining -= 1
self.random.shuffle(badge_locations)
try:
fill_restrictive(self.multiworld, collection_state, badge_locations, badge_items,
single_player_placement=True, lock=True, allow_excluded=True)
break
except FillError as exc:
if attempts_remaining == 0:
raise exc
logging.debug(f"Failed to shuffle badges for player {self.player}. Retrying.")
continue
# Badges are guaranteed to be either placed or in the multiworld's itempool now
if self.options.hms == RandomizeHms.option_shuffle: if self.options.hms == RandomizeHms.option_shuffle:
hm_locations: List[PokemonEmeraldLocation] hm_locations: List[PokemonEmeraldLocation]
hm_items: List[PokemonEmeraldItem] hm_items: List[PokemonEmeraldItem]
@@ -559,33 +548,56 @@ class PokemonEmeraldWorld(World):
if self.options.badges == RandomizeBadges.option_vanilla and \ if self.options.badges == RandomizeBadges.option_vanilla and \
self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave):
hm_priority["HM05 Flash"] = 0 hm_priority["HM05 Flash"] = 0
hm_items.sort(key=lambda item: hm_priority.get(item.name, 0)) hm_items.sort(key=lambda item: hm_priority.get(item.name, 0), reverse=True)
# Un-exclude HM locations, since we need to put progression items on them # Build state
for location in hm_locations: # Badges are either in the item pool, or already placed and collected during sweep
location.progress_type = LocationProgressType.DEFAULT \ state = CollectionState(self.multiworld)
if location.progress_type == LocationProgressType.EXCLUDED \ for item in my_progression_items:
else location.progress_type state.collect(item, True)
state.sweep_for_advancements(my_locations)
collection_state = self.multiworld.get_all_state(False) # Shuffle HMs
self.fill_subset_with_retries(hm_items, hm_locations, state)
# In specific very constrained conditions, fill_restrictive may run def fill_subset_with_retries(self, items: list[PokemonEmeraldItem], locations: list[PokemonEmeraldLocation], state: CollectionState):
# out of swaps before it finds a valid solution if it gets unlucky. # Un-exclude locations, since we need to put progression items on them
# This is a band-aid until fill/swap can reliably find those solutions. for location in locations:
attempts_remaining = 2 location.progress_type = LocationProgressType.DEFAULT \
while attempts_remaining > 0: if location.progress_type == LocationProgressType.EXCLUDED \
attempts_remaining -= 1 else location.progress_type
self.random.shuffle(hm_locations)
try:
fill_restrictive(self.multiworld, collection_state, hm_locations, hm_items,
single_player_placement=True, lock=True, allow_excluded=True)
break
except FillError as exc:
if attempts_remaining == 0:
raise exc
logging.debug(f"Failed to shuffle HMs for player {self.player}. Retrying.") # In specific very constrained conditions, `fill_restrictive` may run
continue # out of swaps before it finds a valid solution if it gets unlucky.
attempts_remaining = 2
while attempts_remaining > 0:
attempts_remaining -= 1
locations_copy = locations.copy()
items_copy = items.copy()
self.random.shuffle(locations_copy)
try:
fill_restrictive(self.multiworld, state, locations_copy, items_copy, single_player_placement=True,
lock=True)
break
except FillError as exc:
if attempts_remaining <= 0:
raise exc
# Undo partial item placement
for location in locations:
location.locked = False
if location.item is not None:
location.item.location = None
location.item = None
logging.debug(f"Failed to shuffle items for player {self.player} ({self.player_name}). Retrying.")
continue
def generate_basic(self) -> None:
# Create auth
self.auth = self.random.randbytes(16)
randomize_types(self)
def generate_output(self, output_directory: str) -> None: def generate_output(self, output_directory: str) -> None:
self.modified_trainers = copy.deepcopy(emerald_data.trainers) self.modified_trainers = copy.deepcopy(emerald_data.trainers)

View File

@@ -124,7 +124,7 @@ class SMWorld(World):
Logic.factory('vanilla') Logic.factory('vanilla')
dummy_rom_file = Utils.user_path(SMSettings.RomFile.copy_to) # actual rom set in generate_output dummy_rom_file = Utils.user_path(SMSettings.RomFile.copy_to) # actual rom set in generate_output
self.variaRando = VariaRandomizer(self.options, dummy_rom_file, self.player) self.variaRando = VariaRandomizer(self.options, dummy_rom_file, self.player, self.multiworld.seed, self.random)
self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty) self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty)
# keeps Nothing items local so no player will ever pickup Nothing # keeps Nothing items local so no player will ever pickup Nothing
@@ -314,11 +314,11 @@ class SMWorld(World):
raise KeyError(f"Item {name} for {self.player_name} is invalid.") raise KeyError(f"Item {name} for {self.player_name} is invalid.")
def get_filler_item_name(self) -> str: def get_filler_item_name(self) -> str:
if self.multiworld.random.randint(0, 100) < self.options.minor_qty.value: if self.random.randint(0, 100) < self.options.minor_qty.value:
power_bombs = self.options.power_bomb_qty.value power_bombs = self.options.power_bomb_qty.value
missiles = self.options.missile_qty.value missiles = self.options.missile_qty.value
super_missiles = self.options.super_qty.value super_missiles = self.options.super_qty.value
roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles) roll = self.random.randint(1, power_bombs + missiles + super_missiles)
if roll <= power_bombs: if roll <= power_bombs:
return "Power Bomb" return "Power Bomb"
elif roll <= power_bombs + missiles: elif roll <= power_bombs + missiles:
@@ -340,8 +340,8 @@ class SMWorld(World):
else: else:
nonChozoLoc.append(loc) nonChozoLoc.append(loc)
self.multiworld.random.shuffle(nonChozoLoc) self.random.shuffle(nonChozoLoc)
self.multiworld.random.shuffle(chozoLoc) self.random.shuffle(chozoLoc)
missingCount = len(self.NothingPool) - len(nonChozoLoc) missingCount = len(self.NothingPool) - len(nonChozoLoc)
locations = nonChozoLoc locations = nonChozoLoc
if (missingCount > 0): if (missingCount > 0):

View File

@@ -1,5 +1,4 @@
import copy import copy
import random
from ..logic.logic import Logic from ..logic.logic import Logic
from ..utils.parameters import Knows from ..utils.parameters import Knows
from ..graph.location import locationsDict from ..graph.location import locationsDict
@@ -136,7 +135,8 @@ class GraphUtils:
refused[apName] = cause refused[apName] = cause
return ret, refused return ret, refused
def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs): @staticmethod
def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs, random):
locs = locationsDict locs = locationsDict
preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)] preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)]
possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs] possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs]
@@ -160,7 +160,8 @@ class GraphUtils:
ap = getAccessPoint(startApName) ap = getAccessPoint(startApName)
return ap.Start['patches'] if 'patches' in ap.Start else [] return ap.Start['patches'] if 'patches' in ap.Start else []
def createBossesTransitions(): @staticmethod
def createBossesTransitions(random):
transitions = vanillaBossesTransitions transitions = vanillaBossesTransitions
def isVanilla(): def isVanilla():
for t in vanillaBossesTransitions: for t in vanillaBossesTransitions:
@@ -180,13 +181,15 @@ class GraphUtils:
transitions.append((src,dst)) transitions.append((src,dst))
return transitions return transitions
def createAreaTransitions(lightAreaRando=False): @staticmethod
def createAreaTransitions(lightAreaRando=False, *, random):
if lightAreaRando: if lightAreaRando:
return GraphUtils.createLightAreaTransitions() return GraphUtils.createLightAreaTransitions(random=random)
else: else:
return GraphUtils.createRegularAreaTransitions() return GraphUtils.createRegularAreaTransitions(random=random)
def createRegularAreaTransitions(apList=None, apPred=None): @staticmethod
def createRegularAreaTransitions(apList=None, apPred=None, *, random):
if apList is None: if apList is None:
apList = Logic.accessPoints apList = Logic.accessPoints
if apPred is None: if apPred is None:
@@ -239,7 +242,8 @@ class GraphUtils:
transitions.append((ap.Name, ap.Name)) transitions.append((ap.Name, ap.Name))
# crateria can be forced in corner cases # crateria can be forced in corner cases
def createMinimizerTransitions(startApName, locLimit, forcedAreas=None): @staticmethod
def createMinimizerTransitions(startApName, locLimit, forcedAreas=None, *, random):
if forcedAreas is None: if forcedAreas is None:
forcedAreas = [] forcedAreas = []
if startApName == 'Ceres': if startApName == 'Ceres':
@@ -316,7 +320,8 @@ class GraphUtils:
GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas)) GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas))
return transitions return transitions
def createLightAreaTransitions(): @staticmethod
def createLightAreaTransitions(random):
# group APs by area # group APs by area
aps = {} aps = {}
totalCount = 0 totalCount = 0
@@ -407,7 +412,8 @@ class GraphUtils:
return rooms return rooms
def escapeAnimalsTransitions(graph, possibleTargets, firstEscape): @staticmethod
def escapeAnimalsTransitions(graph, possibleTargets, firstEscape, random):
n = len(possibleTargets) n = len(possibleTargets)
assert (n < 4 and firstEscape is not None) or (n <= 4 and firstEscape is None), "Invalid possibleTargets list: " + str(possibleTargets) assert (n < 4 and firstEscape is not None) or (n <= 4 and firstEscape is None), "Invalid possibleTargets list: " + str(possibleTargets)
GraphUtils.log.debug("escapeAnimalsTransitions. possibleTargets="+str(possibleTargets)+", firstEscape="+str(firstEscape)) GraphUtils.log.debug("escapeAnimalsTransitions. possibleTargets="+str(possibleTargets)+", firstEscape="+str(firstEscape))

View File

@@ -1,4 +1,3 @@
import random
from ..utils import log from ..utils import log
from ..utils.utils import getRangeDict, chooseFromRange from ..utils.utils import getRangeDict, chooseFromRange
from ..rando.ItemLocContainer import ItemLocation from ..rando.ItemLocContainer import ItemLocation
@@ -23,8 +22,9 @@ class Choice(object):
# simple random choice, that chooses an item first, then a locatio to put it in # simple random choice, that chooses an item first, then a locatio to put it in
class ItemThenLocChoice(Choice): class ItemThenLocChoice(Choice):
def __init__(self, restrictions): def __init__(self, restrictions, random):
super(ItemThenLocChoice, self).__init__(restrictions) super(ItemThenLocChoice, self).__init__(restrictions)
self.random = random
def chooseItemLoc(self, itemLocDict, isProg): def chooseItemLoc(self, itemLocDict, isProg):
itemList = self.getItemList(itemLocDict) itemList = self.getItemList(itemLocDict)
@@ -49,7 +49,7 @@ class ItemThenLocChoice(Choice):
return self.chooseItemRandom(itemList) return self.chooseItemRandom(itemList)
def chooseItemRandom(self, itemList): def chooseItemRandom(self, itemList):
return random.choice(itemList) return self.random.choice(itemList)
def chooseLocation(self, locList, item, isProg): def chooseLocation(self, locList, item, isProg):
if len(locList) == 0: if len(locList) == 0:
@@ -63,12 +63,12 @@ class ItemThenLocChoice(Choice):
return self.chooseLocationRandom(locList) return self.chooseLocationRandom(locList)
def chooseLocationRandom(self, locList): def chooseLocationRandom(self, locList):
return random.choice(locList) return self.random.choice(locList)
# Choice specialization for prog speed based filler # Choice specialization for prog speed based filler
class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
def __init__(self, restrictions, progSpeedParams, distanceProp, services): def __init__(self, restrictions, progSpeedParams, distanceProp, services, random):
super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions) super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions, random)
self.progSpeedParams = progSpeedParams self.progSpeedParams = progSpeedParams
self.distanceProp = distanceProp self.distanceProp = distanceProp
self.services = services self.services = services
@@ -104,7 +104,7 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
if self.restrictions.isLateMorph() and canRollback and len(itemLocDict) == 1: if self.restrictions.isLateMorph() and canRollback and len(itemLocDict) == 1:
item, locList = list(itemLocDict.items())[0] item, locList = list(itemLocDict.items())[0]
if item.Type == 'Morph': if item.Type == 'Morph':
morphLocs = self.restrictions.lateMorphCheck(container, locList) morphLocs = self.restrictions.lateMorphCheck(container, locList, self.random)
if morphLocs is not None: if morphLocs is not None:
itemLocDict[item] = morphLocs itemLocDict[item] = morphLocs
else: else:
@@ -115,7 +115,7 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
assert len(locs) == 1 and locs[0].Name == item.Name assert len(locs) == 1 and locs[0].Name == item.Name
return ItemLocation(item, locs[0]) return ItemLocation(item, locs[0])
# late doors check for random door colors # late doors check for random door colors
if self.restrictions.isLateDoors() and random.random() < self.lateDoorsProb: if self.restrictions.isLateDoors() and self.random.random() < self.lateDoorsProb:
self.processLateDoors(itemLocDict, ap, container) self.processLateDoors(itemLocDict, ap, container)
self.progressionItemLocs = progressionItemLocs self.progressionItemLocs = progressionItemLocs
self.ap = ap self.ap = ap
@@ -145,14 +145,14 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
def chooseLocationProg(self, locs, item): def chooseLocationProg(self, locs, item):
locs = self.getLocsSpreadProgression(locs) locs = self.getLocsSpreadProgression(locs)
random.shuffle(locs) self.random.shuffle(locs)
ret = self.getChooseFunc(self.chooseLocRanges, self.chooseLocFuncs)(locs) ret = self.getChooseFunc(self.chooseLocRanges, self.chooseLocFuncs)(locs)
self.log.debug('chooseLocationProg. ret='+ret.Name) self.log.debug('chooseLocationProg. ret='+ret.Name)
return ret return ret
# get choose function from a weighted dict # get choose function from a weighted dict
def getChooseFunc(self, rangeDict, funcDict): def getChooseFunc(self, rangeDict, funcDict):
v = chooseFromRange(rangeDict) v = chooseFromRange(rangeDict, self.random)
return funcDict[v] return funcDict[v]
@@ -209,6 +209,6 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
for i in range(len(availableLocations)): for i in range(len(availableLocations)):
loc = availableLocations[i] loc = availableLocations[i]
d = distances[i] d = distances[i]
if d == maxDist or random.random() >= self.spreadProb: if d == maxDist or self.random.random() >= self.spreadProb:
locs.append(loc) locs.append(loc)
return locs return locs

View File

@@ -1,5 +1,5 @@
import copy, time, random import copy, time
from ..utils import log from ..utils import log
from ..logic.cache import RequestCache from ..logic.cache import RequestCache
from ..rando.RandoServices import RandoServices from ..rando.RandoServices import RandoServices
@@ -15,11 +15,11 @@ from ..graph.graph_utils import GraphUtils
# item pool is not empty). # item pool is not empty).
# entry point is generateItems # entry point is generateItems
class Filler(object): class Filler(object):
def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity): def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity, *, random):
self.startAP = startAP self.startAP = startAP
self.cache = RequestCache() self.cache = RequestCache()
self.graph = graph self.graph = graph
self.services = RandoServices(graph, restrictions, self.cache) self.services = RandoServices(graph, restrictions, self.cache, random=random)
self.restrictions = restrictions self.restrictions = restrictions
self.settings = restrictions.settings self.settings = restrictions.settings
self.endDate = endDate self.endDate = endDate
@@ -108,9 +108,9 @@ class Filler(object):
# very simple front fill algorithm with no rollback and no "softlock checks" (== dessy algorithm) # very simple front fill algorithm with no rollback and no "softlock checks" (== dessy algorithm)
class FrontFiller(Filler): class FrontFiller(Filler):
def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity): def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity, *, random):
super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate) super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate, random=random)
self.choice = ItemThenLocChoice(restrictions) self.choice = ItemThenLocChoice(restrictions, random)
self.stdStart = GraphUtils.isStandardStart(self.startAP) self.stdStart = GraphUtils.isStandardStart(self.startAP)
def isEarlyGame(self): def isEarlyGame(self):

View File

@@ -1,5 +1,5 @@
import random, copy import copy
from ..utils import log from ..utils import log
from ..graph.graph_utils import GraphUtils, vanillaTransitions, vanillaBossesTransitions, escapeSource, escapeTargets, graphAreas, getAccessPoint from ..graph.graph_utils import GraphUtils, vanillaTransitions, vanillaBossesTransitions, escapeSource, escapeTargets, graphAreas, getAccessPoint
from ..logic.logic import Logic from ..logic.logic import Logic
@@ -11,13 +11,14 @@ from collections import defaultdict
# creates graph and handles randomized escape # creates graph and handles randomized escape
class GraphBuilder(object): class GraphBuilder(object):
def __init__(self, graphSettings): def __init__(self, graphSettings, random):
self.graphSettings = graphSettings self.graphSettings = graphSettings
self.areaRando = graphSettings.areaRando self.areaRando = graphSettings.areaRando
self.bossRando = graphSettings.bossRando self.bossRando = graphSettings.bossRando
self.escapeRando = graphSettings.escapeRando self.escapeRando = graphSettings.escapeRando
self.minimizerN = graphSettings.minimizerN self.minimizerN = graphSettings.minimizerN
self.log = log.get('GraphBuilder') self.log = log.get('GraphBuilder')
self.random = random
# builds everything but escape transitions # builds everything but escape transitions
def createGraph(self, maxDiff): def createGraph(self, maxDiff):
@@ -48,18 +49,18 @@ class GraphBuilder(object):
objForced = forcedAreas.intersection(escAreas) objForced = forcedAreas.intersection(escAreas)
escAreasList = sorted(list(escAreas)) escAreasList = sorted(list(escAreas))
while len(objForced) < n and len(escAreasList) > 0: while len(objForced) < n and len(escAreasList) > 0:
objForced.add(escAreasList.pop(random.randint(0, len(escAreasList)-1))) objForced.add(escAreasList.pop(self.random.randint(0, len(escAreasList)-1)))
forcedAreas = forcedAreas.union(objForced) forcedAreas = forcedAreas.union(objForced)
transitions = GraphUtils.createMinimizerTransitions(self.graphSettings.startAP, self.minimizerN, sorted(list(forcedAreas))) transitions = GraphUtils.createMinimizerTransitions(self.graphSettings.startAP, self.minimizerN, sorted(list(forcedAreas)), random=self.random)
else: else:
if not self.bossRando: if not self.bossRando:
transitions += vanillaBossesTransitions transitions += vanillaBossesTransitions
else: else:
transitions += GraphUtils.createBossesTransitions() transitions += GraphUtils.createBossesTransitions(self.random)
if not self.areaRando: if not self.areaRando:
transitions += vanillaTransitions transitions += vanillaTransitions
else: else:
transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando) transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando, random=self.random)
ret = AccessGraph(Logic.accessPoints, transitions, self.graphSettings.dotFile) ret = AccessGraph(Logic.accessPoints, transitions, self.graphSettings.dotFile)
Objectives.objDict[self.graphSettings.player].setGraph(ret, maxDiff) Objectives.objDict[self.graphSettings.player].setGraph(ret, maxDiff)
return ret return ret
@@ -100,7 +101,7 @@ class GraphBuilder(object):
self.escapeTimer(graph, paths, self.areaRando or escapeTrigger is not None) self.escapeTimer(graph, paths, self.areaRando or escapeTrigger is not None)
self.log.debug("escapeGraph: ({}, {}) timer: {}".format(escapeSource, dst, graph.EscapeAttributes['Timer'])) self.log.debug("escapeGraph: ({}, {}) timer: {}".format(escapeSource, dst, graph.EscapeAttributes['Timer']))
# animals # animals
GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst) GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst, self.random)
return True return True
def _getTargets(self, sm, graph, maxDiff): def _getTargets(self, sm, graph, maxDiff):
@@ -110,7 +111,7 @@ class GraphBuilder(object):
if len(possibleTargets) == 0: if len(possibleTargets) == 0:
self.log.debug("Can't randomize escape, fallback to vanilla") self.log.debug("Can't randomize escape, fallback to vanilla")
possibleTargets.append('Climb Bottom Left') possibleTargets.append('Climb Bottom Left')
random.shuffle(possibleTargets) self.random.shuffle(possibleTargets)
return possibleTargets return possibleTargets
def getPossibleEscapeTargets(self, emptyContainer, graph, maxDiff): def getPossibleEscapeTargets(self, emptyContainer, graph, maxDiff):

View File

@@ -1,6 +1,6 @@
from ..utils.utils import randGaussBounds, getRangeDict, chooseFromRange from ..utils.utils import randGaussBounds, getRangeDict, chooseFromRange
from ..utils import log from ..utils import log
import logging, copy, random import logging, copy
class Item: class Item:
__slots__ = ( 'Category', 'Class', 'Name', 'Code', 'Type', 'BeamBits', 'ItemBits', 'Id' ) __slots__ = ( 'Category', 'Class', 'Name', 'Code', 'Type', 'BeamBits', 'ItemBits', 'Id' )
@@ -335,7 +335,7 @@ class ItemManager:
itemCode = item.Code + modifier itemCode = item.Code + modifier
return itemCode return itemCode
def __init__(self, majorsSplit, qty, sm, nLocs, bossesItems, maxDiff): def __init__(self, majorsSplit, qty, sm, nLocs, bossesItems, maxDiff, random):
self.qty = qty self.qty = qty
self.sm = sm self.sm = sm
self.majorsSplit = majorsSplit self.majorsSplit = majorsSplit
@@ -344,6 +344,7 @@ class ItemManager:
self.maxDiff = maxDiff self.maxDiff = maxDiff
self.majorClass = 'Chozo' if majorsSplit == 'Chozo' else 'Major' self.majorClass = 'Chozo' if majorsSplit == 'Chozo' else 'Major'
self.itemPool = [] self.itemPool = []
self.random = random
def newItemPool(self, addBosses=True): def newItemPool(self, addBosses=True):
self.itemPool = [] self.itemPool = []
@@ -386,7 +387,7 @@ class ItemManager:
return ItemManager.Items[itemType].withClass(itemClass) return ItemManager.Items[itemType].withClass(itemClass)
def createItemPool(self, exclude=None): def createItemPool(self, exclude=None):
itemPoolGenerator = ItemPoolGenerator.factory(self.majorsSplit, self, self.qty, self.sm, exclude, self.nLocs, self.maxDiff) itemPoolGenerator = ItemPoolGenerator.factory(self.majorsSplit, self, self.qty, self.sm, exclude, self.nLocs, self.maxDiff, self.random)
self.itemPool = itemPoolGenerator.getItemPool() self.itemPool = itemPoolGenerator.getItemPool()
@staticmethod @staticmethod
@@ -402,20 +403,20 @@ class ItemPoolGenerator(object):
nbBosses = 9 nbBosses = 9
@staticmethod @staticmethod
def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff): def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff, random):
if majorsSplit == 'Chozo': if majorsSplit == 'Chozo':
return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff) return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff, random)
elif majorsSplit == 'Plando': elif majorsSplit == 'Plando':
return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff) return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff, random)
elif nLocs == ItemPoolGenerator.maxLocs: elif nLocs == ItemPoolGenerator.maxLocs:
if majorsSplit == "Scavenger": if majorsSplit == "Scavenger":
return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff) return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff, random)
else: else:
return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff) return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff, random)
else: else:
return ItemPoolGeneratorMinimizer(itemManager, qty, sm, nLocs, maxDiff) return ItemPoolGeneratorMinimizer(itemManager, qty, sm, nLocs, maxDiff, random)
def __init__(self, itemManager, qty, sm, maxDiff): def __init__(self, itemManager, qty, sm, maxDiff, random):
self.itemManager = itemManager self.itemManager = itemManager
self.qty = qty self.qty = qty
self.sm = sm self.sm = sm
@@ -423,12 +424,13 @@ class ItemPoolGenerator(object):
self.maxEnergy = 18 # 14E, 4R self.maxEnergy = 18 # 14E, 4R
self.maxDiff = maxDiff self.maxDiff = maxDiff
self.log = log.get('ItemPool') self.log = log.get('ItemPool')
self.random = random
def isUltraSparseNoTanks(self): def isUltraSparseNoTanks(self):
# if low stuff botwoon is not known there is a hard energy req of one tank, even # if low stuff botwoon is not known there is a hard energy req of one tank, even
# with both suits # with both suits
lowStuffBotwoon = self.sm.knowsLowStuffBotwoon() lowStuffBotwoon = self.sm.knowsLowStuffBotwoon()
return random.random() < 0.5 and (lowStuffBotwoon.bool == True and lowStuffBotwoon.difficulty <= self.maxDiff) return self.random.random() < 0.5 and (lowStuffBotwoon.bool == True and lowStuffBotwoon.difficulty <= self.maxDiff)
def calcMaxMinors(self): def calcMaxMinors(self):
pool = self.itemManager.getItemPool() pool = self.itemManager.getItemPool()
@@ -464,7 +466,7 @@ class ItemPoolGenerator(object):
rangeDict = getRangeDict(ammoQty) rangeDict = getRangeDict(ammoQty)
self.log.debug("rangeDict: {}".format(rangeDict)) self.log.debug("rangeDict: {}".format(rangeDict))
while len(self.itemManager.getItemPool()) < maxItems: while len(self.itemManager.getItemPool()) < maxItems:
item = chooseFromRange(rangeDict) item = chooseFromRange(rangeDict, self.random)
self.itemManager.addMinor(item) self.itemManager.addMinor(item)
else: else:
minorsTypes = ['Missile', 'Super', 'PowerBomb'] minorsTypes = ['Missile', 'Super', 'PowerBomb']
@@ -522,7 +524,7 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
# no etank nor reserve # no etank nor reserve
self.itemManager.removeItem('ETank') self.itemManager.removeItem('ETank')
self.itemManager.addItem('NoEnergy', 'Chozo') self.itemManager.addItem('NoEnergy', 'Chozo')
elif random.random() < 0.5: elif self.random.random() < 0.5:
# replace only etank with reserve # replace only etank with reserve
self.itemManager.removeItem('ETank') self.itemManager.removeItem('ETank')
self.itemManager.addItem('Reserve', 'Chozo') self.itemManager.addItem('Reserve', 'Chozo')
@@ -535,9 +537,9 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
# 4-6 # 4-6
# already 3E and 1R # already 3E and 1R
alreadyInPool = 4 alreadyInPool = 4
rest = randGaussBounds(2, 5) rest = randGaussBounds(self.random, 2, 5)
if rest >= 1: if rest >= 1:
if random.random() < 0.5: if self.random.random() < 0.5:
self.itemManager.addItem('Reserve', 'Minor') self.itemManager.addItem('Reserve', 'Minor')
else: else:
self.itemManager.addItem('ETank', 'Minor') self.itemManager.addItem('ETank', 'Minor')
@@ -550,13 +552,13 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
# 8-12 # 8-12
# add up to 3 Reserves or ETanks (cannot add more than 3 reserves) # add up to 3 Reserves or ETanks (cannot add more than 3 reserves)
for i in range(3): for i in range(3):
if random.random() < 0.5: if self.random.random() < 0.5:
self.itemManager.addItem('Reserve', 'Minor') self.itemManager.addItem('Reserve', 'Minor')
else: else:
self.itemManager.addItem('ETank', 'Minor') self.itemManager.addItem('ETank', 'Minor')
# 7 already in the pool (3 E, 1 R, + the previous 3) # 7 already in the pool (3 E, 1 R, + the previous 3)
alreadyInPool = 7 alreadyInPool = 7
rest = 1 + randGaussBounds(4, 3.7) rest = 1 + randGaussBounds(self.random, 4, 3.7)
for i in range(rest): for i in range(rest):
self.itemManager.addItem('ETank', 'Minor') self.itemManager.addItem('ETank', 'Minor')
# fill the rest with NoEnergy # fill the rest with NoEnergy
@@ -581,10 +583,10 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
return self.itemManager.getItemPool() return self.itemManager.getItemPool()
class ItemPoolGeneratorMajors(ItemPoolGenerator): class ItemPoolGeneratorMajors(ItemPoolGenerator):
def __init__(self, itemManager, qty, sm, maxDiff): def __init__(self, itemManager, qty, sm, maxDiff, random):
super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff) super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff, random)
self.sparseRest = 1 + randGaussBounds(2, 5) self.sparseRest = 1 + randGaussBounds(self.random,2, 5)
self.mediumRest = 3 + randGaussBounds(4, 3.7) self.mediumRest = 3 + randGaussBounds(self.random, 4, 3.7)
self.ultraSparseNoTanks = self.isUltraSparseNoTanks() self.ultraSparseNoTanks = self.isUltraSparseNoTanks()
def addNoEnergy(self): def addNoEnergy(self):
@@ -609,7 +611,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
# no energy at all # no energy at all
self.addNoEnergy() self.addNoEnergy()
else: else:
if random.random() < 0.5: if self.random.random() < 0.5:
self.itemManager.addItem('ETank') self.itemManager.addItem('ETank')
else: else:
self.itemManager.addItem('Reserve') self.itemManager.addItem('Reserve')
@@ -620,7 +622,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
elif energyQty == 'sparse': elif energyQty == 'sparse':
# 4-6 # 4-6
if random.random() < 0.5: if self.random.random() < 0.5:
self.itemManager.addItem('Reserve') self.itemManager.addItem('Reserve')
else: else:
self.itemManager.addItem('ETank') self.itemManager.addItem('ETank')
@@ -639,7 +641,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
alreadyInPool = 2 alreadyInPool = 2
n = getE(3) n = getE(3)
for i in range(n): for i in range(n):
if random.random() < 0.5: if self.random.random() < 0.5:
self.itemManager.addItem('Reserve') self.itemManager.addItem('Reserve')
else: else:
self.itemManager.addItem('ETank') self.itemManager.addItem('ETank')
@@ -676,15 +678,15 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
return self.itemManager.getItemPool() return self.itemManager.getItemPool()
class ItemPoolGeneratorScavenger(ItemPoolGeneratorMajors): class ItemPoolGeneratorScavenger(ItemPoolGeneratorMajors):
def __init__(self, itemManager, qty, sm, maxDiff): def __init__(self, itemManager, qty, sm, maxDiff, random):
super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff) super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff, random)
def addNoEnergy(self): def addNoEnergy(self):
self.itemManager.addItem('Nothing') self.itemManager.addItem('Nothing')
class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors): class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors):
def __init__(self, itemManager, qty, sm, nLocs, maxDiff): def __init__(self, itemManager, qty, sm, nLocs, maxDiff, random):
super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff) super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff, random)
self.maxItems = nLocs self.maxItems = nLocs
self.calcMaxAmmo() self.calcMaxAmmo()
nMajors = len([itemName for itemName,item in ItemManager.Items.items() if item.Class == 'Major' and item.Category != 'Energy']) nMajors = len([itemName for itemName,item in ItemManager.Items.items() if item.Class == 'Major' and item.Category != 'Energy'])
@@ -716,8 +718,8 @@ class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors):
self.log.debug("maxEnergy: "+str(self.maxEnergy)) self.log.debug("maxEnergy: "+str(self.maxEnergy))
class ItemPoolGeneratorPlando(ItemPoolGenerator): class ItemPoolGeneratorPlando(ItemPoolGenerator):
def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff): def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff, random):
super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff) super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff, random)
# in exclude dict: # in exclude dict:
# in alreadyPlacedItems: # in alreadyPlacedItems:
# dict of 'itemType: count' of items already added in the plando. # dict of 'itemType: count' of items already added in the plando.
@@ -805,7 +807,7 @@ class ItemPoolGeneratorPlando(ItemPoolGenerator):
if ammoQty: if ammoQty:
rangeDict = getRangeDict(ammoQty) rangeDict = getRangeDict(ammoQty)
while len(self.itemManager.getItemPool()) < maxItems and remain > 0: while len(self.itemManager.getItemPool()) < maxItems and remain > 0:
item = chooseFromRange(rangeDict) item = chooseFromRange(rangeDict, self.random)
self.itemManager.addMinor(item) self.itemManager.addMinor(item)
remain -= 1 remain -= 1

View File

@@ -1,4 +1,4 @@
import sys, random, time import sys, time
from ..utils import log from ..utils import log
from ..logic.logic import Logic from ..logic.logic import Logic
@@ -14,7 +14,7 @@ from ..utils.doorsmanager import DoorsManager
# entry point for rando execution ("randomize" method) # entry point for rando execution ("randomize" method)
class RandoExec(object): class RandoExec(object):
def __init__(self, seedName, vcr, randoSettings, graphSettings, player): def __init__(self, seedName, vcr, randoSettings, graphSettings, player, random):
self.errorMsg = "" self.errorMsg = ""
self.seedName = seedName self.seedName = seedName
self.vcr = vcr self.vcr = vcr
@@ -22,6 +22,7 @@ class RandoExec(object):
self.graphSettings = graphSettings self.graphSettings = graphSettings
self.log = log.get('RandoExec') self.log = log.get('RandoExec')
self.player = player self.player = player
self.random = random
# processes settings to : # processes settings to :
# - create Restrictions and GraphBuilder objects # - create Restrictions and GraphBuilder objects
@@ -31,7 +32,7 @@ class RandoExec(object):
vcr = VCR(self.seedName, 'rando') if self.vcr == True else None vcr = VCR(self.seedName, 'rando') if self.vcr == True else None
self.errorMsg = "" self.errorMsg = ""
split = self.randoSettings.restrictions['MajorMinor'] split = self.randoSettings.restrictions['MajorMinor']
self.graphBuilder = GraphBuilder(self.graphSettings) self.graphBuilder = GraphBuilder(self.graphSettings, self.random)
container = None container = None
i = 0 i = 0
attempts = 500 if self.graphSettings.areaRando or self.graphSettings.doorsColorsRando or split == 'Scavenger' else 1 attempts = 500 if self.graphSettings.areaRando or self.graphSettings.doorsColorsRando or split == 'Scavenger' else 1
@@ -43,10 +44,10 @@ class RandoExec(object):
while container is None and i < attempts and now <= endDate: while container is None and i < attempts and now <= endDate:
self.restrictions = Restrictions(self.randoSettings) self.restrictions = Restrictions(self.randoSettings)
if self.graphSettings.doorsColorsRando == True: if self.graphSettings.doorsColorsRando == True:
DoorsManager.randomize(self.graphSettings.allowGreyDoors, self.player) DoorsManager.randomize(self.graphSettings.allowGreyDoors, self.player, self.random)
self.areaGraph = self.graphBuilder.createGraph(self.randoSettings.maxDiff) self.areaGraph = self.graphBuilder.createGraph(self.randoSettings.maxDiff)
services = RandoServices(self.areaGraph, self.restrictions) services = RandoServices(self.areaGraph, self.restrictions, random=self.random)
setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player) setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player, self.random)
self.setup = setup self.setup = setup
container = setup.createItemLocContainer(endDate, vcr) container = setup.createItemLocContainer(endDate, vcr)
if container is None: if container is None:
@@ -78,7 +79,7 @@ class RandoExec(object):
n = nMaj n = nMaj
elif split == 'Chozo': elif split == 'Chozo':
n = nChozo n = nChozo
GraphUtils.updateLocClassesStart(startAP.GraphArea, split, possibleMajLocs, preserveMajLocs, n) GraphUtils.updateLocClassesStart(startAP.GraphArea, split, possibleMajLocs, preserveMajLocs, n, self.random)
def postProcessItemLocs(self, itemLocs, hide): def postProcessItemLocs(self, itemLocs, hide):
# hide some items like in dessy's # hide some items like in dessy's
@@ -89,7 +90,7 @@ class RandoExec(object):
if (item.Category != "Nothing" if (item.Category != "Nothing"
and loc.CanHidden == True and loc.CanHidden == True
and loc.Visibility == 'Visible'): and loc.Visibility == 'Visible'):
if bool(random.getrandbits(1)) == True: if bool(self.random.getrandbits(1)) == True:
loc.Visibility = 'Hidden' loc.Visibility = 'Hidden'
# put nothing in unfilled locations # put nothing in unfilled locations
filledLocNames = [il.Location.Name for il in itemLocs] filledLocNames = [il.Location.Name for il in itemLocs]

View File

@@ -1,5 +1,4 @@
import copy, sys, logging, os
import copy, random, sys, logging, os
from enum import Enum, unique from enum import Enum, unique
from ..utils import log from ..utils import log
from ..utils.parameters import infinity from ..utils.parameters import infinity
@@ -19,12 +18,13 @@ class ComebackCheckType(Enum):
# collection of stateless services to be used mainly by fillers # collection of stateless services to be used mainly by fillers
class RandoServices(object): class RandoServices(object):
def __init__(self, graph, restrictions, cache=None): def __init__(self, graph, restrictions, cache=None, *, random):
self.restrictions = restrictions self.restrictions = restrictions
self.settings = restrictions.settings self.settings = restrictions.settings
self.areaGraph = graph self.areaGraph = graph
self.cache = cache self.cache = cache
self.log = log.get('RandoServices') self.log = log.get('RandoServices')
self.random = random
@staticmethod @staticmethod
def printProgress(s): def printProgress(s):
@@ -217,7 +217,7 @@ class RandoServices(object):
# choose a morph item location in that context # choose a morph item location in that context
morphItemLoc = ItemLocation( morphItemLoc = ItemLocation(
morph, morph,
random.choice(morphLocs) self.random.choice(morphLocs)
) )
# acquire morph in new context and see if we can still open new locs # acquire morph in new context and see if we can still open new locs
newAP = self.collect(ap, containerCpy, morphItemLoc) newAP = self.collect(ap, containerCpy, morphItemLoc)
@@ -232,7 +232,7 @@ class RandoServices(object):
if morphLocItem is None or len(itemLocDict) == 1: if morphLocItem is None or len(itemLocDict) == 1:
# no morph, or it is the only possibility: nothing to do # no morph, or it is the only possibility: nothing to do
return return
morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem]) morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem], self.random)
if morphLocs is not None: if morphLocs is not None:
itemLocDict[morphLocItem] = morphLocs itemLocDict[morphLocItem] = morphLocs
else: else:
@@ -380,10 +380,10 @@ class RandoServices(object):
(itemLocDict, isProg) = self.getPossiblePlacements(ap, container, ComebackCheckType.NoCheck) (itemLocDict, isProg) = self.getPossiblePlacements(ap, container, ComebackCheckType.NoCheck)
assert not isProg assert not isProg
items = list(itemLocDict.keys()) items = list(itemLocDict.keys())
random.shuffle(items) self.random.shuffle(items)
for item in items: for item in items:
cont = copy.copy(container) cont = copy.copy(container)
loc = random.choice(itemLocDict[item]) loc = self.random.choice(itemLocDict[item])
itemLoc1 = ItemLocation(item, loc) itemLoc1 = ItemLocation(item, loc)
self.log.debug("itemLoc1 attempt: "+getItemLocStr(itemLoc1)) self.log.debug("itemLoc1 attempt: "+getItemLocStr(itemLoc1))
newAP = self.collect(ap, cont, itemLoc1) newAP = self.collect(ap, cont, itemLoc1)
@@ -391,8 +391,8 @@ class RandoServices(object):
self.cache.reset() self.cache.reset()
(ild, isProg) = self.getPossiblePlacements(newAP, cont, ComebackCheckType.NoCheck) (ild, isProg) = self.getPossiblePlacements(newAP, cont, ComebackCheckType.NoCheck)
if isProg: if isProg:
item2 = random.choice(list(ild.keys())) item2 = self.random.choice(list(ild.keys()))
itemLoc2 = ItemLocation(item2, random.choice(ild[item2])) itemLoc2 = ItemLocation(item2, self.random.choice(ild[item2]))
self.log.debug("itemLoc2: "+getItemLocStr(itemLoc2)) self.log.debug("itemLoc2: "+getItemLocStr(itemLoc2))
return (itemLoc1, itemLoc2) return (itemLoc1, itemLoc2)
return None return None

View File

@@ -1,5 +1,4 @@
import sys
import sys, random
from collections import defaultdict from collections import defaultdict
from ..rando.Items import ItemManager from ..rando.Items import ItemManager
from ..utils.utils import getRangeDict, chooseFromRange from ..utils.utils import getRangeDict, chooseFromRange
@@ -32,11 +31,11 @@ class RandoSettings(object):
def isPlandoRando(self): def isPlandoRando(self):
return self.PlandoOptions is not None return self.PlandoOptions is not None
def getItemManager(self, smbm, nLocs, bossesItems): def getItemManager(self, smbm, nLocs, bossesItems, random):
if not self.isPlandoRando(): if not self.isPlandoRando():
return ItemManager(self.restrictions['MajorMinor'], self.qty, smbm, nLocs, bossesItems, self.maxDiff) return ItemManager(self.restrictions['MajorMinor'], self.qty, smbm, nLocs, bossesItems, self.maxDiff, random)
else: else:
return ItemManager('Plando', self.qty, smbm, nLocs, bossesItems, self.maxDiff) return ItemManager('Plando', self.qty, smbm, nLocs, bossesItems, self.maxDiff, random)
def getExcludeItems(self, locations): def getExcludeItems(self, locations):
if not self.isPlandoRando(): if not self.isPlandoRando():
@@ -94,7 +93,7 @@ class ProgSpeedParameters(object):
self.restrictions = restrictions self.restrictions = restrictions
self.nLocs = nLocs self.nLocs = nLocs
def getVariableSpeed(self): def getVariableSpeed(self, random):
ranges = getRangeDict({ ranges = getRangeDict({
'slowest':7, 'slowest':7,
'slow':20, 'slow':20,
@@ -102,7 +101,7 @@ class ProgSpeedParameters(object):
'fast':27, 'fast':27,
'fastest':11 'fastest':11
}) })
return chooseFromRange(ranges) return chooseFromRange(ranges, random)
def getMinorHelpProb(self, progSpeed): def getMinorHelpProb(self, progSpeed):
if self.restrictions.split != 'Major': if self.restrictions.split != 'Major':
@@ -134,7 +133,7 @@ class ProgSpeedParameters(object):
def isSlow(self, progSpeed): def isSlow(self, progSpeed):
return progSpeed == "slow" or (progSpeed == "slowest" and self.restrictions.split == "Chozo") return progSpeed == "slow" or (progSpeed == "slowest" and self.restrictions.split == "Chozo")
def getItemLimit(self, progSpeed): def getItemLimit(self, progSpeed, random):
itemLimit = self.nLocs itemLimit = self.nLocs
if self.isSlow(progSpeed): if self.isSlow(progSpeed):
itemLimit = int(self.nLocs*0.209) # 21 for 105 itemLimit = int(self.nLocs*0.209) # 21 for 105

View File

@@ -1,4 +1,4 @@
import copy, random import copy
from ..utils import log from ..utils import log
from ..utils.utils import randGaussBounds from ..utils.utils import randGaussBounds
@@ -16,8 +16,9 @@ from ..rom.rom_patches import RomPatches
# checks init conditions for the randomizer: processes super fun settings, graph, start location, special restrictions # checks init conditions for the randomizer: processes super fun settings, graph, start location, special restrictions
# the entry point is createItemLocContainer # the entry point is createItemLocContainer
class RandoSetup(object): class RandoSetup(object):
def __init__(self, graphSettings, locations, services, player): def __init__(self, graphSettings, locations, services, player, random):
self.sm = SMBoolManager(player, services.settings.maxDiff) self.sm = SMBoolManager(player, services.settings.maxDiff)
self.random = random
self.settings = services.settings self.settings = services.settings
self.graphSettings = graphSettings self.graphSettings = graphSettings
self.startAP = graphSettings.startAP self.startAP = graphSettings.startAP
@@ -31,7 +32,7 @@ class RandoSetup(object):
# print("nLocs Setup: "+str(len(self.locations))) # print("nLocs Setup: "+str(len(self.locations)))
# in minimizer we can have some missing boss locs # in minimizer we can have some missing boss locs
bossesItems = [loc.BossItemType for loc in self.locations if loc.isBoss()] bossesItems = [loc.BossItemType for loc in self.locations if loc.isBoss()]
self.itemManager = self.settings.getItemManager(self.sm, len(self.locations), bossesItems) self.itemManager = self.settings.getItemManager(self.sm, len(self.locations), bossesItems, random)
self.forbiddenItems = [] self.forbiddenItems = []
self.restrictedLocs = [] self.restrictedLocs = []
self.lastRestricted = [] self.lastRestricted = []
@@ -165,7 +166,7 @@ class RandoSetup(object):
return True return True
self.log.debug("********* PRE RANDO START") self.log.debug("********* PRE RANDO START")
container = copy.copy(self.container) container = copy.copy(self.container)
filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions, container) filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions, container, random=self.random)
condition = filler.createStepCountCondition(4) condition = filler.createStepCountCondition(4)
(isStuck, itemLocations, progItems) = filler.generateItems(condition) (isStuck, itemLocations, progItems) = filler.generateItems(condition)
self.log.debug("********* PRE RANDO END") self.log.debug("********* PRE RANDO END")
@@ -345,9 +346,9 @@ class RandoSetup(object):
def getForbiddenItemsFromList(self, itemList): def getForbiddenItemsFromList(self, itemList):
self.log.debug('getForbiddenItemsFromList: ' + str(itemList)) self.log.debug('getForbiddenItemsFromList: ' + str(itemList))
remove = [] remove = []
n = randGaussBounds(len(itemList)) n = randGaussBounds(self.random, len(itemList))
for i in range(n): for i in range(n):
idx = random.randint(0, len(itemList) - 1) idx = self.random.randint(0, len(itemList) - 1)
item = itemList.pop(idx) item = itemList.pop(idx)
if item is not None: if item is not None:
remove.append(item) remove.append(item)

View File

@@ -1,4 +1,4 @@
import copy, random import copy
from ..utils import log from ..utils import log
from ..graph.graph_utils import getAccessPoint from ..graph.graph_utils import getAccessPoint
from ..rando.ItemLocContainer import getLocListStr from ..rando.ItemLocContainer import getLocListStr
@@ -112,7 +112,7 @@ class Restrictions(object):
return item.Class == "Minor" return item.Class == "Minor"
# return True if we can keep morph as a possibility # return True if we can keep morph as a possibility
def lateMorphCheck(self, container, possibleLocs): def lateMorphCheck(self, container, possibleLocs, random):
# the closer we get to the limit the higher the chances of allowing morph # the closer we get to the limit the higher the chances of allowing morph
proba = random.randint(0, self.lateMorphLimit) proba = random.randint(0, self.lateMorphLimit)
if self.split == 'Full': if self.split == 'Full':

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
from Utils import output_path from Utils import output_path
import argparse, os.path, json, sys, shutil, random, copy, requests import argparse, os.path, json, sys, shutil, copy, requests
from .rando.RandoSettings import RandoSettings, GraphSettings from .rando.RandoSettings import RandoSettings, GraphSettings
from .rando.RandoExec import RandoExec from .rando.RandoExec import RandoExec
@@ -39,7 +39,7 @@ objectives = defaultMultiValues['objective']
tourians = defaultMultiValues['tourian'] tourians = defaultMultiValues['tourian']
areaRandomizations = defaultMultiValues['areaRandomization'] areaRandomizations = defaultMultiValues['areaRandomization']
def randomMulti(args, param, defaultMultiValues): def randomMulti(args, param, defaultMultiValues, random):
value = args[param] value = args[param]
isRandom = False isRandom = False
@@ -250,10 +250,11 @@ class VariaRandomizer:
parser.add_argument('--tourianList', help="list to choose from when random", parser.add_argument('--tourianList', help="list to choose from when random",
dest='tourianList', nargs='?', default=None) dest='tourianList', nargs='?', default=None)
def __init__(self, options, rom, player): def __init__(self, options, rom, player, seed, random):
# parse args # parse args
self.args = copy.deepcopy(VariaRandomizer.parser.parse_args(["--logic", "varia"])) #dummy custom args to skip parsing _sys.argv while still get default values self.args = copy.deepcopy(VariaRandomizer.parser.parse_args(["--logic", "varia"])) #dummy custom args to skip parsing _sys.argv while still get default values
self.player = player self.player = player
self.random = random
args = self.args args = self.args
args.rom = rom args.rom = rom
# args.startLocation = to_pascal_case_with_space(options.startLocation.current_key) # args.startLocation = to_pascal_case_with_space(options.startLocation.current_key)
@@ -323,11 +324,13 @@ class VariaRandomizer:
logger.debug("preset: {}".format(preset)) logger.debug("preset: {}".format(preset))
# if no seed given, choose one # Archipelago provides a seed for the multiworld.
if args.seed == 0: self.seed = seed
self.seed = random.randrange(sys.maxsize) # # if no seed given, choose one
else: # if args.seed == 0:
self.seed = args.seed # self.seed = random.randrange(sys.maxsize)
# else:
# self.seed = args.seed
logger.debug("seed: {}".format(self.seed)) logger.debug("seed: {}".format(self.seed))
if args.raceMagic is not None: if args.raceMagic is not None:
@@ -360,12 +363,12 @@ class VariaRandomizer:
logger.debug("maxDifficulty: {}".format(self.maxDifficulty)) logger.debug("maxDifficulty: {}".format(self.maxDifficulty))
# handle random parameters with dynamic pool of values # handle random parameters with dynamic pool of values
(_, progSpeed) = randomMulti(args.__dict__, "progressionSpeed", speeds) (_, progSpeed) = randomMulti(args.__dict__, "progressionSpeed", speeds, random)
(_, progDiff) = randomMulti(args.__dict__, "progressionDifficulty", progDiffs) (_, progDiff) = randomMulti(args.__dict__, "progressionDifficulty", progDiffs, random)
(majorsSplitRandom, args.majorsSplit) = randomMulti(args.__dict__, "majorsSplit", majorsSplits) (majorsSplitRandom, args.majorsSplit) = randomMulti(args.__dict__, "majorsSplit", majorsSplits, random)
(_, self.gravityBehaviour) = randomMulti(args.__dict__, "gravityBehaviour", gravityBehaviours) (_, self.gravityBehaviour) = randomMulti(args.__dict__, "gravityBehaviour", gravityBehaviours, random)
(_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians) (_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians, random)
(areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations) (areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations, random)
areaRandomization = args.area in ['light', 'full'] areaRandomization = args.area in ['light', 'full']
lightArea = args.area == 'light' lightArea = args.area == 'light'
@@ -626,7 +629,7 @@ class VariaRandomizer:
if args.objective: if args.objective:
if (args.objectiveRandom): if (args.objectiveRandom):
availableObjectives = [goal for goal in objectives if goal != "collect 100% items"] if "random" in args.objectiveList else args.objectiveList availableObjectives = [goal for goal in objectives if goal != "collect 100% items"] if "random" in args.objectiveList else args.objectiveList
self.objectivesManager.setRandom(args.nbObjective, availableObjectives) self.objectivesManager.setRandom(args.nbObjective, availableObjectives, self.random)
else: else:
maxActiveGoals = Objectives.maxActiveGoals - addedObjectives maxActiveGoals = Objectives.maxActiveGoals - addedObjectives
if len(args.objective) > maxActiveGoals: if len(args.objective) > maxActiveGoals:
@@ -660,7 +663,7 @@ class VariaRandomizer:
# print("energyQty:{}".format(energyQty)) # print("energyQty:{}".format(energyQty))
#try: #try:
self.randoExec = RandoExec(seedName, args.vcr, randoSettings, graphSettings, self.player) self.randoExec = RandoExec(seedName, args.vcr, randoSettings, graphSettings, self.player, self.random)
self.container = self.randoExec.randomize() self.container = self.randoExec.randomize()
# if we couldn't find an area layout then the escape graph is not created either # if we couldn't find an area layout then the escape graph is not created either
# and getDoorConnections will crash if random escape is activated. # and getDoorConnections will crash if random escape is activated.
@@ -690,7 +693,7 @@ class VariaRandomizer:
'gameend.ips', 'grey_door_animals.ips', 'low_timer.ips', 'metalimals.ips', 'gameend.ips', 'grey_door_animals.ips', 'low_timer.ips', 'metalimals.ips',
'phantoonimals.ips', 'ridleyimals.ips'] 'phantoonimals.ips', 'ridleyimals.ips']
if args.escapeRando == False: if args.escapeRando == False:
args.patches.append(random.choice(animalsPatches)) args.patches.append(self.random.choice(animalsPatches))
args.patches.append("Escape_Animals_Change_Event") args.patches.append("Escape_Animals_Change_Event")
else: else:
optErrMsgs.append("Ignored animals surprise because of escape randomization") optErrMsgs.append("Ignored animals surprise because of escape randomization")
@@ -760,9 +763,9 @@ class VariaRandomizer:
# patch local rom # patch local rom
# romFileName = args.rom # romFileName = args.rom
# shutil.copyfile(romFileName, outputFilename) # shutil.copyfile(romFileName, outputFilename)
romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, player=self.player) romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, player=self.player, random=self.random)
else: else:
romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic) romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, random=self.random)
if customPrePatchApply != None: if customPrePatchApply != None:
customPrePatchApply(romPatcher) customPrePatchApply(romPatcher)

View File

@@ -49,7 +49,7 @@ class RomPatcher:
'DoorsColors': ['beam_doors_plms.ips', 'beam_doors_gfx.ips', 'red_doors.ips'] 'DoorsColors': ['beam_doors_plms.ips', 'beam_doors_gfx.ips', 'red_doors.ips']
} }
def __init__(self, settings=None, romFileName=None, magic=None, player=0): def __init__(self, settings=None, romFileName=None, magic=None, player=0, *, random):
self.log = log.get('RomPatcher') self.log = log.get('RomPatcher')
self.settings = settings self.settings = settings
#self.romFileName = romFileName #self.romFileName = romFileName
@@ -76,6 +76,7 @@ class RomPatcher:
0x93ea: self.forceRoomCRE 0x93ea: self.forceRoomCRE
} }
self.player = player self.player = player
self.random = random
def patchRom(self): def patchRom(self):
self.applyIPSPatches() self.applyIPSPatches()
@@ -496,9 +497,9 @@ class RomPatcher:
self.ipsPatches = [] self.ipsPatches = []
def writeSeed(self, seed): def writeSeed(self, seed):
random.seed(seed) r = random.Random(seed)
seedInfo = random.randint(0, 0xFFFF) seedInfo = r.randint(0, 0xFFFF)
seedInfo2 = random.randint(0, 0xFFFF) seedInfo2 = r.randint(0, 0xFFFF)
self.romFile.writeWord(seedInfo, snes_to_pc(0xdfff00)) self.romFile.writeWord(seedInfo, snes_to_pc(0xdfff00))
self.romFile.writeWord(seedInfo2) self.romFile.writeWord(seedInfo2)
@@ -1066,7 +1067,7 @@ class RomPatcher:
def writeObjectives(self, itemLocs, tourian): def writeObjectives(self, itemLocs, tourian):
objectives = Objectives.objDict[self.player] objectives = Objectives.objDict[self.player]
objectives.writeGoals(self.romFile) objectives.writeGoals(self.romFile, self.random)
objectives.writeIntroObjectives(self.romFile, tourian) objectives.writeIntroObjectives(self.romFile, tourian)
self.writeItemsMasks(itemLocs) self.writeItemsMasks(itemLocs)
# hack bomb_torizo.ips to wake BT in all cases if necessary, ie chozo bots objective is on, and nothing at bombs # hack bomb_torizo.ips to wake BT in all cases if necessary, ie chozo bots objective is on, and nothing at bombs

View File

@@ -1,4 +1,3 @@
import random
from enum import IntEnum,IntFlag from enum import IntEnum,IntFlag
import copy import copy
from ..logic.smbool import SMBool from ..logic.smbool import SMBool
@@ -123,7 +122,7 @@ class Door(object):
else: else:
return [color for color in colorsList if color not in self.forbiddenColors] return [color for color in colorsList if color not in self.forbiddenColors]
def randomize(self, allowGreyDoors): def randomize(self, allowGreyDoors, random):
if self.canRandomize(): if self.canRandomize():
if self.canGrey and allowGreyDoors: if self.canGrey and allowGreyDoors:
self.setColor(random.choice(self.filterColorList(colorsListGrey))) self.setColor(random.choice(self.filterColorList(colorsListGrey)))
@@ -347,9 +346,9 @@ class DoorsManager():
currentDoors['CrabShaftRight'].forceBlue() currentDoors['CrabShaftRight'].forceBlue()
@staticmethod @staticmethod
def randomize(allowGreyDoors, player): def randomize(allowGreyDoors, player, random):
for door in DoorsManager.doorsDict[player].values(): for door in DoorsManager.doorsDict[player].values():
door.randomize(allowGreyDoors) door.randomize(allowGreyDoors, random)
# set both ends of toilet to the same color to avoid soft locking in area rando # set both ends of toilet to the same color to avoid soft locking in area rando
toiletTop = DoorsManager.doorsDict[player]['PlasmaSparkBottom'] toiletTop = DoorsManager.doorsDict[player]['PlasmaSparkBottom']
toiletBottom = DoorsManager.doorsDict[player]['OasisTop'] toiletBottom = DoorsManager.doorsDict[player]['OasisTop']

View File

@@ -1,5 +1,4 @@
import copy import copy
import random
from ..rom.addresses import Addresses from ..rom.addresses import Addresses
from ..rom.rom import pc_to_snes from ..rom.rom import pc_to_snes
from ..logic.helpers import Bosses from ..logic.helpers import Bosses
@@ -28,7 +27,7 @@ class Synonyms(object):
] ]
alreadyUsed = [] alreadyUsed = []
@staticmethod @staticmethod
def getVerb(): def getVerb(random):
verb = random.choice(Synonyms.killSynonyms) verb = random.choice(Synonyms.killSynonyms)
while verb in Synonyms.alreadyUsed: while verb in Synonyms.alreadyUsed:
verb = random.choice(Synonyms.killSynonyms) verb = random.choice(Synonyms.killSynonyms)
@@ -88,10 +87,10 @@ class Goal(object):
# not all objectives require an ap (like limit objectives) # not all objectives require an ap (like limit objectives)
return self.clearFunc(smbm, ap) return self.clearFunc(smbm, ap)
def getText(self): def getText(self, random):
out = "{}. ".format(self.rank) out = "{}. ".format(self.rank)
if self.useSynonym: if self.useSynonym:
out += self.text.format(Synonyms.getVerb()) out += self.text.format(Synonyms.getVerb(random))
else: else:
out += self.text out += self.text
assert len(out) <= 28, "Goal text '{}' is too long: {}, max 28".format(out, len(out)) assert len(out) <= 28, "Goal text '{}' is too long: {}, max 28".format(out, len(out))
@@ -676,7 +675,7 @@ class Objectives(object):
return [goal.name for goal in _goals.values() if goal.available and (not removeNothing or goal.name != "nothing")] return [goal.name for goal in _goals.values() if goal.available and (not removeNothing or goal.name != "nothing")]
# call from rando # call from rando
def setRandom(self, nbGoals, availableGoals): def setRandom(self, nbGoals, availableGoals, random):
while self.nbActiveGoals < nbGoals and availableGoals: while self.nbActiveGoals < nbGoals and availableGoals:
goalName = random.choice(availableGoals) goalName = random.choice(availableGoals)
self.addGoal(goalName) self.addGoal(goalName)
@@ -702,7 +701,7 @@ class Objectives(object):
LOG.debug("tourianRequired: {}".format(self.tourianRequired)) LOG.debug("tourianRequired: {}".format(self.tourianRequired))
# call from rando # call from rando
def writeGoals(self, romFile): def writeGoals(self, romFile, random):
# write check functions # write check functions
romFile.seek(Addresses.getOne('objectivesList')) romFile.seek(Addresses.getOne('objectivesList'))
for goal in self.activeGoals: for goal in self.activeGoals:
@@ -736,7 +735,7 @@ class Objectives(object):
space = 3 if self.nbActiveGoals == 5 else 4 space = 3 if self.nbActiveGoals == 5 else 4
for i, goal in enumerate(self.activeGoals): for i, goal in enumerate(self.activeGoals):
addr = baseAddr + i * lineLength * space addr = baseAddr + i * lineLength * space
text = goal.getText() text = goal.getText(random)
romFile.seek(addr) romFile.seek(addr)
for c in text: for c in text:
if c not in char2tile: if c not in char2tile:

View File

@@ -1,5 +1,5 @@
import io import io
import os, json, re, random import os, json, re
import pathlib import pathlib
import sys import sys
from typing import Any from typing import Any
@@ -88,7 +88,7 @@ def normalizeRounding(n):
# gauss random in [0, r] range # gauss random in [0, r] range
# the higher the slope, the less probable extreme values are. # the higher the slope, the less probable extreme values are.
def randGaussBounds(r, slope=5): def randGaussBounds(random, r, slope=5):
r = float(r) r = float(r)
n = normalizeRounding(random.gauss(r/2, r/slope)) n = normalizeRounding(random.gauss(r/2, r/slope))
if n < 0: if n < 0:
@@ -111,7 +111,7 @@ def getRangeDict(weightDict):
return rangeDict return rangeDict
def chooseFromRange(rangeDict): def chooseFromRange(rangeDict, random):
r = random.random() r = random.random()
val = None val = None
for v in sorted(rangeDict, key=rangeDict.get): for v in sorted(rangeDict, key=rangeDict.get):

View File

View File

View File

View File

@@ -617,7 +617,7 @@ if is_easter_time():
easter_special_option_group = OptionGroup("EASTER SPECIAL", [ easter_special_option_group = OptionGroup("EASTER SPECIAL", [
EasterEggHunt, EasterEggHunt,
]) ])
witness_option_groups = [easter_special_option_group, *witness_option_groups] witness_option_groups.insert(2, easter_special_option_group)
else: else:
silly_options_group = next(group for group in witness_option_groups if group.name == "Silly Options") silly_options_group = next(group for group in witness_option_groups if group.name == "Silly Options")
silly_options_group.options.append(EasterEggHunt) silly_options_group.options.append(EasterEggHunt)