Skip to content

Commit

Permalink
Merge branch 'main' into TS-Update
Browse files Browse the repository at this point in the history
  • Loading branch information
Ehseezed authored Aug 17, 2024
2 parents 89b98b6 + f5218fa commit f8134d6
Show file tree
Hide file tree
Showing 20 changed files with 132 additions and 81 deletions.
3 changes: 1 addition & 2 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,7 @@ def location_condition(location: Location) -> bool:

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

def all_done() -> bool:
"""Check if all access rules are fulfilled"""
Expand Down
2 changes: 1 addition & 1 deletion Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
multiworld.early_items[player][item_name] = max(0, early-count)
remaining_count = count-early
if remaining_count > 0:
local_early = multiworld.early_local_items[player].get(item_name, 0)
local_early = multiworld.local_early_items[player].get(item_name, 0)
if local_early:
multiworld.early_items[player][item_name] = max(0, local_early - remaining_count)
del local_early
Expand Down
18 changes: 9 additions & 9 deletions MultiServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ def collect_player(ctx: Context, team: int, slot: int, is_group: bool = False):
collect_player(ctx, team, group, True)


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


Expand Down Expand Up @@ -1350,10 +1350,10 @@ def _cmd_collect(self) -> bool:
def _cmd_remaining(self) -> bool:
"""List remaining items in your game, but not their location or recipient"""
if self.ctx.remaining_mode == "enabled":
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
for item_id in remaining_item_ids))
rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot)
if rest_locations:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id]
for slot, item_id in rest_locations))
else:
self.output("No remaining items found.")
return True
Expand All @@ -1363,10 +1363,10 @@ def _cmd_remaining(self) -> bool:
return False
else: # is goal
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
for item_id in remaining_item_ids))
rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot)
if rest_locations:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id]
for slot, item_id in rest_locations))
else:
self.output("No remaining items found.")
return True
Expand Down
8 changes: 4 additions & 4 deletions NetUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,12 @@ def get_missing(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]
location_id not in checked]

def get_remaining(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]], team: int, slot: int
) -> typing.List[int]:
) -> typing.List[typing.Tuple[int, int]]:
checked = state[team, slot]
player_locations = self[slot]
return sorted([player_locations[location_id][0] for
location_id in player_locations if
location_id not in checked])
return sorted([(player_locations[location_id][1], player_locations[location_id][0]) for
location_id in player_locations if
location_id not in checked])


if typing.TYPE_CHECKING: # type-check with pure python implementation until we have a typing stub
Expand Down
8 changes: 4 additions & 4 deletions _speedups.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,15 @@ cdef class LocationStore:
entry in self.entries[start:start + count] if
entry.location not in checked]

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


@cython.auto_pickle(False)
Expand Down
20 changes: 12 additions & 8 deletions docs/network protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -702,14 +702,18 @@ GameData is a **dict** but contains these keys and values. It's broken out into
| checksum | str | A checksum hash of this game's data. |

### Tags
Tags are represented as a list of strings, the common Client tags follow:

| Name | Notes |
|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AP | Signifies that this client is a reference client, its usefulness is mostly in debugging to compare client behaviours more easily. |
| DeathLink | Client participates in the DeathLink mechanic, therefore will send and receive DeathLink bounce packets |
| Tracker | Tells the server that this client will not send locations and is actually a Tracker. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. |
| TextOnly | Tells the server that this client will not send locations and is intended for chat. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. |
Tags are represented as a list of strings, the common client tags follow:

| Name | Notes |
|-----------|--------------------------------------------------------------------------------------------------------------------------------------|
| AP | Signifies that this client is a reference client, its usefulness is mostly in debugging to compare client behaviours more easily. |
| DeathLink | Client participates in the DeathLink mechanic, therefore will send and receive DeathLink bounce packets. |
| HintGame | Indicates the client is a hint game, made to send hints instead of locations. Special join/leave message,¹ `game` is optional.² |
| Tracker | Indicates the client is a tracker, made to track instead of sending locations. Special join/leave message,¹ `game` is optional.² |
| TextOnly | Indicates the client is a basic client, made to chat instead of sending locations. Special join/leave message,¹ `game` is optional.² |

¹: When connecting or disconnecting, the chat message shows e.g. "tracking".\
²: Allows `game` to be empty or null in [Connect](#connect). Game and version validation will then be skipped.

### DeathLink
A special kind of Bounce packet that can be supported by any AP game. It targets the tag "DeathLink" and carries the following data:
Expand Down
8 changes: 3 additions & 5 deletions test/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,11 @@ def test_all_state_can_reach_everything(self):
if not (self.run_default_tests and self.constructed):
return
with self.subTest("Game", game=self.game, seed=self.multiworld.seed):
excluded = self.multiworld.worlds[self.player].options.exclude_locations.value
state = self.multiworld.get_all_state(False)
for location in self.multiworld.get_locations():
if location.name not in excluded:
with self.subTest("Location should be reached", location=location.name):
reachable = location.can_reach(state)
self.assertTrue(reachable, f"{location.name} unreachable")
with self.subTest("Location should be reached", location=location.name):
reachable = location.can_reach(state)
self.assertTrue(reachable, f"{location.name} unreachable")
with self.subTest("Beatable"):
self.multiworld.state = state
self.assertBeatable(True)
Expand Down
6 changes: 2 additions & 4 deletions test/general/test_reachability.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,10 @@ def test_default_all_state_can_reach_everything(self):
unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set())
with self.subTest("Game", game=game_name):
multiworld = setup_solo_multiworld(world_type)
excluded = multiworld.worlds[1].options.exclude_locations.value
state = multiworld.get_all_state(False)
for location in multiworld.get_locations():
if location.name not in excluded:
with self.subTest("Location should be reached", location=location.name):
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")
with self.subTest("Location should be reached", location=location.name):
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")

