mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-11 01:53:46 -07:00
Compare commits
10 Commits
0.6.0
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77e6fa0010 | ||
|
|
d26db6f213 | ||
|
|
bb6c753583 | ||
|
|
ca08e4b950 | ||
|
|
5a6b02dbd3 | ||
|
|
14416b1050 | ||
|
|
da4e6fc532 | ||
|
|
57d8b69a6d | ||
|
|
c9d8a8661c | ||
|
|
4a3d23e0e6 |
6
kvui.py
6
kvui.py
@@ -817,6 +817,12 @@ class HintLayout(BoxLayout):
|
||||
boxlayout.add_widget(AutocompleteHintInput())
|
||||
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] = {
|
||||
HintStatus.HINT_FOUND: "Found",
|
||||
|
||||
2
setup.py
2
setup.py
@@ -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
|
||||
requirement = 'cx-Freeze==7.2.0'
|
||||
requirement = 'cx-Freeze==8.0.0'
|
||||
try:
|
||||
import pkg_resources
|
||||
try:
|
||||
|
||||
14
test/general/test_packages.py
Normal file
14
test/general/test_packages.py
Normal 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))
|
||||
@@ -48,6 +48,10 @@ class CivVIContainer(APContainer, metaclass=AutoPatchRegister):
|
||||
opened_zipfile.writestr(filename, yml)
|
||||
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:
|
||||
"""
|
||||
@@ -63,7 +67,7 @@ def get_formatted_player_name(world: 'CivVIWorld', player: int) -> str:
|
||||
Returns the name of the player in the world
|
||||
"""
|
||||
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"
|
||||
|
||||
|
||||
@@ -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!"/>
|
||||
{"".join([f'{tab}<Row TechnologyType="{location.name}" '
|
||||
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'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
|
||||
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!"/>
|
||||
{"".join([f'{tab}<Row CivicType="{location.name}" '
|
||||
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'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
|
||||
f'Cost="{get_cost(world, world.location_table[location.name])}" '
|
||||
|
||||
0
worlds/civ_6/data/__init__.py
Normal file
0
worlds/civ_6/data/__init__.py
Normal file
0
worlds/cv64/data/__init__.py
Normal file
0
worlds/cv64/data/__init__.py
Normal 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
|
||||
if not options["multi_hit_breakables"]:
|
||||
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
|
||||
# game PermaUps are distinguishable.
|
||||
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(0x10CF44, 0x1000FF4D) # CT Renon slab
|
||||
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
|
||||
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,
|
||||
"drop_previous_sub_weapon": world.options.drop_previous_sub_weapon.value,
|
||||
"countdown": world.options.countdown.value,
|
||||
"lizard_locker_items": world.options.lizard_locker_items.value,
|
||||
"shopsanity": world.options.shopsanity.value,
|
||||
"panther_dash": world.options.panther_dash.value,
|
||||
"big_toss": world.options.big_toss.value,
|
||||
|
||||
0
worlds/cvcotm/data/__init__.py
Normal file
0
worlds/cvcotm/data/__init__.py
Normal file
@@ -3,7 +3,6 @@ import settings
|
||||
import base64
|
||||
import threading
|
||||
import requests
|
||||
import yaml
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from BaseClasses import Tutorial
|
||||
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])
|
||||
else:
|
||||
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
|
||||
else:
|
||||
api_urls.remove(api_url)
|
||||
|
||||
0
worlds/ffmq/data/__init__.py
Normal file
0
worlds/ffmq/data/__init__.py
Normal file
0
worlds/kh2/Names/__init__.py
Normal file
0
worlds/kh2/Names/__init__.py
Normal file
0
worlds/ladx/LADXR/__init__.py
Normal file
0
worlds/ladx/LADXR/__init__.py
Normal file
0
worlds/ladx/LADXR/locations/__init__.py
Normal file
0
worlds/ladx/LADXR/locations/__init__.py
Normal file
0
worlds/ladx/LADXR/mapgen/locations/__init__.py
Normal file
0
worlds/ladx/LADXR/mapgen/locations/__init__.py
Normal file
0
worlds/ladx/LADXR/mapgen/roomtype/__init__.py
Normal file
0
worlds/ladx/LADXR/mapgen/roomtype/__init__.py
Normal file
0
worlds/ladx/LADXR/patches/__init__.py
Normal file
0
worlds/ladx/LADXR/patches/__init__.py
Normal file
0
worlds/landstalker/data/__init__.py
Normal file
0
worlds/landstalker/data/__init__.py
Normal file
0
worlds/mlss/Names/__init__.py
Normal file
0
worlds/mlss/Names/__init__.py
Normal file
Binary file not shown.
0
worlds/mmbn3/Names/__init__.py
Normal file
0
worlds/mmbn3/Names/__init__.py
Normal file
@@ -52,11 +52,13 @@ class MuseDashCollections:
|
||||
"Nyaa SFX Trap": STARTING_CODE + 8,
|
||||
"Error SFX Trap": STARTING_CODE + 9,
|
||||
"Focus Line Trap": STARTING_CODE + 10,
|
||||
"Beefcake SFX Trap": STARTING_CODE + 11,
|
||||
}
|
||||
|
||||
sfx_trap_items: List[str] = [
|
||||
"Nyaa SFX Trap",
|
||||
"Error SFX Trap",
|
||||
"Beefcake SFX Trap",
|
||||
]
|
||||
|
||||
filler_items: Dict[str, int] = {
|
||||
|
||||
@@ -627,4 +627,10 @@ SONG_DATA: Dict[str, SongData] = {
|
||||
"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),
|
||||
"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),
|
||||
}
|
||||
|
||||
0
worlds/osrs/LogicCSV/__init__.py
Normal file
0
worlds/osrs/LogicCSV/__init__.py
Normal 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
|
||||
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
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
import pkgutil
|
||||
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 Options import OptionError, Toggle
|
||||
import settings
|
||||
@@ -100,6 +100,7 @@ class PokemonEmeraldWorld(World):
|
||||
|
||||
required_client_version = (0, 4, 6)
|
||||
|
||||
item_pool: List[PokemonEmeraldItem]
|
||||
badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
|
||||
hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
|
||||
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
|
||||
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.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
|
||||
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,
|
||||
self.player_name)
|
||||
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
|
||||
|
||||
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)
|
||||
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:
|
||||
from .regions import create_regions
|
||||
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]
|
||||
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:
|
||||
self.multiworld.itempool += default_itempool
|
||||
|
||||
# Recreate the itempool from random items
|
||||
# Take the itempool as-is
|
||||
self.item_pool = default_itempool
|
||||
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",
|
||||
"Money", "TM", "Held", "Misc", "Berry"]
|
||||
|
||||
@@ -392,6 +399,7 @@ class PokemonEmeraldWorld(World):
|
||||
if not item.advancement:
|
||||
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 = [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 = self.create_item_by_code(item_code)
|
||||
|
||||
self.multiworld.itempool.append(item)
|
||||
self.item_pool.append(item)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
from .rules import set_rules
|
||||
set_rules(self)
|
||||
self.multiworld.itempool += self.item_pool
|
||||
|
||||
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_legendary_cave_entrances(self)
|
||||
|
||||
@@ -475,9 +474,20 @@ class PokemonEmeraldWorld(World):
|
||||
if not self.options.key_items:
|
||||
convert_unrandomized_items_to_events(LocationCategory.KEY)
|
||||
|
||||
def pre_fill(self) -> None:
|
||||
# Badges and HMs that are set to shuffle need to be placed at
|
||||
# their own subset of locations
|
||||
def set_rules(self):
|
||||
from .rules import set_rules
|
||||
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:
|
||||
badge_locations: List[PokemonEmeraldLocation]
|
||||
badge_items: List[PokemonEmeraldItem]
|
||||
@@ -502,41 +512,20 @@ class PokemonEmeraldWorld(World):
|
||||
badge_priority["Knuckle Badge"] = 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
|
||||
for location in badge_locations:
|
||||
location.progress_type = LocationProgressType.DEFAULT \
|
||||
if location.progress_type == LocationProgressType.EXCLUDED \
|
||||
else location.progress_type
|
||||
|
||||
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.
|
||||
# Build state
|
||||
state = CollectionState(self.multiworld)
|
||||
for item in my_progression_items:
|
||||
state.collect(item, True)
|
||||
# If HM shuffle is on, HMs are neither placed in locations nor in
|
||||
# the item pool, so we also need to collect them.
|
||||
if self.hm_shuffle_info is not None:
|
||||
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
|
||||
# out of swaps before it finds a valid solution if it gets unlucky.
|
||||
# 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
|
||||
# Shuffle badges
|
||||
self.fill_subset_with_retries(badge_items, badge_locations, state)
|
||||
|
||||
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:
|
||||
hm_locations: List[PokemonEmeraldLocation]
|
||||
hm_items: List[PokemonEmeraldItem]
|
||||
@@ -559,33 +548,56 @@ class PokemonEmeraldWorld(World):
|
||||
if self.options.badges == RandomizeBadges.option_vanilla and \
|
||||
self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave):
|
||||
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
|
||||
for location in hm_locations:
|
||||
location.progress_type = LocationProgressType.DEFAULT \
|
||||
if location.progress_type == LocationProgressType.EXCLUDED \
|
||||
else location.progress_type
|
||||
# Build state
|
||||
# Badges are either in the item pool, or already placed and collected during sweep
|
||||
state = CollectionState(self.multiworld)
|
||||
for item in my_progression_items:
|
||||
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
|
||||
# out of swaps before it finds a valid solution if it gets unlucky.
|
||||
# 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(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
|
||||
def fill_subset_with_retries(self, items: list[PokemonEmeraldItem], locations: list[PokemonEmeraldLocation], state: CollectionState):
|
||||
# Un-exclude locations, since we need to put progression items on them
|
||||
for location in locations:
|
||||
location.progress_type = LocationProgressType.DEFAULT \
|
||||
if location.progress_type == LocationProgressType.EXCLUDED \
|
||||
else location.progress_type
|
||||
|
||||
logging.debug(f"Failed to shuffle HMs for player {self.player}. Retrying.")
|
||||
continue
|
||||
# In specific very constrained conditions, `fill_restrictive` may run
|
||||
# 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:
|
||||
self.modified_trainers = copy.deepcopy(emerald_data.trainers)
|
||||
|
||||
@@ -124,7 +124,7 @@ class SMWorld(World):
|
||||
Logic.factory('vanilla')
|
||||
|
||||
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)
|
||||
|
||||
# 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.")
|
||||
|
||||
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
|
||||
missiles = self.options.missile_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:
|
||||
return "Power Bomb"
|
||||
elif roll <= power_bombs + missiles:
|
||||
@@ -340,8 +340,8 @@ class SMWorld(World):
|
||||
else:
|
||||
nonChozoLoc.append(loc)
|
||||
|
||||
self.multiworld.random.shuffle(nonChozoLoc)
|
||||
self.multiworld.random.shuffle(chozoLoc)
|
||||
self.random.shuffle(nonChozoLoc)
|
||||
self.random.shuffle(chozoLoc)
|
||||
missingCount = len(self.NothingPool) - len(nonChozoLoc)
|
||||
locations = nonChozoLoc
|
||||
if (missingCount > 0):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import copy
|
||||
import random
|
||||
from ..logic.logic import Logic
|
||||
from ..utils.parameters import Knows
|
||||
from ..graph.location import locationsDict
|
||||
@@ -136,7 +135,8 @@ class GraphUtils:
|
||||
refused[apName] = cause
|
||||
return ret, refused
|
||||
|
||||
def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs):
|
||||
@staticmethod
|
||||
def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs, random):
|
||||
locs = locationsDict
|
||||
preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)]
|
||||
possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs]
|
||||
@@ -160,7 +160,8 @@ class GraphUtils:
|
||||
ap = getAccessPoint(startApName)
|
||||
return ap.Start['patches'] if 'patches' in ap.Start else []
|
||||
|
||||
def createBossesTransitions():
|
||||
@staticmethod
|
||||
def createBossesTransitions(random):
|
||||
transitions = vanillaBossesTransitions
|
||||
def isVanilla():
|
||||
for t in vanillaBossesTransitions:
|
||||
@@ -180,13 +181,15 @@ class GraphUtils:
|
||||
transitions.append((src,dst))
|
||||
return transitions
|
||||
|
||||
def createAreaTransitions(lightAreaRando=False):
|
||||
@staticmethod
|
||||
def createAreaTransitions(lightAreaRando=False, *, random):
|
||||
if lightAreaRando:
|
||||
return GraphUtils.createLightAreaTransitions()
|
||||
return GraphUtils.createLightAreaTransitions(random=random)
|
||||
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:
|
||||
apList = Logic.accessPoints
|
||||
if apPred is None:
|
||||
@@ -239,7 +242,8 @@ class GraphUtils:
|
||||
transitions.append((ap.Name, ap.Name))
|
||||
|
||||
# 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:
|
||||
forcedAreas = []
|
||||
if startApName == 'Ceres':
|
||||
@@ -316,7 +320,8 @@ class GraphUtils:
|
||||
GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas))
|
||||
return transitions
|
||||
|
||||
def createLightAreaTransitions():
|
||||
@staticmethod
|
||||
def createLightAreaTransitions(random):
|
||||
# group APs by area
|
||||
aps = {}
|
||||
totalCount = 0
|
||||
@@ -407,7 +412,8 @@ class GraphUtils:
|
||||
|
||||
return rooms
|
||||
|
||||
def escapeAnimalsTransitions(graph, possibleTargets, firstEscape):
|
||||
@staticmethod
|
||||
def escapeAnimalsTransitions(graph, possibleTargets, firstEscape, random):
|
||||
n = len(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))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import random
|
||||
from ..utils import log
|
||||
from ..utils.utils import getRangeDict, chooseFromRange
|
||||
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
|
||||
class ItemThenLocChoice(Choice):
|
||||
def __init__(self, restrictions):
|
||||
def __init__(self, restrictions, random):
|
||||
super(ItemThenLocChoice, self).__init__(restrictions)
|
||||
self.random = random
|
||||
|
||||
def chooseItemLoc(self, itemLocDict, isProg):
|
||||
itemList = self.getItemList(itemLocDict)
|
||||
@@ -49,7 +49,7 @@ class ItemThenLocChoice(Choice):
|
||||
return self.chooseItemRandom(itemList)
|
||||
|
||||
def chooseItemRandom(self, itemList):
|
||||
return random.choice(itemList)
|
||||
return self.random.choice(itemList)
|
||||
|
||||
def chooseLocation(self, locList, item, isProg):
|
||||
if len(locList) == 0:
|
||||
@@ -63,12 +63,12 @@ class ItemThenLocChoice(Choice):
|
||||
return self.chooseLocationRandom(locList)
|
||||
|
||||
def chooseLocationRandom(self, locList):
|
||||
return random.choice(locList)
|
||||
return self.random.choice(locList)
|
||||
|
||||
# Choice specialization for prog speed based filler
|
||||
class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
|
||||
def __init__(self, restrictions, progSpeedParams, distanceProp, services):
|
||||
super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions)
|
||||
def __init__(self, restrictions, progSpeedParams, distanceProp, services, random):
|
||||
super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions, random)
|
||||
self.progSpeedParams = progSpeedParams
|
||||
self.distanceProp = distanceProp
|
||||
self.services = services
|
||||
@@ -104,7 +104,7 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
|
||||
if self.restrictions.isLateMorph() and canRollback and len(itemLocDict) == 1:
|
||||
item, locList = list(itemLocDict.items())[0]
|
||||
if item.Type == 'Morph':
|
||||
morphLocs = self.restrictions.lateMorphCheck(container, locList)
|
||||
morphLocs = self.restrictions.lateMorphCheck(container, locList, self.random)
|
||||
if morphLocs is not None:
|
||||
itemLocDict[item] = morphLocs
|
||||
else:
|
||||
@@ -115,7 +115,7 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
|
||||
assert len(locs) == 1 and locs[0].Name == item.Name
|
||||
return ItemLocation(item, locs[0])
|
||||
# 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.progressionItemLocs = progressionItemLocs
|
||||
self.ap = ap
|
||||
@@ -145,14 +145,14 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
|
||||
|
||||
def chooseLocationProg(self, locs, item):
|
||||
locs = self.getLocsSpreadProgression(locs)
|
||||
random.shuffle(locs)
|
||||
self.random.shuffle(locs)
|
||||
ret = self.getChooseFunc(self.chooseLocRanges, self.chooseLocFuncs)(locs)
|
||||
self.log.debug('chooseLocationProg. ret='+ret.Name)
|
||||
return ret
|
||||
|
||||
# get choose function from a weighted dict
|
||||
def getChooseFunc(self, rangeDict, funcDict):
|
||||
v = chooseFromRange(rangeDict)
|
||||
v = chooseFromRange(rangeDict, self.random)
|
||||
|
||||
return funcDict[v]
|
||||
|
||||
@@ -209,6 +209,6 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
|
||||
for i in range(len(availableLocations)):
|
||||
loc = availableLocations[i]
|
||||
d = distances[i]
|
||||
if d == maxDist or random.random() >= self.spreadProb:
|
||||
if d == maxDist or self.random.random() >= self.spreadProb:
|
||||
locs.append(loc)
|
||||
return locs
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import copy, time, random
|
||||
import copy, time
|
||||
from ..utils import log
|
||||
from ..logic.cache import RequestCache
|
||||
from ..rando.RandoServices import RandoServices
|
||||
@@ -15,11 +15,11 @@ from ..graph.graph_utils import GraphUtils
|
||||
# item pool is not empty).
|
||||
# entry point is generateItems
|
||||
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.cache = RequestCache()
|
||||
self.graph = graph
|
||||
self.services = RandoServices(graph, restrictions, self.cache)
|
||||
self.services = RandoServices(graph, restrictions, self.cache, random=random)
|
||||
self.restrictions = restrictions
|
||||
self.settings = restrictions.settings
|
||||
self.endDate = endDate
|
||||
@@ -108,9 +108,9 @@ class Filler(object):
|
||||
|
||||
# very simple front fill algorithm with no rollback and no "softlock checks" (== dessy algorithm)
|
||||
class FrontFiller(Filler):
|
||||
def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity):
|
||||
super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate)
|
||||
self.choice = ItemThenLocChoice(restrictions)
|
||||
def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity, *, random):
|
||||
super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate, random=random)
|
||||
self.choice = ItemThenLocChoice(restrictions, random)
|
||||
self.stdStart = GraphUtils.isStandardStart(self.startAP)
|
||||
|
||||
def isEarlyGame(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import random, copy
|
||||
import copy
|
||||
from ..utils import log
|
||||
from ..graph.graph_utils import GraphUtils, vanillaTransitions, vanillaBossesTransitions, escapeSource, escapeTargets, graphAreas, getAccessPoint
|
||||
from ..logic.logic import Logic
|
||||
@@ -11,13 +11,14 @@ from collections import defaultdict
|
||||
|
||||
# creates graph and handles randomized escape
|
||||
class GraphBuilder(object):
|
||||
def __init__(self, graphSettings):
|
||||
def __init__(self, graphSettings, random):
|
||||
self.graphSettings = graphSettings
|
||||
self.areaRando = graphSettings.areaRando
|
||||
self.bossRando = graphSettings.bossRando
|
||||
self.escapeRando = graphSettings.escapeRando
|
||||
self.minimizerN = graphSettings.minimizerN
|
||||
self.log = log.get('GraphBuilder')
|
||||
self.random = random
|
||||
|
||||
# builds everything but escape transitions
|
||||
def createGraph(self, maxDiff):
|
||||
@@ -48,18 +49,18 @@ class GraphBuilder(object):
|
||||
objForced = forcedAreas.intersection(escAreas)
|
||||
escAreasList = sorted(list(escAreas))
|
||||
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)
|
||||
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:
|
||||
if not self.bossRando:
|
||||
transitions += vanillaBossesTransitions
|
||||
else:
|
||||
transitions += GraphUtils.createBossesTransitions()
|
||||
transitions += GraphUtils.createBossesTransitions(self.random)
|
||||
if not self.areaRando:
|
||||
transitions += vanillaTransitions
|
||||
else:
|
||||
transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando)
|
||||
transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando, random=self.random)
|
||||
ret = AccessGraph(Logic.accessPoints, transitions, self.graphSettings.dotFile)
|
||||
Objectives.objDict[self.graphSettings.player].setGraph(ret, maxDiff)
|
||||
return ret
|
||||
@@ -100,7 +101,7 @@ class GraphBuilder(object):
|
||||
self.escapeTimer(graph, paths, self.areaRando or escapeTrigger is not None)
|
||||
self.log.debug("escapeGraph: ({}, {}) timer: {}".format(escapeSource, dst, graph.EscapeAttributes['Timer']))
|
||||
# animals
|
||||
GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst)
|
||||
GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst, self.random)
|
||||
return True
|
||||
|
||||
def _getTargets(self, sm, graph, maxDiff):
|
||||
@@ -110,7 +111,7 @@ class GraphBuilder(object):
|
||||
if len(possibleTargets) == 0:
|
||||
self.log.debug("Can't randomize escape, fallback to vanilla")
|
||||
possibleTargets.append('Climb Bottom Left')
|
||||
random.shuffle(possibleTargets)
|
||||
self.random.shuffle(possibleTargets)
|
||||
return possibleTargets
|
||||
|
||||
def getPossibleEscapeTargets(self, emptyContainer, graph, maxDiff):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from ..utils.utils import randGaussBounds, getRangeDict, chooseFromRange
|
||||
from ..utils import log
|
||||
import logging, copy, random
|
||||
import logging, copy
|
||||
|
||||
class Item:
|
||||
__slots__ = ( 'Category', 'Class', 'Name', 'Code', 'Type', 'BeamBits', 'ItemBits', 'Id' )
|
||||
@@ -335,7 +335,7 @@ class ItemManager:
|
||||
itemCode = item.Code + modifier
|
||||
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.sm = sm
|
||||
self.majorsSplit = majorsSplit
|
||||
@@ -344,6 +344,7 @@ class ItemManager:
|
||||
self.maxDiff = maxDiff
|
||||
self.majorClass = 'Chozo' if majorsSplit == 'Chozo' else 'Major'
|
||||
self.itemPool = []
|
||||
self.random = random
|
||||
|
||||
def newItemPool(self, addBosses=True):
|
||||
self.itemPool = []
|
||||
@@ -386,7 +387,7 @@ class ItemManager:
|
||||
return ItemManager.Items[itemType].withClass(itemClass)
|
||||
|
||||
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()
|
||||
|
||||
@staticmethod
|
||||
@@ -402,20 +403,20 @@ class ItemPoolGenerator(object):
|
||||
nbBosses = 9
|
||||
|
||||
@staticmethod
|
||||
def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff):
|
||||
def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff, random):
|
||||
if majorsSplit == 'Chozo':
|
||||
return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff)
|
||||
return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff, random)
|
||||
elif majorsSplit == 'Plando':
|
||||
return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff)
|
||||
return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff, random)
|
||||
elif nLocs == ItemPoolGenerator.maxLocs:
|
||||
if majorsSplit == "Scavenger":
|
||||
return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff)
|
||||
return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff, random)
|
||||
else:
|
||||
return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff)
|
||||
return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff, random)
|
||||
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.qty = qty
|
||||
self.sm = sm
|
||||
@@ -423,12 +424,13 @@ class ItemPoolGenerator(object):
|
||||
self.maxEnergy = 18 # 14E, 4R
|
||||
self.maxDiff = maxDiff
|
||||
self.log = log.get('ItemPool')
|
||||
self.random = random
|
||||
|
||||
def isUltraSparseNoTanks(self):
|
||||
# if low stuff botwoon is not known there is a hard energy req of one tank, even
|
||||
# with both suits
|
||||
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):
|
||||
pool = self.itemManager.getItemPool()
|
||||
@@ -464,7 +466,7 @@ class ItemPoolGenerator(object):
|
||||
rangeDict = getRangeDict(ammoQty)
|
||||
self.log.debug("rangeDict: {}".format(rangeDict))
|
||||
while len(self.itemManager.getItemPool()) < maxItems:
|
||||
item = chooseFromRange(rangeDict)
|
||||
item = chooseFromRange(rangeDict, self.random)
|
||||
self.itemManager.addMinor(item)
|
||||
else:
|
||||
minorsTypes = ['Missile', 'Super', 'PowerBomb']
|
||||
@@ -522,7 +524,7 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
|
||||
# no etank nor reserve
|
||||
self.itemManager.removeItem('ETank')
|
||||
self.itemManager.addItem('NoEnergy', 'Chozo')
|
||||
elif random.random() < 0.5:
|
||||
elif self.random.random() < 0.5:
|
||||
# replace only etank with reserve
|
||||
self.itemManager.removeItem('ETank')
|
||||
self.itemManager.addItem('Reserve', 'Chozo')
|
||||
@@ -535,9 +537,9 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
|
||||
# 4-6
|
||||
# already 3E and 1R
|
||||
alreadyInPool = 4
|
||||
rest = randGaussBounds(2, 5)
|
||||
rest = randGaussBounds(self.random, 2, 5)
|
||||
if rest >= 1:
|
||||
if random.random() < 0.5:
|
||||
if self.random.random() < 0.5:
|
||||
self.itemManager.addItem('Reserve', 'Minor')
|
||||
else:
|
||||
self.itemManager.addItem('ETank', 'Minor')
|
||||
@@ -550,13 +552,13 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
|
||||
# 8-12
|
||||
# add up to 3 Reserves or ETanks (cannot add more than 3 reserves)
|
||||
for i in range(3):
|
||||
if random.random() < 0.5:
|
||||
if self.random.random() < 0.5:
|
||||
self.itemManager.addItem('Reserve', 'Minor')
|
||||
else:
|
||||
self.itemManager.addItem('ETank', 'Minor')
|
||||
# 7 already in the pool (3 E, 1 R, + the previous 3)
|
||||
alreadyInPool = 7
|
||||
rest = 1 + randGaussBounds(4, 3.7)
|
||||
rest = 1 + randGaussBounds(self.random, 4, 3.7)
|
||||
for i in range(rest):
|
||||
self.itemManager.addItem('ETank', 'Minor')
|
||||
# fill the rest with NoEnergy
|
||||
@@ -581,10 +583,10 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator):
|
||||
return self.itemManager.getItemPool()
|
||||
|
||||
class ItemPoolGeneratorMajors(ItemPoolGenerator):
|
||||
def __init__(self, itemManager, qty, sm, maxDiff):
|
||||
super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff)
|
||||
self.sparseRest = 1 + randGaussBounds(2, 5)
|
||||
self.mediumRest = 3 + randGaussBounds(4, 3.7)
|
||||
def __init__(self, itemManager, qty, sm, maxDiff, random):
|
||||
super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff, random)
|
||||
self.sparseRest = 1 + randGaussBounds(self.random,2, 5)
|
||||
self.mediumRest = 3 + randGaussBounds(self.random, 4, 3.7)
|
||||
self.ultraSparseNoTanks = self.isUltraSparseNoTanks()
|
||||
|
||||
def addNoEnergy(self):
|
||||
@@ -609,7 +611,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
|
||||
# no energy at all
|
||||
self.addNoEnergy()
|
||||
else:
|
||||
if random.random() < 0.5:
|
||||
if self.random.random() < 0.5:
|
||||
self.itemManager.addItem('ETank')
|
||||
else:
|
||||
self.itemManager.addItem('Reserve')
|
||||
@@ -620,7 +622,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
|
||||
|
||||
elif energyQty == 'sparse':
|
||||
# 4-6
|
||||
if random.random() < 0.5:
|
||||
if self.random.random() < 0.5:
|
||||
self.itemManager.addItem('Reserve')
|
||||
else:
|
||||
self.itemManager.addItem('ETank')
|
||||
@@ -639,7 +641,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
|
||||
alreadyInPool = 2
|
||||
n = getE(3)
|
||||
for i in range(n):
|
||||
if random.random() < 0.5:
|
||||
if self.random.random() < 0.5:
|
||||
self.itemManager.addItem('Reserve')
|
||||
else:
|
||||
self.itemManager.addItem('ETank')
|
||||
@@ -676,15 +678,15 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator):
|
||||
return self.itemManager.getItemPool()
|
||||
|
||||
class ItemPoolGeneratorScavenger(ItemPoolGeneratorMajors):
|
||||
def __init__(self, itemManager, qty, sm, maxDiff):
|
||||
super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff)
|
||||
def __init__(self, itemManager, qty, sm, maxDiff, random):
|
||||
super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff, random)
|
||||
|
||||
def addNoEnergy(self):
|
||||
self.itemManager.addItem('Nothing')
|
||||
|
||||
class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors):
|
||||
def __init__(self, itemManager, qty, sm, nLocs, maxDiff):
|
||||
super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff)
|
||||
def __init__(self, itemManager, qty, sm, nLocs, maxDiff, random):
|
||||
super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff, random)
|
||||
self.maxItems = nLocs
|
||||
self.calcMaxAmmo()
|
||||
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))
|
||||
|
||||
class ItemPoolGeneratorPlando(ItemPoolGenerator):
|
||||
def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff):
|
||||
super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff)
|
||||
def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff, random):
|
||||
super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff, random)
|
||||
# in exclude dict:
|
||||
# in alreadyPlacedItems:
|
||||
# dict of 'itemType: count' of items already added in the plando.
|
||||
@@ -805,7 +807,7 @@ class ItemPoolGeneratorPlando(ItemPoolGenerator):
|
||||
if ammoQty:
|
||||
rangeDict = getRangeDict(ammoQty)
|
||||
while len(self.itemManager.getItemPool()) < maxItems and remain > 0:
|
||||
item = chooseFromRange(rangeDict)
|
||||
item = chooseFromRange(rangeDict, self.random)
|
||||
self.itemManager.addMinor(item)
|
||||
remain -= 1
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import sys, random, time
|
||||
import sys, time
|
||||
|
||||
from ..utils import log
|
||||
from ..logic.logic import Logic
|
||||
@@ -14,7 +14,7 @@ from ..utils.doorsmanager import DoorsManager
|
||||
|
||||
# entry point for rando execution ("randomize" method)
|
||||
class RandoExec(object):
|
||||
def __init__(self, seedName, vcr, randoSettings, graphSettings, player):
|
||||
def __init__(self, seedName, vcr, randoSettings, graphSettings, player, random):
|
||||
self.errorMsg = ""
|
||||
self.seedName = seedName
|
||||
self.vcr = vcr
|
||||
@@ -22,6 +22,7 @@ class RandoExec(object):
|
||||
self.graphSettings = graphSettings
|
||||
self.log = log.get('RandoExec')
|
||||
self.player = player
|
||||
self.random = random
|
||||
|
||||
# processes settings to :
|
||||
# - create Restrictions and GraphBuilder objects
|
||||
@@ -31,7 +32,7 @@ class RandoExec(object):
|
||||
vcr = VCR(self.seedName, 'rando') if self.vcr == True else None
|
||||
self.errorMsg = ""
|
||||
split = self.randoSettings.restrictions['MajorMinor']
|
||||
self.graphBuilder = GraphBuilder(self.graphSettings)
|
||||
self.graphBuilder = GraphBuilder(self.graphSettings, self.random)
|
||||
container = None
|
||||
i = 0
|
||||
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:
|
||||
self.restrictions = Restrictions(self.randoSettings)
|
||||
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)
|
||||
services = RandoServices(self.areaGraph, self.restrictions)
|
||||
setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player)
|
||||
services = RandoServices(self.areaGraph, self.restrictions, random=self.random)
|
||||
setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player, self.random)
|
||||
self.setup = setup
|
||||
container = setup.createItemLocContainer(endDate, vcr)
|
||||
if container is None:
|
||||
@@ -78,7 +79,7 @@ class RandoExec(object):
|
||||
n = nMaj
|
||||
elif split == 'Chozo':
|
||||
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):
|
||||
# hide some items like in dessy's
|
||||
@@ -89,7 +90,7 @@ class RandoExec(object):
|
||||
if (item.Category != "Nothing"
|
||||
and loc.CanHidden == True
|
||||
and loc.Visibility == 'Visible'):
|
||||
if bool(random.getrandbits(1)) == True:
|
||||
if bool(self.random.getrandbits(1)) == True:
|
||||
loc.Visibility = 'Hidden'
|
||||
# put nothing in unfilled locations
|
||||
filledLocNames = [il.Location.Name for il in itemLocs]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
import copy, random, sys, logging, os
|
||||
import copy, sys, logging, os
|
||||
from enum import Enum, unique
|
||||
from ..utils import log
|
||||
from ..utils.parameters import infinity
|
||||
@@ -19,12 +18,13 @@ class ComebackCheckType(Enum):
|
||||
|
||||
# collection of stateless services to be used mainly by fillers
|
||||
class RandoServices(object):
|
||||
def __init__(self, graph, restrictions, cache=None):
|
||||
def __init__(self, graph, restrictions, cache=None, *, random):
|
||||
self.restrictions = restrictions
|
||||
self.settings = restrictions.settings
|
||||
self.areaGraph = graph
|
||||
self.cache = cache
|
||||
self.log = log.get('RandoServices')
|
||||
self.random = random
|
||||
|
||||
@staticmethod
|
||||
def printProgress(s):
|
||||
@@ -217,7 +217,7 @@ class RandoServices(object):
|
||||
# choose a morph item location in that context
|
||||
morphItemLoc = ItemLocation(
|
||||
morph,
|
||||
random.choice(morphLocs)
|
||||
self.random.choice(morphLocs)
|
||||
)
|
||||
# acquire morph in new context and see if we can still open new locs
|
||||
newAP = self.collect(ap, containerCpy, morphItemLoc)
|
||||
@@ -232,7 +232,7 @@ class RandoServices(object):
|
||||
if morphLocItem is None or len(itemLocDict) == 1:
|
||||
# no morph, or it is the only possibility: nothing to do
|
||||
return
|
||||
morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem])
|
||||
morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem], self.random)
|
||||
if morphLocs is not None:
|
||||
itemLocDict[morphLocItem] = morphLocs
|
||||
else:
|
||||
@@ -380,10 +380,10 @@ class RandoServices(object):
|
||||
(itemLocDict, isProg) = self.getPossiblePlacements(ap, container, ComebackCheckType.NoCheck)
|
||||
assert not isProg
|
||||
items = list(itemLocDict.keys())
|
||||
random.shuffle(items)
|
||||
self.random.shuffle(items)
|
||||
for item in items:
|
||||
cont = copy.copy(container)
|
||||
loc = random.choice(itemLocDict[item])
|
||||
loc = self.random.choice(itemLocDict[item])
|
||||
itemLoc1 = ItemLocation(item, loc)
|
||||
self.log.debug("itemLoc1 attempt: "+getItemLocStr(itemLoc1))
|
||||
newAP = self.collect(ap, cont, itemLoc1)
|
||||
@@ -391,8 +391,8 @@ class RandoServices(object):
|
||||
self.cache.reset()
|
||||
(ild, isProg) = self.getPossiblePlacements(newAP, cont, ComebackCheckType.NoCheck)
|
||||
if isProg:
|
||||
item2 = random.choice(list(ild.keys()))
|
||||
itemLoc2 = ItemLocation(item2, random.choice(ild[item2]))
|
||||
item2 = self.random.choice(list(ild.keys()))
|
||||
itemLoc2 = ItemLocation(item2, self.random.choice(ild[item2]))
|
||||
self.log.debug("itemLoc2: "+getItemLocStr(itemLoc2))
|
||||
return (itemLoc1, itemLoc2)
|
||||
return None
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
import sys, random
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from ..rando.Items import ItemManager
|
||||
from ..utils.utils import getRangeDict, chooseFromRange
|
||||
@@ -32,11 +31,11 @@ class RandoSettings(object):
|
||||
def isPlandoRando(self):
|
||||
return self.PlandoOptions is not None
|
||||
|
||||
def getItemManager(self, smbm, nLocs, bossesItems):
|
||||
def getItemManager(self, smbm, nLocs, bossesItems, random):
|
||||
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:
|
||||
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):
|
||||
if not self.isPlandoRando():
|
||||
@@ -94,7 +93,7 @@ class ProgSpeedParameters(object):
|
||||
self.restrictions = restrictions
|
||||
self.nLocs = nLocs
|
||||
|
||||
def getVariableSpeed(self):
|
||||
def getVariableSpeed(self, random):
|
||||
ranges = getRangeDict({
|
||||
'slowest':7,
|
||||
'slow':20,
|
||||
@@ -102,7 +101,7 @@ class ProgSpeedParameters(object):
|
||||
'fast':27,
|
||||
'fastest':11
|
||||
})
|
||||
return chooseFromRange(ranges)
|
||||
return chooseFromRange(ranges, random)
|
||||
|
||||
def getMinorHelpProb(self, progSpeed):
|
||||
if self.restrictions.split != 'Major':
|
||||
@@ -134,7 +133,7 @@ class ProgSpeedParameters(object):
|
||||
def isSlow(self, progSpeed):
|
||||
return progSpeed == "slow" or (progSpeed == "slowest" and self.restrictions.split == "Chozo")
|
||||
|
||||
def getItemLimit(self, progSpeed):
|
||||
def getItemLimit(self, progSpeed, random):
|
||||
itemLimit = self.nLocs
|
||||
if self.isSlow(progSpeed):
|
||||
itemLimit = int(self.nLocs*0.209) # 21 for 105
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import copy, random
|
||||
import copy
|
||||
|
||||
from ..utils import log
|
||||
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
|
||||
# the entry point is createItemLocContainer
|
||||
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.random = random
|
||||
self.settings = services.settings
|
||||
self.graphSettings = graphSettings
|
||||
self.startAP = graphSettings.startAP
|
||||
@@ -31,7 +32,7 @@ class RandoSetup(object):
|
||||
# print("nLocs Setup: "+str(len(self.locations)))
|
||||
# in minimizer we can have some missing boss locs
|
||||
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.restrictedLocs = []
|
||||
self.lastRestricted = []
|
||||
@@ -165,7 +166,7 @@ class RandoSetup(object):
|
||||
return True
|
||||
self.log.debug("********* PRE RANDO START")
|
||||
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)
|
||||
(isStuck, itemLocations, progItems) = filler.generateItems(condition)
|
||||
self.log.debug("********* PRE RANDO END")
|
||||
@@ -345,9 +346,9 @@ class RandoSetup(object):
|
||||
def getForbiddenItemsFromList(self, itemList):
|
||||
self.log.debug('getForbiddenItemsFromList: ' + str(itemList))
|
||||
remove = []
|
||||
n = randGaussBounds(len(itemList))
|
||||
n = randGaussBounds(self.random, len(itemList))
|
||||
for i in range(n):
|
||||
idx = random.randint(0, len(itemList) - 1)
|
||||
idx = self.random.randint(0, len(itemList) - 1)
|
||||
item = itemList.pop(idx)
|
||||
if item is not None:
|
||||
remove.append(item)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import copy, random
|
||||
import copy
|
||||
from ..utils import log
|
||||
from ..graph.graph_utils import getAccessPoint
|
||||
from ..rando.ItemLocContainer import getLocListStr
|
||||
@@ -112,7 +112,7 @@ class Restrictions(object):
|
||||
return item.Class == "Minor"
|
||||
|
||||
# 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
|
||||
proba = random.randint(0, self.lateMorphLimit)
|
||||
if self.split == 'Full':
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
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.RandoExec import RandoExec
|
||||
@@ -39,7 +39,7 @@ objectives = defaultMultiValues['objective']
|
||||
tourians = defaultMultiValues['tourian']
|
||||
areaRandomizations = defaultMultiValues['areaRandomization']
|
||||
|
||||
def randomMulti(args, param, defaultMultiValues):
|
||||
def randomMulti(args, param, defaultMultiValues, random):
|
||||
value = args[param]
|
||||
|
||||
isRandom = False
|
||||
@@ -250,10 +250,11 @@ class VariaRandomizer:
|
||||
parser.add_argument('--tourianList', help="list to choose from when random",
|
||||
dest='tourianList', nargs='?', default=None)
|
||||
|
||||
def __init__(self, options, rom, player):
|
||||
def __init__(self, options, rom, player, seed, random):
|
||||
# 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.player = player
|
||||
self.random = random
|
||||
args = self.args
|
||||
args.rom = rom
|
||||
# args.startLocation = to_pascal_case_with_space(options.startLocation.current_key)
|
||||
@@ -323,11 +324,13 @@ class VariaRandomizer:
|
||||
|
||||
logger.debug("preset: {}".format(preset))
|
||||
|
||||
# if no seed given, choose one
|
||||
if args.seed == 0:
|
||||
self.seed = random.randrange(sys.maxsize)
|
||||
else:
|
||||
self.seed = args.seed
|
||||
# Archipelago provides a seed for the multiworld.
|
||||
self.seed = seed
|
||||
# # if no seed given, choose one
|
||||
# if args.seed == 0:
|
||||
# self.seed = random.randrange(sys.maxsize)
|
||||
# else:
|
||||
# self.seed = args.seed
|
||||
logger.debug("seed: {}".format(self.seed))
|
||||
|
||||
if args.raceMagic is not None:
|
||||
@@ -360,12 +363,12 @@ class VariaRandomizer:
|
||||
logger.debug("maxDifficulty: {}".format(self.maxDifficulty))
|
||||
|
||||
# handle random parameters with dynamic pool of values
|
||||
(_, progSpeed) = randomMulti(args.__dict__, "progressionSpeed", speeds)
|
||||
(_, progDiff) = randomMulti(args.__dict__, "progressionDifficulty", progDiffs)
|
||||
(majorsSplitRandom, args.majorsSplit) = randomMulti(args.__dict__, "majorsSplit", majorsSplits)
|
||||
(_, self.gravityBehaviour) = randomMulti(args.__dict__, "gravityBehaviour", gravityBehaviours)
|
||||
(_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians)
|
||||
(areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations)
|
||||
(_, progSpeed) = randomMulti(args.__dict__, "progressionSpeed", speeds, random)
|
||||
(_, progDiff) = randomMulti(args.__dict__, "progressionDifficulty", progDiffs, random)
|
||||
(majorsSplitRandom, args.majorsSplit) = randomMulti(args.__dict__, "majorsSplit", majorsSplits, random)
|
||||
(_, self.gravityBehaviour) = randomMulti(args.__dict__, "gravityBehaviour", gravityBehaviours, random)
|
||||
(_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians, random)
|
||||
(areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations, random)
|
||||
areaRandomization = args.area in ['light', 'full']
|
||||
lightArea = args.area == 'light'
|
||||
|
||||
@@ -626,7 +629,7 @@ class VariaRandomizer:
|
||||
if args.objective:
|
||||
if (args.objectiveRandom):
|
||||
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:
|
||||
maxActiveGoals = Objectives.maxActiveGoals - addedObjectives
|
||||
if len(args.objective) > maxActiveGoals:
|
||||
@@ -660,7 +663,7 @@ class VariaRandomizer:
|
||||
# print("energyQty:{}".format(energyQty))
|
||||
|
||||
#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()
|
||||
# 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.
|
||||
@@ -690,7 +693,7 @@ class VariaRandomizer:
|
||||
'gameend.ips', 'grey_door_animals.ips', 'low_timer.ips', 'metalimals.ips',
|
||||
'phantoonimals.ips', 'ridleyimals.ips']
|
||||
if args.escapeRando == False:
|
||||
args.patches.append(random.choice(animalsPatches))
|
||||
args.patches.append(self.random.choice(animalsPatches))
|
||||
args.patches.append("Escape_Animals_Change_Event")
|
||||
else:
|
||||
optErrMsgs.append("Ignored animals surprise because of escape randomization")
|
||||
@@ -760,9 +763,9 @@ class VariaRandomizer:
|
||||
# patch local rom
|
||||
# romFileName = args.rom
|
||||
# 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:
|
||||
romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic)
|
||||
romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, random=self.random)
|
||||
|
||||
if customPrePatchApply != None:
|
||||
customPrePatchApply(romPatcher)
|
||||
|
||||
@@ -49,7 +49,7 @@ class RomPatcher:
|
||||
'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.settings = settings
|
||||
#self.romFileName = romFileName
|
||||
@@ -76,6 +76,7 @@ class RomPatcher:
|
||||
0x93ea: self.forceRoomCRE
|
||||
}
|
||||
self.player = player
|
||||
self.random = random
|
||||
|
||||
def patchRom(self):
|
||||
self.applyIPSPatches()
|
||||
@@ -496,9 +497,9 @@ class RomPatcher:
|
||||
self.ipsPatches = []
|
||||
|
||||
def writeSeed(self, seed):
|
||||
random.seed(seed)
|
||||
seedInfo = random.randint(0, 0xFFFF)
|
||||
seedInfo2 = random.randint(0, 0xFFFF)
|
||||
r = random.Random(seed)
|
||||
seedInfo = r.randint(0, 0xFFFF)
|
||||
seedInfo2 = r.randint(0, 0xFFFF)
|
||||
self.romFile.writeWord(seedInfo, snes_to_pc(0xdfff00))
|
||||
self.romFile.writeWord(seedInfo2)
|
||||
|
||||
@@ -1066,7 +1067,7 @@ class RomPatcher:
|
||||
|
||||
def writeObjectives(self, itemLocs, tourian):
|
||||
objectives = Objectives.objDict[self.player]
|
||||
objectives.writeGoals(self.romFile)
|
||||
objectives.writeGoals(self.romFile, self.random)
|
||||
objectives.writeIntroObjectives(self.romFile, tourian)
|
||||
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
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import random
|
||||
from enum import IntEnum,IntFlag
|
||||
import copy
|
||||
from ..logic.smbool import SMBool
|
||||
@@ -123,7 +122,7 @@ class Door(object):
|
||||
else:
|
||||
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.canGrey and allowGreyDoors:
|
||||
self.setColor(random.choice(self.filterColorList(colorsListGrey)))
|
||||
@@ -347,9 +346,9 @@ class DoorsManager():
|
||||
currentDoors['CrabShaftRight'].forceBlue()
|
||||
|
||||
@staticmethod
|
||||
def randomize(allowGreyDoors, player):
|
||||
def randomize(allowGreyDoors, player, random):
|
||||
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
|
||||
toiletTop = DoorsManager.doorsDict[player]['PlasmaSparkBottom']
|
||||
toiletBottom = DoorsManager.doorsDict[player]['OasisTop']
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import copy
|
||||
import random
|
||||
from ..rom.addresses import Addresses
|
||||
from ..rom.rom import pc_to_snes
|
||||
from ..logic.helpers import Bosses
|
||||
@@ -28,7 +27,7 @@ class Synonyms(object):
|
||||
]
|
||||
alreadyUsed = []
|
||||
@staticmethod
|
||||
def getVerb():
|
||||
def getVerb(random):
|
||||
verb = random.choice(Synonyms.killSynonyms)
|
||||
while verb in Synonyms.alreadyUsed:
|
||||
verb = random.choice(Synonyms.killSynonyms)
|
||||
@@ -88,10 +87,10 @@ class Goal(object):
|
||||
# not all objectives require an ap (like limit objectives)
|
||||
return self.clearFunc(smbm, ap)
|
||||
|
||||
def getText(self):
|
||||
def getText(self, random):
|
||||
out = "{}. ".format(self.rank)
|
||||
if self.useSynonym:
|
||||
out += self.text.format(Synonyms.getVerb())
|
||||
out += self.text.format(Synonyms.getVerb(random))
|
||||
else:
|
||||
out += self.text
|
||||
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")]
|
||||
|
||||
# call from rando
|
||||
def setRandom(self, nbGoals, availableGoals):
|
||||
def setRandom(self, nbGoals, availableGoals, random):
|
||||
while self.nbActiveGoals < nbGoals and availableGoals:
|
||||
goalName = random.choice(availableGoals)
|
||||
self.addGoal(goalName)
|
||||
@@ -702,7 +701,7 @@ class Objectives(object):
|
||||
LOG.debug("tourianRequired: {}".format(self.tourianRequired))
|
||||
|
||||
# call from rando
|
||||
def writeGoals(self, romFile):
|
||||
def writeGoals(self, romFile, random):
|
||||
# write check functions
|
||||
romFile.seek(Addresses.getOne('objectivesList'))
|
||||
for goal in self.activeGoals:
|
||||
@@ -736,7 +735,7 @@ class Objectives(object):
|
||||
space = 3 if self.nbActiveGoals == 5 else 4
|
||||
for i, goal in enumerate(self.activeGoals):
|
||||
addr = baseAddr + i * lineLength * space
|
||||
text = goal.getText()
|
||||
text = goal.getText(random)
|
||||
romFile.seek(addr)
|
||||
for c in text:
|
||||
if c not in char2tile:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import io
|
||||
import os, json, re, random
|
||||
import os, json, re
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Any
|
||||
@@ -88,7 +88,7 @@ def normalizeRounding(n):
|
||||
|
||||
# gauss random in [0, r] range
|
||||
# the higher the slope, the less probable extreme values are.
|
||||
def randGaussBounds(r, slope=5):
|
||||
def randGaussBounds(random, r, slope=5):
|
||||
r = float(r)
|
||||
n = normalizeRounding(random.gauss(r/2, r/slope))
|
||||
if n < 0:
|
||||
@@ -111,7 +111,7 @@ def getRangeDict(weightDict):
|
||||
|
||||
return rangeDict
|
||||
|
||||
def chooseFromRange(rangeDict):
|
||||
def chooseFromRange(rangeDict, random):
|
||||
r = random.random()
|
||||
val = None
|
||||
for v in sorted(rangeDict, key=rangeDict.get):
|
||||
|
||||
0
worlds/smw/Names/__init__.py
Normal file
0
worlds/smw/Names/__init__.py
Normal file
0
worlds/tww/randomizers/__init__.py
Normal file
0
worlds/tww/randomizers/__init__.py
Normal file
0
worlds/witness/data/settings/__init__.py
Normal file
0
worlds/witness/data/settings/__init__.py
Normal file
@@ -617,7 +617,7 @@ if is_easter_time():
|
||||
easter_special_option_group = OptionGroup("EASTER SPECIAL", [
|
||||
EasterEggHunt,
|
||||
])
|
||||
witness_option_groups = [easter_special_option_group, *witness_option_groups]
|
||||
witness_option_groups.insert(2, easter_special_option_group)
|
||||
else:
|
||||
silly_options_group = next(group for group in witness_option_groups if group.name == "Silly Options")
|
||||
silly_options_group.options.append(EasterEggHunt)
|
||||
|
||||
Reference in New Issue
Block a user