mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-30 08:43:20 -07:00
Merge branch 'main' into tunc-logic-rules-redux
This commit is contained in:
@@ -112,7 +112,7 @@ class AdventureContext(CommonContext):
|
||||
if ': !' not in msg:
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
elif cmd == "ReceivedItems":
|
||||
msg = f"Received {', '.join([self.item_names.lookup_in_slot(item.item) for item in args['items']])}"
|
||||
msg = f"Received {', '.join([self.item_names.lookup_in_game(item.item) for item in args['items']])}"
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
elif cmd == "Retrieved":
|
||||
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
|
||||
|
||||
@@ -225,6 +225,9 @@ class CommonContext:
|
||||
def lookup_in_slot(self, code: int, slot: typing.Optional[int] = None) -> str:
|
||||
"""Returns the name for an item/location id in the context of a specific slot or own slot if `slot` is
|
||||
omitted.
|
||||
|
||||
Use of `lookup_in_slot` should not be used when not connected to a server. If looking in own game, set
|
||||
`ctx.game` and use `lookup_in_game` method instead.
|
||||
"""
|
||||
if slot is None:
|
||||
slot = self.ctx.slot
|
||||
|
||||
@@ -247,8 +247,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
toDraw = ""
|
||||
for i in range(20):
|
||||
if i < len(str(ctx.item_names.lookup_in_slot(l.item))):
|
||||
toDraw += str(ctx.item_names.lookup_in_slot(l.item))[i]
|
||||
if i < len(str(ctx.item_names.lookup_in_game(l.item))):
|
||||
toDraw += str(ctx.item_names.lookup_in_game(l.item))[i]
|
||||
else:
|
||||
break
|
||||
f.write(toDraw)
|
||||
|
||||
@@ -176,7 +176,7 @@ class WargrooveContext(CommonContext):
|
||||
if not os.path.isfile(path):
|
||||
open(path, 'w').close()
|
||||
# Announcing commander unlocks
|
||||
item_name = self.item_names.lookup_in_slot(network_item.item)
|
||||
item_name = self.item_names.lookup_in_game(network_item.item)
|
||||
if item_name in faction_table.keys():
|
||||
for commander in faction_table[item_name]:
|
||||
logger.info(f"{commander.name} has been unlocked!")
|
||||
@@ -197,7 +197,7 @@ class WargrooveContext(CommonContext):
|
||||
open(print_path, 'w').close()
|
||||
with open(print_path, 'w') as f:
|
||||
f.write("Received " +
|
||||
self.item_names.lookup_in_slot(network_item.item) +
|
||||
self.item_names.lookup_in_game(network_item.item) +
|
||||
" from " +
|
||||
self.player_names[network_item.player])
|
||||
f.close()
|
||||
@@ -342,7 +342,7 @@ class WargrooveContext(CommonContext):
|
||||
faction_items = 0
|
||||
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
|
||||
for network_item in self.items_received:
|
||||
if self.item_names.lookup_in_slot(network_item.item) in faction_item_names:
|
||||
if self.item_names.lookup_in_game(network_item.item) in faction_item_names:
|
||||
faction_items += 1
|
||||
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
|
||||
# Must be an integer larger than 0
|
||||
|
||||
@@ -8,7 +8,8 @@ from . import cache
|
||||
def robots():
|
||||
# If this host is not official, do not allow search engine crawling
|
||||
if not app.config["ASSET_RIGHTS"]:
|
||||
return app.send_static_file('robots.txt')
|
||||
# filename changed in case the path is intercepted and served by an outside service
|
||||
return app.send_static_file('robots_file.txt')
|
||||
|
||||
# Send 404 if the host has affirmed this to be the official WebHost
|
||||
abort(404)
|
||||
|
||||
@@ -1366,28 +1366,28 @@ if "Starcraft 2" in network_data_package["games"]:
|
||||
organics_icon_base_url = "https://0rganics.org/archipelago/sc2wol/"
|
||||
|
||||
icons = {
|
||||
"Starting Minerals": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-mineral-protoss.png",
|
||||
"Starting Vespene": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-gas-terran.png",
|
||||
"Starting Minerals": github_icon_base_url + "blizzard/icon-mineral-nobg.png",
|
||||
"Starting Vespene": github_icon_base_url + "blizzard/icon-gas-terran-nobg.png",
|
||||
"Starting Supply": github_icon_base_url + "blizzard/icon-supply-terran_nobg.png",
|
||||
|
||||
"Terran Infantry Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel1.png",
|
||||
"Terran Infantry Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel2.png",
|
||||
"Terran Infantry Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel3.png",
|
||||
"Terran Infantry Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel1.png",
|
||||
"Terran Infantry Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel2.png",
|
||||
"Terran Infantry Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel3.png",
|
||||
"Terran Vehicle Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel1.png",
|
||||
"Terran Vehicle Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel2.png",
|
||||
"Terran Vehicle Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel3.png",
|
||||
"Terran Vehicle Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel1.png",
|
||||
"Terran Vehicle Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel2.png",
|
||||
"Terran Vehicle Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel3.png",
|
||||
"Terran Ship Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel1.png",
|
||||
"Terran Ship Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel2.png",
|
||||
"Terran Ship Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel3.png",
|
||||
"Terran Ship Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel1.png",
|
||||
"Terran Ship Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel2.png",
|
||||
"Terran Ship Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel3.png",
|
||||
"Terran Infantry Weapons Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryweaponslevel1.png",
|
||||
"Terran Infantry Weapons Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryweaponslevel2.png",
|
||||
"Terran Infantry Weapons Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryweaponslevel3.png",
|
||||
"Terran Infantry Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryarmorlevel1.png",
|
||||
"Terran Infantry Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryarmorlevel2.png",
|
||||
"Terran Infantry Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryarmorlevel3.png",
|
||||
"Terran Vehicle Weapons Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleweaponslevel1.png",
|
||||
"Terran Vehicle Weapons Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleweaponslevel2.png",
|
||||
"Terran Vehicle Weapons Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleweaponslevel3.png",
|
||||
"Terran Vehicle Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleplatinglevel1.png",
|
||||
"Terran Vehicle Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleplatinglevel2.png",
|
||||
"Terran Vehicle Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleplatinglevel3.png",
|
||||
"Terran Ship Weapons Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-shipweaponslevel1.png",
|
||||
"Terran Ship Weapons Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-shipweaponslevel2.png",
|
||||
"Terran Ship Weapons Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-shipweaponslevel3.png",
|
||||
"Terran Ship Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-shipplatinglevel1.png",
|
||||
"Terran Ship Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-shipplatinglevel2.png",
|
||||
"Terran Ship Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-shipplatinglevel3.png",
|
||||
|
||||
"Bunker": "https://static.wikia.nocookie.net/starcraft/images/c/c5/Bunker_SC2_Icon1.jpg",
|
||||
"Missile Turret": "https://static.wikia.nocookie.net/starcraft/images/5/5f/MissileTurret_SC2_Icon1.jpg",
|
||||
|
||||
@@ -152,7 +152,7 @@ def get_payload(ctx: ZeldaContext):
|
||||
|
||||
|
||||
def reconcile_shops(ctx: ZeldaContext):
|
||||
checked_location_names = [ctx.location_names.lookup_in_slot(location) for location in ctx.checked_locations]
|
||||
checked_location_names = [ctx.location_names.lookup_in_game(location) for location in ctx.checked_locations]
|
||||
shops = [location for location in checked_location_names if "Shop" in location]
|
||||
left_slots = [shop for shop in shops if "Left" in shop]
|
||||
middle_slots = [shop for shop in shops if "Middle" in shop]
|
||||
@@ -190,7 +190,7 @@ async def parse_locations(locations_array, ctx: ZeldaContext, force: bool, zone=
|
||||
locations_checked = []
|
||||
location = None
|
||||
for location in ctx.missing_locations:
|
||||
location_name = ctx.location_names.lookup_in_slot(location)
|
||||
location_name = ctx.location_names.lookup_in_game(location)
|
||||
|
||||
if location_name in Locations.overworld_locations and zone == "overworld":
|
||||
status = locations_array[Locations.major_location_offsets[location_name]]
|
||||
|
||||
@@ -87,9 +87,6 @@
|
||||
# Lingo
|
||||
/worlds/lingo/ @hatkirby
|
||||
|
||||
# Links Awakening DX
|
||||
/worlds/ladx/ @zig-for
|
||||
|
||||
# Lufia II Ancient Cave
|
||||
/worlds/lufia2ac/ @el-u
|
||||
/worlds/lufia2ac/docs/ @wordfcuk @el-u
|
||||
@@ -218,6 +215,8 @@
|
||||
# Final Fantasy (1)
|
||||
# /worlds/ff1/
|
||||
|
||||
# Links Awakening DX
|
||||
# /worlds/ladx/
|
||||
|
||||
## Disabled Unmaintained Worlds
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{
|
||||
Root: HKCR; Subkey: ".apworld"; ValueData: "{#MyAppName}worlddata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}worlddata"; ValueData: "Archipelago World Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}worlddata\DefaultIcon"; ValueData: "{app}\ArchipelagoLauncher.exe,0"; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}worlddata\shell\open\command"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1""";
|
||||
Root: HKCR; Subkey: "{#MyAppName}worlddata\shell\open\command"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1"""; ValueType: string; ValueName: "";
|
||||
|
||||
Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueData: "Archipegalo Protocol"; Flags: uninsdeletekey;
|
||||
Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueName: "URL Protocol"; ValueData: "";
|
||||
|
||||
@@ -292,12 +292,12 @@ class WorldTestBase(unittest.TestCase):
|
||||
"""Ensure all state can reach everything and complete the game with the defined options"""
|
||||
if not (self.run_default_tests and self.constructed):
|
||||
return
|
||||
with self.subTest("Game", game=self.game):
|
||||
with self.subTest("Game", game=self.game, seed=self.multiworld.seed):
|
||||
excluded = self.multiworld.worlds[self.player].options.exclude_locations.value
|
||||
state = self.multiworld.get_all_state(False)
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.name not in excluded:
|
||||
with self.subTest("Location should be reached", location=location):
|
||||
with self.subTest("Location should be reached", location=location.name):
|
||||
reachable = location.can_reach(state)
|
||||
self.assertTrue(reachable, f"{location.name} unreachable")
|
||||
with self.subTest("Beatable"):
|
||||
@@ -308,7 +308,7 @@ class WorldTestBase(unittest.TestCase):
|
||||
"""Ensure empty state can reach at least one location with the defined options"""
|
||||
if not (self.run_default_tests and self.constructed):
|
||||
return
|
||||
with self.subTest("Game", game=self.game):
|
||||
with self.subTest("Game", game=self.game, seed=self.multiworld.seed):
|
||||
state = CollectionState(self.multiworld)
|
||||
locations = self.multiworld.get_reachable_locations(state, self.player)
|
||||
self.assertGreater(len(locations), 0,
|
||||
@@ -329,7 +329,7 @@ class WorldTestBase(unittest.TestCase):
|
||||
for n in range(len(locations) - 1, -1, -1):
|
||||
if locations[n].can_reach(state):
|
||||
sphere.append(locations.pop(n))
|
||||
self.assertTrue(sphere or self.multiworld.accessibility[1] == "minimal",
|
||||
self.assertTrue(sphere or self.multiworld.worlds[1].options.accessibility == "minimal",
|
||||
f"Unreachable locations: {locations}")
|
||||
if not sphere:
|
||||
break
|
||||
|
||||
@@ -280,7 +280,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
future. Protocol level compatibility check moved to MultiServer.min_client_version.
|
||||
"""
|
||||
|
||||
required_server_version: Tuple[int, int, int] = (0, 2, 4)
|
||||
required_server_version: Tuple[int, int, int] = (0, 5, 0)
|
||||
"""update this if the resulting multidata breaks forward-compatibility of the server"""
|
||||
|
||||
hint_blacklist: ClassVar[FrozenSet[str]] = frozenset()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from worlds.adventure import location_table
|
||||
from worlds.adventure.Options import BatLogic, DifficultySwitchB, DifficultySwitchA
|
||||
from .Options import BatLogic, DifficultySwitchB
|
||||
from worlds.generic.Rules import add_rule, set_rule, forbid_item
|
||||
from BaseClasses import LocationProgressType
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
|
||||
@@ -339,7 +339,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
|
||||
def new_check(location_id):
|
||||
new_locations.append(location_id)
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
location = ctx.location_names.lookup_in_game(location_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ' +
|
||||
f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' +
|
||||
@@ -552,7 +552,7 @@ class ALTTPSNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
@@ -682,7 +682,7 @@ def get_alttp_settings(romfile: str):
|
||||
|
||||
if 'yes' in choice:
|
||||
import LttPAdjuster
|
||||
from worlds.alttp.Rom import get_base_rom_path
|
||||
from .Rom import get_base_rom_path
|
||||
last_settings.rom = romfile
|
||||
last_settings.baserom = get_base_rom_path()
|
||||
last_settings.world = None
|
||||
|
||||
@@ -1437,7 +1437,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player):
|
||||
invalid_cave_connections = defaultdict(set)
|
||||
|
||||
if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
|
||||
from worlds.alttp import OverworldGlitchRules
|
||||
from . import OverworldGlitchRules
|
||||
for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'):
|
||||
invalid_connections[entrance] = set()
|
||||
if entrance in must_be_exits:
|
||||
|
||||
@@ -486,7 +486,7 @@ class LTTPBosses(PlandoBosses):
|
||||
|
||||
@classmethod
|
||||
def can_place_boss(cls, boss: str, location: str) -> bool:
|
||||
from worlds.alttp.Bosses import can_place_boss
|
||||
from .Bosses import can_place_boss
|
||||
level = ''
|
||||
words = location.split(" ")
|
||||
if words[-1] in ("top", "middle", "bottom"):
|
||||
|
||||
@@ -406,7 +406,7 @@ def create_dungeon_region(world: MultiWorld, player: int, name: str, hint: str,
|
||||
|
||||
def _create_region(world: MultiWorld, player: int, name: str, type: LTTPRegionType, hint: str, locations=None,
|
||||
exits=None):
|
||||
from worlds.alttp.SubClasses import ALttPLocation
|
||||
from .SubClasses import ALttPLocation
|
||||
ret = LTTPRegion(name, type, hint, player, world)
|
||||
if exits:
|
||||
for exit in exits:
|
||||
@@ -760,7 +760,7 @@ location_table: typing.Dict[str,
|
||||
'Turtle Rock - Prize': (
|
||||
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
||||
|
||||
from worlds.alttp.Shops import shop_table_by_location_id, shop_table_by_location
|
||||
from .Shops import shop_table_by_location_id, shop_table_by_location
|
||||
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
||||
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}}
|
||||
lookup_id_to_name.update(shop_table_by_location_id)
|
||||
|
||||
@@ -405,16 +405,14 @@ def global_rules(multiworld: MultiWorld, player: int):
|
||||
set_rule(multiworld.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player))
|
||||
|
||||
set_rule(multiworld.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player))
|
||||
|
||||
if multiworld.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind":
|
||||
set_rule(multiworld.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player))
|
||||
|
||||
set_rule(multiworld.get_location('Thieves\' Town - Big Chest', player),
|
||||
lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player))
|
||||
|
||||
set_rule(multiworld.get_location('Thieves\' Town - Blind\'s Cell', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
|
||||
if multiworld.accessibility[player] != 'locations' and not multiworld.key_drop_shuffle[player]:
|
||||
set_always_allow(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player)
|
||||
|
||||
set_rule(multiworld.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3))
|
||||
set_rule(multiworld.get_location('Thieves\' Town - Spike Switch Pot Key', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
|
||||
|
||||
@@ -37,7 +37,8 @@ class TestThievesTown(TestDungeon):
|
||||
|
||||
["Thieves' Town - Blind's Cell", False, []],
|
||||
["Thieves' Town - Blind's Cell", False, [], ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Blind's Cell", True, ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Blind's Cell", False, [], ['Small Key (Thieves Town)']],
|
||||
["Thieves' Town - Blind's Cell", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']],
|
||||
|
||||
["Thieves' Town - Boss", False, []],
|
||||
["Thieves' Town - Boss", False, [], ['Big Key (Thieves Town)']],
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the beast form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class BeastFormAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -5,7 +5,7 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
under rock needing bind song option)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase, after_home_water_locations
|
||||
from . import AquariaTestBase, after_home_water_locations
|
||||
|
||||
|
||||
class BindSongAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -5,8 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
under rock needing bind song option)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from worlds.aquaria.test.test_bind_song_access import after_home_water_locations
|
||||
from . import AquariaTestBase
|
||||
from .test_bind_song_access import after_home_water_locations
|
||||
|
||||
|
||||
class BindSongOptionAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test accessibility of region with the home water confine via option
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class ConfinedHomeWaterAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the dual song
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class LiAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -5,7 +5,7 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
energy form option)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class EnergyFormAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the fish form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class FishFormAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without Li
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class LiAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without a light (Dumbo pet or sun form)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class LightAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the nature form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class NatureFormAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test that no progression items can be put in hard or hidden locations when option enabled
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
from BaseClasses import ItemClassification
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test that progression items can be put in hard or hidden locations when option disabled
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from BaseClasses import ItemClassification
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the spirit form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class SpiritFormAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the sun form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class SunFormAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -5,7 +5,7 @@ Description: Unit test used to test accessibility of region with the unconfined
|
||||
turtle and energy door
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class UnconfineHomeWaterBothAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test accessibility of region with the unconfined home water option via the energy door
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class UnconfineHomeWaterEnergyDoorAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test accessibility of region with the unconfined home water option via transturtle
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class UnconfineHomeWaterTransturtleAccessTest(AquariaTestBase):
|
||||
|
||||
@@ -146,7 +146,7 @@ class Castlevania64Client(BizHawkClient):
|
||||
text_color = bytearray([0xA2, 0x0B])
|
||||
else:
|
||||
text_color = bytearray([0xA2, 0x02])
|
||||
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_slot(next_item.item)}\n"
|
||||
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_game(next_item.item)}\n"
|
||||
f"from {ctx.player_names[next_item.player]}", 96)
|
||||
await bizhawk.guarded_write(ctx.bizhawk_ctx,
|
||||
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
|
||||
|
||||
@@ -60,7 +60,7 @@ class DKC3SNIClient(SNIClient):
|
||||
return
|
||||
|
||||
new_checks = []
|
||||
from worlds.dkc3.Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map
|
||||
from .Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map
|
||||
location_ram_data = await snes_read(ctx, WRAM_START + 0x5FE, 0x81)
|
||||
for loc_id, loc_data in location_rom_data.items():
|
||||
if loc_id not in ctx.locations_checked:
|
||||
@@ -86,7 +86,7 @@ class DKC3SNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
@@ -99,7 +99,7 @@ class DKC3SNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
|
||||
@@ -60,17 +60,18 @@ class DOOM2World(World):
|
||||
# Item ratio that scales depending on episode count. These are the ratio for 3 episode. In DOOM1.
|
||||
# The ratio have been tweaked seem, and feel good.
|
||||
items_ratio: Dict[str, float] = {
|
||||
"Armor": 41,
|
||||
"Mega Armor": 25,
|
||||
"Berserk": 12,
|
||||
"Armor": 39,
|
||||
"Mega Armor": 23,
|
||||
"Berserk": 11,
|
||||
"Invulnerability": 10,
|
||||
"Partial invisibility": 18,
|
||||
"Supercharge": 28,
|
||||
"Supercharge": 26,
|
||||
"Medikit": 15,
|
||||
"Box of bullets": 13,
|
||||
"Box of rockets": 13,
|
||||
"Box of shotgun shells": 13,
|
||||
"Energy cell pack": 10
|
||||
"Energy cell pack": 10,
|
||||
"Megasphere": 7
|
||||
}
|
||||
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
@@ -233,6 +234,7 @@ class DOOM2World(World):
|
||||
self.create_ratioed_items("Invulnerability", itempool)
|
||||
self.create_ratioed_items("Partial invisibility", itempool)
|
||||
self.create_ratioed_items("Supercharge", itempool)
|
||||
self.create_ratioed_items("Megasphere", itempool)
|
||||
|
||||
while len(itempool) < self.location_count:
|
||||
itempool.append(self.create_item(self.get_filler_item_name()))
|
||||
|
||||
@@ -247,7 +247,7 @@ async def game_watcher(ctx: FactorioContext):
|
||||
if ctx.locations_checked != research_data:
|
||||
bridge_logger.debug(
|
||||
f"New researches done: "
|
||||
f"{[ctx.location_names.lookup_in_slot(rid) for rid in research_data - ctx.locations_checked]}")
|
||||
f"{[ctx.location_names.lookup_in_game(rid) for rid in research_data - ctx.locations_checked]}")
|
||||
ctx.locations_checked = research_data
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
||||
death_link_tick = data.get("death_link_tick", 0)
|
||||
@@ -360,7 +360,7 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
||||
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
|
||||
item_id = transfer_item.item
|
||||
player_name = ctx.player_names[transfer_item.player]
|
||||
item_name = ctx.item_names.lookup_in_slot(item_id)
|
||||
item_name = ctx.item_names.lookup_in_game(item_id)
|
||||
factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.")
|
||||
commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}"
|
||||
ctx.send_index += 1
|
||||
|
||||
@@ -11,8 +11,8 @@ Some steps also assume use of Windows, so may vary with your OS.
|
||||
|
||||
## Installing the Archipelago software
|
||||
|
||||
The most recent public release of Archipelago can be found on the GitHub Releases page:
|
||||
[Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases).
|
||||
The most recent public release of Archipelago can be found on GitHub:
|
||||
[Archipelago Lastest Release](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
|
||||
|
||||
Run the exe file, and after accepting the license agreement you will be asked which components you would like to
|
||||
install.
|
||||
|
||||
@@ -71,6 +71,7 @@ class HereticWorld(World):
|
||||
"Tome of Power": 16,
|
||||
"Silver Shield": 10,
|
||||
"Enchanted Shield": 5,
|
||||
"Torch": 5,
|
||||
"Morph Ovum": 3,
|
||||
"Mystic Urn": 2,
|
||||
"Chaos Device": 1,
|
||||
@@ -242,6 +243,7 @@ class HereticWorld(World):
|
||||
self.create_ratioed_items("Mystic Urn", itempool)
|
||||
self.create_ratioed_items("Ring of Invincibility", itempool)
|
||||
self.create_ratioed_items("Shadowsphere", itempool)
|
||||
self.create_ratioed_items("Torch", itempool)
|
||||
self.create_ratioed_items("Timebomb of the Ancients", itempool)
|
||||
self.create_ratioed_items("Tome of Power", itempool)
|
||||
self.create_ratioed_items("Silver Shield", itempool)
|
||||
|
||||
@@ -330,7 +330,7 @@ class KDL3SNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_amount]
|
||||
recv_amount += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_amount, len(ctx.items_received)))
|
||||
|
||||
@@ -415,7 +415,7 @@ class KDL3SNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/'
|
||||
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
|
||||
@@ -4,6 +4,7 @@ import importlib.machinery
|
||||
import os
|
||||
import pkgutil
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .romTables import ROMWithTables
|
||||
from . import assembler
|
||||
@@ -67,10 +68,14 @@ from BaseClasses import ItemClassification
|
||||
from ..Locations import LinksAwakeningLocation
|
||||
from ..Options import TrendyGame, Palette, MusicChangeCondition, BootsControls
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import LinksAwakeningWorld
|
||||
|
||||
|
||||
# Function to generate a final rom, this patches the rom with all required patches
|
||||
def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, multiworld=None, player_name=None, player_names=[], player_id = 0):
|
||||
def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
rom_patches = []
|
||||
player_names = list(world.multiworld.player_name.values())
|
||||
|
||||
rom = ROMWithTables(args.input_filename, rom_patches)
|
||||
rom.player_names = player_names
|
||||
@@ -84,10 +89,10 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
for pymod in pymods:
|
||||
pymod.prePatch(rom)
|
||||
|
||||
if settings.gfxmod:
|
||||
patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", settings.gfxmod))
|
||||
if world.ladxr_settings.gfxmod:
|
||||
patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", world.ladxr_settings.gfxmod))
|
||||
|
||||
item_list = [item for item in logic.iteminfo_list if not isinstance(item, KeyLocation)]
|
||||
item_list = [item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]
|
||||
|
||||
assembler.resetConsts()
|
||||
assembler.const("INV_SIZE", 16)
|
||||
@@ -116,7 +121,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
assembler.const("wLinkSpawnDelay", 0xDE13)
|
||||
|
||||
#assembler.const("HARDWARE_LINK", 1)
|
||||
assembler.const("HARD_MODE", 1 if settings.hardmode != "none" else 0)
|
||||
assembler.const("HARD_MODE", 1 if world.ladxr_settings.hardmode != "none" else 0)
|
||||
|
||||
patches.core.cleanup(rom)
|
||||
patches.save.singleSaveSlot(rom)
|
||||
@@ -130,7 +135,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
patches.core.easyColorDungeonAccess(rom)
|
||||
patches.owl.removeOwlEvents(rom)
|
||||
patches.enemies.fixArmosKnightAsMiniboss(rom)
|
||||
patches.bank3e.addBank3E(rom, auth, player_id, player_names)
|
||||
patches.bank3e.addBank3E(rom, world.multi_key, world.player, player_names)
|
||||
patches.bank3f.addBank3F(rom)
|
||||
patches.bank34.addBank34(rom, item_list)
|
||||
patches.core.removeGhost(rom)
|
||||
@@ -141,10 +146,11 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
|
||||
from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys
|
||||
|
||||
if ap_settings["shuffle_small_keys"] != ShuffleSmallKeys.option_original_dungeon or ap_settings["shuffle_nightmare_keys"] != ShuffleNightmareKeys.option_original_dungeon:
|
||||
if world.options.shuffle_small_keys != ShuffleSmallKeys.option_original_dungeon or\
|
||||
world.options.shuffle_nightmare_keys != ShuffleNightmareKeys.option_original_dungeon:
|
||||
patches.inventory.advancedInventorySubscreen(rom)
|
||||
patches.inventory.moreSlots(rom)
|
||||
if settings.witch:
|
||||
if world.ladxr_settings.witch:
|
||||
patches.witch.updateWitch(rom)
|
||||
patches.softlock.fixAll(rom)
|
||||
patches.maptweaks.tweakMap(rom)
|
||||
@@ -158,9 +164,9 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
patches.tarin.updateTarin(rom)
|
||||
patches.fishingMinigame.updateFinishingMinigame(rom)
|
||||
patches.health.upgradeHealthContainers(rom)
|
||||
if settings.owlstatues in ("dungeon", "both"):
|
||||
if world.ladxr_settings.owlstatues in ("dungeon", "both"):
|
||||
patches.owl.upgradeDungeonOwlStatues(rom)
|
||||
if settings.owlstatues in ("overworld", "both"):
|
||||
if world.ladxr_settings.owlstatues in ("overworld", "both"):
|
||||
patches.owl.upgradeOverworldOwlStatues(rom)
|
||||
patches.goldenLeaf.fixGoldenLeaf(rom)
|
||||
patches.heartPiece.fixHeartPiece(rom)
|
||||
@@ -170,106 +176,110 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
patches.songs.upgradeMarin(rom)
|
||||
patches.songs.upgradeManbo(rom)
|
||||
patches.songs.upgradeMamu(rom)
|
||||
if settings.tradequest:
|
||||
patches.tradeSequence.patchTradeSequence(rom, settings.boomerang)
|
||||
if world.ladxr_settings.tradequest:
|
||||
patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings.boomerang)
|
||||
else:
|
||||
# Monkey bridge patch, always have the bridge there.
|
||||
rom.patch(0x00, 0x333D, assembler.ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True)
|
||||
patches.bowwow.fixBowwow(rom, everywhere=settings.bowwow != 'normal')
|
||||
if settings.bowwow != 'normal':
|
||||
patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal')
|
||||
if world.ladxr_settings.bowwow != 'normal':
|
||||
patches.bowwow.bowwowMapPatches(rom)
|
||||
patches.desert.desertAccess(rom)
|
||||
if settings.overworld == 'dungeondive':
|
||||
if world.ladxr_settings.overworld == 'dungeondive':
|
||||
patches.overworld.patchOverworldTilesets(rom)
|
||||
patches.overworld.createDungeonOnlyOverworld(rom)
|
||||
elif settings.overworld == 'nodungeons':
|
||||
elif world.ladxr_settings.overworld == 'nodungeons':
|
||||
patches.dungeon.patchNoDungeons(rom)
|
||||
elif settings.overworld == 'random':
|
||||
elif world.ladxr_settings.overworld == 'random':
|
||||
patches.overworld.patchOverworldTilesets(rom)
|
||||
mapgen.store_map(rom, logic.world.map)
|
||||
mapgen.store_map(rom, world.ladxr_logic.world.map)
|
||||
#if settings.dungeon_items == 'keysy':
|
||||
# patches.dungeon.removeKeyDoors(rom)
|
||||
# patches.reduceRNG.slowdownThreeOfAKind(rom)
|
||||
patches.reduceRNG.fixHorseHeads(rom)
|
||||
patches.bomb.onlyDropBombsWhenHaveBombs(rom)
|
||||
if ap_settings['music_change_condition'] == MusicChangeCondition.option_always:
|
||||
if world.options.music_change_condition == MusicChangeCondition.option_always:
|
||||
patches.aesthetics.noSwordMusic(rom)
|
||||
patches.aesthetics.reduceMessageLengths(rom, rnd)
|
||||
patches.aesthetics.reduceMessageLengths(rom, world.random)
|
||||
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
|
||||
if settings.music == 'random':
|
||||
patches.music.randomizeMusic(rom, rnd)
|
||||
elif settings.music == 'off':
|
||||
if world.ladxr_settings.music == 'random':
|
||||
patches.music.randomizeMusic(rom, world.random)
|
||||
elif world.ladxr_settings.music == 'off':
|
||||
patches.music.noMusic(rom)
|
||||
if settings.noflash:
|
||||
if world.ladxr_settings.noflash:
|
||||
patches.aesthetics.removeFlashingLights(rom)
|
||||
if settings.hardmode == "oracle":
|
||||
if world.ladxr_settings.hardmode == "oracle":
|
||||
patches.hardMode.oracleMode(rom)
|
||||
elif settings.hardmode == "hero":
|
||||
elif world.ladxr_settings.hardmode == "hero":
|
||||
patches.hardMode.heroMode(rom)
|
||||
elif settings.hardmode == "ohko":
|
||||
elif world.ladxr_settings.hardmode == "ohko":
|
||||
patches.hardMode.oneHitKO(rom)
|
||||
if settings.superweapons:
|
||||
if world.ladxr_settings.superweapons:
|
||||
patches.weapons.patchSuperWeapons(rom)
|
||||
if settings.textmode == 'fast':
|
||||
if world.ladxr_settings.textmode == 'fast':
|
||||
patches.aesthetics.fastText(rom)
|
||||
if settings.textmode == 'none':
|
||||
if world.ladxr_settings.textmode == 'none':
|
||||
patches.aesthetics.fastText(rom)
|
||||
patches.aesthetics.noText(rom)
|
||||
if not settings.nagmessages:
|
||||
if not world.ladxr_settings.nagmessages:
|
||||
patches.aesthetics.removeNagMessages(rom)
|
||||
if settings.lowhpbeep == 'slow':
|
||||
if world.ladxr_settings.lowhpbeep == 'slow':
|
||||
patches.aesthetics.slowLowHPBeep(rom)
|
||||
if settings.lowhpbeep == 'none':
|
||||
if world.ladxr_settings.lowhpbeep == 'none':
|
||||
patches.aesthetics.removeLowHPBeep(rom)
|
||||
if 0 <= int(settings.linkspalette):
|
||||
patches.aesthetics.forceLinksPalette(rom, int(settings.linkspalette))
|
||||
if 0 <= int(world.ladxr_settings.linkspalette):
|
||||
patches.aesthetics.forceLinksPalette(rom, int(world.ladxr_settings.linkspalette))
|
||||
if args.romdebugmode:
|
||||
# The default rom has this build in, just need to set a flag and we get this save.
|
||||
rom.patch(0, 0x0003, "00", "01")
|
||||
|
||||
# Patch the sword check on the shopkeeper turning around.
|
||||
if settings.steal == 'never':
|
||||
if world.ladxr_settings.steal == 'never':
|
||||
rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
|
||||
elif settings.steal == 'always':
|
||||
elif world.ladxr_settings.steal == 'always':
|
||||
rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
|
||||
|
||||
if settings.hpmode == 'inverted':
|
||||
if world.ladxr_settings.hpmode == 'inverted':
|
||||
patches.health.setStartHealth(rom, 9)
|
||||
elif settings.hpmode == '1':
|
||||
elif world.ladxr_settings.hpmode == '1':
|
||||
patches.health.setStartHealth(rom, 1)
|
||||
|
||||
patches.inventory.songSelectAfterOcarinaSelect(rom)
|
||||
if settings.quickswap == 'a':
|
||||
if world.ladxr_settings.quickswap == 'a':
|
||||
patches.core.quickswap(rom, 1)
|
||||
elif settings.quickswap == 'b':
|
||||
elif world.ladxr_settings.quickswap == 'b':
|
||||
patches.core.quickswap(rom, 0)
|
||||
|
||||
patches.core.addBootsControls(rom, ap_settings['boots_controls'])
|
||||
patches.core.addBootsControls(rom, world.options.boots_controls)
|
||||
|
||||
|
||||
world_setup = logic.world_setup
|
||||
world_setup = world.ladxr_logic.world_setup
|
||||
|
||||
JUNK_HINT = 0.33
|
||||
RANDOM_HINT= 0.66
|
||||
# USEFUL_HINT = 1.0
|
||||
# TODO: filter events, filter unshuffled keys
|
||||
all_items = multiworld.get_items()
|
||||
our_items = [item for item in all_items if item.player == player_id and item.location and item.code is not None and item.location.show_in_spoiler]
|
||||
all_items = world.multiworld.get_items()
|
||||
our_items = [item for item in all_items
|
||||
if item.player == world.player
|
||||
and item.location
|
||||
and item.code is not None
|
||||
and item.location.show_in_spoiler]
|
||||
our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification]
|
||||
|
||||
def gen_hint():
|
||||
chance = rnd.uniform(0, 1)
|
||||
chance = world.random.uniform(0, 1)
|
||||
if chance < JUNK_HINT:
|
||||
return None
|
||||
elif chance < RANDOM_HINT:
|
||||
location = rnd.choice(our_items).location
|
||||
location = world.random.choice(our_items).location
|
||||
else: # USEFUL_HINT
|
||||
location = rnd.choice(our_useful_items).location
|
||||
location = world.random.choice(our_useful_items).location
|
||||
|
||||
if location.item.player == player_id:
|
||||
if location.item.player == world.player:
|
||||
name = "Your"
|
||||
else:
|
||||
name = f"{multiworld.player_name[location.item.player]}'s"
|
||||
name = f"{world.multiworld.player_name[location.item.player]}'s"
|
||||
|
||||
if isinstance(location, LinksAwakeningLocation):
|
||||
location_name = location.ladxr_item.metadata.name
|
||||
@@ -277,8 +287,8 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
location_name = location.name
|
||||
|
||||
hint = f"{name} {location.item} is at {location_name}"
|
||||
if location.player != player_id:
|
||||
hint += f" in {multiworld.player_name[location.player]}'s world"
|
||||
if location.player != world.player:
|
||||
hint += f" in {world.multiworld.player_name[location.player]}'s world"
|
||||
|
||||
# Cap hint size at 85
|
||||
# Realistically we could go bigger but let's be safe instead
|
||||
@@ -286,7 +296,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
|
||||
return hint
|
||||
|
||||
hints.addHints(rom, rnd, gen_hint)
|
||||
hints.addHints(rom, world.random, gen_hint)
|
||||
|
||||
if world_setup.goal == "raft":
|
||||
patches.goal.setRaftGoal(rom)
|
||||
@@ -299,7 +309,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
|
||||
# Patch the generated logic into the rom
|
||||
patches.chest.setMultiChest(rom, world_setup.multichest)
|
||||
if settings.overworld not in {"dungeondive", "random"}:
|
||||
if world.ladxr_settings.overworld not in {"dungeondive", "random"}:
|
||||
patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
|
||||
for spot in item_list:
|
||||
if spot.item and spot.item.startswith("*"):
|
||||
@@ -318,15 +328,16 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
patches.core.addFrameCounter(rom, len(item_list))
|
||||
|
||||
patches.core.warpHome(rom) # Needs to be done after setting the start location.
|
||||
patches.titleScreen.setRomInfo(rom, auth, seed_name, settings, player_name, player_id)
|
||||
if ap_settings["ap_title_screen"]:
|
||||
patches.titleScreen.setRomInfo(rom, world.multi_key, world.multiworld.seed_name, world.ladxr_settings,
|
||||
world.player_name, world.player)
|
||||
if world.options.ap_title_screen:
|
||||
patches.titleScreen.setTitleGraphics(rom)
|
||||
patches.endscreen.updateEndScreen(rom)
|
||||
patches.aesthetics.updateSpriteData(rom)
|
||||
if args.doubletrouble:
|
||||
patches.enemies.doubleTrouble(rom)
|
||||
|
||||
if ap_settings["text_shuffle"]:
|
||||
if world.options.text_shuffle:
|
||||
buckets = defaultdict(list)
|
||||
# For each ROM bank, shuffle text within the bank
|
||||
for n, data in enumerate(rom.texts._PointerTable__data):
|
||||
@@ -336,20 +347,20 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
for bucket in buckets.values():
|
||||
# For each bucket, make a copy and shuffle
|
||||
shuffled = bucket.copy()
|
||||
rnd.shuffle(shuffled)
|
||||
world.random.shuffle(shuffled)
|
||||
# Then put new text in
|
||||
for bucket_idx, (orig_idx, data) in enumerate(bucket):
|
||||
rom.texts[shuffled[bucket_idx][0]] = data
|
||||
|
||||
|
||||
if ap_settings["trendy_game"] != TrendyGame.option_normal:
|
||||
if world.options.trendy_game != TrendyGame.option_normal:
|
||||
|
||||
# TODO: if 0 or 4, 5, remove inaccurate conveyor tiles
|
||||
|
||||
|
||||
room_editor = RoomEditor(rom, 0x2A0)
|
||||
|
||||
if ap_settings["trendy_game"] == TrendyGame.option_easy:
|
||||
if world.options.trendy_game == TrendyGame.option_easy:
|
||||
# Set physics flag on all objects
|
||||
for i in range(0, 6):
|
||||
rom.banks[0x4][0x6F1E + i -0x4000] = 0x4
|
||||
@@ -360,7 +371,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
# Add new conveyor to "push" yoshi (it's only a visual)
|
||||
room_editor.objects.append(Object(5, 3, 0xD0))
|
||||
|
||||
if int(ap_settings["trendy_game"]) >= TrendyGame.option_harder:
|
||||
if world.options.trendy_game >= TrendyGame.option_harder:
|
||||
"""
|
||||
Data_004_76A0::
|
||||
db $FC, $00, $04, $00, $00
|
||||
@@ -374,12 +385,12 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
TrendyGame.option_impossible: (3, 16),
|
||||
}
|
||||
def speed():
|
||||
return rnd.randint(*speeds[ap_settings["trendy_game"]])
|
||||
return world.random.randint(*speeds[world.options.trendy_game])
|
||||
rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A2-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A6-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed()
|
||||
if int(ap_settings["trendy_game"]) >= TrendyGame.option_hardest:
|
||||
if world.options.trendy_game >= TrendyGame.option_hardest:
|
||||
rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A3-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A5-0x4000] = speed()
|
||||
@@ -403,10 +414,10 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
for channel in range(3):
|
||||
color[channel] = color[channel] * 31 // 0xbc
|
||||
|
||||
if ap_settings["warp_improvements"]:
|
||||
patches.core.addWarpImprovements(rom, ap_settings["additional_warp_points"])
|
||||
if world.options.warp_improvements:
|
||||
patches.core.addWarpImprovements(rom, world.options.additional_warp_points)
|
||||
|
||||
palette = ap_settings["palette"]
|
||||
palette = world.options.palette
|
||||
if palette != Palette.option_normal:
|
||||
ranges = {
|
||||
# Object palettes
|
||||
@@ -472,8 +483,8 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
|
||||
SEED_LOCATION = 0x0134
|
||||
# Patch over the title
|
||||
assert(len(auth) == 12)
|
||||
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(auth))
|
||||
assert(len(world.multi_key) == 12)
|
||||
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(world.multi_key))
|
||||
|
||||
for pymod in pymods:
|
||||
pymod.postPatch(rom)
|
||||
|
||||
@@ -19,7 +19,7 @@ class DroppedKey(ItemInfo):
|
||||
extra = 0x01F8
|
||||
super().__init__(room, extra)
|
||||
def patch(self, rom, option, *, multiworld=None):
|
||||
if (option.startswith(MAP) and option != MAP) or (option.startswith(COMPASS) and option != COMPASS) or option.startswith(STONE_BEAK) or (option.startswith(NIGHTMARE_KEY) and option != NIGHTMARE_KEY )or (option.startswith(KEY) and option != KEY):
|
||||
if (option.startswith(MAP) and option != MAP) or (option.startswith(COMPASS) and option != COMPASS) or (option.startswith(STONE_BEAK) and option != STONE_BEAK) or (option.startswith(NIGHTMARE_KEY) and option != NIGHTMARE_KEY) or (option.startswith(KEY) and option != KEY):
|
||||
if option[-1] == 'P':
|
||||
print(option)
|
||||
if self._location.dungeon == int(option[-1]) and multiworld is None and self.room not in {0x166, 0x223}:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import os.path
|
||||
import typing
|
||||
import logging
|
||||
from Options import Choice, Option, Toggle, DefaultOnToggle, Range, FreeText
|
||||
from Options import Choice, Toggle, DefaultOnToggle, Range, FreeText, PerGameCommonOptions, OptionGroup
|
||||
from collections import defaultdict
|
||||
import Utils
|
||||
|
||||
@@ -14,7 +16,7 @@ class LADXROption:
|
||||
def to_ladxr_option(self, all_options):
|
||||
if not self.ladxr_name:
|
||||
return None, None
|
||||
|
||||
|
||||
return (self.ladxr_name, self.name_lookup[self.value].replace("_", ""))
|
||||
|
||||
|
||||
@@ -32,9 +34,10 @@ class Logic(Choice, LADXROption):
|
||||
option_hard = 2
|
||||
option_glitched = 3
|
||||
option_hell = 4
|
||||
|
||||
|
||||
default = option_normal
|
||||
|
||||
|
||||
class TradeQuest(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
[On] adds the trade items to the pool (the trade locations will always be local items)
|
||||
@@ -43,12 +46,14 @@ class TradeQuest(DefaultOffToggle, LADXROption):
|
||||
display_name = "Trade Quest"
|
||||
ladxr_name = "tradequest"
|
||||
|
||||
|
||||
class TextShuffle(DefaultOffToggle):
|
||||
"""
|
||||
[On] Shuffles all the text in the game
|
||||
[Off] (default) doesn't shuffle them.
|
||||
"""
|
||||
|
||||
|
||||
class Rooster(DefaultOnToggle, LADXROption):
|
||||
"""
|
||||
[On] Adds the rooster to the item pool.
|
||||
@@ -57,6 +62,7 @@ class Rooster(DefaultOnToggle, LADXROption):
|
||||
display_name = "Rooster"
|
||||
ladxr_name = "rooster"
|
||||
|
||||
|
||||
class Boomerang(Choice):
|
||||
"""
|
||||
[Normal] requires Magnifying Lens to get the boomerang.
|
||||
@@ -67,6 +73,7 @@ class Boomerang(Choice):
|
||||
gift = 1
|
||||
default = gift
|
||||
|
||||
|
||||
class EntranceShuffle(Choice, LADXROption):
|
||||
"""
|
||||
[WARNING] Experimental, may fail to fill
|
||||
@@ -75,19 +82,20 @@ class EntranceShuffle(Choice, LADXROption):
|
||||
If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the non-connector entrance pool.
|
||||
Note, some entrances can lead into water, use the warp-to-home from the save&quit menu to escape this."""
|
||||
|
||||
#[Advanced] Simple, but two-way connector caves are shuffled in their own pool as well.
|
||||
#[Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool.
|
||||
#[Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool.
|
||||
# [Advanced] Simple, but two-way connector caves are shuffled in their own pool as well.
|
||||
# [Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool.
|
||||
# [Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool.
|
||||
|
||||
option_none = 0
|
||||
option_simple = 1
|
||||
#option_advanced = 2
|
||||
#option_expert = 3
|
||||
#option_insanity = 4
|
||||
# option_advanced = 2
|
||||
# option_expert = 3
|
||||
# option_insanity = 4
|
||||
default = option_none
|
||||
display_name = "Experimental Entrance Shuffle"
|
||||
ladxr_name = "entranceshuffle"
|
||||
|
||||
|
||||
class DungeonShuffle(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
[WARNING] Experimental, may fail to fill
|
||||
@@ -96,12 +104,14 @@ class DungeonShuffle(DefaultOffToggle, LADXROption):
|
||||
display_name = "Experimental Dungeon Shuffle"
|
||||
ladxr_name = "dungeonshuffle"
|
||||
|
||||
|
||||
class APTitleScreen(DefaultOnToggle):
|
||||
"""
|
||||
Enables AP specific title screen and disables the intro cutscene
|
||||
"""
|
||||
display_name = "AP Title Screen"
|
||||
|
||||
|
||||
class BossShuffle(Choice):
|
||||
none = 0
|
||||
shuffle = 1
|
||||
@@ -115,10 +125,12 @@ class DungeonItemShuffle(Choice):
|
||||
option_own_world = 2
|
||||
option_any_world = 3
|
||||
option_different_world = 4
|
||||
#option_delete = 5
|
||||
#option_start_with = 6
|
||||
# option_delete = 5
|
||||
# option_start_with = 6
|
||||
alias_true = 3
|
||||
alias_false = 0
|
||||
ladxr_item: str
|
||||
|
||||
|
||||
class ShuffleNightmareKeys(DungeonItemShuffle):
|
||||
"""
|
||||
@@ -132,6 +144,7 @@ class ShuffleNightmareKeys(DungeonItemShuffle):
|
||||
display_name = "Shuffle Nightmare Keys"
|
||||
ladxr_item = "NIGHTMARE_KEY"
|
||||
|
||||
|
||||
class ShuffleSmallKeys(DungeonItemShuffle):
|
||||
"""
|
||||
Shuffle Small Keys
|
||||
@@ -143,6 +156,8 @@ class ShuffleSmallKeys(DungeonItemShuffle):
|
||||
"""
|
||||
display_name = "Shuffle Small Keys"
|
||||
ladxr_item = "KEY"
|
||||
|
||||
|
||||
class ShuffleMaps(DungeonItemShuffle):
|
||||
"""
|
||||
Shuffle Dungeon Maps
|
||||
@@ -155,6 +170,7 @@ class ShuffleMaps(DungeonItemShuffle):
|
||||
display_name = "Shuffle Maps"
|
||||
ladxr_item = "MAP"
|
||||
|
||||
|
||||
class ShuffleCompasses(DungeonItemShuffle):
|
||||
"""
|
||||
Shuffle Dungeon Compasses
|
||||
@@ -167,6 +183,7 @@ class ShuffleCompasses(DungeonItemShuffle):
|
||||
display_name = "Shuffle Compasses"
|
||||
ladxr_item = "COMPASS"
|
||||
|
||||
|
||||
class ShuffleStoneBeaks(DungeonItemShuffle):
|
||||
"""
|
||||
Shuffle Owl Beaks
|
||||
@@ -179,6 +196,7 @@ class ShuffleStoneBeaks(DungeonItemShuffle):
|
||||
display_name = "Shuffle Stone Beaks"
|
||||
ladxr_item = "STONE_BEAK"
|
||||
|
||||
|
||||
class ShuffleInstruments(DungeonItemShuffle):
|
||||
"""
|
||||
Shuffle Instruments
|
||||
@@ -195,6 +213,7 @@ class ShuffleInstruments(DungeonItemShuffle):
|
||||
option_vanilla = 100
|
||||
alias_false = 100
|
||||
|
||||
|
||||
class Goal(Choice, LADXROption):
|
||||
"""
|
||||
The Goal of the game
|
||||
@@ -207,7 +226,7 @@ class Goal(Choice, LADXROption):
|
||||
option_instruments = 1
|
||||
option_seashells = 2
|
||||
option_open = 3
|
||||
|
||||
|
||||
default = option_instruments
|
||||
|
||||
def to_ladxr_option(self, all_options):
|
||||
@@ -216,6 +235,7 @@ class Goal(Choice, LADXROption):
|
||||
else:
|
||||
return LADXROption.to_ladxr_option(self, all_options)
|
||||
|
||||
|
||||
class InstrumentCount(Range, LADXROption):
|
||||
"""
|
||||
Sets the number of instruments required to open the Egg
|
||||
@@ -226,6 +246,7 @@ class InstrumentCount(Range, LADXROption):
|
||||
range_end = 8
|
||||
default = 8
|
||||
|
||||
|
||||
class NagMessages(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
Controls if nag messages are shown when rocks and crystals are touched. Useful for glitches, annoying for everyone else.
|
||||
@@ -233,6 +254,7 @@ class NagMessages(DefaultOffToggle, LADXROption):
|
||||
display_name = "Nag Messages"
|
||||
ladxr_name = "nagmessages"
|
||||
|
||||
|
||||
class MusicChangeCondition(Choice):
|
||||
"""
|
||||
Controls how the music changes.
|
||||
@@ -243,6 +265,8 @@ class MusicChangeCondition(Choice):
|
||||
option_sword = 0
|
||||
option_always = 1
|
||||
default = option_always
|
||||
|
||||
|
||||
# Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max')], default='default',
|
||||
# description="""
|
||||
# [Normal} health works as you would expect.
|
||||
@@ -271,6 +295,7 @@ class Bowwow(Choice):
|
||||
swordless = 1
|
||||
default = normal
|
||||
|
||||
|
||||
class Overworld(Choice, LADXROption):
|
||||
"""
|
||||
[Dungeon Dive] Create a different overworld where all the dungeons are directly accessible and almost no chests are located in the overworld.
|
||||
@@ -284,9 +309,10 @@ class Overworld(Choice, LADXROption):
|
||||
# option_shuffled = 3
|
||||
default = option_normal
|
||||
|
||||
#Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
|
||||
|
||||
# Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
|
||||
# description='All items will be more powerful, faster, harder, bigger stronger. You name it.'),
|
||||
#Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none',
|
||||
# Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none',
|
||||
# description='Adds that the select button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.',
|
||||
# aesthetic=True),
|
||||
# Setting('textmode', 'User options', 'f', 'Text mode', options=[('fast', '', 'Fast'), ('default', 'd', 'Normal'), ('none', 'n', 'No-text')], default='fast',
|
||||
@@ -329,7 +355,7 @@ class BootsControls(Choice):
|
||||
option_bracelet = 1
|
||||
option_press_a = 2
|
||||
option_press_b = 3
|
||||
|
||||
|
||||
|
||||
class LinkPalette(Choice, LADXROption):
|
||||
"""
|
||||
@@ -352,6 +378,7 @@ class LinkPalette(Choice, LADXROption):
|
||||
def to_ladxr_option(self, all_options):
|
||||
return self.ladxr_name, str(self.value)
|
||||
|
||||
|
||||
class TrendyGame(Choice):
|
||||
"""
|
||||
[Easy] All of the items hold still for you
|
||||
@@ -370,6 +397,7 @@ class TrendyGame(Choice):
|
||||
option_impossible = 5
|
||||
default = option_normal
|
||||
|
||||
|
||||
class GfxMod(FreeText, LADXROption):
|
||||
"""
|
||||
Sets the sprite for link, among other things
|
||||
@@ -380,7 +408,7 @@ class GfxMod(FreeText, LADXROption):
|
||||
normal = ''
|
||||
default = 'Link'
|
||||
|
||||
__spriteDir: str = Utils.local_path(os.path.join('data', 'sprites','ladx'))
|
||||
__spriteDir: str = Utils.local_path(os.path.join('data', 'sprites', 'ladx'))
|
||||
__spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list)
|
||||
|
||||
extensions = [".bin", ".bdiff", ".png", ".bmp"]
|
||||
@@ -389,16 +417,15 @@ class GfxMod(FreeText, LADXROption):
|
||||
name, extension = os.path.splitext(file)
|
||||
if extension in extensions:
|
||||
__spriteFiles[name].append(file)
|
||||
|
||||
|
||||
def __init__(self, value: str):
|
||||
super().__init__(value)
|
||||
|
||||
|
||||
def verify(self, world, player_name: str, plando_options) -> None:
|
||||
if self.value == "Link" or self.value in GfxMod.__spriteFiles:
|
||||
return
|
||||
raise Exception(f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}")
|
||||
|
||||
raise Exception(
|
||||
f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}")
|
||||
|
||||
def to_ladxr_option(self, all_options):
|
||||
if self.value == -1 or self.value == "Link":
|
||||
@@ -407,10 +434,12 @@ class GfxMod(FreeText, LADXROption):
|
||||
assert self.value in GfxMod.__spriteFiles
|
||||
|
||||
if len(GfxMod.__spriteFiles[self.value]) > 1:
|
||||
logger.warning(f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}")
|
||||
logger.warning(
|
||||
f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}")
|
||||
|
||||
return self.ladxr_name, self.__spriteDir + "/" + GfxMod.__spriteFiles[self.value][0]
|
||||
|
||||
|
||||
class Palette(Choice):
|
||||
"""
|
||||
Sets the palette for the game.
|
||||
@@ -430,6 +459,7 @@ class Palette(Choice):
|
||||
option_pink = 4
|
||||
option_inverted = 5
|
||||
|
||||
|
||||
class Music(Choice, LADXROption):
|
||||
"""
|
||||
[Vanilla] Regular Music
|
||||
@@ -441,7 +471,6 @@ class Music(Choice, LADXROption):
|
||||
option_shuffled = 1
|
||||
option_off = 2
|
||||
|
||||
|
||||
def to_ladxr_option(self, all_options):
|
||||
s = ""
|
||||
if self.value == self.option_shuffled:
|
||||
@@ -450,55 +479,95 @@ class Music(Choice, LADXROption):
|
||||
s = "off"
|
||||
return self.ladxr_name, s
|
||||
|
||||
|
||||
class WarpImprovements(DefaultOffToggle):
|
||||
"""
|
||||
[On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
|
||||
[Off] No change
|
||||
"""
|
||||
|
||||
|
||||
class AdditionalWarpPoints(DefaultOffToggle):
|
||||
"""
|
||||
[On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower
|
||||
[Off] No change
|
||||
"""
|
||||
|
||||
|
||||
links_awakening_options: typing.Dict[str, typing.Type[Option]] = {
|
||||
'logic': Logic,
|
||||
ladx_option_groups = [
|
||||
OptionGroup("Goal Options", [
|
||||
Goal,
|
||||
InstrumentCount,
|
||||
]),
|
||||
OptionGroup("Shuffles", [
|
||||
ShuffleInstruments,
|
||||
ShuffleNightmareKeys,
|
||||
ShuffleSmallKeys,
|
||||
ShuffleMaps,
|
||||
ShuffleCompasses,
|
||||
ShuffleStoneBeaks
|
||||
]),
|
||||
OptionGroup("Warp Points", [
|
||||
WarpImprovements,
|
||||
AdditionalWarpPoints,
|
||||
]),
|
||||
OptionGroup("Miscellaneous", [
|
||||
TradeQuest,
|
||||
Rooster,
|
||||
TrendyGame,
|
||||
NagMessages,
|
||||
BootsControls
|
||||
]),
|
||||
OptionGroup("Experimental", [
|
||||
DungeonShuffle,
|
||||
EntranceShuffle
|
||||
]),
|
||||
OptionGroup("Visuals & Sound", [
|
||||
LinkPalette,
|
||||
Palette,
|
||||
TextShuffle,
|
||||
APTitleScreen,
|
||||
GfxMod,
|
||||
Music,
|
||||
MusicChangeCondition
|
||||
])
|
||||
]
|
||||
|
||||
@dataclass
|
||||
class LinksAwakeningOptions(PerGameCommonOptions):
|
||||
logic: Logic
|
||||
# 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'),
|
||||
# 'seashells': DefaultOnToggle, # description='Randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)'),
|
||||
# 'heartcontainers': DefaultOnToggle, # description='Includes boss heart container drops in the item pool'),
|
||||
# 'instruments': DefaultOffToggle, # description='Instruments are placed on random locations, dungeon goal will just contain a random item.'),
|
||||
'tradequest': TradeQuest, # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'),
|
||||
tradequest: TradeQuest # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'),
|
||||
# 'witch': DefaultOnToggle, # description='Adds both the toadstool and the reward for giving the toadstool to the witch to the item pool'),
|
||||
'rooster': Rooster, # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'),
|
||||
rooster: Rooster # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'),
|
||||
# 'boomerang': Boomerang,
|
||||
# 'randomstartlocation': DefaultOffToggle, # 'Randomize where your starting house is located'),
|
||||
'experimental_dungeon_shuffle': DungeonShuffle, # 'Randomizes the dungeon that each dungeon entrance leads to'),
|
||||
'experimental_entrance_shuffle': EntranceShuffle,
|
||||
experimental_dungeon_shuffle: DungeonShuffle # 'Randomizes the dungeon that each dungeon entrance leads to'),
|
||||
experimental_entrance_shuffle: EntranceShuffle
|
||||
# 'bossshuffle': BossShuffle,
|
||||
# 'minibossshuffle': BossShuffle,
|
||||
'goal': Goal,
|
||||
'instrument_count': InstrumentCount,
|
||||
goal: Goal
|
||||
instrument_count: InstrumentCount
|
||||
# 'itempool': ItemPool,
|
||||
# 'bowwow': Bowwow,
|
||||
# 'overworld': Overworld,
|
||||
'link_palette': LinkPalette,
|
||||
'warp_improvements': WarpImprovements,
|
||||
'additional_warp_points': AdditionalWarpPoints,
|
||||
'trendy_game': TrendyGame,
|
||||
'gfxmod': GfxMod,
|
||||
'palette': Palette,
|
||||
'text_shuffle': TextShuffle,
|
||||
'shuffle_nightmare_keys': ShuffleNightmareKeys,
|
||||
'shuffle_small_keys': ShuffleSmallKeys,
|
||||
'shuffle_maps': ShuffleMaps,
|
||||
'shuffle_compasses': ShuffleCompasses,
|
||||
'shuffle_stone_beaks': ShuffleStoneBeaks,
|
||||
'music': Music,
|
||||
'shuffle_instruments': ShuffleInstruments,
|
||||
'music_change_condition': MusicChangeCondition,
|
||||
'nag_messages': NagMessages,
|
||||
'ap_title_screen': APTitleScreen,
|
||||
'boots_controls': BootsControls,
|
||||
}
|
||||
link_palette: LinkPalette
|
||||
warp_improvements: WarpImprovements
|
||||
additional_warp_points: AdditionalWarpPoints
|
||||
trendy_game: TrendyGame
|
||||
gfxmod: GfxMod
|
||||
palette: Palette
|
||||
text_shuffle: TextShuffle
|
||||
shuffle_nightmare_keys: ShuffleNightmareKeys
|
||||
shuffle_small_keys: ShuffleSmallKeys
|
||||
shuffle_maps: ShuffleMaps
|
||||
shuffle_compasses: ShuffleCompasses
|
||||
shuffle_stone_beaks: ShuffleStoneBeaks
|
||||
music: Music
|
||||
shuffle_instruments: ShuffleInstruments
|
||||
music_change_condition: MusicChangeCondition
|
||||
nag_messages: NagMessages
|
||||
ap_title_screen: APTitleScreen
|
||||
boots_controls: BootsControls
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import settings
|
||||
import worlds.Files
|
||||
import hashlib
|
||||
import Utils
|
||||
@@ -32,7 +32,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
|
||||
|
||||
|
||||
def get_base_rom_path(file_name: str = "") -> str:
|
||||
options = Utils.get_options()
|
||||
options = settings.get_settings()
|
||||
if not file_name:
|
||||
file_name = options["ladx_options"]["rom_file"]
|
||||
if not os.path.exists(file_name):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from worlds.ladx.LADXR.checkMetadata import checkMetadataTable
|
||||
from .LADXR.checkMetadata import checkMetadataTable
|
||||
import json
|
||||
import logging
|
||||
import websockets
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import binascii
|
||||
import dataclasses
|
||||
import os
|
||||
import pkgutil
|
||||
import tempfile
|
||||
@@ -17,13 +18,13 @@ from .LADXR import generator
|
||||
from .LADXR.itempool import ItemPool as LADXRItemPool
|
||||
from .LADXR.locations.constants import CHEST_ITEMS
|
||||
from .LADXR.locations.instrument import Instrument
|
||||
from .LADXR.logic import Logic as LAXDRLogic
|
||||
from .LADXR.logic import Logic as LADXRLogic
|
||||
from .LADXR.main import get_parser
|
||||
from .LADXR.settings import Settings as LADXRSettings
|
||||
from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup
|
||||
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
|
||||
create_regions_from_ladxr, get_locations_to_id)
|
||||
from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments
|
||||
from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions, ladx_option_groups
|
||||
from .Rom import LADXDeltaPatch, get_base_rom_path
|
||||
|
||||
DEVELOPER_MODE = False
|
||||
@@ -64,7 +65,7 @@ class LinksAwakeningWebWorld(WebWorld):
|
||||
["zig"]
|
||||
)]
|
||||
theme = "dirt"
|
||||
|
||||
option_groups = ladx_option_groups
|
||||
|
||||
class LinksAwakeningWorld(World):
|
||||
"""
|
||||
@@ -73,8 +74,9 @@ class LinksAwakeningWorld(World):
|
||||
"""
|
||||
game = LINKS_AWAKENING # name of the game/world
|
||||
web = LinksAwakeningWebWorld()
|
||||
|
||||
option_definitions = links_awakening_options # options the player can set
|
||||
|
||||
options_dataclass = LinksAwakeningOptions
|
||||
options: LinksAwakeningOptions
|
||||
settings: typing.ClassVar[LinksAwakeningSettings]
|
||||
topology_present = True # show path to required location checks in spoiler
|
||||
|
||||
@@ -102,7 +104,11 @@ class LinksAwakeningWorld(World):
|
||||
|
||||
prefill_dungeon_items = None
|
||||
|
||||
player_options = None
|
||||
ladxr_settings: LADXRSettings
|
||||
ladxr_logic: LADXRLogic
|
||||
ladxr_itempool: LADXRItemPool
|
||||
|
||||
multi_key: bytearray
|
||||
|
||||
rupees = {
|
||||
ItemName.RUPEES_20: 20,
|
||||
@@ -113,17 +119,13 @@ class LinksAwakeningWorld(World):
|
||||
}
|
||||
|
||||
def convert_ap_options_to_ladxr_logic(self):
|
||||
self.player_options = {
|
||||
option: getattr(self.multiworld, option)[self.player] for option in self.option_definitions
|
||||
}
|
||||
self.ladxr_settings = LADXRSettings(dataclasses.asdict(self.options))
|
||||
|
||||
self.laxdr_options = LADXRSettings(self.player_options)
|
||||
|
||||
self.laxdr_options.validate()
|
||||
self.ladxr_settings.validate()
|
||||
world_setup = LADXRWorldSetup()
|
||||
world_setup.randomize(self.laxdr_options, self.multiworld.random)
|
||||
self.ladxr_logic = LAXDRLogic(configuration_options=self.laxdr_options, world_setup=world_setup)
|
||||
self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.laxdr_options, self.multiworld.random).toDict()
|
||||
world_setup.randomize(self.ladxr_settings, self.random)
|
||||
self.ladxr_logic = LADXRLogic(configuration_options=self.ladxr_settings, world_setup=world_setup)
|
||||
self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.ladxr_settings, self.random).toDict()
|
||||
|
||||
def create_regions(self) -> None:
|
||||
# Initialize
|
||||
@@ -180,8 +182,8 @@ class LinksAwakeningWorld(World):
|
||||
# For any and different world, set item rule instead
|
||||
|
||||
for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]:
|
||||
option = "shuffle_" + dungeon_item_type
|
||||
option = self.player_options[option]
|
||||
option_name = "shuffle_" + dungeon_item_type
|
||||
option: DungeonItemShuffle = getattr(self.options, option_name)
|
||||
|
||||
dungeon_item_types[option.ladxr_item] = option.value
|
||||
|
||||
@@ -189,11 +191,11 @@ class LinksAwakeningWorld(World):
|
||||
num_items = 8 if dungeon_item_type == "instruments" else 9
|
||||
|
||||
if option.value == DungeonItemShuffle.option_own_world:
|
||||
self.multiworld.local_items[self.player].value |= {
|
||||
self.options.local_items.value |= {
|
||||
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
|
||||
}
|
||||
elif option.value == DungeonItemShuffle.option_different_world:
|
||||
self.multiworld.non_local_items[self.player].value |= {
|
||||
self.options.non_local_items.value |= {
|
||||
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
|
||||
}
|
||||
# option_original_dungeon = 0
|
||||
@@ -215,7 +217,7 @@ class LinksAwakeningWorld(World):
|
||||
else:
|
||||
item = self.create_item(item_name)
|
||||
|
||||
if not self.multiworld.tradequest[self.player] and isinstance(item.item_data, TradeItemData):
|
||||
if not self.options.tradequest and isinstance(item.item_data, TradeItemData):
|
||||
location = self.multiworld.get_location(item.item_data.vanilla_location, self.player)
|
||||
location.place_locked_item(item)
|
||||
location.show_in_spoiler = False
|
||||
@@ -287,7 +289,7 @@ class LinksAwakeningWorld(World):
|
||||
if item.player == self.player
|
||||
and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location]
|
||||
if possible_start_items:
|
||||
index = self.multiworld.random.choice(possible_start_items)
|
||||
index = self.random.choice(possible_start_items)
|
||||
start_item = self.multiworld.itempool.pop(index)
|
||||
start_loc.place_locked_item(start_item)
|
||||
|
||||
@@ -336,7 +338,7 @@ class LinksAwakeningWorld(World):
|
||||
# Get the list of locations and shuffle
|
||||
all_dungeon_locs_to_fill = sorted(all_dungeon_locs)
|
||||
|
||||
self.multiworld.random.shuffle(all_dungeon_locs_to_fill)
|
||||
self.random.shuffle(all_dungeon_locs_to_fill)
|
||||
|
||||
# Get the list of items and sort by priority
|
||||
def priority(item):
|
||||
@@ -465,34 +467,19 @@ class LinksAwakeningWorld(World):
|
||||
loc.ladxr_item.location_owner = self.player
|
||||
|
||||
rom_name = Rom.get_base_rom_path()
|
||||
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.gbc"
|
||||
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.player_name}.gbc"
|
||||
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc")
|
||||
|
||||
parser = get_parser()
|
||||
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
||||
|
||||
name_for_rom = self.multiworld.player_name[self.player]
|
||||
|
||||
all_names = [self.multiworld.player_name[i + 1] for i in range(len(self.multiworld.player_name))]
|
||||
|
||||
rom = generator.generateRom(
|
||||
args,
|
||||
self.laxdr_options,
|
||||
self.player_options,
|
||||
self.multi_key,
|
||||
self.multiworld.seed_name,
|
||||
self.ladxr_logic,
|
||||
rnd=self.multiworld.per_slot_randoms[self.player],
|
||||
player_name=name_for_rom,
|
||||
player_names=all_names,
|
||||
player_id = self.player,
|
||||
multiworld=self.multiworld)
|
||||
rom = generator.generateRom(args, self)
|
||||
|
||||
with open(out_path, "wb") as handle:
|
||||
rom.save(handle, name="LADXR")
|
||||
|
||||
# Write title screen after everything else is done - full gfxmods may stomp over the egg tiles
|
||||
if self.player_options["ap_title_screen"]:
|
||||
if self.options.ap_title_screen:
|
||||
with tempfile.NamedTemporaryFile(delete=False) as title_patch:
|
||||
title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
|
||||
|
||||
@@ -500,16 +487,16 @@ class LinksAwakeningWorld(World):
|
||||
os.unlink(title_patch.name)
|
||||
|
||||
patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=out_path)
|
||||
player_name=self.player_name, patched_path=out_path)
|
||||
patch.write()
|
||||
if not DEVELOPER_MODE:
|
||||
os.unlink(out_path)
|
||||
|
||||
def generate_multi_key(self):
|
||||
return bytearray(self.multiworld.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big')
|
||||
return bytearray(self.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big')
|
||||
|
||||
def modify_multidata(self, multidata: dict):
|
||||
multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.player_name]
|
||||
|
||||
def collect(self, state, item: Item) -> bool:
|
||||
change = super().collect(state, item)
|
||||
|
||||
@@ -147,7 +147,7 @@ class L2ACSNIClient(SNIClient):
|
||||
snes_items_received += 1
|
||||
|
||||
snes_logger.info("Received %s from %s (%s) (%d/%d in list)" % (
|
||||
ctx.item_names.lookup_in_slot(item.item),
|
||||
ctx.item_names.lookup_in_game(item.item),
|
||||
ctx.player_names[item.player],
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player),
|
||||
snes_items_received, len(ctx.items_received)))
|
||||
|
||||
@@ -231,6 +231,8 @@ class MessengerRules:
|
||||
self.is_aerobatic,
|
||||
"Autumn Hills Seal - Trip Saws":
|
||||
self.has_wingsuit,
|
||||
"Autumn Hills Seal - Double Swing Saws":
|
||||
self.has_vertical,
|
||||
# forlorn temple
|
||||
"Forlorn Temple Seal - Rocket Maze":
|
||||
self.has_vertical,
|
||||
@@ -430,6 +432,8 @@ class MessengerHardRules(MessengerRules):
|
||||
{
|
||||
"Autumn Hills Seal - Spike Ball Darts":
|
||||
lambda state: self.has_vertical(state) and self.has_windmill(state) or self.is_aerobatic(state),
|
||||
"Autumn Hills Seal - Double Swing Saws":
|
||||
lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
||||
"Bamboo Creek - Claustro":
|
||||
self.has_wingsuit,
|
||||
"Bamboo Creek Seal - Spike Ball Pits":
|
||||
|
||||
@@ -553,7 +553,7 @@ NOVA|73-4|Happy Otaku Pack Vol.19|True|6|8|10|
|
||||
Heaven's Gradius|73-5|Happy Otaku Pack Vol.19|True|6|8|10|
|
||||
Ray Tuning|74-0|CHUNITHM COURSE MUSE|True|6|8|10|
|
||||
World Vanquisher|74-1|CHUNITHM COURSE MUSE|True|6|8|10|11
|
||||
Territory Battles|74-2|CHUNITHM COURSE MUSE|True|5|7|9|
|
||||
Tsukuyomi Ni Naru|74-2|CHUNITHM COURSE MUSE|False|5|7|9|
|
||||
The wheel to the right|74-3|CHUNITHM COURSE MUSE|True|5|7|9|11
|
||||
Climax|74-4|CHUNITHM COURSE MUSE|True|4|8|11|11
|
||||
Spider's Thread|74-5|CHUNITHM COURSE MUSE|True|5|8|10|12
|
||||
|
||||
@@ -1173,10 +1173,35 @@ class OOTWorld(World):
|
||||
|
||||
def fill_slot_data(self):
|
||||
self.collectible_flags_available.wait()
|
||||
return {
|
||||
|
||||
slot_data = {
|
||||
'collectible_override_flags': self.collectible_override_flags,
|
||||
'collectible_flag_offsets': self.collectible_flag_offsets
|
||||
}
|
||||
slot_data.update(self.options.as_dict(
|
||||
"open_forest", "open_kakariko", "open_door_of_time", "zora_fountain", "gerudo_fortress",
|
||||
"bridge", "bridge_stones", "bridge_medallions", "bridge_rewards", "bridge_tokens", "bridge_hearts",
|
||||
"shuffle_ganon_bosskey", "ganon_bosskey_medallions", "ganon_bosskey_stones", "ganon_bosskey_rewards",
|
||||
"ganon_bosskey_tokens", "ganon_bosskey_hearts", "trials",
|
||||
"triforce_hunt", "triforce_goal", "extra_triforce_percentage",
|
||||
"shopsanity", "shop_slots", "shopsanity_prices", "tokensanity",
|
||||
"dungeon_shortcuts", "dungeon_shortcuts_list",
|
||||
"mq_dungeons_mode", "mq_dungeons_list", "mq_dungeons_count",
|
||||
"shuffle_interior_entrances", "shuffle_grotto_entrances", "shuffle_dungeon_entrances",
|
||||
"shuffle_overworld_entrances", "shuffle_bosses", "key_rings", "key_rings_list", "enhance_map_compass",
|
||||
"shuffle_mapcompass", "shuffle_smallkeys", "shuffle_hideoutkeys", "shuffle_bosskeys",
|
||||
"logic_rules", "logic_no_night_tokens_without_suns_song", "logic_tricks",
|
||||
"warp_songs", "shuffle_song_items","shuffle_medigoron_carpet_salesman", "shuffle_frog_song_rupees",
|
||||
"shuffle_scrubs", "shuffle_child_trade", "shuffle_freestanding_items", "shuffle_pots", "shuffle_crates",
|
||||
"shuffle_cows", "shuffle_beehives", "shuffle_kokiri_sword", "shuffle_ocarinas", "shuffle_gerudo_card",
|
||||
"shuffle_beans", "starting_age", "bombchus_in_logic", "spawn_positions", "owl_drops",
|
||||
"no_epona_race", "skip_some_minigame_phases", "complete_mask_quest", "free_scarecrow", "plant_beans",
|
||||
"chicken_count", "big_poe_count", "fae_torch_count", "blue_fire_arrows",
|
||||
"damage_multiplier", "deadly_bonks", "starting_tod", "junk_ice_traps",
|
||||
"start_with_consumables", "adult_trade_start", "plando_connections"
|
||||
)
|
||||
)
|
||||
return slot_data
|
||||
|
||||
|
||||
def modify_multidata(self, multidata: dict):
|
||||
|
||||
@@ -4,10 +4,11 @@ from random import Random
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
from test.general import setup_solo_multiworld
|
||||
|
||||
from worlds.overcooked2.Items import *
|
||||
from worlds.overcooked2.Overcooked2Levels import Overcooked2Dlc, Overcooked2Level, OverworldRegion, overworld_region_by_level, level_id_to_shortname
|
||||
from worlds.overcooked2.Logic import level_logic, overworld_region_logic, level_shuffle_factory
|
||||
from worlds.overcooked2.Locations import oc2_location_name_to_id
|
||||
from ..Items import *
|
||||
from ..Overcooked2Levels import (Overcooked2Dlc, Overcooked2Level, OverworldRegion, overworld_region_by_level,
|
||||
level_id_to_shortname)
|
||||
from ..Logic import level_logic, overworld_region_logic, level_shuffle_factory
|
||||
from ..Locations import oc2_location_name_to_id
|
||||
|
||||
|
||||
class Overcooked2Test(unittest.TestCase):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Dict
|
||||
|
||||
from . import RLTestBase
|
||||
from worlds.rogue_legacy.Items import RLItemData, item_table
|
||||
from worlds.rogue_legacy.Locations import RLLocationData, location_table
|
||||
from ..Items import item_table
|
||||
from ..Locations import location_table
|
||||
|
||||
|
||||
class UniqueTest(RLTestBase):
|
||||
|
||||
@@ -22,10 +22,9 @@ from pathlib import Path
|
||||
# CommonClient import first to trigger ModuleUpdater
|
||||
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser
|
||||
from Utils import init_logging, is_windows, async_start
|
||||
from worlds.sc2 import ItemNames
|
||||
from worlds.sc2.ItemGroups import item_name_groups, unlisted_item_name_groups
|
||||
from worlds.sc2 import Options
|
||||
from worlds.sc2.Options import (
|
||||
from . import ItemNames, Options
|
||||
from .ItemGroups import item_name_groups
|
||||
from .Options import (
|
||||
MissionOrder, KerriganPrimalStatus, kerrigan_unit_available, KerriganPresence,
|
||||
GameSpeed, GenericUpgradeItems, GenericUpgradeResearch, ColorChoice, GenericUpgradeMissions,
|
||||
LocationInclusion, ExtraLocations, MasteryLocations, ChallengeLocations, VanillaLocations,
|
||||
@@ -46,11 +45,12 @@ from worlds._sc2common import bot
|
||||
from worlds._sc2common.bot.data import Race
|
||||
from worlds._sc2common.bot.main import run_game
|
||||
from worlds._sc2common.bot.player import Bot
|
||||
from worlds.sc2.Items import lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers, upgrade_numbers_all
|
||||
from worlds.sc2.Locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET
|
||||
from worlds.sc2.MissionTables import lookup_id_to_mission, SC2Campaign, lookup_name_to_mission, \
|
||||
lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race, get_no_build_missions
|
||||
from worlds.sc2.Regions import MissionInfo
|
||||
from .Items import (lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers,
|
||||
upgrade_numbers_all)
|
||||
from .Locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET
|
||||
from .MissionTables import (lookup_id_to_mission, SC2Campaign, lookup_name_to_mission,
|
||||
lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race)
|
||||
from .Regions import MissionInfo
|
||||
|
||||
import colorama
|
||||
from Options import Option
|
||||
@@ -243,10 +243,10 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
self.formatted_print(f" [u]{faction.name}[/u] ")
|
||||
|
||||
for item_id in categorized_items[faction]:
|
||||
item_name = self.ctx.item_names.lookup_in_slot(item_id)
|
||||
item_name = self.ctx.item_names.lookup_in_game(item_id)
|
||||
received_child_items = items_received_set.intersection(parent_to_child.get(item_id, []))
|
||||
matching_children = [child for child in received_child_items
|
||||
if item_matches_filter(self.ctx.item_names.lookup_in_slot(child))]
|
||||
if item_matches_filter(self.ctx.item_names.lookup_in_game(child))]
|
||||
received_items_of_this_type = items_received.get(item_id, [])
|
||||
item_is_match = item_matches_filter(item_name)
|
||||
if item_is_match or len(matching_children) > 0:
|
||||
@@ -1164,7 +1164,7 @@ def request_unfinished_missions(ctx: SC2Context) -> None:
|
||||
objectives = set(ctx.locations_for_mission(mission))
|
||||
if objectives:
|
||||
remaining_objectives = objectives.difference(ctx.checked_locations)
|
||||
unfinished_locations[mission] = [ctx.location_names.lookup_in_slot(location_id) for location_id in remaining_objectives]
|
||||
unfinished_locations[mission] = [ctx.location_names.lookup_in_game(location_id) for location_id in remaining_objectives]
|
||||
else:
|
||||
unfinished_locations[mission] = []
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from worlds.sc2.Client import SC2Context, calc_unfinished_missions, parse_unlock
|
||||
from worlds.sc2.MissionTables import lookup_id_to_mission, lookup_name_to_mission, campaign_race_exceptions, \
|
||||
SC2Mission, SC2Race, SC2Campaign
|
||||
from worlds.sc2.Locations import LocationType, lookup_location_id_to_type
|
||||
from worlds.sc2.Options import LocationInclusion
|
||||
from worlds.sc2 import SC2World, get_first_mission
|
||||
from .Client import SC2Context, calc_unfinished_missions, parse_unlock
|
||||
from .MissionTables import (lookup_id_to_mission, lookup_name_to_mission, campaign_race_exceptions, SC2Mission, SC2Race,
|
||||
SC2Campaign)
|
||||
from .Locations import LocationType, lookup_location_id_to_type
|
||||
from .Options import LocationInclusion
|
||||
from . import SC2World, get_first_mission
|
||||
|
||||
|
||||
class HoverableButton(HoverBehavior, Button):
|
||||
@@ -269,7 +269,7 @@ class SC2Manager(GameManager):
|
||||
for loc in self.ctx.locations_for_mission(mission_name):
|
||||
if loc in self.ctx.missing_locations:
|
||||
count += 1
|
||||
locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names.lookup_in_slot(loc))
|
||||
locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names.lookup_in_game(loc))
|
||||
|
||||
plando_locations = []
|
||||
for plando_loc in self.ctx.plando_locations:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, TYPE_CHECKING
|
||||
from typing import Dict, TYPE_CHECKING
|
||||
from collections.abc import Callable
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import forbid_item
|
||||
@@ -78,7 +78,7 @@ def all_skull_dials_available(state: CollectionState, player: int) -> bool:
|
||||
|
||||
|
||||
def get_rules_lookup(player: int):
|
||||
rules_lookup: Dict[str, List[Callable[[CollectionState], bool]]] = {
|
||||
rules_lookup: Dict[str, Dict[str, Callable[[CollectionState], bool]]] = {
|
||||
"entrances": {
|
||||
"To Office Elevator From Underground Blue Tunnels": lambda state: state.has("Key for Office Elevator", player),
|
||||
"To Office Elevator From Office": lambda state: state.has("Key for Office Elevator", player),
|
||||
@@ -195,6 +195,15 @@ def set_rules(world: "ShiversWorld") -> None:
|
||||
for location_name, rule in rules_lookup["lightning"].items():
|
||||
multiworld.get_location(location_name, player).access_rule = rule
|
||||
|
||||
# Register indirect conditions
|
||||
multiworld.register_indirect_condition(world.get_region("Burial"), world.get_entrance("To Slide Room"))
|
||||
multiworld.register_indirect_condition(world.get_region("Egypt"), world.get_entrance("To Slide Room"))
|
||||
multiworld.register_indirect_condition(world.get_region("Gods Room"), world.get_entrance("To Slide Room"))
|
||||
multiworld.register_indirect_condition(world.get_region("Prehistoric"), world.get_entrance("To Slide Room"))
|
||||
multiworld.register_indirect_condition(world.get_region("Tar River"), world.get_entrance("To Slide Room"))
|
||||
multiworld.register_indirect_condition(world.get_region("Werewolf"), world.get_entrance("To Slide Room"))
|
||||
multiworld.register_indirect_condition(world.get_region("Prehistoric"), world.get_entrance("To Tar River From Lobby"))
|
||||
|
||||
# forbid cloth in janitor closet and oil in tar river
|
||||
forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Bottom DUPE", player)
|
||||
forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Top DUPE", player)
|
||||
@@ -226,7 +235,3 @@ def set_rules(world: "ShiversWorld") -> None:
|
||||
|
||||
# Set completion condition
|
||||
multiworld.completion_condition[player] = lambda state: (first_nine_ixupi_capturable(state, player) and lightning_capturable(state, player))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ class SMSNIClient(SNIClient):
|
||||
location_id = locations_start_id + item_index
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
location = ctx.location_names.lookup_in_game(location_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
@@ -151,7 +151,7 @@ class SMSNIClient(SNIClient):
|
||||
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
||||
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ class SMWSNIClient(SNIClient):
|
||||
|
||||
next_trap, message = self.trap_queue.pop(0)
|
||||
|
||||
from worlds.smw.Rom import trap_rom_data
|
||||
from .Rom import trap_rom_data
|
||||
if next_trap.item in trap_rom_data:
|
||||
trap_active = await snes_read(ctx, WRAM_START + trap_rom_data[next_trap.item][0], 0x3)
|
||||
|
||||
@@ -349,8 +349,8 @@ class SMWSNIClient(SNIClient):
|
||||
blocksanity_flags = bytearray(await snes_read(ctx, SMW_BLOCKSANITY_FLAGS, 0xC))
|
||||
blocksanity_active = await snes_read(ctx, SMW_BLOCKSANITY_ACTIVE_ADDR, 0x1)
|
||||
level_clear_flags = bytearray(await snes_read(ctx, SMW_LEVEL_CLEAR_FLAGS, 0x60))
|
||||
from worlds.smw.Rom import item_rom_data, ability_rom_data, trap_rom_data, icon_rom_data
|
||||
from worlds.smw.Levels import location_id_to_level_id, level_info_dict, level_blocks_data
|
||||
from .Rom import item_rom_data, ability_rom_data, trap_rom_data, icon_rom_data
|
||||
from .Levels import location_id_to_level_id, level_info_dict, level_blocks_data
|
||||
from worlds import AutoWorldRegister
|
||||
for loc_name, level_data in location_id_to_level_id.items():
|
||||
loc_id = AutoWorldRegister.world_types[ctx.game].location_name_to_id[loc_name]
|
||||
@@ -448,7 +448,7 @@ class SMWSNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
@@ -501,14 +501,14 @@ class SMWSNIClient(SNIClient):
|
||||
recv_index += 1
|
||||
sending_game = ctx.slot_info[item.player].game
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
if self.should_show_message(ctx, item):
|
||||
if item.item != 0xBC0012 and item.item not in trap_rom_data:
|
||||
# Don't send messages for Boss Tokens
|
||||
item_name = ctx.item_names.lookup_in_slot(item.item)
|
||||
item_name = ctx.item_names.lookup_in_game(item.item)
|
||||
player_name = ctx.player_names[item.player]
|
||||
|
||||
receive_message = generate_received_text(item_name, player_name)
|
||||
@@ -516,7 +516,7 @@ class SMWSNIClient(SNIClient):
|
||||
|
||||
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF]))
|
||||
if item.item in trap_rom_data:
|
||||
item_name = ctx.item_names.lookup_in_slot(item.item)
|
||||
item_name = ctx.item_names.lookup_in_game(item.item)
|
||||
player_name = ctx.player_names[item.player]
|
||||
|
||||
receive_message = generate_received_text(item_name, player_name)
|
||||
@@ -597,7 +597,7 @@ class SMWSNIClient(SNIClient):
|
||||
for loc_id in ctx.checked_locations:
|
||||
if loc_id not in ctx.locations_checked:
|
||||
ctx.locations_checked.add(loc_id)
|
||||
loc_name = ctx.location_names.lookup_in_slot(loc_id)
|
||||
loc_name = ctx.location_names.lookup_in_game(loc_id)
|
||||
|
||||
if loc_name not in location_id_to_level_id:
|
||||
continue
|
||||
|
||||
@@ -109,7 +109,7 @@ class SMZ3SNIClient(SNIClient):
|
||||
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
location = ctx.location_names.lookup_in_game(location_id)
|
||||
snes_logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
|
||||
@@ -132,7 +132,7 @@ class SMZ3SNIClient(SNIClient):
|
||||
item_out_ptr += 1
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + recv_progress_addr_table_offset, bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
@@ -15,7 +15,7 @@ class SubnauticaTest(unittest.TestCase):
|
||||
self.assertGreater(self.scancutoff, id)
|
||||
|
||||
def testGroupAssociation(self):
|
||||
from worlds.subnautica import items
|
||||
from .. import items
|
||||
for item_id, item_data in items.item_table.items():
|
||||
if item_data.type == items.ItemType.group:
|
||||
with self.subTest(item=item_data.name):
|
||||
|
||||
@@ -53,6 +53,7 @@ You can also use the Universal Tracker (by Faris and qwint) to find a complete l
|
||||
## What should I know regarding logic?
|
||||
In general:
|
||||
- Nighttime is not considered in logic. Every check in the game is obtainable during the day.
|
||||
- Bushes are not considered in logic. It is assumed that the player will find a way past them, whether it is with a sword, a bomb, fire, luring an enemy, etc. There is also an option in the in-game randomizer settings menu to clear some of the early bushes.
|
||||
- The Cathedral is accessible during the day by using the Hero's Laurels to reach the Overworld fuse near the Swamp entrance.
|
||||
- The Secret Legend chest at the Cathedral can be obtained during the day by opening the Holy Cross door from the outside.
|
||||
|
||||
@@ -81,8 +82,13 @@ Notes:
|
||||
- The Entrance Randomizer option must be enabled for it to work.
|
||||
- The `direction` field is not supported. Connections are always coupled.
|
||||
- For a list of entrance names, check `er_data.py` in the TUNIC world folder or generate a game with the Entrance Randomizer option enabled and check the spoiler log.
|
||||
- There is no limit to the number of Shops hard-coded into place.
|
||||
- There is no limit to the number of Shops you can plando.
|
||||
- If you have more than one shop in a scene, you may be wrong warped when exiting a shop.
|
||||
- If you have a shop in every scene, and you have an odd number of shops, it will error out.
|
||||
|
||||
See the [Archipelago Plando Guide](../../../tutorial/Archipelago/plando/en) for more information on Plando and Connection Plando.
|
||||
|
||||
## Is there anything else I should know?
|
||||
You can go to [The TUNIC Randomizer Website](https://rando.tunic.run/) for a list of randomizer features as well as some helpful tips.
|
||||
You can use the Fairy Seeking Spell (ULU RDR) to locate the nearest unchecked location.
|
||||
You can use the Entrance Seeking Spell (RDR ULU) to locate the nearest unused entrance.
|
||||
|
||||
@@ -105,7 +105,6 @@ item_table = {
|
||||
non_key_items = {
|
||||
"Butterscotch Pie": 1,
|
||||
"500G": 2,
|
||||
"1000G": 2,
|
||||
"Face Steak": 1,
|
||||
"Snowman Piece": 1,
|
||||
"Instant Noodles": 1,
|
||||
@@ -147,6 +146,7 @@ plot_items = {
|
||||
key_items = {
|
||||
"Complete Skeleton": 1,
|
||||
"Fish": 1,
|
||||
"1000G": 2,
|
||||
"DT Extractor": 1,
|
||||
"Mettaton Plush": 1,
|
||||
"Punch Card": 3,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import typing
|
||||
from Options import Choice, Option, Toggle, Range
|
||||
from Options import Choice, Toggle, Range, PerGameCommonOptions
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class RouteRequired(Choice):
|
||||
@@ -86,17 +86,17 @@ class RandoBattleOptions(Toggle):
|
||||
default = 0
|
||||
|
||||
|
||||
undertale_options: typing.Dict[str, type(Option)] = {
|
||||
"route_required": RouteRequired,
|
||||
"starting_area": StartingArea,
|
||||
"key_hunt": KeyHunt,
|
||||
"key_pieces": KeyPieces,
|
||||
"rando_love": RandomizeLove,
|
||||
"rando_stats": RandomizeStats,
|
||||
"temy_include": IncludeTemy,
|
||||
"no_equips": NoEquips,
|
||||
"only_flakes": OnlyFlakes,
|
||||
"prog_armor": ProgressiveArmor,
|
||||
"prog_weapons": ProgressiveWeapons,
|
||||
"rando_item_button": RandoBattleOptions,
|
||||
}
|
||||
@dataclass
|
||||
class UndertaleOptions(PerGameCommonOptions):
|
||||
route_required: RouteRequired
|
||||
starting_area: StartingArea
|
||||
key_hunt: KeyHunt
|
||||
key_pieces: KeyPieces
|
||||
rando_love: RandomizeLove
|
||||
rando_stats: RandomizeStats
|
||||
temy_include: IncludeTemy
|
||||
no_equips: NoEquips
|
||||
only_flakes: OnlyFlakes
|
||||
prog_armor: ProgressiveArmor
|
||||
prog_weapons: ProgressiveWeapons
|
||||
rando_item_button: RandoBattleOptions
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
from worlds.generic.Rules import set_rule, add_rule, add_item_rule
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from worlds.generic.Rules import set_rule, add_rule
|
||||
from BaseClasses import CollectionState
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import UndertaleWorld
|
||||
|
||||
|
||||
def _undertale_is_route(state: CollectionState, player: int, route: int):
|
||||
def _undertale_is_route(world: "UndertaleWorld", route: int):
|
||||
if route == 3:
|
||||
return state.multiworld.route_required[player].current_key == "all_routes"
|
||||
if state.multiworld.route_required[player].current_key == "all_routes":
|
||||
return world.options.route_required.current_key == "all_routes"
|
||||
if world.options.route_required.current_key == "all_routes":
|
||||
return True
|
||||
if route == 0:
|
||||
return state.multiworld.route_required[player].current_key == "neutral"
|
||||
return world.options.route_required.current_key == "neutral"
|
||||
if route == 1:
|
||||
return state.multiworld.route_required[player].current_key == "pacifist"
|
||||
return world.options.route_required.current_key == "pacifist"
|
||||
if route == 2:
|
||||
return state.multiworld.route_required[player].current_key == "genocide"
|
||||
return world.options.route_required.current_key == "genocide"
|
||||
return False
|
||||
|
||||
|
||||
@@ -27,7 +31,7 @@ def _undertale_has_plot(state: CollectionState, player: int, item: str):
|
||||
return state.has("DT Extractor", player)
|
||||
|
||||
|
||||
def _undertale_can_level(state: CollectionState, exp: int, lvl: int):
|
||||
def _undertale_can_level(exp: int, lvl: int):
|
||||
if exp >= 10 and lvl == 1:
|
||||
return True
|
||||
elif exp >= 30 and lvl == 2:
|
||||
@@ -70,7 +74,9 @@ def _undertale_can_level(state: CollectionState, exp: int, lvl: int):
|
||||
|
||||
|
||||
# Sets rules on entrances and advancements that are always applied
|
||||
def set_rules(multiworld: MultiWorld, player: int):
|
||||
def set_rules(world: "UndertaleWorld"):
|
||||
player = world.player
|
||||
multiworld = world.multiworld
|
||||
set_rule(multiworld.get_entrance("Ruins Hub", player), lambda state: state.has("Ruins Key", player))
|
||||
set_rule(multiworld.get_entrance("Snowdin Hub", player), lambda state: state.has("Snowdin Key", player))
|
||||
set_rule(multiworld.get_entrance("Waterfall Hub", player), lambda state: state.has("Waterfall Key", player))
|
||||
@@ -81,16 +87,16 @@ def set_rules(multiworld: MultiWorld, player: int):
|
||||
set_rule(multiworld.get_entrance("New Home Exit", player),
|
||||
lambda state: (state.has("Left Home Key", player) and
|
||||
state.has("Right Home Key", player)) or
|
||||
state.has("Key Piece", player, state.multiworld.key_pieces[player].value))
|
||||
if _undertale_is_route(multiworld.state, player, 1):
|
||||
state.has("Key Piece", player, world.options.key_pieces.value))
|
||||
if _undertale_is_route(world, 1):
|
||||
set_rule(multiworld.get_entrance("Papyrus\" Home Entrance", player),
|
||||
lambda state: _undertale_has_plot(state, player, "Complete Skeleton"))
|
||||
set_rule(multiworld.get_entrance("Undyne\"s Home Entrance", player),
|
||||
lambda state: _undertale_has_plot(state, player, "Fish") and state.has("Papyrus Date", player))
|
||||
set_rule(multiworld.get_entrance("Lab Elevator", player),
|
||||
lambda state: state.has("Alphys Date", player) and state.has("DT Extractor", player) and
|
||||
((state.has("Left Home Key", player) and state.has("Right Home Key", player)) or
|
||||
state.has("Key Piece", player, state.multiworld.key_pieces[player].value)))
|
||||
((state.has("Left Home Key", player) and state.has("Right Home Key", player)) or
|
||||
state.has("Key Piece", player, world.options.key_pieces.value)))
|
||||
set_rule(multiworld.get_location("Alphys Date", player),
|
||||
lambda state: state.can_reach("New Home", "Region", player) and state.has("Undyne Letter EX", player)
|
||||
and state.has("Undyne Date", player))
|
||||
@@ -101,7 +107,10 @@ def set_rules(multiworld: MultiWorld, player: int):
|
||||
set_rule(multiworld.get_location("True Lab Plot", player),
|
||||
lambda state: state.can_reach("New Home", "Region", player)
|
||||
and state.can_reach("Letter Quest", "Location", player)
|
||||
and state.can_reach("Alphys Date", "Location", player))
|
||||
and state.can_reach("Alphys Date", "Location", player)
|
||||
and ((state.has("Left Home Key", player) and
|
||||
state.has("Right Home Key", player)) or
|
||||
state.has("Key Piece", player, world.options.key_pieces.value)))
|
||||
set_rule(multiworld.get_location("Chisps Machine", player),
|
||||
lambda state: state.can_reach("True Lab", "Region", player))
|
||||
set_rule(multiworld.get_location("Dog Sale 1", player),
|
||||
@@ -118,7 +127,7 @@ def set_rules(multiworld: MultiWorld, player: int):
|
||||
lambda state: state.can_reach("News Show", "Region", player) and state.has("Hot Dog...?", player, 1))
|
||||
set_rule(multiworld.get_location("Letter Quest", player),
|
||||
lambda state: state.can_reach("Last Corridor", "Region", player) and state.has("Undyne Date", player))
|
||||
if (not _undertale_is_route(multiworld.state, player, 2)) or _undertale_is_route(multiworld.state, player, 3):
|
||||
if (not _undertale_is_route(world, 2)) or _undertale_is_route(world, 3):
|
||||
set_rule(multiworld.get_location("Nicecream Punch Card", player),
|
||||
lambda state: state.has("Punch Card", player, 3) and state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Nicecream Snowdin", player),
|
||||
@@ -129,26 +138,26 @@ def set_rules(multiworld: MultiWorld, player: int):
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Apron Hidden", player),
|
||||
lambda state: state.can_reach("Cooking Show", "Region", player))
|
||||
if _undertale_is_route(multiworld.state, player, 2) and \
|
||||
(bool(multiworld.rando_love[player].value) or bool(multiworld.rando_stats[player].value)):
|
||||
if _undertale_is_route(world, 2) and \
|
||||
(bool(world.options.rando_love.value) or bool(world.options.rando_stats.value)):
|
||||
maxlv = 1
|
||||
exp = 190
|
||||
curarea = "Old Home"
|
||||
|
||||
while maxlv < 20:
|
||||
maxlv += 1
|
||||
if multiworld.rando_love[player]:
|
||||
if world.options.rando_love:
|
||||
set_rule(multiworld.get_location(("LOVE " + str(maxlv)), player), lambda state: False)
|
||||
if multiworld.rando_stats[player]:
|
||||
if world.options.rando_stats:
|
||||
set_rule(multiworld.get_location(("ATK "+str(maxlv)), player), lambda state: False)
|
||||
set_rule(multiworld.get_location(("HP "+str(maxlv)), player), lambda state: False)
|
||||
if maxlv in {5, 9, 13, 17}:
|
||||
set_rule(multiworld.get_location(("DEF "+str(maxlv)), player), lambda state: False)
|
||||
maxlv = 1
|
||||
while maxlv < 20:
|
||||
while _undertale_can_level(multiworld.state, exp, maxlv):
|
||||
while _undertale_can_level(exp, maxlv):
|
||||
maxlv += 1
|
||||
if multiworld.rando_stats[player]:
|
||||
if world.options.rando_stats:
|
||||
if curarea == "Old Home":
|
||||
add_rule(multiworld.get_location(("ATK "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Old Home", "Region", player)), combine="or")
|
||||
@@ -197,7 +206,7 @@ def set_rules(multiworld: MultiWorld, player: int):
|
||||
if maxlv == 5 or maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
add_rule(multiworld.get_location(("DEF "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("New Home Exit", "Entrance", player)), combine="or")
|
||||
if multiworld.rando_love[player]:
|
||||
if world.options.rando_love:
|
||||
if curarea == "Old Home":
|
||||
add_rule(multiworld.get_location(("LOVE "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Old Home", "Region", player)), combine="or")
|
||||
@@ -307,9 +316,9 @@ def set_rules(multiworld: MultiWorld, player: int):
|
||||
|
||||
|
||||
# Sets rules on completion condition
|
||||
def set_completion_rules(multiworld: MultiWorld, player: int):
|
||||
completion_requirements = lambda state: state.can_reach("Barrier", "Region", player)
|
||||
if _undertale_is_route(multiworld.state, player, 1):
|
||||
completion_requirements = lambda state: state.can_reach("True Lab", "Region", player)
|
||||
|
||||
multiworld.completion_condition[player] = lambda state: completion_requirements(state)
|
||||
def set_completion_rules(world: "UndertaleWorld"):
|
||||
player = world.player
|
||||
multiworld = world.multiworld
|
||||
multiworld.completion_condition[player] = lambda state: state.can_reach("Barrier", "Region", player)
|
||||
if _undertale_is_route(world, 1):
|
||||
multiworld.completion_condition[player] = lambda state: state.can_reach("True Lab", "Region", player)
|
||||
|
||||
@@ -5,9 +5,9 @@ from .Regions import undertale_regions, link_undertale_areas
|
||||
from .Rules import set_rules, set_completion_rules
|
||||
from worlds.generic.Rules import exclusion_rules
|
||||
from BaseClasses import Region, Entrance, Tutorial, Item
|
||||
from .Options import undertale_options
|
||||
from .Options import UndertaleOptions
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import Component, components, Type
|
||||
from worlds.LauncherComponents import Component, components
|
||||
from multiprocessing import Process
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ class UndertaleWorld(World):
|
||||
from their underground prison.
|
||||
"""
|
||||
game = "Undertale"
|
||||
option_definitions = undertale_options
|
||||
options_dataclass = UndertaleOptions
|
||||
options: UndertaleOptions
|
||||
web = UndertaleWeb()
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
@@ -54,39 +55,55 @@ class UndertaleWorld(World):
|
||||
|
||||
def _get_undertale_data(self):
|
||||
return {
|
||||
"world_seed": self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
||||
"world_seed": self.random.getrandbits(32),
|
||||
"seed_name": self.multiworld.seed_name,
|
||||
"player_name": self.multiworld.get_player_name(self.player),
|
||||
"player_id": self.player,
|
||||
"client_version": self.required_client_version,
|
||||
"race": self.multiworld.is_race,
|
||||
"route": self.multiworld.route_required[self.player].current_key,
|
||||
"starting_area": self.multiworld.starting_area[self.player].current_key,
|
||||
"temy_armor_include": bool(self.multiworld.temy_include[self.player].value),
|
||||
"only_flakes": bool(self.multiworld.only_flakes[self.player].value),
|
||||
"no_equips": bool(self.multiworld.no_equips[self.player].value),
|
||||
"key_hunt": bool(self.multiworld.key_hunt[self.player].value),
|
||||
"key_pieces": self.multiworld.key_pieces[self.player].value,
|
||||
"rando_love": bool(self.multiworld.rando_love[self.player].value),
|
||||
"rando_stats": bool(self.multiworld.rando_stats[self.player].value),
|
||||
"prog_armor": bool(self.multiworld.prog_armor[self.player].value),
|
||||
"prog_weapons": bool(self.multiworld.prog_weapons[self.player].value),
|
||||
"rando_item_button": bool(self.multiworld.rando_item_button[self.player].value)
|
||||
"route": self.options.route_required.current_key,
|
||||
"starting_area": self.options.starting_area.current_key,
|
||||
"temy_armor_include": bool(self.options.temy_include.value),
|
||||
"only_flakes": bool(self.options.only_flakes.value),
|
||||
"no_equips": bool(self.options.no_equips.value),
|
||||
"key_hunt": bool(self.options.key_hunt.value),
|
||||
"key_pieces": self.options.key_pieces.value,
|
||||
"rando_love": bool(self.options.rando_love.value),
|
||||
"rando_stats": bool(self.options.rando_stats.value),
|
||||
"prog_armor": bool(self.options.prog_armor.value),
|
||||
"prog_weapons": bool(self.options.prog_weapons.value),
|
||||
"rando_item_button": bool(self.options.rando_item_button.value)
|
||||
}
|
||||
|
||||
def get_filler_item_name(self):
|
||||
if self.options.route_required == "all_routes":
|
||||
junk_pool = junk_weights_all
|
||||
elif self.options.route_required == "genocide":
|
||||
junk_pool = junk_weights_genocide
|
||||
elif self.options.route_required == "neutral":
|
||||
junk_pool = junk_weights_neutral
|
||||
elif self.options.route_required == "pacifist":
|
||||
junk_pool = junk_weights_pacifist
|
||||
else:
|
||||
junk_pool = junk_weights_all
|
||||
if not self.options.only_flakes:
|
||||
return self.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()))[0]
|
||||
else:
|
||||
return "Temmie Flakes"
|
||||
|
||||
def create_items(self):
|
||||
self.multiworld.get_location("Undyne Date", self.player).place_locked_item(self.create_item("Undyne Date"))
|
||||
self.multiworld.get_location("Alphys Date", self.player).place_locked_item(self.create_item("Alphys Date"))
|
||||
self.multiworld.get_location("Papyrus Date", self.player).place_locked_item(self.create_item("Papyrus Date"))
|
||||
# Generate item pool
|
||||
itempool = []
|
||||
if self.multiworld.route_required[self.player] == "all_routes":
|
||||
if self.options.route_required == "all_routes":
|
||||
junk_pool = junk_weights_all.copy()
|
||||
elif self.multiworld.route_required[self.player] == "genocide":
|
||||
elif self.options.route_required == "genocide":
|
||||
junk_pool = junk_weights_genocide.copy()
|
||||
elif self.multiworld.route_required[self.player] == "neutral":
|
||||
elif self.options.route_required == "neutral":
|
||||
junk_pool = junk_weights_neutral.copy()
|
||||
elif self.multiworld.route_required[self.player] == "pacifist":
|
||||
elif self.options.route_required == "pacifist":
|
||||
junk_pool = junk_weights_pacifist.copy()
|
||||
else:
|
||||
junk_pool = junk_weights_all.copy()
|
||||
@@ -99,73 +116,68 @@ class UndertaleWorld(World):
|
||||
itempool += [name] * num
|
||||
for name, num in non_key_items.items():
|
||||
itempool += [name] * num
|
||||
if self.multiworld.rando_item_button[self.player]:
|
||||
if self.options.rando_item_button:
|
||||
itempool += ["ITEM"]
|
||||
else:
|
||||
self.multiworld.push_precollected(self.create_item("ITEM"))
|
||||
self.multiworld.push_precollected(self.create_item("FIGHT"))
|
||||
self.multiworld.push_precollected(self.create_item("ACT"))
|
||||
self.multiworld.push_precollected(self.create_item("MERCY"))
|
||||
if self.multiworld.route_required[self.player] == "genocide":
|
||||
if self.options.route_required == "genocide":
|
||||
itempool = [item for item in itempool if item != "Popato Chisps" and item != "Stained Apron" and
|
||||
item != "Nice Cream" and item != "Hot Cat" and item != "Hot Dog...?" and item != "Punch Card"]
|
||||
elif self.multiworld.route_required[self.player] == "neutral":
|
||||
elif self.options.route_required == "neutral":
|
||||
itempool = [item for item in itempool if item != "Popato Chisps" and item != "Hot Cat" and
|
||||
item != "Hot Dog...?"]
|
||||
if self.multiworld.route_required[self.player] == "pacifist" or \
|
||||
self.multiworld.route_required[self.player] == "all_routes":
|
||||
if self.options.route_required == "pacifist" or self.options.route_required == "all_routes":
|
||||
itempool += ["Undyne Letter EX"]
|
||||
else:
|
||||
itempool.remove("Complete Skeleton")
|
||||
itempool.remove("Fish")
|
||||
itempool.remove("DT Extractor")
|
||||
itempool.remove("Hush Puppy")
|
||||
if self.multiworld.key_hunt[self.player]:
|
||||
itempool += ["Key Piece"] * self.multiworld.key_pieces[self.player].value
|
||||
if self.options.key_hunt:
|
||||
itempool += ["Key Piece"] * self.options.key_pieces.value
|
||||
else:
|
||||
itempool += ["Left Home Key"]
|
||||
itempool += ["Right Home Key"]
|
||||
if not self.multiworld.rando_love[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
if not self.options.rando_love or \
|
||||
(self.options.route_required != "genocide" and self.options.route_required != "all_routes"):
|
||||
itempool = [item for item in itempool if not item == "LOVE"]
|
||||
if not self.multiworld.rando_stats[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
if not self.options.rando_stats or \
|
||||
(self.options.route_required != "genocide" and self.options.route_required != "all_routes"):
|
||||
itempool = [item for item in itempool if not (item == "ATK Up" or item == "DEF Up" or item == "HP Up")]
|
||||
if self.multiworld.temy_include[self.player]:
|
||||
if self.options.temy_include:
|
||||
itempool += ["temy armor"]
|
||||
if self.multiworld.no_equips[self.player]:
|
||||
if self.options.no_equips:
|
||||
itempool = [item for item in itempool if item not in required_armor and item not in required_weapons]
|
||||
else:
|
||||
if self.multiworld.prog_armor[self.player]:
|
||||
if self.options.prog_armor:
|
||||
itempool = [item if (item not in required_armor and not item == "temy armor") else
|
||||
"Progressive Armor" for item in itempool]
|
||||
if self.multiworld.prog_weapons[self.player]:
|
||||
if self.options.prog_weapons:
|
||||
itempool = [item if item not in required_weapons else "Progressive Weapons" for item in itempool]
|
||||
if self.multiworld.route_required[self.player] == "genocide" or \
|
||||
self.multiworld.route_required[self.player] == "all_routes":
|
||||
if not self.multiworld.only_flakes[self.player]:
|
||||
if self.options.route_required == "genocide" or \
|
||||
self.options.route_required == "all_routes":
|
||||
if not self.options.only_flakes:
|
||||
itempool += ["Snowman Piece"] * 2
|
||||
if not self.multiworld.no_equips[self.player]:
|
||||
if not self.options.no_equips:
|
||||
itempool = ["Real Knife" if item == "Worn Dagger" else "The Locket"
|
||||
if item == "Heart Locket" else item for item in itempool]
|
||||
if self.multiworld.only_flakes[self.player]:
|
||||
if self.options.only_flakes:
|
||||
itempool = [item for item in itempool if item not in non_key_items]
|
||||
|
||||
starting_key = self.multiworld.starting_area[self.player].current_key.title() + " Key"
|
||||
starting_key = self.options.starting_area.current_key.title() + " Key"
|
||||
itempool.remove(starting_key)
|
||||
self.multiworld.push_precollected(self.create_item(starting_key))
|
||||
# Choose locations to automatically exclude based on settings
|
||||
exclusion_pool = set()
|
||||
exclusion_pool.update(exclusion_table[self.multiworld.route_required[self.player].current_key])
|
||||
if not self.multiworld.rando_love[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
exclusion_pool.update(exclusion_table[self.options.route_required.current_key])
|
||||
if not self.options.rando_love or \
|
||||
(self.options.route_required != "genocide" and self.options.route_required != "all_routes"):
|
||||
exclusion_pool.update(exclusion_table["NoLove"])
|
||||
if not self.multiworld.rando_stats[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
if not self.options.rando_stats or \
|
||||
(self.options.route_required != "genocide" and self.options.route_required != "all_routes"):
|
||||
exclusion_pool.update(exclusion_table["NoStats"])
|
||||
|
||||
# Choose locations to automatically exclude based on settings
|
||||
@@ -173,36 +185,33 @@ class UndertaleWorld(World):
|
||||
exclusion_checks.update(["Nicecream Punch Card", "Hush Trade"])
|
||||
exclusion_rules(self.multiworld, self.player, exclusion_checks)
|
||||
|
||||
# Fill remaining items with randomly generated junk or Temmie Flakes
|
||||
if not self.multiworld.only_flakes[self.player]:
|
||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
||||
k=len(self.location_names)-len(itempool)-len(exclusion_pool))
|
||||
else:
|
||||
itempool += ["Temmie Flakes"] * (len(self.location_names) - len(itempool) - len(exclusion_pool))
|
||||
# Convert itempool into real items
|
||||
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
||||
# Fill remaining items with randomly generated junk or Temmie Flakes
|
||||
while len(itempool) < len(self.multiworld.get_unfilled_locations(self.player)):
|
||||
itempool.append(self.create_filler())
|
||||
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.multiworld, self.player)
|
||||
set_completion_rules(self.multiworld, self.player)
|
||||
set_rules(self)
|
||||
set_completion_rules(self)
|
||||
|
||||
def create_regions(self):
|
||||
def UndertaleRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, self.player, self.multiworld)
|
||||
ret.locations += [UndertaleAdvancement(self.player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name and
|
||||
(loc_name not in exclusion_table["NoStats"] or
|
||||
(self.multiworld.rando_stats[self.player] and
|
||||
(self.multiworld.route_required[self.player] == "genocide" or
|
||||
self.multiworld.route_required[self.player] == "all_routes"))) and
|
||||
(loc_name not in exclusion_table["NoLove"] or
|
||||
(self.multiworld.rando_love[self.player] and
|
||||
(self.multiworld.route_required[self.player] == "genocide" or
|
||||
self.multiworld.route_required[self.player] == "all_routes"))) and
|
||||
loc_name not in exclusion_table[self.multiworld.route_required[self.player].current_key]]
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name and
|
||||
(loc_name not in exclusion_table["NoStats"] or
|
||||
(self.options.rando_stats and
|
||||
(self.options.route_required == "genocide" or
|
||||
self.options.route_required == "all_routes"))) and
|
||||
(loc_name not in exclusion_table["NoLove"] or
|
||||
(self.options.rando_love and
|
||||
(self.options.route_required == "genocide" or
|
||||
self.options.route_required == "all_routes"))) and
|
||||
loc_name not in exclusion_table[self.options.route_required.current_key]]
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(self.player, exit, ret))
|
||||
return ret
|
||||
@@ -212,11 +221,11 @@ class UndertaleWorld(World):
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = self._get_undertale_data()
|
||||
for option_name in undertale_options:
|
||||
for option_name in self.options.as_dict():
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if (option_name == "rando_love" or option_name == "rando_stats") and \
|
||||
self.multiworld.route_required[self.player] != "genocide" and \
|
||||
self.multiworld.route_required[self.player] != "all_routes":
|
||||
self.options.route_required != "genocide" and \
|
||||
self.options.route_required != "all_routes":
|
||||
option.value = False
|
||||
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
|
||||
@@ -87,9 +87,7 @@ class YoshisIslandSNIClient(SNIClient):
|
||||
|
||||
if game_mode is None:
|
||||
return
|
||||
elif goal_flag[0] != 0x00:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
|
||||
elif game_mode[0] not in VALID_GAME_STATES:
|
||||
return
|
||||
elif item_received[0] > 0x00:
|
||||
@@ -101,6 +99,10 @@ class YoshisIslandSNIClient(SNIClient):
|
||||
ctx.rom = None
|
||||
return
|
||||
|
||||
if goal_flag[0] != 0x00:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
|
||||
new_checks = []
|
||||
from .Rom import location_table
|
||||
|
||||
@@ -116,7 +118,7 @@ class YoshisIslandSNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
total_locations = len(ctx.missing_locations) + len(ctx.checked_locations)
|
||||
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
|
||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
|
||||
@@ -127,7 +129,7 @@ class YoshisIslandSNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info("Received %s from %s (%s) (%d/%d in list)" % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), "red", "bold"),
|
||||
color(ctx.item_names.lookup_in_game(item.item), "red", "bold"),
|
||||
color(ctx.player_names[item.player], "yellow"),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from . import ZillionTestBase
|
||||
|
||||
from worlds.zillion.options import ZillionJumpLevels, ZillionGunLevels, ZillionOptions, validate
|
||||
from ..options import ZillionJumpLevels, ZillionGunLevels, ZillionOptions, validate
|
||||
from zilliandomizer.options import VBLR_CHOICES
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import cast
|
||||
from . import ZillionTestBase
|
||||
|
||||
from worlds.zillion import ZillionWorld
|
||||
from .. import ZillionWorld
|
||||
|
||||
|
||||
class SeedTest(ZillionTestBase):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import cast
|
||||
from test.bases import WorldTestBase
|
||||
from worlds.zillion import ZillionWorld
|
||||
from .. import ZillionWorld
|
||||
|
||||
|
||||
class ZillionTestBase(WorldTestBase):
|
||||
|
||||
Reference in New Issue
Block a user