Skip to content

Commit 0ffd433

Browse files
authored
Merge pull request ArchipelagoMW#2 from ArchipelagoMW/main
Update to original branch
2 parents 9d460cd + 2b1802c commit 0ffd433

File tree

109 files changed

+12723
-3556
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+12723
-3556
lines changed

BaseClasses.py

+14-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import collections
4-
import copy
54
import itertools
65
import functools
76
import logging
@@ -617,8 +616,7 @@ def location_condition(location: Location) -> bool:
617616

618617
def location_relevant(location: Location) -> bool:
619618
"""Determine if this location is relevant to sweep."""
620-
return location.progress_type != LocationProgressType.EXCLUDED \
621-
and (location.player in players["full"] or location.advancement)
619+
return location.player in players["full"] or location.advancement
622620

623621
def all_done() -> bool:
624622
"""Check if all access rules are fulfilled"""
@@ -719,14 +717,14 @@ def update_reachable_regions(self, player: int):
719717

720718
def copy(self) -> CollectionState:
721719
ret = CollectionState(self.multiworld)
722-
ret.prog_items = copy.deepcopy(self.prog_items)
723-
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
724-
self.reachable_regions}
725-
ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in
726-
self.blocked_connections}
727-
ret.events = copy.copy(self.events)
728-
ret.path = copy.copy(self.path)
729-
ret.locations_checked = copy.copy(self.locations_checked)
720+
ret.prog_items = {player: counter.copy() for player, counter in self.prog_items.items()}
721+
ret.reachable_regions = {player: region_set.copy() for player, region_set in
722+
self.reachable_regions.items()}
723+
ret.blocked_connections = {player: entrance_set.copy() for player, entrance_set in
724+
self.blocked_connections.items()}
725+
ret.events = self.events.copy()
726+
ret.path = self.path.copy()
727+
ret.locations_checked = self.locations_checked.copy()
730728
for function in self.additional_copy_functions:
731729
ret = function(self, ret)
732730
return ret
@@ -864,19 +862,15 @@ def count_group_unique(self, item_name_group: str, player: int) -> int:
864862
)
865863

866864
# Item related
867-
def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool:
865+
def collect(self, item: Item, prevent_sweep: bool = False, location: Optional[Location] = None) -> bool:
868866
if location:
869867
self.locations_checked.add(location)
870868

871869
changed = self.multiworld.worlds[item.player].collect(self, item)
872870

873-
if not changed and event:
874-
self.prog_items[item.player][item.name] += 1
875-
changed = True
876-
877871
self.stale[item.player] = True
878872

879-
if changed and not event:
873+
if changed and not prevent_sweep:
880874
self.sweep_for_events()
881875

882876
return changed
@@ -1128,9 +1122,9 @@ def can_fill(self, state: CollectionState, item: Item, check_access=True) -> boo
11281122
and (not check_access or self.can_reach(state))))
11291123

11301124
def can_reach(self, state: CollectionState) -> bool:
1131-
# self.access_rule computes faster on average, so placing it first for faster abort
1125+
# Region.can_reach is just a cache lookup, so placing it first for faster abort on average
11321126
assert self.parent_region, "Can't reach location without region"
1133-
return self.access_rule(state) and self.parent_region.can_reach(state)
1127+
return self.parent_region.can_reach(state) and self.access_rule(state)
11341128

11351129
def place_locked_item(self, item: Item):
11361130
if self.item:
@@ -1428,7 +1422,7 @@ def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, st
14281422
# Maybe move the big bomb over to the Event system instead?
14291423
if any(exit_path == 'Pyramid Fairy' for path in self.paths.values()
14301424
for (_, exit_path) in path):
1431-
if multiworld.mode[player] != 'inverted':
1425+
if multiworld.worlds[player].options.mode != 'inverted':
14321426
self.paths[str(multiworld.get_region('Big Bomb Shop', player))] = \
14331427
get_path(state, multiworld.get_region('Big Bomb Shop', player))
14341428
else:

