diff --git a/UndertaleClient.py b/UndertaleClient.py index 94ed15d6..e0ec642b 100644 --- a/UndertaleClient.py +++ b/UndertaleClient.py @@ -1,5 +1,6 @@ from __future__ import annotations import os +import sys import asyncio import typing import bsdiff4 @@ -11,7 +12,7 @@ from NetUtils import NetworkItem, ClientStatus from worlds import undertale from MultiServer import mark_raw from CommonClient import CommonContext, server_loop, \ - gui_enabled, ClientCommandProcessor, get_base_parser + gui_enabled, ClientCommandProcessor, logger, get_base_parser from Utils import async_start @@ -105,6 +106,8 @@ class UndertaleContext(CommonContext): self.tem_armor = False self.completed_count = 0 self.completed_routes = {"pacifist": 0, "genocide": 0, "neutral": 0} + # self.save_game_folder: files go in this path to pass data between us and the actual game + self.save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE") def patch_game(self): with open(os.getcwd() + "/Undertale/data.win", "rb") as f: @@ -233,9 +236,11 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict): f.close() filename = f"check.spot" with open(os.path.join(ctx.save_game_folder, filename), "a") as f: - for ss in ctx.checked_locations: + for ss in set(args["checked_locations"]): f.write(str(ss-12000)+"\n") f.close() + message = [{"cmd": "LocationChecks", "locations": [79067]}] + await ctx.send_msgs(message) elif cmd == "LocationInfo": for l in args["locations"]: locationid = l.location @@ -359,7 +364,7 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict): if "checked_locations" in args: filename = f"check.spot" with open(os.path.join(ctx.save_game_folder, filename), "a") as f: - for ss in ctx.checked_locations: + for ss in set(args["checked_locations"]): f.write(str(ss-12000)+"\n") f.close() @@ -430,7 +435,7 @@ async def game_watcher(ctx: UndertaleContext): lines = f.readlines() for l in lines: if ctx.server_locations.__contains__(int(l)+12000): - sending = sending + [int(l)+12000] + sending = sending + [int(l.rstrip('\n'))+12000] await ctx.send_msgs([{"cmd": "LocationScouts", "locations": sending, "create_as_hint": int(2)}]) finally: @@ -441,7 +446,7 @@ async def game_watcher(ctx: UndertaleContext): with open(root+"/"+file, "r") as f: lines = f.readlines() for l in lines: - sending = sending+[(int(l))+12000] + sending = sending+[(int(l.rstrip('\n')))+12000] message = [{"cmd": "LocationChecks", "locations": sending}] await ctx.send_msgs(message) finally: diff --git a/worlds/undertale/Locations.py b/worlds/undertale/Locations.py index 2f7de445..c000a46d 100644 --- a/worlds/undertale/Locations.py +++ b/worlds/undertale/Locations.py @@ -77,6 +77,7 @@ advancement_table = { "True Lab Plot": AdvData(79063, "Hotland"), "Left New Home Key": AdvData(79064, "New Home"), "Right New Home Key": AdvData(79065, "New Home"), + "Starting Key": AdvData(79067, "Hub"), "LOVE 2": AdvData(79902, "???"), "LOVE 3": AdvData(79903, "???"), "LOVE 4": AdvData(79904, "???"), diff --git a/worlds/undertale/Options.py b/worlds/undertale/Options.py index 87eaa820..d4fd1488 100644 --- a/worlds/undertale/Options.py +++ b/worlds/undertale/Options.py @@ -57,13 +57,13 @@ class NoEquips(Toggle): class RandomizeLove(Toggle): - """Adds LOVE to the pool. GENOCIDE ONLY!""" + """Adds LOVE to the pool. Only matters if your goal includes Genocide route""" display_name = "Randomize LOVE" default = 0 class RandomizeStats(Toggle): - """Makes each stat increase from LV a separate item. GENOCIDE ONLY! + """Makes each stat increase from LV a separate item. Only matters if your goal includes Genocide route Warning: This tends to spam chat with sending out checks.""" display_name = "Randomize Stats" default = 0 diff --git a/worlds/undertale/Rules.py b/worlds/undertale/Rules.py index 21ae8216..eb99e8ca 100644 --- a/worlds/undertale/Rules.py +++ b/worlds/undertale/Rules.py @@ -1,4 +1,4 @@ -from worlds.generic.Rules import set_rule, add_rule +from worlds.generic.Rules import set_rule, add_rule, add_item_rule from BaseClasses import MultiWorld, CollectionState @@ -250,6 +250,10 @@ def set_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance("??? Exit", player), lambda state: state.has("FIGHT", player)) set_rule(multiworld.get_location("Snowman", player), lambda state: state.can_reach("Snowdin Town", "Region", player)) + add_item_rule(multiworld.get_location("Starting Key", player), lambda item: item.name == "Ruins Key" or + item.name == "Snowdin Key" or + item.name == "Waterfall Key" or + item.name == "Hotland Key") if _undertale_is_route(multiworld.state, player, 1): set_rule(multiworld.get_location("Donut Sale", player), lambda state: state.has("ACT", player) and state.has("MERCY", player)) diff --git a/worlds/undertale/__init__.py b/worlds/undertale/__init__.py index e0cc7034..717b5c0f 100644 --- a/worlds/undertale/__init__.py +++ b/worlds/undertale/__init__.py @@ -47,13 +47,12 @@ class UndertaleWorld(World): """ game = "Undertale" option_definitions = undertale_options - topology_present = True web = UndertaleWeb() item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {name: data.id for name, data in advancement_table.items()} - data_version = 5 + data_version = 6 def _get_undertale_data(self): return { @@ -107,9 +106,6 @@ class UndertaleWorld(World): self.multiworld.push_precollected(self.create_item("ITEM")) self.multiworld.push_precollected(self.create_item("FIGHT")) self.multiworld.push_precollected(self.create_item("ACT")) - chosen_key_start = self.multiworld.per_slot_randoms[self.player].choice(["Ruins Key", "Snowdin Key", "Waterfall Key", "Hotland Key"]) - self.multiworld.push_precollected(self.create_item(chosen_key_start)) - itempool.remove(chosen_key_start) self.multiworld.push_precollected(self.create_item("MERCY")) if self.multiworld.route_required[self.player] == "genocide": itempool = [item for item in itempool if item != "Popato Chisps" and item != "Stained Apron" and diff --git a/worlds/undertale/data/patch.bsdiff b/worlds/undertale/data/patch.bsdiff index 0924da29..8d7dcbf4 100644 Binary files a/worlds/undertale/data/patch.bsdiff and b/worlds/undertale/data/patch.bsdiff differ diff --git a/worlds/undertale/docs/en_Undertale.md b/worlds/undertale/docs/en_Undertale.md index 511f3240..79ca2168 100644 --- a/worlds/undertale/docs/en_Undertale.md +++ b/worlds/undertale/docs/en_Undertale.md @@ -8,7 +8,7 @@ config file. ## What is considered a location check in Undertale? Location checks in Undertale are all the spots in the game where you can get an item. Exceptions are Dog Residue, -the Nicecream bought in Hotland, and anything you cannot get in your chosen route. +the Nicecream bought in Hotland, and anything you cannot get on your chosen route. ## When the player receives an item, what happens? @@ -18,4 +18,30 @@ wait until they do have space. ## What is the victory condition? Victory is achieved when the player completes their chosen route. If they chose `all_routes` then they need to complete -every major route in the game, those being `Pacifist`, `Neutral`, and `Genocide`. \ No newline at end of file +every major route in the game, those being `Pacifist`, `Neutral`, and `Genocide`. + +## What is different from the vanilla game? + +There are some major differences between vanilla and the randomizer. + +There are now doors to every major area in the underground located in the flower room (The first room of the game), those being Ruins, Snowdin, Waterfall, Hotland, and Core. +Each door needs their respective key from the pool to enter. +You start with one key for a random door. (Core will never be given to start with.) +The rest of the keys will be in the item pool. + +Genocide works a little differently in terms of the requirements. +You now only need to get through Core and fight Mettaton NEO, and then beat Sans, to win. +If you choose to fight other major bosses, you will still need to grind out the area before fighting them like normal. + +Pacifist is mostly the same, except you are not required to go to the Ruins to spare Toriel, +you only need to spare Papyrus, Undyne, and Mettaton EX. Although you still cannot kill anyone. +You are also still required to do the date/hangout with Papyrus, the hangout with Undyne, and the date with Alphys, +in that order, before entering the True Lab. + +You now require custom items to Hangout with Papyrus, Undyne, to enter the True Lab, and to fight Mettaton EX/NEO. +Those being `Complete Skeleton`, `Fish`, `DT Extractor`, and `Mettaton Plush`. + +The Riverperson will only take you to locations you have actually seen the Riverperson at. +Meaning they will only take you to, for example, Waterfall, if you have seen the Riverperson at Waterfall at least once. + +If you press `W` while in the save menu, you will teleport back to the flower room, for quick access to the other areas. \ No newline at end of file diff --git a/worlds/undertale/docs/undertale_en.md b/worlds/undertale/docs/undertale_en.md index ea10ab22..a2f3d257 100644 --- a/worlds/undertale/docs/undertale_en.md +++ b/worlds/undertale/docs/undertale_en.md @@ -48,6 +48,12 @@ before connecting. When the console tells you that you have joined the room, you're all set. Congratulations on successfully joining a multiworld game! +### PLEASE READ! + +Please read this page in its entirety before asking questions! Most importantly, there is a list of +gameplay differences at the bottom. +[Undertale Game Info Page](/games/Undertale/info/en) + ### Where do I get a YAML file? You can customize your settings by visiting the [Undertale Player Settings Page](/games/Undertale/player-settings)