mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-13 19:13:48 -07:00
Compare commits
1 Commits
revert-404
...
core_negat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffecc62155 |
@@ -454,7 +454,6 @@ class CommonContext:
|
||||
if kwargs:
|
||||
payload.update(kwargs)
|
||||
await self.send_msgs([payload])
|
||||
await self.send_msgs([{"cmd": "Get", "keys": ["_read_race_mode"]}])
|
||||
|
||||
async def console_input(self) -> str:
|
||||
if self.ui:
|
||||
|
||||
1
Main.py
1
Main.py
@@ -338,7 +338,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
"seed_name": multiworld.seed_name,
|
||||
"spheres": spheres,
|
||||
"datapackage": data_package,
|
||||
"race_mode": int(multiworld.is_race),
|
||||
}
|
||||
AutoWorld.call_all(multiworld, "modify_multidata", multidata)
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import math
|
||||
import operator
|
||||
import pickle
|
||||
import random
|
||||
import shlex
|
||||
import threading
|
||||
import time
|
||||
import typing
|
||||
@@ -428,8 +427,6 @@ class Context:
|
||||
use_embedded_server_options: bool):
|
||||
|
||||
self.read_data = {}
|
||||
# there might be a better place to put this.
|
||||
self.read_data["race_mode"] = lambda: decoded_obj.get("race_mode", 0)
|
||||
mdata_ver = decoded_obj["minimum_versions"]["server"]
|
||||
if mdata_ver > version_tuple:
|
||||
raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver},"
|
||||
@@ -1153,7 +1150,7 @@ class CommandProcessor(metaclass=CommandMeta):
|
||||
if not raw:
|
||||
return
|
||||
try:
|
||||
command = shlex.split(raw, comments=False)
|
||||
command = raw.split()
|
||||
basecommand = command[0]
|
||||
if basecommand[0] == self.marker:
|
||||
method = self.commands.get(basecommand[1:].lower(), None)
|
||||
|
||||
22
Options.py
22
Options.py
@@ -705,10 +705,26 @@ class Range(NumericOption):
|
||||
f"random-range-high-<min>-<max>, or random-range-<min>-<max>.")
|
||||
|
||||
@classmethod
|
||||
def custom_range(cls, text) -> Range:
|
||||
textsplit = text.split("-")
|
||||
def custom_range(cls, text: str) -> Range:
|
||||
numeric_text: str = text[len("random-range-"):]
|
||||
if numeric_text.startswith(("low", "middle", "high")):
|
||||
numeric_text = numeric_text.split("-", 1)[1]
|
||||
textsplit = numeric_text.split("-")
|
||||
if len(textsplit) > 2: # looks like there may be minus signs, which will now be empty string from the split
|
||||
new_textsplit: typing.List[str] = []
|
||||
next_negative: bool = False
|
||||
for element in textsplit:
|
||||
if not element: # empty string -> next element gets a minus sign in front
|
||||
next_negative = True
|
||||
elif next_negative:
|
||||
new_textsplit.append("-"+element)
|
||||
next_negative = False
|
||||
else:
|
||||
new_textsplit.append(element)
|
||||
textsplit = new_textsplit
|
||||
del next_negative, new_textsplit
|
||||
try:
|
||||
random_range = [int(textsplit[len(textsplit) - 2]), int(textsplit[len(textsplit) - 1])]
|
||||
random_range = [int(textsplit[0]), int(textsplit[1])]
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid random range {text} for option {cls.__name__}")
|
||||
random_range.sort()
|
||||
|
||||
@@ -81,7 +81,6 @@ def start_generation(options: Dict[str, Union[dict, str]], meta: Dict[str, Any])
|
||||
elif len(gen_options) > app.config["MAX_ROLL"]:
|
||||
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. "
|
||||
f"If you have a larger group, please generate it yourself and upload it.")
|
||||
return redirect(url_for(request.endpoint, **(request.view_args or {})))
|
||||
elif len(gen_options) >= app.config["JOB_THRESHOLD"]:
|
||||
gen = Generation(
|
||||
options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}),
|
||||
|
||||
@@ -395,7 +395,6 @@ Some special keys exist with specific return data, all of them have the prefix `
|
||||
| item_name_groups_{game_name} | dict\[str, list\[str\]\] | item_name_groups belonging to the requested game. |
|
||||
| location_name_groups_{game_name} | dict\[str, list\[str\]\] | location_name_groups belonging to the requested game. |
|
||||
| client_status_{team}_{slot} | [ClientStatus](#ClientStatus) | The current game status of the requested player. |
|
||||
| race_mode | int | 0 if race mode is disabled, and 1 if it's enabled. |
|
||||
|
||||
### Set
|
||||
Used to write data to the server's data storage, that data can then be shared across worlds or just saved for later. Values for keys in the data storage can be retrieved with a [Get](#Get) package, or monitored with a [SetNotify](#SetNotify) package.
|
||||
|
||||
3
kvui.py
3
kvui.py
@@ -243,9 +243,6 @@ class ServerLabel(HovererableLabel):
|
||||
f"\nYou currently have {ctx.hint_points} points."
|
||||
elif ctx.hint_cost == 0:
|
||||
text += "\n!hint is free to use."
|
||||
if ctx.stored_data and "_read_race_mode" in ctx.stored_data:
|
||||
text += "\nRace mode is enabled." \
|
||||
if ctx.stored_data["_read_race_mode"] else "\nRace mode is disabled."
|
||||
else:
|
||||
text += f"\nYou are not authenticated yet."
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class TestTwoPlayerMulti(MultiworldTestBase):
|
||||
for world in self.multiworld.worlds.values():
|
||||
world.options.accessibility.value = Accessibility.option_full
|
||||
self.assertSteps(gen_steps)
|
||||
with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
with self.subTest("filling multiworld", seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
from flask import url_for
|
||||
|
||||
from . import TestBase
|
||||
|
||||
|
||||
class TestGenerate(TestBase):
|
||||
def test_valid_yaml(self) -> None:
|
||||
"""
|
||||
Verify that posting a valid yaml will start generating a game.
|
||||
"""
|
||||
with self.app.app_context(), self.app.test_request_context():
|
||||
yaml_data = """
|
||||
name: Player1
|
||||
game: Archipelago
|
||||
Archipelago: {}
|
||||
"""
|
||||
response = self.client.post(url_for("generate"),
|
||||
data={"file": (BytesIO(yaml_data.encode("utf-8")), "test.yaml")},
|
||||
follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue("/seed/" in response.request.path or
|
||||
"/wait/" in response.request.path,
|
||||
f"Response did not properly redirect ({response.request.path})")
|
||||
|
||||
def test_empty_zip(self) -> None:
|
||||
"""
|
||||
Verify that posting an empty zip will give an error.
|
||||
"""
|
||||
with self.app.app_context(), self.app.test_request_context():
|
||||
zip_data = BytesIO()
|
||||
zipfile.ZipFile(zip_data, "w").close()
|
||||
zip_data.seek(0)
|
||||
self.assertGreater(len(zip_data.read()), 0)
|
||||
zip_data.seek(0)
|
||||
response = self.client.post(url_for("generate"),
|
||||
data={"file": (zip_data, "test.zip")},
|
||||
follow_redirects=True)
|
||||
self.assertIn("user-message", response.text,
|
||||
"Request did not call flash()")
|
||||
self.assertIn("not find any valid files", response.text,
|
||||
"Response shows unexpected error")
|
||||
self.assertIn("generate-game-form", response.text,
|
||||
"Response did not get user back to the form")
|
||||
|
||||
def test_too_many_players(self) -> None:
|
||||
"""
|
||||
Verify that posting too many players will give an error.
|
||||
"""
|
||||
max_roll = self.app.config["MAX_ROLL"]
|
||||
# validate that max roll has a sensible value, otherwise we probably changed how it works
|
||||
self.assertIsInstance(max_roll, int)
|
||||
self.assertGreater(max_roll, 1)
|
||||
self.assertLess(max_roll, 100)
|
||||
# create a yaml with max_roll+1 players and watch it fail
|
||||
with self.app.app_context(), self.app.test_request_context():
|
||||
yaml_data = "---\n".join([
|
||||
f"name: Player{n}\n"
|
||||
"game: Archipelago\n"
|
||||
"Archipelago: {}\n"
|
||||
for n in range(1, max_roll + 2)
|
||||
])
|
||||
response = self.client.post(url_for("generate"),
|
||||
data={"file": (BytesIO(yaml_data.encode("utf-8")), "test.yaml")},
|
||||
follow_redirects=True)
|
||||
self.assertIn("user-message", response.text,
|
||||
"Request did not call flash()")
|
||||
self.assertIn("limited to", response.text,
|
||||
"Response shows unexpected error")
|
||||
self.assertIn("generate-game-form", response.text,
|
||||
"Response did not get user back to the form")
|
||||
@@ -325,7 +325,7 @@ class KDL3World(World):
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
try:
|
||||
patch = KDL3ProcedurePatch(player=self.player, player_name=self.player_name)
|
||||
patch = KDL3ProcedurePatch()
|
||||
patch_rom(self, patch)
|
||||
|
||||
self.rom_name = patch.name
|
||||
|
||||
@@ -101,18 +101,7 @@ class KH2World(World):
|
||||
if ability in self.goofy_ability_dict and self.goofy_ability_dict[ability] >= 1:
|
||||
self.goofy_ability_dict[ability] -= 1
|
||||
|
||||
slot_data = self.options.as_dict(
|
||||
"Goal",
|
||||
"FinalXemnas",
|
||||
"LuckyEmblemsRequired",
|
||||
"BountyRequired",
|
||||
"FightLogic",
|
||||
"FinalFormLogic",
|
||||
"AutoFormLogic",
|
||||
"LevelDepth",
|
||||
"DonaldGoofyStatsanity",
|
||||
"CorSkipToggle"
|
||||
)
|
||||
slot_data = self.options.as_dict("Goal", "FinalXemnas", "LuckyEmblemsRequired", "BountyRequired")
|
||||
slot_data.update({
|
||||
"hitlist": [], # remove this after next update
|
||||
"PoptrackerVersionCheck": 4.3,
|
||||
|
||||
@@ -81,23 +81,23 @@ talking:
|
||||
|
||||
; Give powder
|
||||
ld a, [$DB4C]
|
||||
cp $20
|
||||
cp $10
|
||||
jr nc, doNotGivePowder
|
||||
ld a, $20
|
||||
ld a, $10
|
||||
ld [$DB4C], a
|
||||
doNotGivePowder:
|
||||
|
||||
ld a, [$DB4D]
|
||||
cp $30
|
||||
cp $10
|
||||
jr nc, doNotGiveBombs
|
||||
ld a, $30
|
||||
ld a, $10
|
||||
ld [$DB4D], a
|
||||
doNotGiveBombs:
|
||||
|
||||
ld a, [$DB45]
|
||||
cp $30
|
||||
cp $10
|
||||
jr nc, doNotGiveArrows
|
||||
ld a, $30
|
||||
ld a, $10
|
||||
ld [$DB45], a
|
||||
doNotGiveArrows:
|
||||
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fixed a rare issue where receiving a wonder trade could partially corrupt the save data, preventing the player from
|
||||
receiving new items.
|
||||
- Fixed the client spamming the "goal complete" status update to the server instead of sending it once.
|
||||
- Fixed a logic issue where the "Mauville City - Coin Case from Lady in House" location only required a Harbor Mail if
|
||||
the player randomized NPC gifts.
|
||||
- The Dig tutor has its compatibility percentage raised to 50% if the player's TM/tutor compatibility is set lower.
|
||||
|
||||
@@ -545,12 +545,11 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
if trade_is_sent == 0 and wonder_trade_pokemon_data[19] == 2:
|
||||
# Game has wonder trade data to send. Send it to data storage, remove it from the game's memory,
|
||||
# and mark that the game is waiting on receiving a trade
|
||||
success = await bizhawk.guarded_write(ctx.bizhawk_ctx, [
|
||||
Utils.async_start(self.wonder_trade_send(ctx, pokemon_data_to_json(wonder_trade_pokemon_data)))
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [
|
||||
(sb1_address + 0x377C, bytes(0x50), "System Bus"),
|
||||
(sb1_address + 0x37CC, [1], "System Bus"),
|
||||
], [guards["SAVE BLOCK 1"]])
|
||||
if success:
|
||||
Utils.async_start(self.wonder_trade_send(ctx, pokemon_data_to_json(wonder_trade_pokemon_data)))
|
||||
])
|
||||
elif trade_is_sent != 0 and wonder_trade_pokemon_data[19] != 2:
|
||||
# Game is waiting on receiving a trade.
|
||||
if self.queued_received_trade is not None:
|
||||
|
||||
@@ -14,7 +14,7 @@ from .data import static_items as static_witness_items
|
||||
from .data import static_locations as static_witness_locations
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.item_definition_classes import DoorItemDefinition, ItemData
|
||||
from .data.utils import cast_not_none, get_audio_logs
|
||||
from .data.utils import get_audio_logs
|
||||
from .hints import CompactHintData, create_all_hints, make_compact_hint_data, make_laser_hints
|
||||
from .locations import WitnessPlayerLocations
|
||||
from .options import TheWitnessOptions, witness_option_groups
|
||||
@@ -55,7 +55,7 @@ class WitnessWorld(World):
|
||||
|
||||
item_name_to_id = {
|
||||
# ITEM_DATA doesn't have any event items in it
|
||||
name: cast_not_none(data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
name: cast(int, data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
}
|
||||
location_name_to_id = static_witness_locations.ALL_LOCATIONS_TO_ID
|
||||
item_name_groups = static_witness_items.ITEM_GROUPS
|
||||
@@ -336,7 +336,7 @@ class WitnessWorld(World):
|
||||
for item_name, hint in laser_hints.items():
|
||||
item_def = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name])
|
||||
self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player)
|
||||
already_hinted_locations.add(cast_not_none(hint.location))
|
||||
already_hinted_locations.add(cast(Location, hint.location))
|
||||
|
||||
# Audio Log Hints
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from math import floor
|
||||
from pkgutil import get_data
|
||||
from random import Random
|
||||
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
||||
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Set, Tuple, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -13,11 +13,6 @@ T = TypeVar("T")
|
||||
WitnessRule = FrozenSet[FrozenSet[str]]
|
||||
|
||||
|
||||
def cast_not_none(value: Optional[T]) -> T:
|
||||
assert value is not None
|
||||
return value
|
||||
|
||||
|
||||
def weighted_sample(world_random: Random, population: List[T], weights: List[float], k: int) -> List[T]:
|
||||
positions = range(len(population))
|
||||
indices: List[int] = []
|
||||
|
||||
@@ -15,7 +15,7 @@ from .data.item_definition_classes import (
|
||||
ProgressiveItemDefinition,
|
||||
WeightedItemDefinition,
|
||||
)
|
||||
from .data.utils import build_weighted_int_list, cast_not_none
|
||||
from .data.utils import build_weighted_int_list
|
||||
from .locations import WitnessPlayerLocations
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
@@ -200,7 +200,7 @@ class WitnessPlayerItems:
|
||||
"""
|
||||
return [
|
||||
# data.ap_code is guaranteed for a symbol definition
|
||||
cast_not_none(data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
cast(int, data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
if name not in self.item_data.keys() and data.definition.category is ItemCategory.SYMBOL
|
||||
]
|
||||
|
||||
@@ -211,8 +211,8 @@ class WitnessPlayerItems:
|
||||
if isinstance(item.definition, ProgressiveItemDefinition):
|
||||
# Note: we need to reference the static table here rather than the player-specific one because the child
|
||||
# items were removed from the pool when we pruned out all progression items not in the options.
|
||||
output[cast_not_none(item.ap_code)] = [cast_not_none(static_witness_items.ITEM_DATA[child_item].ap_code)
|
||||
for child_item in item.definition.child_item_names]
|
||||
output[cast(int, item.ap_code)] = [cast(int, static_witness_items.ITEM_DATA[child_item].ap_code)
|
||||
for child_item in item.definition.child_item_names]
|
||||
return output
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, ClassVar, Dict, Iterable, List, Mapping, Union
|
||||
from typing import Any, ClassVar, Dict, Iterable, List, Mapping, Union, cast
|
||||
|
||||
from BaseClasses import CollectionState, Entrance, Item, Location, Region
|
||||
|
||||
@@ -7,7 +7,6 @@ from test.general import gen_steps, setup_multiworld
|
||||
from test.multiworld.test_multiworlds import MultiworldTestBase
|
||||
|
||||
from .. import WitnessWorld
|
||||
from ..data.utils import cast_not_none
|
||||
|
||||
|
||||
class WitnessTestBase(WorldTestBase):
|
||||
@@ -33,7 +32,7 @@ class WitnessTestBase(WorldTestBase):
|
||||
event_items = [item for item in self.multiworld.get_items() if item.name == item_name]
|
||||
self.assertTrue(event_items, f"Event item {item_name} does not exist.")
|
||||
|
||||
event_locations = [cast_not_none(event_item.location) for event_item in event_items]
|
||||
event_locations = [cast(Location, event_item.location) for event_item in event_items]
|
||||
|
||||
# Checking for an access dependency on an event item requires a bit of extra work,
|
||||
# as state.remove forces a sweep, which will pick up the event item again right after we tried to remove it.
|
||||
|
||||
Reference in New Issue
Block a user