CommonClient.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def update_game(self, game: str, name_to_id_lookup_table: typing.Dict[str, int])
252252
starting_reconnect_delay: int = 5
253253
current_reconnect_delay: int = starting_reconnect_delay
254254
command_processor: typing.Type[CommandProcessor] = ClientCommandProcessor
255-
ui = None
255+
ui: typing.Optional["kvui.GameManager"] = None
256256
ui_task: typing.Optional["asyncio.Task[None]"] = None
257257
input_task: typing.Optional["asyncio.Task[None]"] = None
258258
keep_alive_task: typing.Optional["asyncio.Task[None]"] = None

Fill.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212

1313

1414
class FillError(RuntimeError):
15-
pass
15+
def __init__(self, *args: typing.Union[str, typing.Any], **kwargs) -> None:
16+
if "multiworld" in kwargs and isinstance(args[0], str):
17+
placements = (args[0] + f"\nAll Placements:\n" +
18+
f"{[(loc, loc.item) for loc in kwargs['multiworld'].get_filled_locations()]}")
19+
args = (placements, *args[1:])
20+
super().__init__(*args)
1621

1722

1823
def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
@@ -212,7 +217,7 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
212217
f"Unfilled locations:\n"
213218
f"{', '.join(str(location) for location in locations)}\n"
214219
f"Already placed {len(placements)}:\n"
215-
f"{', '.join(str(place) for place in placements)}")
220+
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)
216221

217222
item_pool.extend(unplaced_items)
218223

@@ -299,7 +304,7 @@ def remaining_fill(multiworld: MultiWorld,
299304
f"Unfilled locations:\n"
300305
f"{', '.join(str(location) for location in locations)}\n"
301306
f"Already placed {len(placements)}:\n"
302-
f"{', '.join(str(place) for place in placements)}")
307+
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)
303308

304309
itempool.extend(unplaced_items)
305310

@@ -506,7 +511,8 @@ def mark_for_locking(location: Location):
506511
if progitempool:
507512
raise FillError(
508513
f"Not enough locations for progression items. "
509-
f"There are {len(progitempool)} more progression items than there are available locations."
514+
f"There are {len(progitempool)} more progression items than there are available locations.",
515+
multiworld=multiworld,
510516
)
511517
accessibility_corrections(multiworld, multiworld.state, defaultlocations)
512518

@@ -523,7 +529,8 @@ def mark_for_locking(location: Location):
523529
if excludedlocations:
524530
raise FillError(
525531
f"Not enough filler items for excluded locations. "
526-
f"There are {len(excludedlocations)} more excluded locations than filler or trap items."
532+
f"There are {len(excludedlocations)} more excluded locations than filler or trap items.",
533+
multiworld=multiworld,
527534
)
528535

529536
restitempool = filleritempool + usefulitempool
@@ -589,7 +596,7 @@ def flood_items(multiworld: MultiWorld) -> None:
589596
if candidate_item_to_place is not None:
590597
item_to_place = candidate_item_to_place
591598
else:
592-
raise FillError('No more progress items left to place.')
599+
raise FillError('No more progress items left to place.', multiworld=multiworld)
593600

594601
# find item to replace with progress item
595602
location_list = multiworld.get_reachable_locations()

Launcher.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None
266266
if file and component:
267267
run_component(component, file)
268268
else:
269-
logging.warning(f"unable to identify component for {filename}")
269+
logging.warning(f"unable to identify component for {file}")
270270

271271
def _stop(self, *largs):
272272
# ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm.

