Skip to content

Commit

Permalink
Merge branch 'main' into core_worlds_files_reorg
Browse files Browse the repository at this point in the history
  • Loading branch information
Berserker66 authored Dec 8, 2024
2 parents ef036d6 + c9625e1 commit 9f48255
Show file tree
Hide file tree
Showing 98 changed files with 2,805 additions and 721 deletions.
9 changes: 8 additions & 1 deletion Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,14 @@ def main(args=None) -> Tuple[argparse.Namespace, int]:
os.path.join(args.player_files_path, fname) not in {args.meta_file_path, args.weights_file_path}:
path = os.path.join(args.player_files_path, fname)
try:
weights_cache[fname] = read_weights_yamls(path)
weights_for_file = []
for doc_idx, yaml in enumerate(read_weights_yamls(path)):
if yaml is None:
logging.warning(f"Ignoring empty yaml document #{doc_idx + 1} in {fname}")
else:
weights_for_file.append(yaml)
weights_cache[fname] = tuple(weights_for_file)

except Exception as e:
raise ValueError(f"File {fname} is invalid. Please fix your yaml.") from e

Expand Down
58 changes: 18 additions & 40 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,61 +126,39 @@ def handle_uri(path: str, launch_args: Tuple[str, ...]) -> None:
elif component.display_name == "Text Client":
text_client_component = component

from kvui import App, Button, BoxLayout, Label, Clock, Window
if client_component is None:
run_component(text_client_component, *launch_args)
return

class Popup(App):
timer_label: Label
remaining_time: Optional[int]
from kvui import App, Button, BoxLayout, Label, Window

class Popup(App):
def __init__(self):
self.title = "Connect to Multiworld"
self.icon = r"data/icon.png"
super().__init__()

def build(self):
layout = BoxLayout(orientation="vertical")
layout.add_widget(Label(text="Select client to open and connect with."))
button_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.4))

if client_component is None:
self.remaining_time = 7
label_text = (f"A game client able to parse URIs was not detected for {game}.\n"
f"Launching Text Client in 7 seconds...")
self.timer_label = Label(text=label_text)
layout.add_widget(self.timer_label)
Clock.schedule_interval(self.update_label, 1)
else:
layout.add_widget(Label(text="Select client to open and connect with."))
button_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.4))

text_client_button = Button(
text=text_client_component.display_name,
on_release=lambda *args: run_component(text_client_component, *launch_args)
)
button_row.add_widget(text_client_button)
text_client_button = Button(
text=text_client_component.display_name,
on_release=lambda *args: run_component(text_client_component, *launch_args)
)
button_row.add_widget(text_client_button)

game_client_button = Button(
text=client_component.display_name,
on_release=lambda *args: run_component(client_component, *launch_args)
)
button_row.add_widget(game_client_button)
game_client_button = Button(
text=client_component.display_name,
on_release=lambda *args: run_component(client_component, *launch_args)
)
button_row.add_widget(game_client_button)

layout.add_widget(button_row)
layout.add_widget(button_row)

return layout

def update_label(self, dt):
if self.remaining_time > 1:
# countdown the timer and string replace the number
self.remaining_time -= 1
self.timer_label.text = self.timer_label.text.replace(
str(self.remaining_time + 1), str(self.remaining_time)
)
else:
# our timer is finished so launch text client and close down
run_component(text_client_component, *launch_args)
Clock.unschedule(self.update_label)
App.get_running_app().stop()
Window.close()

def _stop(self, *largs):
# see run_gui Launcher _stop comment for details
self.root_window.close()
Expand Down
14 changes: 9 additions & 5 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No

def write_multidata():
import NetUtils
from NetUtils import HintStatus
slot_data = {}
client_versions = {}
games = {}
Expand All @@ -266,10 +267,10 @@ def write_multidata():
for slot in multiworld.player_ids:
slot_data[slot] = multiworld.worlds[slot].fill_slot_data()