for region in multiworld.get_regions():
if region.name in unreachable_regions:
Expand Down
6 changes: 3 additions & 3 deletions test/netutils/test_location_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ def test_get_missing(self) -> None:

def test_get_remaining(self) -> None:
self.assertEqual(self.store.get_remaining(full_state, 0, 1), [])
self.assertEqual(self.store.get_remaining(one_state, 0, 1), [13, 21])
self.assertEqual(self.store.get_remaining(empty_state, 0, 1), [13, 21, 22])
self.assertEqual(self.store.get_remaining(empty_state, 0, 3), [99])
self.assertEqual(self.store.get_remaining(one_state, 0, 1), [(1, 13), (2, 21)])
self.assertEqual(self.store.get_remaining(empty_state, 0, 1), [(1, 13), (2, 21), (2, 22)])
self.assertEqual(self.store.get_remaining(empty_state, 0, 3), [(4, 99)])

def test_location_set_intersection(self) -> None:
locations = {10, 11, 12}
Expand Down
30 changes: 16 additions & 14 deletions worlds/lingo/data/LL1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2049,6 +2049,18 @@
panel: I
- room: Elements Area
panel: A
Eight Door (Outside The Initiated):
id: Red Blue Purple Room Area Doors/Door_a_strands2
item_name: Outside The Initiated - Eight Door
item_group: Achievement Room Entrances
skip_location: True
panels:
- room: The Incomparable
panel: I (Seven)
- room: Courtyard
panel: I
- room: Elements Area
panel: A
panel_doors:
Giant Sevens:
item_name: Giant Seven Panels
Expand All @@ -2067,8 +2079,8 @@
room: The Incomparable
door: Eight Door
Outside The Initiated:
room: Outside The Initiated
door: Eight Door
room: The Incomparable
door: Eight Door (Outside The Initiated)
paintings:
- id: eight_painting2
orientation: north
Expand Down Expand Up @@ -3310,7 +3322,8 @@
room: Art Gallery
door: Exit
Eight Alcove:
door: Eight Door
room: The Incomparable
door: Eight Door (Outside The Initiated)
The Optimistic: True
panels:
SEVEN (1):
Expand Down Expand Up @@ -3463,17 +3476,6 @@
panel: GREEN
- room: Outside The Agreeable
panel: PURPLE
Eight Door:
id: Red Blue Purple Room Area Doors/Door_a_strands2
item_group: Achievement Room Entrances
skip_location: True
panels:
- room: The Incomparable
panel: I (Seven)
- room: Courtyard
panel: I
- room: Elements Area
panel: A
panel_doors:
UNCOVER:
panels:
Expand Down
Binary file modified worlds/lingo/data/generated.dat
Binary file not shown.
4 changes: 2 additions & 2 deletions worlds/lingo/data/ids.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,8 @@ doors:
Eight Door:
item: 444475
location: 445219
Eight Door (Outside The Initiated):
item: 444578
Orange Tower:
Second Floor:
item: 444476
Expand Down Expand Up @@ -1242,8 +1244,6 @@ doors:
Entrance:
item: 444516
location: 445237
Eight Door:
item: 444578
The Traveled:
Color Hallways Entrance:
item: 444517
Expand Down
6 changes: 3 additions & 3 deletions worlds/osrs/Names.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ class ItemNames(str, Enum):
Progressive_Armor = "Progressive Armor"
Progressive_Weapons = "Progressive Weapons"
Progressive_Tools = "Progressive Tools"
Progressive_Range_Armor = "Progressive Range Armor"
Progressive_Range_Weapon = "Progressive Range Weapon"
Progressive_Magic = "Progressive Magic Spell"
Progressive_Range_Armor = "Progressive Ranged Armor"
Progressive_Range_Weapon = "Progressive Ranged Weapons"
Progressive_Magic = "Progressive Magic"
Lobsters = "10 Lobsters"
Swordfish = "5 Swordfish"
Energy_Potions = "10 Energy Potions"
Expand Down
4 changes: 3 additions & 1 deletion worlds/osrs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,9 @@ def create_region(self, name: str) -> "Region":
return region

def create_item(self, item_name: str) -> "Item":
item = [item for item in item_rows if item.name == item_name][0]
items = [item for item in item_rows if item.name == item_name]
assert len(items) > 0, f"No matching item found for name {item_name} for player {self.player_name}"
item = items[0]
index = item_rows.index(item)
return OSRSItem(item.name, item.progression, self.base_id + index, self.player)