Main.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
import worlds
1313
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
14-
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
14+
from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, distribute_planned, \
15+
flood_items
1516
from Options import StartInventoryPool
1617
from Utils import __version__, output_path, version_tuple, get_settings
1718
from settings import get_settings
@@ -100,7 +101,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
100101
multiworld.early_items[player][item_name] = max(0, early-count)
101102
remaining_count = count-early
102103
if remaining_count > 0:
103-
local_early = multiworld.early_local_items[player].get(item_name, 0)
104+
local_early = multiworld.local_early_items[player].get(item_name, 0)
104105
if local_early:
105106
multiworld.early_items[player][item_name] = max(0, local_early - remaining_count)
106107
del local_early
@@ -151,6 +152,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
151152
# Because some worlds don't actually create items during create_items this has to be as late as possible.
152153
if any(getattr(multiworld.worlds[player].options, "start_inventory_from_pool", None) for player in multiworld.player_ids):
153154
new_items: List[Item] = []
155+
old_items: List[Item] = []
154156
depletion_pool: Dict[int, Dict[str, int]] = {
155157
player: getattr(multiworld.worlds[player].options,
156158
"start_inventory_from_pool",
@@ -169,20 +171,24 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
169171
depletion_pool[item.player][item.name] -= 1
170172
# quick abort if we have found all items
171173
if not target:
172-
new_items.extend(multiworld.itempool[i+1:])
174+
old_items.extend(multiworld.itempool[i+1:])
173175
break
174176
else:
175-
new_items.append(item)
177+
old_items.append(item)
176178

177179
# leftovers?
178180
if target:
179181
for player, remaining_items in depletion_pool.items():
180182
remaining_items = {name: count for name, count in remaining_items.items() if count}
181183
if remaining_items:
182-
raise Exception(f"{multiworld.get_player_name(player)}"
184+
logger.warning(f"{multiworld.get_player_name(player)}"
183185
f" is trying to remove items from their pool that don't exist: {remaining_items}")
184-
assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change."
185-
multiworld.itempool[:] = new_items
186+
# find all filler we generated for the current player and remove until it matches
187+
removables = [item for item in new_items if item.player == player]
188+
for _ in range(sum(remaining_items.values())):
189+
new_items.remove(removables.pop())
190+
assert len(multiworld.itempool) == len(new_items + old_items), "Item Pool amounts should not change."
191+
multiworld.itempool[:] = new_items + old_items
186192

187193
multiworld.link_items()
188194

@@ -341,7 +347,7 @@ def precollect_hint(location):
341347
output_file_futures.append(pool.submit(write_multidata))
342348
if not check_accessibility_task.result():
343349
if not multiworld.can_beat_game():
344-
raise Exception("Game appears as unbeatable. Aborting.")
350+
raise FillError("Game appears as unbeatable. Aborting.", multiworld=multiworld)
345351
else:
346352
logger.warning("Location Accessibility requirements not fulfilled.")
347353

MultiServer.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ def collect_player(ctx: Context, team: int, slot: int, is_group: bool = False):
991991
collect_player(ctx, team, group, True)
992992

993993

994-
def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[int]:
994+
def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[typing.Tuple[int, int]]:
995995
return ctx.locations.get_remaining(ctx.location_checks, team, slot)
996996

997997

@@ -1350,10 +1350,10 @@ def _cmd_collect(self) -> bool:
13501350
def _cmd_remaining(self) -> bool:
13511351
"""List remaining items in your game, but not their location or recipient"""
13521352
if self.ctx.remaining_mode == "enabled":
1353-
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
1354-
if remaining_item_ids:
1355-
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
1356-
for item_id in remaining_item_ids))
1353+
rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot)
1354+
if rest_locations:
1355+
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id]
1356+
for slot, item_id in rest_locations))
13571357
else:
13581358
self.output("No remaining items found.")
13591359
return True
@@ -1363,10 +1363,10 @@ def _cmd_remaining(self) -> bool:
13631363
return False
13641364
else: # is goal
13651365
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
1366-
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
1367-
if remaining_item_ids:
1368-
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
1369-
for item_id in remaining_item_ids))
1366+
rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot)
1367+
if rest_locations:
1368+
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id]
1369+
for slot, item_id in rest_locations))
13701370
else:
13711371
self.output("No remaining items found.")
13721372
return True

NetUtils.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -397,12 +397,12 @@ def get_missing(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]
397397
location_id not in checked]
398398