def precollect_hint(location):
def precollect_hint(location: Location, auto_status: HintStatus):
entrance = er_hint_data.get(location.player, {}).get(location.address, "")
hint = NetUtils.Hint(location.item.player, location.player, location.address,
location.item.code, False, entrance, location.item.flags, False)
location.item.code, False, entrance, location.item.flags, auto_status)
precollected_hints[location.player].add(hint)
if location.item.player not in multiworld.groups:
precollected_hints[location.item.player].add(hint)
Expand All @@ -288,13 +289,16 @@ def precollect_hint(location):
f"{locations_data[location.player][location.address]}")
locations_data[location.player][location.address] = \
location.item.code, location.item.player, location.item.flags
auto_status = HintStatus.HINT_AVOID if location.item.trap else HintStatus.HINT_PRIORITY
if location.name in multiworld.worlds[location.player].options.start_location_hints:
precollect_hint(location)
if not location.item.trap: # Unspecified status for location hints, except traps
auto_status = HintStatus.HINT_UNSPECIFIED
precollect_hint(location, auto_status)
elif location.item.name in multiworld.worlds[location.item.player].options.start_hints:
precollect_hint(location)
precollect_hint(location, auto_status)
elif any([location.item.name in multiworld.worlds[player].options.start_hints
for player in multiworld.groups.get(location.item.player, {}).get("players", [])]):
precollect_hint(location)
precollect_hint(location, auto_status)

# embedded data package
data_package = {
Expand Down
11 changes: 10 additions & 1 deletion MultiServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1929,6 +1929,11 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
[{'cmd': 'InvalidPacket', "type": "arguments",
"text": 'UpdateHint: Invalid Status', "original_cmd": cmd}])
return
if status == HintStatus.HINT_FOUND:
await ctx.send_msgs(client,
[{'cmd': 'InvalidPacket', "type": "arguments",
"text": 'UpdateHint: Cannot manually update status to "HINT_FOUND"', "original_cmd": cmd}])
return
new_hint = new_hint.re_prioritize(ctx, status)
if hint == new_hint:
return
Expand Down Expand Up @@ -2378,6 +2383,8 @@ def parse_args() -> argparse.Namespace:
parser.add_argument('--cert_key', help="Path to SSL Certificate Key file")
parser.add_argument('--loglevel', default=defaults["loglevel"],
choices=['debug', 'info', 'warning', 'error', 'critical'])
parser.add_argument('--logtime', help="Add timestamps to STDOUT",
default=defaults["logtime"], action='store_true')
parser.add_argument('--location_check_points', default=defaults["location_check_points"], type=int)
parser.add_argument('--hint_cost', default=defaults["hint_cost"], type=int)
parser.add_argument('--disable_item_cheat', default=defaults["disable_item_cheat"], action='store_true')
Expand Down Expand Up @@ -2458,7 +2465,9 @@ def load_server_cert(path: str, cert_key: typing.Optional[str]) -> "ssl.SSLConte


async def main(args: argparse.Namespace):
Utils.init_logging("Server", loglevel=args.loglevel.lower())
Utils.init_logging(name="Server",
loglevel=args.loglevel.lower(),
add_timestamp=args.logtime)

ctx = Context(args.host, args.port, args.server_password, args.password, args.location_check_points,
args.hint_cost, not args.disable_item_cheat, args.release_mode, args.collect_mode,
Expand Down
2 changes: 2 additions & 0 deletions Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,8 @@ class ItemDict(OptionDict):
verify_item_name = True

def __init__(self, value: typing.Dict[str, int]):
if any(item_count is None for item_count in value.values()):
raise Exception("Items must have counts associated with them. Please provide positive integer values in the format \"item\": count .")
if any(item_count < 1 for item_count in value.values()):
raise Exception("Cannot have non-positive item counts.")
super(ItemDict, self).__init__(value)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Currently, the following games are supported:
* Mega Man 2
* Yacht Dice
* Faxanadu
* Saving Princess

For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
Expand Down
11 changes: 6 additions & 5 deletions Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,9 @@ def get_text_after(text: str, start: str) -> str:
loglevel_mapping = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}


