Undertale for AP (#439)
Randomizes the items, and adds a new item to the pool, "Plot" which lets you go further and further in the game the more you have. Developers: WirTheAvali (Preferred name for professional use, mewlif)
This commit is contained in:
parent
71bfb6babd
commit
553fe0be19
|
@ -28,6 +28,7 @@
|
|||
*.apsave
|
||||
*.BIN
|
||||
|
||||
setups
|
||||
build
|
||||
bundle/components.wxs
|
||||
dist
|
||||
|
@ -176,6 +177,9 @@ minecraft_versions.json
|
|||
# pyenv
|
||||
.python-version
|
||||
|
||||
#undertale stuff
|
||||
/Undertale/
|
||||
|
||||
# OS General Files
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
|
|
|
@ -45,6 +45,7 @@ Currently, the following games are supported:
|
|||
* Adventure
|
||||
* DLC Quest
|
||||
* Noita
|
||||
* Undertale
|
||||
|
||||
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
|
||||
|
|
|
@ -0,0 +1,498 @@
|
|||
from __future__ import annotations
|
||||
import os
|
||||
import asyncio
|
||||
import typing
|
||||
import bsdiff4
|
||||
import shutil
|
||||
|
||||
import Utils
|
||||
|
||||
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
|
||||
from Utils import async_start
|
||||
|
||||
|
||||
class UndertaleCommandProcessor(ClientCommandProcessor):
|
||||
def __init__(self, ctx):
|
||||
super().__init__(ctx)
|
||||
|
||||
def _cmd_resync(self):
|
||||
"""Manually trigger a resync."""
|
||||
if isinstance(self.ctx, UndertaleContext):
|
||||
self.output(f"Syncing items.")
|
||||
self.ctx.syncing = True
|
||||
|
||||
def _cmd_patch(self):
|
||||
"""Patch the game."""
|
||||
if isinstance(self.ctx, UndertaleContext):
|
||||
os.makedirs(name=os.getcwd() + "\\Undertale", exist_ok=True)
|
||||
self.ctx.patch_game()
|
||||
self.output("Patched.")
|
||||
|
||||
@mark_raw
|
||||
def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None):
|
||||
"""Patch the game automatically."""
|
||||
if isinstance(self.ctx, UndertaleContext):
|
||||
os.makedirs(name=os.getcwd() + "\\Undertale", exist_ok=True)
|
||||
tempInstall = steaminstall
|
||||
if not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||
tempInstall = None
|
||||
if tempInstall is None:
|
||||
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
||||
if not os.path.exists("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"):
|
||||
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
|
||||
elif not os.path.exists(tempInstall):
|
||||
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
||||
if not os.path.exists("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"):
|
||||
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
|
||||
if not os.path.exists(tempInstall) or not os.path.exists(tempInstall) or not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||
self.output("ERROR: Cannot find Undertale. Please rerun the command with the correct folder."
|
||||
" command. \"/auto_patch (Steam directory)\".")
|
||||
else:
|
||||
for file_name in os.listdir(tempInstall):
|
||||
if file_name != "steam_api.dll":
|
||||
shutil.copy(tempInstall+"\\"+file_name,
|
||||
os.getcwd() + "\\Undertale\\" + file_name)
|
||||
self.ctx.patch_game()
|
||||
self.output("Patching successful!")
|
||||
|
||||
def _cmd_online(self):
|
||||
"""Makes you no longer able to see other Undertale players."""
|
||||
if isinstance(self.ctx, UndertaleContext):
|
||||
self.ctx.update_online_mode(not ("Online" in self.ctx.tags))
|
||||
if "Online" in self.ctx.tags:
|
||||
self.output(f"Now online.")
|
||||
else:
|
||||
self.output(f"Now offline.")
|
||||
|
||||
def _cmd_deathlink(self):
|
||||
"""Toggles deathlink"""
|
||||
if isinstance(self.ctx, UndertaleContext):
|
||||
self.ctx.deathlink_status = not self.ctx.deathlink_status
|
||||
if self.ctx.deathlink_status:
|
||||
self.output(f"Deathlink enabled.")
|
||||
else:
|
||||
self.output(f"Deathlink disabled.")
|
||||
|
||||
|
||||
class UndertaleContext(CommonContext):
|
||||
tags = {"AP", "Online"}
|
||||
game = "Undertale"
|
||||
command_processor = UndertaleCommandProcessor
|
||||
items_handling = 0b111
|
||||
route = None
|
||||
pieces_needed = None
|
||||
completed_routes = None
|
||||
completed_count = 0
|
||||
save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE")
|
||||
|
||||
def __init__(self, server_address, password):
|
||||
super().__init__(server_address, password)
|
||||
self.pieces_needed = 0
|
||||
self.game = "Undertale"
|
||||
self.got_deathlink = False
|
||||
self.syncing = False
|
||||
self.deathlink_status = False
|
||||
self.tem_armor = False
|
||||
self.completed_count = 0
|
||||
self.completed_routes = {"pacifist": 0, "genocide": 0, "neutral": 0}
|
||||
|
||||
def patch_game(self):
|
||||
with open(os.getcwd() + "/Undertale/data.win", "rb") as f:
|
||||
patchedFile = bsdiff4.patch(f.read(), undertale.data_path("patch.bsdiff"))
|
||||
with open(os.getcwd() + "/Undertale/data.win", "wb") as f:
|
||||
f.write(patchedFile)
|
||||
os.makedirs(name=os.getcwd() + "\\Undertale\\" + "Custom Sprites", exist_ok=True)
|
||||
with open(os.path.expandvars(os.getcwd() + "\\Undertale\\" + "Custom Sprites\\" +
|
||||
"Which Character.txt"), "w") as f:
|
||||
f.writelines(["// Put the folder name of the sprites you want to play as, make sure it is the only "
|
||||
"line other than this one.\n", "frisk"])
|
||||
f.close()
|
||||
|
||||
async def server_auth(self, password_requested: bool = False):
|
||||
if password_requested and not self.password:
|
||||
await super().server_auth(password_requested)
|
||||
await self.get_username()
|
||||
await self.send_connect()
|
||||
|
||||
def clear_undertale_files(self):
|
||||
path = self.save_game_folder
|
||||
self.finished_game = False
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if "check.spot" == file or "scout" == file:
|
||||
os.remove(os.path.join(root, file))
|
||||
elif file.endswith((".item", ".victory", ".route", ".playerspot", ".mad",
|
||||
".youDied", ".LV", ".mine", ".flag", ".hint")):
|
||||
os.remove(os.path.join(root, file))
|
||||
|
||||
async def connect(self, address: typing.Optional[str] = None):
|
||||
self.clear_undertale_files()
|
||||
await super().connect(address)
|
||||
|
||||
async def disconnect(self, allow_autoreconnect: bool = False):
|
||||
self.clear_undertale_files()
|
||||
await super().disconnect(allow_autoreconnect)
|
||||
|
||||
async def connection_closed(self):
|
||||
self.clear_undertale_files()
|
||||
await super().connection_closed()
|
||||
|
||||
async def shutdown(self):
|
||||
self.clear_undertale_files()
|
||||
await super().shutdown()
|
||||
|
||||
def update_online_mode(self, online):
|
||||
old_tags = self.tags.copy()
|
||||
if online:
|
||||
self.tags.add("Online")
|
||||
else:
|
||||
self.tags -= {"Online"}
|
||||
if old_tags != self.tags and self.server and not self.server.socket.closed:
|
||||
async_start(self.send_msgs([{"cmd": "ConnectUpdate", "tags": self.tags}]))
|
||||
|
||||
def on_package(self, cmd: str, args: dict):
|
||||
if cmd == "Connected":
|
||||
self.game = self.slot_info[self.slot].game
|
||||
async_start(process_undertale_cmd(self, cmd, args))
|
||||
|
||||
def run_gui(self):
|
||||
from kvui import GameManager
|
||||
|
||||
class UTManager(GameManager):
|
||||
logging_pairs = [
|
||||
("Client", "Archipelago")
|
||||
]
|
||||
base_title = "Archipelago Undertale Client"
|
||||
|
||||
self.ui = UTManager(self)
|
||||
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
|
||||
|
||||
def on_deathlink(self, data: typing.Dict[str, typing.Any]):
|
||||
self.got_deathlink = True
|
||||
super().on_deathlink(data)
|
||||
|
||||
|
||||
def to_room_name(place_name: str):
|
||||
if place_name == "Old Home Exit":
|
||||
return "room_ruinsexit"
|
||||
elif place_name == "Snowdin Forest":
|
||||
return "room_tundra1"
|
||||
elif place_name == "Snowdin Town Exit":
|
||||
return "room_fogroom"
|
||||
elif place_name == "Waterfall":
|
||||
return "room_water1"
|
||||
elif place_name == "Waterfall Exit":
|
||||
return "room_fire2"
|
||||
elif place_name == "Hotland":
|
||||
return "room_fire_prelab"
|
||||
elif place_name == "Hotland Exit":
|
||||
return "room_fire_precore"
|
||||
elif place_name == "Core":
|
||||
return "room_fire_core1"
|
||||
|
||||
|
||||
async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
if cmd == "Connected":
|
||||
if not os.path.exists(ctx.save_game_folder):
|
||||
os.mkdir(ctx.save_game_folder)
|
||||
ctx.route = args["slot_data"]["route"]
|
||||
ctx.pieces_needed = args["slot_data"]["key_pieces"]
|
||||
ctx.tem_armor = args["slot_data"]["temy_armor_include"]
|
||||
|
||||
await ctx.send_msgs([{"cmd": "Get", "keys": [str(ctx.slot)+" RoutesDone neutral",
|
||||
str(ctx.slot)+" RoutesDone pacifist",
|
||||
str(ctx.slot)+" RoutesDone genocide"]}])
|
||||
await ctx.send_msgs([{"cmd": "SetNotify", "keys": [str(ctx.slot)+" RoutesDone neutral",
|
||||
str(ctx.slot)+" RoutesDone pacifist",
|
||||
str(ctx.slot)+" RoutesDone genocide"]}])
|
||||
if args["slot_data"]["only_flakes"]:
|
||||
with open(os.path.join(ctx.save_game_folder, "GenoNoChest.flag"), "w") as f:
|
||||
f.close()
|
||||
if not args["slot_data"]["key_hunt"]:
|
||||
ctx.pieces_needed = 0
|
||||
if args["slot_data"]["rando_love"]:
|
||||
filename = f"LOVErando.LV"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
f.close()
|
||||
if args["slot_data"]["rando_stats"]:
|
||||
filename = f"STATrando.LV"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
f.close()
|
||||
filename = f"{ctx.route}.route"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
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:
|
||||
f.write(str(ss-12000)+"\n")
|
||||
f.close()
|
||||
elif cmd == "LocationInfo":
|
||||
for l in args["locations"]:
|
||||
locationid = l.location
|
||||
filename = f"{str(locationid-12000)}.hint"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
toDraw = ""
|
||||
for i in range(20):
|
||||
if i < len(str(ctx.item_names[l.item])):
|
||||
toDraw += str(ctx.item_names[l.item])[i]
|
||||
else:
|
||||
break
|
||||
f.write(toDraw)
|
||||
f.close()
|
||||
elif cmd == "Retrieved":
|
||||
if str(ctx.slot)+" RoutesDone neutral" in args["keys"]:
|
||||
if args["keys"][str(ctx.slot)+" RoutesDone neutral"] is not None:
|
||||
ctx.completed_routes["neutral"] = args["keys"][str(ctx.slot)+" RoutesDone neutral"]
|
||||
if str(ctx.slot)+" RoutesDone genocide" in args["keys"]:
|
||||
if args["keys"][str(ctx.slot)+" RoutesDone genocide"] is not None:
|
||||
ctx.completed_routes["genocide"] = args["keys"][str(ctx.slot)+" RoutesDone genocide"]
|
||||
if str(ctx.slot)+" RoutesDone pacifist" in args["keys"]:
|
||||
if args["keys"][str(ctx.slot) + " RoutesDone pacifist"] is not None:
|
||||
ctx.completed_routes["pacifist"] = args["keys"][str(ctx.slot)+" RoutesDone pacifist"]
|
||||
elif cmd == "SetReply":
|
||||
if args["value"] is not None:
|
||||
if str(ctx.slot)+" RoutesDone pacifist" == args["key"]:
|
||||
ctx.completed_routes["pacifist"] = args["value"]
|
||||
elif str(ctx.slot)+" RoutesDone genocide" == args["key"]:
|
||||
ctx.completed_routes["genocide"] = args["value"]
|
||||
elif str(ctx.slot)+" RoutesDone neutral" == args["key"]:
|
||||
ctx.completed_routes["neutral"] = args["value"]
|
||||
elif cmd == "ReceivedItems":
|
||||
start_index = args["index"]
|
||||
|
||||
if start_index == 0:
|
||||
ctx.items_received = []
|
||||
elif start_index != len(ctx.items_received):
|
||||
sync_msg = [{"cmd": "Sync"}]
|
||||
if ctx.locations_checked:
|
||||
sync_msg.append({"cmd": "LocationChecks",
|
||||
"locations": list(ctx.locations_checked)})
|
||||
await ctx.send_msgs(sync_msg)
|
||||
if start_index == len(ctx.items_received):
|
||||
counter = -1
|
||||
placedWeapon = 0
|
||||
placedArmor = 0
|
||||
for item in args["items"]:
|
||||
id = NetworkItem(*item).location
|
||||
while NetworkItem(*item).location < 0 and \
|
||||
counter <= id:
|
||||
id -= 1
|
||||
if NetworkItem(*item).location < 0:
|
||||
counter -= 1
|
||||
filename = f"{str(id)}PLR{str(NetworkItem(*item).player)}.item"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
if NetworkItem(*item).item == 77701:
|
||||
if placedWeapon == 0:
|
||||
f.write(str(77013-11000))
|
||||
elif placedWeapon == 1:
|
||||
f.write(str(77014-11000))
|
||||
elif placedWeapon == 2:
|
||||
f.write(str(77025-11000))
|
||||
elif placedWeapon == 3:
|
||||
f.write(str(77045-11000))
|
||||
elif placedWeapon == 4:
|
||||
f.write(str(77049-11000))
|
||||
elif placedWeapon == 5:
|
||||
f.write(str(77047-11000))
|
||||
elif placedWeapon == 6:
|
||||
if str(ctx.route) == "genocide" or str(ctx.route) == "all_routes":
|
||||
f.write(str(77052-11000))
|
||||
else:
|
||||
f.write(str(77051-11000))
|
||||
else:
|
||||
f.write(str(77003-11000))
|
||||
placedWeapon += 1
|
||||
elif NetworkItem(*item).item == 77702:
|
||||
if placedArmor == 0:
|
||||
f.write(str(77012-11000))
|
||||
elif placedArmor == 1:
|
||||
f.write(str(77015-11000))
|
||||
elif placedArmor == 2:
|
||||
f.write(str(77024-11000))
|
||||
elif placedArmor == 3:
|
||||
f.write(str(77044-11000))
|
||||
elif placedArmor == 4:
|
||||
f.write(str(77048-11000))
|
||||
elif placedArmor == 5:
|
||||
if str(ctx.route) == "genocide":
|
||||
f.write(str(77053-11000))
|
||||
else:
|
||||
f.write(str(77046-11000))
|
||||
elif placedArmor == 6 and ((not str(ctx.route) == "genocide") or ctx.tem_armor):
|
||||
if str(ctx.route) == "all_routes":
|
||||
f.write(str(77053-11000))
|
||||
elif str(ctx.route) == "genocide":
|
||||
f.write(str(77064-11000))
|
||||
else:
|
||||
f.write(str(77050-11000))
|
||||
elif placedArmor == 7 and ctx.tem_armor and not str(ctx.route) == "genocide":
|
||||
f.write(str(77064-11000))
|
||||
else:
|
||||
f.write(str(77004-11000))
|
||||
placedArmor += 1
|
||||
else:
|
||||
f.write(str(NetworkItem(*item).item-11000))
|
||||
f.close()
|
||||
ctx.items_received.append(NetworkItem(*item))
|
||||
if [item.item for item in ctx.items_received].count(77000) >= ctx.pieces_needed > 0:
|
||||
filename = f"{str(-99999)}PLR{str(0)}.item"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
f.write(str(77787 - 11000))
|
||||
f.close()
|
||||
filename = f"{str(-99998)}PLR{str(0)}.item"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
f.write(str(77789 - 11000))
|
||||
f.close()
|
||||
ctx.watcher_event.set()
|
||||
|
||||
elif cmd == "RoomUpdate":
|
||||
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:
|
||||
f.write(str(ss-12000)+"\n")
|
||||
f.close()
|
||||
|
||||
elif cmd == "Bounced":
|
||||
tags = args.get("tags", [])
|
||||
if "Online" in tags:
|
||||
data = args.get("worlds/undertale/data", {})
|
||||
if data["player"] != ctx.slot and data["player"] is not None:
|
||||
filename = f"FRISK" + str(data["player"]) + ".playerspot"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
f.write(str(data["x"]) + str(data["y"]) + str(data["room"]) + str(
|
||||
data["spr"]) + str(data["frm"]))
|
||||
f.close()
|
||||
|
||||
|
||||
async def multi_watcher(ctx: UndertaleContext):
|
||||
while not ctx.exit_event.is_set():
|
||||
path = ctx.save_game_folder
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if "spots.mine" in file and "Online" in ctx.tags:
|
||||
with open(root + "/" + file, "r") as mine:
|
||||
this_x = mine.readline()
|
||||
this_y = mine.readline()
|
||||
this_room = mine.readline()
|
||||
this_sprite = mine.readline()
|
||||
this_frame = mine.readline()
|
||||
mine.close()
|
||||
message = [{"cmd": "Bounce", "tags": ["Online"],
|
||||
"data": {"player": ctx.slot, "x": this_x, "y": this_y, "room": this_room,
|
||||
"spr": this_sprite, "frm": this_frame}}]
|
||||
await ctx.send_msgs(message)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
async def game_watcher(ctx: UndertaleContext):
|
||||
while not ctx.exit_event.is_set():
|
||||
await ctx.update_death_link(ctx.deathlink_status)
|
||||
path = ctx.save_game_folder
|
||||
if ctx.syncing:
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if ".item" in file:
|
||||
os.remove(root+"/"+file)
|
||||
sync_msg = [{"cmd": "Sync"}]
|
||||
if ctx.locations_checked:
|
||||
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
|
||||
await ctx.send_msgs(sync_msg)
|
||||
ctx.syncing = False
|
||||
if ctx.got_deathlink:
|
||||
ctx.got_deathlink = False
|
||||
with open(os.path.join(ctx.save_game_folder, "/WelcomeToTheDead.youDied"), "w") as f:
|
||||
f.close()
|
||||
sending = []
|
||||
victory = False
|
||||
found_routes = 0
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if "DontBeMad.mad" in file and "DeathLink" in ctx.tags:
|
||||
os.remove(root+"/"+file)
|
||||
await ctx.send_death()
|
||||
if "scout" == file:
|
||||
sending = []
|
||||
with open(root+"/"+file, "r") as f:
|
||||
lines = f.readlines()
|
||||
for l in lines:
|
||||
if ctx.server_locations.__contains__(int(l)+12000):
|
||||
sending = sending + [int(l)+12000]
|
||||
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": sending,
|
||||
"create_as_hint": int(2)}])
|
||||
os.remove(root+"/"+file)
|
||||
if "check.spot" in file:
|
||||
sending = []
|
||||
with open(root+"/"+file, "r") as f:
|
||||
lines = f.readlines()
|
||||
for l in lines:
|
||||
sending = sending+[(int(l))+12000]
|
||||
message = [{"cmd": "LocationChecks", "locations": sending}]
|
||||
await ctx.send_msgs(message)
|
||||
if "victory" in file and str(ctx.route) in file:
|
||||
victory = True
|
||||
if ".playerspot" in file and "Online" not in ctx.tags:
|
||||
os.remove(root+"/"+file)
|
||||
if "victory" in file:
|
||||
if str(ctx.route) == "all_routes":
|
||||
if "neutral" in file and ctx.completed_routes["neutral"] != 1:
|
||||
await ctx.send_msgs([{"cmd": "Set", "key": str(ctx.slot)+" RoutesDone neutral",
|
||||
"default": 0, "want_reply": True, "operations": [{"operation": "max",
|
||||
"value": 1}]}])
|
||||
elif "pacifist" in file and ctx.completed_routes["pacifist"] != 1:
|
||||
await ctx.send_msgs([{"cmd": "Set", "key": str(ctx.slot)+" RoutesDone pacifist",
|
||||
"default": 0, "want_reply": True, "operations": [{"operation": "max",
|
||||
"value": 1}]}])
|
||||
elif "genocide" in file and ctx.completed_routes["genocide"] != 1:
|
||||
await ctx.send_msgs([{"cmd": "Set", "key": str(ctx.slot)+" RoutesDone genocide",
|
||||
"default": 0, "want_reply": True, "operations": [{"operation": "max",
|
||||
"value": 1}]}])
|
||||
if str(ctx.route) == "all_routes":
|
||||
found_routes += ctx.completed_routes["neutral"]
|
||||
found_routes += ctx.completed_routes["pacifist"]
|
||||
found_routes += ctx.completed_routes["genocide"]
|
||||
if str(ctx.route) == "all_routes" and found_routes >= 3:
|
||||
victory = True
|
||||
ctx.locations_checked = sending
|
||||
if (not ctx.finished_game) and victory:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
def main():
|
||||
Utils.init_logging("UndertaleClient", exception_logger="Client")
|
||||
|
||||
async def _main():
|
||||
ctx = UndertaleContext(None, None)
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
|
||||
asyncio.create_task(
|
||||
game_watcher(ctx), name="UndertaleProgressionWatcher")
|
||||
|
||||
asyncio.create_task(
|
||||
multi_watcher(ctx), name="UndertaleMultiplayerWatcher")
|
||||
|
||||
if gui_enabled:
|
||||
ctx.run_gui()
|
||||
ctx.run_cli()
|
||||
|
||||
await ctx.exit_event.wait()
|
||||
await ctx.shutdown()
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(_main())
|
||||
colorama.deinit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = get_base_parser(description="Undertale Client, for text interfacing.")
|
||||
args = parser.parse_args()
|
||||
main()
|
|
@ -88,6 +88,7 @@ Name: "client/wargroove"; Description: "Wargroove"; Types: full playing
|
|||
Name: "client/zl"; Description: "Zillion"; Types: full playing
|
||||
Name: "client/tloz"; Description: "The Legend of Zelda"; Types: full playing
|
||||
Name: "client/advn"; Description: "Adventure"; Types: full playing
|
||||
Name: "client/ut"; Description: "Undertale"; Types: full playing
|
||||
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
|
||||
|
||||
[Dirs]
|
||||
|
@ -131,6 +132,7 @@ Source: "{#source_path}\ArchipelagoZelda1Client.exe"; DestDir: "{app}"; Flags: i
|
|||
Source: "{#source_path}\ArchipelagoWargrooveClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/wargroove
|
||||
Source: "{#source_path}\ArchipelagoKH2Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/kh2
|
||||
Source: "{#source_path}\ArchipelagoAdventureClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/advn
|
||||
Source: "{#source_path}\ArchipelagoUndertaleClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/ut
|
||||
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||
|
||||
[Icons]
|
||||
|
@ -150,6 +152,7 @@ Name: "{group}\{#MyAppName} The Legend of Zelda Client"; Filename: "{app}\Archip
|
|||
Name: "{group}\{#MyAppName} Kingdom Hearts 2 Client"; Filename: "{app}\ArchipelagoKH2Client.exe"; Components: client/kh2
|
||||
Name: "{group}\{#MyAppName} Adventure Client"; Filename: "{app}\ArchipelagoAdventureClient.exe"; Components: client/advn
|
||||
Name: "{group}\{#MyAppName} Wargroove Client"; Filename: "{app}\ArchipelagoWargrooveClient.exe"; Components: client/wargroove
|
||||
Name: "{group}\{#MyAppName} Undertale Client"; Filename: "{app}\ArchipelagoUndertaleClient.exe"; Components: client/ut
|
||||
|
||||
Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon
|
||||
Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server
|
||||
|
@ -166,6 +169,7 @@ Name: "{commondesktop}\{#MyAppName} The Legend of Zelda Client"; Filename: "{app
|
|||
Name: "{commondesktop}\{#MyAppName} Wargroove Client"; Filename: "{app}\ArchipelagoWargrooveClient.exe"; Tasks: desktopicon; Components: client/wargroove
|
||||
Name: "{commondesktop}\{#MyAppName} Kingdom Hearts 2 Client"; Filename: "{app}\ArchipelagoKH2Client.exe"; Tasks: desktopicon; Components: client/kh2
|
||||
Name: "{commondesktop}\{#MyAppName} Adventure Client"; Filename: "{app}\ArchipelagoAdventureClient.exe"; Tasks: desktopicon; Components: client/advn
|
||||
Name: "{commondesktop}\{#MyAppName} Undertale Client"; Filename: "{app}\ArchipelagoUndertaleClient.exe"; Tasks: desktopicon; Components: client/ut
|
||||
|
||||
[Run]
|
||||
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
from BaseClasses import Item, ItemClassification
|
||||
import typing
|
||||
|
||||
|
||||
class ItemData(typing.NamedTuple):
|
||||
code: typing.Optional[int]
|
||||
classification: any
|
||||
|
||||
|
||||
class UndertaleItem(Item):
|
||||
game: str = "Undertale"
|
||||
|
||||
|
||||
item_table = {
|
||||
"Progressive Plot": ItemData(77700, ItemClassification.progression),
|
||||
"Progressive Weapons": ItemData(77701, ItemClassification.useful),
|
||||
"Progressive Armor": ItemData(77702, ItemClassification.useful),
|
||||
"Monster Candy": ItemData(77001, ItemClassification.filler),
|
||||
"Croquet Roll": ItemData(77002, ItemClassification.filler),
|
||||
"Stick": ItemData(77003, ItemClassification.useful),
|
||||
"Bandage": ItemData(77004, ItemClassification.useful),
|
||||
"Rock Candy": ItemData(77005, ItemClassification.filler),
|
||||
"Pumpkin Rings": ItemData(77006, ItemClassification.filler),
|
||||
"Spider Donut": ItemData(77007, ItemClassification.filler),
|
||||
"Stoic Onion": ItemData(77008, ItemClassification.filler),
|
||||
"Ghost Fruit": ItemData(77009, ItemClassification.filler),
|
||||
"Spider Cider": ItemData(77010, ItemClassification.filler),
|
||||
"Butterscotch Pie": ItemData(77011, ItemClassification.useful),
|
||||
"Faded Ribbon": ItemData(77012, ItemClassification.useful),
|
||||
"Toy Knife": ItemData(77013, ItemClassification.useful),
|
||||
"Tough Glove": ItemData(77014, ItemClassification.useful),
|
||||
"Manly Bandanna": ItemData(77015, ItemClassification.useful),
|
||||
"Snowman Piece": ItemData(77016, ItemClassification.useful),
|
||||
"Nice Cream": ItemData(77017, ItemClassification.filler),
|
||||
"Puppydough Icecream": ItemData(77018, ItemClassification.filler),
|
||||
"Bisicle": ItemData(77019, ItemClassification.filler),
|
||||
"Unisicle": ItemData(77020, ItemClassification.filler),
|
||||
"Cinnamon Bun": ItemData(77021, ItemClassification.filler),
|
||||
"Temmie Flakes": ItemData(77022, ItemClassification.filler),
|
||||
"Abandoned Quiche": ItemData(77023, ItemClassification.filler),
|
||||
"Old Tutu": ItemData(77024, ItemClassification.useful),
|
||||
"Ballet Shoes": ItemData(77025, ItemClassification.useful),
|
||||
"Punch Card": ItemData(77026, ItemClassification.progression),
|
||||
"Annoying Dog": ItemData(77027, ItemClassification.filler),
|
||||
"Dog Salad": ItemData(77028, ItemClassification.filler),
|
||||
"Dog Residue": ItemData(77029, ItemClassification.filler),
|
||||
"Astronaut Food": ItemData(77035, ItemClassification.filler),
|
||||
"Instant Noodles": ItemData(77036, ItemClassification.useful),
|
||||
"Crab Apple": ItemData(77037, ItemClassification.filler),
|
||||
"Hot Dog...?": ItemData(77038, ItemClassification.progression),
|
||||
"Hot Cat": ItemData(77039, ItemClassification.filler),
|
||||
"Glamburger": ItemData(77040, ItemClassification.filler),
|
||||
"Sea Tea": ItemData(77041, ItemClassification.filler),
|
||||
"Starfait": ItemData(77042, ItemClassification.filler),
|
||||
"Legendary Hero": ItemData(77043, ItemClassification.filler),
|
||||
"Cloudy Glasses": ItemData(77044, ItemClassification.useful),
|
||||
"Torn Notebook": ItemData(77045, ItemClassification.useful),
|
||||
"Stained Apron": ItemData(77046, ItemClassification.useful),
|
||||
"Burnt Pan": ItemData(77047, ItemClassification.useful),
|
||||
"Cowboy Hat": ItemData(77048, ItemClassification.useful),
|
||||
"Empty Gun": ItemData(77049, ItemClassification.useful),
|
||||
"Heart Locket": ItemData(77050, ItemClassification.useful),
|
||||
"Worn Dagger": ItemData(77051, ItemClassification.useful),
|
||||
"Real Knife": ItemData(77052, ItemClassification.useful),
|
||||
"The Locket": ItemData(77053, ItemClassification.useful),
|
||||
"Bad Memory": ItemData(77054, ItemClassification.filler),
|
||||
"Dream": ItemData(77055, ItemClassification.filler),
|
||||
"Undyne's Letter": ItemData(77056, ItemClassification.filler),
|
||||
"Undyne Letter EX": ItemData(77057, ItemClassification.progression),
|
||||
"Popato Chisps": ItemData(77058, ItemClassification.filler),
|
||||
"Junk Food": ItemData(77059, ItemClassification.filler),
|
||||
"Mystery Key": ItemData(77060, ItemClassification.filler),
|
||||
"Face Steak": ItemData(77061, ItemClassification.filler),
|
||||
"Hush Puppy": ItemData(77062, ItemClassification.filler),
|
||||
"Snail Pie": ItemData(77063, ItemClassification.filler),
|
||||
"temy armor": ItemData(77064, ItemClassification.useful),
|
||||
"Complete Skeleton": ItemData(77779, ItemClassification.progression),
|
||||
"Fish": ItemData(77780, ItemClassification.progression),
|
||||
"DT Extractor": ItemData(77782, ItemClassification.progression),
|
||||
"Mettaton Plush": ItemData(77786, ItemClassification.progression),
|
||||
"Left Home Key": ItemData(77787, ItemClassification.progression),
|
||||
"LOVE": ItemData(77788, ItemClassification.useful),
|
||||
"Right Home Key": ItemData(77789, ItemClassification.progression),
|
||||
"Key Piece": ItemData(77000, ItemClassification.progression),
|
||||
"100G": ItemData(77999, ItemClassification.useful),
|
||||
"500G": ItemData(77998, ItemClassification.useful),
|
||||
"1000G": ItemData(77997, ItemClassification.progression),
|
||||
"ATK Up": ItemData(77065, ItemClassification.useful),
|
||||
"DEF Up": ItemData(77066, ItemClassification.useful),
|
||||
"HP Up": ItemData(77067, ItemClassification.useful),
|
||||
"FIGHT": ItemData(77077, ItemClassification.progression),
|
||||
"ACT": ItemData(77078, ItemClassification.progression),
|
||||
"ITEM": ItemData(77079, ItemClassification.progression),
|
||||
"MERCY": ItemData(77080, ItemClassification.progression),
|
||||
"Ruins Key": ItemData(77081, ItemClassification.progression),
|
||||
"Snowdin Key": ItemData(77082, ItemClassification.progression),
|
||||
"Waterfall Key": ItemData(77083, ItemClassification.progression),
|
||||
"Hotland Key": ItemData(77084, ItemClassification.progression),
|
||||
"Core Key": ItemData(77085, ItemClassification.progression),
|
||||
"Undyne Date": ItemData(None, ItemClassification.progression),
|
||||
"Alphys Date": ItemData(None, ItemClassification.progression),
|
||||
"Papyrus Date": ItemData(None, ItemClassification.progression),
|
||||
}
|
||||
|
||||
non_key_items = {
|
||||
"Butterscotch Pie": 1,
|
||||
"500G": 2,
|
||||
"1000G": 2,
|
||||
"Face Steak": 1,
|
||||
"Snowman Piece": 1,
|
||||
"Instant Noodles": 1,
|
||||
"Astronaut Food": 2,
|
||||
"Hot Cat": 1,
|
||||
"Abandoned Quiche": 1,
|
||||
"Spider Donut": 1,
|
||||
"Spider Cider": 1,
|
||||
"Hush Puppy": 1,
|
||||
}
|
||||
|
||||
required_armor = {
|
||||
"Cloudy Glasses": 1,
|
||||
"Manly Bandanna": 1,
|
||||
"Old Tutu": 1,
|
||||
"Stained Apron": 1,
|
||||
"Heart Locket": 1,
|
||||
"Faded Ribbon": 1,
|
||||
"Cowboy Hat": 1,
|
||||
}
|
||||
|
||||
required_weapons = {
|
||||
"Torn Notebook": 1,
|
||||
"Tough Glove": 1,
|
||||
"Ballet Shoes": 1,
|
||||
"Burnt Pan": 1,
|
||||
"Worn Dagger": 1,
|
||||
"Toy Knife": 1,
|
||||
"Empty Gun": 1,
|
||||
}
|
||||
|
||||
plot_items = {
|
||||
"Complete Skeleton": 1,
|
||||
"Fish": 1,
|
||||
"Mettaton Plush": 1,
|
||||
"DT Extractor": 1,
|
||||
}
|
||||
|
||||
key_items = {
|
||||
"Complete Skeleton": 1,
|
||||
"Fish": 1,
|
||||
"DT Extractor": 1,
|
||||
"Mettaton Plush": 1,
|
||||
"Punch Card": 3,
|
||||
"Hot Dog...?": 1,
|
||||
"ATK Up": 19,
|
||||
"DEF Up": 4,
|
||||
"HP Up": 19,
|
||||
"LOVE": 19,
|
||||
"Ruins Key": 1,
|
||||
"Snowdin Key": 1,
|
||||
"Waterfall Key": 1,
|
||||
"Hotland Key": 1,
|
||||
"Core Key": 1,
|
||||
}
|
||||
|
||||
junk_weights_all = {
|
||||
"Bisicle": 12,
|
||||
"Legendary Hero": 8,
|
||||
"Glamburger": 10,
|
||||
"Crab Apple": 12,
|
||||
"Sea Tea": 12,
|
||||
"Nice Cream": 10,
|
||||
"Spider Donut": 10,
|
||||
"Popato Chisps": 12,
|
||||
"Junk Food": 12,
|
||||
"Temmie Flakes": 10,
|
||||
"Spider Cider": 8,
|
||||
"Hot Dog...?": 10,
|
||||
"Cinnamon Bun": 10,
|
||||
"Starfait": 12,
|
||||
"Punch Card": 8,
|
||||
"Monster Candy": 6,
|
||||
"100G": 6,
|
||||
"500G": 3,
|
||||
}
|
||||
|
||||
junk_weights_neutral = {
|
||||
"Bisicle": 12,
|
||||
"Legendary Hero": 8,
|
||||
"Glamburger": 10,
|
||||
"Crab Apple": 12,
|
||||
"Sea Tea": 12,
|
||||
"Nice Cream": 10,
|
||||
"Spider Donut": 10,
|
||||
"Junk Food": 12,
|
||||
"Temmie Flakes": 10,
|
||||
"Spider Cider": 8,
|
||||
"Cinnamon Bun": 10,
|
||||
"Starfait": 12,
|
||||
"Punch Card": 8,
|
||||
"Monster Candy": 6,
|
||||
"100G": 6,
|
||||
"500G": 3,
|
||||
}
|
||||
|
||||
junk_weights_pacifist = {
|
||||
"Bisicle": 12,
|
||||
"Legendary Hero": 8,
|
||||
"Glamburger": 10,
|
||||
"Crab Apple": 12,
|
||||
"Sea Tea": 12,
|
||||
"Nice Cream": 10,
|
||||
"Spider Donut": 10,
|
||||
"Popato Chisps": 12,
|
||||
"Junk Food": 12,
|
||||
"Temmie Flakes": 10,
|
||||
"Spider Cider": 8,
|
||||
"Hot Dog...?": 10,
|
||||
"Cinnamon Bun": 10,
|
||||
"Starfait": 12,
|
||||
"Punch Card": 8,
|
||||
"Monster Candy": 6,
|
||||
"100G": 6,
|
||||
"500G": 3,
|
||||
}
|
||||
|
||||
junk_weights_genocide = {
|
||||
"Bisicle": 12,
|
||||
"Legendary Hero": 8,
|
||||
"Glamburger": 10,
|
||||
"Crab Apple": 12,
|
||||
"Sea Tea": 12,
|
||||
"Spider Donut": 10,
|
||||
"Junk Food": 12,
|
||||
"Temmie Flakes": 10,
|
||||
"Spider Cider": 8,
|
||||
"Cinnamon Bun": 10,
|
||||
"Starfait": 12,
|
||||
"Monster Candy": 6,
|
||||
"100G": 6,
|
||||
"500G": 3,
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
from BaseClasses import Location
|
||||
import typing
|
||||
|
||||
|
||||
class AdvData(typing.NamedTuple):
|
||||
id: typing.Optional[int]
|
||||
region: str
|
||||
|
||||
|
||||
class UndertaleAdvancement(Location):
|
||||
game: str = "Undertale"
|
||||
|
||||
def __init__(self, player: int, name: str, address: typing.Optional[int], parent):
|
||||
super().__init__(player, name, address, parent)
|
||||
self.event = not address
|
||||
|
||||
|
||||
advancement_table = {
|
||||
"Snowman": AdvData(79100, "Snowdin Forest"),
|
||||
"Snowman 2": AdvData(79101, "Snowdin Forest"),
|
||||
"Snowman 3": AdvData(79102, "Snowdin Forest"),
|
||||
"Nicecream Snowdin": AdvData(79001, "Snowdin Forest"),
|
||||
"Nicecream Waterfall": AdvData(79002, "Waterfall"),
|
||||
"Nicecream Punch Card": AdvData(79003, "Waterfall"),
|
||||
"Quiche Bench": AdvData(79004, "Waterfall"),
|
||||
"Tutu Hidden": AdvData(79005, "Waterfall"),
|
||||
"Card Reward": AdvData(79006, "Waterfall"),
|
||||
"Grass Shoes": AdvData(79007, "Waterfall"),
|
||||
"Noodles Fridge": AdvData(79008, "Hotland"),
|
||||
"Pan Hidden": AdvData(79009, "Hotland"),
|
||||
"Apron Hidden": AdvData(79010, "Hotland"),
|
||||
"Trash Burger": AdvData(79011, "Core"),
|
||||
"Present Knife": AdvData(79012, "New Home"),
|
||||
"Present Locket": AdvData(79013, "New Home"),
|
||||
"Candy 1": AdvData(79014, "Ruins"),
|
||||
"Candy 2": AdvData(79015, "Ruins"),
|
||||
"Candy 3": AdvData(79016, "Ruins"),
|
||||
"Candy 4": AdvData(79017, "Ruins"),
|
||||
"Donut Sale": AdvData(79018, "Ruins"),
|
||||
"Cider Sale": AdvData(79019, "Ruins"),
|
||||
"Ribbon Cracks": AdvData(79020, "Ruins"),
|
||||
"Toy Knife Edge": AdvData(79021, "Ruins"),
|
||||
"B.Scotch Pie Given": AdvData(79022, "Ruins"),
|
||||
"Astro 1": AdvData(79023, "Waterfall"),
|
||||
"Astro 2": AdvData(79024, "Waterfall"),
|
||||
"Dog Sale 1": AdvData(79026, "Hotland"),
|
||||
"Cat Sale": AdvData(79027, "Hotland"),
|
||||
"Dog Sale 2": AdvData(79028, "Hotland"),
|
||||
"Dog Sale 3": AdvData(79029, "Hotland"),
|
||||
"Dog Sale 4": AdvData(79030, "Hotland"),
|
||||
"Chisps Machine": AdvData(79031, "True Lab"),
|
||||
"Hush Trade": AdvData(79032, "Hotland"),
|
||||
"Letter Quest": AdvData(79033, "Snowdin Town"),
|
||||
"Bunny 1": AdvData(79034, "Snowdin Town"),
|
||||
"Bunny 2": AdvData(79035, "Snowdin Town"),
|
||||
"Bunny 3": AdvData(79036, "Snowdin Town"),
|
||||
"Bunny 4": AdvData(79037, "Snowdin Town"),
|
||||
"Gerson 1": AdvData(79038, "Waterfall"),
|
||||
"Gerson 2": AdvData(79039, "Waterfall"),
|
||||
"Gerson 3": AdvData(79040, "Waterfall"),
|
||||
"Gerson 4": AdvData(79041, "Waterfall"),
|
||||
"Bratty Catty 1": AdvData(79042, "Hotland"),
|
||||
"Bratty Catty 2": AdvData(79043, "Hotland"),
|
||||
"Bratty Catty 3": AdvData(79044, "Hotland"),
|
||||
"Bratty Catty 4": AdvData(79045, "Hotland"),
|
||||
"Burgerpants 1": AdvData(79046, "Hotland"),
|
||||
"Burgerpants 2": AdvData(79047, "Hotland"),
|
||||
"Burgerpants 3": AdvData(79048, "Hotland"),
|
||||
"Burgerpants 4": AdvData(79049, "Hotland"),
|
||||
"TemmieShop 1": AdvData(79050, "Waterfall"),
|
||||
"TemmieShop 2": AdvData(79051, "Waterfall"),
|
||||
"TemmieShop 3": AdvData(79052, "Waterfall"),
|
||||
"TemmieShop 4": AdvData(79053, "Waterfall"),
|
||||
"Papyrus Plot": AdvData(79056, "Snowdin Town"),
|
||||
"Undyne Plot": AdvData(79057, "Waterfall"),
|
||||
"Mettaton Plot": AdvData(79062, "Core"),
|
||||
"True Lab Plot": AdvData(79063, "Hotland"),
|
||||
"Left New Home Key": AdvData(79064, "New Home"),
|
||||
"Right New Home Key": AdvData(79065, "New Home"),
|
||||
"LOVE 2": AdvData(79902, "???"),
|
||||
"LOVE 3": AdvData(79903, "???"),
|
||||
"LOVE 4": AdvData(79904, "???"),
|
||||
"LOVE 5": AdvData(79905, "???"),
|
||||
"LOVE 6": AdvData(79906, "???"),
|
||||
"LOVE 7": AdvData(79907, "???"),
|
||||
"LOVE 8": AdvData(79908, "???"),
|
||||
"LOVE 9": AdvData(79909, "???"),
|
||||
"LOVE 10": AdvData(79910, "???"),
|
||||
"LOVE 11": AdvData(79911, "???"),
|
||||
"LOVE 12": AdvData(79912, "???"),
|
||||
"LOVE 13": AdvData(79913, "???"),
|
||||
"LOVE 14": AdvData(79914, "???"),
|
||||
"LOVE 15": AdvData(79915, "???"),
|
||||
"LOVE 16": AdvData(79916, "???"),
|
||||
"LOVE 17": AdvData(79917, "???"),
|
||||
"LOVE 18": AdvData(79918, "???"),
|
||||
"LOVE 19": AdvData(79919, "???"),
|
||||
"LOVE 20": AdvData(79920, "???"),
|
||||
"ATK 2": AdvData(79800, "???"),
|
||||
"ATK 3": AdvData(79801, "???"),
|
||||
"ATK 4": AdvData(79802, "???"),
|
||||
"ATK 5": AdvData(79803, "???"),
|
||||
"ATK 6": AdvData(79804, "???"),
|
||||
"ATK 7": AdvData(79805, "???"),
|
||||
"ATK 8": AdvData(79806, "???"),
|
||||
"ATK 9": AdvData(79807, "???"),
|
||||
"ATK 10": AdvData(79808, "???"),
|
||||
"ATK 11": AdvData(79809, "???"),
|
||||
"ATK 12": AdvData(79810, "???"),
|
||||
"ATK 13": AdvData(79811, "???"),
|
||||
"ATK 14": AdvData(79812, "???"),
|
||||
"ATK 15": AdvData(79813, "???"),
|
||||
"ATK 16": AdvData(79814, "???"),
|
||||
"ATK 17": AdvData(79815, "???"),
|
||||
"ATK 18": AdvData(79816, "???"),
|
||||
"ATK 19": AdvData(79817, "???"),
|
||||
"ATK 20": AdvData(79818, "???"),
|
||||
"DEF 5": AdvData(79700, "???"),
|
||||
"DEF 9": AdvData(79701, "???"),
|
||||
"DEF 13": AdvData(79702, "???"),
|
||||
"DEF 17": AdvData(79703, "???"),
|
||||
"HP 2": AdvData(79600, "???"),
|
||||
"HP 3": AdvData(79601, "???"),
|
||||
"HP 4": AdvData(79602, "???"),
|
||||
"HP 5": AdvData(79603, "???"),
|
||||
"HP 6": AdvData(79604, "???"),
|
||||
"HP 7": AdvData(79605, "???"),
|
||||
"HP 8": AdvData(79606, "???"),
|
||||
"HP 9": AdvData(79607, "???"),
|
||||
"HP 10": AdvData(79608, "???"),
|
||||
"HP 11": AdvData(79609, "???"),
|
||||
"HP 12": AdvData(79610, "???"),
|
||||
"HP 13": AdvData(79611, "???"),
|
||||
"HP 14": AdvData(79612, "???"),
|
||||
"HP 15": AdvData(79613, "???"),
|
||||
"HP 16": AdvData(79614, "???"),
|
||||
"HP 17": AdvData(79615, "???"),
|
||||
"HP 18": AdvData(79616, "???"),
|
||||
"HP 19": AdvData(79617, "???"),
|
||||
"HP 20": AdvData(79618, "???"),
|
||||
"Undyne Date": AdvData(None, "Undyne\"s Home"),
|
||||
"Alphys Date": AdvData(None, "Hotland"),
|
||||
"Papyrus Date": AdvData(None, "Papyrus\" Home"),
|
||||
}
|
||||
|
||||
exclusion_table = {
|
||||
"pacifist": {
|
||||
"LOVE 2",
|
||||
"LOVE 3",
|
||||
"LOVE 4",
|
||||
"LOVE 5",
|
||||
"LOVE 6",
|
||||
"LOVE 7",
|
||||
"LOVE 8",
|
||||
"LOVE 9",
|
||||
"LOVE 10",
|
||||
"LOVE 11",
|
||||
"LOVE 12",
|
||||
"LOVE 13",
|
||||
"LOVE 14",
|
||||
"LOVE 15",
|
||||
"LOVE 16",
|
||||
"LOVE 17",
|
||||
"LOVE 18",
|
||||
"LOVE 19",
|
||||
"LOVE 20",
|
||||
"ATK 2",
|
||||
"ATK 3",
|
||||
"ATK 4",
|
||||
"ATK 5",
|
||||
"ATK 6",
|
||||
"ATK 7",
|
||||
"ATK 8",
|
||||
"ATK 9",
|
||||
"ATK 10",
|
||||
"ATK 11",
|
||||
"ATK 12",
|
||||
"ATK 13",
|
||||
"ATK 14",
|
||||
"ATK 15",
|
||||
"ATK 16",
|
||||
"ATK 17",
|
||||
"ATK 18",
|
||||
"ATK 19",
|
||||
"ATK 20",
|
||||
"DEF 5",
|
||||
"DEF 9",
|
||||
"DEF 13",
|
||||
"DEF 17",
|
||||
"HP 2",
|
||||
"HP 3",
|
||||
"HP 4",
|
||||
"HP 5",
|
||||
"HP 6",
|
||||
"HP 7",
|
||||
"HP 8",
|
||||
"HP 9",
|
||||
"HP 10",
|
||||
"HP 11",
|
||||
"HP 12",
|
||||
"HP 13",
|
||||
"HP 14",
|
||||
"HP 15",
|
||||
"HP 16",
|
||||
"HP 17",
|
||||
"HP 18",
|
||||
"HP 19",
|
||||
"HP 20",
|
||||
"Snowman 2",
|
||||
"Snowman 3",
|
||||
},
|
||||
"neutral": {
|
||||
"Letter Quest",
|
||||
"Dog Sale 1",
|
||||
"Cat Sale",
|
||||
"Dog Sale 2",
|
||||
"Dog Sale 3",
|
||||
"Dog Sale 4",
|
||||
"Chisps Machine",
|
||||
"Hush Trade",
|
||||
"LOVE 2",
|
||||
"LOVE 3",
|
||||
"LOVE 4",
|
||||
"LOVE 5",
|
||||
"LOVE 6",
|
||||
"LOVE 7",
|
||||
"LOVE 8",
|
||||
"LOVE 9",
|
||||
"LOVE 10",
|
||||
"LOVE 11",
|
||||
"LOVE 12",
|
||||
"LOVE 13",
|
||||
"LOVE 14",
|
||||
"LOVE 15",
|
||||
"LOVE 16",
|
||||
"LOVE 17",
|
||||
"LOVE 18",
|
||||
"LOVE 19",
|
||||
"LOVE 20",
|
||||
"Papyrus Plot",
|
||||
"Undyne Plot",
|
||||
"True Lab Plot",
|
||||
"ATK 2",
|
||||
"ATK 3",
|
||||
"ATK 4",
|
||||
"ATK 5",
|
||||
"ATK 6",
|
||||
"ATK 7",
|
||||
"ATK 8",
|
||||
"ATK 9",
|
||||
"ATK 10",
|
||||
"ATK 11",
|
||||
"ATK 12",
|
||||
"ATK 13",
|
||||
"ATK 14",
|
||||
"ATK 15",
|
||||
"ATK 16",
|
||||
"ATK 17",
|
||||
"ATK 18",
|
||||
"ATK 19",
|
||||
"ATK 20",
|
||||
"DEF 5",
|
||||
"DEF 9",
|
||||
"DEF 13",
|
||||
"DEF 17",
|
||||
"HP 2",
|
||||
"HP 3",
|
||||
"HP 4",
|
||||
"HP 5",
|
||||
"HP 6",
|
||||
"HP 7",
|
||||
"HP 8",
|
||||
"HP 9",
|
||||
"HP 10",
|
||||
"HP 11",
|
||||
"HP 12",
|
||||
"HP 13",
|
||||
"HP 14",
|
||||
"HP 15",
|
||||
"HP 16",
|
||||
"HP 17",
|
||||
"HP 18",
|
||||
"HP 19",
|
||||
"HP 20",
|
||||
"Snowman 2",
|
||||
"Snowman 3",
|
||||
},
|
||||
"genocide": {
|
||||
"Letter Quest",
|
||||
"Dog Sale 1",
|
||||
"Cat Sale",
|
||||
"Dog Sale 2",
|
||||
"Dog Sale 3",
|
||||
"Dog Sale 4",
|
||||
"Chisps Machine",
|
||||
"Nicecream Snowdin",
|
||||
"Nicecream Waterfall",
|
||||
"Nicecream Punch Card",
|
||||
"Card Reward",
|
||||
"Apron Hidden",
|
||||
"Hush Trade",
|
||||
"Papyrus Plot",
|
||||
"Undyne Plot",
|
||||
"True Lab Plot",
|
||||
},
|
||||
"NoLove": {
|
||||
"LOVE 2",
|
||||
"LOVE 3",
|
||||
"LOVE 4",
|
||||
"LOVE 5",
|
||||
"LOVE 6",
|
||||
"LOVE 7",
|
||||
"LOVE 8",
|
||||
"LOVE 9",
|
||||
"LOVE 10",
|
||||
"LOVE 11",
|
||||
"LOVE 12",
|
||||
"LOVE 13",
|
||||
"LOVE 14",
|
||||
"LOVE 15",
|
||||
"LOVE 16",
|
||||
"LOVE 17",
|
||||
"LOVE 18",
|
||||
"LOVE 19",
|
||||
"LOVE 20",
|
||||
},
|
||||
"NoStats": {
|
||||
"ATK 2",
|
||||
"ATK 3",
|
||||
"ATK 4",
|
||||
"ATK 5",
|
||||
"ATK 6",
|
||||
"ATK 7",
|
||||
"ATK 8",
|
||||
"ATK 9",
|
||||
"ATK 10",
|
||||
"ATK 11",
|
||||
"ATK 12",
|
||||
"ATK 13",
|
||||
"ATK 14",
|
||||
"ATK 15",
|
||||
"ATK 16",
|
||||
"ATK 17",
|
||||
"ATK 18",
|
||||
"ATK 19",
|
||||
"ATK 20",
|
||||
"DEF 5",
|
||||
"DEF 9",
|
||||
"DEF 13",
|
||||
"DEF 17",
|
||||
"HP 2",
|
||||
"HP 3",
|
||||
"HP 4",
|
||||
"HP 5",
|
||||
"HP 6",
|
||||
"HP 7",
|
||||
"HP 8",
|
||||
"HP 9",
|
||||
"HP 10",
|
||||
"HP 11",
|
||||
"HP 12",
|
||||
"HP 13",
|
||||
"HP 14",
|
||||
"HP 15",
|
||||
"HP 16",
|
||||
"HP 17",
|
||||
"HP 18",
|
||||
"HP 19",
|
||||
"HP 20",
|
||||
},
|
||||
"all_routes": {
|
||||
}
|
||||
}
|
||||
|
||||
events_table = {
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import typing
|
||||
from Options import Choice, Option, Toggle, Range
|
||||
|
||||
|
||||
class RouteRequired(Choice):
|
||||
"""Main route of the game required to win."""
|
||||
display_name = "Required Route"
|
||||
option_neutral = 0
|
||||
option_pacifist = 1
|
||||
option_genocide = 2
|
||||
option_all_routes = 3
|
||||
default = 0
|
||||
|
||||
|
||||
class IncludeTemy(Toggle):
|
||||
"""Adds Temmy Armor to the item pool."""
|
||||
display_name = "Include Temy Armor"
|
||||
default = 1
|
||||
|
||||
|
||||
class KeyPieces(Range):
|
||||
"""How many Key Pieces are added to the pool, only matters with Key Piece Hunt enabled."""
|
||||
display_name = "Key Piece Amount"
|
||||
default = 5
|
||||
range_start = 1
|
||||
range_end = 10
|
||||
|
||||
|
||||
class KeyHunt(Toggle):
|
||||
"""Adds Key Pieces to the item pool, you need all of them to enter the last corridor."""
|
||||
display_name = "Key Piece Hunt"
|
||||
default = 0
|
||||
|
||||
|
||||
class ProgressiveArmor(Toggle):
|
||||
"""Makes the armor progressive."""
|
||||
display_name = "Progressive Armor"
|
||||
default = 0
|
||||
|
||||
|
||||
class ProgressiveWeapons(Toggle):
|
||||
"""Makes the weapons progressive."""
|
||||
display_name = "Progressive Weapons"
|
||||
default = 0
|
||||
|
||||
|
||||
class OnlyFlakes(Toggle):
|
||||
"""Replaces all non-required items, except equipment, with Temmie Flakes."""
|
||||
display_name = "Only Temmie Flakes"
|
||||
default = 0
|
||||
|
||||
|
||||
class NoEquips(Toggle):
|
||||
"""Removes all equippable items."""
|
||||
display_name = "No Equippables"
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeLove(Toggle):
|
||||
"""Adds LOVE to the pool. GENOCIDE ONLY!"""
|
||||
display_name = "Randomize LOVE"
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeStats(Toggle):
|
||||
"""Makes each stat increase from LV a separate item. GENOCIDE ONLY!
|
||||
Warning: This tends to spam chat with sending out checks."""
|
||||
display_name = "Randomize Stats"
|
||||
default = 0
|
||||
|
||||
|
||||
class RandoBattleOptions(Toggle):
|
||||
"""Turns the ITEM button in battle into an item you have to receive."""
|
||||
display_name = "Randomize Item Button"
|
||||
default = 0
|
||||
|
||||
|
||||
undertale_options: typing.Dict[str, type(Option)] = {
|
||||
"route_required": RouteRequired,
|
||||
"key_hunt": KeyHunt,
|
||||
"key_pieces": KeyPieces,
|
||||
"rando_love": RandomizeLove,
|
||||
"rando_stats": RandomizeStats,
|
||||
"temy_include": IncludeTemy,
|
||||
"no_equips": NoEquips,
|
||||
"only_flakes": OnlyFlakes,
|
||||
"prog_armor": ProgressiveArmor,
|
||||
"prog_weapons": ProgressiveWeapons,
|
||||
"rando_item_button": RandoBattleOptions,
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
from BaseClasses import MultiWorld
|
||||
|
||||
|
||||
def link_undertale_areas(world: MultiWorld, player: int):
|
||||
for (exit, region) in mandatory_connections:
|
||||
world.get_entrance(exit, player).connect(world.get_region(region, player))
|
||||
|
||||
|
||||
# (Region name, list of exits)
|
||||
undertale_regions = [
|
||||
("Menu", ["New Game", "??? Exit"]),
|
||||
("???", []),
|
||||
("Hub", ["Ruins Hub", "Snowdin Hub", "Waterfall Hub", "Hotland Hub", "Core Hub"]),
|
||||
("Ruins", ["Ruins Exit"]),
|
||||
("Old Home", []),
|
||||
("Snowdin Forest", ["Snowdin Forest Exit"]),
|
||||
("Snowdin Town", ["Papyrus\" Home Entrance"]),
|
||||
("Papyrus\" Home", []),
|
||||
("Waterfall", ["Undyne\"s Home Entrance"]),
|
||||
("Undyne\"s Home", []),
|
||||
("Hotland", ["Cooking Show Entrance", "Lab Elevator"]),
|
||||
("Cooking Show", ["News Show Entrance"]),
|
||||
("News Show", []),
|
||||
("True Lab", []),
|
||||
("Core", ["Core Exit"]),
|
||||
("New Home", ["New Home Exit"]),
|
||||
("Barrier", []),
|
||||
]
|
||||
|
||||
# (Entrance, region pointed to)
|
||||
mandatory_connections = [
|
||||
("??? Exit", "???"),
|
||||
("New Game", "Hub"),
|
||||
("Ruins Hub", "Ruins"),
|
||||
("Ruins Exit", "Old Home"),
|
||||
("Snowdin Forest Exit", "Snowdin Town"),
|
||||
("Papyrus\" Home Entrance", "Papyrus\" Home"),
|
||||
("Undyne\"s Home Entrance", "Undyne\"s Home"),
|
||||
("Cooking Show Entrance", "Cooking Show"),
|
||||
("News Show Entrance", "News Show"),
|
||||
("Lab Elevator", "True Lab"),
|
||||
("Core Exit", "New Home"),
|
||||
("New Home Exit", "Barrier"),
|
||||
("Snowdin Hub", "Snowdin Forest"),
|
||||
("Waterfall Hub", "Waterfall"),
|
||||
("Hotland Hub", "Hotland"),
|
||||
("Core Hub", "Core"),
|
||||
]
|
|
@ -0,0 +1,360 @@
|
|||
from ..generic.Rules import set_rule, add_rule
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
|
||||
|
||||
def _undertale_is_route(state: CollectionState, player: int, route: int):
|
||||
if route == 3:
|
||||
return state.multiworld.route_required[player].current_key == "all_routes"
|
||||
if state.multiworld.route_required[player].current_key == "all_routes":
|
||||
return True
|
||||
if route == 0:
|
||||
return state.multiworld.route_required[player].current_key == "neutral"
|
||||
if route == 1:
|
||||
return state.multiworld.route_required[player].current_key == "pacifist"
|
||||
if route == 2:
|
||||
return state.multiworld.route_required[player].current_key == "genocide"
|
||||
return False
|
||||
|
||||
|
||||
def _undertale_has_plot(state: CollectionState, player: int, item: str):
|
||||
if item == "Complete Skeleton":
|
||||
return state.has("Complete Skeleton", player)
|
||||
elif item == "Fish":
|
||||
return state.has("Fish", player)
|
||||
elif item == "Mettaton Plush":
|
||||
return state.has("Mettaton Plush", player)
|
||||
elif item == "DT Extractor":
|
||||
return state.has("DT Extractor", player)
|
||||
|
||||
|
||||
def _undertale_can_level(state: CollectionState, exp: int, lvl: int):
|
||||
if exp >= 10 and lvl == 1:
|
||||
return True
|
||||
elif exp >= 30 and lvl == 2:
|
||||
return True
|
||||
elif exp >= 70 and lvl == 3:
|
||||
return True
|
||||
elif exp >= 120 and lvl == 4:
|
||||
return True
|
||||
elif exp >= 200 and lvl == 5:
|
||||
return True
|
||||
elif exp >= 300 and lvl == 6:
|
||||
return True
|
||||
elif exp >= 500 and lvl == 7:
|
||||
return True
|
||||
elif exp >= 800 and lvl == 8:
|
||||
return True
|
||||
elif exp >= 1200 and lvl == 9:
|
||||
return True
|
||||
elif exp >= 1700 and lvl == 10:
|
||||
return True
|
||||
elif exp >= 2500 and lvl == 11:
|
||||
return True
|
||||
elif exp >= 3500 and lvl == 12:
|
||||
return True
|
||||
elif exp >= 5000 and lvl == 13:
|
||||
return True
|
||||
elif exp >= 7000 and lvl == 14:
|
||||
return True
|
||||
elif exp >= 10000 and lvl == 15:
|
||||
return True
|
||||
elif exp >= 15000 and lvl == 16:
|
||||
return True
|
||||
elif exp >= 25000 and lvl == 17:
|
||||
return True
|
||||
elif exp >= 50000 and lvl == 18:
|
||||
return True
|
||||
elif exp >= 99999 and lvl == 19:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# Sets rules on entrances and advancements that are always applied
|
||||
def set_rules(multiworld: MultiWorld, player: int):
|
||||
set_rule(multiworld.get_entrance("Ruins Hub", player), lambda state: state.has("Ruins Key", player))
|
||||
set_rule(multiworld.get_entrance("Snowdin Hub", player), lambda state: state.has("Snowdin Key", player))
|
||||
set_rule(multiworld.get_entrance("Waterfall Hub", player), lambda state: state.has("Waterfall Key", player))
|
||||
set_rule(multiworld.get_entrance("Hotland Hub", player), lambda state: state.has("Hotland Key", player))
|
||||
set_rule(multiworld.get_entrance("Core Hub", player), lambda state: state.has("Core Key", player))
|
||||
if _undertale_is_route(multiworld.state, player, 1):
|
||||
add_rule(multiworld.get_entrance("Snowdin Hub", player), lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
add_rule(multiworld.get_entrance("Waterfall Hub", player), lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
add_rule(multiworld.get_entrance("Hotland Hub", player), lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
add_rule(multiworld.get_entrance("Core Hub", player), lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
if _undertale_is_route(multiworld.state, player, 2) or _undertale_is_route(multiworld.state, player, 3):
|
||||
add_rule(multiworld.get_entrance("Snowdin Hub", player), lambda state: state.has("FIGHT", player))
|
||||
add_rule(multiworld.get_entrance("Waterfall Hub", player), lambda state: state.has("FIGHT", player))
|
||||
add_rule(multiworld.get_entrance("Hotland Hub", player), lambda state: state.has("FIGHT", player))
|
||||
add_rule(multiworld.get_entrance("Core Hub", player), lambda state: state.has("FIGHT", player))
|
||||
if _undertale_is_route(multiworld.state, player, 0):
|
||||
add_rule(multiworld.get_entrance("Snowdin Hub", player), lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
add_rule(multiworld.get_entrance("Waterfall Hub", player), lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
add_rule(multiworld.get_entrance("Hotland Hub", player), lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
add_rule(multiworld.get_entrance("Core Hub", player), lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
set_rule(multiworld.get_entrance("Core Exit", player),
|
||||
lambda state: _undertale_has_plot(state, player, "Mettaton Plush"))
|
||||
set_rule(multiworld.get_entrance("New Home Exit", player),
|
||||
lambda state: (state.has("Left Home Key", player) and
|
||||
state.has("Right Home Key", player)) or
|
||||
state.has("Key Piece", player, state.multiworld.key_pieces[player]))
|
||||
if _undertale_is_route(multiworld.state, player, 1):
|
||||
set_rule(multiworld.get_entrance("Papyrus\" Home Entrance", player),
|
||||
lambda state: _undertale_has_plot(state, player, "Complete Skeleton"))
|
||||
set_rule(multiworld.get_entrance("Undyne\"s Home Entrance", player),
|
||||
lambda state: _undertale_has_plot(state, player, "Fish") and state.has("Papyrus Date", player))
|
||||
set_rule(multiworld.get_entrance("Lab Elevator", player),
|
||||
lambda state: state.has("Alphys Date", player) and _undertale_has_plot(state, player, "DT Extractor"))
|
||||
set_rule(multiworld.get_location("Alphys Date", player),
|
||||
lambda state: state.has("Undyne Letter EX", player) and state.has("Undyne Date", player))
|
||||
set_rule(multiworld.get_location("Papyrus Plot", player),
|
||||
lambda state: state.can_reach("Snowdin Town", "Region", player))
|
||||
set_rule(multiworld.get_location("Undyne Plot", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("True Lab Plot", player),
|
||||
lambda state: state.can_reach("New Home", "Region", player)
|
||||
and state.can_reach("Letter Quest", "Location", player))
|
||||
set_rule(multiworld.get_location("Chisps Machine", player),
|
||||
lambda state: state.can_reach("True Lab", "Region", player))
|
||||
set_rule(multiworld.get_location("Dog Sale 1", player),
|
||||
lambda state: state.can_reach("Cooking Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Cat Sale", player),
|
||||
lambda state: state.can_reach("Cooking Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Dog Sale 2", player),
|
||||
lambda state: state.can_reach("Cooking Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Dog Sale 3", player),
|
||||
lambda state: state.can_reach("Cooking Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Dog Sale 4", player),
|
||||
lambda state: state.can_reach("Cooking Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Hush Trade", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player) and state.has("Hot Dog...?", player, 1))
|
||||
set_rule(multiworld.get_location("Letter Quest", player),
|
||||
lambda state: state.can_reach("New Home Exit", "Entrance", player))
|
||||
if (not _undertale_is_route(multiworld.state, player, 2)) or _undertale_is_route(multiworld.state, player, 3):
|
||||
set_rule(multiworld.get_location("Nicecream Punch Card", player),
|
||||
lambda state: state.has("Punch Card", player, 3) and state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Nicecream Snowdin", player),
|
||||
lambda state: state.can_reach("Snowdin Town", "Region", player))
|
||||
set_rule(multiworld.get_location("Nicecream Waterfall", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Card Reward", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Apron Hidden", player),
|
||||
lambda state: state.can_reach("Cooking Show", "Region", player))
|
||||
if _undertale_is_route(multiworld.state, player, 2) and \
|
||||
(multiworld.rando_love[player] or multiworld.rando_stats[player]):
|
||||
maxlv = 1
|
||||
exp = 190
|
||||
curarea = "Old Home"
|
||||
|
||||
while maxlv < 20:
|
||||
maxlv += 1
|
||||
if multiworld.rando_love[player]:
|
||||
set_rule(multiworld.get_location(("LOVE " + str(maxlv)), player), lambda state: False)
|
||||
if multiworld.rando_stats[player]:
|
||||
set_rule(multiworld.get_location(("ATK "+str(maxlv)), player), lambda state: False)
|
||||
set_rule(multiworld.get_location(("HP "+str(maxlv)), player), lambda state: False)
|
||||
if maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
set_rule(multiworld.get_location(("DEF "+str(maxlv)), player), lambda state: False)
|
||||
maxlv = 1
|
||||
while maxlv < 20:
|
||||
while _undertale_can_level(multiworld.state, exp, maxlv):
|
||||
maxlv += 1
|
||||
if multiworld.rando_stats[player]:
|
||||
if curarea == "Old Home":
|
||||
add_rule(multiworld.get_location(("ATK "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Old Home", "Region", player)), combine="or")
|
||||
add_rule(multiworld.get_location(("HP "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Old Home", "Region", player)), combine="or")
|
||||
if maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
add_rule(multiworld.get_location(("DEF "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Old Home", "Region", player)), combine="or")
|
||||
elif curarea == "Snowdin Town":
|
||||
add_rule(multiworld.get_location(("ATK "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Snowdin Town", "Region", player)), combine="or")
|
||||
add_rule(multiworld.get_location(("HP "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Snowdin Town", "Region", player)), combine="or")
|
||||
if maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
add_rule(multiworld.get_location(("DEF "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Snowdin Town", "Region", player)), combine="or")
|
||||
elif curarea == "Waterfall":
|
||||
add_rule(multiworld.get_location(("ATK "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Waterfall", "Region", player)), combine="or")
|
||||
add_rule(multiworld.get_location(("HP "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Waterfall", "Region", player)), combine="or")
|
||||
if maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
add_rule(multiworld.get_location(("DEF "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Waterfall", "Region", player)), combine="or")
|
||||
elif curarea == "News Show":
|
||||
add_rule(multiworld.get_location(("ATK "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("News Show", "Region", player)), combine="or")
|
||||
add_rule(multiworld.get_location(("HP "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("News Show", "Region", player)), combine="or")
|
||||
if maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
add_rule(multiworld.get_location(("DEF "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("News Show", "Region", player)), combine="or")
|
||||
elif curarea == "Core":
|
||||
add_rule(multiworld.get_location(("ATK "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Core Exit", "Entrance", player)), combine="or")
|
||||
add_rule(multiworld.get_location(("HP "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Core Exit", "Entrance", player)), combine="or")
|
||||
if maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
add_rule(multiworld.get_location(("DEF "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Core Exit", "Entrance", player)), combine="or")
|
||||
elif curarea == "Sans":
|
||||
add_rule(multiworld.get_location(("ATK "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("New Home Exit", "Entrance", player)), combine="or")
|
||||
add_rule(multiworld.get_location(("HP "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("New Home Exit", "Entrance", player)), combine="or")
|
||||
if maxlv == 9 or maxlv == 13 or maxlv == 17:
|
||||
add_rule(multiworld.get_location(("DEF "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("New Home Exit", "Entrance", player)), combine="or")
|
||||
if multiworld.rando_love[player]:
|
||||
if curarea == "Old Home":
|
||||
add_rule(multiworld.get_location(("LOVE "+str(maxlv)), player),
|
||||
lambda state: ( state.can_reach("Old Home", "Region", player)), combine="or")
|
||||
elif curarea == "Snowdin Town":
|
||||
add_rule(multiworld.get_location(("LOVE "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Snowdin Town", "Region", player)), combine="or")
|
||||
elif curarea == "Waterfall":
|
||||
add_rule(multiworld.get_location(("LOVE "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Waterfall", "Region", player)), combine="or")
|
||||
elif curarea == "News Show":
|
||||
add_rule(multiworld.get_location(("LOVE "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("News Show", "Region", player)), combine="or")
|
||||
elif curarea == "Core":
|
||||
add_rule(multiworld.get_location(("LOVE "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("Core Exit", "Entrance", player)), combine="or")
|
||||
elif curarea == "Sans":
|
||||
add_rule(multiworld.get_location(("LOVE "+str(maxlv)), player),
|
||||
lambda state: (state.can_reach("New Home Exit", "Entrance", player)), combine="or")
|
||||
if curarea == "Old Home":
|
||||
curarea = "Snowdin Town"
|
||||
maxlv = 1
|
||||
exp = 407
|
||||
elif curarea == "Snowdin Town":
|
||||
curarea = "Waterfall"
|
||||
maxlv = 1
|
||||
exp = 1643
|
||||
elif curarea == "Waterfall":
|
||||
curarea = "News Show"
|
||||
maxlv = 1
|
||||
exp = 3320
|
||||
elif curarea == "News Show":
|
||||
curarea = "Core"
|
||||
maxlv = 1
|
||||
exp = 50000
|
||||
elif curarea == "Core":
|
||||
curarea = "Sans"
|
||||
maxlv = 1
|
||||
exp = 99999
|
||||
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))
|
||||
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))
|
||||
set_rule(multiworld.get_location("Cider Sale", player),
|
||||
lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
set_rule(multiworld.get_location("Ribbon Cracks", player),
|
||||
lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
set_rule(multiworld.get_location("Toy Knife Edge", player),
|
||||
lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
set_rule(multiworld.get_location("B.Scotch Pie Given", player),
|
||||
lambda state: state.has("ACT", player) and state.has("MERCY", player))
|
||||
if _undertale_is_route(multiworld.state, player, 2) or _undertale_is_route(multiworld.state, player, 3):
|
||||
set_rule(multiworld.get_location("Donut Sale", player),
|
||||
lambda state: state.has("FIGHT", player))
|
||||
set_rule(multiworld.get_location("Cider Sale", player),
|
||||
lambda state: state.has("FIGHT", player))
|
||||
set_rule(multiworld.get_location("Ribbon Cracks", player),
|
||||
lambda state: state.has("FIGHT", player))
|
||||
set_rule(multiworld.get_location("Toy Knife Edge", player),
|
||||
lambda state: state.has("FIGHT", player))
|
||||
set_rule(multiworld.get_location("B.Scotch Pie Given", player),
|
||||
lambda state: state.has("FIGHT", player))
|
||||
if _undertale_is_route(multiworld.state, player, 0):
|
||||
set_rule(multiworld.get_location("Donut Sale", player),
|
||||
lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
set_rule(multiworld.get_location("Cider Sale", player),
|
||||
lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
set_rule(multiworld.get_location("Ribbon Cracks", player),
|
||||
lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
set_rule(multiworld.get_location("Toy Knife Edge", player),
|
||||
lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
set_rule(multiworld.get_location("B.Scotch Pie Given", player),
|
||||
lambda state: ((state.has("ACT", player) and state.has("MERCY", player)) or state.has("FIGHT", player)))
|
||||
set_rule(multiworld.get_location("Mettaton Plot", player),
|
||||
lambda state: state.can_reach("Core Exit", "Entrance", player))
|
||||
set_rule(multiworld.get_location("Bunny 1", player),
|
||||
lambda state: state.can_reach("Snowdin Town", "Region", player))
|
||||
set_rule(multiworld.get_location("Bunny 2", player),
|
||||
lambda state: state.can_reach("Snowdin Town", "Region", player))
|
||||
set_rule(multiworld.get_location("Bunny 3", player),
|
||||
lambda state: state.can_reach("Snowdin Town", "Region", player))
|
||||
set_rule(multiworld.get_location("Bunny 4", player),
|
||||
lambda state: state.can_reach("Snowdin Town", "Region", player))
|
||||
set_rule(multiworld.get_location("Astro 1", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Astro 2", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Gerson 1", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Gerson 2", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Gerson 3", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Gerson 4", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Present Knife", player),
|
||||
lambda state: state.can_reach("New Home", "Region", player))
|
||||
set_rule(multiworld.get_location("Present Locket", player),
|
||||
lambda state: state.can_reach("New Home", "Region", player))
|
||||
set_rule(multiworld.get_location("Left New Home Key", player),
|
||||
lambda state: state.can_reach("New Home", "Region", player))
|
||||
set_rule(multiworld.get_location("Right New Home Key", player),
|
||||
lambda state: state.can_reach("New Home", "Region", player))
|
||||
set_rule(multiworld.get_location("Trash Burger", player),
|
||||
lambda state: state.can_reach("Core", "Region", player))
|
||||
set_rule(multiworld.get_location("Quiche Bench", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Tutu Hidden", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("Grass Shoes", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("TemmieShop 1", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("TemmieShop 2", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("TemmieShop 3", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player))
|
||||
set_rule(multiworld.get_location("TemmieShop 4", player),
|
||||
lambda state: state.can_reach("Waterfall", "Region", player) and state.has("1000G", player, 2))
|
||||
set_rule(multiworld.get_location("Noodles Fridge", player),
|
||||
lambda state: state.can_reach("Hotland", "Region", player))
|
||||
set_rule(multiworld.get_location("Pan Hidden", player),
|
||||
lambda state: state.can_reach("Hotland", "Region", player))
|
||||
set_rule(multiworld.get_location("Bratty Catty 1", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Bratty Catty 2", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Bratty Catty 3", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Bratty Catty 4", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Burgerpants 1", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Burgerpants 2", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Burgerpants 3", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
set_rule(multiworld.get_location("Burgerpants 4", player),
|
||||
lambda state: state.can_reach("News Show", "Region", player))
|
||||
|
||||
|
||||
# Sets rules on completion condition
|
||||
def set_completion_rules(multiworld: MultiWorld, player: int):
|
||||
completion_requirements = lambda state: state.can_reach("New Home Exit", "Entrance", player) and state.has("FIGHT", player)
|
||||
if _undertale_is_route(multiworld.state, player, 1):
|
||||
completion_requirements = lambda state: state.can_reach("True Lab", "Region", player)
|
||||
|
||||
multiworld.completion_condition[player] = lambda state: completion_requirements(state)
|
|
@ -0,0 +1,228 @@
|
|||
from .Items import UndertaleItem, item_table, required_armor, required_weapons, non_key_items, key_items, \
|
||||
junk_weights_all, plot_items, junk_weights_neutral, junk_weights_pacifist, junk_weights_genocide
|
||||
from .Locations import UndertaleAdvancement, advancement_table, exclusion_table
|
||||
from .Regions import undertale_regions, link_undertale_areas
|
||||
from .Rules import set_rules, set_completion_rules
|
||||
from worlds.generic.Rules import exclusion_rules
|
||||
from BaseClasses import Region, Entrance, Tutorial, Item
|
||||
from .Options import undertale_options
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import Component, components, Type
|
||||
from multiprocessing import Process
|
||||
|
||||
|
||||
def run_client():
|
||||
print('running undertale client')
|
||||
from UndertaleClient import main # lazy import
|
||||
p = Process(target=main)
|
||||
p.start()
|
||||
|
||||
|
||||
components.append(Component("Undertale Client", "UndertaleClient"))
|
||||
|
||||
|
||||
def data_path(file_name: str):
|
||||
import pkgutil
|
||||
return pkgutil.get_data(__name__, "data/" + file_name)
|
||||
|
||||
|
||||
class UndertaleWeb(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Tutorial",
|
||||
"A guide to setting up the Archipelago Undertale software on your computer. This guide covers "
|
||||
"single-player, multiworld, and related software.",
|
||||
"English",
|
||||
"undertale_en.md",
|
||||
"undertale/en",
|
||||
["Mewlif"]
|
||||
)]
|
||||
|
||||
|
||||
class UndertaleWorld(World):
|
||||
"""
|
||||
Undertale is an RPG where every choice you make matters. You could choose to hurt all the enemies, eventually
|
||||
causing genocide of the monster species. Or you can spare all the enemies, befriending them and freeing them
|
||||
from their underground prison.
|
||||
"""
|
||||
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
|
||||
|
||||
def _get_undertale_data(self):
|
||||
return {
|
||||
"world_seed": self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
||||
"seed_name": self.multiworld.seed_name,
|
||||
"player_name": self.multiworld.get_player_name(self.player),
|
||||
"player_id": self.player,
|
||||
"client_version": self.required_client_version,
|
||||
"race": self.multiworld.is_race,
|
||||
"route": self.multiworld.route_required[self.player].current_key,
|
||||
"temy_armor_include": bool(self.multiworld.temy_include[self.player].value),
|
||||
"only_flakes": bool(self.multiworld.only_flakes[self.player].value),
|
||||
"no_equips": bool(self.multiworld.no_equips[self.player].value),
|
||||
"key_hunt": bool(self.multiworld.key_hunt[self.player].value),
|
||||
"key_pieces": self.multiworld.key_pieces[self.player].value,
|
||||
"rando_love": bool(self.multiworld.rando_love[self.player].value),
|
||||
"rando_stats": bool(self.multiworld.rando_stats[self.player].value),
|
||||
"prog_armor": bool(self.multiworld.prog_armor[self.player].value),
|
||||
"prog_weapons": bool(self.multiworld.prog_weapons[self.player].value),
|
||||
"rando_item_button": bool(self.multiworld.rando_item_button[self.player].value)
|
||||
}
|
||||
|
||||
def create_items(self):
|
||||
self.multiworld.get_location("Undyne Date", self.player).place_locked_item(self.create_item("Undyne Date"))
|
||||
self.multiworld.get_location("Alphys Date", self.player).place_locked_item(self.create_item("Alphys Date"))
|
||||
self.multiworld.get_location("Papyrus Date", self.player).place_locked_item(self.create_item("Papyrus Date"))
|
||||
# Generate item pool
|
||||
itempool = []
|
||||
if self.multiworld.route_required[self.player] == "all_routes":
|
||||
junk_pool = junk_weights_all.copy()
|
||||
elif self.multiworld.route_required[self.player] == "genocide":
|
||||
junk_pool = junk_weights_genocide.copy()
|
||||
elif self.multiworld.route_required[self.player] == "neutral":
|
||||
junk_pool = junk_weights_neutral.copy()
|
||||
elif self.multiworld.route_required[self.player] == "pacifist":
|
||||
junk_pool = junk_weights_pacifist.copy()
|
||||
else:
|
||||
junk_pool = junk_weights_all.copy()
|
||||
# Add all required progression items
|
||||
for name, num in key_items.items():
|
||||
itempool += [name] * num
|
||||
for name, num in required_armor.items():
|
||||
itempool += [name] * num
|
||||
for name, num in required_weapons.items():
|
||||
itempool += [name] * num
|
||||
for name, num in non_key_items.items():
|
||||
itempool += [name] * num
|
||||
if self.multiworld.rando_item_button[self.player]:
|
||||
itempool += ["ITEM"]
|
||||
else:
|
||||
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
|
||||
item != "Nice Cream" and item != "Hot Cat" and item != "Hot Dog...?" and item != "Punch Card"]
|
||||
elif self.multiworld.route_required[self.player] == "neutral":
|
||||
itempool = [item for item in itempool if item != "Popato Chisps" and item != "Hot Cat" and
|
||||
item != "Hot Dog...?"]
|
||||
if self.multiworld.route_required[self.player] == "pacifist" or \
|
||||
self.multiworld.route_required[self.player] == "all_routes":
|
||||
itempool += ["Undyne Letter EX"]
|
||||
else:
|
||||
itempool.remove("Complete Skeleton")
|
||||
itempool.remove("Fish")
|
||||
itempool.remove("DT Extractor")
|
||||
itempool.remove("Hush Puppy")
|
||||
if self.multiworld.key_hunt[self.player]:
|
||||
itempool += ["Key Piece"] * self.multiworld.key_pieces[self.player].value
|
||||
else:
|
||||
itempool += ["Left Home Key"]
|
||||
itempool += ["Right Home Key"]
|
||||
if not self.multiworld.rando_love[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
itempool = [item for item in itempool if not item == "LOVE"]
|
||||
if not self.multiworld.rando_stats[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
itempool = [item for item in itempool if not (item == "ATK Up" or item == "DEF Up" or item == "HP Up")]
|
||||
if self.multiworld.temy_include[self.player]:
|
||||
itempool += ["temy armor"]
|
||||
if self.multiworld.no_equips[self.player]:
|
||||
itempool = [item for item in itempool if item not in required_armor and item not in required_weapons]
|
||||
else:
|
||||
if self.multiworld.prog_armor[self.player]:
|
||||
itempool = [item if (item not in required_armor and not item == "temy armor") else
|
||||
"Progressive Armor" for item in itempool]
|
||||
if self.multiworld.prog_weapons[self.player]:
|
||||
itempool = [item if item not in required_weapons else "Progressive Weapons" for item in itempool]
|
||||
if self.multiworld.route_required[self.player] == "genocide" or \
|
||||
self.multiworld.route_required[self.player] == "all_routes":
|
||||
if not self.multiworld.only_flakes[self.player]:
|
||||
itempool += ["Snowman Piece"] * 2
|
||||
if not self.multiworld.no_equips[self.player]:
|
||||
itempool = ["Real Knife" if item == "Worn Dagger" else "The Locket"
|
||||
if item == "Heart Locket" else item for item in itempool]
|
||||
if self.multiworld.only_flakes[self.player]:
|
||||
itempool = [item for item in itempool if item not in non_key_items]
|
||||
# Choose locations to automatically exclude based on settings
|
||||
exclusion_pool = set()
|
||||
exclusion_pool.update(exclusion_table[self.multiworld.route_required[self.player].current_key])
|
||||
if not self.multiworld.rando_love[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
exclusion_pool.update(exclusion_table["NoLove"])
|
||||
if not self.multiworld.rando_stats[self.player] or \
|
||||
(self.multiworld.route_required[self.player] != "genocide" and
|
||||
self.multiworld.route_required[self.player] != "all_routes"):
|
||||
exclusion_pool.update(exclusion_table["NoStats"])
|
||||
|
||||
# Choose locations to automatically exclude based on settings
|
||||
exclusion_checks = set()
|
||||
exclusion_checks.update(["Nicecream Punch Card", "Hush Trade"])
|
||||
exclusion_rules(self.multiworld, self.player, exclusion_checks)
|
||||
|
||||
# Fill remaining items with randomly generated junk or Temmie Flakes
|
||||
if not self.multiworld.only_flakes[self.player]:
|
||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
||||
k=len(self.location_names)-len(itempool)-len(exclusion_pool))
|
||||
else:
|
||||
itempool += ["Temmie Flakes"] * (len(self.location_names) - len(itempool) - len(exclusion_pool))
|
||||
# Convert itempool into real items
|
||||
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
||||
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.multiworld, self.player)
|
||||
set_completion_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
def UndertaleRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, self.player, self.multiworld)
|
||||
ret.locations = [UndertaleAdvancement(self.player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name and
|
||||
(loc_name not in exclusion_table["NoStats"] or
|
||||
(self.multiworld.rando_stats[self.player] and
|
||||
(self.multiworld.route_required[self.player] == "genocide" or
|
||||
self.multiworld.route_required[self.player] == "all_routes"))) and
|
||||
(loc_name not in exclusion_table["NoLove"] or
|
||||
(self.multiworld.rando_love[self.player] and
|
||||
(self.multiworld.route_required[self.player] == "genocide" or
|
||||
self.multiworld.route_required[self.player] == "all_routes"))) and
|
||||
loc_name not in exclusion_table[self.multiworld.route_required[self.player].current_key]]
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(self.player, exit, ret))
|
||||
return ret
|
||||
|
||||
self.multiworld.regions += [UndertaleRegion(*r) for r in undertale_regions]
|
||||
link_undertale_areas(self.multiworld, self.player)
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = self._get_undertale_data()
|
||||
for option_name in undertale_options:
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if (option_name == "rando_love" or option_name == "rando_stats") and \
|
||||
self.multiworld.route_required[self.player] != "genocide" and \
|
||||
self.multiworld.route_required[self.player] != "all_routes":
|
||||
option.value = False
|
||||
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_data = item_table[name]
|
||||
item = UndertaleItem(name, item_data.classification, item_data.code, self.player)
|
||||
return item
|
Binary file not shown.
|
@ -0,0 +1,21 @@
|
|||
# Undertale
|
||||
|
||||
## Where is the settings page?
|
||||
|
||||
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
|
||||
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.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
When the player receives an item in Undertale, it will go into their inventory if they have space, otherwise it will
|
||||
wait until they do have space. That includes items that don't appear in your inventory.
|
||||
|
||||
## 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`.
|
|
@ -0,0 +1,33 @@
|
|||
# Undertale Randomizer Setup Guide
|
||||
|
||||
### Required Software
|
||||
|
||||
- Undertale from the [Steam page](https://store.steampowered.com/app/391540)
|
||||
- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
- (select `Undertale Client` during installation.)
|
||||
|
||||
### First time setup
|
||||
|
||||
Start the Undertale client, and in the bottom text box, input `/auto_patch (Input your Undertale install directory here)` (It is usually located at `C:\Program Files\Steam\steamapps\Undertale`, but it can be different, you can more easily find the directory
|
||||
by opening the Undertale directory through Steam), it will then make an Undertale folder that will be created in the
|
||||
Archipelago install location. That contains the version of Undertale you will use for Archipelago. (You will need to
|
||||
redo this step when updating Archipelago.)
|
||||
|
||||
### Connect to the MultiServer
|
||||
|
||||
Make sure both Undertale and its client are running. (Undertale will ask for a saveslot, it can be 1 through 99, none
|
||||
of the slots will overwrite your vanilla save, although you may want to make a backup just in case.)
|
||||
|
||||
In the top text box of the client, type the
|
||||
`Ip Address` (or `Hostname`) and `Port` separated with a `:` symbol. (Ex. `archipelago.gg:38281`)
|
||||
|
||||
The client will then ask for the slot name, input that in the text box at the bottom of the client.
|
||||
|
||||
### Play the game
|
||||
|
||||
When the console tells you that you have joined the room, you're all set. Congratulations on successfully joining a
|
||||
multiworld game!
|
||||
|
||||
### Where do I get a YAML file?
|
||||
|
||||
You can customize your settings by visiting the [Undertale Player Settings Page](/games/Undertale/player-settings)
|
Loading…
Reference in New Issue