Expand Down
20 changes: 15 additions & 5 deletions worlds/pokemon_emerald/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", patch: Pokemon


def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
FORTREE_MOVE_TUTOR_INDEX = 24

if easter_egg[0] == 2:
for i in range(30):
patch.write_token(
Expand All @@ -840,18 +842,26 @@ def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", patch: PokemonEmer
# Always set Fortree move tutor to Dig
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gTutorMoves"] + (24 * 2),
data.rom_addresses["gTutorMoves"] + (FORTREE_MOVE_TUTOR_INDEX * 2),
struct.pack("<H", data.constants["MOVE_DIG"])
)

# Modify compatibility
if world.options.tm_tutor_compatibility.value != -1:
for species in data.species.values():
compatibility = bool_array_to_int([
world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value
for _ in range(32)
])

# Make sure Dig tutor has reasonable (>=50%) compatibility
if world.options.tm_tutor_compatibility.value < 50:
compatibility &= ~(1 << FORTREE_MOVE_TUTOR_INDEX)
if world.random.random() < 0.5:
compatibility |= 1 << FORTREE_MOVE_TUTOR_INDEX

patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["sTutorLearnsets"] + (species.species_id * 4),
struct.pack("<I", bool_array_to_int([
world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value
for _ in range(32)
]))
struct.pack("<I", compatibility)
)
20 changes: 11 additions & 9 deletions worlds/tloz/ItemPool.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from collections import Counter

from BaseClasses import ItemClassification
from .Locations import level_locations, all_level_locations, standard_level_locations, shop_locations
from .Options import TriforceLocations, StartingPosition
Expand Down Expand Up @@ -58,11 +60,11 @@
"Small Key": 2,
"Five Rupees": 2
}
basic_pool = {
item: overworld_items.get(item, 0) + shop_items.get(item, 0)
+ major_dungeon_items.get(item, 0) + map_compass_replacements.get(item, 0)
for item in set(overworld_items) | set(shop_items) | set(major_dungeon_items) | set(map_compass_replacements)
}
basic_pool = Counter()
basic_pool.update(overworld_items)
basic_pool.update(shop_items)
basic_pool.update(major_dungeon_items)
basic_pool.update(map_compass_replacements)

starting_weapons = ["Sword", "White Sword", "Magical Sword", "Magical Rod", "Red Candle"]
guaranteed_shop_items = ["Small Key", "Bomb", "Water of Life (Red)", "Arrow"]
Expand Down Expand Up @@ -135,10 +137,10 @@ def get_pool_core(world):
# Finish Pool
final_pool = basic_pool
if world.options.ExpandedPool:
final_pool = {
item: basic_pool.get(item, 0) + minor_items.get(item, 0) + take_any_items.get(item, 0)
for item in set(basic_pool) | set(minor_items) | set(take_any_items)
}
final_pool = Counter()
final_pool.update(basic_pool)
final_pool.update(minor_items)
final_pool.update(take_any_items)
final_pool["Five Rupees"] -= 1
for item in final_pool.keys():
for i in range(0, final_pool[item]):
Expand Down
9 changes: 8 additions & 1 deletion worlds/tunic/er_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def destination_scene(self) -> str: # the vanilla connection
destination="Town Basement", tag="_beach"),
Portal(name="Changing Room Entrance", region="Overworld",
destination="Changing Room", tag="_"),
Portal(name="Cube Cave Entrance", region="Overworld",
Portal(name="Cube Cave Entrance", region="Cube Cave Entrance Region",
destination="CubeRoom", tag="_"),
Portal(name="Stairs from Overworld to Mountain", region="Upper Overworld",
destination="Mountain", tag="_"),
Expand Down Expand Up @@ -562,6 +562,7 @@ class DeadEnd(IntEnum):
"Overworld Temple Door": RegionInfo("Overworld Redux"), # the small space betweeen the door and the portal
"Overworld Town Portal": RegionInfo("Overworld Redux"), # being able to go to or come from the portal
"Overworld Spawn Portal": RegionInfo("Overworld Redux"), # being able to go to or come from the portal
"Cube Cave Entrance Region": RegionInfo("Overworld Redux"), # other side of the bomb wall
"Stick House": RegionInfo("Sword Cave", dead_end=DeadEnd.all_cats),
"Windmill": RegionInfo("Windmill"),
"Old House Back": RegionInfo("Overworld Interiors"), # part with the hc door
Expand Down Expand Up @@ -775,6 +776,8 @@ class DeadEnd(IntEnum):
[["UR"]],
"Overworld Old House Door":
[],
"Cube Cave Entrance Region":
[],
},
"East Overworld": {
"Above Ruined Passage":
Expand Down Expand Up @@ -920,6 +923,10 @@ class DeadEnd(IntEnum):
"Overworld":
[],
},
"Cube Cave Entrance Region": {
"Overworld":
[],
},
"Old House Front": {
"Old House Back":
[],
Expand Down
Loading

0 comments on commit f8134d6

Please sign in to comment.