def init_logging(name: str, loglevel: typing.Union[str, int] = logging.INFO, write_mode: str = "w",
log_format: str = "[%(name)s at %(asctime)s]: %(message)s",
exception_logger: typing.Optional[str] = None):
def init_logging(name: str, loglevel: typing.Union[str, int] = logging.INFO,
write_mode: str = "w", log_format: str = "[%(name)s at %(asctime)s]: %(message)s",
add_timestamp: bool = False, exception_logger: typing.Optional[str] = None):
import datetime
loglevel: int = loglevel_mapping.get(loglevel, loglevel)
log_folder = user_path("logs")
Expand Down Expand Up @@ -521,7 +521,8 @@ def filter(self, record: logging.LogRecord) -> bool:
formatter = logging.Formatter(fmt='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.addFilter(Filter("NoFile", lambda record: not getattr(record, "NoStream", False)))
stream_handler.setFormatter(formatter)
if add_timestamp:
stream_handler.setFormatter(formatter)
root_logger.addHandler(stream_handler)

# Relay unhandled exceptions to logger.
Expand Down Expand Up @@ -556,7 +557,7 @@ def _cleanup():
import platform
logging.info(
f"Archipelago ({__version__}) logging initialized"
f" on {platform.platform()}"
f" on {platform.platform()} process {os.getpid()}"
f" running Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
f"{' (frozen)' if is_frozen() else ''}"
)
Expand Down
5 changes: 3 additions & 2 deletions WebHostLib/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ def roll_options(options: Dict[str, Union[dict, str]],
plando_options=plando_options)
else:
for i, yaml_data in enumerate(yaml_datas):
rolled_results[f"{filename}/{i + 1}"] = roll_settings(yaml_data,
plando_options=plando_options)
if yaml_data is not None:
rolled_results[f"{filename}/{i + 1}"] = roll_settings(yaml_data,
plando_options=plando_options)
except Exception as e:
if e.__cause__:
results[filename] = f"Failed to generate options in {filename}: {e} - {e.__cause__}"
Expand Down
3 changes: 3 additions & 0 deletions docs/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@
# Risk of Rain 2
/worlds/ror2/ @kindasneaki

# Saving Princess
/worlds/saving_princess/ @LeonarthCG

# Shivers
/worlds/shivers/ @GodlFire

Expand Down
17 changes: 11 additions & 6 deletions docs/network protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,20 +351,24 @@ Sent to the server to update the status of a Hint. The client must be the 'recei
| ---- | ---- | ----- |
| player | int | The ID of the player whose location is being hinted for. |
| location | int | The ID of the location to update the hint for. If no hint exists for this location, the packet is ignored. |
| status | [HintStatus](#HintStatus) | Optional. If included, sets the status of the hint to this status. |
| status | [HintStatus](#HintStatus) | Optional. If included, sets the status of the hint to this status. Cannot set `HINT_FOUND`, or change the status from `HINT_FOUND`. |

#### HintStatus
An enumeration containing the possible hint states.

```python
import enum
class HintStatus(enum.IntEnum):
HINT_FOUND = 0
HINT_UNSPECIFIED = 1
HINT_NO_PRIORITY = 10
HINT_AVOID = 20
HINT_PRIORITY = 30
HINT_FOUND = 0 # The location has been collected. Status cannot be changed once found.
HINT_UNSPECIFIED = 1 # The receiving player has not specified any status
HINT_NO_PRIORITY = 10 # The receiving player has specified that the item is unneeded
HINT_AVOID = 20 # The receiving player has specified that the item is detrimental
HINT_PRIORITY = 30 # The receiving player has specified that the item is needed
```
- Hints for items with `ItemClassification.trap` default to `HINT_AVOID`.
- Hints created with `LocationScouts`, `!hint_location`, or similar (hinting a location) default to `HINT_UNSPECIFIED`.
- Hints created with `!hint` or similar (hinting an item for yourself) default to `HINT_PRIORITY`.
- Once a hint is collected, its' status is updated to `HINT_FOUND` automatically, and can no longer be changed.

### StatusUpdate
Sent to the server to update on the sender's status. Examples include readiness or goal completion. (Example: defeated Ganon in A Link to the Past)
Expand Down Expand Up @@ -668,6 +672,7 @@ class Hint(typing.NamedTuple):
found: bool
entrance: str = ""
item_flags: int = 0
status: HintStatus = HintStatus.HINT_UNSPECIFIED
```

### Data Package Contents
Expand Down
1 change: 1 addition & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ class LogNetwork(IntEnum):
savefile: Optional[str] = None
disable_save: bool = False
loglevel: str = "info"
logtime: bool = False
server_password: Optional[ServerPassword] = None
disable_item_cheat: Union[DisableItemCheat, bool] = False
location_check_points: LocationCheckPoints = LocationCheckPoints(1)
Expand Down
2 changes: 0 additions & 2 deletions worlds/adventure/Locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ def __init__(self, region, name, location_id, world_positions: [WorldPosition] =
self.local_item: int = None

def get_random_position(self, random):
x: int = None
y: int = None
if self.world_positions is None or len(self.world_positions) == 0:
if self.room_id is None:
return None
Expand Down
6 changes: 3 additions & 3 deletions worlds/adventure/Regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,9 @@ def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player
multiworld.regions.append(credits_room_far_side)

dragon_slay_check = options.dragon_slay_check.value
priority_locations = determine_priority_locations(multiworld, dragon_slay_check)
priority_locations = determine_priority_locations()

for name, location_data in location_table.items():
require_sword = False
if location_data.region == "Varies":
if location_data.name == "Slay Yorgle":
if not dragon_slay_check:
Expand Down Expand Up @@ -154,6 +153,7 @@ def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player


# Placeholder for adding sets of priority locations at generation, possibly as an option in the future
def determine_priority_locations(world: MultiWorld, dragon_slay_check: bool) -> {}:
# def determine_priority_locations(multiworld: MultiWorld, dragon_slay_check: bool) -> {}:
def determine_priority_locations() -> {}:
priority_locations = {}
return priority_locations
2 changes: 0 additions & 2 deletions worlds/adventure/Rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ class AdventureDeltaPatch(APPatch, metaclass=AutoPatchRegister):

# locations: [], autocollect: [], seed_name: bytes,
def __init__(self, *args: Any, **kwargs: Any) -> None:
patch_only = True
if "autocollect" in kwargs:
patch_only = False
self.foreign_items: [AdventureForeignItemInfo] = [AdventureForeignItemInfo(loc.short_location_id, loc.room_id, loc.room_x, loc.room_y)
for loc in kwargs["locations"]]

Expand Down
2 changes: 1 addition & 1 deletion worlds/adventure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def generate_output(self, output_directory: str) -> None:
# end of ordered Main.py calls

def create_item(self, name: str) -> Item:
item_data: ItemData = item_table.get(name)
item_data: ItemData = item_table[name]
return AdventureItem(name, item_data.classification, item_data.id, self.player)

def create_event(self, name: str, classification: ItemClassification) -> Item:
Expand Down
8 changes: 4 additions & 4 deletions worlds/dark_souls_3/Bosses.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ class DS3BossInfo:
}),
DS3BossInfo("Lords of Cinder", 4100800, locations = {
"KFF: Soul of the Lords",
"FS: Billed Mask - Yuria after killing KFF boss",
"FS: Black Dress - Yuria after killing KFF boss",
"FS: Black Gauntlets - Yuria after killing KFF boss",
"FS: Black Leggings - Yuria after killing KFF boss"
"FS: Billed Mask - shop after killing Yuria",
"FS: Black Dress - shop after killing Yuria",
"FS: Black Gauntlets - shop after killing Yuria",
"FS: Black Leggings - shop after killing Yuria"
}),
]

Expand Down
Loading

0 comments on commit 9f48255

Please sign in to comment.