Skip to content

Commit

Permalink
Merge branch 'main' into lingo-imports
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholassaylor authored Dec 1, 2024
2 parents e287788 + f26cda0 commit dae2c0b
Show file tree
Hide file tree
Showing 56 changed files with 701 additions and 380 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
9 changes: 5 additions & 4 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
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
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: 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
28 changes: 14 additions & 14 deletions worlds/dark_souls_3/Locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,29 +764,29 @@ def __init__(
DS3LocationData("US -> RS", None),

# Yoel/Yuria of Londor
DS3LocationData("FS: Soul Arrow - Yoel/Yuria", "Soul Arrow",
DS3LocationData("FS: Soul Arrow - Yoel/Yuria shop", "Soul Arrow",
static='99,0:-1:50000,110000,70000116:', missable=True, npc=True,
shop=True),
DS3LocationData("FS: Heavy Soul Arrow - Yoel/Yuria", "Heavy Soul Arrow",
DS3LocationData("FS: Heavy Soul Arrow - Yoel/Yuria shop", "Heavy Soul Arrow",
static='99,0:-1:50000,110000,70000116:',
missable=True, npc=True, shop=True),
DS3LocationData("FS: Magic Weapon - Yoel/Yuria", "Magic Weapon",
DS3LocationData("FS: Magic Weapon - Yoel/Yuria shop", "Magic Weapon",
static='99,0:-1:50000,110000,70000116:', missable=True, npc=True,
shop=True),
DS3LocationData("FS: Magic Shield - Yoel/Yuria", "Magic Shield",
DS3LocationData("FS: Magic Shield - Yoel/Yuria shop", "Magic Shield",
static='99,0:-1:50000,110000,70000116:', missable=True, npc=True,
shop=True),
DS3LocationData("FS: Soul Greatsword - Yoel/Yuria", "Soul Greatsword",
DS3LocationData("FS: Soul Greatsword - Yoel/Yuria shop", "Soul Greatsword",
static='99,0:-1:50000,110000,70000450,70000475:', missable=True,
npc=True, shop=True),
DS3LocationData("FS: Dark Hand - Yoel/Yuria", "Dark Hand", missable=True, npc=True),
DS3LocationData("FS: Untrue White Ring - Yoel/Yuria", "Untrue White Ring", missable=True,
DS3LocationData("FS: Dark Hand - Yuria shop", "Dark Hand", missable=True, npc=True),
DS3LocationData("FS: Untrue White Ring - Yuria shop", "Untrue White Ring", missable=True,
npc=True),
DS3LocationData("FS: Untrue Dark Ring - Yoel/Yuria", "Untrue Dark Ring", missable=True,
DS3LocationData("FS: Untrue Dark Ring - Yuria shop", "Untrue Dark Ring", missable=True,
npc=True),
DS3LocationData("FS: Londor Braille Divine Tome - Yoel/Yuria", "Londor Braille Divine Tome",
DS3LocationData("FS: Londor Braille Divine Tome - Yuria shop", "Londor Braille Divine Tome",
static='99,0:-1:40000,110000,70000116:', missable=True, npc=True),
DS3LocationData("FS: Darkdrift - Yoel/Yuria", "Darkdrift", missable=True, drop=True,
DS3LocationData("FS: Darkdrift - kill Yuria", "Darkdrift", missable=True, drop=True,
npc=True), # kill her or kill Soul of Cinder

# Cornyx of the Great Swamp
Expand Down Expand Up @@ -2476,13 +2476,13 @@ def __init__(
"Firelink Leggings", boss=True, shop=True),

# Yuria (quest, after Soul of Cinder)
DS3LocationData("FS: Billed Mask - Yuria after killing KFF boss", "Billed Mask",
DS3LocationData("FS: Billed Mask - shop after killing Yuria", "Billed Mask",
missable=True, npc=True),
DS3LocationData("FS: Black Dress - Yuria after killing KFF boss", "Black Dress",
DS3LocationData("FS: Black Dress - shop after killing Yuria", "Black Dress",
missable=True, npc=True),
DS3LocationData("FS: Black Gauntlets - Yuria after killing KFF boss", "Black Gauntlets",
DS3LocationData("FS: Black Gauntlets - shop after killing Yuria", "Black Gauntlets",
missable=True, npc=True),
DS3LocationData("FS: Black Leggings - Yuria after killing KFF boss", "Black Leggings",
DS3LocationData("FS: Black Leggings - shop after killing Yuria", "Black Leggings",
missable=True, npc=True),
],

Expand Down
6 changes: 5 additions & 1 deletion worlds/dark_souls_3/detailed_location_descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@
table += f"<tr><td>{html.escape(name)}</td><td>{html.escape(description)}</td></tr>\n"
table += "</table>\n"

with open(os.path.join(os.path.dirname(__file__), 'docs/locations_en.md'), 'r+') as f:
with open(
os.path.join(os.path.dirname(__file__), 'docs/locations_en.md'),
'r+',
encoding='utf-8'
) as f:
original = f.read()
start_flag = "<!-- begin location table -->\n"
start = original.index(start_flag) + len(start_flag)
Expand Down
Loading

0 comments on commit dae2c0b

Please sign in to comment.