399399
def get_remaining(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]], team: int, slot: int
400-
) -> typing.List[int]:
400+
) -> typing.List[typing.Tuple[int, int]]:
401401
checked = state[team, slot]
402402
player_locations = self[slot]
403-
return sorted([player_locations[location_id][0] for
404-
location_id in player_locations if
405-
location_id not in checked])
403+
return sorted([(player_locations[location_id][1], player_locations[location_id][0]) for
404+
location_id in player_locations if
405+
location_id not in checked])
406406

407407

408408
if typing.TYPE_CHECKING: # type-check with pure python implementation until we have a typing stub

Options.py

+1-28
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,7 @@ def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str,
12361236
:param option_names: names of the options to return
12371237
:param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab`
12381238
"""
1239+
assert option_names, "options.as_dict() was used without any option names."
12391240
option_results = {}
12401241
for option_name in option_names:
12411242
if option_name in type(self).type_hints:
@@ -1517,31 +1518,3 @@ def yaml_dump_scalar(scalar) -> str:
15171518

15181519
with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f:
15191520
f.write(res)
1520-
1521-
1522-
if __name__ == "__main__":
1523-
1524-
from worlds.alttp.Options import Logic
1525-
import argparse
1526-
1527-
map_shuffle = Toggle
1528-
compass_shuffle = Toggle
1529-
key_shuffle = Toggle
1530-
big_key_shuffle = Toggle
1531-
hints = Toggle
1532-
test = argparse.Namespace()
1533-
test.logic = Logic.from_text("no_logic")
1534-
test.map_shuffle = map_shuffle.from_text("ON")
1535-
test.hints = hints.from_text('OFF')
1536-
try:
1537-
test.logic = Logic.from_text("overworld_glitches_typo")
1538-
except KeyError as e:
1539-
print(e)
1540-
try:
1541-
test.logic_owg = Logic.from_text("owg")
1542-
except KeyError as e:
1543-
print(e)
1544-
if test.map_shuffle:
1545-
print("map_shuffle is on")
1546-
print(f"Hints are {bool(test.hints)}")
1547-
print(test)

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Currently, the following games are supported:
7272
* Aquaria
7373
* Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006
7474
* A Hat in Time
75+
* Old School Runescape
7576

7677
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
7778
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

WebHostLib/templates/weightedOptions/macros.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
id="{{ option_name }}-{{ key }}"
139139
name="{{ option_name }}||{{ key }}"
140140
value="1"
141-
checked="{{ "checked" if key in option.default else "" }}"
141+
{{ "checked" if key in option.default }}
142142
/>
143143
<label for="{{ option_name }}-{{ key }}">
144144
{{ key }}

_speedups.pyx

+4-4
Original file line numberDiff line numberDiff line change
@@ -287,15 +287,15 @@ cdef class LocationStore:
287287
entry in self.entries[start:start + count] if
288288
entry.location not in checked]
289289

290-
def get_remaining(self, state: State, team: int, slot: int) -> List[int]:
290+
def get_remaining(self, state: State, team: int, slot: int) -> List[Tuple[int, int]]:
291291
cdef LocationEntry* entry
292292
cdef ap_player_t sender = slot
293293
cdef size_t start = self.sender_index[sender].start
294294
cdef size_t count = self.sender_index[sender].count
295295
cdef set checked = state[team, slot]
296-
return sorted([entry.item for
297-
entry in self.entries[start:start+count] if
298-
entry.location not in checked])
296+
return sorted([(entry.receiver, entry.item) for
297+
entry in self.entries[start:start+count] if
298+
entry.location not in checked])
299299

300300

301301
@cython.auto_pickle(False)

docs/CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@
115115
# Ocarina of Time
116116
/worlds/oot/ @espeon65536
117117

118+
# Old School Runescape
119+
/worlds/osrs @digiholic
120+
118121
# Overcooked! 2
119122
/worlds/overcooked2/ @toasterparty
120123

0 commit comments

Comments
 (0)