forked from mirror/Archipelago
Merge branch 'main' of https://github.com/Magnemania/Archipelago into sc2wol
This commit is contained in:
4
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -2,7 +2,7 @@ name: Bug Report
|
||||
description: File a bug report.
|
||||
title: "Bug: "
|
||||
labels:
|
||||
- bug
|
||||
- bug / fix
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -32,4 +32,4 @@ body:
|
||||
- Local generation
|
||||
- While playing
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -18,8 +18,8 @@ jobs:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest
|
||||
python -m pip install --upgrade pip wheel
|
||||
pip install flake8 pytest pytest-subtests
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
|
||||
4
.github/workflows/unittests.yml
vendored
4
.github/workflows/unittests.yml
vendored
@@ -32,8 +32,8 @@ jobs:
|
||||
python-version: ${{ matrix.python.version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest
|
||||
python -m pip install --upgrade pip wheel
|
||||
pip install flake8 pytest pytest-subtests
|
||||
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
|
||||
- name: Unittests
|
||||
run: |
|
||||
|
||||
@@ -152,8 +152,9 @@ class CommonContext:
|
||||
# locations
|
||||
locations_checked: typing.Set[int] # local state
|
||||
locations_scouted: typing.Set[int]
|
||||
missing_locations: typing.Set[int]
|
||||
missing_locations: typing.Set[int] # server state
|
||||
checked_locations: typing.Set[int] # server state
|
||||
server_locations: typing.Set[int] # all locations the server knows of, missing_location | checked_locations
|
||||
locations_info: typing.Dict[int, NetworkItem]
|
||||
|
||||
# internals
|
||||
@@ -184,8 +185,9 @@ class CommonContext:
|
||||
self.locations_checked = set() # local state
|
||||
self.locations_scouted = set()
|
||||
self.items_received = []
|
||||
self.missing_locations = set()
|
||||
self.missing_locations = set() # server state
|
||||
self.checked_locations = set() # server state
|
||||
self.server_locations = set() # all locations the server knows of, missing_location | checked_locations
|
||||
self.locations_info = {}
|
||||
|
||||
self.input_queue = asyncio.Queue()
|
||||
@@ -345,6 +347,8 @@ class CommonContext:
|
||||
cache_package = Utils.persistent_load().get("datapackage", {}).get("games", {})
|
||||
needed_updates: typing.Set[str] = set()
|
||||
for game in relevant_games:
|
||||
if game not in remote_datepackage_versions:
|
||||
continue
|
||||
remote_version: int = remote_datepackage_versions[game]
|
||||
|
||||
if remote_version == 0: # custom datapackage for this game
|
||||
@@ -632,6 +636,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
||||
# when /missing is used for the client side view of what is missing.
|
||||
ctx.missing_locations = set(args["missing_locations"])
|
||||
ctx.checked_locations = set(args["checked_locations"])
|
||||
ctx.server_locations = ctx.missing_locations | ctx. checked_locations
|
||||
|
||||
elif cmd == 'ReceivedItems':
|
||||
start_index = args["index"]
|
||||
|
||||
11
Generate.py
11
Generate.py
@@ -63,7 +63,7 @@ class PlandoSettings(enum.IntFlag):
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.value:
|
||||
return ", ".join((flag.name for flag in PlandoSettings if self.value & flag.value))
|
||||
return ", ".join(flag.name for flag in PlandoSettings if self.value & flag.value)
|
||||
return "Off"
|
||||
|
||||
|
||||
@@ -84,11 +84,6 @@ def mystery_argparse():
|
||||
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||
parser.add_argument('--multi', default=defaults["players"], type=lambda value: max(int(value), 1))
|
||||
parser.add_argument('--spoiler', type=int, default=defaults["spoiler"])
|
||||
parser.add_argument('--lttp_rom', default=options["lttp_options"]["rom_file"],
|
||||
help="Path to the 1.0 JP LttP Baserom.") # absolute, relative to cwd or relative to app path
|
||||
parser.add_argument('--sm_rom', default=options["sm_options"]["rom_file"],
|
||||
help="Path to the 1.0 JP SM Baserom.")
|
||||
parser.add_argument('--enemizercli', default=resolve_path(defaults["enemizer_path"], local_path))
|
||||
parser.add_argument('--outputpath', default=resolve_path(options["general_options"]["output_path"], user_path),
|
||||
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
|
||||
parser.add_argument('--race', action='store_true', default=defaults["race"])
|
||||
@@ -183,10 +178,6 @@ def main(args=None, callback=ERmain):
|
||||
|
||||
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level)
|
||||
|
||||
erargs.lttp_rom = args.lttp_rom
|
||||
erargs.sm_rom = args.sm_rom
|
||||
erargs.enemizercli = args.enemizercli
|
||||
|
||||
settings_cache: Dict[str, Tuple[argparse.Namespace, ...]] = \
|
||||
{fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.samesettings else None)
|
||||
for fname, yamls in weights_cache.items()}
|
||||
|
||||
18
Launcher.py
18
Launcher.py
@@ -10,16 +10,20 @@ Scroll down to components= to add components to the launcher as well as setup.py
|
||||
|
||||
|
||||
import argparse
|
||||
from os.path import isfile
|
||||
import sys
|
||||
from typing import Iterable, Sequence, Callable, Union, Optional
|
||||
import subprocess
|
||||
import itertools
|
||||
from Utils import is_frozen, user_path, local_path, init_logging, open_filename, messagebox,\
|
||||
is_windows, is_macos, is_linux
|
||||
from shutil import which
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from enum import Enum, auto
|
||||
from os.path import isfile
|
||||
from shutil import which
|
||||
from typing import Iterable, Sequence, Callable, Union, Optional
|
||||
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
||||
from Utils import is_frozen, user_path, local_path, init_logging, open_filename, messagebox, \
|
||||
is_windows, is_macos, is_linux
|
||||
|
||||
|
||||
def open_host_yaml():
|
||||
|
||||
@@ -752,6 +752,7 @@ class SpriteSelector():
|
||||
self.window['pady'] = 5
|
||||
self.spritesPerRow = 32
|
||||
self.all_sprites = []
|
||||
self.invalid_sprites = []
|
||||
self.sprite_pool = spritePool
|
||||
|
||||
def open_custom_sprite_dir(_evt):
|
||||
@@ -833,6 +834,13 @@ class SpriteSelector():
|
||||
self.window.focus()
|
||||
tkinter_center_window(self.window)
|
||||
|
||||
if self.invalid_sprites:
|
||||
invalid = sorted(self.invalid_sprites)
|
||||
logging.warning(f"The following sprites are invalid: {', '.join(invalid)}")
|
||||
msg = f"{invalid[0]} "
|
||||
msg += f"and {len(invalid)-1} more are invalid" if len(invalid) > 1 else "is invalid"
|
||||
messagebox.showerror("Invalid sprites detected", msg, parent=self.window)
|
||||
|
||||
def remove_from_sprite_pool(self, button, spritename):
|
||||
self.callback(("remove", spritename))
|
||||
self.spritePoolButtons.buttons.remove(button)
|
||||
@@ -897,7 +905,13 @@ class SpriteSelector():
|
||||
sprites = []
|
||||
|
||||
for file in os.listdir(path):
|
||||
sprites.append((file, Sprite(os.path.join(path, file))))
|
||||
if file == '.gitignore':
|
||||
continue
|
||||
sprite = Sprite(os.path.join(path, file))
|
||||
if sprite.valid:
|
||||
sprites.append((file, sprite))
|
||||
else:
|
||||
self.invalid_sprites.append(file)
|
||||
|
||||
sprites.sort(key=lambda s: str.lower(s[1].name or "").strip())
|
||||
|
||||
|
||||
1
Main.py
1
Main.py
@@ -70,7 +70,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
world.required_medallions = args.required_medallions.copy()
|
||||
world.game = args.game.copy()
|
||||
world.player_name = args.name.copy()
|
||||
world.enemizer = args.enemizercli
|
||||
world.sprite = args.sprite.copy()
|
||||
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ class Toggle(NumericOption):
|
||||
if type(data) == str:
|
||||
return cls.from_text(data)
|
||||
else:
|
||||
return cls(data)
|
||||
return cls(int(data))
|
||||
|
||||
@classmethod
|
||||
def get_option_name(cls, value):
|
||||
|
||||
53
SNIClient.py
53
SNIClient.py
@@ -149,8 +149,8 @@ class Context(CommonContext):
|
||||
def event_invalid_slot(self):
|
||||
if self.snes_socket is not None and not self.snes_socket.closed:
|
||||
asyncio.create_task(self.snes_socket.close())
|
||||
raise Exception('Invalid ROM detected, '
|
||||
'please verify that you have loaded the correct rom and reconnect your snes (/snes)')
|
||||
raise Exception("Invalid ROM detected, "
|
||||
"please verify that you have loaded the correct rom and reconnect your snes (/snes)")
|
||||
|
||||
async def server_auth(self, password_requested: bool = False):
|
||||
if password_requested and not self.password:
|
||||
@@ -158,7 +158,7 @@ class Context(CommonContext):
|
||||
if self.rom is None:
|
||||
self.awaiting_rom = True
|
||||
snes_logger.info(
|
||||
'No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)')
|
||||
"No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)")
|
||||
return
|
||||
self.awaiting_rom = False
|
||||
self.auth = self.rom
|
||||
@@ -262,7 +262,7 @@ async def deathlink_kill_player(ctx: Context):
|
||||
|
||||
SNES_RECONNECT_DELAY = 5
|
||||
|
||||
# LttP
|
||||
# FXPAK Pro protocol memory mapping used by SNI
|
||||
ROM_START = 0x000000
|
||||
WRAM_START = 0xF50000
|
||||
WRAM_SIZE = 0x20000
|
||||
@@ -293,21 +293,24 @@ SHOP_LEN = (len(Shops.shop_table) * 3) + 5
|
||||
DEATH_LINK_ACTIVE_ADDR = ROMNAME_START + 0x15 # 1 byte
|
||||
|
||||
# SM
|
||||
SM_ROMNAME_START = 0x007FC0
|
||||
SM_ROMNAME_START = ROM_START + 0x007FC0
|
||||
|
||||
SM_INGAME_MODES = {0x07, 0x09, 0x0b}
|
||||
SM_ENDGAME_MODES = {0x26, 0x27}
|
||||
SM_DEATH_MODES = {0x15, 0x17, 0x18, 0x19, 0x1A}
|
||||
|
||||
SM_RECV_PROGRESS_ADDR = SRAM_START + 0x2000 # 2 bytes
|
||||
SM_RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||
SM_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||
# RECV and SEND are from the gameplay's perspective: SNIClient writes to RECV queue and reads from SEND queue
|
||||
SM_RECV_QUEUE_START = SRAM_START + 0x2000
|
||||
SM_RECV_QUEUE_WCOUNT = SRAM_START + 0x2602
|
||||
SM_SEND_QUEUE_START = SRAM_START + 0x2700
|
||||
SM_SEND_QUEUE_RCOUNT = SRAM_START + 0x2680
|
||||
SM_SEND_QUEUE_WCOUNT = SRAM_START + 0x2682
|
||||
|
||||
SM_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x277f04 # 1 byte
|
||||
SM_REMOTE_ITEM_FLAG_ADDR = ROM_START + 0x277f06 # 1 byte
|
||||
|
||||
# SMZ3
|
||||
SMZ3_ROMNAME_START = 0x00FFC0
|
||||
SMZ3_ROMNAME_START = ROM_START + 0x00FFC0
|
||||
|
||||
SMZ3_INGAME_MODES = {0x07, 0x09, 0x0b}
|
||||
SMZ3_ENDGAME_MODES = {0x26, 0x27}
|
||||
@@ -1083,6 +1086,9 @@ async def game_watcher(ctx: Context):
|
||||
|
||||
if ctx.awaiting_rom:
|
||||
await ctx.server_auth(False)
|
||||
elif ctx.server is None:
|
||||
snes_logger.warning("ROM detected but no active multiworld server connection. " +
|
||||
"Connect using command: /connect server:port")
|
||||
|
||||
if ctx.auth and ctx.auth != ctx.rom:
|
||||
snes_logger.warning("ROM change detected, please reconnect to the multiworld server")
|
||||
@@ -1159,6 +1165,9 @@ async def game_watcher(ctx: Context):
|
||||
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": [scout_location]}])
|
||||
await track_locations(ctx, roomid, roomdata)
|
||||
elif ctx.game == GAME_SM:
|
||||
if ctx.server is None or ctx.slot is None:
|
||||
# not successfully connected to a multiworld server, cannot process the game sending items
|
||||
continue
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x0998, 1)
|
||||
if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
|
||||
currently_dead = gamemode[0] in SM_DEATH_MODES
|
||||
@@ -1169,25 +1178,25 @@ async def game_watcher(ctx: Context):
|
||||
ctx.finished_game = True
|
||||
continue
|
||||
|
||||
data = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x680, 4)
|
||||
data = await snes_read(ctx, SM_SEND_QUEUE_RCOUNT, 4)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
recv_index = data[0] | (data[1] << 8)
|
||||
recv_item = data[2] | (data[3] << 8)
|
||||
recv_item = data[2] | (data[3] << 8) # this is actually SM_SEND_QUEUE_WCOUNT
|
||||
|
||||
while (recv_index < recv_item):
|
||||
itemAdress = recv_index * 8
|
||||
message = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x700 + itemAdress, 8)
|
||||
message = await snes_read(ctx, SM_SEND_QUEUE_START + itemAdress, 8)
|
||||
# worldId = message[0] | (message[1] << 8) # unused
|
||||
# itemId = message[2] | (message[3] << 8) # unused
|
||||
itemIndex = (message[4] | (message[5] << 8)) >> 3
|
||||
|
||||
recv_index += 1
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x680,
|
||||
snes_buffered_write(ctx, SM_SEND_QUEUE_RCOUNT,
|
||||
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
|
||||
from worlds.sm.Locations import locations_start_id
|
||||
from worlds.sm import locations_start_id
|
||||
location_id = locations_start_id + itemIndex
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
@@ -1196,15 +1205,14 @@ async def game_watcher(ctx: Context):
|
||||
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]}])
|
||||
|
||||
data = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x600, 4)
|
||||
data = await snes_read(ctx, SM_RECV_QUEUE_WCOUNT, 2)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
# recv_itemOutPtr = data[0] | (data[1] << 8) # unused
|
||||
itemOutPtr = data[2] | (data[3] << 8)
|
||||
itemOutPtr = data[0] | (data[1] << 8)
|
||||
|
||||
from worlds.sm.Items import items_start_id
|
||||
from worlds.sm.Locations import locations_start_id
|
||||
from worlds.sm import items_start_id
|
||||
from worlds.sm import locations_start_id
|
||||
if itemOutPtr < len(ctx.items_received):
|
||||
item = ctx.items_received[itemOutPtr]
|
||||
itemId = item.item - items_start_id
|
||||
@@ -1214,10 +1222,10 @@ async def game_watcher(ctx: Context):
|
||||
locationId = 0x00 #backward compat
|
||||
|
||||
playerID = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + itemOutPtr * 4, bytes(
|
||||
snes_buffered_write(ctx, SM_RECV_QUEUE_START + itemOutPtr * 4, bytes(
|
||||
[playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, locationId & 0xFF]))
|
||||
itemOutPtr += 1
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x602,
|
||||
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
||||
bytes([itemOutPtr & 0xFF, (itemOutPtr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'),
|
||||
@@ -1225,6 +1233,9 @@ async def game_watcher(ctx: Context):
|
||||
ctx.location_names[item.location], itemOutPtr, len(ctx.items_received)))
|
||||
await snes_flush_writes(ctx)
|
||||
elif ctx.game == GAME_SMZ3:
|
||||
if ctx.server is None or ctx.slot is None:
|
||||
# not successfully connected to a multiworld server, cannot process the game sending items
|
||||
continue
|
||||
currentGame = await snes_read(ctx, SRAM_START + 0x33FE, 2)
|
||||
if (currentGame is not None):
|
||||
if (currentGame[0] != 0):
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import multiprocessing
|
||||
import logging
|
||||
import asyncio
|
||||
import copy
|
||||
import ctypes
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
import typing
|
||||
import queue
|
||||
from pathlib import Path
|
||||
|
||||
import nest_asyncio
|
||||
import sc2
|
||||
|
||||
from sc2.main import run_game
|
||||
from sc2.data import Race
|
||||
from sc2.bot_ai import BotAI
|
||||
from sc2.data import Race
|
||||
from sc2.main import run_game
|
||||
from sc2.player import Bot
|
||||
|
||||
from worlds.sc2wol.Regions import MissionInfo
|
||||
from worlds.sc2wol.MissionTables import lookup_id_to_mission
|
||||
from MultiServer import mark_raw
|
||||
from Utils import init_logging, is_windows
|
||||
from worlds.sc2wol import SC2WoLWorld
|
||||
from worlds.sc2wol.Items import lookup_id_to_name, item_table
|
||||
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
|
||||
from worlds.sc2wol import SC2WoLWorld
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
from MultiServer import mark_raw
|
||||
import ctypes
|
||||
import sys
|
||||
|
||||
from Utils import init_logging, is_windows
|
||||
from worlds.sc2wol.MissionTables import lookup_id_to_mission
|
||||
from worlds.sc2wol.Regions import MissionInfo
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_logging("SC2Client", exception_logger="Client")
|
||||
@@ -35,10 +35,12 @@ sc2_logger = logging.getLogger("Starcraft2")
|
||||
|
||||
import colorama
|
||||
|
||||
from NetUtils import *
|
||||
from NetUtils import ClientStatus, RawJSONtoTextParser
|
||||
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser
|
||||
|
||||
nest_asyncio.apply()
|
||||
max_bonus: int = 8
|
||||
victory_modulo: int = 100
|
||||
|
||||
|
||||
class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
@@ -98,13 +100,13 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
def _cmd_available(self) -> bool:
|
||||
"""Get what missions are currently available to play"""
|
||||
|
||||
request_available_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui)
|
||||
request_available_missions(self.ctx)
|
||||
return True
|
||||
|
||||
def _cmd_unfinished(self) -> bool:
|
||||
"""Get what missions are currently available to play and have not had all locations checked"""
|
||||
|
||||
request_unfinished_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui, self.ctx)
|
||||
request_unfinished_missions(self.ctx)
|
||||
return True
|
||||
|
||||
@mark_raw
|
||||
@@ -125,18 +127,19 @@ class SC2Context(CommonContext):
|
||||
items_handling = 0b111
|
||||
difficulty = -1
|
||||
all_in_choice = 0
|
||||
mission_req_table = None
|
||||
items_rec_to_announce = []
|
||||
rec_announce_pos = 0
|
||||
items_sent_to_announce = []
|
||||
sent_announce_pos = 0
|
||||
announcements = []
|
||||
announcement_pos = 0
|
||||
mission_req_table: typing.Dict[str, MissionInfo] = {}
|
||||
announcements = queue.Queue()
|
||||
sc2_run_task: typing.Optional[asyncio.Task] = None
|
||||
missions_unlocked = False
|
||||
missions_unlocked: bool = False # allow launching missions ignoring requirements
|
||||
current_tooltip = None
|
||||
last_loc_list = None
|
||||
difficulty_override = -1
|
||||
mission_id_to_location_ids: typing.Dict[int, typing.List[int]] = {}
|
||||
raw_text_parser: RawJSONtoTextParser
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SC2Context, self).__init__(*args, **kwargs)
|
||||
self.raw_text_parser = RawJSONtoTextParser(self)
|
||||
|
||||
async def server_auth(self, password_requested: bool = False):
|
||||
if password_requested and not self.password:
|
||||
@@ -149,30 +152,32 @@ class SC2Context(CommonContext):
|
||||
self.difficulty = args["slot_data"]["game_difficulty"]
|
||||
self.all_in_choice = args["slot_data"]["all_in_map"]
|
||||
slot_req_table = args["slot_data"]["mission_req"]
|
||||
self.mission_req_table = {}
|
||||
# Compatibility for 0.3.2 server data.
|
||||
if "category" not in next(iter(slot_req_table)):
|
||||
for i, mission_data in enumerate(slot_req_table.values()):
|
||||
mission_data["category"] = wol_default_categories[i]
|
||||
for mission in slot_req_table:
|
||||
self.mission_req_table[mission] = MissionInfo(**slot_req_table[mission])
|
||||
self.mission_req_table = {
|
||||
mission: MissionInfo(**slot_req_table[mission]) for mission in slot_req_table
|
||||
}
|
||||
|
||||
self.build_location_to_mission_mapping()
|
||||
|
||||
# Look for and set SC2PATH.
|
||||
# check_game_install_path() returns True if and only if it finds + sets SC2PATH.
|
||||
if "SC2PATH" not in os.environ and check_game_install_path():
|
||||
check_mod_install()
|
||||
|
||||
if cmd in {"PrintJSON"}:
|
||||
if "receiving" in args:
|
||||
if self.slot_concerns_self(args["receiving"]):
|
||||
self.announcements.append(args["data"])
|
||||
return
|
||||
if "item" in args:
|
||||
if self.slot_concerns_self(args["item"].player):
|
||||
self.announcements.append(args["data"])
|
||||
def on_print_json(self, args: dict):
|
||||
if "receiving" in args and self.slot_concerns_self(args["receiving"]):
|
||||
relevant = True
|
||||
elif "item" in args and self.slot_concerns_self(args["item"].player):
|
||||
relevant = True
|
||||
else:
|
||||
relevant = False
|
||||
|
||||
if relevant:
|
||||
self.announcements.put(self.raw_text_parser(copy.deepcopy(args["data"])))
|
||||
|
||||
super(SC2Context, self).on_print_json(args)
|
||||
|
||||
def run_gui(self):
|
||||
from kvui import GameManager, HoverBehavior, ServerToolTip, fade_in_animation
|
||||
from kvui import GameManager, HoverBehavior, ServerToolTip
|
||||
from kivy.app import App
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.tabbedpanel import TabbedPanelItem
|
||||
@@ -190,6 +195,7 @@ class SC2Context(CommonContext):
|
||||
|
||||
class MissionButton(HoverableButton):
|
||||
tooltip_text = StringProperty("Test")
|
||||
ctx: SC2Context
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HoverableButton, self).__init__(*args, **kwargs)
|
||||
@@ -210,10 +216,7 @@ class SC2Context(CommonContext):
|
||||
self.ctx.current_tooltip = self.layout
|
||||
|
||||
def on_leave(self):
|
||||
if self.ctx.current_tooltip:
|
||||
App.get_running_app().root.remove_widget(self.ctx.current_tooltip)
|
||||
|
||||
self.ctx.current_tooltip = None
|
||||
self.ctx.ui.clear_tooltip()
|
||||
|
||||
@property
|
||||
def ctx(self) -> CommonContext:
|
||||
@@ -235,13 +238,20 @@ class SC2Context(CommonContext):
|
||||
mission_panel = None
|
||||
last_checked_locations = {}
|
||||
mission_id_to_button = {}
|
||||
launching = False
|
||||
launching: typing.Union[bool, int] = False # if int -> mission ID
|
||||
refresh_from_launching = True
|
||||
first_check = True
|
||||
ctx: SC2Context
|
||||
|
||||
def __init__(self, ctx):
|
||||
super().__init__(ctx)
|
||||
|
||||
def clear_tooltip(self):
|
||||
if self.ctx.current_tooltip:
|
||||
App.get_running_app().root.remove_widget(self.ctx.current_tooltip)
|
||||
|
||||
self.ctx.current_tooltip = None
|
||||
|
||||
def build(self):
|
||||
container = super().build()
|
||||
|
||||
@@ -256,7 +266,7 @@ class SC2Context(CommonContext):
|
||||
|
||||
def build_mission_table(self, dt):
|
||||
if (not self.launching and (not self.last_checked_locations == self.ctx.checked_locations or
|
||||
not self.refresh_from_launching)) or self.first_check:
|
||||
not self.refresh_from_launching)) or self.first_check:
|
||||
self.refresh_from_launching = True
|
||||
|
||||
self.mission_panel.clear_widgets()
|
||||
@@ -267,12 +277,7 @@ class SC2Context(CommonContext):
|
||||
|
||||
self.mission_id_to_button = {}
|
||||
categories = {}
|
||||
available_missions = []
|
||||
unfinished_locations = initialize_blank_mission_dict(self.ctx.mission_req_table)
|
||||
unfinished_missions = calc_unfinished_missions(self.ctx.checked_locations,
|
||||
self.ctx.mission_req_table,
|
||||
self.ctx, available_missions=available_missions,
|
||||
unfinished_locations=unfinished_locations)
|
||||
available_missions, unfinished_missions = calc_unfinished_missions(self.ctx)
|
||||
|
||||
# separate missions into categories
|
||||
for mission in self.ctx.mission_req_table:
|
||||
@@ -283,7 +288,8 @@ class SC2Context(CommonContext):
|
||||
|
||||
for category in categories:
|
||||
category_panel = MissionCategory()
|
||||
category_panel.add_widget(Label(text=category, size_hint_y=None, height=50, outline_width=1))
|
||||
category_panel.add_widget(
|
||||
Label(text=category, size_hint_y=None, height=50, outline_width=1))
|
||||
|
||||
# Map is completed
|
||||
for mission in categories[category]:
|
||||
@@ -295,7 +301,9 @@ class SC2Context(CommonContext):
|
||||
text = f"[color=6495ED]{text}[/color]"
|
||||
|
||||
tooltip = f"Uncollected locations:\n"
|
||||
tooltip += "\n".join(location for location in unfinished_locations[mission])
|
||||
tooltip += "\n".join([self.ctx.location_names[loc] for loc in
|
||||
self.ctx.locations_for_mission(mission)
|
||||
if loc in self.ctx.missing_locations])
|
||||
elif mission in available_missions:
|
||||
text = f"[color=FFFFFF]{text}[/color]"
|
||||
# Map requirements not met
|
||||
@@ -303,7 +311,7 @@ class SC2Context(CommonContext):
|
||||
text = f"[color=a9a9a9]{text}[/color]"
|
||||
tooltip = f"Requires: "
|
||||
if len(self.ctx.mission_req_table[mission].required_world) > 0:
|
||||
tooltip += ", ".join(list(self.ctx.mission_req_table)[req_mission-1] for
|
||||
tooltip += ", ".join(list(self.ctx.mission_req_table)[req_mission - 1] for
|
||||
req_mission in
|
||||
self.ctx.mission_req_table[mission].required_world)
|
||||
|
||||
@@ -325,13 +333,17 @@ class SC2Context(CommonContext):
|
||||
self.refresh_from_launching = False
|
||||
|
||||
self.mission_panel.clear_widgets()
|
||||
self.mission_panel.add_widget(Label(text="Launching Mission"))
|
||||
self.mission_panel.add_widget(Label(text="Launching Mission: " +
|
||||
lookup_id_to_mission[self.launching]))
|
||||
if self.ctx.ui:
|
||||
self.ctx.ui.clear_tooltip()
|
||||
|
||||
def mission_callback(self, button):
|
||||
if not self.launching:
|
||||
self.ctx.play_mission(list(self.mission_id_to_button.keys())
|
||||
[list(self.mission_id_to_button.values()).index(button)])
|
||||
self.launching = True
|
||||
mission_id: int = list(self.mission_id_to_button.values()).index(button)
|
||||
self.ctx.play_mission(list(self.mission_id_to_button)
|
||||
[mission_id])
|
||||
self.launching = mission_id
|
||||
Clock.schedule_once(self.finish_launching, 10)
|
||||
|
||||
def finish_launching(self, dt):
|
||||
@@ -349,7 +361,7 @@ class SC2Context(CommonContext):
|
||||
|
||||
def play_mission(self, mission_id):
|
||||
if self.missions_unlocked or \
|
||||
is_mission_available(mission_id, self.checked_locations, self.mission_req_table):
|
||||
is_mission_available(self, mission_id):
|
||||
if self.sc2_run_task:
|
||||
if not self.sc2_run_task.done():
|
||||
sc2_logger.warning("Starcraft 2 Client is still running!")
|
||||
@@ -358,12 +370,29 @@ class SC2Context(CommonContext):
|
||||
sc2_logger.warning("Launching Mission without Archipelago authentication, "
|
||||
"checks will not be registered to server.")
|
||||
self.sc2_run_task = asyncio.create_task(starcraft_launch(self, mission_id),
|
||||
name="Starcraft 2 Launch")
|
||||
name="Starcraft 2 Launch")
|
||||
else:
|
||||
sc2_logger.info(
|
||||
f"{lookup_id_to_mission[mission_id]} is not currently unlocked. "
|
||||
f"Use /unfinished or /available to see what is available.")
|
||||
|
||||
def build_location_to_mission_mapping(self):
|
||||
mission_id_to_location_ids: typing.Dict[int, typing.Set[int]] = {
|
||||
mission_info.id: set() for mission_info in self.mission_req_table.values()
|
||||
}
|
||||
|
||||
for loc in self.server_locations:
|
||||
mission_id, objective = divmod(loc - SC2WOL_LOC_ID_OFFSET, victory_modulo)
|
||||
mission_id_to_location_ids[mission_id].add(objective)
|
||||
self.mission_id_to_location_ids = {mission_id: sorted(objectives) for mission_id, objectives in
|
||||
mission_id_to_location_ids.items()}
|
||||
|
||||
def locations_for_mission(self, mission: str):
|
||||
mission_id: int = self.mission_req_table[mission].id
|
||||
objectives = self.mission_id_to_location_ids[self.mission_req_table[mission].id]
|
||||
for objective in objectives:
|
||||
yield SC2WOL_LOC_ID_OFFSET + mission_id * 100 + objective
|
||||
|
||||
|
||||
async def main():
|
||||
multiprocessing.freeze_support()
|
||||
@@ -459,11 +488,7 @@ def calc_difficulty(difficulty):
|
||||
return 'X'
|
||||
|
||||
|
||||
async def starcraft_launch(ctx: SC2Context, mission_id):
|
||||
ctx.rec_announce_pos = len(ctx.items_rec_to_announce)
|
||||
ctx.sent_announce_pos = len(ctx.items_sent_to_announce)
|
||||
ctx.announcements_pos = len(ctx.announcements)
|
||||
|
||||
async def starcraft_launch(ctx: SC2Context, mission_id: int):
|
||||
sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.")
|
||||
|
||||
with DllDirectory(None):
|
||||
@@ -472,32 +497,29 @@ async def starcraft_launch(ctx: SC2Context, mission_id):
|
||||
|
||||
|
||||
class ArchipelagoBot(sc2.bot_ai.BotAI):
|
||||
game_running = False
|
||||
mission_completed = False
|
||||
first_bonus = False
|
||||
second_bonus = False
|
||||
third_bonus = False
|
||||
fourth_bonus = False
|
||||
fifth_bonus = False
|
||||
sixth_bonus = False
|
||||
seventh_bonus = False
|
||||
eight_bonus = False
|
||||
ctx: SC2Context = None
|
||||
mission_id = 0
|
||||
game_running: bool = False
|
||||
mission_completed: bool = False
|
||||
boni: typing.List[bool]
|
||||
setup_done: bool
|
||||
ctx: SC2Context
|
||||
mission_id: int
|
||||
|
||||
can_read_game = False
|
||||
|
||||
last_received_update = 0
|
||||
last_received_update: int = 0
|
||||
|
||||
def __init__(self, ctx: SC2Context, mission_id):
|
||||
self.setup_done = False
|
||||
self.ctx = ctx
|
||||
self.mission_id = mission_id
|
||||
self.boni = [False for _ in range(max_bonus)]
|
||||
|
||||
super(ArchipelagoBot, self).__init__()
|
||||
|
||||
async def on_step(self, iteration: int):
|
||||
game_state = 0
|
||||
if iteration == 0:
|
||||
if not self.setup_done:
|
||||
self.setup_done = True
|
||||
start_items = calculate_items(self.ctx.items_received)
|
||||
if self.ctx.difficulty_override >= 0:
|
||||
difficulty = calc_difficulty(self.ctx.difficulty_override)
|
||||
@@ -511,36 +533,10 @@ class ArchipelagoBot(sc2.bot_ai.BotAI):
|
||||
self.last_received_update = len(self.ctx.items_received)
|
||||
|
||||
else:
|
||||
if self.ctx.announcement_pos < len(self.ctx.announcements):
|
||||
index = 0
|
||||
message = ""
|
||||
while index < len(self.ctx.announcements[self.ctx.announcement_pos]):
|
||||
message += self.ctx.announcements[self.ctx.announcement_pos][index]["text"]
|
||||
index += 1
|
||||
|
||||
index = 0
|
||||
start_rem_pos = -1
|
||||
# Remove unneeded [Color] tags
|
||||
while index < len(message):
|
||||
if message[index] == '[':
|
||||
start_rem_pos = index
|
||||
index += 1
|
||||
elif message[index] == ']' and start_rem_pos > -1:
|
||||
temp_msg = ""
|
||||
|
||||
if start_rem_pos > 0:
|
||||
temp_msg = message[:start_rem_pos]
|
||||
if index < len(message) - 1:
|
||||
temp_msg += message[index + 1:]
|
||||
|
||||
message = temp_msg
|
||||
index += start_rem_pos - index
|
||||
start_rem_pos = -1
|
||||
else:
|
||||
index += 1
|
||||
|
||||
if not self.ctx.announcements.empty():
|
||||
message = self.ctx.announcements.get(timeout=1)
|
||||
await self.chat_send("SendMessage " + message)
|
||||
self.ctx.announcement_pos += 1
|
||||
self.ctx.announcements.task_done()
|
||||
|
||||
# Archipelago reads the health
|
||||
for unit in self.all_own_units():
|
||||
@@ -568,169 +564,97 @@ class ArchipelagoBot(sc2.bot_ai.BotAI):
|
||||
if game_state & (1 << 1) and not self.mission_completed:
|
||||
if self.mission_id != 29:
|
||||
print("Mission Completed")
|
||||
await self.ctx.send_msgs([
|
||||
{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id]}])
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + victory_modulo * self.mission_id]}])
|
||||
self.mission_completed = True
|
||||
else:
|
||||
print("Game Complete")
|
||||
await self.ctx.send_msgs([{"cmd": 'StatusUpdate', "status": ClientStatus.CLIENT_GOAL}])
|
||||
self.mission_completed = True
|
||||
|
||||
if game_state & (1 << 2) and not self.first_bonus:
|
||||
print("1st Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 1]}])
|
||||
self.first_bonus = True
|
||||
|
||||
if not self.second_bonus and game_state & (1 << 3):
|
||||
print("2nd Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 2]}])
|
||||
self.second_bonus = True
|
||||
|
||||
if not self.third_bonus and game_state & (1 << 4):
|
||||
print("3rd Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 3]}])
|
||||
self.third_bonus = True
|
||||
|
||||
if not self.fourth_bonus and game_state & (1 << 5):
|
||||
print("4th Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 4]}])
|
||||
self.fourth_bonus = True
|
||||
|
||||
if not self.fifth_bonus and game_state & (1 << 6):
|
||||
print("5th Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 5]}])
|
||||
self.fifth_bonus = True
|
||||
|
||||
if not self.sixth_bonus and game_state & (1 << 7):
|
||||
print("6th Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 6]}])
|
||||
self.sixth_bonus = True
|
||||
|
||||
if not self.seventh_bonus and game_state & (1 << 8):
|
||||
print("6th Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 7]}])
|
||||
self.seventh_bonus = True
|
||||
|
||||
if not self.eight_bonus and game_state & (1 << 9):
|
||||
print("6th Bonus Collected")
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 8]}])
|
||||
self.eight_bonus = True
|
||||
for x, completed in enumerate(self.boni):
|
||||
if not completed and game_state & (1 << (x + 2)):
|
||||
await self.ctx.send_msgs(
|
||||
[{"cmd": 'LocationChecks',
|
||||
"locations": [SC2WOL_LOC_ID_OFFSET + victory_modulo * self.mission_id + x + 1]}])
|
||||
self.boni[x] = True
|
||||
|
||||
else:
|
||||
await self.chat_send("LostConnection - Lost connection to game.")
|
||||
|
||||
|
||||
def calc_objectives_completed(mission, missions_info, locations_done, unfinished_locations, ctx):
|
||||
objectives_complete = 0
|
||||
|
||||
if missions_info[mission].extra_locations > 0:
|
||||
for i in range(missions_info[mission].extra_locations):
|
||||
if (missions_info[mission].id * 100 + SC2WOL_LOC_ID_OFFSET + i) in locations_done:
|
||||
objectives_complete += 1
|
||||
else:
|
||||
unfinished_locations[mission].append(ctx.location_names[
|
||||
missions_info[mission].id * 100 + SC2WOL_LOC_ID_OFFSET + i])
|
||||
|
||||
return objectives_complete
|
||||
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
def request_unfinished_missions(locations_done, location_table, ui, ctx):
|
||||
if location_table:
|
||||
def request_unfinished_missions(ctx: SC2Context):
|
||||
if ctx.mission_req_table:
|
||||
message = "Unfinished Missions: "
|
||||
unlocks = initialize_blank_mission_dict(location_table)
|
||||
unfinished_locations = initialize_blank_mission_dict(location_table)
|
||||
unlocks = initialize_blank_mission_dict(ctx.mission_req_table)
|
||||
unfinished_locations = initialize_blank_mission_dict(ctx.mission_req_table)
|
||||
|
||||
unfinished_missions = calc_unfinished_missions(locations_done, location_table, ctx, unlocks=unlocks,
|
||||
unfinished_locations=unfinished_locations)
|
||||
_, unfinished_missions = calc_unfinished_missions(ctx, unlocks=unlocks)
|
||||
|
||||
message += ", ".join(f"{mark_up_mission_name(mission, location_table, ui,unlocks)}[{location_table[mission].id}] " +
|
||||
message += ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}[{ctx.mission_req_table[mission].id}] " +
|
||||
mark_up_objectives(
|
||||
f"[{unfinished_missions[mission]}/{location_table[mission].extra_locations}]",
|
||||
f"[{len(unfinished_missions[mission])}/"
|
||||
f"{sum(1 for _ in ctx.locations_for_mission(mission))}]",
|
||||
ctx, unfinished_locations, mission)
|
||||
for mission in unfinished_missions)
|
||||
|
||||
if ui:
|
||||
ui.log_panels['All'].on_message_markup(message)
|
||||
ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
if ctx.ui:
|
||||
ctx.ui.log_panels['All'].on_message_markup(message)
|
||||
ctx.ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
else:
|
||||
sc2_logger.info(message)
|
||||
else:
|
||||
sc2_logger.warning("No mission table found, you are likely not connected to a server.")
|
||||
|
||||
|
||||
def calc_unfinished_missions(locations_done, locations, ctx, unlocks=None, unfinished_locations=None,
|
||||
available_missions=[]):
|
||||
def calc_unfinished_missions(ctx: SC2Context, unlocks=None):
|
||||
unfinished_missions = []
|
||||
locations_completed = []
|
||||
|
||||
if not unlocks:
|
||||
unlocks = initialize_blank_mission_dict(locations)
|
||||
unlocks = initialize_blank_mission_dict(ctx.mission_req_table)
|
||||
|
||||
if not unfinished_locations:
|
||||
unfinished_locations = initialize_blank_mission_dict(locations)
|
||||
|
||||
if len(available_missions) > 0:
|
||||
available_missions = []
|
||||
|
||||
available_missions.extend(calc_available_missions(locations_done, locations, unlocks))
|
||||
available_missions = calc_available_missions(ctx, unlocks)
|
||||
|
||||
for name in available_missions:
|
||||
if not locations[name].extra_locations == -1:
|
||||
objectives_completed = calc_objectives_completed(name, locations, locations_done, unfinished_locations, ctx)
|
||||
|
||||
if objectives_completed < locations[name].extra_locations:
|
||||
objectives = set(ctx.locations_for_mission(name))
|
||||
if objectives:
|
||||
objectives_completed = ctx.checked_locations & objectives
|
||||
if len(objectives_completed) < len(objectives):
|
||||
unfinished_missions.append(name)
|
||||
locations_completed.append(objectives_completed)
|
||||
|
||||
else:
|
||||
else: # infer that this is the final mission as it has no objectives
|
||||
unfinished_missions.append(name)
|
||||
locations_completed.append(-1)
|
||||
|
||||
return {unfinished_missions[i]: locations_completed[i] for i in range(len(unfinished_missions))}
|
||||
return available_missions, dict(zip(unfinished_missions, locations_completed))
|
||||
|
||||
|
||||
def is_mission_available(mission_id_to_check, locations_done, locations):
|
||||
unfinished_missions = calc_available_missions(locations_done, locations)
|
||||
def is_mission_available(ctx: SC2Context, mission_id_to_check):
|
||||
unfinished_missions = calc_available_missions(ctx)
|
||||
|
||||
return any(mission_id_to_check == locations[mission].id for mission in unfinished_missions)
|
||||
return any(mission_id_to_check == ctx.mission_req_table[mission].id for mission in unfinished_missions)
|
||||
|
||||
|
||||
def mark_up_mission_name(mission, location_table, ui, unlock_table):
|
||||
def mark_up_mission_name(ctx: SC2Context, mission, unlock_table):
|
||||
"""Checks if the mission is required for game completion and adds '*' to the name to mark that."""
|
||||
|
||||
if location_table[mission].completion_critical:
|
||||
if ui:
|
||||
if ctx.mission_req_table[mission].completion_critical:
|
||||
if ctx.ui:
|
||||
message = "[color=AF99EF]" + mission + "[/color]"
|
||||
else:
|
||||
message = "*" + mission + "*"
|
||||
else:
|
||||
message = mission
|
||||
|
||||
if ui:
|
||||
if ctx.ui:
|
||||
unlocks = unlock_table[mission]
|
||||
|
||||
if len(unlocks) > 0:
|
||||
pre_message = f"[ref={list(location_table).index(mission)}|Unlocks: "
|
||||
pre_message += ", ".join(f"{unlock}({location_table[unlock].id})" for unlock in unlocks)
|
||||
pre_message = f"[ref={list(ctx.mission_req_table).index(mission)}|Unlocks: "
|
||||
pre_message += ", ".join(f"{unlock}({ctx.mission_req_table[unlock].id})" for unlock in unlocks)
|
||||
pre_message += f"]"
|
||||
message = pre_message + message + "[/ref]"
|
||||
|
||||
@@ -743,7 +667,7 @@ def mark_up_objectives(message, ctx, unfinished_locations, mission):
|
||||
if ctx.ui:
|
||||
locations = unfinished_locations[mission]
|
||||
|
||||
pre_message = f"[ref={list(ctx.mission_req_table).index(mission)+30}|"
|
||||
pre_message = f"[ref={list(ctx.mission_req_table).index(mission) + 30}|"
|
||||
pre_message += "<br>".join(location for location in locations)
|
||||
pre_message += f"]"
|
||||
formatted_message = pre_message + message + "[/ref]"
|
||||
@@ -751,90 +675,91 @@ def mark_up_objectives(message, ctx, unfinished_locations, mission):
|
||||
return formatted_message
|
||||
|
||||
|
||||
def request_available_missions(locations_done, location_table, ui):
|
||||
if location_table:
|
||||
def request_available_missions(ctx: SC2Context):
|
||||
if ctx.mission_req_table:
|
||||
message = "Available Missions: "
|
||||
|
||||
# Initialize mission unlock table
|
||||
unlocks = initialize_blank_mission_dict(location_table)
|
||||
unlocks = initialize_blank_mission_dict(ctx.mission_req_table)
|
||||
|
||||
missions = calc_available_missions(locations_done, location_table, unlocks)
|
||||
missions = calc_available_missions(ctx, unlocks)
|
||||
message += \
|
||||
", ".join(f"{mark_up_mission_name(mission, location_table, ui, unlocks)}[{location_table[mission].id}]"
|
||||
", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}"
|
||||
f"[{ctx.mission_req_table[mission].id}]"
|
||||
for mission in missions)
|
||||
|
||||
if ui:
|
||||
ui.log_panels['All'].on_message_markup(message)
|
||||
ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
if ctx.ui:
|
||||
ctx.ui.log_panels['All'].on_message_markup(message)
|
||||
ctx.ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
else:
|
||||
sc2_logger.info(message)
|
||||
else:
|
||||
sc2_logger.warning("No mission table found, you are likely not connected to a server.")
|
||||
|
||||
|
||||
def calc_available_missions(locations_done, locations, unlocks=None):
|
||||
def calc_available_missions(ctx: SC2Context, unlocks=None):
|
||||
available_missions = []
|
||||
missions_complete = 0
|
||||
|
||||
# Get number of missions completed
|
||||
for loc in locations_done:
|
||||
if loc % 100 == 0:
|
||||
for loc in ctx.checked_locations:
|
||||
if loc % victory_modulo == 0:
|
||||
missions_complete += 1
|
||||
|
||||
for name in locations:
|
||||
for name in ctx.mission_req_table:
|
||||
# Go through the required missions for each mission and fill up unlock table used later for hover-over tooltips
|
||||
if unlocks:
|
||||
for unlock in locations[name].required_world:
|
||||
unlocks[list(locations)[unlock-1]].append(name)
|
||||
for unlock in ctx.mission_req_table[name].required_world:
|
||||
unlocks[list(ctx.mission_req_table)[unlock - 1]].append(name)
|
||||
|
||||
if mission_reqs_completed(name, missions_complete, locations_done, locations):
|
||||
if mission_reqs_completed(ctx, name, missions_complete):
|
||||
available_missions.append(name)
|
||||
|
||||
return available_missions
|
||||
|
||||
|
||||
def mission_reqs_completed(location_to_check, missions_complete, locations_done, locations):
|
||||
def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete):
|
||||
"""Returns a bool signifying if the mission has all requirements complete and can be done
|
||||
|
||||
Keyword arguments:
|
||||
Arguments:
|
||||
ctx -- instance of SC2Context
|
||||
locations_to_check -- the mission string name to check
|
||||
missions_complete -- an int of how many missions have been completed
|
||||
locations_done -- a list of the location ids that have been complete
|
||||
locations -- a dict of MissionInfo for mission requirements for this world"""
|
||||
if len(locations[location_to_check].required_world) >= 1:
|
||||
"""
|
||||
if len(ctx.mission_req_table[mission_name].required_world) >= 1:
|
||||
# A check for when the requirements are being or'd
|
||||
or_success = False
|
||||
|
||||
# Loop through required missions
|
||||
for req_mission in locations[location_to_check].required_world:
|
||||
for req_mission in ctx.mission_req_table[mission_name].required_world:
|
||||
req_success = True
|
||||
|
||||
# Check if required mission has been completed
|
||||
if not (locations[list(locations)[req_mission-1]].id * 100 + SC2WOL_LOC_ID_OFFSET) in locations_done:
|
||||
if not locations[location_to_check].or_requirements:
|
||||
if not (ctx.mission_req_table[list(ctx.mission_req_table)[req_mission - 1]].id *
|
||||
victory_modulo + SC2WOL_LOC_ID_OFFSET) in ctx.checked_locations:
|
||||
if not ctx.mission_req_table[mission_name].or_requirements:
|
||||
return False
|
||||
else:
|
||||
req_success = False
|
||||
|
||||
# Recursively check required mission to see if it's requirements are met, in case !collect has been done
|
||||
if not mission_reqs_completed(list(locations)[req_mission-1], missions_complete, locations_done,
|
||||
locations):
|
||||
if not locations[location_to_check].or_requirements:
|
||||
if not mission_reqs_completed(ctx, list(ctx.mission_req_table)[req_mission - 1], missions_complete):
|
||||
if not ctx.mission_req_table[mission_name].or_requirements:
|
||||
return False
|
||||
else:
|
||||
req_success = False
|
||||
|
||||
# If requirement check succeeded mark or as satisfied
|
||||
if locations[location_to_check].or_requirements and req_success:
|
||||
if ctx.mission_req_table[mission_name].or_requirements and req_success:
|
||||
or_success = True
|
||||
|
||||
if locations[location_to_check].or_requirements:
|
||||
if ctx.mission_req_table[mission_name].or_requirements:
|
||||
# Return false if or requirements not met
|
||||
if not or_success:
|
||||
return False
|
||||
|
||||
# Check number of missions
|
||||
if missions_complete >= locations[location_to_check].number:
|
||||
if missions_complete >= ctx.mission_req_table[mission_name].number:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -929,7 +854,7 @@ class DllDirectory:
|
||||
self.set(self._old)
|
||||
|
||||
@staticmethod
|
||||
def get() -> str:
|
||||
def get() -> typing.Optional[str]:
|
||||
if sys.platform == "win32":
|
||||
n = ctypes.windll.kernel32.GetDllDirectoryW(0, None)
|
||||
buf = ctypes.create_unicode_buffer(n)
|
||||
|
||||
6
Utils.py
6
Utils.py
@@ -35,7 +35,7 @@ class Version(typing.NamedTuple):
|
||||
build: int
|
||||
|
||||
|
||||
__version__ = "0.3.4"
|
||||
__version__ = "0.3.5"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
|
||||
is_linux = sys.platform.startswith("linux")
|
||||
@@ -619,7 +619,7 @@ def title_sorted(data: typing.Sequence, key=None, ignore: typing.Set = frozenset
|
||||
def sorter(element: str) -> str:
|
||||
parts = element.split(maxsplit=1)
|
||||
if parts[0].lower() in ignore:
|
||||
return parts[1]
|
||||
return parts[1].lower()
|
||||
else:
|
||||
return element
|
||||
return element.lower()
|
||||
return sorted(data, key=lambda i: sorter(key(i)) if key else sorter(i))
|
||||
|
||||
@@ -12,7 +12,7 @@ ModuleUpdate.update()
|
||||
# in case app gets imported by something like gunicorn
|
||||
import Utils
|
||||
|
||||
Utils.local_path.cached_path = os.path.dirname(__file__)
|
||||
Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8
|
||||
|
||||
from WebHostLib import register, app as raw_app
|
||||
from waitress import serve
|
||||
@@ -104,7 +104,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
|
||||
for games in data:
|
||||
if 'Archipelago' in games['gameTitle']:
|
||||
generic_data = data.pop(data.index(games))
|
||||
sorted_data = [generic_data] + Utils.title_sorted(data, key=lambda entry: entry["gameTitle"].lower())
|
||||
sorted_data = [generic_data] + Utils.title_sorted(data, key=lambda entry: entry["gameTitle"])
|
||||
json.dump(sorted_data, json_target, indent=2, ensure_ascii=False)
|
||||
return sorted_data
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends 'pageWrapper.html' %}
|
||||
|
||||
{% block head %}
|
||||
<title>Player Settings</title>
|
||||
<title>Supported Games</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/supportedGames.css") }}" />
|
||||
{% endblock %}
|
||||
|
||||
@@ -13,9 +13,18 @@ These steps should be followed in order to establish a gameplay connection with
|
||||
|
||||
In the case that the client does not authenticate properly and receives a [ConnectionRefused](#ConnectionRefused) then the server will maintain the connection and allow for follow-up [Connect](#Connect) packet.
|
||||
|
||||
There are libraries available that implement this network protocol in [Python](https://github.com/ArchipelagoMW/Archipelago/blob/main/CommonClient.py), [Java](https://github.com/ArchipelagoMW/Archipelago.MultiClient.Java), [.Net](https://github.com/ArchipelagoMW/Archipelago.MultiClient.Net) and [C++](https://github.com/black-sliver/apclientpp)
|
||||
There are also a number of community-supported libraries available that implement this network protocol to make integrating with Archipelago easier.
|
||||
|
||||
For Super Nintendo games there are clients available in either [Node](https://github.com/ArchipelagoMW/SuperNintendoClient) or [Python](https://github.com/ArchipelagoMW/Archipelago/blob/main/SNIClient.py), There are also game specific clients available for [The Legend of Zelda: Ocarina of Time](https://github.com/ArchipelagoMW/Z5Client) or [Final Fantasy 1](https://github.com/ArchipelagoMW/Archipelago/blob/main/FF1Client.py)
|
||||
| Language/Runtime | Project | Remarks |
|
||||
|-------------------------------|----------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
|
||||
| Python | [Archipelago CommonClient](https://github.com/ArchipelagoMW/Archipelago/blob/main/CommonClient.py) | |
|
||||
| | [Archipelago SNIClient](https://github.com/ArchipelagoMW/Archipelago/blob/main/SNIClient.py) | For Super Nintendo Game Support; Utilizes [SNI](https://github.com/alttpo/sni). |
|
||||
| JVM (Java / Kotlin) | [Archipelago.MultiClient.Java](https://github.com/ArchipelagoMW/Archipelago.MultiClient.Java) | |
|
||||
| .NET (C# / C++ / F# / VB.NET) | [Archipelago.MultiClient.Net](https://www.nuget.org/packages/Archipelago.MultiClient.Net) | |
|
||||
| C++ | [apclientpp](https://github.com/black-sliver/apclientpp) | almost-header-only |
|
||||
| | [APCpp](https://github.com/N00byKing/APCpp) | CMake |
|
||||
| JavaScript / TypeScript | [archipelago.js](https://www.npmjs.com/package/archipelago.js) | Browser and Node.js Supported |
|
||||
| Haxe | [hxArchipelago](https://lib.haxe.org/p/hxArchipelago) | |
|
||||
|
||||
## Synchronizing Items
|
||||
When the client receives a [ReceivedItems](#ReceivedItems) packet, if the `index` argument does not match the next index that the client expects then it is expected that the client will re-sync items with the server. This can be accomplished by sending the server a [Sync](#Sync) packet and then a [LocationChecks](#LocationChecks) packet.
|
||||
|
||||
@@ -56,3 +56,8 @@ SNI is required to use SNIClient. If not integrated into the project, it has to
|
||||
You can get the latest SNI release at [SNI Github releases](https://github.com/alttpo/sni/releases).
|
||||
It should be dropped as "SNI" into the root folder of the project. Alternatively, you can point the sni setting in
|
||||
host.yaml at your SNI folder.
|
||||
|
||||
|
||||
## Running tests
|
||||
|
||||
Run `pip install pytest pytest-subtests`, then use your IDE to run tests or run `pytest` from the source folder.
|
||||
|
||||
@@ -52,3 +52,13 @@ class TestIDs(unittest.TestCase):
|
||||
else:
|
||||
for location_id in world_type.location_id_to_name:
|
||||
self.assertGreater(location_id, 0)
|
||||
|
||||
def testDuplicateItemIDs(self):
|
||||
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest(game=gamename):
|
||||
self.assertEqual(len(world_type.item_id_to_name), len(world_type.item_name_to_id))
|
||||
|
||||
def testDuplicateLocationIDs(self):
|
||||
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest(game=gamename):
|
||||
self.assertEqual(len(world_type.location_id_to_name), len(world_type.location_name_to_id))
|
||||
|
||||
@@ -212,9 +212,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||
Alternatively, can be a ALttP Rom patched with a Link
|
||||
sprite that will be extracted.
|
||||
''')
|
||||
parser.add_argument('--gui', help='Launch the GUI', action='store_true')
|
||||
|
||||
parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core'))
|
||||
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos',
|
||||
"singularity"])
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts
|
||||
DeathMountain_texts, \
|
||||
LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \
|
||||
SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
||||
from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen
|
||||
from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml
|
||||
from worlds.alttp.Items import ItemFactory, item_table, item_name_groups, progression_items
|
||||
from worlds.alttp.EntranceShuffle import door_addresses
|
||||
from worlds.alttp.Options import smallkey_shuffle
|
||||
@@ -551,18 +551,22 @@ class Sprite():
|
||||
Sprite.base_data = Sprite.sprite + Sprite.palette + Sprite.glove_palette
|
||||
|
||||
def from_ap_sprite(self, filedata):
|
||||
filedata = filedata.decode("utf-8-sig")
|
||||
import yaml
|
||||
obj = yaml.safe_load(filedata)
|
||||
if obj["min_format_version"] > 1:
|
||||
raise Exception("Sprite file requires an updated reader.")
|
||||
self.author_name = obj["author"]
|
||||
self.name = obj["name"]
|
||||
if obj["data"]: # skip patching for vanilla content
|
||||
data = bsdiff4.patch(Sprite.base_data, obj["data"])
|
||||
self.sprite = data[:self.sprite_size]
|
||||
self.palette = data[self.sprite_size:self.palette_size]
|
||||
self.glove_palette = data[self.sprite_size + self.palette_size:]
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
obj = parse_yaml(filedata.decode("utf-8-sig"))
|
||||
if obj["min_format_version"] > 1:
|
||||
raise Exception("Sprite file requires an updated reader.")
|
||||
self.author_name = obj["author"]
|
||||
self.name = obj["name"]
|
||||
if obj["data"]: # skip patching for vanilla content
|
||||
data = bsdiff4.patch(Sprite.base_data, obj["data"])
|
||||
self.sprite = data[:self.sprite_size]
|
||||
self.palette = data[self.sprite_size:self.palette_size]
|
||||
self.glove_palette = data[self.sprite_size + self.palette_size:]
|
||||
except Exception:
|
||||
logger = logging.getLogger("apsprite")
|
||||
logger.exception("Error parsing apsprite file")
|
||||
self.valid = False
|
||||
|
||||
@property
|
||||
def author_game_display(self) -> str:
|
||||
@@ -659,7 +663,7 @@ class Sprite():
|
||||
|
||||
@staticmethod
|
||||
def parse_zspr(filedata, expected_kind):
|
||||
logger = logging.getLogger('ZSPR')
|
||||
logger = logging.getLogger("ZSPR")
|
||||
headerstr = "<4xBHHIHIHH6x"
|
||||
headersize = struct.calcsize(headerstr)
|
||||
if len(filedata) < headersize:
|
||||
@@ -667,7 +671,7 @@ class Sprite():
|
||||
version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind = struct.unpack_from(
|
||||
headerstr, filedata)
|
||||
if version not in [1]:
|
||||
logger.error('Error parsing ZSPR file: Version %g not supported', version)
|
||||
logger.error("Error parsing ZSPR file: Version %g not supported", version)
|
||||
return None
|
||||
if kind != expected_kind:
|
||||
return None
|
||||
@@ -676,36 +680,42 @@ class Sprite():
|
||||
stream.seek(headersize)
|
||||
|
||||
def read_utf16le(stream):
|
||||
"Decodes a null-terminated UTF-16_LE string of unknown size from a stream"
|
||||
"""Decodes a null-terminated UTF-16_LE string of unknown size from a stream"""
|
||||
raw = bytearray()
|
||||
while True:
|
||||
char = stream.read(2)
|
||||
if char in [b'', b'\x00\x00']:
|
||||
if char in [b"", b"\x00\x00"]:
|
||||
break
|
||||
raw += char
|
||||
return raw.decode('utf-16_le')
|
||||
return raw.decode("utf-16_le")
|
||||
|
||||
sprite_name = read_utf16le(stream)
|
||||
author_name = read_utf16le(stream)
|
||||
author_credits_name = stream.read().split(b"\x00", 1)[0].decode()
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
sprite_name = read_utf16le(stream)
|
||||
author_name = read_utf16le(stream)
|
||||
author_credits_name = stream.read().split(b"\x00", 1)[0].decode()
|
||||
|
||||
# Ignoring the Author Rom name for the time being.
|
||||
# Ignoring the Author Rom name for the time being.
|
||||
|
||||
real_csum = sum(filedata) % 0x10000
|
||||
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
|
||||
logger.warning('ZSPR file has incorrect checksum. It may be corrupted.')
|
||||
real_csum = sum(filedata) % 0x10000
|
||||
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
|
||||
logger.warning("ZSPR file has incorrect checksum. It may be corrupted.")
|
||||
|
||||
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
|
||||
palette = filedata[palette_offset:palette_offset + palette_size]
|
||||
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
|
||||
palette = filedata[palette_offset:palette_offset + palette_size]
|
||||
|
||||
if len(sprite) != sprite_size or len(palette) != palette_size:
|
||||
logger.error('Error parsing ZSPR file: Unexpected end of file')
|
||||
if len(sprite) != sprite_size or len(palette) != palette_size:
|
||||
logger.error("Error parsing ZSPR file: Unexpected end of file")
|
||||
return None
|
||||
|
||||
return sprite, palette, sprite_name, author_name, author_credits_name
|
||||
|
||||
except Exception:
|
||||
logger.exception("Error parsing ZSPR file")
|
||||
return None
|
||||
|
||||
return (sprite, palette, sprite_name, author_name, author_credits_name)
|
||||
|
||||
def decode_palette(self):
|
||||
"Returns the palettes as an array of arrays of 15 colors"
|
||||
"""Returns the palettes as an array of arrays of 15 colors"""
|
||||
|
||||
def array_chunk(arr, size):
|
||||
return list(zip(*[iter(arr)] * size))
|
||||
|
||||
@@ -4,6 +4,7 @@ import random
|
||||
import threading
|
||||
import typing
|
||||
|
||||
import Utils
|
||||
from BaseClasses import Item, CollectionState, Tutorial
|
||||
from .Dungeons import create_dungeons
|
||||
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
||||
@@ -136,6 +137,10 @@ class ALTTPWorld(World):
|
||||
|
||||
create_items = generate_itempool
|
||||
|
||||
enemizer_path: str = Utils.get_options()["generator"]["enemizer_path"] \
|
||||
if os.path.isabs(Utils.get_options()["generator"]["enemizer_path"]) \
|
||||
else Utils.local_path(Utils.get_options()["generator"]["enemizer_path"])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.dungeon_local_item_names = set()
|
||||
self.dungeon_specific_item_names = set()
|
||||
@@ -150,12 +155,12 @@ class ALTTPWorld(World):
|
||||
raise FileNotFoundError(rom_file)
|
||||
|
||||
def generate_early(self):
|
||||
if self.use_enemizer():
|
||||
check_enemizer(self.enemizer_path)
|
||||
|
||||
player = self.player
|
||||
world = self.world
|
||||
|
||||
if self.use_enemizer():
|
||||
check_enemizer(world.enemizer)
|
||||
|
||||
# system for sharing ER layouts
|
||||
self.er_seed = str(world.random.randint(0, 2 ** 64))
|
||||
|
||||
@@ -360,7 +365,7 @@ class ALTTPWorld(World):
|
||||
patch_rom(world, rom, player, use_enemizer)
|
||||
|
||||
if use_enemizer:
|
||||
patch_enemizer(world, player, rom, world.enemizer, output_directory)
|
||||
patch_enemizer(world, player, rom, self.enemizer_path, output_directory)
|
||||
|
||||
if world.is_race:
|
||||
patch_race_rom(rom, world, player)
|
||||
|
||||
@@ -502,6 +502,10 @@ def patch_rom(world, rom, player, active_level_list):
|
||||
# Make Swanky free
|
||||
rom.write_byte(0x348C48, 0x00)
|
||||
|
||||
rom.write_bytes(0x34AB70, bytearray([0xEA, 0xEA]))
|
||||
rom.write_bytes(0x34ABF7, bytearray([0xEA, 0xEA]))
|
||||
rom.write_bytes(0x34ACD0, bytearray([0xEA, 0xEA]))
|
||||
|
||||
# Banana Bird Costs
|
||||
if world.goal[player] == "banana_bird_hunt":
|
||||
banana_bird_cost = math.floor(world.number_of_banana_birds[player] * world.percentage_of_banana_birds[player] / 100.0)
|
||||
|
||||
@@ -249,6 +249,10 @@ script.on_event(defines.events.on_player_main_inventory_changed, update_player_e
|
||||
|
||||
function add_samples(force, name, count)
|
||||
local function add_to_table(t)
|
||||
if count <= 0 then
|
||||
-- Fixes a bug with single craft, if a recipe gives 0 of a given item.
|
||||
return
|
||||
end
|
||||
t[name] = (t[name] or 0) + count
|
||||
end
|
||||
-- Add to global table of earned samples for future new players
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
-- Find out if more than one AP mod is loaded, and if so, error out.
|
||||
function mod_is_AP(str)
|
||||
-- lua string.match is way more restrictive than regex. Regex would be "^AP-W?\d{20}-P[1-9]\d*-.+$"
|
||||
local result = string.match(str, "^AP%-W?%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%-P[1-9]%d-%-.+$")
|
||||
if result ~= nil then
|
||||
log("Archipelago Mod: " .. result .. " is loaded.")
|
||||
end
|
||||
return result ~= nil
|
||||
end
|
||||
local ap_mod_count = 0
|
||||
for name, _ in pairs(mods) do
|
||||
if mod_is_AP(name) then
|
||||
ap_mod_count = ap_mod_count + 1
|
||||
if ap_mod_count > 1 then
|
||||
error("More than one Archipelago Factorio mod is loaded.")
|
||||
end
|
||||
end
|
||||
end
|
||||
data:extend({
|
||||
{
|
||||
type = "bool-setting",
|
||||
|
||||
@@ -7,9 +7,9 @@ config file.
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
Recipes are removed from the crafting book and shuffled into the item pool. It can also optionally change which
|
||||
Some recipes are locked from being able to be crafted and shuffled into the item pool. It can also optionally change which
|
||||
structures appear in each dimension. Crafting recipes are re-learned when they are received from other players as item
|
||||
checks, and occasionally when completing your own achievements.
|
||||
checks, and occasionally when completing your own achievements. See below for which recipes are shuffled.
|
||||
|
||||
## What is considered a location check in minecraft?
|
||||
|
||||
@@ -25,3 +25,86 @@ inventory directly.
|
||||
|
||||
Victory is achieved when the player kills the Ender Dragon, enters the portal in The End, and completes the credits
|
||||
sequence either by skipping it or watching hit play out.
|
||||
|
||||
## Which recipes are locked?
|
||||
|
||||
* Archery
|
||||
* Bow
|
||||
* Arrow
|
||||
* Crossbow
|
||||
* Brewing
|
||||
* Blaze Powder
|
||||
* Brewing Stand
|
||||
* Enchanting
|
||||
* Enchanting Table
|
||||
* Bookshelf
|
||||
* Bucket
|
||||
* Flint & Steel
|
||||
* All Beds
|
||||
* Bottles
|
||||
* Shield
|
||||
* Fishing Rod
|
||||
* Fishing Rod
|
||||
* Carrot on a Stick
|
||||
* Warped Fungus on a Stick
|
||||
* Campfire
|
||||
* Campfire
|
||||
* Soul Campfire
|
||||
* Spyglass
|
||||
* Lead
|
||||
* Progressive Weapons
|
||||
* Tier I
|
||||
* Stone Sword
|
||||
* Stone Axe
|
||||
* Tier II
|
||||
* Iron Sword
|
||||
* Iron Axe
|
||||
* Tier III
|
||||
* Diamond Sword
|
||||
* Diamond Axe
|
||||
* Progessive Tools
|
||||
* Tier I
|
||||
* Stone Shovel
|
||||
* Stone Hoe
|
||||
* Tier II
|
||||
* Iron Shovel
|
||||
* Iron Hoe
|
||||
* Tier III
|
||||
* Diamond Shovel
|
||||
* Diamond Hoe
|
||||
* Netherite Ingot
|
||||
* Progressive Armor
|
||||
* Tier I
|
||||
* Iron Helmet
|
||||
* Iron Chestplate
|
||||
* Iron Leggings
|
||||
* Iron Boots
|
||||
* Tier II
|
||||
* Diamond Helmet
|
||||
* Diamond Chestplate
|
||||
* Diamond Leggings
|
||||
* Diamond Boots
|
||||
* Progressive Resource Crafting
|
||||
* Tier I
|
||||
* Iron Ingot from Nuggets
|
||||
* Iron Nugget
|
||||
* Gold Ingot from Nuggets
|
||||
* Gold Nugget
|
||||
* Furnace
|
||||
* Blast Furnace
|
||||
* Tier II
|
||||
* Redstone
|
||||
* Redstone Block
|
||||
* Glowstone
|
||||
* Iron Ingot from Iron Block
|
||||
* Iron Block
|
||||
* Gold Ingot from Gold Block
|
||||
* Gold Block
|
||||
* Diamond
|
||||
* Diamond Block
|
||||
* Netherite Block
|
||||
* Netherite Ingot from Netherite Block
|
||||
* Anvil
|
||||
* Emerald
|
||||
* Emerald Block
|
||||
* Copper Block
|
||||
|
||||
@@ -1388,6 +1388,10 @@ def get_pool_core(world):
|
||||
remove_junk_pool = list(remove_junk_pool) + ['Recovery Heart', 'Bombs (20)', 'Arrows (30)', 'Ice Trap']
|
||||
|
||||
junk_candidates = [item for item in pool if item in remove_junk_pool]
|
||||
if len(pending_junk_pool) > len(junk_candidates):
|
||||
excess = len(pending_junk_pool) - len(junk_candidates)
|
||||
if world.triforce_hunt:
|
||||
raise RuntimeError(f"Items in the pool for player {world.player} exceed locations. Add {excess} location(s) or remove {excess} triforce piece(s).")
|
||||
while pending_junk_pool:
|
||||
pending_item = pending_junk_pool.pop()
|
||||
if not junk_candidates:
|
||||
|
||||
@@ -49,7 +49,6 @@ class OOTItem(Item):
|
||||
self.type = type
|
||||
self.index = index
|
||||
self.special = special or {}
|
||||
self.looks_like_item = None
|
||||
self.price = special.get('price', None) if special else None
|
||||
self.internal = False
|
||||
|
||||
|
||||
@@ -158,12 +158,12 @@ class TriforceGoal(Range):
|
||||
"""Number of Triforce pieces required to complete the game."""
|
||||
display_name = "Required Triforce Pieces"
|
||||
range_start = 1
|
||||
range_end = 100
|
||||
range_end = 80
|
||||
default = 20
|
||||
|
||||
|
||||
class ExtraTriforces(Range):
|
||||
"""Percentage of additional Triforce pieces in the pool, separate from the item pool setting."""
|
||||
"""Percentage of additional Triforce pieces in the pool. With high numbers, you may need to randomize additional locations to have enough items."""
|
||||
display_name = "Percentage of Extra Triforce Pieces"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
|
||||
@@ -1844,7 +1844,7 @@ def write_rom_item(rom, item_id, item):
|
||||
|
||||
|
||||
def get_override_table(world):
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world.player), world.world.get_filled_locations(world.player))))
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world), world.world.get_filled_locations(world.player))))
|
||||
|
||||
|
||||
override_struct = struct.Struct('>xBBBHBB') # match override_t in get_items.c
|
||||
@@ -1852,10 +1852,10 @@ def get_override_table_bytes(override_table):
|
||||
return b''.join(sorted(itertools.starmap(override_struct.pack, override_table)))
|
||||
|
||||
|
||||
def get_override_entry(player_id, location):
|
||||
def get_override_entry(ootworld, location):
|
||||
scene = location.scene
|
||||
default = location.default
|
||||
player_id = 0 if player_id == location.item.player else min(location.item.player, 255)
|
||||
player_id = 0 if ootworld.player == location.item.player else min(location.item.player, 255)
|
||||
if location.item.game != 'Ocarina of Time':
|
||||
# This is an AP sendable. It's guaranteed to not be None.
|
||||
if location.item.advancement:
|
||||
@@ -1869,7 +1869,7 @@ def get_override_entry(player_id, location):
|
||||
|
||||
if location.item.trap:
|
||||
item_id = 0x7C # Ice Trap ID, to get "X is a fool" message
|
||||
looks_like_item_id = location.item.looks_like_item.index
|
||||
looks_like_item_id = ootworld.trap_appearances[location.address].index
|
||||
else:
|
||||
looks_like_item_id = 0
|
||||
|
||||
@@ -2091,7 +2091,8 @@ def get_locked_doors(rom, world):
|
||||
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
|
||||
|
||||
# If boss door, set the door's unlock flag
|
||||
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A):
|
||||
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (
|
||||
world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A and not world.triforce_hunt):
|
||||
if actor_id == 0x002E and actor_type == 0x05:
|
||||
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
|
||||
|
||||
@@ -2109,23 +2110,20 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
|
||||
rom.write_int16(location.address1, location.item.index)
|
||||
else:
|
||||
if location.item.trap:
|
||||
item_display = location.item.looks_like_item
|
||||
elif location.item.game != "Ocarina of Time":
|
||||
item_display = location.item
|
||||
if location.item.advancement:
|
||||
item_display.index = 0xCB
|
||||
else:
|
||||
item_display.index = 0xCC
|
||||
item_display.special = {}
|
||||
item_display = world.trap_appearances[location.address]
|
||||
else:
|
||||
item_display = location.item
|
||||
|
||||
# bottles in shops should look like empty bottles
|
||||
# so that that are different than normal shop refils
|
||||
if 'shop_object' in item_display.special:
|
||||
rom_item = read_rom_item(rom, item_display.special['shop_object'])
|
||||
if location.item.trap or location.item.game == "Ocarina of Time":
|
||||
if 'shop_object' in item_display.special:
|
||||
rom_item = read_rom_item(rom, item_display.special['shop_object'])
|
||||
else:
|
||||
rom_item = read_rom_item(rom, item_display.index)
|
||||
else:
|
||||
rom_item = read_rom_item(rom, item_display.index)
|
||||
display_index = 0xCB if location.item.advancement else 0xCC
|
||||
rom_item = read_rom_item(rom, display_index)
|
||||
|
||||
shop_objs.add(rom_item['object_id'])
|
||||
shop_id = world.current_shop_id
|
||||
|
||||
@@ -178,6 +178,10 @@ class OOTWorld(World):
|
||||
if self.skip_child_zelda:
|
||||
self.shuffle_weird_egg = False
|
||||
|
||||
# Ganon boss key should not be in itempool in triforce hunt
|
||||
if self.triforce_hunt:
|
||||
self.shuffle_ganon_bosskey = 'remove'
|
||||
|
||||
# Determine skipped trials in GT
|
||||
# This needs to be done before the logic rules in GT are parsed
|
||||
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light']
|
||||
@@ -803,9 +807,10 @@ class OOTWorld(World):
|
||||
|
||||
with i_o_limiter:
|
||||
# Make traps appear as other random items
|
||||
ice_traps = [loc.item for loc in self.get_locations() if loc.item.trap]
|
||||
for trap in ice_traps:
|
||||
trap.looks_like_item = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
|
||||
trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
|
||||
self.trap_appearances = {}
|
||||
for loc_id in trap_location_ids:
|
||||
self.trap_appearances[loc_id] = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
|
||||
|
||||
# Seed hint RNG, used for ganon text lines also
|
||||
self.hint_rng = self.world.slot_seeds[self.player]
|
||||
|
||||
@@ -69,8 +69,8 @@ vanilla_mission_req_table = {
|
||||
"Zero Hour": MissionInfo(3, 4, [2], "Mar Sara", completion_critical=True),
|
||||
"Evacuation": MissionInfo(4, 4, [3], "Colonist"),
|
||||
"Outbreak": MissionInfo(5, 3, [4], "Colonist"),
|
||||
"Safe Haven": MissionInfo(6, 1, [5], "Colonist", number=7),
|
||||
"Haven's Fall": MissionInfo(7, 1, [5], "Colonist", number=7),
|
||||
"Safe Haven": MissionInfo(6, 4, [5], "Colonist", number=7),
|
||||
"Haven's Fall": MissionInfo(7, 4, [5], "Colonist", number=7),
|
||||
"Smash and Grab": MissionInfo(8, 5, [3], "Artifact", completion_critical=True),
|
||||
"The Dig": MissionInfo(9, 4, [8], "Artifact", number=8, completion_critical=True),
|
||||
"The Moebius Factor": MissionInfo(10, 9, [9], "Artifact", number=11, completion_critical=True),
|
||||
|
||||
@@ -43,6 +43,7 @@ class SC2WoLWorld(World):
|
||||
locked_locations: typing.List[str]
|
||||
location_cache: typing.List[Location]
|
||||
mission_req_table = {}
|
||||
required_client_version = 0, 3, 5
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super(SC2WoLWorld, self).__init__(world, player)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from worlds.sm.variaRandomizer.rando.Items import ItemManager
|
||||
|
||||
items_start_id = 83000
|
||||
|
||||
def gen_special_id():
|
||||
special_id_value_start = 32
|
||||
while True:
|
||||
yield special_id_value_start
|
||||
special_id_value_start += 1
|
||||
|
||||
gen_run = gen_special_id()
|
||||
|
||||
lookup_id_to_name = dict((items_start_id + (value.Id if value.Id != None else next(gen_run)), value.Name) for key, value in ItemManager.Items.items())
|
||||
lookup_name_to_id = {item_name: item_id for item_id, item_name in lookup_id_to_name.items()}
|
||||
@@ -1,14 +0,0 @@
|
||||
from worlds.sm.variaRandomizer.graph.location import locationsDict
|
||||
|
||||
locations_start_id = 82000
|
||||
|
||||
def gen_boss_id():
|
||||
boss_id_value_start = 256
|
||||
while True:
|
||||
yield boss_id_value_start
|
||||
boss_id_value_start += 1
|
||||
|
||||
gen_run = gen_boss_id()
|
||||
|
||||
lookup_id_to_name = dict((locations_start_id + (value.Id if value.Id != None else next(gen_run)), key) for key, value in locationsDict.items())
|
||||
lookup_name_to_id = {location_name: location_id for location_id, location_name in lookup_id_to_name.items()}
|
||||
@@ -11,8 +11,6 @@ from worlds.sm.variaRandomizer.graph.graph_utils import GraphUtils
|
||||
|
||||
logger = logging.getLogger("Super Metroid")
|
||||
|
||||
from .Locations import lookup_name_to_id as locations_lookup_name_to_id
|
||||
from .Items import lookup_name_to_id as items_lookup_name_to_id
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules, add_entrance_rule
|
||||
from .Options import sm_options
|
||||
@@ -68,6 +66,8 @@ class SMWeb(WebWorld):
|
||||
["Farrak Kilhn"]
|
||||
)]
|
||||
|
||||
locations_start_id = 82000
|
||||
items_start_id = 83000
|
||||
|
||||
class SMWorld(World):
|
||||
"""
|
||||
@@ -78,12 +78,11 @@ class SMWorld(World):
|
||||
|
||||
game: str = "Super Metroid"
|
||||
topology_present = True
|
||||
data_version = 1
|
||||
data_version = 2
|
||||
option_definitions = sm_options
|
||||
item_names: Set[str] = frozenset(items_lookup_name_to_id)
|
||||
location_names: Set[str] = frozenset(locations_lookup_name_to_id)
|
||||
item_name_to_id = items_lookup_name_to_id
|
||||
location_name_to_id = locations_lookup_name_to_id
|
||||
|
||||
item_name_to_id = {value.Name: items_start_id + value.Id for key, value in ItemManager.Items.items() if value.Id != None}
|
||||
location_name_to_id = {key: locations_start_id + value.Id for key, value in locationsDict.items() if value.Id != None}
|
||||
web = SMWeb()
|
||||
|
||||
remote_items: bool = False
|
||||
@@ -701,8 +700,8 @@ class SMWorld(World):
|
||||
dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss]))
|
||||
|
||||
def create_locations(self, player: int):
|
||||
for name, id in locations_lookup_name_to_id.items():
|
||||
self.locations[name] = SMLocation(player, name, id)
|
||||
for name in locationsDict:
|
||||
self.locations[name] = SMLocation(player, name, self.location_name_to_id.get(name, None))
|
||||
|
||||
|
||||
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
|
||||
Binary file not shown.
@@ -2,14 +2,14 @@
|
||||
; generated by asar
|
||||
|
||||
[labels]
|
||||
B8:8026 :neg_1_1
|
||||
B8:80C1 :neg_1_1
|
||||
85:B9B4 :neg_1_2
|
||||
85:B9E6 :neg_1_3
|
||||
B8:C81F :neg_1_4
|
||||
B8:C831 :neg_1_5
|
||||
B8:C843 :neg_1_6
|
||||
B8:800C :pos_1_0
|
||||
B8:81DE :pos_1_1
|
||||
B8:82D7 :pos_1_1
|
||||
84:FA6B :pos_1_2
|
||||
84:FA75 :pos_1_3
|
||||
B8:C862 :pos_1_4
|
||||
@@ -20,7 +20,7 @@ B8:C87C :pos_1_6
|
||||
85:990F CLIPLEN_end
|
||||
85:990C CLIPLEN_no_multi
|
||||
85:FF1D CLIPSET
|
||||
B8:80EF COLLECTTANK
|
||||
B8:81E8 COLLECTTANK
|
||||
85:FF45 MISCFX
|
||||
84:8BF2 NORMAL
|
||||
85:FF4E SETFX
|
||||
@@ -38,6 +38,11 @@ CE:FF00 config_multiworld
|
||||
CE:FF08 config_player_id
|
||||
CE:FF06 config_remote_items
|
||||
CE:FF02 config_sprite
|
||||
B8:8119 copy_config_to_sram
|
||||
B8:80FD copy_memory
|
||||
B8:8117 copy_memory_done
|
||||
B8:8109 copy_memory_even
|
||||
B8:810F copy_memory_loop
|
||||
84:F894 h_item
|
||||
84:F8AD i_chozo_item
|
||||
84:F8B4 i_hidden_item
|
||||
@@ -46,11 +51,11 @@ B8:885C i_item_setup_shared
|
||||
B8:8878 i_item_setup_shared_all_items
|
||||
B8:8883 i_item_setup_shared_alwaysloaded
|
||||
84:FA79 i_live_pickup
|
||||
B8:817F i_live_pickup_multiworld
|
||||
B8:81C4 i_live_pickup_multiworld_end
|
||||
B8:819B i_live_pickup_multiworld_local_item_or_offworld
|
||||
B8:81B0 i_live_pickup_multiworld_own_item
|
||||
B8:81BC i_live_pickup_multiworld_own_item1
|
||||
B8:8278 i_live_pickup_multiworld
|
||||
B8:82BD i_live_pickup_multiworld_end
|
||||
B8:8294 i_live_pickup_multiworld_local_item_or_offworld
|
||||
B8:82A9 i_live_pickup_multiworld_own_item
|
||||
B8:82B5 i_live_pickup_multiworld_own_item1
|
||||
84:FA1E i_load_custom_graphics
|
||||
84:FA39 i_load_custom_graphics_all_items
|
||||
84:FA49 i_load_custom_graphics_alwaysloaded
|
||||
@@ -85,22 +90,27 @@ B8:81BC i_live_pickup_multiworld_own_item1
|
||||
85:B9CA message_write_placeholders_loop
|
||||
85:B9DC message_write_placeholders_notfound
|
||||
85:B9DF message_write_placeholders_value_ok
|
||||
B8:8092 mw_display_item_sent
|
||||
B8:80FF mw_handle_queue
|
||||
B8:8178 mw_handle_queue_end
|
||||
B8:8101 mw_handle_queue_loop
|
||||
B8:8151 mw_handle_queue_new_remote_item
|
||||
B8:816D mw_handle_queue_next
|
||||
B8:8163 mw_handle_queue_perform_receive
|
||||
B8:81C8 mw_hook_main_game
|
||||
B8:818B mw_display_item_sent
|
||||
B8:81F8 mw_handle_queue
|
||||
B8:8271 mw_handle_queue_end
|
||||
B8:81FA mw_handle_queue_loop
|
||||
B8:824A mw_handle_queue_new_remote_item
|
||||
B8:8266 mw_handle_queue_next
|
||||
B8:825C mw_handle_queue_perform_receive
|
||||
B8:82C1 mw_hook_main_game
|
||||
B8:8011 mw_init
|
||||
B8:8044 mw_init_end
|
||||
B8:8066 mw_init_continuereset
|
||||
B8:80EA mw_init_end
|
||||
B8:8000 mw_init_memory
|
||||
B8:8083 mw_load_sram
|
||||
B8:80B0 mw_receive_item
|
||||
B8:80E8 mw_receive_item_end
|
||||
B8:8070 mw_save_sram
|
||||
B8:8049 mw_write_message
|
||||
B8:803B mw_init_reset_sram
|
||||
B8:8051 mw_init_smstringdata
|
||||
B8:8174 mw_load_sram
|
||||
B8:8182 mw_load_sram_done
|
||||
B8:8185 mw_load_sram_setnewgame
|
||||
B8:81A9 mw_receive_item
|
||||
B8:81E1 mw_receive_item_end
|
||||
B8:8169 mw_save_sram
|
||||
B8:8142 mw_write_message
|
||||
84:F888 nonprog_item_eight_palette_indices
|
||||
89:9200 offworld_graphics_data_item
|
||||
89:9100 offworld_graphics_data_progression_item
|
||||
@@ -125,7 +135,7 @@ B8:8049 mw_write_message
|
||||
84:F96E p_visible_item_end
|
||||
84:F95B p_visible_item_loop
|
||||
84:F967 p_visible_item_trigger
|
||||
B8:81DF patch_load_multiworld
|
||||
B8:82D8 patch_load_multiworld
|
||||
84:FA7E perform_item_pickup
|
||||
84:F886 plm_graphics_entry_offworld_item
|
||||
84:F87C plm_graphics_entry_offworld_progression_item
|
||||
@@ -144,17 +154,19 @@ B8:C808 start_item_data_minor
|
||||
B8:C818 start_item_data_reserve
|
||||
B8:C856 update_graphic
|
||||
84:F890 v_item
|
||||
B8:80EF write_repeated_memory
|
||||
B8:80F4 write_repeated_memory_loop
|
||||
|
||||
[source files]
|
||||
0000 e25029c5 main.asm
|
||||
0001 06780555 ../common/nofanfare.asm
|
||||
0002 e76d1f83 ../common/multiworld.asm
|
||||
0002 4f9a780e ../common/multiworld.asm
|
||||
0003 613d24e1 ../common/itemextras.asm
|
||||
0004 d6616c0c ../common/items.asm
|
||||
0005 440b54fe ../common/startitem.asm
|
||||
|
||||
[rom checksum]
|
||||
09b134c5
|
||||
ad81eda1
|
||||
|
||||
[addr-to-line mapping]
|
||||
ff:ffff 0000:00000001
|
||||
@@ -204,330 +216,423 @@ ff:ffff 0000:00000001
|
||||
84:8bf2 0001:00000152
|
||||
84:8bf6 0001:00000153
|
||||
84:8bf7 0001:00000153
|
||||
b8:8000 0002:00000019
|
||||
b8:8002 0002:0000001a
|
||||
b8:8006 0002:0000001b
|
||||
b8:8008 0002:0000001c
|
||||
b8:800c 0002:00000020
|
||||
b8:800e 0002:00000021
|
||||
b8:8010 0002:00000022
|
||||
b8:8011 0002:00000025
|
||||
b8:8012 0002:00000025
|
||||
b8:8013 0002:00000025
|
||||
b8:8014 0002:00000025
|
||||
b8:8000 0002:0000005a
|
||||
b8:8002 0002:0000005b
|
||||
b8:8006 0002:0000005c
|
||||
b8:8008 0002:0000005d
|
||||
b8:800c 0002:00000061
|
||||
b8:800e 0002:00000062
|
||||
b8:8010 0002:00000063
|
||||
b8:8011 0002:00000066
|
||||
b8:8012 0002:00000066
|
||||
b8:8013 0002:00000066
|
||||
b8:8014 0002:00000066
|
||||
b8:8015 0000:00000013
|
||||
b8:8017 0002:00000029
|
||||
b8:801b 0002:0000002a
|
||||
b8:801e 0002:0000002b
|
||||
b8:8020 0002:0000002d
|
||||
b8:8023 0002:0000002e
|
||||
b8:8026 0002:00000031
|
||||
b8:802a 0002:00000032
|
||||
b8:802e 0002:00000033
|
||||
b8:8032 0002:00000034
|
||||
b8:8036 0002:00000035
|
||||
b8:8037 0002:00000035
|
||||
b8:8038 0002:00000036
|
||||
b8:803b 0002:00000037
|
||||
b8:803d 0002:00000039
|
||||
b8:8040 0002:0000003a
|
||||
b8:8044 0002:0000003d
|
||||
b8:8045 0002:0000003d
|
||||
b8:8046 0002:0000003d
|
||||
b8:8047 0002:0000003d
|
||||
b8:8048 0002:0000003e
|
||||
b8:8049 0002:00000043
|
||||
b8:804a 0002:00000043
|
||||
b8:804b 0002:00000044
|
||||
b8:804c 0002:00000044
|
||||
b8:804d 0002:00000045
|
||||
b8:8051 0002:00000046
|
||||
b8:8054 0002:00000046
|
||||
b8:8055 0002:00000047
|
||||
b8:8056 0002:00000048
|
||||
b8:805a 0002:00000049
|
||||
b8:805b 0002:0000004a
|
||||
b8:805f 0002:0000004b
|
||||
b8:8060 0002:0000004c
|
||||
b8:8064 0002:0000004e
|
||||
b8:8068 0002:0000004f
|
||||
b8:8069 0002:00000050
|
||||
b8:806d 0002:00000051
|
||||
b8:806e 0002:00000051
|
||||
b8:806f 0002:00000052
|
||||
b8:8070 0002:00000055
|
||||
b8:8071 0002:00000055
|
||||
b8:8072 0000:00000013
|
||||
b8:8074 0002:00000057
|
||||
b8:8078 0002:00000058
|
||||
b8:807c 0002:00000059
|
||||
b8:807d 0002:00000059
|
||||
b8:807e 0002:0000005b
|
||||
b8:807f 0002:0000005c
|
||||
b8:8082 0002:0000005d
|
||||
b8:8083 0002:00000060
|
||||
b8:8084 0002:00000060
|
||||
b8:8085 0000:00000013
|
||||
b8:8087 0002:00000062
|
||||
b8:808b 0002:00000063
|
||||
b8:808f 0002:00000064
|
||||
b8:8090 0002:00000064
|
||||
b8:8091 0002:00000065
|
||||
b8:8092 0002:0000006a
|
||||
b8:8094 0002:0000006b
|
||||
b8:8096 0002:0000006e
|
||||
b8:8099 0002:0000006f
|
||||
b8:809b 0002:00000070
|
||||
b8:809e 0002:00000071
|
||||
b8:80a0 0002:00000072
|
||||
b8:80a3 0002:00000073
|
||||
b8:80a7 0002:00000074
|
||||
b8:80a9 0002:00000075
|
||||
b8:80ab 0002:00000076
|
||||
b8:80ad 0002:00000077
|
||||
b8:80af 0002:00000078
|
||||
b8:80b0 0002:0000007c
|
||||
b8:80b1 0002:0000007c
|
||||
b8:80b2 0002:0000007d
|
||||
b8:80b5 0002:0000007e
|
||||
b8:80b7 0002:0000007f
|
||||
b8:80ba 0002:00000080
|
||||
b8:80bc 0002:00000081
|
||||
b8:80bd 0002:00000082
|
||||
b8:80be 0002:00000084
|
||||
b8:80c1 0002:00000085
|
||||
b8:80c3 0002:00000086
|
||||
b8:80c6 0002:00000087
|
||||
b8:80c7 0002:00000088
|
||||
b8:80ca 0002:00000089
|
||||
b8:80cb 0002:00000089
|
||||
b8:80cc 0002:0000008a
|
||||
b8:80d0 0002:0000008b
|
||||
b8:80d1 0002:0000008c
|
||||
b8:80d4 0002:0000008d
|
||||
b8:80d8 0002:0000008e
|
||||
b8:80da 0002:00000090
|
||||
b8:80dd 0002:00000091
|
||||
b8:80df 0002:00000092
|
||||
b8:80e2 0002:00000093
|
||||
b8:80e4 0002:00000095
|
||||
b8:80e8 0002:00000097
|
||||
b8:80ea 0002:00000098
|
||||
b8:80ec 0002:00000099
|
||||
b8:80ed 0002:00000099
|
||||
b8:80ee 0002:0000009a
|
||||
b8:80ef 0002:000000a5
|
||||
b8:80f0 0002:000000a6
|
||||
b8:80f4 0002:000000a7
|
||||
b8:80f5 0002:000000a8
|
||||
b8:80f9 0002:000000a9
|
||||
b8:80fa 0002:000000ab
|
||||
b8:80fe 0002:000000ac
|
||||
b8:80ff 0002:000000de
|
||||
b8:8100 0002:000000de
|
||||
b8:8101 0002:000000e1
|
||||
b8:8105 0002:000000e2
|
||||
b8:8109 0002:000000e3
|
||||
b8:810b 0002:000000e5
|
||||
b8:810d 0002:000000e5
|
||||
b8:810e 0002:000000e8
|
||||
b8:8112 0002:000000e9
|
||||
b8:8114 0002:000000ea
|
||||
b8:8118 0002:000000eb
|
||||
b8:811a 0002:000000ec
|
||||
b8:811e 0002:000000ed
|
||||
b8:8121 0002:000000ee
|
||||
b8:8123 0002:000000ef
|
||||
b8:8125 0002:000000f0
|
||||
b8:8129 0002:000000f1
|
||||
b8:812b 0002:000000f2
|
||||
b8:812d 0002:000000f3
|
||||
b8:8130 0002:000000f4
|
||||
b8:8133 0002:000000f5
|
||||
b8:8135 0002:000000f6
|
||||
b8:813d 0002:000000fa
|
||||
b8:813e 0002:000000fb
|
||||
b8:813f 0002:000000fc
|
||||
b8:8143 0002:000000ff
|
||||
b8:8147 0002:00000100
|
||||
b8:814b 0002:00000101
|
||||
b8:814d 0002:00000103
|
||||
b8:814e 0002:00000104
|
||||
b8:814f 0002:00000105
|
||||
b8:8151 0002:0000010a
|
||||
b8:8152 0002:0000010b
|
||||
b8:8156 0002:0000010e
|
||||
b8:815a 0002:0000010f
|
||||
b8:815e 0002:00000110
|
||||
b8:8162 0002:00000111
|
||||
b8:8163 0002:00000115
|
||||
b8:8165 0002:00000116
|
||||
b8:8168 0002:00000117
|
||||
b8:816a 0002:00000118
|
||||
b8:816d 0002:0000011b
|
||||
b8:8171 0002:0000011c
|
||||
b8:8172 0002:0000011d
|
||||
b8:8176 0002:0000011f
|
||||
b8:8178 0002:00000122
|
||||
b8:817a 0002:00000123
|
||||
b8:817c 0002:00000124
|
||||
b8:817d 0002:00000124
|
||||
b8:817e 0002:00000125
|
||||
b8:817f 0002:00000129
|
||||
b8:8180 0002:00000129
|
||||
b8:8181 0002:00000129
|
||||
b8:8182 0002:0000012a
|
||||
b8:8186 0002:0000012b
|
||||
b8:8189 0002:0000012b
|
||||
b8:818a 0002:0000012d
|
||||
b8:818e 0002:0000012e
|
||||
b8:818f 0002:0000012f
|
||||
b8:8193 0002:00000130
|
||||
b8:8196 0002:00000131
|
||||
b8:8198 0002:00000133
|
||||
b8:819b 0002:00000136
|
||||
b8:819f 0002:00000137
|
||||
b8:81a3 0002:00000138
|
||||
b8:81a5 0002:0000013a
|
||||
b8:81a9 0002:0000013b
|
||||
b8:81aa 0002:0000013d
|
||||
b8:81ae 0002:0000013e
|
||||
b8:81b0 0002:00000141
|
||||
b8:81b4 0002:00000142
|
||||
b8:81b7 0002:00000143
|
||||
b8:81b9 0002:00000144
|
||||
b8:81bc 0002:00000147
|
||||
b8:81bd 0002:00000148
|
||||
b8:81be 0002:00000149
|
||||
b8:81c2 0002:0000014a
|
||||
b8:81c4 0002:0000014d
|
||||
b8:81c5 0002:0000014d
|
||||
b8:81c6 0002:0000014d
|
||||
b8:81c7 0002:0000014e
|
||||
b8:81c8 0002:00000152
|
||||
b8:81cc 0002:00000153
|
||||
b8:81d0 0002:00000154
|
||||
b8:81d2 0002:00000155
|
||||
b8:81d6 0002:00000156
|
||||
b8:81d9 0002:00000157
|
||||
b8:81db 0002:00000158
|
||||
b8:81de 0002:0000015a
|
||||
b8:81df 0002:0000015d
|
||||
b8:81e3 0002:0000015e
|
||||
b8:81e4 0002:0000015f
|
||||
b8:81e7 0002:00000160
|
||||
b8:81eb 0002:00000162
|
||||
b8:81ec 0002:00000163
|
||||
b8:81ed 0002:00000164
|
||||
b8:81ee 0002:00000165
|
||||
b8:81ef 0002:00000166
|
||||
8b:914a 0002:0000016b
|
||||
81:80f7 0002:0000016e
|
||||
81:8027 0002:00000171
|
||||
82:8bb3 0002:00000174
|
||||
85:b9a3 0002:0000020e
|
||||
85:b9a4 0002:0000020e
|
||||
85:b9a5 0002:00000211
|
||||
85:b9a7 0002:00000212
|
||||
85:b9ad 0002:00000212
|
||||
85:b9ae 0002:00000213
|
||||
85:b9b1 0002:00000214
|
||||
85:b9b2 0002:00000215
|
||||
85:b9b3 0002:00000215
|
||||
85:b9b4 0002:00000219
|
||||
85:b9b7 0002:0000021a
|
||||
85:b9bb 0002:0000021b
|
||||
85:b9bd 0002:0000021b
|
||||
85:b9bf 0002:0000021c
|
||||
85:b9c2 0002:0000021d
|
||||
85:b9c4 0002:0000021f
|
||||
85:b9c5 0002:00000220
|
||||
85:b9c7 0002:00000224
|
||||
85:b9ca 0002:00000226
|
||||
85:b9cd 0002:00000227
|
||||
85:b9cf 0002:00000228
|
||||
85:b9d1 0002:00000229
|
||||
85:b9d5 0002:0000022a
|
||||
85:b9d7 0002:0000022b
|
||||
85:b9d9 0002:0000022c
|
||||
85:b9da 0002:0000022d
|
||||
85:b9dc 0002:0000022f
|
||||
85:b9df 0002:00000231
|
||||
85:b9e2 0002:00000231
|
||||
85:b9e3 0002:00000232
|
||||
85:b9e6 0002:00000234
|
||||
85:b9ea 0002:00000235
|
||||
85:b9ed 0002:00000236
|
||||
85:b9ee 0002:00000237
|
||||
85:b9ef 0002:00000237
|
||||
85:b9f0 0002:00000238
|
||||
85:b9f4 0002:00000239
|
||||
85:b9f5 0002:0000023a
|
||||
85:b9f9 0002:0000023b
|
||||
85:b9fb 0002:0000023c
|
||||
85:b9fc 0002:0000023d
|
||||
85:b9fd 0002:0000023e
|
||||
85:ba00 0002:0000023f
|
||||
85:ba02 0002:00000240
|
||||
85:ba04 0002:00000243
|
||||
85:ba05 0002:00000243
|
||||
85:ba06 0002:00000244
|
||||
85:ba09 0002:00000245
|
||||
85:ba8a 0002:00000253
|
||||
85:ba8c 0002:00000254
|
||||
85:ba8f 0002:00000255
|
||||
85:ba92 0002:00000256
|
||||
85:ba95 0002:0000025e
|
||||
85:ba96 0002:0000025f
|
||||
85:ba98 0002:00000260
|
||||
85:ba9b 0002:00000261
|
||||
85:ba9d 0002:00000262
|
||||
85:ba9f 0002:00000263
|
||||
85:baa2 0002:00000264
|
||||
85:baa4 0002:00000265
|
||||
85:baa7 0002:00000266
|
||||
85:baa9 0002:00000269
|
||||
85:baaa 0002:0000026a
|
||||
85:baab 0002:0000026b
|
||||
85:baac 0002:0000026c
|
||||
85:baae 0002:0000026d
|
||||
85:baaf 0002:0000026e
|
||||
85:bab0 0002:0000026f
|
||||
85:bab1 0002:00000274
|
||||
85:bab4 0002:00000275
|
||||
85:bab5 0002:00000276
|
||||
85:bab8 0002:00000277
|
||||
85:bab9 0002:00000278
|
||||
85:baba 0002:00000279
|
||||
85:babb 0002:0000027a
|
||||
85:babc 0002:00000285
|
||||
85:babd 0002:00000286
|
||||
85:babf 0002:00000287
|
||||
85:bac2 0002:00000288
|
||||
85:bac4 0002:00000289
|
||||
85:bac7 0002:0000028a
|
||||
85:bac9 0002:0000028d
|
||||
85:baca 0002:0000028e
|
||||
85:bacb 0002:0000028f
|
||||
85:bacd 0002:00000290
|
||||
85:bace 0002:00000292
|
||||
85:bacf 0002:00000293
|
||||
85:bad1 0002:00000294
|
||||
85:bad4 0002:00000295
|
||||
85:bad6 0002:00000296
|
||||
85:bad9 0002:00000297
|
||||
85:badb 0002:00000298
|
||||
85:badc 0002:0000029a
|
||||
85:badd 0002:0000029b
|
||||
85:badf 0002:0000029c
|
||||
85:bae2 0002:0000029d
|
||||
85:bae4 0002:0000029e
|
||||
85:bae7 0002:0000029f
|
||||
85:bae9 0002:000002a0
|
||||
85:8246 0002:000002a5
|
||||
85:8249 0002:000002a6
|
||||
85:824b 0002:000002a7
|
||||
85:82f9 0002:000002ab
|
||||
b8:8017 0002:0000006a
|
||||
b8:801b 0002:0000006b
|
||||
b8:801e 0002:0000006c
|
||||
b8:8020 0002:0000006d
|
||||
b8:8024 0002:0000006e
|
||||
b8:8028 0002:0000006f
|
||||
b8:802a 0002:00000070
|
||||
b8:802e 0002:00000071
|
||||
b8:8032 0002:00000072
|
||||
b8:8034 0002:00000074
|
||||
b8:8038 0002:00000075
|
||||
b8:803b 0002:00000078
|
||||
b8:803c 0002:00000079
|
||||
b8:803f 0002:0000007a
|
||||
b8:8042 0002:0000007b
|
||||
b8:8045 0002:0000007c
|
||||
b8:8048 0002:0000007d
|
||||
b8:8049 0002:0000007e
|
||||
b8:804a 0002:0000007f
|
||||
b8:804e 0002:00000080
|
||||
b8:804f 0002:00000082
|
||||
b8:8066 0002:00000086
|
||||
b8:8068 0002:00000087
|
||||
b8:8069 0002:00000088
|
||||
b8:806a 0002:00000089
|
||||
b8:806c 0002:0000008a
|
||||
b8:806e 0002:0000008b
|
||||
b8:8070 0002:0000008c
|
||||
b8:8072 0002:0000008d
|
||||
b8:8075 0002:0000008e
|
||||
b8:8077 0002:0000008f
|
||||
b8:807a 0002:00000090
|
||||
b8:807d 0002:00000091
|
||||
b8:807f 0002:00000092
|
||||
b8:8083 0002:00000094
|
||||
b8:8085 0002:00000095
|
||||
b8:8087 0002:00000096
|
||||
b8:8089 0002:00000097
|
||||
b8:808b 0002:00000098
|
||||
b8:808d 0002:00000099
|
||||
b8:808f 0002:0000009a
|
||||
b8:8092 0002:0000009b
|
||||
b8:8094 0002:0000009c
|
||||
b8:8097 0002:0000009d
|
||||
b8:809a 0002:0000009e
|
||||
b8:809c 0002:0000009f
|
||||
b8:80a0 0002:000000a1
|
||||
b8:80a3 0002:000000a2
|
||||
b8:80a7 0002:000000a3
|
||||
b8:80ab 0002:000000a4
|
||||
b8:80af 0002:000000a5
|
||||
b8:80b3 0002:000000a6
|
||||
b8:80b7 0002:000000a8
|
||||
b8:80bb 0002:000000b0
|
||||
b8:80be 0002:000000b1
|
||||
b8:80c1 0002:000000b3
|
||||
b8:80c2 0002:000000b4
|
||||
b8:80c3 0002:000000b5
|
||||
b8:80c7 0002:000000b6
|
||||
b8:80cb 0002:000000b7
|
||||
b8:80cd 0002:000000c4
|
||||
b8:80d1 0002:000000c5
|
||||
b8:80d4 0002:000000c6
|
||||
b8:80d6 0002:000000c7
|
||||
b8:80da 0002:000000c8
|
||||
b8:80dd 0002:000000c9
|
||||
b8:80df 0002:000000ce
|
||||
b8:80e2 0002:000000cf
|
||||
b8:80e6 0002:000000d0
|
||||
b8:80ea 0002:000000d3
|
||||
b8:80eb 0002:000000d3
|
||||
b8:80ec 0002:000000d3
|
||||
b8:80ed 0002:000000d3
|
||||
b8:80ee 0002:000000d4
|
||||
b8:80ef 0002:000000db
|
||||
b8:80f0 0002:000000dc
|
||||
b8:80f1 0002:000000dd
|
||||
b8:80f2 0002:000000de
|
||||
b8:80f3 0002:000000df
|
||||
b8:80f4 0002:000000e1
|
||||
b8:80f7 0002:000000e2
|
||||
b8:80f8 0002:000000e3
|
||||
b8:80f9 0002:000000e4
|
||||
b8:80fa 0002:000000e5
|
||||
b8:80fc 0002:000000e7
|
||||
b8:80fd 0002:000000ee
|
||||
b8:80fe 0002:000000ef
|
||||
b8:80ff 0002:000000f0
|
||||
b8:8100 0002:000000f1
|
||||
b8:8102 0002:000000f3
|
||||
b8:8104 0002:000000f4
|
||||
b8:8105 0002:000000f5
|
||||
b8:8107 0002:000000f6
|
||||
b8:8109 0002:000000f8
|
||||
b8:810b 0002:000000f9
|
||||
b8:810c 0002:000000fa
|
||||
b8:810d 0002:000000fb
|
||||
b8:810f 0002:000000fd
|
||||
b8:8111 0002:000000fe
|
||||
b8:8113 0002:000000ff
|
||||
b8:8114 0002:00000100
|
||||
b8:8115 0002:00000101
|
||||
b8:8117 0002:00000103
|
||||
b8:8118 0002:00000104
|
||||
b8:8119 0002:00000108
|
||||
b8:811d 0002:00000109
|
||||
b8:8121 0002:0000010a
|
||||
b8:8125 0002:0000010b
|
||||
b8:8129 0002:0000010c
|
||||
b8:812d 0002:0000010d
|
||||
b8:8131 0002:0000010e
|
||||
b8:8135 0002:0000010f
|
||||
b8:8139 0002:00000110
|
||||
b8:813d 0002:00000111
|
||||
b8:8141 0002:00000112
|
||||
b8:8142 0002:00000118
|
||||
b8:8143 0002:00000118
|
||||
b8:8144 0002:00000119
|
||||
b8:8145 0002:00000119
|
||||
b8:8146 0002:0000011a
|
||||
b8:814a 0002:0000011b
|
||||
b8:814d 0002:0000011b
|
||||
b8:814e 0002:0000011c
|
||||
b8:814f 0002:0000011d
|
||||
b8:8153 0002:0000011e
|
||||
b8:8154 0002:0000011f
|
||||
b8:8158 0002:00000120
|
||||
b8:8159 0002:00000121
|
||||
b8:815d 0002:00000123
|
||||
b8:8161 0002:00000124
|
||||
b8:8162 0002:00000125
|
||||
b8:8166 0002:00000126
|
||||
b8:8167 0002:00000126
|
||||
b8:8168 0002:00000127
|
||||
b8:8169 0002:0000012c
|
||||
b8:816a 0002:0000012c
|
||||
b8:816b 0000:00000013
|
||||
b8:816d 0002:0000012f
|
||||
b8:816e 0002:0000012f
|
||||
b8:816f 0002:00000131
|
||||
b8:8170 0002:00000132
|
||||
b8:8173 0002:00000133
|
||||
b8:8174 0002:00000138
|
||||
b8:8175 0002:00000138
|
||||
b8:8176 0000:00000013
|
||||
b8:8178 0002:0000013a
|
||||
b8:817c 0002:0000013b
|
||||
b8:8180 0002:0000013c
|
||||
b8:8182 0002:0000013e
|
||||
b8:8183 0002:0000013e
|
||||
b8:8184 0002:0000013f
|
||||
b8:8185 0002:00000147
|
||||
b8:8189 0002:00000148
|
||||
b8:818b 0002:0000014e
|
||||
b8:818d 0002:0000014f
|
||||
b8:818f 0002:00000152
|
||||
b8:8192 0002:00000153
|
||||
b8:8194 0002:00000154
|
||||
b8:8197 0002:00000155
|
||||
b8:8199 0002:00000156
|
||||
b8:819c 0002:00000157
|
||||
b8:81a0 0002:00000158
|
||||
b8:81a2 0002:00000159
|
||||
b8:81a4 0002:0000015a
|
||||
b8:81a6 0002:0000015b
|
||||
b8:81a8 0002:0000015c
|
||||
b8:81a9 0002:00000160
|
||||
b8:81aa 0002:00000160
|
||||
b8:81ab 0002:00000161
|
||||
b8:81ae 0002:00000162
|
||||
b8:81b0 0002:00000163
|
||||
b8:81b3 0002:00000164
|
||||
b8:81b5 0002:00000165
|
||||
b8:81b6 0002:00000166
|
||||
b8:81b7 0002:00000168
|
||||
b8:81ba 0002:00000169
|
||||
b8:81bc 0002:0000016a
|
||||
b8:81bf 0002:0000016b
|
||||
b8:81c0 0002:0000016c
|
||||
b8:81c3 0002:0000016d
|
||||
b8:81c4 0002:0000016d
|
||||
b8:81c5 0002:0000016e
|
||||
b8:81c9 0002:0000016f
|
||||
b8:81ca 0002:00000170
|
||||
b8:81cd 0002:00000171
|
||||
b8:81d1 0002:00000172
|
||||
b8:81d3 0002:00000174
|
||||
b8:81d6 0002:00000175
|
||||
b8:81d8 0002:00000176
|
||||
b8:81db 0002:00000177
|
||||
b8:81dd 0002:00000179
|
||||
b8:81e1 0002:0000017b
|
||||
b8:81e3 0002:0000017c
|
||||
b8:81e5 0002:0000017d
|
||||
b8:81e6 0002:0000017d
|
||||
b8:81e7 0002:0000017e
|
||||
b8:81e8 0002:00000189
|
||||
b8:81e9 0002:0000018a
|
||||
b8:81ed 0002:0000018b
|
||||
b8:81ee 0002:0000018c
|
||||
b8:81f2 0002:0000018d
|
||||
b8:81f3 0002:0000018f
|
||||
b8:81f7 0002:00000190
|
||||
b8:81f8 0002:000001c2
|
||||
b8:81f9 0002:000001c2
|
||||
b8:81fa 0002:000001c5
|
||||
b8:81fe 0002:000001c6
|
||||
b8:8202 0002:000001c7
|
||||
b8:8204 0002:000001c9
|
||||
b8:8206 0002:000001c9
|
||||
b8:8207 0002:000001cc
|
||||
b8:820b 0002:000001cd
|
||||
b8:820d 0002:000001ce
|
||||
b8:8211 0002:000001cf
|
||||
b8:8213 0002:000001d0
|
||||
b8:8217 0002:000001d1
|
||||
b8:821a 0002:000001d2
|
||||
b8:821c 0002:000001d3
|
||||
b8:821e 0002:000001d4
|
||||
b8:8222 0002:000001d5
|
||||
b8:8224 0002:000001d6
|
||||
b8:8226 0002:000001d7
|
||||
b8:8229 0002:000001d8
|
||||
b8:822c 0002:000001d9
|
||||
b8:822e 0002:000001da
|
||||
b8:8236 0002:000001de
|
||||
b8:8237 0002:000001df
|
||||
b8:8238 0002:000001e0
|
||||
b8:823c 0002:000001e3
|
||||
b8:8240 0002:000001e4
|
||||
b8:8244 0002:000001e5
|
||||
b8:8246 0002:000001e7
|
||||
b8:8247 0002:000001e8
|
||||
b8:8248 0002:000001e9
|
||||
b8:824a 0002:000001ee
|
||||
b8:824b 0002:000001ef
|
||||
b8:824f 0002:000001f2
|
||||
b8:8253 0002:000001f3
|
||||
b8:8257 0002:000001f4
|
||||
b8:825b 0002:000001f5
|
||||
b8:825c 0002:000001f9
|
||||
b8:825e 0002:000001fa
|
||||
b8:8261 0002:000001fb
|
||||
b8:8263 0002:000001fc
|
||||
b8:8266 0002:000001ff
|
||||
b8:826a 0002:00000200
|
||||
b8:826b 0002:00000201
|
||||
b8:826f 0002:00000203
|
||||
b8:8271 0002:00000206
|
||||
b8:8273 0002:00000207
|
||||
b8:8275 0002:00000208
|
||||
b8:8276 0002:00000208
|
||||
b8:8277 0002:00000209
|
||||
b8:8278 0002:0000020d
|
||||
b8:8279 0002:0000020d
|
||||
b8:827a 0002:0000020d
|
||||
b8:827b 0002:0000020e
|
||||
b8:827f 0002:0000020f
|
||||
b8:8282 0002:0000020f
|
||||
b8:8283 0002:00000211
|
||||
b8:8287 0002:00000212
|
||||
b8:8288 0002:00000213
|
||||
b8:828c 0002:00000214
|
||||
b8:828f 0002:00000215
|
||||
b8:8291 0002:00000217
|
||||
b8:8294 0002:0000021a
|
||||
b8:8298 0002:0000021b
|
||||
b8:829c 0002:0000021c
|
||||
b8:829e 0002:0000021e
|
||||
b8:82a2 0002:0000021f
|
||||
b8:82a3 0002:00000221
|
||||
b8:82a7 0002:00000222
|
||||
b8:82a9 0002:00000225
|
||||
b8:82ad 0002:00000226
|
||||
b8:82b0 0002:00000227
|
||||
b8:82b2 0002:00000228
|
||||
b8:82b5 0002:0000022b
|
||||
b8:82b6 0002:0000022c
|
||||
b8:82b7 0002:0000022d
|
||||
b8:82bb 0002:0000022e
|
||||
b8:82bd 0002:00000231
|
||||
b8:82be 0002:00000231
|
||||
b8:82bf 0002:00000231
|
||||
b8:82c0 0002:00000232
|
||||
b8:82c1 0002:00000236
|
||||
b8:82c5 0002:00000237
|
||||
b8:82c9 0002:00000238
|
||||
b8:82cb 0002:00000239
|
||||
b8:82cf 0002:0000023a
|
||||
b8:82d2 0002:0000023b
|
||||
b8:82d4 0002:0000023c
|
||||
b8:82d7 0002:0000023e
|
||||
b8:82d8 0002:00000241
|
||||
b8:82dc 0002:00000243
|
||||
b8:82dd 0002:00000244
|
||||
b8:82de 0002:00000245
|
||||
b8:82df 0002:00000246
|
||||
b8:82e0 0002:00000247
|
||||
8b:914a 0002:0000024c
|
||||
81:80f7 0002:0000024f
|
||||
81:8027 0002:00000252
|
||||
82:8bb3 0002:00000255
|
||||
85:b9a3 0002:000002ef
|
||||
85:b9a4 0002:000002ef
|
||||
85:b9a5 0002:000002f2
|
||||
85:b9a7 0002:000002f3
|
||||
85:b9ad 0002:000002f3
|
||||
85:b9ae 0002:000002f4
|
||||
85:b9b1 0002:000002f5
|
||||
85:b9b2 0002:000002f6
|
||||
85:b9b3 0002:000002f6
|
||||
85:b9b4 0002:000002fa
|
||||
85:b9b7 0002:000002fb
|
||||
85:b9bb 0002:000002fc
|
||||
85:b9bd 0002:000002fc
|
||||
85:b9bf 0002:000002fd
|
||||
85:b9c2 0002:000002fe
|
||||
85:b9c4 0002:00000300
|
||||
85:b9c5 0002:00000301
|
||||
85:b9c7 0002:00000305
|
||||
85:b9ca 0002:00000307
|
||||
85:b9cd 0002:00000308
|
||||
85:b9cf 0002:00000309
|
||||
85:b9d1 0002:0000030a
|
||||
85:b9d5 0002:0000030b
|
||||
85:b9d7 0002:0000030c
|
||||
85:b9d9 0002:0000030d
|
||||
85:b9da 0002:0000030e
|
||||
85:b9dc 0002:00000310
|
||||
85:b9df 0002:00000312
|
||||
85:b9e2 0002:00000312
|
||||
85:b9e3 0002:00000313
|
||||
85:b9e6 0002:00000315
|
||||
85:b9ea 0002:00000316
|
||||
85:b9ed 0002:00000317
|
||||
85:b9ee 0002:00000318
|
||||
85:b9ef 0002:00000318
|
||||
85:b9f0 0002:00000319
|
||||
85:b9f4 0002:0000031a
|
||||
85:b9f5 0002:0000031b
|
||||
85:b9f9 0002:0000031c
|
||||
85:b9fb 0002:0000031d
|
||||
85:b9fc 0002:0000031e
|
||||
85:b9fd 0002:0000031f
|
||||
85:ba00 0002:00000320
|
||||
85:ba02 0002:00000321
|
||||
85:ba04 0002:00000324
|
||||
85:ba05 0002:00000324
|
||||
85:ba06 0002:00000325
|
||||
85:ba09 0002:00000326
|
||||
85:ba8a 0002:00000334
|
||||
85:ba8c 0002:00000335
|
||||
85:ba8f 0002:00000336
|
||||
85:ba92 0002:00000337
|
||||
85:ba95 0002:0000033f
|
||||
85:ba96 0002:00000340
|
||||
85:ba98 0002:00000341
|
||||
85:ba9b 0002:00000342
|
||||
85:ba9d 0002:00000343
|
||||
85:ba9f 0002:00000344
|
||||
85:baa2 0002:00000345
|
||||
85:baa4 0002:00000346
|
||||
85:baa7 0002:00000347
|
||||
85:baa9 0002:0000034a
|
||||
85:baaa 0002:0000034b
|
||||
85:baab 0002:0000034c
|
||||
85:baac 0002:0000034d
|
||||
85:baae 0002:0000034e
|
||||
85:baaf 0002:0000034f
|
||||
85:bab0 0002:00000350
|
||||
85:bab1 0002:00000355
|
||||
85:bab4 0002:00000356
|
||||
85:bab5 0002:00000357
|
||||
85:bab8 0002:00000358
|
||||
85:bab9 0002:00000359
|
||||
85:baba 0002:0000035a
|
||||
85:babb 0002:0000035b
|
||||
85:babc 0002:00000366
|
||||
85:babd 0002:00000367
|
||||
85:babf 0002:00000368
|
||||
85:bac2 0002:00000369
|
||||
85:bac4 0002:0000036a
|
||||
85:bac7 0002:0000036b
|
||||
85:bac9 0002:0000036e
|
||||
85:baca 0002:0000036f
|
||||
85:bacb 0002:00000370
|
||||
85:bacd 0002:00000371
|
||||
85:bace 0002:00000373
|
||||
85:bacf 0002:00000374
|
||||
85:bad1 0002:00000375
|
||||
85:bad4 0002:00000376
|
||||
85:bad6 0002:00000377
|
||||
85:bad9 0002:00000378
|
||||
85:badb 0002:00000379
|
||||
85:badc 0002:0000037b
|
||||
85:badd 0002:0000037c
|
||||
85:badf 0002:0000037d
|
||||
85:bae2 0002:0000037e
|
||||
85:bae4 0002:0000037f
|
||||
85:bae7 0002:00000380
|
||||
85:bae9 0002:00000381
|
||||
85:8246 0002:00000386
|
||||
85:8249 0002:00000387
|
||||
85:824b 0002:00000388
|
||||
85:82f9 0002:0000038c
|
||||
b8:885c 0003:00000045
|
||||
b8:885d 0003:00000045
|
||||
b8:885e 0003:00000046
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"CLIPLEN_end": "85:990F",
|
||||
"CLIPLEN_no_multi": "85:990C",
|
||||
"CLIPSET": "85:FF1D",
|
||||
"COLLECTTANK": "B8:80EF",
|
||||
"COLLECTTANK": "B8:81E8",
|
||||
"MISCFX": "85:FF45",
|
||||
"NORMAL": "84:8BF2",
|
||||
"SETFX": "85:FF4E",
|
||||
@@ -22,6 +22,11 @@
|
||||
"config_player_id": "CE:FF08",
|
||||
"config_remote_items": "CE:FF06",
|
||||
"config_sprite": "CE:FF02",
|
||||
"copy_config_to_sram": "B8:8119",
|
||||
"copy_memory": "B8:80FD",
|
||||
"copy_memory_done": "B8:8117",
|
||||
"copy_memory_even": "B8:8109",
|
||||
"copy_memory_loop": "B8:810F",
|
||||
"h_item": "84:F894",
|
||||
"i_chozo_item": "84:F8AD",
|
||||
"i_hidden_item": "84:F8B4",
|
||||
@@ -30,11 +35,11 @@
|
||||
"i_item_setup_shared_all_items": "B8:8878",
|
||||
"i_item_setup_shared_alwaysloaded": "B8:8883",
|
||||
"i_live_pickup": "84:FA79",
|
||||
"i_live_pickup_multiworld": "B8:817F",
|
||||
"i_live_pickup_multiworld_end": "B8:81C4",
|
||||
"i_live_pickup_multiworld_local_item_or_offworld": "B8:819B",
|
||||
"i_live_pickup_multiworld_own_item": "B8:81B0",
|
||||
"i_live_pickup_multiworld_own_item1": "B8:81BC",
|
||||
"i_live_pickup_multiworld": "B8:8278",
|
||||
"i_live_pickup_multiworld_end": "B8:82BD",
|
||||
"i_live_pickup_multiworld_local_item_or_offworld": "B8:8294",
|
||||
"i_live_pickup_multiworld_own_item": "B8:82A9",
|
||||
"i_live_pickup_multiworld_own_item1": "B8:82B5",
|
||||
"i_load_custom_graphics": "84:FA1E",
|
||||
"i_load_custom_graphics_all_items": "84:FA39",
|
||||
"i_load_custom_graphics_alwaysloaded": "84:FA49",
|
||||
@@ -69,22 +74,27 @@
|
||||
"message_write_placeholders_loop": "85:B9CA",
|
||||
"message_write_placeholders_notfound": "85:B9DC",
|
||||
"message_write_placeholders_value_ok": "85:B9DF",
|
||||
"mw_display_item_sent": "B8:8092",
|
||||
"mw_handle_queue": "B8:80FF",
|
||||
"mw_handle_queue_end": "B8:8178",
|
||||
"mw_handle_queue_loop": "B8:8101",
|
||||
"mw_handle_queue_new_remote_item": "B8:8151",
|
||||
"mw_handle_queue_next": "B8:816D",
|
||||
"mw_handle_queue_perform_receive": "B8:8163",
|
||||
"mw_hook_main_game": "B8:81C8",
|
||||
"mw_display_item_sent": "B8:818B",
|
||||
"mw_handle_queue": "B8:81F8",
|
||||
"mw_handle_queue_end": "B8:8271",
|
||||
"mw_handle_queue_loop": "B8:81FA",
|
||||
"mw_handle_queue_new_remote_item": "B8:824A",
|
||||
"mw_handle_queue_next": "B8:8266",
|
||||
"mw_handle_queue_perform_receive": "B8:825C",
|
||||
"mw_hook_main_game": "B8:82C1",
|
||||
"mw_init": "B8:8011",
|
||||
"mw_init_end": "B8:8044",
|
||||
"mw_init_continuereset": "B8:8066",
|
||||
"mw_init_end": "B8:80EA",
|
||||
"mw_init_memory": "B8:8000",
|
||||
"mw_load_sram": "B8:8083",
|
||||
"mw_receive_item": "B8:80B0",
|
||||
"mw_receive_item_end": "B8:80E8",
|
||||
"mw_save_sram": "B8:8070",
|
||||
"mw_write_message": "B8:8049",
|
||||
"mw_init_reset_sram": "B8:803B",
|
||||
"mw_init_smstringdata": "B8:8051",
|
||||
"mw_load_sram": "B8:8174",
|
||||
"mw_load_sram_done": "B8:8182",
|
||||
"mw_load_sram_setnewgame": "B8:8185",
|
||||
"mw_receive_item": "B8:81A9",
|
||||
"mw_receive_item_end": "B8:81E1",
|
||||
"mw_save_sram": "B8:8169",
|
||||
"mw_write_message": "B8:8142",
|
||||
"nonprog_item_eight_palette_indices": "84:F888",
|
||||
"offworld_graphics_data_item": "89:9200",
|
||||
"offworld_graphics_data_progression_item": "89:9100",
|
||||
@@ -109,7 +119,7 @@
|
||||
"p_visible_item_end": "84:F96E",
|
||||
"p_visible_item_loop": "84:F95B",
|
||||
"p_visible_item_trigger": "84:F967",
|
||||
"patch_load_multiworld": "B8:81DF",
|
||||
"patch_load_multiworld": "B8:82D8",
|
||||
"perform_item_pickup": "84:FA7E",
|
||||
"plm_graphics_entry_offworld_item": "84:F886",
|
||||
"plm_graphics_entry_offworld_progression_item": "84:F87C",
|
||||
@@ -128,14 +138,24 @@
|
||||
"start_item_data_reserve": "B8:C818",
|
||||
"update_graphic": "B8:C856",
|
||||
"v_item": "84:F890",
|
||||
"write_repeated_memory": "B8:80EF",
|
||||
"write_repeated_memory_loop": "B8:80F4",
|
||||
"ITEM_RAM": "7E:09A2",
|
||||
"SRAM_MW_ITEMS_RECV": "70:2000",
|
||||
"SRAM_MW_ITEMS_RECV_RPTR": "70:2600",
|
||||
"SRAM_MW_ITEMS_RECV_WPTR": "70:2602",
|
||||
"SRAM_MW_ITEMS_RECV_SPTR": "70:2604",
|
||||
"SRAM_MW_ITEMS_SENT_RPTR": "70:2680",
|
||||
"SRAM_MW_ITEMS_SENT_WPTR": "70:2682",
|
||||
"SRAM_MW_ITEMS_RECV_WCOUNT": "70:2602",
|
||||
"ReceiveQueueCompletedCount_InRamThatGetsSavedToSaveSlot": "7e:d8ae",
|
||||
"SRAM_MW_ITEMS_SENT_RCOUNT": "70:2680",
|
||||
"SRAM_MW_ITEMS_SENT_WCOUNT": "70:2682",
|
||||
"SRAM_MW_ITEMS_SENT": "70:2700",
|
||||
"SRAM_MW_INITIALIZED": "70:26fe",
|
||||
"SRAM_MW_SM": "70:3000",
|
||||
"SRAM_MW_ROMTITLE": "70:3015",
|
||||
"SRAM_MW_SEEDINT": "70:3060",
|
||||
"SRAM_MW_INITIALIZED": "70:3064",
|
||||
"SRAM_MW_CONFIG_ENABLED": "70:3070",
|
||||
"SRAM_MW_CONFIG_CUSTOM_SPRITE": "70:3072",
|
||||
"SRAM_MW_CONFIG_DEATHLINK": "70:3074",
|
||||
"SRAM_MW_CONFIG_REMOTE_ITEMS": "70:3076",
|
||||
"SRAM_MW_CONFIG_PLAYER_ID": "70:3078",
|
||||
"varia_seedint_location": "df:ff00",
|
||||
"CollectedItems": "7E:D86E"
|
||||
}
|
||||
@@ -294,7 +294,18 @@ accessPoints = [
|
||||
sm.canGetBackFromRidleyZone(),
|
||||
sm.canPassWastelandDessgeegas(),
|
||||
sm.canPassRedKiHunters())),
|
||||
'RidleyRoomOut': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']))
|
||||
'RidleyRoomOut': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])),
|
||||
'Wasteland': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canGetBackFromRidleyZone(),
|
||||
sm.canPassWastelandDessgeegas()))
|
||||
}, internal=True),
|
||||
AccessPoint('Wasteland', 'LowerNorfair', {
|
||||
# no transition to firefleas to exlude pb of shame location when starting at firefleas top
|
||||
'Ridley Zone': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.traverse('WastelandLeft'),
|
||||
sm.canGetBackFromRidleyZone(),
|
||||
sm.canPassWastelandDessgeegas(),
|
||||
sm.canPassNinjaPirates()))
|
||||
}, internal=True),
|
||||
AccessPoint('Three Muskateers Room Left', 'LowerNorfair', {
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
|
||||
@@ -797,10 +797,10 @@ locationsDict["Power Bomb (lower Norfair above fire flea room)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (Power Bombs of shame)"].AccessFrom = {
|
||||
'Ridley Zone': lambda sm: sm.canUsePowerBombs()
|
||||
'Wasteland': lambda sm: sm.canUsePowerBombs()
|
||||
}
|
||||
locationsDict["Power Bomb (Power Bombs of shame)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (lower Norfair near Wave Beam)"].AccessFrom = {
|
||||
'Firefleas': lambda sm: SMBool(True)
|
||||
|
||||
@@ -245,7 +245,7 @@ locBitFS_table = {
|
||||
|
||||
locWMotR_table = {
|
||||
"Wing Mario Over the Rainbow Red Coins": 3626154,
|
||||
"Wing Mario Over the Rainbow 1Up Block": 3626242
|
||||
"Wing Mario Over the Rainbow 1Up Block": 3626243
|
||||
}
|
||||
|
||||
locBitS_table = {
|
||||
@@ -268,4 +268,4 @@ location_table = {**locBoB_table,**locWhomp_table,**locJRB_table,**locCCM_table,
|
||||
**locWDW_table,**locTTM_table,**locTHI_table,**locTTC_table,**locRR_table, \
|
||||
**loc100Coin_table,**locPSS_table,**locSA_table,**locBitDW_table,**locTotWC_table, \
|
||||
**locCotMC_table, **locVCutM_table, **locBitFS_table, **locWMotR_table, **locBitS_table, \
|
||||
**locSS_table}
|
||||
**locSS_table}
|
||||
|
||||
@@ -34,7 +34,7 @@ class SM64World(World):
|
||||
item_name_to_id = item_table
|
||||
location_name_to_id = location_table
|
||||
|
||||
data_version = 7
|
||||
data_version = 8
|
||||
required_client_version = (0, 3, 0)
|
||||
|
||||
area_connections: typing.Dict[int, int]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import functools
|
||||
from typing import Dict, Set, List
|
||||
|
||||
# EN Locale Creature Name to rough depth in meters found at
|
||||
@@ -54,21 +55,27 @@ all_creatures: Dict[str, int] = {
|
||||
"Sea Emperor Juvenile": 1700,
|
||||
}
|
||||
|
||||
# be nice and make these require Stasis Rifle
|
||||
aggressive: Set[str] = {
|
||||
"Cave Crawler", # is very easy without Stasis Rifle, but included for consistency
|
||||
"Crashfish",
|
||||
"Biter",
|
||||
"Bleeder",
|
||||
"Blighter",
|
||||
"Blood Crawler",
|
||||
"Mesmer",
|
||||
"Reaper Leviathan",
|
||||
"Crabsquid",
|
||||
"Warper",
|
||||
"Crabsnake",
|
||||
"Ampeel",
|
||||
"Stalker",
|
||||
"Sand Shark",
|
||||
"Boneshark",
|
||||
"Lava Lizard",
|
||||
"Sea Dragon Leviathan",
|
||||
"River Prowler",
|
||||
"Ghost Leviathan Juvenile",
|
||||
"Ghost Leviathan"
|
||||
}
|
||||
|
||||
containment: Set[str] = { # creatures that have to be raised from eggs
|
||||
@@ -94,6 +101,25 @@ creature_locations: Dict[str, int] = {
|
||||
creature + suffix: creature_id for creature_id, creature in enumerate(all_creatures, start=34000)
|
||||
}
|
||||
|
||||
all_creatures_presorted: List[str] = sorted(all_creatures)
|
||||
all_creatures_presorted_without_containment = [name for name in all_creatures_presorted if name not in containment]
|
||||
|
||||
class Definitions:
|
||||
"""Only compute lists if needed and then cache them."""
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted(self) -> List[str]:
|
||||
return sorted(all_creatures)
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted_without_containment(self) -> List[str]:
|
||||
return [name for name in self.all_creatures_presorted if name not in containment]
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted_without_stasis(self) -> List[str]:
|
||||
return [name for name in self.all_creatures_presorted if name not in aggressive or name in hatchable]
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted_without_aggressive(self) -> List[str]:
|
||||
return [name for name in self.all_creatures_presorted if name not in aggressive]
|
||||
|
||||
# only singleton needed
|
||||
Definitions: Definitions = Definitions()
|
||||
|
||||
@@ -15,7 +15,12 @@ class LocationDict(TypedDict, total=False):
|
||||
need_propulsion_cannon: bool
|
||||
|
||||
|
||||
events: List[str] = ["Neptune Launch", "Disable Quarantine", "Full Infection", "Repair Aurora Drive"]
|
||||
events: List[str] = [
|
||||
"Neptune Launch",
|
||||
"Disable Quarantine",
|
||||
"Full Infection",
|
||||
"Repair Aurora Drive",
|
||||
]
|
||||
|
||||
location_table: Dict[int, LocationDict] = {
|
||||
33000: {'can_slip_through': False,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import typing
|
||||
|
||||
from Options import Choice, Range, DeathLink
|
||||
from .Creatures import all_creatures
|
||||
from .Creatures import all_creatures, Definitions
|
||||
|
||||
|
||||
class ItemPool(Choice):
|
||||
@@ -46,14 +46,27 @@ class AggressiveScanLogic(Choice):
|
||||
Containment: Removes Stasis Rifle as expected solution and expects Alien Containment instead.
|
||||
Either: Creatures may be expected to be scanned via Stasis Rifle or Containment, whichever is found first.
|
||||
None: Aggressive Creatures are assumed to not need any tools to scan.
|
||||
Removed: No Creatures needing Stasis or Containment will be in the pool at all.
|
||||
|
||||
Note: Containment, Either and None adds Cuddlefish as an option for scans.
|
||||
Note: Stasis, Either and None adds unhatchable aggressive species, such as Warper.
|
||||
Note: This is purely a logic expectation, and does not affect gameplay, only placement."""
|
||||
display_name = "Aggressive Creature Scan Logic"
|
||||
option_stasis = 0
|
||||
option_containment = 1
|
||||
option_either = 2
|
||||
option_none = 3
|
||||
option_removed = 4
|
||||
|
||||
def get_pool(self) -> typing.List[str]:
|
||||
if self == self.option_removed:
|
||||
return Definitions.all_creatures_presorted_without_aggressive
|
||||
elif self == self.option_stasis:
|
||||
return Definitions.all_creatures_presorted_without_containment
|
||||
elif self == self.option_containment:
|
||||
return Definitions.all_creatures_presorted_without_stasis
|
||||
else:
|
||||
return Definitions.all_creatures_presorted
|
||||
|
||||
|
||||
class SubnauticaDeathLink(DeathLink):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import TYPE_CHECKING, Dict, Callable
|
||||
from typing import TYPE_CHECKING, Dict, Callable, Optional
|
||||
|
||||
from worlds.generic.Rules import set_rule, add_rule
|
||||
from .Locations import location_table, LocationDict
|
||||
from .Creatures import all_creatures, aggressive, suffix
|
||||
from .Creatures import all_creatures, aggressive, suffix, hatchable, containment
|
||||
from .Options import AggressiveScanLogic
|
||||
import math
|
||||
|
||||
@@ -258,6 +258,15 @@ def set_creature_rule(world, player: int, creature_name: str) -> "Location":
|
||||
return location
|
||||
|
||||
|
||||
def get_aggression_rule(option: AggressiveScanLogic, creature_name: str) -> \
|
||||
Optional[Callable[["CollectionState", int], bool]]:
|
||||
"""Get logic rule for a creature scan location."""
|
||||
if creature_name not in hatchable and option != option.option_none: # can only be done via stasis
|
||||
return has_stasis_rifle
|
||||
# otherwise allow option preference
|
||||
return aggression_rules.get(option.value, None)
|
||||
|
||||
|
||||
aggression_rules: Dict[int, Callable[["CollectionState", int], bool]] = {
|
||||
AggressiveScanLogic.option_stasis: has_stasis_rifle,
|
||||
AggressiveScanLogic.option_containment: has_containment,
|
||||
@@ -274,14 +283,21 @@ def set_rules(subnautica_world: "SubnauticaWorld"):
|
||||
set_location_rule(world, player, loc)
|
||||
|
||||
if subnautica_world.creatures_to_scan:
|
||||
aggressive_rule = aggression_rules.get(world.creature_scan_logic[player], None)
|
||||
option = world.creature_scan_logic[player]
|
||||
|
||||
for creature_name in subnautica_world.creatures_to_scan:
|
||||
location = set_creature_rule(world, player, creature_name)
|
||||
if creature_name in aggressive and aggressive_rule:
|
||||
add_rule(location, lambda state: aggressive_rule(state, player))
|
||||
if creature_name in containment: # there is no other way, hard-required containment
|
||||
add_rule(location, lambda state: has_containment(state, player))
|
||||
elif creature_name in aggressive:
|
||||
rule = get_aggression_rule(option, creature_name)
|
||||
if rule:
|
||||
add_rule(location,
|
||||
lambda state, loc_rule=get_aggression_rule(option, creature_name): loc_rule(state, player))
|
||||
|
||||
# Victory locations
|
||||
set_rule(world.get_location("Neptune Launch", player), lambda state:
|
||||
set_rule(world.get_location("Neptune Launch", player),
|
||||
lambda state:
|
||||
get_max_depth(state, player) >= 1444 and
|
||||
has_mobile_vehicle_bay(state, player) and
|
||||
state.has("Neptune Launch Platform", player) and
|
||||
|
||||
@@ -52,14 +52,15 @@ class SubnauticaWorld(World):
|
||||
self.create_item("Seaglide Fragment"),
|
||||
self.create_item("Seaglide Fragment")
|
||||
]
|
||||
if self.world.creature_scan_logic[self.player] == Options.AggressiveScanLogic.option_stasis:
|
||||
valid_creatures = Creatures.all_creatures_presorted_without_containment
|
||||
self.world.creature_scans[self.player].value = min(len(
|
||||
Creatures.all_creatures_presorted_without_containment),
|
||||
self.world.creature_scans[self.player].value)
|
||||
else:
|
||||
valid_creatures = Creatures.all_creatures_presorted
|
||||
self.creatures_to_scan = self.world.random.sample(valid_creatures,
|
||||
scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player]
|
||||
creature_pool = scan_option.get_pool()
|
||||
|
||||
self.world.creature_scans[self.player].value = min(
|
||||
len(creature_pool),
|
||||
self.world.creature_scans[self.player].value
|
||||
)
|
||||
|
||||
self.creatures_to_scan = self.world.random.sample(creature_pool,
|
||||
self.world.creature_scans[self.player].value)
|
||||
|
||||
def create_regions(self):
|
||||
|
||||
Reference in New Issue
Block a user