From 3f53502a5907ed1982d28a392c54331f0c1c2c42 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 25 Sep 2025 12:09:50 -0400 Subject: Move the client into the apworld Only works on source right now, not as an apworld. --- apworld/__init__.py | 43 ++ apworld/client/animationListener.gd | 38 ++ apworld/client/client.gd | 417 +++++++++++++++++ apworld/client/collectable.gd | 16 + apworld/client/compass.gd | 66 +++ apworld/client/compass_overlay.gd | 17 + apworld/client/door.gd | 46 ++ apworld/client/gamedata.gd | 159 +++++++ apworld/client/keyHolder.gd | 38 ++ apworld/client/keyHolderChecker.gd | 24 + apworld/client/keyHolderResetterListener.gd | 8 + apworld/client/keyboard.gd | 199 +++++++++ apworld/client/locationListener.gd | 20 + apworld/client/main.gd | 284 ++++++++++++ apworld/client/manager.gd | 571 ++++++++++++++++++++++++ apworld/client/messages.gd | 74 +++ apworld/client/minimap.gd | 175 ++++++++ apworld/client/painting.gd | 38 ++ apworld/client/panel.gd | 101 +++++ apworld/client/pauseMenu.gd | 51 +++ apworld/client/player.gd | 369 +++++++++++++++ apworld/client/rainbowText.gd | 10 + apworld/client/run_from_source.tscn | 23 + apworld/client/saver.gd | 23 + apworld/client/settings_screen.tscn | 173 +++++++ apworld/client/source_runtime.gd | 15 + apworld/client/teleport.gd | 38 ++ apworld/client/teleportListener.gd | 49 ++ apworld/client/textclient.gd | 95 ++++ apworld/client/vendor/LICENSE | 21 + apworld/client/vendor/uuid.gd | 195 ++++++++ apworld/client/victoryListener.gd | 20 + apworld/client/visibilityListener.gd | 38 ++ apworld/client/worldport.gd | 53 +++ apworld/client/worldportListener.gd | 8 + client/Archipelago/animationListener.gd | 38 -- client/Archipelago/client.gd | 417 ----------------- client/Archipelago/collectable.gd | 16 - client/Archipelago/compass.gd | 66 --- client/Archipelago/compass_overlay.gd | 17 - client/Archipelago/door.gd | 46 -- client/Archipelago/gamedata.gd | 159 ------- client/Archipelago/keyHolder.gd | 38 -- client/Archipelago/keyHolderChecker.gd | 24 - client/Archipelago/keyHolderResetterListener.gd | 8 - client/Archipelago/keyboard.gd | 199 --------- client/Archipelago/locationListener.gd | 20 - client/Archipelago/manager.gd | 571 ------------------------ client/Archipelago/messages.gd | 74 --- client/Archipelago/minimap.gd | 175 -------- client/Archipelago/painting.gd | 38 -- client/Archipelago/panel.gd | 101 ----- client/Archipelago/pauseMenu.gd | 44 -- client/Archipelago/player.gd | 369 --------------- client/Archipelago/rainbowText.gd | 10 - client/Archipelago/saver.gd | 23 - client/Archipelago/settings_buttons.gd | 24 - client/Archipelago/settings_screen.gd | 261 ----------- client/Archipelago/teleport.gd | 38 -- client/Archipelago/teleportListener.gd | 49 -- client/Archipelago/textclient.gd | 95 ---- client/Archipelago/vendor/LICENSE | 21 - client/Archipelago/vendor/uuid.gd | 195 -------- client/Archipelago/victoryListener.gd | 20 - client/Archipelago/visibilityListener.gd | 38 -- client/Archipelago/worldport.gd | 53 --- client/Archipelago/worldportListener.gd | 8 - 67 files changed, 3515 insertions(+), 3255 deletions(-) create mode 100644 apworld/client/animationListener.gd create mode 100644 apworld/client/client.gd create mode 100644 apworld/client/collectable.gd create mode 100644 apworld/client/compass.gd create mode 100644 apworld/client/compass_overlay.gd create mode 100644 apworld/client/door.gd create mode 100644 apworld/client/gamedata.gd create mode 100644 apworld/client/keyHolder.gd create mode 100644 apworld/client/keyHolderChecker.gd create mode 100644 apworld/client/keyHolderResetterListener.gd create mode 100644 apworld/client/keyboard.gd create mode 100644 apworld/client/locationListener.gd create mode 100644 apworld/client/main.gd create mode 100644 apworld/client/manager.gd create mode 100644 apworld/client/messages.gd create mode 100644 apworld/client/minimap.gd create mode 100644 apworld/client/painting.gd create mode 100644 apworld/client/panel.gd create mode 100644 apworld/client/pauseMenu.gd create mode 100644 apworld/client/player.gd create mode 100644 apworld/client/rainbowText.gd create mode 100644 apworld/client/run_from_source.tscn create mode 100644 apworld/client/saver.gd create mode 100644 apworld/client/settings_screen.tscn create mode 100644 apworld/client/source_runtime.gd create mode 100644 apworld/client/teleport.gd create mode 100644 apworld/client/teleportListener.gd create mode 100644 apworld/client/textclient.gd create mode 100644 apworld/client/vendor/LICENSE create mode 100644 apworld/client/vendor/uuid.gd create mode 100644 apworld/client/victoryListener.gd create mode 100644 apworld/client/visibilityListener.gd create mode 100644 apworld/client/worldport.gd create mode 100644 apworld/client/worldportListener.gd delete mode 100644 client/Archipelago/animationListener.gd delete mode 100644 client/Archipelago/client.gd delete mode 100644 client/Archipelago/collectable.gd delete mode 100644 client/Archipelago/compass.gd delete mode 100644 client/Archipelago/compass_overlay.gd delete mode 100644 client/Archipelago/door.gd delete mode 100644 client/Archipelago/gamedata.gd delete mode 100644 client/Archipelago/keyHolder.gd delete mode 100644 client/Archipelago/keyHolderChecker.gd delete mode 100644 client/Archipelago/keyHolderResetterListener.gd delete mode 100644 client/Archipelago/keyboard.gd delete mode 100644 client/Archipelago/locationListener.gd delete mode 100644 client/Archipelago/manager.gd delete mode 100644 client/Archipelago/messages.gd delete mode 100644 client/Archipelago/minimap.gd delete mode 100644 client/Archipelago/painting.gd delete mode 100644 client/Archipelago/panel.gd delete mode 100644 client/Archipelago/pauseMenu.gd delete mode 100644 client/Archipelago/player.gd delete mode 100644 client/Archipelago/rainbowText.gd delete mode 100644 client/Archipelago/saver.gd delete mode 100644 client/Archipelago/settings_buttons.gd delete mode 100644 client/Archipelago/settings_screen.gd delete mode 100644 client/Archipelago/teleport.gd delete mode 100644 client/Archipelago/teleportListener.gd delete mode 100644 client/Archipelago/textclient.gd delete mode 100644 client/Archipelago/vendor/LICENSE delete mode 100644 client/Archipelago/vendor/uuid.gd delete mode 100644 client/Archipelago/victoryListener.gd delete mode 100644 client/Archipelago/visibilityListener.gd delete mode 100644 client/Archipelago/worldport.gd delete mode 100644 client/Archipelago/worldportListener.gd diff --git a/apworld/__init__.py b/apworld/__init__.py index 2213e33..6ac2926 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py @@ -1,7 +1,15 @@ """ Archipelago init file for Lingo 2 """ +import asyncio +import os.path +import subprocess +from typing import ClassVar + +import Utils +import settings from BaseClasses import ItemClassification, Item, Tutorial +from settings import Group, UserFilePath from worlds.AutoWorld import WebWorld, World from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS from .options import Lingo2Options @@ -9,6 +17,30 @@ from .player_logic import Lingo2PlayerLogic from .regions import create_regions, shuffle_entrances, connect_ports_from_ut from .static_logic import Lingo2StaticLogic from .version import APWORLD_VERSION +from ..LauncherComponents import Component, Type, components + + +async def run_game(): + exe_file = settings.get_settings().lingo2_options.exe_file + + subprocess.Popen( + [ + exe_file, + "--scene", + Utils.local_path("worlds", "lingo2", "client", "run_from_source.tscn"), + "--", + Utils.local_path("worlds", "lingo2", "client"), + ], + cwd=os.path.dirname(exe_file), + ) + + +async def client_main(): + Utils.async_start(run_game()) + + +def launch_client(*args): + asyncio.run(client_main()) class Lingo2WebWorld(WebWorld): @@ -24,6 +56,14 @@ class Lingo2WebWorld(WebWorld): )] +class Lingo2Settings(Group): + class ExecutableFile(UserFilePath): + """Path to the Lingo 2 executable""" + is_exe = True + + exe_file: ExecutableFile = ExecutableFile() + + class Lingo2World(World): """ Lingo 2 is a first person indie puzzle game where you solve word puzzles in a labyrinthe world. Compared to its @@ -33,6 +73,9 @@ class Lingo2World(World): game = "Lingo 2" web = Lingo2WebWorld() + settings: ClassVar[Lingo2Settings] + settings_key = "lingo2_options" + topology_present = True options_dataclass = Lingo2Options diff --git a/apworld/client/animationListener.gd b/apworld/client/animationListener.gd new file mode 100644 index 0000000..c3b26db --- /dev/null +++ b/apworld/client/animationListener.gd @@ -0,0 +1,38 @@ +extends "res://scripts/nodes/listeners/animationListener.gd" + +var item_id +var item_amount + + +func _ready(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + var gamedata = global.get_node("Gamedata") + var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) + if door_id != null: + var ap = global.get_node("Archipelago") + var item_lock = ap.get_item_id_for_door(door_id) + + if item_lock != null: + item_id = item_lock[0] + item_amount = item_lock[1] + + self.senders = [] + self.senderGroup = [] + self.nested = false + self.complete_at = 0 + self.max_length = 0 + self.excludeSenders = [] + + call_deferred("_readier") + + super._ready() + + +func _readier(): + var ap = global.get_node("Archipelago") + + if ap.client.getItemAmount(item_id) >= item_amount: + handleTriggered() diff --git a/apworld/client/client.gd b/apworld/client/client.gd new file mode 100644 index 0000000..843647d --- /dev/null +++ b/apworld/client/client.gd @@ -0,0 +1,417 @@ +extends Node + +const ap_version = {"major": 0, "minor": 6, "build": 3, "class": "Version"} + +var SCRIPT_uuid + +var _ws = WebSocketPeer.new() +var _should_process = false +var _initiated_disconnect = false +var _try_wss = false +var _has_connected = false + +var _datapackages = {} +var _pending_packages = [] +var _item_id_to_name = {} # All games +var _location_id_to_name = {} # All games +var _item_name_to_id = {} # Lingo 2 only +var _location_name_to_id = {} # Lingo 2 only + +var _remote_version = {"major": 0, "minor": 0, "build": 0} +var _gen_version = {"major": 0, "minor": 0, "build": 0} + +var ap_server = "" +var ap_user = "" +var ap_pass = "" + +var _authenticated = false +var _seed = "" +var _team = 0 +var _slot = 0 +var _players = [] +var _player_name_by_slot = {} +var _game_by_player = {} +var _checked_locations = [] +var _received_indexes = [] +var _received_items = {} +var _slot_data = {} + +signal could_not_connect +signal connect_status +signal client_connected(slot_data) +signal item_received(item_id, index, player, flags, amount) +signal message_received(message) +signal location_scout_received(item_id, location_id, player, flags) + + +func _init(): + set_process_mode(Node.PROCESS_MODE_ALWAYS) + + _ws.inbound_buffer_size = 8388608 + + global._print("Instantiated APClient") + + # Read AP datapackages from file, if there are any + if FileAccess.file_exists("user://ap_datapackages"): + var file = FileAccess.open("user://ap_datapackages", FileAccess.READ) + var data = file.get_var(true) + file.close() + + if typeof(data) != TYPE_DICTIONARY: + global._print("AP datapackages file is corrupted") + data = {} + + _datapackages = data + + processDatapackages() + + +func _ready(): + pass + #_ws.connect("connection_closed", _closed) + #_ws.connect("connection_failed", _closed) + #_ws.connect("server_disconnected", _closed) + #_ws.connect("connection_error", _errored) + #_ws.connect("connection_established", _connected) + + +func _reset_state(): + _should_process = false + _authenticated = false + _try_wss = false + _has_connected = false + _received_items = {} + _received_indexes = [] + + +func _errored(): + if _try_wss: + global._print("Could not connect to AP with ws://, now trying wss://") + connectToServer(ap_server, ap_user, ap_pass) + else: + global._print("AP connection failed") + _reset_state() + + emit_signal( + "could_not_connect", + "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information." + ) + + +func _closed(_was_clean = true): + global._print("Connection closed") + _reset_state() + + if not _initiated_disconnect: + emit_signal("could_not_connect", "Disconnected from Archipelago") + + _initiated_disconnect = false + + +func _connected(_proto = ""): + global._print("Connected!") + _try_wss = false + + +func disconnect_from_ap(): + _initiated_disconnect = true + _ws.close() + + +func _process(_delta): + if _should_process: + _ws.poll() + + var state = _ws.get_ready_state() + if state == WebSocketPeer.STATE_OPEN: + if not _has_connected: + _has_connected = true + + _connected() + + while _ws.get_available_packet_count(): + var packet = _ws.get_packet() + global._print("Got data from server: " + packet.get_string_from_utf8()) + var json = JSON.new() + var jserror = json.parse(packet.get_string_from_utf8()) + if jserror != OK: + global._print("Error parsing packet from AP: " + jserror.error_string) + return + + for message in json.data: + var cmd = message["cmd"] + global._print("Received command: " + cmd) + + if cmd == "RoomInfo": + _seed = message["seed_name"] + _remote_version = message["version"] + _gen_version = message["generator_version"] + + var needed_games = [] + for game in message["datapackage_checksums"].keys(): + if ( + !_datapackages.has(game) + or ( + _datapackages[game]["checksum"] + != message["datapackage_checksums"][game] + ) + ): + needed_games.append(game) + + if !needed_games.is_empty(): + _pending_packages = needed_games + var cur_needed = _pending_packages.pop_front() + requestDatapackages([cur_needed]) + else: + connectToRoom() + + elif cmd == "DataPackage": + for game in message["data"]["games"].keys(): + _datapackages[game] = message["data"]["games"][game] + saveDatapackages() + + if !_pending_packages.is_empty(): + var cur_needed = _pending_packages.pop_front() + requestDatapackages([cur_needed]) + else: + processDatapackages() + connectToRoom() + + elif cmd == "Connected": + _authenticated = true + _team = message["team"] + _slot = message["slot"] + _players = message["players"] + _checked_locations = message["checked_locations"] + _slot_data = message["slot_data"] + + for player in _players: + _player_name_by_slot[player["slot"]] = player["alias"] + _game_by_player[player["slot"]] = message["slot_info"][str( + player["slot"] + )]["game"] + + emit_signal("client_connected", _slot_data) + + elif cmd == "ConnectionRefused": + var error_message = "" + for error in message["errors"]: + var submsg = "" + if error == "InvalidSlot": + submsg = "Invalid player name." + elif error == "InvalidGame": + submsg = "The specified player is not playing Lingo." + elif error == "IncompatibleVersion": + submsg = ( + "The Archipelago server is not the correct version for this client. Expected v%d.%d.%d. Found v%d.%d.%d." + % [ + ap_version["major"], + ap_version["minor"], + ap_version["build"], + _remote_version["major"], + _remote_version["minor"], + _remote_version["build"] + ] + ) + elif error == "InvalidPassword": + submsg = "Incorrect password." + elif error == "InvalidItemsHandling": + submsg = "Invalid item handling flag. This is a bug with the client." + + if submsg != "": + if error_message != "": + error_message += " " + error_message += submsg + + if error_message == "": + error_message = "Unknown error." + + _initiated_disconnect = true + _ws.close() + + emit_signal("could_not_connect", error_message) + global._print("Connection to AP refused") + global._print(message) + + elif cmd == "ReceivedItems": + var i = 0 + for item in message["items"]: + var index = int(message["index"] + i) + i += 1 + + if _received_indexes.has(index): + # Do not re-process items. + continue + + _received_indexes.append(index) + + var item_id = int(item["item"]) + _received_items[item_id] = _received_items.get(item_id, 0) + 1 + + emit_signal( + "item_received", + item_id, + index, + int(item["player"]), + int(item["flags"]), + _received_items[item_id] + ) + + elif cmd == "PrintJSON": + emit_signal("message_received", message) + + elif cmd == "LocationInfo": + for loc in message["locations"]: + emit_signal( + "location_scout_received", + int(loc["item"]), + int(loc["location"]), + int(loc["player"]), + int(loc["flags"]) + ) + + elif state == WebSocketPeer.STATE_CLOSED: + if _has_connected: + _closed() + else: + _errored() + + +func saveDatapackages(): + # Save the AP datapackages to disk. + var file = FileAccess.open("user://ap_datapackages", FileAccess.WRITE) + file.store_var(_datapackages, true) + file.close() + + +func connectToServer(server, un, pw): + ap_server = server + ap_user = un + ap_pass = pw + + _initiated_disconnect = false + + var url = "" + if ap_server.begins_with("ws://") or ap_server.begins_with("wss://"): + url = ap_server + _try_wss = false + elif _try_wss: + url = "wss://" + ap_server + _try_wss = false + else: + url = "ws://" + ap_server + _try_wss = true + + var err = _ws.connect_to_url(url) + if err != OK: + emit_signal( + "could_not_connect", + ( + "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information. Error code: %d." + % err + ) + ) + global._print("Could not connect to AP: %d" % err) + return + _should_process = true + + emit_signal("connect_status", "Connecting...") + + +func sendMessage(msg): + var payload = JSON.stringify(msg) + _ws.send_text(payload) + + +func requestDatapackages(games): + emit_signal("connect_status", "Downloading %s data package..." % games[0]) + + sendMessage([{"cmd": "GetDataPackage", "games": games}]) + + +func processDatapackages(): + _item_id_to_name = {} + _location_id_to_name = {} + for game in _datapackages.keys(): + var package = _datapackages[game] + + _item_id_to_name[game] = {} + for item_name in package["item_name_to_id"].keys(): + _item_id_to_name[game][int(package["item_name_to_id"][item_name])] = item_name + + _location_id_to_name[game] = {} + for location_name in package["location_name_to_id"].keys(): + _location_id_to_name[game][int(package["location_name_to_id"][location_name])] = location_name + + if _datapackages.has("Lingo 2"): + _item_name_to_id = _datapackages["Lingo 2"]["item_name_to_id"] + _location_name_to_id = _datapackages["Lingo 2"]["location_name_to_id"] + + +func connectToRoom(): + emit_signal("connect_status", "Authenticating...") + + sendMessage( + [ + { + "cmd": "Connect", + "password": ap_pass, + "game": "Lingo 2", + "name": ap_user, + "uuid": SCRIPT_uuid.v4(), + "version": ap_version, + "items_handling": 0b111, # always receive our items + "tags": [], + "slot_data": true + } + ] + ) + + +func sendConnectUpdate(tags): + sendMessage([{"cmd": "ConnectUpdate", "tags": tags}]) + + +func requestSync(): + sendMessage([{"cmd": "Sync"}]) + + +func sendLocation(loc_id): + sendMessage([{"cmd": "LocationChecks", "locations": [loc_id]}]) + + +func sendLocations(loc_ids): + sendMessage([{"cmd": "LocationChecks", "locations": loc_ids}]) + + +func setValue(key, value, operation = "replace"): + sendMessage( + [ + { + "cmd": "Set", + "key": "Lingo2_%d_%s" % [_slot, key], + "want_reply": false, + "operations": [{"operation": operation, "value": value}] + } + ] + ) + + +func say(textdata): + sendMessage([{"cmd": "Say", "text": textdata}]) + + +func completedGoal(): + sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL + + +func scoutLocations(loc_ids): + sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}]) + + +func hasItem(item_id): + return _received_items.has(item_id) + + +func getItemAmount(item_id): + return _received_items.get(item_id, 0) diff --git a/apworld/client/collectable.gd b/apworld/client/collectable.gd new file mode 100644 index 0000000..4a17a2a --- /dev/null +++ b/apworld/client/collectable.gd @@ -0,0 +1,16 @@ +extends "res://scripts/nodes/collectable.gd" + + +func pickedUp(): + if unlock_type == "key": + var ap = global.get_node("Archipelago") + if ap.get_letter_behavior(unlock_key, level == 2) == ap.kLETTER_BEHAVIOR_VANILLA: + ap.keyboard.collect_local_letter(unlock_key, level) + else: + ap.keyboard.update_unlocks() + + super.pickedUp() + + +func setScoutedText(text): + get_node("MeshInstance3D").mesh.text = text.replace(" ", "\n") diff --git a/apworld/client/compass.gd b/apworld/client/compass.gd new file mode 100644 index 0000000..c90475a --- /dev/null +++ b/apworld/client/compass.gd @@ -0,0 +1,66 @@ +extends Node2D + +const RADIUS = 48 + +var _font + + +func _ready(): + _font = load("res://assets/fonts/Lingo2.ttf") + + +func _draw(): + draw_circle(Vector2.ZERO, RADIUS, Color(1.0, 1.0, 1.0, 0.8), true) + draw_circle(Vector2.ZERO, RADIUS, Color.BLACK, false) + draw_string( + _font, + Vector2(-4, -RADIUS * 3.0 / 4.0), + "N", + HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, + -1, + 16, + Color.BLACK + ) + draw_set_transform(Vector2.ZERO, PI / 2) + draw_string( + _font, + Vector2(-4, -RADIUS * 3.0 / 4.0), + "E", + HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, + -1, + 16, + Color.BLACK + ) + draw_set_transform(Vector2.ZERO, PI) + draw_string( + _font, + Vector2(-4, -RADIUS * 3.0 / 4.0), + "S", + HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, + -1, + 16, + Color.BLACK + ) + draw_set_transform(Vector2.ZERO, PI * 3.0 / 2.0) + draw_string( + _font, + Vector2(-4, -RADIUS * 3.0 / 4.0), + "W", + HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, + -1, + 16, + Color.BLACK + ) + draw_set_transform(Vector2.ZERO) + draw_colored_polygon( + PackedVector2Array( + [Vector2(0, -RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)] + ), + Color.RED + ) + draw_colored_polygon( + PackedVector2Array( + [Vector2(0, RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)] + ), + Color.GRAY + ) diff --git a/apworld/client/compass_overlay.gd b/apworld/client/compass_overlay.gd new file mode 100644 index 0000000..56e81ff --- /dev/null +++ b/apworld/client/compass_overlay.gd @@ -0,0 +1,17 @@ +extends CanvasLayer + +var SCRIPT_compass + +var compass + + +func _ready(): + compass = SCRIPT_compass.new() + compass.position = Vector2(1840, 80) + add_child(compass) + + visible = false + + +func update_rotation(ry): + compass.rotation = ry diff --git a/apworld/client/door.gd b/apworld/client/door.gd new file mode 100644 index 0000000..49f5728 --- /dev/null +++ b/apworld/client/door.gd @@ -0,0 +1,46 @@ +extends "res://scripts/nodes/door.gd" + +var item_id +var item_amount + + +func _ready(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + var gamedata = global.get_node("Gamedata") + var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) + if door_id != null: + var ap = global.get_node("Archipelago") + var item_lock = ap.get_item_id_for_door(door_id) + + if item_lock != null: + item_id = item_lock[0] + item_amount = item_lock[1] + + self.senders = [] + self.senderGroup = [] + self.nested = false + self.complete_at = 0 + self.max_length = 0 + self.excludeSenders = [] + + call_deferred("_readier") + + if global.map == "the_sun_temple": + if name == "spe_EndPlatform" or name == "spe_entry_2": + senders = [NodePath("/root/scene/Panels/EndCheck_dog")] + + if global.map == "the_parthenon": + if name == "spe_entry_1": + senders = [NodePath("/root/scene/Panels/EndCheck_dog")] + + super._ready() + + +func _readier(): + var ap = global.get_node("Archipelago") + + if ap.client.getItemAmount(item_id) >= item_amount: + handleTriggered() diff --git a/apworld/client/gamedata.gd b/apworld/client/gamedata.gd new file mode 100644 index 0000000..9eeec3b --- /dev/null +++ b/apworld/client/gamedata.gd @@ -0,0 +1,159 @@ +extends Node + +var SCRIPT_proto + +var objects +var door_id_by_map_node_path = {} +var painting_id_by_map_node_path = {} +var panel_id_by_map_node_path = {} +var port_id_by_map_node_path = {} +var door_id_by_ap_id = {} +var map_id_by_name = {} +var progressive_id_by_ap_id = {} +var letter_id_by_ap_id = {} +var symbol_item_ids = [] +var anti_trap_ids = {} + +var kSYMBOL_ITEMS + + +func _init(proto_script): + SCRIPT_proto = proto_script + + kSYMBOL_ITEMS = { + SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol", + SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol", + SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol", + SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol", + SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol", + SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol", + SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol", + SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol", + SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol", + SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol", + SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol", + SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol", + SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol", + SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol", + SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol", + SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol", + SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol", + SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol", + SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol", + } + + +func load(data_bytes): + objects = SCRIPT_proto.AllObjects.new() + + var result_code = objects.from_bytes(data_bytes) + if result_code != SCRIPT_proto.PB_ERR.NO_ERRORS: + print("Could not load generated data: %d" % result_code) + return + + for map in objects.get_maps(): + map_id_by_name[map.get_name()] = map.get_id() + + for door in objects.get_doors(): + var map = objects.get_maps()[door.get_map_id()] + + if not map.get_name() in door_id_by_map_node_path: + door_id_by_map_node_path[map.get_name()] = {} + + var map_data = door_id_by_map_node_path[map.get_name()] + for receiver in door.get_receivers(): + map_data[receiver] = door.get_id() + + for painting_id in door.get_move_paintings(): + var painting = objects.get_paintings()[painting_id] + map_data[painting.get_path()] = door.get_id() + + if door.has_ap_id(): + door_id_by_ap_id[door.get_ap_id()] = door.get_id() + + for painting in objects.get_paintings(): + var room = objects.get_rooms()[painting.get_room_id()] + var map = objects.get_maps()[room.get_map_id()] + + if not map.get_name() in painting_id_by_map_node_path: + painting_id_by_map_node_path[map.get_name()] = {} + + var _map_data = painting_id_by_map_node_path[map.get_name()] + + for port in objects.get_ports(): + var room = objects.get_rooms()[port.get_room_id()] + var map = objects.get_maps()[room.get_map_id()] + + if not map.get_name() in port_id_by_map_node_path: + port_id_by_map_node_path[map.get_name()] = {} + + var map_data = port_id_by_map_node_path[map.get_name()] + map_data[port.get_path()] = port.get_id() + + for progressive in objects.get_progressives(): + progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id() + + for letter in objects.get_letters(): + letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() + + for panel in objects.get_panels(): + var room = objects.get_rooms()[panel.get_room_id()] + var map = objects.get_maps()[room.get_map_id()] + + if not map.get_name() in panel_id_by_map_node_path: + panel_id_by_map_node_path[map.get_name()] = {} + + var map_data = panel_id_by_map_node_path[map.get_name()] + map_data[panel.get_path()] = panel.get_id() + + for symbol_name in kSYMBOL_ITEMS.values(): + symbol_item_ids.append(objects.get_special_ids()[symbol_name]) + + for special_name in objects.get_special_ids().keys(): + if special_name.begins_with("Anti "): + anti_trap_ids[objects.get_special_ids()[special_name]] = ( + special_name.substr(5).to_lower() + ) + + +func get_door_for_map_node_path(map_name, node_path): + if not door_id_by_map_node_path.has(map_name): + return null + + var map_data = door_id_by_map_node_path[map_name] + return map_data.get(node_path, null) + + +func get_panel_for_map_node_path(map_name, node_path): + if not panel_id_by_map_node_path.has(map_name): + return null + + var map_data = panel_id_by_map_node_path[map_name] + return map_data.get(node_path, null) + + +func get_port_for_map_node_path(map_name, node_path): + if not port_id_by_map_node_path.has(map_name): + return null + + var map_data = port_id_by_map_node_path[map_name] + return map_data.get(node_path, null) + + +func get_door_ap_id(door_id): + var door = objects.get_doors()[door_id] + if door.has_ap_id(): + return door.get_ap_id() + else: + return null + + +func get_door_receivers(door_id): + var door = objects.get_doors()[door_id] + return door.get_receivers() + + +func get_door_map_name(door_id): + var door = objects.get_doors()[door_id] + var map = objects.get_maps()[door.get_map_id()] + return map.get_name() diff --git a/apworld/client/keyHolder.gd b/apworld/client/keyHolder.gd new file mode 100644 index 0000000..3c037ff --- /dev/null +++ b/apworld/client/keyHolder.gd @@ -0,0 +1,38 @@ +extends "res://scripts/nodes/keyHolder.gd" + + +func setFromAp(key, level): + if level > 0: + has_key = true + is_complete = "%s%d" % [key, level] + held_key = key + held_level = level + get_node("Hinge/Letter").mesh.text = held_key + get_node("Hinge/Letter2").mesh.text = held_key + setMaterial() + emit_signal("trigger") + else: + has_key = false + held_key = "" + held_level = 0 + setMaterial() + get_node("Hinge/Letter").mesh.text = "-" + get_node("Hinge/Letter2").mesh.text = "-" + is_complete = "" + emit_signal("untrigger") + + +func addKey(key): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + var ap = global.get_node("Archipelago") + ap.keyboard.put_in_keyholder(key, global.map, node_path) + + +func removeKey(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + var ap = global.get_node("Archipelago") + ap.keyboard.remove_from_keyholder(held_key, global.map, node_path) diff --git a/apworld/client/keyHolderChecker.gd b/apworld/client/keyHolderChecker.gd new file mode 100644 index 0000000..a75a9e4 --- /dev/null +++ b/apworld/client/keyHolderChecker.gd @@ -0,0 +1,24 @@ +extends "res://scripts/nodes/listeners/keyHolderChecker.gd" + + +func check(): + var ap = global.get_node("Archipelago") + var matches = [] + for map in ap.keyboard.keyholder_state.keys(): + var nodes = ap.keyboard.keyholder_state[map] + for node in nodes.keys(): + matches.append([nodes[node], 1, map, "/root/scene/%s" % node]) + + var count = 0 + for key_match in matches: + var active = ( + key_match[2] + String(key_match[3]).replace("/root/scene/Components/KeyHolders/", ".") + ) + if map[active] == key_match[0]: + emit_signal("trigger_letter", key_match[0], true) + count += 1 + else: + emit_signal("trigger_letter", key_match[0], false) + + if count > 25: + emit_signal("trigger") diff --git a/apworld/client/keyHolderResetterListener.gd b/apworld/client/keyHolderResetterListener.gd new file mode 100644 index 0000000..d5300f3 --- /dev/null +++ b/apworld/client/keyHolderResetterListener.gd @@ -0,0 +1,8 @@ +extends "res://scripts/nodes/listeners/keyHolderResetterListener.gd" + + +func reset(): + var ap = global.get_node("Archipelago") + var was_removed = ap.keyboard.reset_keyholders() + if was_removed: + sfxPlayer.sfx_play("pickup") diff --git a/apworld/client/keyboard.gd b/apworld/client/keyboard.gd new file mode 100644 index 0000000..450566d --- /dev/null +++ b/apworld/client/keyboard.gd @@ -0,0 +1,199 @@ +extends Node + +const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz" + +var letters_saved = {} +var letters_in_keyholders = [] +var letters_blocked = [] +var letters_dynamic = {} +var keyholder_state = {} + +var filename = "" + + +func _init(): + reset() + + +func reset(): + letters_saved.clear() + letters_in_keyholders.clear() + letters_blocked.clear() + letters_dynamic.clear() + keyholder_state.clear() + + +func load_seed(): + var ap = global.get_node("Archipelago") + + reset() + + filename = "user://archipelago_keys/%s_%d" % [ap.client._seed, ap.client._slot] + + if FileAccess.file_exists(filename): + var ap_file = FileAccess.open(filename, FileAccess.READ) + var localdata = [] + if ap_file != null: + localdata = ap_file.get_var(true) + ap_file.close() + + if typeof(localdata) != TYPE_ARRAY: + print("AP keyboard file is corrupted") + localdata = [] + + if localdata.size() > 0: + letters_saved = localdata[0] + if localdata.size() > 1: + letters_in_keyholders = localdata[1] + if localdata.size() > 2: + keyholder_state = localdata[2] + + for k in kALL_LETTERS: + var level = 0 + + if ap.get_letter_behavior(k, false) == ap.kLETTER_BEHAVIOR_UNLOCKED: + level += 1 + if ap.get_letter_behavior(k, true) == ap.kLETTER_BEHAVIOR_UNLOCKED: + level += 1 + + letters_dynamic[k] = level + + update_unlocks() + + +func save(): + var dir = DirAccess.open("user://") + var folder = "archipelago_keys" + if not dir.dir_exists(folder): + dir.make_dir(folder) + + var file = FileAccess.open(filename, FileAccess.WRITE) + + var data = [ + letters_saved, + letters_in_keyholders, + keyholder_state, + ] + file.store_var(data, true) + file.close() + + +func update_unlocks(): + unlocks.resetKeys() + + var has_doubles = false + + for k in kALL_LETTERS: + var level = 0 + + if not letters_in_keyholders.has(k): + level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0) + + if level >= 2: + level = 2 + has_doubles = true + + if letters_blocked.has(k): + level = 0 + + unlocks.unlockKey(k, level) + + if has_doubles and unlocks.data["double_letters"] != "unlocked": + var ap = global.get_node("Archipelago") + if ap.cyan_door_behavior == ap.kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER: + unlocks.setData("double_letters", "unlocked") + + +func collect_local_letter(key, level): + if level < 0 or level > 2 or level < letters_saved.get(key, 0): + return + + letters_saved[key] = level + + if letters_blocked.has(key): + letters_blocked.erase(key) + + update_unlocks() + save() + + +func collect_remote_letter(key, level): + if level < 0 or level > 2 or level < letters_dynamic.get(key, 0): + return + + letters_dynamic[key] = level + + if letters_blocked.has(key): + letters_blocked.erase(key) + + update_unlocks() + save() + + +func put_in_keyholder(key, map, kh_path): + if not keyholder_state.has(map): + keyholder_state[map] = {} + + keyholder_state[map][kh_path] = key + letters_in_keyholders.append(key) + + get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp( + key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2) + ) + + update_unlocks() + save() + + +func remove_from_keyholder(key, map, kh_path): + if not keyholder_state.has(map): + # This... shouldn't happen. + keyholder_state[map] = {} + + keyholder_state[map].erase(kh_path) + letters_in_keyholders.erase(key) + + get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(key, 0) + + update_unlocks() + save() + + +func block_letter(key): + if not letters_blocked.has(key): + letters_blocked.append(key) + + update_unlocks() + + +func load_keyholders(map): + if keyholder_state.has(map): + var khs = keyholder_state[map] + + for path in khs.keys(): + var key = khs[path] + get_tree().get_root().get_node("scene").get_node(path).setFromAp( + key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2) + ) + + +func reset_keyholders(): + if letters_in_keyholders.is_empty() and letters_blocked.is_empty(): + return false + + var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty() + + if keyholder_state.has(global.map): + for path in keyholder_state[global.map]: + get_tree().get_root().get_node("scene").get_node(path).setFromAp( + keyholder_state[global.map][path], 0 + ) + + keyholder_state.clear() + letters_in_keyholders.clear() + letters_blocked.clear() + + update_unlocks() + save() + + return cleared_anything diff --git a/apworld/client/locationListener.gd b/apworld/client/locationListener.gd new file mode 100644 index 0000000..71792ed --- /dev/null +++ b/apworld/client/locationListener.gd @@ -0,0 +1,20 @@ +extends Receiver + +var location_id + + +func _ready(): + super._ready() + + +func handleTriggered(): + triggered += 1 + if triggered >= total: + var ap = global.get_node("Archipelago") + ap.send_location(location_id) + + +func handleUntriggered(): + triggered -= 1 + if triggered < total: + pass diff --git a/apworld/client/main.gd b/apworld/client/main.gd new file mode 100644 index 0000000..a31eb89 --- /dev/null +++ b/apworld/client/main.gd @@ -0,0 +1,284 @@ +extends Node + + +func _ready(): + var runtime = global.get_node("Runtime") + + # Some helpful logging. + if Steam.isSubscribed(): + global._print("Provisioning successful! Build ID: %d" % Steam.getAppBuildId()) + else: + global._print("Provisioning failed.") + + # Undo the load screen removing our cursor + get_tree().get_root().set_disable_input(false) + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + + # Increase the WebSocket input buffer size so that we can download large + # data packages. + ProjectSettings.set_setting("network/limits/websocket_client/max_in_buffer_kb", 8192) + + switcher.layer = 4 + + # Create the global AP manager, if it doesn't already exist. + if not global.has_node("Archipelago"): + var ap_script = runtime.load_path("manager.gd") + var ap_instance = ap_script.new() + ap_instance.name = "Archipelago" + + ap_instance.SCRIPT_client = runtime.load_path("client.gd") + ap_instance.SCRIPT_keyboard = runtime.load_path("keyboard.gd") + ap_instance.SCRIPT_locationListener = runtime.load_path("locationListener.gd") + ap_instance.SCRIPT_minimap = runtime.load_path("minimap.gd") + ap_instance.SCRIPT_uuid = runtime.load_path("vendor/uuid.gd") + ap_instance.SCRIPT_victoryListener = runtime.load_path("victoryListener.gd") + + global.add_child(ap_instance) + + # Let's also inject any scripts we need to inject now. + installScriptExtension(runtime.load_path("animationListener.gd")) + installScriptExtension(runtime.load_path("collectable.gd")) + installScriptExtension(runtime.load_path("door.gd")) + installScriptExtension(runtime.load_path("keyHolder.gd")) + installScriptExtension(runtime.load_path("keyHolderChecker.gd")) + installScriptExtension(runtime.load_path("keyHolderResetterListener.gd")) + installScriptExtension(runtime.load_path("painting.gd")) + installScriptExtension(runtime.load_path("panel.gd")) + installScriptExtension(runtime.load_path("pauseMenu.gd")) + installScriptExtension(runtime.load_path("player.gd")) + installScriptExtension(runtime.load_path("saver.gd")) + installScriptExtension(runtime.load_path("teleport.gd")) + installScriptExtension(runtime.load_path("teleportListener.gd")) + installScriptExtension(runtime.load_path("visibilityListener.gd")) + installScriptExtension(runtime.load_path("worldport.gd")) + installScriptExtension(runtime.load_path("worldportListener.gd")) + + var proto_script = runtime.load_path("../generated/proto.gd") + var gamedata_script = runtime.load_path("gamedata.gd") + var gamedata_instance = gamedata_script.new(proto_script) + gamedata_instance.load(runtime.read_path("../generated/data.binpb")) + gamedata_instance.name = "Gamedata" + global.add_child(gamedata_instance) + + var messages_script = runtime.load_path("messages.gd") + var messages_instance = messages_script.new() + messages_instance.name = "Messages" + messages_instance.SCRIPT_rainbowText = runtime.load_path("rainbowText.gd") + global.add_child(messages_instance) + + var textclient_script = runtime.load_path("textclient.gd") + var textclient_instance = textclient_script.new() + textclient_instance.name = "Textclient" + global.add_child(textclient_instance) + + var compass_overlay_script = runtime.load_path("compass_overlay.gd") + var compass_overlay_instance = compass_overlay_script.new() + compass_overlay_instance.name = "Compass" + compass_overlay_instance.SCRIPT_compass = runtime.load_path("compass.gd") + global.add_child(compass_overlay_instance) + + var ap = global.get_node("Archipelago") + var gamedata = global.get_node("Gamedata") + ap.connect("ap_connected", connectionSuccessful) + ap.connect("could_not_connect", connectionUnsuccessful) + ap.connect("connect_status", connectionStatus) + + # Populate textboxes with AP settings. + get_node("../Panel/server_box").text = ap.ap_server + get_node("../Panel/player_box").text = ap.ap_user + get_node("../Panel/password_box").text = ap.ap_pass + + var history_box = get_node("../Panel/connection_history") + if ap.connection_history.is_empty(): + history_box.disabled = true + else: + history_box.disabled = false + + var i = 0 + for details in ap.connection_history: + history_box.get_popup().add_item("%s (%s)" % [details[1], details[0]], i) + i += 1 + + history_box.get_popup().connect("id_pressed", historySelected) + + # Show client version. + get_node("../Panel/title").text = ( + "ARCHIPELAGO (%d.%d)" % [gamedata.objects.get_version(), ap.MOD_VERSION] + ) + + # Increase font size in text boxes. + get_node("../Panel/server_box").add_theme_font_size_override("font_size", 36) + get_node("../Panel/player_box").add_theme_font_size_override("font_size", 36) + get_node("../Panel/password_box").add_theme_font_size_override("font_size", 36) + + # Set up version mismatch dialog. + get_node("../Panel/VersionMismatch").connect("confirmed", startGame) + get_node("../Panel/VersionMismatch").get_cancel_button().pressed.connect( + versionMismatchDeclined + ) + + # Set up buttons. + get_node("../Panel/connect_button").connect("pressed", _connect_pressed) + get_node("../Panel/quit_button").connect("pressed", _back_pressed) + + +func _connect_pressed(): + get_node("../Panel/connect_button").disabled = true + + var ap = global.get_node("Archipelago") + ap.ap_server = get_node("../Panel/server_box").text + ap.ap_user = get_node("../Panel/player_box").text + ap.ap_pass = get_node("../Panel/password_box").text + ap.saveSettings() + + ap.connectToServer() + + +func _back_pressed(): + var ap = global.get_node("Archipelago") + ap.disconnect_from_ap() + + get_tree().quit() + + +# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd +func installScriptExtension(childScript: Resource): + # Force Godot to compile the script now. + # We need to do this here to ensure that the inheritance chain is + # properly set up, and multiple mods can chain-extend the same + # class multiple times. + # This is also needed to make Godot instantiate the extended class + # when creating singletons. + # The actual instance is thrown away. + childScript.new() + + var parentScript = childScript.get_base_script() + var parentScriptPath = parentScript.resource_path + global._print("ModLoader: Installing script extension over %s" % parentScriptPath) + childScript.take_over_path(parentScriptPath) + + +func connectionStatus(message): + var popup = get_node("../Panel/AcceptDialog") + popup.title = "Connecting to Archipelago" + popup.dialog_text = message + popup.exclusive = true + popup.get_ok_button().visible = false + popup.popup_centered() + + +func connectionSuccessful(): + var ap = global.get_node("Archipelago") + var gamedata = global.get_node("Gamedata") + + # Check for major version mismatch. + if ap.apworld_version[0] != gamedata.objects.get_version(): + get_node("../Panel/AcceptDialog").exclusive = false + + var popup = get_node("../Panel/VersionMismatch") + popup.title = "Version Mismatch!" + popup.dialog_text = ( + "This slot was generated using v%d.%d of the Lingo 2 apworld,\nwhich has a different major version than this client (v%d.%d).\nIt is highly recommended to play using the correct version of the client.\nYou may experience bugs or logic issues if you continue." + % [ + ap.apworld_version[0], + ap.apworld_version[1], + gamedata.objects.get_version(), + ap.MOD_VERSION + ] + ) + popup.exclusive = true + popup.popup_centered() + + return + + startGame() + + +func startGame(): + var ap = global.get_node("Archipelago") + + # Save connection details + var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass] + if ap.connection_history.has(connection_details): + ap.connection_history.erase(connection_details) + ap.connection_history.push_front(connection_details) + if ap.connection_history.size() > 10: + ap.connection_history.resize(10) + ap.saveSettings() + + # Switch to the_entry + Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) + global.user = ap.getSaveFileName() + global.universe = "lingo" + global.map = "the_entry" + + unlocks.resetCollectables() + unlocks.resetData() + + ap.setup_keys() + + unlocks.loadCollectables() + unlocks.loadData() + unlocks.unlockKey("capslock", 1) + + if ap.shuffle_worldports: + settings.worldport_fades = "default" + else: + settings.worldport_fades = "never" + + clearResourceCache("res://objects/meshes/gridDoor.tscn") + clearResourceCache("res://objects/nodes/collectable.tscn") + clearResourceCache("res://objects/nodes/door.tscn") + clearResourceCache("res://objects/nodes/keyHolder.tscn") + clearResourceCache("res://objects/nodes/listeners/animationListener.tscn") + clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn") + clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") + clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") + clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn") + clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") + clearResourceCache("res://objects/nodes/panel.tscn") + clearResourceCache("res://objects/nodes/player.tscn") + clearResourceCache("res://objects/nodes/saver.tscn") + clearResourceCache("res://objects/nodes/teleport.tscn") + clearResourceCache("res://objects/nodes/worldport.tscn") + clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") + + var paintings_dir = DirAccess.open("res://objects/meshes/paintings") + if paintings_dir: + paintings_dir.list_dir_begin() + var file_name = paintings_dir.get_next() + while file_name != "": + if not paintings_dir.current_is_dir() and file_name.ends_with(".tscn"): + clearResourceCache("res://objects/meshes/paintings/" + file_name) + file_name = paintings_dir.get_next() + + switcher.switch_map.call_deferred("res://objects/scenes/the_entry.tscn") + + +func connectionUnsuccessful(error_message): + get_node("../Panel/connect_button").disabled = false + + var popup = get_node("../Panel/AcceptDialog") + popup.title = "Could not connect to Archipelago" + popup.dialog_text = error_message + popup.exclusive = true + popup.get_ok_button().visible = true + popup.popup_centered() + + +func versionMismatchDeclined(): + get_node("../Panel/AcceptDialog").hide() + get_node("../Panel/connect_button").disabled = false + + +func historySelected(index): + var ap = global.get_node("Archipelago") + var details = ap.connection_history[index] + + get_node("../Panel/server_box").text = details[0] + get_node("../Panel/player_box").text = details[1] + get_node("../Panel/password_box").text = details[2] + + +func clearResourceCache(path): + ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE) diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd new file mode 100644 index 0000000..b170c77 --- /dev/null +++ b/apworld/client/manager.gd @@ -0,0 +1,571 @@ +extends Node + +const MOD_VERSION = 7 + +var SCRIPT_client +var SCRIPT_keyboard +var SCRIPT_locationListener +var SCRIPT_minimap +var SCRIPT_uuid +var SCRIPT_victoryListener + +var ap_server = "" +var ap_user = "" +var ap_pass = "" +var connection_history = [] +var show_compass = false + +var client +var keyboard + +var _localdata_file = "" +var _last_new_item = -1 +var _batch_locations = false +var _held_locations = [] +var _held_location_scouts = [] +var _location_scouts = {} +var _item_locks = {} +var _inverse_item_locks = {} +var _held_letters = {} +var _letters_setup = false + +const kSHUFFLE_LETTERS_VANILLA = 0 +const kSHUFFLE_LETTERS_UNLOCKED = 1 +const kSHUFFLE_LETTERS_PROGRESSIVE = 2 +const kSHUFFLE_LETTERS_VANILLA_CYAN = 3 +const kSHUFFLE_LETTERS_ITEM_CYAN = 4 + +const kLETTER_BEHAVIOR_VANILLA = 0 +const kLETTER_BEHAVIOR_ITEM = 1 +const kLETTER_BEHAVIOR_UNLOCKED = 2 + +const kCYAN_DOOR_BEHAVIOR_H2 = 0 +const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1 +const kCYAN_DOOR_BEHAVIOR_ITEM = 2 + +var apworld_version = [0, 0] +var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 +var daedalus_roof_access = false +var keyholder_sanity = false +var port_pairings = {} +var shuffle_control_center_colors = false +var shuffle_doors = false +var shuffle_gallery_paintings = false +var shuffle_letters = kSHUFFLE_LETTERS_VANILLA +var shuffle_symbols = false +var shuffle_worldports = false +var strict_cyan_ending = false +var strict_purple_ending = false +var victory_condition = -1 + +signal could_not_connect +signal connect_status +signal ap_connected + + +func _init(): + # Read AP settings from file, if there are any + if FileAccess.file_exists("user://ap_settings"): + var file = FileAccess.open("user://ap_settings", FileAccess.READ) + var data = file.get_var(true) + file.close() + + if typeof(data) != TYPE_ARRAY: + global._print("AP settings file is corrupted") + data = [] + + if data.size() > 0: + ap_server = data[0] + + if data.size() > 1: + ap_user = data[1] + + if data.size() > 2: + ap_pass = data[2] + + if data.size() > 3: + connection_history = data[3] + + if data.size() > 4: + show_compass = data[4] + + +func _ready(): + client = SCRIPT_client.new() + client.SCRIPT_uuid = SCRIPT_uuid + + client.connect("item_received", _process_item) + client.connect("message_received", _process_message) + client.connect("location_scout_received", _process_location_scout) + client.connect("could_not_connect", _client_could_not_connect) + client.connect("connect_status", _client_connect_status) + client.connect("client_connected", _client_connected) + + add_child(client) + + keyboard = SCRIPT_keyboard.new() + add_child(keyboard) + + +func saveSettings(): + # Save the AP settings to disk. + var path = "user://ap_settings" + var file = FileAccess.open(path, FileAccess.WRITE) + + var data = [ + ap_server, + ap_user, + ap_pass, + connection_history, + show_compass, + ] + file.store_var(data, true) + file.close() + + +func saveLocaldata(): + # Save the MW/slot specific settings to disk. + var dir = DirAccess.open("user://") + var folder = "archipelago_data" + if not dir.dir_exists(folder): + dir.make_dir(folder) + + var file = FileAccess.open(_localdata_file, FileAccess.WRITE) + + var data = [ + _last_new_item, + ] + file.store_var(data, true) + file.close() + + +func connectToServer(): + _last_new_item = -1 + _batch_locations = false + _held_locations = [] + _held_location_scouts = [] + _location_scouts = {} + _letters_setup = false + _held_letters = {} + + client.connectToServer(ap_server, ap_user, ap_pass) + + +func getSaveFileName(): + return "zzAP_%s_%d" % [client._seed, client._slot] + + +func disconnect_from_ap(): + client.disconnect_from_ap() + + +func get_item_id_for_door(door_id): + return _item_locks.get(door_id, null) + + +func _process_item(item, index, from, flags, amount): + var item_name = "Unknown" + if client._item_id_to_name["Lingo 2"].has(item): + item_name = client._item_id_to_name["Lingo 2"][item] + + var gamedata = global.get_node("Gamedata") + + var prog_id = null + if _inverse_item_locks.has(item): + for lock in _inverse_item_locks.get(item): + if lock[1] != amount: + continue + + if gamedata.progressive_id_by_ap_id.has(item): + prog_id = lock[0] + + if gamedata.get_door_map_name(lock[0]) != global.map: + continue + + var receivers = gamedata.get_door_receivers(lock[0]) + var scene = get_tree().get_root().get_node_or_null("scene") + if scene != null: + for receiver in receivers: + var rnode = scene.get_node_or_null(receiver) + if rnode != null: + rnode.handleTriggered() + + var letter_id = gamedata.letter_id_by_ap_id.get(item, null) + if letter_id != null: + var letter = gamedata.objects.get_letters()[letter_id] + if not letter.has_level2() or not letter.get_level2(): + _process_key_item(letter.get_key(), amount) + + if gamedata.symbol_item_ids.has(item): + var player = get_tree().get_root().get_node_or_null("scene/player") + if player != null: + player.emit_signal("evaluate_solvability") + + # Show a message about the item if it's new. + if index != null and index > _last_new_item: + _last_new_item = index + saveLocaldata() + + var player_name = "Unknown" + if client._player_name_by_slot.has(float(from)): + player_name = client._player_name_by_slot[float(from)] + + var full_item_name = item_name + if prog_id != null: + var door = gamedata.objects.get_doors()[prog_id] + full_item_name = "%s (%s)" % [item_name, door.get_name()] + + var message + if from == client._slot: + message = "Found %s" % wrapInItemColorTags(full_item_name, flags) + else: + message = ( + "Received %s from %s" % [wrapInItemColorTags(full_item_name, flags), player_name] + ) + + if gamedata.anti_trap_ids.has(item): + keyboard.block_letter(gamedata.anti_trap_ids[item]) + + global._print(message) + + global.get_node("Messages").showMessage(message) + + +func _process_message(message): + parse_printjson_for_textclient(message) + + if ( + !message.has("receiving") + or !message.has("item") + or message["item"]["player"] != client._slot + ): + return + + var item_name = "Unknown" + var item_player_game = client._game_by_player[message["receiving"]] + if client._item_id_to_name[item_player_game].has(int(message["item"]["item"])): + item_name = client._item_id_to_name[item_player_game][int(message["item"]["item"])] + + var location_name = "Unknown" + var location_player_game = client._game_by_player[message["item"]["player"]] + if client._location_id_to_name[location_player_game].has(int(message["item"]["location"])): + location_name = (client._location_id_to_name[location_player_game][int( + message["item"]["location"] + )]) + + var player_name = "Unknown" + if client._player_name_by_slot.has(message["receiving"]): + player_name = client._player_name_by_slot[message["receiving"]] + + var item_color = colorForItemType(message["item"]["flags"]) + + if message["type"] == "Hint": + var is_for = "" + if message["receiving"] != client._slot: + is_for = " for %s" % player_name + if !message.has("found") || !message["found"]: + global.get_node("Messages").showMessage( + ( + "Hint: %s%s is on %s" + % [ + wrapInItemColorTags(item_name, message["item"]["flags"]), + is_for, + location_name + ] + ) + ) + else: + if message["receiving"] != client._slot: + var sentMsg = ( + "Sent %s to %s" + % [wrapInItemColorTags(item_name, message["item"]["flags"]), player_name] + ) + #if _hinted_locations.has(message["item"]["location"]): + # sentMsg += " ([color=#fafad2]Hinted![/color])" + global.get_node("Messages").showMessage(sentMsg) + + +func parse_printjson_for_textclient(message): + var parts = [] + for message_part in message["data"]: + if !message_part.has("type") and message_part.has("text"): + parts.append(message_part["text"]) + elif message_part["type"] == "player_id": + if int(message_part["text"]) == client._slot: + parts.append( + "[color=#ee00ee]%s[/color]" % client._player_name_by_slot[client._slot] + ) + else: + var from = float(message_part["text"]) + parts.append("[color=#fafad2]%s[/color]" % client._player_name_by_slot[from]) + elif message_part["type"] == "item_id": + var item_name = "Unknown" + var item_player_game = client._game_by_player[message_part["player"]] + if client._item_id_to_name[item_player_game].has(int(message_part["text"])): + item_name = client._item_id_to_name[item_player_game][int(message_part["text"])] + + parts.append(wrapInItemColorTags(item_name, message_part["flags"])) + elif message_part["type"] == "location_id": + var location_name = "Unknown" + var location_player_game = client._game_by_player[message_part["player"]] + if client._location_id_to_name[location_player_game].has(int(message_part["text"])): + location_name = client._location_id_to_name[location_player_game][int( + message_part["text"] + )] + + parts.append("[color=#00ff7f]%s[/color]" % location_name) + elif message_part.has("text"): + parts.append(message_part["text"]) + + var textclient_node = global.get_node("Textclient") + if textclient_node != null: + textclient_node.parse_printjson("".join(parts)) + + +func _process_location_scout(item_id, location_id, player, flags): + _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags} + + if player == client._slot and flags & 4 != 0: + # This is a trap for us, so let's not display it. + return + + var gamedata = global.get_node("Gamedata") + var map_id = gamedata.map_id_by_name.get(global.map) + + var item_name = "Unknown" + var item_player_game = client._game_by_player[float(player)] + if client._item_id_to_name[item_player_game].has(item_id): + item_name = client._item_id_to_name[item_player_game][item_id] + + var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null) + if letter_id != null: + var letter = gamedata.objects.get_letters()[letter_id] + var room = gamedata.objects.get_rooms()[letter.get_room_id()] + if room.get_map_id() == map_id: + var collectable = get_tree().get_root().get_node("scene").get_node_or_null( + letter.get_path() + ) + if collectable != null: + collectable.setScoutedText(item_name) + + +func _client_could_not_connect(message): + emit_signal("could_not_connect", message) + + +func _client_connect_status(message): + emit_signal("connect_status", message) + + +func _client_connected(slot_data): + var gamedata = global.get_node("Gamedata") + + _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot] + _last_new_item = -1 + + if FileAccess.file_exists(_localdata_file): + var ap_file = FileAccess.open(_localdata_file, FileAccess.READ) + var localdata = [] + if ap_file != null: + localdata = ap_file.get_var(true) + ap_file.close() + + if typeof(localdata) != TYPE_ARRAY: + print("AP localdata file is corrupted") + localdata = [] + + if localdata.size() > 0: + _last_new_item = localdata[0] + + # Read slot data. + cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0)) + daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) + keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) + shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) + shuffle_doors = bool(slot_data.get("shuffle_doors", false)) + shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false)) + shuffle_letters = int(slot_data.get("shuffle_letters", 0)) + shuffle_symbols = bool(slot_data.get("shuffle_symbols", false)) + shuffle_worldports = bool(slot_data.get("shuffle_worldports", false)) + strict_cyan_ending = bool(slot_data.get("strict_cyan_ending", false)) + strict_purple_ending = bool(slot_data.get("strict_purple_ending", false)) + victory_condition = int(slot_data.get("victory_condition", 0)) + + if slot_data.has("version"): + apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])] + + port_pairings.clear() + if slot_data.has("port_pairings"): + var raw_pp = slot_data.get("port_pairings") + + for p1 in raw_pp.keys(): + port_pairings[int(p1)] = int(raw_pp[p1]) + + # Set up item locks. + _item_locks = {} + + if shuffle_doors: + for door in gamedata.objects.get_doors(): + if ( + door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD + or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY + ): + _item_locks[door.get_id()] = [door.get_ap_id(), 1] + + for progressive in gamedata.objects.get_progressives(): + for i in range(0, progressive.get_doors().size()): + var door = gamedata.objects.get_doors()[progressive.get_doors()[i]] + _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1] + + for door_group in gamedata.objects.get_door_groups(): + if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR: + if shuffle_worldports: + continue + elif door_group.get_type() != gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP: + continue + + for door in door_group.get_doors(): + _item_locks[door] = [door_group.get_ap_id(), 1] + + if shuffle_control_center_colors: + for door in gamedata.objects.get_doors(): + if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR: + _item_locks[door.get_id()] = [door.get_ap_id(), 1] + + for door_group in gamedata.objects.get_door_groups(): + if ( + door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR + and not shuffle_worldports + ): + for door in door_group.get_doors(): + _item_locks[door] = [door_group.get_ap_id(), 1] + + if shuffle_gallery_paintings: + for door in gamedata.objects.get_doors(): + if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING: + _item_locks[door.get_id()] = [door.get_ap_id(), 1] + + if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM: + for door_group in gamedata.objects.get_door_groups(): + if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS: + for door in door_group.get_doors(): + if not _item_locks.has(door): + _item_locks[door] = [door_group.get_ap_id(), 1] + + # Create a reverse item locks map for processing items. + _inverse_item_locks = {} + + for door_id in _item_locks.keys(): + var lock = _item_locks.get(door_id) + + if not _inverse_item_locks.has(lock[0]): + _inverse_item_locks[lock[0]] = [] + + _inverse_item_locks[lock[0]].append([door_id, lock[1]]) + + emit_signal("ap_connected") + + +func start_batching_locations(): + _batch_locations = true + + +func send_location(loc_id): + if _batch_locations: + _held_locations.append(loc_id) + else: + client.sendLocation(loc_id) + + +func scout_location(loc_id): + if _location_scouts.has(loc_id): + return _location_scouts.get(loc_id) + + if _batch_locations: + _held_location_scouts.append(loc_id) + else: + client.scoutLocation(loc_id) + + return null + + +func stop_batching_locations(): + _batch_locations = false + + if not _held_locations.is_empty(): + client.sendLocations(_held_locations) + _held_locations.clear() + + if not _held_location_scouts.is_empty(): + client.scoutLocations(_held_location_scouts) + _held_location_scouts.clear() + + +func colorForItemType(flags): + var int_flags = int(flags) + if int_flags & 1: # progression + if int_flags & 2: # proguseful + return "#f0d200" + else: + return "#bc51e0" + elif int_flags & 2: # useful + return "#2b67ff" + elif int_flags & 4: # trap + return "#d63a22" + else: # filler + return "#14de9e" + + +func wrapInItemColorTags(text, flags): + var int_flags = int(flags) + if int_flags & 1 and int_flags & 2: # proguseful + return "[rainbow]%s[/rainbow]" % text + else: + return "[color=%s]%s[/color]" % [colorForItemType(flags), text] + + +func get_letter_behavior(key, level2): + if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED: + return kLETTER_BEHAVIOR_UNLOCKED + + if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters): + if level2: + if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN: + return kLETTER_BEHAVIOR_VANILLA + else: + return kLETTER_BEHAVIOR_ITEM + else: + return kLETTER_BEHAVIOR_UNLOCKED + + if not level2 and ["h", "i", "n", "t"].has(key): + # This differs from the equivalent function in the apworld. Logically it is + # the same as UNLOCKED since they are in the starting room, but VANILLA + # means the player still has to actually pick up the letters. + return kLETTER_BEHAVIOR_VANILLA + + if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE: + return kLETTER_BEHAVIOR_ITEM + + return kLETTER_BEHAVIOR_VANILLA + + +func setup_keys(): + keyboard.load_seed() + + _letters_setup = true + + for k in _held_letters.keys(): + _process_key_item(k, _held_letters[k]) + + _held_letters.clear() + + +func _process_key_item(key, level): + if not _letters_setup: + _held_letters[key] = max(_held_letters.get(key, 0), level) + return + + if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN: + level += 1 + + keyboard.collect_remote_letter(key, level) diff --git a/apworld/client/messages.gd b/apworld/client/messages.gd new file mode 100644 index 0000000..ab4f071 --- /dev/null +++ b/apworld/client/messages.gd @@ -0,0 +1,74 @@ +extends CanvasLayer + +var SCRIPT_rainbowText + +var _message_queue = [] +var _font +var _container +var _ordered_labels = [] + + +func _ready(): + _container = VBoxContainer.new() + _container.set_name("Container") + _container.anchor_bottom = 1 + _container.offset_left = 20.0 + _container.offset_right = 1920.0 + _container.offset_top = 0.0 + _container.offset_bottom = -20.0 + _container.alignment = BoxContainer.ALIGNMENT_END + _container.mouse_filter = Control.MOUSE_FILTER_IGNORE + self.add_child(_container) + + _font = load("res://assets/fonts/Lingo2.ttf") + + +func _add_message(text): + var new_label = RichTextLabel.new() + new_label.install_effect(SCRIPT_rainbowText.new()) + new_label.push_font(_font) + new_label.push_font_size(36) + new_label.push_outline_color(Color(0, 0, 0, 1)) + new_label.push_outline_size(2) + new_label.append_text(text) + new_label.fit_content = true + + _container.add_child(new_label) + _ordered_labels.push_back(new_label) + + +func showMessage(text): + if _ordered_labels.size() >= 9: + _message_queue.append(text) + return + + _add_message(text) + + if _ordered_labels.size() > 1: + return + + var timeout = 10.0 + while !_ordered_labels.is_empty(): + await get_tree().create_timer(timeout).timeout + + if !_ordered_labels.is_empty(): + var to_remove = _ordered_labels.pop_front() + var to_tween = get_tree().create_tween().bind_node(to_remove) + to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5) + to_tween.tween_callback(to_remove.queue_free) + + if !_message_queue.is_empty(): + var next_msg = _message_queue.pop_front() + _add_message(next_msg) + + if timeout > 4: + timeout -= 3 + + +func clear(): + _message_queue.clear() + + for message_label in _ordered_labels: + message_label.queue_free() + + _ordered_labels.clear() diff --git a/apworld/client/minimap.gd b/apworld/client/minimap.gd new file mode 100644 index 0000000..5640716 --- /dev/null +++ b/apworld/client/minimap.gd @@ -0,0 +1,175 @@ +extends CanvasLayer + +var player +var drawer +var sprite +var label + +var cell_left +var cell_top +var cell_right +var cell_bottom +var cell_width +var cell_height +var center_x_min +var center_x_max +var center_y_min +var center_y_max + + +func _ready(): + player = get_tree().get_root().get_node("scene/player") + + var svc = PanelContainer.new() + svc.anchor_left = 1.0 + svc.anchor_top = 1.0 + svc.anchor_right = 1.0 + svc.anchor_bottom = 1.0 + svc.offset_left = -320.0 + svc.offset_top = -320.0 + svc.offset_right = -64.0 + svc.offset_bottom = -64.0 + svc.clip_contents = true + add_child(svc) + + var background_color = Color.WHITE + + var world_env = get_tree().get_root().get_node("scene/WorldEnvironment") + if world_env != null and world_env.environment != null: + if world_env.environment.background_mode == Environment.BG_COLOR: + background_color = world_env.environment.background_color + elif ( + world_env.environment.background_mode == Environment.BG_SKY + and world_env.environment.sky != null + and world_env.environment.sky.sky_material != null + ): + var sky = world_env.environment.sky.sky_material + if sky is PhysicalSkyMaterial: + background_color = sky.ground_color + elif sky is ProceduralSkyMaterial: + background_color = sky.sky_top_color + + var stylebox = StyleBoxFlat.new() + stylebox.bg_color = Color(background_color, 0.6) + svc.add_theme_stylebox_override("panel", stylebox) + + drawer = Node2D.new() + svc.add_child(drawer) + + var gridmap = get_tree().get_root().get_node("scene/GridMap") + if gridmap == null: + visible = false + return + + cell_left = 0 + cell_top = 0 + cell_right = 0 + cell_bottom = 0 + + for pos in gridmap.get_used_cells(): + if pos.x < cell_left: + cell_left = pos.x + if pos.x > cell_right: + cell_right = pos.x + if pos.z < cell_top: + cell_top = pos.z + if pos.z > cell_bottom: + cell_bottom = pos.z + + cell_width = cell_right - cell_left + 1 + cell_height = cell_bottom - cell_top + 1 + + var rendered = _renderMap(gridmap) + + var image_texture = ImageTexture.create_from_image(rendered) + sprite = Sprite2D.new() + sprite.texture = image_texture + sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + sprite.scale = Vector2(2, 2) + sprite.centered = false + drawer.add_child(sprite) + + label = Label.new() + label.theme = preload("res://assets/themes/baseUI.tres") + label.add_theme_font_size_override("font_size", 32) + label.text = "@" + drawer.add_child(label) + + #var local_tl = gridmap.map_to_local(Vector3i(cell_left, 0, cell_top)) + #var global_tl = gridmap.to_global(local_tl) + #var local_br = gridmap.map_to_local(Vector3i(cell_right, 0, cell_bottom)) + #var global_br = gridmap.to_global(local_br) + + center_x_min = 0 + center_x_max = cell_width - 128 + center_y_min = 0 + center_y_max = cell_height - 128 + + if center_x_max < center_x_min: + center_x_min = (center_x_min + center_x_max) / 2 + center_x_max = center_x_min + + if center_y_max < center_y_min: + center_y_min = (center_y_min + center_y_max) / 2 + center_y_max = center_y_min + + +func _process(_delta): + if visible == false: + return + + drawer.position.x = clamp(player.position.x - cell_left - 64, center_x_min, center_x_max) * -2 + drawer.position.y = clamp(player.position.z - cell_top - 64, center_y_min, center_y_max) * -2 + + label.position.x = (player.position.x - cell_left) * 2 - 16 + label.position.y = (player.position.z - cell_top) * 2 - 16 + + +func _renderMap(gridmap): + var heights = {} + + var rendered = Image.create_empty(cell_width, cell_height, false, Image.FORMAT_RGBA8) + rendered.fill(Color.TRANSPARENT) + + var meshes_node = get_tree().get_root().get_node("scene/Meshes") + if meshes_node != null: + _renderMeshNode(gridmap, meshes_node, rendered) + + for pos in gridmap.get_used_cells(): + var in_plane = Vector2i(pos.x, pos.z) + + if in_plane in heights and heights[in_plane] > pos.y: + continue + + heights[in_plane] = pos.y + + var cell_item = gridmap.get_cell_item(pos) + var mesh = gridmap.mesh_library.get_item_mesh(cell_item) + var material = mesh.surface_get_material(0) + var color = material.albedo_color + + rendered.set_pixel(pos.x - cell_left, pos.z - cell_top, color) + + return rendered + + +func _renderMeshNode(gridmap, mesh, rendered): + if mesh is MeshInstance3D: + var local_tl = gridmap.map_to_local(Vector3i(cell_left, 0, cell_top)) + var global_tl = gridmap.to_global(local_tl) + var mesh_material = mesh.get_surface_override_material(0) + if mesh_material != null: + var mesh_color = mesh_material.albedo_color + + for y in range( + max(mesh.position.z - mesh.scale.z / 2 - global_tl.z, 0), + min(mesh.position.z + mesh.scale.z / 2 - global_tl.z, cell_height) + ): + for x in range( + max(mesh.position.x - mesh.scale.x / 2 - global_tl.x, 0), + min(mesh.position.x + mesh.scale.x / 2 - global_tl.x, cell_width) + ): + rendered.set_pixel(x, y, mesh_color) + + for child in mesh.get_children(): + _renderMeshNode(gridmap, child, rendered) diff --git a/apworld/client/painting.gd b/apworld/client/painting.gd new file mode 100644 index 0000000..276d4eb --- /dev/null +++ b/apworld/client/painting.gd @@ -0,0 +1,38 @@ +extends "res://scripts/nodes/painting.gd" + +var item_id +var item_amount + + +func _ready(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + var gamedata = global.get_node("Gamedata") + var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) + if door_id != null: + var ap = global.get_node("Archipelago") + var item_lock = ap.get_item_id_for_door(door_id) + + if item_lock != null: + item_id = item_lock[0] + item_amount = item_lock[1] + + self.senders = [] + self.senderGroup = [] + self.nested = false + self.complete_at = 0 + self.max_length = 0 + self.excludeSenders = [] + + call_deferred("_readier") + + super._ready() + + +func _readier(): + var ap = global.get_node("Archipelago") + + if ap.client.getItemAmount(item_id) >= item_amount: + handleTriggered() diff --git a/apworld/client/panel.gd b/apworld/client/panel.gd new file mode 100644 index 0000000..fdaaf0e --- /dev/null +++ b/apworld/client/panel.gd @@ -0,0 +1,101 @@ +extends "res://scripts/nodes/panel.gd" + +var panel_logic = null +var symbol_solvable = true + +var black = load("res://assets/materials/black.material") + + +func _ready(): + super._ready() + + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + var gamedata = global.get_node("Gamedata") + var panel_id = gamedata.get_panel_for_map_node_path(global.map, node_path) + if panel_id != null: + var ap = global.get_node("Archipelago") + if ap.shuffle_symbols: + if global.map == "the_entry" and node_path == "Panels/Entry/front_1": + clue = "i" + symbol = "" + + setField("clue", clue) + setField("symbol", symbol) + + panel_logic = gamedata.objects.get_panels()[panel_id] + checkSymbolSolvable() + + if not symbol_solvable: + get_tree().get_root().get_node("scene/player").connect( + "evaluate_solvability", evaluateSolvability + ) + + +func checkSymbolSolvable(): + var old_solvable = symbol_solvable + symbol_solvable = true + + if panel_logic == null: + # There's no logic for this panel. + return + + var ap = global.get_node("Archipelago") + if not ap.shuffle_symbols: + # Symbols aren't item-locked. + return + + var gamedata = global.get_node("Gamedata") + for symbol in panel_logic.get_symbols(): + var item_name = gamedata.kSYMBOL_ITEMS.get(symbol) + var item_id = gamedata.objects.get_special_ids()[item_name] + if ap.client.getItemAmount(item_id) < 1: + symbol_solvable = false + break + + if symbol_solvable != old_solvable: + if symbol_solvable: + setField("clue", clue) + setField("symbol", symbol) + setField("answer", answer) + else: + quad_mesh.surface_set_material(0, black) + get_node("Hinge/clue").text = "missing" + get_node("Hinge/answer").text = "symbols" + + +func checkSolvable(key): + checkSymbolSolvable() + if not symbol_solvable: + return false + + return super.checkSolvable(key) + + +func evaluateSolvability(): + checkSolvable("") + + +func passedInput(key, skip_focus_check = false): + if not symbol_solvable: + return + + super.passedInput(key, skip_focus_check) + + +func focus(): + if not symbol_solvable: + has_focus = false + return + + super.focus() + + +func unfocus(): + if not symbol_solvable: + has_focus = false + return + + super.unfocus() diff --git a/apworld/client/pauseMenu.gd b/apworld/client/pauseMenu.gd new file mode 100644 index 0000000..448f690 --- /dev/null +++ b/apworld/client/pauseMenu.gd @@ -0,0 +1,51 @@ +extends "res://scripts/ui/pauseMenu.gd" + +var compass_button + + +func _ready(): + var ap_panel = Panel.new() + ap_panel.name = "Archipelago" + get_node("menu/settings/settingsInner/TabContainer").add_child(ap_panel) + + var ap = global.get_node("Archipelago") + + compass_button = CheckBox.new() + compass_button.text = "show compass" + compass_button.button_pressed = ap.show_compass + compass_button.position = Vector2(65, 100) + compass_button.theme = preload("res://assets/themes/baseUI.tres") + compass_button.add_theme_font_size_override("font_size", 60) + compass_button.pressed.connect(_toggle_compass) + ap_panel.add_child(compass_button) + + super._ready() + + +func _pause_game(): + global.get_node("Textclient").dismiss() + super._pause_game() + + +func _main_menu(): + global.loaded = false + global.get_node("Archipelago").disconnect_from_ap() + global.get_node("Messages").clear() + global.get_node("Compass").visible = false + + autosplitter.reset() + _unpause_game() + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + musicPlayer.stop() + + var runtime = global.get_node("Runtime") + get_tree().change_scene_to_packed(runtime.load_path("settings_screen.tscn")) + + +func _toggle_compass(): + var ap = global.get_node("Archipelago") + ap.show_compass = compass_button.button_pressed + ap.saveSettings() + + var compass = global.get_node("Compass") + compass.visible = compass_button.button_pressed diff --git a/apworld/client/player.gd b/apworld/client/player.gd new file mode 100644 index 0000000..e58f1bc --- /dev/null +++ b/apworld/client/player.gd @@ -0,0 +1,369 @@ +extends "res://scripts/nodes/player.gd" + +const kEndingNameByVictoryValue = { + 0: "GRAY", + 1: "PURPLE", + 2: "MINT", + 3: "BLACK", + 4: "BLUE", + 5: "CYAN", + 6: "RED", + 7: "PLUM", + 8: "ORANGE", + 9: "GOLD", + 10: "YELLOW", + 11: "GREEN", + 12: "WHITE", +} + +signal evaluate_solvability + +var compass + + +func _ready(): + var khl_script = load("res://scripts/nodes/keyHolderListener.gd") + + var pause_menu = get_node("pause_menu") + pause_menu.layer = 3 + + var ap = global.get_node("Archipelago") + var gamedata = global.get_node("Gamedata") + + compass = global.get_node("Compass") + compass.visible = ap.show_compass + + ap.start_batching_locations() + + # Set up door locations. + var map_id = gamedata.map_id_by_name.get(global.map) + for door in gamedata.objects.get_doors(): + if door.get_map_id() != map_id: + continue + + if not door.has_ap_id(): + continue + + if ( + door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY + or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING + ): + continue + + var locationListener = ap.SCRIPT_locationListener.new() + locationListener.location_id = door.get_ap_id() + locationListener.name = "locationListener_%d" % door.get_ap_id() + + for panel_ref in door.get_panels(): + var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()] + var panel_path = panel_data.get_path() + + if panel_ref.has_answer(): + for proxy in panel_data.get_proxies(): + if proxy.get_answer() == panel_ref.get_answer(): + panel_path = proxy.get_path() + break + + locationListener.senders.append(NodePath("/root/scene/" + panel_path)) + + for keyholder_ref in door.get_keyholders(): + var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()] + + var khl = khl_script.new() + khl.name = ( + "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()] + ) + khl.answer = keyholder_ref.get_key() + khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path())) + get_parent().add_child.call_deferred(khl) + + locationListener.senders.append(NodePath("../" + khl.name)) + + for sender in door.get_senders(): + locationListener.senders.append(NodePath("/root/scene/" + sender)) + + if door.has_complete_at(): + locationListener.complete_at = door.get_complete_at() + + get_parent().add_child.call_deferred(locationListener) + + # Set up letter locations. + for letter in gamedata.objects.get_letters(): + var room = gamedata.objects.get_rooms()[letter.get_room_id()] + if room.get_map_id() != map_id: + continue + + var locationListener = ap.SCRIPT_locationListener.new() + locationListener.location_id = letter.get_ap_id() + locationListener.name = "locationListener_%d" % letter.get_ap_id() + locationListener.senders.append(NodePath("/root/scene/" + letter.get_path())) + + get_parent().add_child.call_deferred(locationListener) + + if ( + ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2()) + != ap.kLETTER_BEHAVIOR_VANILLA + ): + var scout = ap.scout_location(letter.get_ap_id()) + if ( + scout != null + and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0) + ): + var item_name = "Unknown" + var item_player_game = ap.client._game_by_player[float(scout["player"])] + if ap.client._item_id_to_name[item_player_game].has(scout["item"]): + item_name = ap.client._item_id_to_name[item_player_game][scout["item"]] + + var collectable = get_tree().get_root().get_node("scene").get_node_or_null( + letter.get_path() + ) + if collectable != null: + collectable.setScoutedText.call_deferred(item_name) + + # Set up mastery locations. + for mastery in gamedata.objects.get_masteries(): + var room = gamedata.objects.get_rooms()[mastery.get_room_id()] + if room.get_map_id() != map_id: + continue + + var locationListener = ap.SCRIPT_locationListener.new() + locationListener.location_id = mastery.get_ap_id() + locationListener.name = "locationListener_%d" % mastery.get_ap_id() + locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path())) + + get_parent().add_child.call_deferred(locationListener) + + # Set up ending locations. + for ending in gamedata.objects.get_endings(): + var room = gamedata.objects.get_rooms()[ending.get_room_id()] + if room.get_map_id() != map_id: + continue + + var locationListener = ap.SCRIPT_locationListener.new() + locationListener.location_id = ending.get_ap_id() + locationListener.name = "locationListener_%d" % ending.get_ap_id() + locationListener.senders.append(NodePath("/root/scene/" + ending.get_path())) + + get_parent().add_child.call_deferred(locationListener) + + if kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name(): + var victoryListener = ap.SCRIPT_victoryListener.new() + victoryListener.name = "victoryListener" + victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path())) + + get_parent().add_child.call_deferred(victoryListener) + + # Set up keyholder locations, in keyholder sanity. + if ap.keyholder_sanity: + for keyholder in gamedata.objects.get_keyholders(): + if not keyholder.has_key(): + continue + + var room = gamedata.objects.get_rooms()[keyholder.get_room_id()] + if room.get_map_id() != map_id: + continue + + var locationListener = ap.SCRIPT_locationListener.new() + locationListener.location_id = keyholder.get_ap_id() + locationListener.name = "locationListener_%d" % keyholder.get_ap_id() + + var khl = khl_script.new() + khl.name = "location_%d_keyholder" % keyholder.get_ap_id() + khl.answer = keyholder.get_key() + khl.senders.append(NodePath("/root/scene/" + keyholder.get_path())) + get_parent().add_child.call_deferred(khl) + + locationListener.senders.append(NodePath("../" + khl.name)) + + get_parent().add_child.call_deferred(locationListener) + + # Block off roof access in Daedalus. + if global.map == "daedalus" and not ap.daedalus_roof_access: + _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49) + _set_up_invis_wall(51.5, 11, -17, 16, 10, 1) + _set_up_invis_wall(46, 10, -9.5, 1, 10, 10) + _set_up_invis_wall(67.5, 11, 17, 16, 10, 1) + _set_up_invis_wall(50.5, 11, 14, 10, 10, 1) + _set_up_invis_wall(39, 10, 18.5, 1, 10, 22) + _set_up_invis_wall(20, 15, 18.5, 1, 10, 16) + _set_up_invis_wall(11.5, 15, 3, 32, 10, 1) + _set_up_invis_wall(11.5, 16, -20, 14, 20, 1) + _set_up_invis_wall(14, 16, -26.5, 1, 20, 4) + _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25) + _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1) + _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1) + _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11) + _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11) + + var warp_exit_prefab = preload("res://objects/nodes/exit.tscn") + var warp_exit = warp_exit_prefab.instantiate() + warp_exit.name = "roof_access_blocker_warp_exit" + warp_exit.position = Vector3(58, 10, 0) + warp_exit.rotation_degrees.y = 90 + get_parent().add_child.call_deferred(warp_exit) + + var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn") + var warp_enter = warp_enter_prefab.instantiate() + warp_enter.target = warp_exit + warp_enter.position = Vector3(76.5, 30, 1) + warp_enter.scale = Vector3(4, 1.5, 1) + warp_enter.rotation_degrees.y = 90 + get_parent().add_child.call_deferred(warp_enter) + + if global.map == "the_entry": + # Remove door behind X1. + var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1") + door_node.handleTriggered() + + # Display win condition. + var sign_prefab = preload("res://objects/nodes/sign.tscn") + var sign1 = sign_prefab.instantiate() + sign1.position = Vector3(-7, 5, -15.01) + sign1.text = "victory" + get_parent().add_child.call_deferred(sign1) + + var sign2 = sign_prefab.instantiate() + sign2.position = Vector3(-7, 4, -15.01) + sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?") + + var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower() + if sign2_color == "white": + sign2_color = "silver" + + sign2.material = load("res://assets/materials/%s.material" % sign2_color) + get_parent().add_child.call_deferred(sign2) + + # Add the strict purple ending validation. + if global.map == "the_sun_temple" and ap.strict_purple_ending: + var panel_prefab = preload("res://objects/nodes/panel.tscn") + var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") + var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn") + + var previous_panel = null + var next_y = -100 + var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] + for word in words: + var panel = panel_prefab.instantiate() + panel.position = Vector3(0, next_y, 0) + next_y -= 10 + panel.clue = word + panel.symbol = "" + panel.answer = word + panel.name = "EndCheck_%s" % word + + var tpl = tpl_prefab.instantiate() + tpl.teleport_point = Vector3(0, 1, 0) + tpl.teleport_rotate = Vector3(-45, 180, 0) + tpl.target_path = panel + tpl.name = "Teleport" + + if previous_panel == null: + tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24")) + else: + tpl.senders.append(NodePath("../../%s" % previous_panel.name)) + + var reversing = reverse_prefab.instantiate() + reversing.senders.append(NodePath("..")) + reversing.name = "Reversing" + tpl.senders.append(NodePath("../Reversing")) + + panel.add_child.call_deferred(tpl) + panel.add_child.call_deferred(reversing) + get_parent().get_node("Panels").add_child.call_deferred(panel) + + previous_panel = panel + + # Duplicate the doors that usually wait on EQUINOX. We can't set the senders + # here for some reason so we actually set them in the door ready function. + var endplat = get_node("/root/scene/Components/Doors/EndPlatform") + var endplat2 = endplat.duplicate() + endplat2.name = "spe_EndPlatform" + endplat.get_parent().add_child.call_deferred(endplat2) + endplat.queue_free() + + var entry2 = get_node("/root/scene/Components/Doors/entry_2") + var entry22 = entry2.duplicate() + entry22.name = "spe_entry_2" + entry2.get_parent().add_child.call_deferred(entry22) + entry2.queue_free() + + # Add the strict cyan ending validation. + if global.map == "the_parthenon" and ap.strict_cyan_ending: + var panel_prefab = preload("res://objects/nodes/panel.tscn") + var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") + var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn") + + var previous_panel = null + var next_y = -100 + var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] + for word in words: + var panel = panel_prefab.instantiate() + panel.position = Vector3(0, next_y, 0) + next_y -= 10 + panel.clue = word + panel.symbol = "." + panel.answer = "%s%s" % [word, word] + panel.name = "EndCheck_%s" % word + + var tpl = tpl_prefab.instantiate() + tpl.teleport_point = Vector3(0, 1, -11) + tpl.teleport_rotate = Vector3(-45, 0, 0) + tpl.target_path = panel + tpl.name = "Teleport" + + if previous_panel == null: + tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers")) + else: + tpl.senders.append(NodePath("../../%s" % previous_panel.name)) + + var reversing = reverse_prefab.instantiate() + reversing.senders.append(NodePath("..")) + reversing.name = "Reversing" + tpl.senders.append(NodePath("../Reversing")) + + panel.add_child.call_deferred(tpl) + panel.add_child.call_deferred(reversing) + get_parent().get_node("Panels").add_child.call_deferred(panel) + + previous_panel = panel + + # Duplicate the door that usually waits on the rulers. We can't set the + # senders here for some reason so we actually set them in the door ready + # function. + var entry1 = get_node("/root/scene/Components/Doors/entry_1") + var entry12 = entry1.duplicate() + entry12.name = "spe_entry_1" + entry1.get_parent().add_child.call_deferred(entry12) + entry1.queue_free() + + var minimap = ap.SCRIPT_minimap.new() + minimap.name = "Minimap" + get_parent().add_child.call_deferred(minimap) + + super._ready() + + await get_tree().process_frame + await get_tree().process_frame + + ap.stop_batching_locations() + + +func _set_up_invis_wall(x, y, z, sx, sy, sz): + var prefab = preload("res://objects/nodes/block.tscn") + var newwall = prefab.instantiate() + newwall.position.x = x + newwall.position.y = y + newwall.position.z = z + newwall.scale.x = sz + newwall.scale.y = sy + newwall.scale.z = sx + newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material")) + newwall.visibility_range_end = 3 + newwall.visibility_range_end_margin = 1 + newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF + newwall.skeleton = ".." + get_parent().add_child.call_deferred(newwall) + + +func _process(_dt): + compass.update_rotation(global_rotation.y) diff --git a/apworld/client/rainbowText.gd b/apworld/client/rainbowText.gd new file mode 100644 index 0000000..9a4c1d0 --- /dev/null +++ b/apworld/client/rainbowText.gd @@ -0,0 +1,10 @@ +extends RichTextEffect + +var bbcode = "rainbow" + + +func _process_custom_fx(char_fx: CharFXTransform): + char_fx.color = Color.from_hsv( + char_fx.elapsed_time - floor(char_fx.elapsed_time), 1.0, 1.0, 1.0 + ) + return true diff --git a/apworld/client/run_from_source.tscn b/apworld/client/run_from_source.tscn new file mode 100644 index 0000000..b710677 --- /dev/null +++ b/apworld/client/run_from_source.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=11 format=2] + +[sub_resource id=2 type="GDScript"] +script/source = "extends Node2D + + +func _ready(): + var args = OS.get_cmdline_user_args() + var source_path = args[0] + + var runtime_script = ResourceLoader.load(\"%s/source_runtime.gd\" % source_path) + var runtime = runtime_script.new(source_path) + runtime.name = \"Runtime\" + + global.add_child(runtime) + + var settings_screen = runtime.load_path(\"settings_screen.tscn\") + get_tree().change_scene_to_packed(settings_screen) + +" + +[node name="loader" type="Node2D"] +script = SubResource( 2 ) diff --git a/apworld/client/saver.gd b/apworld/client/saver.gd new file mode 100644 index 0000000..44bc179 --- /dev/null +++ b/apworld/client/saver.gd @@ -0,0 +1,23 @@ +extends "res://scripts/nodes/saver.gd" + + +func levelLoaded(): + if type == "keyholders": + var ap = global.get_node("Archipelago") + ap.keyboard.load_keyholders.call_deferred(global.map) + else: + reload.call_deferred() + + +func reload(): + # Just rewriting this whole thing so I can remove Chris's safeguard. + var file = FileAccess.open(path + type + ".save", FileAccess.READ) + if file: + var data = file.get_var(true) + file.close() + for datum in data: + var saveable = get_node_or_null(datum[0]) + if saveable != null: + saveable.is_complete = datum[1] + if saveable.is_complete: + saveable.loadData(saveable.is_complete) diff --git a/apworld/client/settings_screen.tscn b/apworld/client/settings_screen.tscn new file mode 100644 index 0000000..63d5dbe --- /dev/null +++ b/apworld/client/settings_screen.tscn @@ -0,0 +1,173 @@ +[gd_scene load_steps=11 format=2] + +[ext_resource path="res://images/unchecked.png" type="Texture" id=7] +[ext_resource path="res://images/checked.png" type="Texture" id=8] +[ext_resource type="Theme" uid="uid://7w454egydi41" path="res://assets/themes/baseUI.tres" id="2_g4bvn"] + +[sub_resource type="StyleBoxFlat" id=1] +bg_color = Color( 0, 0, 0, 0 ) + +[sub_resource type="StyleBoxFlat" id=2] +bg_color = Color( 1, 1, 1, 1 ) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color( 1, 1, 0, 1 ) +border_blend = true +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +expand_offset_left = 5.0 +expand_offset_right = 5.0 +expand_offset_top = 5.0 +expand_offset_bottom = 5.0 + +[sub_resource id=4 type="GDScript"] +script/source = "extends Node2D + + +func _ready(): + var runtime = global.get_node(\"Runtime\") + var main_script = runtime.load_path(\"main.gd\") + var main_node = main_script.new() + main_node.name = \"Main\" + add_child(main_node) + +" + +[node name="settings_screen" type="Node2D"] +script = SubResource( 4 ) + +[node name="Panel" type="Panel" parent="."] +offset_right = 1920.0 +offset_bottom = 1080.0 + +[node name="title" parent="Panel" type="Label"] +offset_left = 0.0 +offset_top = 75.0 +offset_right = 1920.0 +offset_bottom = 225.0 +text = "ARCHIPELAGO" +valign = 1 +horizontal_alignment = 1 +theme = ExtResource("2_g4bvn") + +[node name="credit" parent="Panel" type="Label"] +visible = false +offset_left = 1278.0 +offset_top = 974.0 +offset_right = 1868.0 +offset_bottom = 1034.0 +text = "Brenton Wildes" +theme = ExtResource("2_g4bvn") + +[node name="connect_button" parent="Panel" type="Button"] +offset_left = 255.0 +offset_top = 875.0 +offset_right = 891.0 +offset_bottom = 1025.0 +custom_colors/font_color_hover = Color( 1, 0.501961, 0, 1 ) +text = "CONNECT" +theme = ExtResource("2_g4bvn") + +[node name="quit_button" parent="Panel" type="Button"] +offset_left = 1102.0 +offset_top = 875.0 +offset_right = 1738.0 +offset_bottom = 1025.0 +custom_colors/font_color_hover = Color( 1, 0, 0, 1 ) +text = "QUIT" +theme = ExtResource("2_g4bvn") + +[node name="credit2" parent="Panel" type="Label"] +offset_left = -105.0 +offset_top = 346.0 +offset_right = 485.0 +offset_bottom = 410.0 +custom_styles/normal = SubResource( 1 ) +text = "SERVER" +align = 2 +theme = ExtResource("2_g4bvn") + +[node name="credit5" parent="Panel" type="Label"] +offset_left = 1239.0 +offset_top = 422.0 +offset_right = 1829.0 +offset_bottom = 486.0 +custom_styles/normal = SubResource( 1 ) +text = "OPTIONS" +theme = ExtResource("2_g4bvn") + +[node name="credit3" parent="Panel" type="Label"] +offset_left = -105.0 +offset_top = 519.0 +offset_right = 485.0 +offset_bottom = 583.0 +custom_styles/normal = SubResource( 1 ) +text = "PLAYER" +align = 2 +theme = ExtResource("2_g4bvn") + +[node name="credit4" parent="Panel" type="Label"] +offset_left = -105.0 +offset_top = 704.0 +offset_right = 485.0 +offset_bottom = 768.0 +custom_styles/normal = SubResource( 1 ) +text = "PASSWORD" +align = 2 +theme = ExtResource("2_g4bvn") + +[node name="server_box" type="LineEdit" parent="Panel"] +offset_left = 502.0 +offset_top = 295.0 +offset_right = 1144.0 +offset_bottom = 445.0 +custom_colors/selection_color = Color( 0.482353, 0, 0, 1 ) +custom_colors/cursor_color = Color( 0, 0, 0, 1 ) +custom_colors/font_color = Color( 0, 0, 0, 1 ) +custom_styles/focus = SubResource( 2 ) +align = 1 +caret_blink = true + +[node name="player_box" type="LineEdit" parent="Panel"] +offset_left = 502.0 +offset_top = 477.0 +offset_right = 1144.0 +offset_bottom = 627.0 +custom_colors/selection_color = Color( 0.482353, 0, 0, 1 ) +custom_colors/cursor_color = Color( 0, 0, 0, 1 ) +custom_colors/font_color = Color( 0, 0, 0, 1 ) +custom_styles/focus = SubResource( 2 ) +align = 1 +caret_blink = true + +[node name="password_box" type="LineEdit" parent="Panel"] +offset_left = 502.0 +offset_top = 659.0 +offset_right = 1144.0 +offset_bottom = 809.0 +custom_colors/selection_color = Color( 0.482353, 0, 0, 1 ) +custom_colors/cursor_color = Color( 0, 0, 0, 1 ) +custom_colors/font_color = Color( 0, 0, 0, 1 ) +custom_styles/focus = SubResource( 2 ) +align = 1 +caret_blink = true + +[node name="AcceptDialog" type="AcceptDialog" parent="Panel"] +offset_right = 83.0 +offset_bottom = 58.0 + +[node name="VersionMismatch" type="ConfirmationDialog" parent="Panel"] +offset_right = 83.0 +offset_bottom = 58.0 + +[node name="connection_history" type="MenuButton" parent="Panel"] +offset_left = 1239.0 +offset_top = 276.0 +offset_right = 1829.0 +offset_bottom = 372.0 +text = "connection history" +flat = false diff --git a/apworld/client/source_runtime.gd b/apworld/client/source_runtime.gd new file mode 100644 index 0000000..fbb7009 --- /dev/null +++ b/apworld/client/source_runtime.gd @@ -0,0 +1,15 @@ +extends Node + +var source_path + + +func _init(path): + source_path = path + + +func load_path(path): + return ResourceLoader.load("%s/%s" % [source_path, path]) + + +func read_path(path): + return FileAccess.get_file_as_bytes("%s/%s" % [source_path, path]) diff --git a/apworld/client/teleport.gd b/apworld/client/teleport.gd new file mode 100644 index 0000000..428d50b --- /dev/null +++ b/apworld/client/teleport.gd @@ -0,0 +1,38 @@ +extends "res://scripts/nodes/teleport.gd" + +var item_id +var item_amount + + +func _ready(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + var gamedata = global.get_node("Gamedata") + var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) + if door_id != null: + var ap = global.get_node("Archipelago") + var item_lock = ap.get_item_id_for_door(door_id) + + if item_lock != null: + item_id = item_lock[0] + item_amount = item_lock[1] + + self.senders = [] + self.senderGroup = [] + self.nested = false + self.complete_at = 0 + self.max_length = 0 + self.excludeSenders = [] + + call_deferred("_readier") + + super._ready() + + +func _readier(): + var ap = global.get_node("Archipelago") + + if ap.client.getItemAmount(item_id) >= item_amount: + handleTriggered() diff --git a/apworld/client/teleportListener.gd b/apworld/client/teleportListener.gd new file mode 100644 index 0000000..6f363af --- /dev/null +++ b/apworld/client/teleportListener.gd @@ -0,0 +1,49 @@ +extends "res://scripts/nodes/listeners/teleportListener.gd" + +var item_id +var item_amount + + +func _ready(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + if ( + global.map == "daedalus" + and ( + node_path == "Components/Triggers/teleportListenerConnections" + or node_path == "Components/Triggers/teleportListenerConnections2" + ) + ): + # Effectively disable these. + teleport_point = target_path.position + return + + var gamedata = global.get_node("Gamedata") + var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) + if door_id != null: + var ap = global.get_node("Archipelago") + var item_lock = ap.get_item_id_for_door(door_id) + + if item_lock != null: + item_id = item_lock[0] + item_amount = item_lock[1] + + self.senders = [] + self.senderGroup = [] + self.nested = false + self.complete_at = 0 + self.max_length = 0 + self.excludeSenders = [] + + call_deferred("_readier") + + super._ready() + + +func _readier(): + var ap = global.get_node("Archipelago") + + if ap.client.getItemAmount(item_id) >= item_amount: + handleTriggered() diff --git a/apworld/client/textclient.gd b/apworld/client/textclient.gd new file mode 100644 index 0000000..26831b4 --- /dev/null +++ b/apworld/client/textclient.gd @@ -0,0 +1,95 @@ +extends CanvasLayer + +var panel +var label +var entry +var is_open = false + + +func _ready(): + process_mode = ProcessMode.PROCESS_MODE_ALWAYS + layer = 2 + + panel = Panel.new() + panel.set_name("Panel") + panel.offset_left = 100 + panel.offset_right = 1820 + panel.offset_top = 100 + panel.offset_bottom = 980 + panel.visible = false + add_child(panel) + + label = RichTextLabel.new() + label.set_name("Label") + label.offset_left = 80 + label.offset_right = 1640 + label.offset_top = 80 + label.offset_bottom = 720 + label.scroll_following = true + label.selection_enabled = true + panel.add_child(label) + + label.push_font(load("res://assets/fonts/Lingo2.ttf")) + label.push_font_size(36) + + var entry_style = StyleBoxFlat.new() + entry_style.bg_color = Color(0.9, 0.9, 0.9, 1) + + entry = LineEdit.new() + entry.set_name("Entry") + entry.offset_left = 80 + entry.offset_right = 1640 + entry.offset_top = 760 + entry.offset_bottom = 840 + entry.add_theme_font_override("font", load("res://assets/fonts/Lingo2.ttf")) + entry.add_theme_font_size_override("font_size", 36) + entry.add_theme_color_override("font_color", Color(0, 0, 0, 1)) + entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1)) + entry.add_theme_stylebox_override("focus", entry_style) + panel.add_child(entry) + entry.connect("text_submitted", text_entered) + + +func _input(event): + if global.loaded and event is InputEventKey and event.pressed: + if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT): + if !get_tree().paused: + is_open = true + get_tree().paused = true + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + panel.visible = true + entry.grab_focus() + get_viewport().set_input_as_handled() + else: + dismiss() + elif event.keycode == KEY_ESCAPE: + if is_open: + dismiss() + get_viewport().set_input_as_handled() + + +func dismiss(): + if is_open: + get_tree().paused = false + Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) + panel.visible = false + is_open = false + + +func parse_printjson(text): + label.append_text("[p]" + text + "[/p]") + + +func text_entered(text): + var ap = global.get_node("Archipelago") + var cmd = text.trim_suffix("\n") + entry.text = "" + if OS.is_debug_build(): + if cmd.begins_with("/tp_map "): + var new_map = cmd.substr(8) + global.map = new_map + global.sets_entry_point = false + switcher.switch_map("res://objects/scenes/%s.tscn" % new_map) + return + + ap.client.say(cmd) diff --git a/apworld/client/vendor/LICENSE b/apworld/client/vendor/LICENSE new file mode 100644 index 0000000..115ba15 --- /dev/null +++ b/apworld/client/vendor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Xavier Sellier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/apworld/client/vendor/uuid.gd b/apworld/client/vendor/uuid.gd new file mode 100644 index 0000000..b63fa04 --- /dev/null +++ b/apworld/client/vendor/uuid.gd @@ -0,0 +1,195 @@ +# Note: The code might not be as pretty it could be, since it's written +# in a way that maximizes performance. Methods are inlined and loops are avoided. +extends Node + +const BYTE_MASK: int = 0b11111111 + + +static func uuidbin(): + randomize() + # 16 random bytes with the bytes on index 6 and 8 modified + return [ + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + ((randi() & BYTE_MASK) & 0x0f) | 0x40, + randi() & BYTE_MASK, + ((randi() & BYTE_MASK) & 0x3f) | 0x80, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + randi() & BYTE_MASK, + ] + + +static func uuidbinrng(rng: RandomNumberGenerator): + rng.randomize() + return [ + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, + rng.randi() & BYTE_MASK, + ((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, + ] + + +static func v4(): + # 16 random bytes with the bytes on index 6 and 8 modified + var b = uuidbin() + + return ( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" + % [ + # low + b[0], + b[1], + b[2], + b[3], + # mid + b[4], + b[5], + # hi + b[6], + b[7], + # clock + b[8], + b[9], + # clock + b[10], + b[11], + b[12], + b[13], + b[14], + b[15] + ] + ) + + +static func v4_rng(rng: RandomNumberGenerator): + # 16 random bytes with the bytes on index 6 and 8 modified + var b = uuidbinrng(rng) + + return ( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" + % [ + # low + b[0], + b[1], + b[2], + b[3], + # mid + b[4], + b[5], + # hi + b[6], + b[7], + # clock + b[8], + b[9], + # clock + b[10], + b[11], + b[12], + b[13], + b[14], + b[15] + ] + ) + + +var _uuid: Array + + +func _init(rng := RandomNumberGenerator.new()) -> void: + _uuid = uuidbinrng(rng) + + +func as_array() -> Array: + return _uuid.duplicate() + + +func as_dict(big_endian := true) -> Dictionary: + if big_endian: + return { + "low": (_uuid[0] << 24) + (_uuid[1] << 16) + (_uuid[2] << 8) + _uuid[3], + "mid": (_uuid[4] << 8) + _uuid[5], + "hi": (_uuid[6] << 8) + _uuid[7], + "clock": (_uuid[8] << 8) + _uuid[9], + "node": + ( + (_uuid[10] << 40) + + (_uuid[11] << 32) + + (_uuid[12] << 24) + + (_uuid[13] << 16) + + (_uuid[14] << 8) + + _uuid[15] + ) + } + else: + return { + "low": _uuid[0] + (_uuid[1] << 8) + (_uuid[2] << 16) + (_uuid[3] << 24), + "mid": _uuid[4] + (_uuid[5] << 8), + "hi": _uuid[6] + (_uuid[7] << 8), + "clock": _uuid[8] + (_uuid[9] << 8), + "node": + ( + _uuid[10] + + (_uuid[11] << 8) + + (_uuid[12] << 16) + + (_uuid[13] << 24) + + (_uuid[14] << 32) + + (_uuid[15] << 40) + ) + } + + +func as_string() -> String: + return ( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" + % [ + # low + _uuid[0], + _uuid[1], + _uuid[2], + _uuid[3], + # mid + _uuid[4], + _uuid[5], + # hi + _uuid[6], + _uuid[7], + # clock + _uuid[8], + _uuid[9], + # node + _uuid[10], + _uuid[11], + _uuid[12], + _uuid[13], + _uuid[14], + _uuid[15] + ] + ) + + +func is_equal(other) -> bool: + # Godot Engine compares Array recursively + # There's no need for custom comparison here. + return _uuid == other._uuid diff --git a/apworld/client/victoryListener.gd b/apworld/client/victoryListener.gd new file mode 100644 index 0000000..e9089d7 --- /dev/null +++ b/apworld/client/victoryListener.gd @@ -0,0 +1,20 @@ +extends Receiver + + +func _ready(): + super._ready() + + +func handleTriggered(): + triggered += 1 + if triggered >= total: + var ap = global.get_node("Archipelago") + ap.client.completedGoal() + + global.get_node("Messages").showMessage("You have completed your goal!") + + +func handleUntriggered(): + triggered -= 1 + if triggered < total: + pass diff --git a/apworld/client/visibilityListener.gd b/apworld/client/visibilityListener.gd new file mode 100644 index 0000000..5ea17a0 --- /dev/null +++ b/apworld/client/visibilityListener.gd @@ -0,0 +1,38 @@ +extends "res://scripts/nodes/listeners/visibilityListener.gd" + +var item_id +var item_amount + + +func _ready(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + var gamedata = global.get_node("Gamedata") + var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) + if door_id != null: + var ap = global.get_node("Archipelago") + var item_lock = ap.get_item_id_for_door(door_id) + + if item_lock != null: + item_id = item_lock[0] + item_amount = item_lock[1] + + self.senders = [] + self.senderGroup = [] + self.nested = false + self.complete_at = 0 + self.max_length = 0 + self.excludeSenders = [] + + call_deferred("_readier") + + super._ready() + + +func _readier(): + var ap = global.get_node("Archipelago") + + if ap.client.getItemAmount(item_id) >= item_amount: + handleTriggered() diff --git a/apworld/client/worldport.gd b/apworld/client/worldport.gd new file mode 100644 index 0000000..cdca248 --- /dev/null +++ b/apworld/client/worldport.gd @@ -0,0 +1,53 @@ +extends "res://scripts/nodes/worldport.gd" + +var absolute_rotation = false +var target_rotation = 0 + + +func _ready(): + var node_path = String( + get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() + ) + + var ap = global.get_node("Archipelago") + + if ap.shuffle_worldports: + var gamedata = global.get_node("Gamedata") + var port_id = gamedata.get_port_for_map_node_path(global.map, node_path) + if port_id != null: + if port_id in ap.port_pairings: + var target_port = gamedata.objects.get_ports()[ap.port_pairings[port_id]] + var target_room = gamedata.objects.get_rooms()[target_port.get_room_id()] + var target_map = gamedata.objects.get_maps()[target_room.get_map_id()] + + exit = target_map.get_name() + entry_point.x = target_port.get_destination().get_x() + entry_point.y = target_port.get_destination().get_y() + entry_point.z = target_port.get_destination().get_z() + absolute_rotation = true + target_rotation = target_port.get_rotation() + sets_entry_point = true + invisible = false + fades = true + + if global.map == "icarus" and exit == "daedalus": + if not ap.daedalus_roof_access: + entry_point = Vector3(58, 10, 0) + + super._ready() + + +func bodyEntered(body): + if body.is_in_group("player"): + if absolute_rotation: + entry_rotate.y = target_rotation - body.rotation_degrees.y + + super.bodyEntered(body) + + +func changeScene(): + var player = get_tree().get_root().get_node("scene/player") + if player != null: + player.playable = false + + super.changeScene() diff --git a/apworld/client/worldportListener.gd b/apworld/client/worldportListener.gd new file mode 100644 index 0000000..5c2faff --- /dev/null +++ b/apworld/client/worldportListener.gd @@ -0,0 +1,8 @@ +extends "res://scripts/nodes/listeners/worldportListener.gd" + + +func handleTriggered(): + if exit == "menus/credits": + return + + super.handleTriggered() diff --git a/client/Archipelago/animationListener.gd b/client/Archipelago/animationListener.gd deleted file mode 100644 index c3b26db..0000000 --- a/client/Archipelago/animationListener.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends "res://scripts/nodes/listeners/animationListener.gd" - -var item_id -var item_amount - - -func _ready(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - var gamedata = global.get_node("Gamedata") - var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) - if door_id != null: - var ap = global.get_node("Archipelago") - var item_lock = ap.get_item_id_for_door(door_id) - - if item_lock != null: - item_id = item_lock[0] - item_amount = item_lock[1] - - self.senders = [] - self.senderGroup = [] - self.nested = false - self.complete_at = 0 - self.max_length = 0 - self.excludeSenders = [] - - call_deferred("_readier") - - super._ready() - - -func _readier(): - var ap = global.get_node("Archipelago") - - if ap.client.getItemAmount(item_id) >= item_amount: - handleTriggered() diff --git a/client/Archipelago/client.gd b/client/Archipelago/client.gd deleted file mode 100644 index 843647d..0000000 --- a/client/Archipelago/client.gd +++ /dev/null @@ -1,417 +0,0 @@ -extends Node - -const ap_version = {"major": 0, "minor": 6, "build": 3, "class": "Version"} - -var SCRIPT_uuid - -var _ws = WebSocketPeer.new() -var _should_process = false -var _initiated_disconnect = false -var _try_wss = false -var _has_connected = false - -var _datapackages = {} -var _pending_packages = [] -var _item_id_to_name = {} # All games -var _location_id_to_name = {} # All games -var _item_name_to_id = {} # Lingo 2 only -var _location_name_to_id = {} # Lingo 2 only - -var _remote_version = {"major": 0, "minor": 0, "build": 0} -var _gen_version = {"major": 0, "minor": 0, "build": 0} - -var ap_server = "" -var ap_user = "" -var ap_pass = "" - -var _authenticated = false -var _seed = "" -var _team = 0 -var _slot = 0 -var _players = [] -var _player_name_by_slot = {} -var _game_by_player = {} -var _checked_locations = [] -var _received_indexes = [] -var _received_items = {} -var _slot_data = {} - -signal could_not_connect -signal connect_status -signal client_connected(slot_data) -signal item_received(item_id, index, player, flags, amount) -signal message_received(message) -signal location_scout_received(item_id, location_id, player, flags) - - -func _init(): - set_process_mode(Node.PROCESS_MODE_ALWAYS) - - _ws.inbound_buffer_size = 8388608 - - global._print("Instantiated APClient") - - # Read AP datapackages from file, if there are any - if FileAccess.file_exists("user://ap_datapackages"): - var file = FileAccess.open("user://ap_datapackages", FileAccess.READ) - var data = file.get_var(true) - file.close() - - if typeof(data) != TYPE_DICTIONARY: - global._print("AP datapackages file is corrupted") - data = {} - - _datapackages = data - - processDatapackages() - - -func _ready(): - pass - #_ws.connect("connection_closed", _closed) - #_ws.connect("connection_failed", _closed) - #_ws.connect("server_disconnected", _closed) - #_ws.connect("connection_error", _errored) - #_ws.connect("connection_established", _connected) - - -func _reset_state(): - _should_process = false - _authenticated = false - _try_wss = false - _has_connected = false - _received_items = {} - _received_indexes = [] - - -func _errored(): - if _try_wss: - global._print("Could not connect to AP with ws://, now trying wss://") - connectToServer(ap_server, ap_user, ap_pass) - else: - global._print("AP connection failed") - _reset_state() - - emit_signal( - "could_not_connect", - "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information." - ) - - -func _closed(_was_clean = true): - global._print("Connection closed") - _reset_state() - - if not _initiated_disconnect: - emit_signal("could_not_connect", "Disconnected from Archipelago") - - _initiated_disconnect = false - - -func _connected(_proto = ""): - global._print("Connected!") - _try_wss = false - - -func disconnect_from_ap(): - _initiated_disconnect = true - _ws.close() - - -func _process(_delta): - if _should_process: - _ws.poll() - - var state = _ws.get_ready_state() - if state == WebSocketPeer.STATE_OPEN: - if not _has_connected: - _has_connected = true - - _connected() - - while _ws.get_available_packet_count(): - var packet = _ws.get_packet() - global._print("Got data from server: " + packet.get_string_from_utf8()) - var json = JSON.new() - var jserror = json.parse(packet.get_string_from_utf8()) - if jserror != OK: - global._print("Error parsing packet from AP: " + jserror.error_string) - return - - for message in json.data: - var cmd = message["cmd"] - global._print("Received command: " + cmd) - - if cmd == "RoomInfo": - _seed = message["seed_name"] - _remote_version = message["version"] - _gen_version = message["generator_version"] - - var needed_games = [] - for game in message["datapackage_checksums"].keys(): - if ( - !_datapackages.has(game) - or ( - _datapackages[game]["checksum"] - != message["datapackage_checksums"][game] - ) - ): - needed_games.append(game) - - if !needed_games.is_empty(): - _pending_packages = needed_games - var cur_needed = _pending_packages.pop_front() - requestDatapackages([cur_needed]) - else: - connectToRoom() - - elif cmd == "DataPackage": - for game in message["data"]["games"].keys(): - _datapackages[game] = message["data"]["games"][game] - saveDatapackages() - - if !_pending_packages.is_empty(): - var cur_needed = _pending_packages.pop_front() - requestDatapackages([cur_needed]) - else: - processDatapackages() - connectToRoom() - - elif cmd == "Connected": - _authenticated = true - _team = message["team"] - _slot = message["slot"] - _players = message["players"] - _checked_locations = message["checked_locations"] - _slot_data = message["slot_data"] - - for player in _players: - _player_name_by_slot[player["slot"]] = player["alias"] - _game_by_player[player["slot"]] = message["slot_info"][str( - player["slot"] - )]["game"] - - emit_signal("client_connected", _slot_data) - - elif cmd == "ConnectionRefused": - var error_message = "" - for error in message["errors"]: - var submsg = "" - if error == "InvalidSlot": - submsg = "Invalid player name." - elif error == "InvalidGame": - submsg = "The specified player is not playing Lingo." - elif error == "IncompatibleVersion": - submsg = ( - "The Archipelago server is not the correct version for this client. Expected v%d.%d.%d. Found v%d.%d.%d." - % [ - ap_version["major"], - ap_version["minor"], - ap_version["build"], - _remote_version["major"], - _remote_version["minor"], - _remote_version["build"] - ] - ) - elif error == "InvalidPassword": - submsg = "Incorrect password." - elif error == "InvalidItemsHandling": - submsg = "Invalid item handling flag. This is a bug with the client." - - if submsg != "": - if error_message != "": - error_message += " " - error_message += submsg - - if error_message == "": - error_message = "Unknown error." - - _initiated_disconnect = true - _ws.close() - - emit_signal("could_not_connect", error_message) - global._print("Connection to AP refused") - global._print(message) - - elif cmd == "ReceivedItems": - var i = 0 - for item in message["items"]: - var index = int(message["index"] + i) - i += 1 - - if _received_indexes.has(index): - # Do not re-process items. - continue - - _received_indexes.append(index) - - var item_id = int(item["item"]) - _received_items[item_id] = _received_items.get(item_id, 0) + 1 - - emit_signal( - "item_received", - item_id, - index, - int(item["player"]), - int(item["flags"]), - _received_items[item_id] - ) - - elif cmd == "PrintJSON": - emit_signal("message_received", message) - - elif cmd == "LocationInfo": - for loc in message["locations"]: - emit_signal( - "location_scout_received", - int(loc["item"]), - int(loc["location"]), - int(loc["player"]), - int(loc["flags"]) - ) - - elif state == WebSocketPeer.STATE_CLOSED: - if _has_connected: - _closed() - else: - _errored() - - -func saveDatapackages(): - # Save the AP datapackages to disk. - var file = FileAccess.open("user://ap_datapackages", FileAccess.WRITE) - file.store_var(_datapackages, true) - file.close() - - -func connectToServer(server, un, pw): - ap_server = server - ap_user = un - ap_pass = pw - - _initiated_disconnect = false - - var url = "" - if ap_server.begins_with("ws://") or ap_server.begins_with("wss://"): - url = ap_server - _try_wss = false - elif _try_wss: - url = "wss://" + ap_server - _try_wss = false - else: - url = "ws://" + ap_server - _try_wss = true - - var err = _ws.connect_to_url(url) - if err != OK: - emit_signal( - "could_not_connect", - ( - "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information. Error code: %d." - % err - ) - ) - global._print("Could not connect to AP: %d" % err) - return - _should_process = true - - emit_signal("connect_status", "Connecting...") - - -func sendMessage(msg): - var payload = JSON.stringify(msg) - _ws.send_text(payload) - - -func requestDatapackages(games): - emit_signal("connect_status", "Downloading %s data package..." % games[0]) - - sendMessage([{"cmd": "GetDataPackage", "games": games}]) - - -func processDatapackages(): - _item_id_to_name = {} - _location_id_to_name = {} - for game in _datapackages.keys(): - var package = _datapackages[game] - - _item_id_to_name[game] = {} - for item_name in package["item_name_to_id"].keys(): - _item_id_to_name[game][int(package["item_name_to_id"][item_name])] = item_name - - _location_id_to_name[game] = {} - for location_name in package["location_name_to_id"].keys(): - _location_id_to_name[game][int(package["location_name_to_id"][location_name])] = location_name - - if _datapackages.has("Lingo 2"): - _item_name_to_id = _datapackages["Lingo 2"]["item_name_to_id"] - _location_name_to_id = _datapackages["Lingo 2"]["location_name_to_id"] - - -func connectToRoom(): - emit_signal("connect_status", "Authenticating...") - - sendMessage( - [ - { - "cmd": "Connect", - "password": ap_pass, - "game": "Lingo 2", - "name": ap_user, - "uuid": SCRIPT_uuid.v4(), - "version": ap_version, - "items_handling": 0b111, # always receive our items - "tags": [], - "slot_data": true - } - ] - ) - - -func sendConnectUpdate(tags): - sendMessage([{"cmd": "ConnectUpdate", "tags": tags}]) - - -func requestSync(): - sendMessage([{"cmd": "Sync"}]) - - -func sendLocation(loc_id): - sendMessage([{"cmd": "LocationChecks", "locations": [loc_id]}]) - - -func sendLocations(loc_ids): - sendMessage([{"cmd": "LocationChecks", "locations": loc_ids}]) - - -func setValue(key, value, operation = "replace"): - sendMessage( - [ - { - "cmd": "Set", - "key": "Lingo2_%d_%s" % [_slot, key], - "want_reply": false, - "operations": [{"operation": operation, "value": value}] - } - ] - ) - - -func say(textdata): - sendMessage([{"cmd": "Say", "text": textdata}]) - - -func completedGoal(): - sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL - - -func scoutLocations(loc_ids): - sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}]) - - -func hasItem(item_id): - return _received_items.has(item_id) - - -func getItemAmount(item_id): - return _received_items.get(item_id, 0) diff --git a/client/Archipelago/collectable.gd b/client/Archipelago/collectable.gd deleted file mode 100644 index 4a17a2a..0000000 --- a/client/Archipelago/collectable.gd +++ /dev/null @@ -1,16 +0,0 @@ -extends "res://scripts/nodes/collectable.gd" - - -func pickedUp(): - if unlock_type == "key": - var ap = global.get_node("Archipelago") - if ap.get_letter_behavior(unlock_key, level == 2) == ap.kLETTER_BEHAVIOR_VANILLA: - ap.keyboard.collect_local_letter(unlock_key, level) - else: - ap.keyboard.update_unlocks() - - super.pickedUp() - - -func setScoutedText(text): - get_node("MeshInstance3D").mesh.text = text.replace(" ", "\n") diff --git a/client/Archipelago/compass.gd b/client/Archipelago/compass.gd deleted file mode 100644 index c90475a..0000000 --- a/client/Archipelago/compass.gd +++ /dev/null @@ -1,66 +0,0 @@ -extends Node2D - -const RADIUS = 48 - -var _font - - -func _ready(): - _font = load("res://assets/fonts/Lingo2.ttf") - - -func _draw(): - draw_circle(Vector2.ZERO, RADIUS, Color(1.0, 1.0, 1.0, 0.8), true) - draw_circle(Vector2.ZERO, RADIUS, Color.BLACK, false) - draw_string( - _font, - Vector2(-4, -RADIUS * 3.0 / 4.0), - "N", - HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, - -1, - 16, - Color.BLACK - ) - draw_set_transform(Vector2.ZERO, PI / 2) - draw_string( - _font, - Vector2(-4, -RADIUS * 3.0 / 4.0), - "E", - HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, - -1, - 16, - Color.BLACK - ) - draw_set_transform(Vector2.ZERO, PI) - draw_string( - _font, - Vector2(-4, -RADIUS * 3.0 / 4.0), - "S", - HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, - -1, - 16, - Color.BLACK - ) - draw_set_transform(Vector2.ZERO, PI * 3.0 / 2.0) - draw_string( - _font, - Vector2(-4, -RADIUS * 3.0 / 4.0), - "W", - HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT, - -1, - 16, - Color.BLACK - ) - draw_set_transform(Vector2.ZERO) - draw_colored_polygon( - PackedVector2Array( - [Vector2(0, -RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)] - ), - Color.RED - ) - draw_colored_polygon( - PackedVector2Array( - [Vector2(0, RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)] - ), - Color.GRAY - ) diff --git a/client/Archipelago/compass_overlay.gd b/client/Archipelago/compass_overlay.gd deleted file mode 100644 index 56e81ff..0000000 --- a/client/Archipelago/compass_overlay.gd +++ /dev/null @@ -1,17 +0,0 @@ -extends CanvasLayer - -var SCRIPT_compass - -var compass - - -func _ready(): - compass = SCRIPT_compass.new() - compass.position = Vector2(1840, 80) - add_child(compass) - - visible = false - - -func update_rotation(ry): - compass.rotation = ry diff --git a/client/Archipelago/door.gd b/client/Archipelago/door.gd deleted file mode 100644 index 49f5728..0000000 --- a/client/Archipelago/door.gd +++ /dev/null @@ -1,46 +0,0 @@ -extends "res://scripts/nodes/door.gd" - -var item_id -var item_amount - - -func _ready(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - var gamedata = global.get_node("Gamedata") - var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) - if door_id != null: - var ap = global.get_node("Archipelago") - var item_lock = ap.get_item_id_for_door(door_id) - - if item_lock != null: - item_id = item_lock[0] - item_amount = item_lock[1] - - self.senders = [] - self.senderGroup = [] - self.nested = false - self.complete_at = 0 - self.max_length = 0 - self.excludeSenders = [] - - call_deferred("_readier") - - if global.map == "the_sun_temple": - if name == "spe_EndPlatform" or name == "spe_entry_2": - senders = [NodePath("/root/scene/Panels/EndCheck_dog")] - - if global.map == "the_parthenon": - if name == "spe_entry_1": - senders = [NodePath("/root/scene/Panels/EndCheck_dog")] - - super._ready() - - -func _readier(): - var ap = global.get_node("Archipelago") - - if ap.client.getItemAmount(item_id) >= item_amount: - handleTriggered() diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd deleted file mode 100644 index 9eeec3b..0000000 --- a/client/Archipelago/gamedata.gd +++ /dev/null @@ -1,159 +0,0 @@ -extends Node - -var SCRIPT_proto - -var objects -var door_id_by_map_node_path = {} -var painting_id_by_map_node_path = {} -var panel_id_by_map_node_path = {} -var port_id_by_map_node_path = {} -var door_id_by_ap_id = {} -var map_id_by_name = {} -var progressive_id_by_ap_id = {} -var letter_id_by_ap_id = {} -var symbol_item_ids = [] -var anti_trap_ids = {} - -var kSYMBOL_ITEMS - - -func _init(proto_script): - SCRIPT_proto = proto_script - - kSYMBOL_ITEMS = { - SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol", - SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol", - SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol", - SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol", - SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol", - SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol", - SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol", - SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol", - SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol", - SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol", - SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol", - SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol", - SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol", - SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol", - SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol", - SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol", - SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol", - SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol", - SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol", - } - - -func load(data_bytes): - objects = SCRIPT_proto.AllObjects.new() - - var result_code = objects.from_bytes(data_bytes) - if result_code != SCRIPT_proto.PB_ERR.NO_ERRORS: - print("Could not load generated data: %d" % result_code) - return - - for map in objects.get_maps(): - map_id_by_name[map.get_name()] = map.get_id() - - for door in objects.get_doors(): - var map = objects.get_maps()[door.get_map_id()] - - if not map.get_name() in door_id_by_map_node_path: - door_id_by_map_node_path[map.get_name()] = {} - - var map_data = door_id_by_map_node_path[map.get_name()] - for receiver in door.get_receivers(): - map_data[receiver] = door.get_id() - - for painting_id in door.get_move_paintings(): - var painting = objects.get_paintings()[painting_id] - map_data[painting.get_path()] = door.get_id() - - if door.has_ap_id(): - door_id_by_ap_id[door.get_ap_id()] = door.get_id() - - for painting in objects.get_paintings(): - var room = objects.get_rooms()[painting.get_room_id()] - var map = objects.get_maps()[room.get_map_id()] - - if not map.get_name() in painting_id_by_map_node_path: - painting_id_by_map_node_path[map.get_name()] = {} - - var _map_data = painting_id_by_map_node_path[map.get_name()] - - for port in objects.get_ports(): - var room = objects.get_rooms()[port.get_room_id()] - var map = objects.get_maps()[room.get_map_id()] - - if not map.get_name() in port_id_by_map_node_path: - port_id_by_map_node_path[map.get_name()] = {} - - var map_data = port_id_by_map_node_path[map.get_name()] - map_data[port.get_path()] = port.get_id() - - for progressive in objects.get_progressives(): - progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id() - - for letter in objects.get_letters(): - letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() - - for panel in objects.get_panels(): - var room = objects.get_rooms()[panel.get_room_id()] - var map = objects.get_maps()[room.get_map_id()] - - if not map.get_name() in panel_id_by_map_node_path: - panel_id_by_map_node_path[map.get_name()] = {} - - var map_data = panel_id_by_map_node_path[map.get_name()] - map_data[panel.get_path()] = panel.get_id() - - for symbol_name in kSYMBOL_ITEMS.values(): - symbol_item_ids.append(objects.get_special_ids()[symbol_name]) - - for special_name in objects.get_special_ids().keys(): - if special_name.begins_with("Anti "): - anti_trap_ids[objects.get_special_ids()[special_name]] = ( - special_name.substr(5).to_lower() - ) - - -func get_door_for_map_node_path(map_name, node_path): - if not door_id_by_map_node_path.has(map_name): - return null - - var map_data = door_id_by_map_node_path[map_name] - return map_data.get(node_path, null) - - -func get_panel_for_map_node_path(map_name, node_path): - if not panel_id_by_map_node_path.has(map_name): - return null - - var map_data = panel_id_by_map_node_path[map_name] - return map_data.get(node_path, null) - - -func get_port_for_map_node_path(map_name, node_path): - if not port_id_by_map_node_path.has(map_name): - return null - - var map_data = port_id_by_map_node_path[map_name] - return map_data.get(node_path, null) - - -func get_door_ap_id(door_id): - var door = objects.get_doors()[door_id] - if door.has_ap_id(): - return door.get_ap_id() - else: - return null - - -func get_door_receivers(door_id): - var door = objects.get_doors()[door_id] - return door.get_receivers() - - -func get_door_map_name(door_id): - var door = objects.get_doors()[door_id] - var map = objects.get_maps()[door.get_map_id()] - return map.get_name() diff --git a/client/Archipelago/keyHolder.gd b/client/Archipelago/keyHolder.gd deleted file mode 100644 index 3c037ff..0000000 --- a/client/Archipelago/keyHolder.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends "res://scripts/nodes/keyHolder.gd" - - -func setFromAp(key, level): - if level > 0: - has_key = true - is_complete = "%s%d" % [key, level] - held_key = key - held_level = level - get_node("Hinge/Letter").mesh.text = held_key - get_node("Hinge/Letter2").mesh.text = held_key - setMaterial() - emit_signal("trigger") - else: - has_key = false - held_key = "" - held_level = 0 - setMaterial() - get_node("Hinge/Letter").mesh.text = "-" - get_node("Hinge/Letter2").mesh.text = "-" - is_complete = "" - emit_signal("untrigger") - - -func addKey(key): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - var ap = global.get_node("Archipelago") - ap.keyboard.put_in_keyholder(key, global.map, node_path) - - -func removeKey(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - var ap = global.get_node("Archipelago") - ap.keyboard.remove_from_keyholder(held_key, global.map, node_path) diff --git a/client/Archipelago/keyHolderChecker.gd b/client/Archipelago/keyHolderChecker.gd deleted file mode 100644 index a75a9e4..0000000 --- a/client/Archipelago/keyHolderChecker.gd +++ /dev/null @@ -1,24 +0,0 @@ -extends "res://scripts/nodes/listeners/keyHolderChecker.gd" - - -func check(): - var ap = global.get_node("Archipelago") - var matches = [] - for map in ap.keyboard.keyholder_state.keys(): - var nodes = ap.keyboard.keyholder_state[map] - for node in nodes.keys(): - matches.append([nodes[node], 1, map, "/root/scene/%s" % node]) - - var count = 0 - for key_match in matches: - var active = ( - key_match[2] + String(key_match[3]).replace("/root/scene/Components/KeyHolders/", ".") - ) - if map[active] == key_match[0]: - emit_signal("trigger_letter", key_match[0], true) - count += 1 - else: - emit_signal("trigger_letter", key_match[0], false) - - if count > 25: - emit_signal("trigger") diff --git a/client/Archipelago/keyHolderResetterListener.gd b/client/Archipelago/keyHolderResetterListener.gd deleted file mode 100644 index d5300f3..0000000 --- a/client/Archipelago/keyHolderResetterListener.gd +++ /dev/null @@ -1,8 +0,0 @@ -extends "res://scripts/nodes/listeners/keyHolderResetterListener.gd" - - -func reset(): - var ap = global.get_node("Archipelago") - var was_removed = ap.keyboard.reset_keyholders() - if was_removed: - sfxPlayer.sfx_play("pickup") diff --git a/client/Archipelago/keyboard.gd b/client/Archipelago/keyboard.gd deleted file mode 100644 index 450566d..0000000 --- a/client/Archipelago/keyboard.gd +++ /dev/null @@ -1,199 +0,0 @@ -extends Node - -const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz" - -var letters_saved = {} -var letters_in_keyholders = [] -var letters_blocked = [] -var letters_dynamic = {} -var keyholder_state = {} - -var filename = "" - - -func _init(): - reset() - - -func reset(): - letters_saved.clear() - letters_in_keyholders.clear() - letters_blocked.clear() - letters_dynamic.clear() - keyholder_state.clear() - - -func load_seed(): - var ap = global.get_node("Archipelago") - - reset() - - filename = "user://archipelago_keys/%s_%d" % [ap.client._seed, ap.client._slot] - - if FileAccess.file_exists(filename): - var ap_file = FileAccess.open(filename, FileAccess.READ) - var localdata = [] - if ap_file != null: - localdata = ap_file.get_var(true) - ap_file.close() - - if typeof(localdata) != TYPE_ARRAY: - print("AP keyboard file is corrupted") - localdata = [] - - if localdata.size() > 0: - letters_saved = localdata[0] - if localdata.size() > 1: - letters_in_keyholders = localdata[1] - if localdata.size() > 2: - keyholder_state = localdata[2] - - for k in kALL_LETTERS: - var level = 0 - - if ap.get_letter_behavior(k, false) == ap.kLETTER_BEHAVIOR_UNLOCKED: - level += 1 - if ap.get_letter_behavior(k, true) == ap.kLETTER_BEHAVIOR_UNLOCKED: - level += 1 - - letters_dynamic[k] = level - - update_unlocks() - - -func save(): - var dir = DirAccess.open("user://") - var folder = "archipelago_keys" - if not dir.dir_exists(folder): - dir.make_dir(folder) - - var file = FileAccess.open(filename, FileAccess.WRITE) - - var data = [ - letters_saved, - letters_in_keyholders, - keyholder_state, - ] - file.store_var(data, true) - file.close() - - -func update_unlocks(): - unlocks.resetKeys() - - var has_doubles = false - - for k in kALL_LETTERS: - var level = 0 - - if not letters_in_keyholders.has(k): - level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0) - - if level >= 2: - level = 2 - has_doubles = true - - if letters_blocked.has(k): - level = 0 - - unlocks.unlockKey(k, level) - - if has_doubles and unlocks.data["double_letters"] != "unlocked": - var ap = global.get_node("Archipelago") - if ap.cyan_door_behavior == ap.kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER: - unlocks.setData("double_letters", "unlocked") - - -func collect_local_letter(key, level): - if level < 0 or level > 2 or level < letters_saved.get(key, 0): - return - - letters_saved[key] = level - - if letters_blocked.has(key): - letters_blocked.erase(key) - - update_unlocks() - save() - - -func collect_remote_letter(key, level): - if level < 0 or level > 2 or level < letters_dynamic.get(key, 0): - return - - letters_dynamic[key] = level - - if letters_blocked.has(key): - letters_blocked.erase(key) - - update_unlocks() - save() - - -func put_in_keyholder(key, map, kh_path): - if not keyholder_state.has(map): - keyholder_state[map] = {} - - keyholder_state[map][kh_path] = key - letters_in_keyholders.append(key) - - get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp( - key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2) - ) - - update_unlocks() - save() - - -func remove_from_keyholder(key, map, kh_path): - if not keyholder_state.has(map): - # This... shouldn't happen. - keyholder_state[map] = {} - - keyholder_state[map].erase(kh_path) - letters_in_keyholders.erase(key) - - get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(key, 0) - - update_unlocks() - save() - - -func block_letter(key): - if not letters_blocked.has(key): - letters_blocked.append(key) - - update_unlocks() - - -func load_keyholders(map): - if keyholder_state.has(map): - var khs = keyholder_state[map] - - for path in khs.keys(): - var key = khs[path] - get_tree().get_root().get_node("scene").get_node(path).setFromAp( - key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2) - ) - - -func reset_keyholders(): - if letters_in_keyholders.is_empty() and letters_blocked.is_empty(): - return false - - var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty() - - if keyholder_state.has(global.map): - for path in keyholder_state[global.map]: - get_tree().get_root().get_node("scene").get_node(path).setFromAp( - keyholder_state[global.map][path], 0 - ) - - keyholder_state.clear() - letters_in_keyholders.clear() - letters_blocked.clear() - - update_unlocks() - save() - - return cleared_anything diff --git a/client/Archipelago/locationListener.gd b/client/Archipelago/locationListener.gd deleted file mode 100644 index 71792ed..0000000 --- a/client/Archipelago/locationListener.gd +++ /dev/null @@ -1,20 +0,0 @@ -extends Receiver - -var location_id - - -func _ready(): - super._ready() - - -func handleTriggered(): - triggered += 1 - if triggered >= total: - var ap = global.get_node("Archipelago") - ap.send_location(location_id) - - -func handleUntriggered(): - triggered -= 1 - if triggered < total: - pass diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd deleted file mode 100644 index b170c77..0000000 --- a/client/Archipelago/manager.gd +++ /dev/null @@ -1,571 +0,0 @@ -extends Node - -const MOD_VERSION = 7 - -var SCRIPT_client -var SCRIPT_keyboard -var SCRIPT_locationListener -var SCRIPT_minimap -var SCRIPT_uuid -var SCRIPT_victoryListener - -var ap_server = "" -var ap_user = "" -var ap_pass = "" -var connection_history = [] -var show_compass = false - -var client -var keyboard - -var _localdata_file = "" -var _last_new_item = -1 -var _batch_locations = false -var _held_locations = [] -var _held_location_scouts = [] -var _location_scouts = {} -var _item_locks = {} -var _inverse_item_locks = {} -var _held_letters = {} -var _letters_setup = false - -const kSHUFFLE_LETTERS_VANILLA = 0 -const kSHUFFLE_LETTERS_UNLOCKED = 1 -const kSHUFFLE_LETTERS_PROGRESSIVE = 2 -const kSHUFFLE_LETTERS_VANILLA_CYAN = 3 -const kSHUFFLE_LETTERS_ITEM_CYAN = 4 - -const kLETTER_BEHAVIOR_VANILLA = 0 -const kLETTER_BEHAVIOR_ITEM = 1 -const kLETTER_BEHAVIOR_UNLOCKED = 2 - -const kCYAN_DOOR_BEHAVIOR_H2 = 0 -const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1 -const kCYAN_DOOR_BEHAVIOR_ITEM = 2 - -var apworld_version = [0, 0] -var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 -var daedalus_roof_access = false -var keyholder_sanity = false -var port_pairings = {} -var shuffle_control_center_colors = false -var shuffle_doors = false -var shuffle_gallery_paintings = false -var shuffle_letters = kSHUFFLE_LETTERS_VANILLA -var shuffle_symbols = false -var shuffle_worldports = false -var strict_cyan_ending = false -var strict_purple_ending = false -var victory_condition = -1 - -signal could_not_connect -signal connect_status -signal ap_connected - - -func _init(): - # Read AP settings from file, if there are any - if FileAccess.file_exists("user://ap_settings"): - var file = FileAccess.open("user://ap_settings", FileAccess.READ) - var data = file.get_var(true) - file.close() - - if typeof(data) != TYPE_ARRAY: - global._print("AP settings file is corrupted") - data = [] - - if data.size() > 0: - ap_server = data[0] - - if data.size() > 1: - ap_user = data[1] - - if data.size() > 2: - ap_pass = data[2] - - if data.size() > 3: - connection_history = data[3] - - if data.size() > 4: - show_compass = data[4] - - -func _ready(): - client = SCRIPT_client.new() - client.SCRIPT_uuid = SCRIPT_uuid - - client.connect("item_received", _process_item) - client.connect("message_received", _process_message) - client.connect("location_scout_received", _process_location_scout) - client.connect("could_not_connect", _client_could_not_connect) - client.connect("connect_status", _client_connect_status) - client.connect("client_connected", _client_connected) - - add_child(client) - - keyboard = SCRIPT_keyboard.new() - add_child(keyboard) - - -func saveSettings(): - # Save the AP settings to disk. - var path = "user://ap_settings" - var file = FileAccess.open(path, FileAccess.WRITE) - - var data = [ - ap_server, - ap_user, - ap_pass, - connection_history, - show_compass, - ] - file.store_var(data, true) - file.close() - - -func saveLocaldata(): - # Save the MW/slot specific settings to disk. - var dir = DirAccess.open("user://") - var folder = "archipelago_data" - if not dir.dir_exists(folder): - dir.make_dir(folder) - - var file = FileAccess.open(_localdata_file, FileAccess.WRITE) - - var data = [ - _last_new_item, - ] - file.store_var(data, true) - file.close() - - -func connectToServer(): - _last_new_item = -1 - _batch_locations = false - _held_locations = [] - _held_location_scouts = [] - _location_scouts = {} - _letters_setup = false - _held_letters = {} - - client.connectToServer(ap_server, ap_user, ap_pass) - - -func getSaveFileName(): - return "zzAP_%s_%d" % [client._seed, client._slot] - - -func disconnect_from_ap(): - client.disconnect_from_ap() - - -func get_item_id_for_door(door_id): - return _item_locks.get(door_id, null) - - -func _process_item(item, index, from, flags, amount): - var item_name = "Unknown" - if client._item_id_to_name["Lingo 2"].has(item): - item_name = client._item_id_to_name["Lingo 2"][item] - - var gamedata = global.get_node("Gamedata") - - var prog_id = null - if _inverse_item_locks.has(item): - for lock in _inverse_item_locks.get(item): - if lock[1] != amount: - continue - - if gamedata.progressive_id_by_ap_id.has(item): - prog_id = lock[0] - - if gamedata.get_door_map_name(lock[0]) != global.map: - continue - - var receivers = gamedata.get_door_receivers(lock[0]) - var scene = get_tree().get_root().get_node_or_null("scene") - if scene != null: - for receiver in receivers: - var rnode = scene.get_node_or_null(receiver) - if rnode != null: - rnode.handleTriggered() - - var letter_id = gamedata.letter_id_by_ap_id.get(item, null) - if letter_id != null: - var letter = gamedata.objects.get_letters()[letter_id] - if not letter.has_level2() or not letter.get_level2(): - _process_key_item(letter.get_key(), amount) - - if gamedata.symbol_item_ids.has(item): - var player = get_tree().get_root().get_node_or_null("scene/player") - if player != null: - player.emit_signal("evaluate_solvability") - - # Show a message about the item if it's new. - if index != null and index > _last_new_item: - _last_new_item = index - saveLocaldata() - - var player_name = "Unknown" - if client._player_name_by_slot.has(float(from)): - player_name = client._player_name_by_slot[float(from)] - - var full_item_name = item_name - if prog_id != null: - var door = gamedata.objects.get_doors()[prog_id] - full_item_name = "%s (%s)" % [item_name, door.get_name()] - - var message - if from == client._slot: - message = "Found %s" % wrapInItemColorTags(full_item_name, flags) - else: - message = ( - "Received %s from %s" % [wrapInItemColorTags(full_item_name, flags), player_name] - ) - - if gamedata.anti_trap_ids.has(item): - keyboard.block_letter(gamedata.anti_trap_ids[item]) - - global._print(message) - - global.get_node("Messages").showMessage(message) - - -func _process_message(message): - parse_printjson_for_textclient(message) - - if ( - !message.has("receiving") - or !message.has("item") - or message["item"]["player"] != client._slot - ): - return - - var item_name = "Unknown" - var item_player_game = client._game_by_player[message["receiving"]] - if client._item_id_to_name[item_player_game].has(int(message["item"]["item"])): - item_name = client._item_id_to_name[item_player_game][int(message["item"]["item"])] - - var location_name = "Unknown" - var location_player_game = client._game_by_player[message["item"]["player"]] - if client._location_id_to_name[location_player_game].has(int(message["item"]["location"])): - location_name = (client._location_id_to_name[location_player_game][int( - message["item"]["location"] - )]) - - var player_name = "Unknown" - if client._player_name_by_slot.has(message["receiving"]): - player_name = client._player_name_by_slot[message["receiving"]] - - var item_color = colorForItemType(message["item"]["flags"]) - - if message["type"] == "Hint": - var is_for = "" - if message["receiving"] != client._slot: - is_for = " for %s" % player_name - if !message.has("found") || !message["found"]: - global.get_node("Messages").showMessage( - ( - "Hint: %s%s is on %s" - % [ - wrapInItemColorTags(item_name, message["item"]["flags"]), - is_for, - location_name - ] - ) - ) - else: - if message["receiving"] != client._slot: - var sentMsg = ( - "Sent %s to %s" - % [wrapInItemColorTags(item_name, message["item"]["flags"]), player_name] - ) - #if _hinted_locations.has(message["item"]["location"]): - # sentMsg += " ([color=#fafad2]Hinted![/color])" - global.get_node("Messages").showMessage(sentMsg) - - -func parse_printjson_for_textclient(message): - var parts = [] - for message_part in message["data"]: - if !message_part.has("type") and message_part.has("text"): - parts.append(message_part["text"]) - elif message_part["type"] == "player_id": - if int(message_part["text"]) == client._slot: - parts.append( - "[color=#ee00ee]%s[/color]" % client._player_name_by_slot[client._slot] - ) - else: - var from = float(message_part["text"]) - parts.append("[color=#fafad2]%s[/color]" % client._player_name_by_slot[from]) - elif message_part["type"] == "item_id": - var item_name = "Unknown" - var item_player_game = client._game_by_player[message_part["player"]] - if client._item_id_to_name[item_player_game].has(int(message_part["text"])): - item_name = client._item_id_to_name[item_player_game][int(message_part["text"])] - - parts.append(wrapInItemColorTags(item_name, message_part["flags"])) - elif message_part["type"] == "location_id": - var location_name = "Unknown" - var location_player_game = client._game_by_player[message_part["player"]] - if client._location_id_to_name[location_player_game].has(int(message_part["text"])): - location_name = client._location_id_to_name[location_player_game][int( - message_part["text"] - )] - - parts.append("[color=#00ff7f]%s[/color]" % location_name) - elif message_part.has("text"): - parts.append(message_part["text"]) - - var textclient_node = global.get_node("Textclient") - if textclient_node != null: - textclient_node.parse_printjson("".join(parts)) - - -func _process_location_scout(item_id, location_id, player, flags): - _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags} - - if player == client._slot and flags & 4 != 0: - # This is a trap for us, so let's not display it. - return - - var gamedata = global.get_node("Gamedata") - var map_id = gamedata.map_id_by_name.get(global.map) - - var item_name = "Unknown" - var item_player_game = client._game_by_player[float(player)] - if client._item_id_to_name[item_player_game].has(item_id): - item_name = client._item_id_to_name[item_player_game][item_id] - - var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null) - if letter_id != null: - var letter = gamedata.objects.get_letters()[letter_id] - var room = gamedata.objects.get_rooms()[letter.get_room_id()] - if room.get_map_id() == map_id: - var collectable = get_tree().get_root().get_node("scene").get_node_or_null( - letter.get_path() - ) - if collectable != null: - collectable.setScoutedText(item_name) - - -func _client_could_not_connect(message): - emit_signal("could_not_connect", message) - - -func _client_connect_status(message): - emit_signal("connect_status", message) - - -func _client_connected(slot_data): - var gamedata = global.get_node("Gamedata") - - _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot] - _last_new_item = -1 - - if FileAccess.file_exists(_localdata_file): - var ap_file = FileAccess.open(_localdata_file, FileAccess.READ) - var localdata = [] - if ap_file != null: - localdata = ap_file.get_var(true) - ap_file.close() - - if typeof(localdata) != TYPE_ARRAY: - print("AP localdata file is corrupted") - localdata = [] - - if localdata.size() > 0: - _last_new_item = localdata[0] - - # Read slot data. - cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0)) - daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) - keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) - shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) - shuffle_doors = bool(slot_data.get("shuffle_doors", false)) - shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false)) - shuffle_letters = int(slot_data.get("shuffle_letters", 0)) - shuffle_symbols = bool(slot_data.get("shuffle_symbols", false)) - shuffle_worldports = bool(slot_data.get("shuffle_worldports", false)) - strict_cyan_ending = bool(slot_data.get("strict_cyan_ending", false)) - strict_purple_ending = bool(slot_data.get("strict_purple_ending", false)) - victory_condition = int(slot_data.get("victory_condition", 0)) - - if slot_data.has("version"): - apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])] - - port_pairings.clear() - if slot_data.has("port_pairings"): - var raw_pp = slot_data.get("port_pairings") - - for p1 in raw_pp.keys(): - port_pairings[int(p1)] = int(raw_pp[p1]) - - # Set up item locks. - _item_locks = {} - - if shuffle_doors: - for door in gamedata.objects.get_doors(): - if ( - door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD - or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY - ): - _item_locks[door.get_id()] = [door.get_ap_id(), 1] - - for progressive in gamedata.objects.get_progressives(): - for i in range(0, progressive.get_doors().size()): - var door = gamedata.objects.get_doors()[progressive.get_doors()[i]] - _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1] - - for door_group in gamedata.objects.get_door_groups(): - if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR: - if shuffle_worldports: - continue - elif door_group.get_type() != gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP: - continue - - for door in door_group.get_doors(): - _item_locks[door] = [door_group.get_ap_id(), 1] - - if shuffle_control_center_colors: - for door in gamedata.objects.get_doors(): - if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR: - _item_locks[door.get_id()] = [door.get_ap_id(), 1] - - for door_group in gamedata.objects.get_door_groups(): - if ( - door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR - and not shuffle_worldports - ): - for door in door_group.get_doors(): - _item_locks[door] = [door_group.get_ap_id(), 1] - - if shuffle_gallery_paintings: - for door in gamedata.objects.get_doors(): - if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING: - _item_locks[door.get_id()] = [door.get_ap_id(), 1] - - if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM: - for door_group in gamedata.objects.get_door_groups(): - if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS: - for door in door_group.get_doors(): - if not _item_locks.has(door): - _item_locks[door] = [door_group.get_ap_id(), 1] - - # Create a reverse item locks map for processing items. - _inverse_item_locks = {} - - for door_id in _item_locks.keys(): - var lock = _item_locks.get(door_id) - - if not _inverse_item_locks.has(lock[0]): - _inverse_item_locks[lock[0]] = [] - - _inverse_item_locks[lock[0]].append([door_id, lock[1]]) - - emit_signal("ap_connected") - - -func start_batching_locations(): - _batch_locations = true - - -func send_location(loc_id): - if _batch_locations: - _held_locations.append(loc_id) - else: - client.sendLocation(loc_id) - - -func scout_location(loc_id): - if _location_scouts.has(loc_id): - return _location_scouts.get(loc_id) - - if _batch_locations: - _held_location_scouts.append(loc_id) - else: - client.scoutLocation(loc_id) - - return null - - -func stop_batching_locations(): - _batch_locations = false - - if not _held_locations.is_empty(): - client.sendLocations(_held_locations) - _held_locations.clear() - - if not _held_location_scouts.is_empty(): - client.scoutLocations(_held_location_scouts) - _held_location_scouts.clear() - - -func colorForItemType(flags): - var int_flags = int(flags) - if int_flags & 1: # progression - if int_flags & 2: # proguseful - return "#f0d200" - else: - return "#bc51e0" - elif int_flags & 2: # useful - return "#2b67ff" - elif int_flags & 4: # trap - return "#d63a22" - else: # filler - return "#14de9e" - - -func wrapInItemColorTags(text, flags): - var int_flags = int(flags) - if int_flags & 1 and int_flags & 2: # proguseful - return "[rainbow]%s[/rainbow]" % text - else: - return "[color=%s]%s[/color]" % [colorForItemType(flags), text] - - -func get_letter_behavior(key, level2): - if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED: - return kLETTER_BEHAVIOR_UNLOCKED - - if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters): - if level2: - if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN: - return kLETTER_BEHAVIOR_VANILLA - else: - return kLETTER_BEHAVIOR_ITEM - else: - return kLETTER_BEHAVIOR_UNLOCKED - - if not level2 and ["h", "i", "n", "t"].has(key): - # This differs from the equivalent function in the apworld. Logically it is - # the same as UNLOCKED since they are in the starting room, but VANILLA - # means the player still has to actually pick up the letters. - return kLETTER_BEHAVIOR_VANILLA - - if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE: - return kLETTER_BEHAVIOR_ITEM - - return kLETTER_BEHAVIOR_VANILLA - - -func setup_keys(): - keyboard.load_seed() - - _letters_setup = true - - for k in _held_letters.keys(): - _process_key_item(k, _held_letters[k]) - - _held_letters.clear() - - -func _process_key_item(key, level): - if not _letters_setup: - _held_letters[key] = max(_held_letters.get(key, 0), level) - return - - if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN: - level += 1 - - keyboard.collect_remote_letter(key, level) diff --git a/client/Archipelago/messages.gd b/client/Archipelago/messages.gd deleted file mode 100644 index ab4f071..0000000 --- a/client/Archipelago/messages.gd +++ /dev/null @@ -1,74 +0,0 @@ -extends CanvasLayer - -var SCRIPT_rainbowText - -var _message_queue = [] -var _font -var _container -var _ordered_labels = [] - - -func _ready(): - _container = VBoxContainer.new() - _container.set_name("Container") - _container.anchor_bottom = 1 - _container.offset_left = 20.0 - _container.offset_right = 1920.0 - _container.offset_top = 0.0 - _container.offset_bottom = -20.0 - _container.alignment = BoxContainer.ALIGNMENT_END - _container.mouse_filter = Control.MOUSE_FILTER_IGNORE - self.add_child(_container) - - _font = load("res://assets/fonts/Lingo2.ttf") - - -func _add_message(text): - var new_label = RichTextLabel.new() - new_label.install_effect(SCRIPT_rainbowText.new()) - new_label.push_font(_font) - new_label.push_font_size(36) - new_label.push_outline_color(Color(0, 0, 0, 1)) - new_label.push_outline_size(2) - new_label.append_text(text) - new_label.fit_content = true - - _container.add_child(new_label) - _ordered_labels.push_back(new_label) - - -func showMessage(text): - if _ordered_labels.size() >= 9: - _message_queue.append(text) - return - - _add_message(text) - - if _ordered_labels.size() > 1: - return - - var timeout = 10.0 - while !_ordered_labels.is_empty(): - await get_tree().create_timer(timeout).timeout - - if !_ordered_labels.is_empty(): - var to_remove = _ordered_labels.pop_front() - var to_tween = get_tree().create_tween().bind_node(to_remove) - to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5) - to_tween.tween_callback(to_remove.queue_free) - - if !_message_queue.is_empty(): - var next_msg = _message_queue.pop_front() - _add_message(next_msg) - - if timeout > 4: - timeout -= 3 - - -func clear(): - _message_queue.clear() - - for message_label in _ordered_labels: - message_label.queue_free() - - _ordered_labels.clear() diff --git a/client/Archipelago/minimap.gd b/client/Archipelago/minimap.gd deleted file mode 100644 index 5640716..0000000 --- a/client/Archipelago/minimap.gd +++ /dev/null @@ -1,175 +0,0 @@ -extends CanvasLayer - -var player -var drawer -var sprite -var label - -var cell_left -var cell_top -var cell_right -var cell_bottom -var cell_width -var cell_height -var center_x_min -var center_x_max -var center_y_min -var center_y_max - - -func _ready(): - player = get_tree().get_root().get_node("scene/player") - - var svc = PanelContainer.new() - svc.anchor_left = 1.0 - svc.anchor_top = 1.0 - svc.anchor_right = 1.0 - svc.anchor_bottom = 1.0 - svc.offset_left = -320.0 - svc.offset_top = -320.0 - svc.offset_right = -64.0 - svc.offset_bottom = -64.0 - svc.clip_contents = true - add_child(svc) - - var background_color = Color.WHITE - - var world_env = get_tree().get_root().get_node("scene/WorldEnvironment") - if world_env != null and world_env.environment != null: - if world_env.environment.background_mode == Environment.BG_COLOR: - background_color = world_env.environment.background_color - elif ( - world_env.environment.background_mode == Environment.BG_SKY - and world_env.environment.sky != null - and world_env.environment.sky.sky_material != null - ): - var sky = world_env.environment.sky.sky_material - if sky is PhysicalSkyMaterial: - background_color = sky.ground_color - elif sky is ProceduralSkyMaterial: - background_color = sky.sky_top_color - - var stylebox = StyleBoxFlat.new() - stylebox.bg_color = Color(background_color, 0.6) - svc.add_theme_stylebox_override("panel", stylebox) - - drawer = Node2D.new() - svc.add_child(drawer) - - var gridmap = get_tree().get_root().get_node("scene/GridMap") - if gridmap == null: - visible = false - return - - cell_left = 0 - cell_top = 0 - cell_right = 0 - cell_bottom = 0 - - for pos in gridmap.get_used_cells(): - if pos.x < cell_left: - cell_left = pos.x - if pos.x > cell_right: - cell_right = pos.x - if pos.z < cell_top: - cell_top = pos.z - if pos.z > cell_bottom: - cell_bottom = pos.z - - cell_width = cell_right - cell_left + 1 - cell_height = cell_bottom - cell_top + 1 - - var rendered = _renderMap(gridmap) - - var image_texture = ImageTexture.create_from_image(rendered) - sprite = Sprite2D.new() - sprite.texture = image_texture - sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - sprite.scale = Vector2(2, 2) - sprite.centered = false - drawer.add_child(sprite) - - label = Label.new() - label.theme = preload("res://assets/themes/baseUI.tres") - label.add_theme_font_size_override("font_size", 32) - label.text = "@" - drawer.add_child(label) - - #var local_tl = gridmap.map_to_local(Vector3i(cell_left, 0, cell_top)) - #var global_tl = gridmap.to_global(local_tl) - #var local_br = gridmap.map_to_local(Vector3i(cell_right, 0, cell_bottom)) - #var global_br = gridmap.to_global(local_br) - - center_x_min = 0 - center_x_max = cell_width - 128 - center_y_min = 0 - center_y_max = cell_height - 128 - - if center_x_max < center_x_min: - center_x_min = (center_x_min + center_x_max) / 2 - center_x_max = center_x_min - - if center_y_max < center_y_min: - center_y_min = (center_y_min + center_y_max) / 2 - center_y_max = center_y_min - - -func _process(_delta): - if visible == false: - return - - drawer.position.x = clamp(player.position.x - cell_left - 64, center_x_min, center_x_max) * -2 - drawer.position.y = clamp(player.position.z - cell_top - 64, center_y_min, center_y_max) * -2 - - label.position.x = (player.position.x - cell_left) * 2 - 16 - label.position.y = (player.position.z - cell_top) * 2 - 16 - - -func _renderMap(gridmap): - var heights = {} - - var rendered = Image.create_empty(cell_width, cell_height, false, Image.FORMAT_RGBA8) - rendered.fill(Color.TRANSPARENT) - - var meshes_node = get_tree().get_root().get_node("scene/Meshes") - if meshes_node != null: - _renderMeshNode(gridmap, meshes_node, rendered) - - for pos in gridmap.get_used_cells(): - var in_plane = Vector2i(pos.x, pos.z) - - if in_plane in heights and heights[in_plane] > pos.y: - continue - - heights[in_plane] = pos.y - - var cell_item = gridmap.get_cell_item(pos) - var mesh = gridmap.mesh_library.get_item_mesh(cell_item) - var material = mesh.surface_get_material(0) - var color = material.albedo_color - - rendered.set_pixel(pos.x - cell_left, pos.z - cell_top, color) - - return rendered - - -func _renderMeshNode(gridmap, mesh, rendered): - if mesh is MeshInstance3D: - var local_tl = gridmap.map_to_local(Vector3i(cell_left, 0, cell_top)) - var global_tl = gridmap.to_global(local_tl) - var mesh_material = mesh.get_surface_override_material(0) - if mesh_material != null: - var mesh_color = mesh_material.albedo_color - - for y in range( - max(mesh.position.z - mesh.scale.z / 2 - global_tl.z, 0), - min(mesh.position.z + mesh.scale.z / 2 - global_tl.z, cell_height) - ): - for x in range( - max(mesh.position.x - mesh.scale.x / 2 - global_tl.x, 0), - min(mesh.position.x + mesh.scale.x / 2 - global_tl.x, cell_width) - ): - rendered.set_pixel(x, y, mesh_color) - - for child in mesh.get_children(): - _renderMeshNode(gridmap, child, rendered) diff --git a/client/Archipelago/painting.gd b/client/Archipelago/painting.gd deleted file mode 100644 index 276d4eb..0000000 --- a/client/Archipelago/painting.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends "res://scripts/nodes/painting.gd" - -var item_id -var item_amount - - -func _ready(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - var gamedata = global.get_node("Gamedata") - var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) - if door_id != null: - var ap = global.get_node("Archipelago") - var item_lock = ap.get_item_id_for_door(door_id) - - if item_lock != null: - item_id = item_lock[0] - item_amount = item_lock[1] - - self.senders = [] - self.senderGroup = [] - self.nested = false - self.complete_at = 0 - self.max_length = 0 - self.excludeSenders = [] - - call_deferred("_readier") - - super._ready() - - -func _readier(): - var ap = global.get_node("Archipelago") - - if ap.client.getItemAmount(item_id) >= item_amount: - handleTriggered() diff --git a/client/Archipelago/panel.gd b/client/Archipelago/panel.gd deleted file mode 100644 index fdaaf0e..0000000 --- a/client/Archipelago/panel.gd +++ /dev/null @@ -1,101 +0,0 @@ -extends "res://scripts/nodes/panel.gd" - -var panel_logic = null -var symbol_solvable = true - -var black = load("res://assets/materials/black.material") - - -func _ready(): - super._ready() - - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - var gamedata = global.get_node("Gamedata") - var panel_id = gamedata.get_panel_for_map_node_path(global.map, node_path) - if panel_id != null: - var ap = global.get_node("Archipelago") - if ap.shuffle_symbols: - if global.map == "the_entry" and node_path == "Panels/Entry/front_1": - clue = "i" - symbol = "" - - setField("clue", clue) - setField("symbol", symbol) - - panel_logic = gamedata.objects.get_panels()[panel_id] - checkSymbolSolvable() - - if not symbol_solvable: - get_tree().get_root().get_node("scene/player").connect( - "evaluate_solvability", evaluateSolvability - ) - - -func checkSymbolSolvable(): - var old_solvable = symbol_solvable - symbol_solvable = true - - if panel_logic == null: - # There's no logic for this panel. - return - - var ap = global.get_node("Archipelago") - if not ap.shuffle_symbols: - # Symbols aren't item-locked. - return - - var gamedata = global.get_node("Gamedata") - for symbol in panel_logic.get_symbols(): - var item_name = gamedata.kSYMBOL_ITEMS.get(symbol) - var item_id = gamedata.objects.get_special_ids()[item_name] - if ap.client.getItemAmount(item_id) < 1: - symbol_solvable = false - break - - if symbol_solvable != old_solvable: - if symbol_solvable: - setField("clue", clue) - setField("symbol", symbol) - setField("answer", answer) - else: - quad_mesh.surface_set_material(0, black) - get_node("Hinge/clue").text = "missing" - get_node("Hinge/answer").text = "symbols" - - -func checkSolvable(key): - checkSymbolSolvable() - if not symbol_solvable: - return false - - return super.checkSolvable(key) - - -func evaluateSolvability(): - checkSolvable("") - - -func passedInput(key, skip_focus_check = false): - if not symbol_solvable: - return - - super.passedInput(key, skip_focus_check) - - -func focus(): - if not symbol_solvable: - has_focus = false - return - - super.focus() - - -func unfocus(): - if not symbol_solvable: - has_focus = false - return - - super.unfocus() diff --git a/client/Archipelago/pauseMenu.gd b/client/Archipelago/pauseMenu.gd deleted file mode 100644 index cd1813c..0000000 --- a/client/Archipelago/pauseMenu.gd +++ /dev/null @@ -1,44 +0,0 @@ -extends "res://scripts/ui/pauseMenu.gd" - -var compass_button - - -func _ready(): - var ap_panel = Panel.new() - ap_panel.name = "Archipelago" - get_node("menu/settings/settingsInner/TabContainer").add_child(ap_panel) - - var ap = global.get_node("Archipelago") - - compass_button = CheckBox.new() - compass_button.text = "show compass" - compass_button.button_pressed = ap.show_compass - compass_button.position = Vector2(65, 100) - compass_button.theme = preload("res://assets/themes/baseUI.tres") - compass_button.add_theme_font_size_override("font_size", 60) - compass_button.pressed.connect(_toggle_compass) - ap_panel.add_child(compass_button) - - super._ready() - - -func _pause_game(): - global.get_node("Textclient").dismiss() - super._pause_game() - - -func _main_menu(): - global.loaded = false - global.get_node("Archipelago").disconnect_from_ap() - global.get_node("Messages").clear() - global.get_node("Compass").visible = false - super._main_menu() - - -func _toggle_compass(): - var ap = global.get_node("Archipelago") - ap.show_compass = compass_button.button_pressed - ap.saveSettings() - - var compass = global.get_node("Compass") - compass.visible = compass_button.button_pressed diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd deleted file mode 100644 index e58f1bc..0000000 --- a/client/Archipelago/player.gd +++ /dev/null @@ -1,369 +0,0 @@ -extends "res://scripts/nodes/player.gd" - -const kEndingNameByVictoryValue = { - 0: "GRAY", - 1: "PURPLE", - 2: "MINT", - 3: "BLACK", - 4: "BLUE", - 5: "CYAN", - 6: "RED", - 7: "PLUM", - 8: "ORANGE", - 9: "GOLD", - 10: "YELLOW", - 11: "GREEN", - 12: "WHITE", -} - -signal evaluate_solvability - -var compass - - -func _ready(): - var khl_script = load("res://scripts/nodes/keyHolderListener.gd") - - var pause_menu = get_node("pause_menu") - pause_menu.layer = 3 - - var ap = global.get_node("Archipelago") - var gamedata = global.get_node("Gamedata") - - compass = global.get_node("Compass") - compass.visible = ap.show_compass - - ap.start_batching_locations() - - # Set up door locations. - var map_id = gamedata.map_id_by_name.get(global.map) - for door in gamedata.objects.get_doors(): - if door.get_map_id() != map_id: - continue - - if not door.has_ap_id(): - continue - - if ( - door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY - or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING - ): - continue - - var locationListener = ap.SCRIPT_locationListener.new() - locationListener.location_id = door.get_ap_id() - locationListener.name = "locationListener_%d" % door.get_ap_id() - - for panel_ref in door.get_panels(): - var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()] - var panel_path = panel_data.get_path() - - if panel_ref.has_answer(): - for proxy in panel_data.get_proxies(): - if proxy.get_answer() == panel_ref.get_answer(): - panel_path = proxy.get_path() - break - - locationListener.senders.append(NodePath("/root/scene/" + panel_path)) - - for keyholder_ref in door.get_keyholders(): - var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()] - - var khl = khl_script.new() - khl.name = ( - "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()] - ) - khl.answer = keyholder_ref.get_key() - khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path())) - get_parent().add_child.call_deferred(khl) - - locationListener.senders.append(NodePath("../" + khl.name)) - - for sender in door.get_senders(): - locationListener.senders.append(NodePath("/root/scene/" + sender)) - - if door.has_complete_at(): - locationListener.complete_at = door.get_complete_at() - - get_parent().add_child.call_deferred(locationListener) - - # Set up letter locations. - for letter in gamedata.objects.get_letters(): - var room = gamedata.objects.get_rooms()[letter.get_room_id()] - if room.get_map_id() != map_id: - continue - - var locationListener = ap.SCRIPT_locationListener.new() - locationListener.location_id = letter.get_ap_id() - locationListener.name = "locationListener_%d" % letter.get_ap_id() - locationListener.senders.append(NodePath("/root/scene/" + letter.get_path())) - - get_parent().add_child.call_deferred(locationListener) - - if ( - ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2()) - != ap.kLETTER_BEHAVIOR_VANILLA - ): - var scout = ap.scout_location(letter.get_ap_id()) - if ( - scout != null - and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0) - ): - var item_name = "Unknown" - var item_player_game = ap.client._game_by_player[float(scout["player"])] - if ap.client._item_id_to_name[item_player_game].has(scout["item"]): - item_name = ap.client._item_id_to_name[item_player_game][scout["item"]] - - var collectable = get_tree().get_root().get_node("scene").get_node_or_null( - letter.get_path() - ) - if collectable != null: - collectable.setScoutedText.call_deferred(item_name) - - # Set up mastery locations. - for mastery in gamedata.objects.get_masteries(): - var room = gamedata.objects.get_rooms()[mastery.get_room_id()] - if room.get_map_id() != map_id: - continue - - var locationListener = ap.SCRIPT_locationListener.new() - locationListener.location_id = mastery.get_ap_id() - locationListener.name = "locationListener_%d" % mastery.get_ap_id() - locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path())) - - get_parent().add_child.call_deferred(locationListener) - - # Set up ending locations. - for ending in gamedata.objects.get_endings(): - var room = gamedata.objects.get_rooms()[ending.get_room_id()] - if room.get_map_id() != map_id: - continue - - var locationListener = ap.SCRIPT_locationListener.new() - locationListener.location_id = ending.get_ap_id() - locationListener.name = "locationListener_%d" % ending.get_ap_id() - locationListener.senders.append(NodePath("/root/scene/" + ending.get_path())) - - get_parent().add_child.call_deferred(locationListener) - - if kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name(): - var victoryListener = ap.SCRIPT_victoryListener.new() - victoryListener.name = "victoryListener" - victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path())) - - get_parent().add_child.call_deferred(victoryListener) - - # Set up keyholder locations, in keyholder sanity. - if ap.keyholder_sanity: - for keyholder in gamedata.objects.get_keyholders(): - if not keyholder.has_key(): - continue - - var room = gamedata.objects.get_rooms()[keyholder.get_room_id()] - if room.get_map_id() != map_id: - continue - - var locationListener = ap.SCRIPT_locationListener.new() - locationListener.location_id = keyholder.get_ap_id() - locationListener.name = "locationListener_%d" % keyholder.get_ap_id() - - var khl = khl_script.new() - khl.name = "location_%d_keyholder" % keyholder.get_ap_id() - khl.answer = keyholder.get_key() - khl.senders.append(NodePath("/root/scene/" + keyholder.get_path())) - get_parent().add_child.call_deferred(khl) - - locationListener.senders.append(NodePath("../" + khl.name)) - - get_parent().add_child.call_deferred(locationListener) - - # Block off roof access in Daedalus. - if global.map == "daedalus" and not ap.daedalus_roof_access: - _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49) - _set_up_invis_wall(51.5, 11, -17, 16, 10, 1) - _set_up_invis_wall(46, 10, -9.5, 1, 10, 10) - _set_up_invis_wall(67.5, 11, 17, 16, 10, 1) - _set_up_invis_wall(50.5, 11, 14, 10, 10, 1) - _set_up_invis_wall(39, 10, 18.5, 1, 10, 22) - _set_up_invis_wall(20, 15, 18.5, 1, 10, 16) - _set_up_invis_wall(11.5, 15, 3, 32, 10, 1) - _set_up_invis_wall(11.5, 16, -20, 14, 20, 1) - _set_up_invis_wall(14, 16, -26.5, 1, 20, 4) - _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25) - _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1) - _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1) - _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11) - _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11) - - var warp_exit_prefab = preload("res://objects/nodes/exit.tscn") - var warp_exit = warp_exit_prefab.instantiate() - warp_exit.name = "roof_access_blocker_warp_exit" - warp_exit.position = Vector3(58, 10, 0) - warp_exit.rotation_degrees.y = 90 - get_parent().add_child.call_deferred(warp_exit) - - var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn") - var warp_enter = warp_enter_prefab.instantiate() - warp_enter.target = warp_exit - warp_enter.position = Vector3(76.5, 30, 1) - warp_enter.scale = Vector3(4, 1.5, 1) - warp_enter.rotation_degrees.y = 90 - get_parent().add_child.call_deferred(warp_enter) - - if global.map == "the_entry": - # Remove door behind X1. - var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1") - door_node.handleTriggered() - - # Display win condition. - var sign_prefab = preload("res://objects/nodes/sign.tscn") - var sign1 = sign_prefab.instantiate() - sign1.position = Vector3(-7, 5, -15.01) - sign1.text = "victory" - get_parent().add_child.call_deferred(sign1) - - var sign2 = sign_prefab.instantiate() - sign2.position = Vector3(-7, 4, -15.01) - sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?") - - var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower() - if sign2_color == "white": - sign2_color = "silver" - - sign2.material = load("res://assets/materials/%s.material" % sign2_color) - get_parent().add_child.call_deferred(sign2) - - # Add the strict purple ending validation. - if global.map == "the_sun_temple" and ap.strict_purple_ending: - var panel_prefab = preload("res://objects/nodes/panel.tscn") - var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") - var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn") - - var previous_panel = null - var next_y = -100 - var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] - for word in words: - var panel = panel_prefab.instantiate() - panel.position = Vector3(0, next_y, 0) - next_y -= 10 - panel.clue = word - panel.symbol = "" - panel.answer = word - panel.name = "EndCheck_%s" % word - - var tpl = tpl_prefab.instantiate() - tpl.teleport_point = Vector3(0, 1, 0) - tpl.teleport_rotate = Vector3(-45, 180, 0) - tpl.target_path = panel - tpl.name = "Teleport" - - if previous_panel == null: - tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24")) - else: - tpl.senders.append(NodePath("../../%s" % previous_panel.name)) - - var reversing = reverse_prefab.instantiate() - reversing.senders.append(NodePath("..")) - reversing.name = "Reversing" - tpl.senders.append(NodePath("../Reversing")) - - panel.add_child.call_deferred(tpl) - panel.add_child.call_deferred(reversing) - get_parent().get_node("Panels").add_child.call_deferred(panel) - - previous_panel = panel - - # Duplicate the doors that usually wait on EQUINOX. We can't set the senders - # here for some reason so we actually set them in the door ready function. - var endplat = get_node("/root/scene/Components/Doors/EndPlatform") - var endplat2 = endplat.duplicate() - endplat2.name = "spe_EndPlatform" - endplat.get_parent().add_child.call_deferred(endplat2) - endplat.queue_free() - - var entry2 = get_node("/root/scene/Components/Doors/entry_2") - var entry22 = entry2.duplicate() - entry22.name = "spe_entry_2" - entry2.get_parent().add_child.call_deferred(entry22) - entry2.queue_free() - - # Add the strict cyan ending validation. - if global.map == "the_parthenon" and ap.strict_cyan_ending: - var panel_prefab = preload("res://objects/nodes/panel.tscn") - var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") - var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn") - - var previous_panel = null - var next_y = -100 - var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] - for word in words: - var panel = panel_prefab.instantiate() - panel.position = Vector3(0, next_y, 0) - next_y -= 10 - panel.clue = word - panel.symbol = "." - panel.answer = "%s%s" % [word, word] - panel.name = "EndCheck_%s" % word - - var tpl = tpl_prefab.instantiate() - tpl.teleport_point = Vector3(0, 1, -11) - tpl.teleport_rotate = Vector3(-45, 0, 0) - tpl.target_path = panel - tpl.name = "Teleport" - - if previous_panel == null: - tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers")) - else: - tpl.senders.append(NodePath("../../%s" % previous_panel.name)) - - var reversing = reverse_prefab.instantiate() - reversing.senders.append(NodePath("..")) - reversing.name = "Reversing" - tpl.senders.append(NodePath("../Reversing")) - - panel.add_child.call_deferred(tpl) - panel.add_child.call_deferred(reversing) - get_parent().get_node("Panels").add_child.call_deferred(panel) - - previous_panel = panel - - # Duplicate the door that usually waits on the rulers. We can't set the - # senders here for some reason so we actually set them in the door ready - # function. - var entry1 = get_node("/root/scene/Components/Doors/entry_1") - var entry12 = entry1.duplicate() - entry12.name = "spe_entry_1" - entry1.get_parent().add_child.call_deferred(entry12) - entry1.queue_free() - - var minimap = ap.SCRIPT_minimap.new() - minimap.name = "Minimap" - get_parent().add_child.call_deferred(minimap) - - super._ready() - - await get_tree().process_frame - await get_tree().process_frame - - ap.stop_batching_locations() - - -func _set_up_invis_wall(x, y, z, sx, sy, sz): - var prefab = preload("res://objects/nodes/block.tscn") - var newwall = prefab.instantiate() - newwall.position.x = x - newwall.position.y = y - newwall.position.z = z - newwall.scale.x = sz - newwall.scale.y = sy - newwall.scale.z = sx - newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material")) - newwall.visibility_range_end = 3 - newwall.visibility_range_end_margin = 1 - newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF - newwall.skeleton = ".." - get_parent().add_child.call_deferred(newwall) - - -func _process(_dt): - compass.update_rotation(global_rotation.y) diff --git a/client/Archipelago/rainbowText.gd b/client/Archipelago/rainbowText.gd deleted file mode 100644 index 9a4c1d0..0000000 --- a/client/Archipelago/rainbowText.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends RichTextEffect - -var bbcode = "rainbow" - - -func _process_custom_fx(char_fx: CharFXTransform): - char_fx.color = Color.from_hsv( - char_fx.elapsed_time - floor(char_fx.elapsed_time), 1.0, 1.0, 1.0 - ) - return true diff --git a/client/Archipelago/saver.gd b/client/Archipelago/saver.gd deleted file mode 100644 index 44bc179..0000000 --- a/client/Archipelago/saver.gd +++ /dev/null @@ -1,23 +0,0 @@ -extends "res://scripts/nodes/saver.gd" - - -func levelLoaded(): - if type == "keyholders": - var ap = global.get_node("Archipelago") - ap.keyboard.load_keyholders.call_deferred(global.map) - else: - reload.call_deferred() - - -func reload(): - # Just rewriting this whole thing so I can remove Chris's safeguard. - var file = FileAccess.open(path + type + ".save", FileAccess.READ) - if file: - var data = file.get_var(true) - file.close() - for datum in data: - var saveable = get_node_or_null(datum[0]) - if saveable != null: - saveable.is_complete = datum[1] - if saveable.is_complete: - saveable.loadData(saveable.is_complete) diff --git a/client/Archipelago/settings_buttons.gd b/client/Archipelago/settings_buttons.gd deleted file mode 100644 index 9e61cb0..0000000 --- a/client/Archipelago/settings_buttons.gd +++ /dev/null @@ -1,24 +0,0 @@ -extends Button - - -func _ready(): - pass - - -func _connect_pressed(): - self.disabled = true - - var ap = global.get_node("Archipelago") - ap.ap_server = self.get_parent().get_node("server_box").text - ap.ap_user = self.get_parent().get_node("player_box").text - ap.ap_pass = self.get_parent().get_node("password_box").text - ap.saveSettings() - - ap.connectToServer() - - -func _back_pressed(): - var ap = global.get_node("Archipelago") - ap.disconnect_from_ap() - - get_tree().change_scene_to_file("res://objects/scenes/menus/main_menu.tscn") diff --git a/client/Archipelago/settings_screen.gd b/client/Archipelago/settings_screen.gd deleted file mode 100644 index d5aa747..0000000 --- a/client/Archipelago/settings_screen.gd +++ /dev/null @@ -1,261 +0,0 @@ -extends Node2D - - -func _ready(): - # Some helpful logging. - if Steam.isSubscribed(): - global._print("Provisioning successful! Build ID: %d" % Steam.getAppBuildId()) - else: - global._print("Provisioning failed.") - - # Undo the load screen removing our cursor - get_tree().get_root().set_disable_input(false) - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - - # Increase the WebSocket input buffer size so that we can download large - # data packages. - ProjectSettings.set_setting("network/limits/websocket_client/max_in_buffer_kb", 8192) - - switcher.layer = 4 - - # Create the global AP manager, if it doesn't already exist. - if not global.has_node("Archipelago"): - var ap_script = ResourceLoader.load("user://maps/Archipelago/manager.gd") - var ap_instance = ap_script.new() - ap_instance.name = "Archipelago" - - ap_instance.SCRIPT_client = load("user://maps/Archipelago/client.gd") - ap_instance.SCRIPT_keyboard = load("user://maps/Archipelago/keyboard.gd") - ap_instance.SCRIPT_locationListener = load("user://maps/Archipelago/locationListener.gd") - ap_instance.SCRIPT_minimap = load("user://maps/Archipelago/minimap.gd") - ap_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd") - ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd") - - global.add_child(ap_instance) - - # Let's also inject any scripts we need to inject now. - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/collectable.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/door.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolder.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolderChecker.gd")) - installScriptExtension( - ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd") - ) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/panel.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleport.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldport.gd")) - installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd")) - - var proto_script = load("user://maps/Archipelago/generated/proto.gd") - var gamedata_script = load("user://maps/Archipelago/gamedata.gd") - var gamedata_instance = gamedata_script.new(proto_script) - gamedata_instance.load( - FileAccess.get_file_as_bytes("user://maps/Archipelago/generated/data.binpb") - ) - gamedata_instance.name = "Gamedata" - global.add_child(gamedata_instance) - - var messages_script = load("user://maps/Archipelago/messages.gd") - var messages_instance = messages_script.new() - messages_instance.name = "Messages" - messages_instance.SCRIPT_rainbowText = load("user://maps/Archipelago/rainbowText.gd") - global.add_child(messages_instance) - - var textclient_script = load("user://maps/Archipelago/textclient.gd") - var textclient_instance = textclient_script.new() - textclient_instance.name = "Textclient" - global.add_child(textclient_instance) - - var compass_overlay_script = load("user://maps/Archipelago/compass_overlay.gd") - var compass_overlay_instance = compass_overlay_script.new() - compass_overlay_instance.name = "Compass" - compass_overlay_instance.SCRIPT_compass = load("user://maps/Archipelago/compass.gd") - global.add_child(compass_overlay_instance) - - var ap = global.get_node("Archipelago") - var gamedata = global.get_node("Gamedata") - ap.connect("ap_connected", connectionSuccessful) - ap.connect("could_not_connect", connectionUnsuccessful) - ap.connect("connect_status", connectionStatus) - - # Populate textboxes with AP settings. - $Panel/server_box.text = ap.ap_server - $Panel/player_box.text = ap.ap_user - $Panel/password_box.text = ap.ap_pass - - var history_box = $Panel/connection_history - if ap.connection_history.is_empty(): - history_box.disabled = true - else: - history_box.disabled = false - - var i = 0 - for details in ap.connection_history: - history_box.get_popup().add_item("%s (%s)" % [details[1], details[0]], i) - i += 1 - - history_box.get_popup().connect("id_pressed", historySelected) - - # Show client version. - $Panel/title.text = "ARCHIPELAGO (%d.%d)" % [gamedata.objects.get_version(), ap.MOD_VERSION] - - # Increase font size in text boxes. - $Panel/server_box.add_theme_font_size_override("font_size", 36) - $Panel/player_box.add_theme_font_size_override("font_size", 36) - $Panel/password_box.add_theme_font_size_override("font_size", 36) - - # Set up version mismatch dialog. - $Panel/VersionMismatch.connect("confirmed", startGame) - $Panel/VersionMismatch.get_cancel_button().pressed.connect(versionMismatchDeclined) - - -# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd -func installScriptExtension(childScript: Resource): - # Force Godot to compile the script now. - # We need to do this here to ensure that the inheritance chain is - # properly set up, and multiple mods can chain-extend the same - # class multiple times. - # This is also needed to make Godot instantiate the extended class - # when creating singletons. - # The actual instance is thrown away. - childScript.new() - - var parentScript = childScript.get_base_script() - var parentScriptPath = parentScript.resource_path - global._print("ModLoader: Installing script extension over %s" % parentScriptPath) - childScript.take_over_path(parentScriptPath) - - -func connectionStatus(message): - var popup = self.get_node("Panel/AcceptDialog") - popup.title = "Connecting to Archipelago" - popup.dialog_text = message - popup.exclusive = true - popup.get_ok_button().visible = false - popup.popup_centered() - - -func connectionSuccessful(): - var ap = global.get_node("Archipelago") - var gamedata = global.get_node("Gamedata") - - # Check for major version mismatch. - if ap.apworld_version[0] != gamedata.objects.get_version(): - $Panel/AcceptDialog.exclusive = false - - var popup = self.get_node("Panel/VersionMismatch") - popup.title = "Version Mismatch!" - popup.dialog_text = ( - "This slot was generated using v%d.%d of the Lingo 2 apworld,\nwhich has a different major version than this client (v%d.%d).\nIt is highly recommended to play using the correct version of the client.\nYou may experience bugs or logic issues if you continue." - % [ - ap.apworld_version[0], - ap.apworld_version[1], - gamedata.objects.get_version(), - ap.MOD_VERSION - ] - ) - popup.exclusive = true - popup.popup_centered() - - return - - startGame() - - -func startGame(): - var ap = global.get_node("Archipelago") - - # Save connection details - var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass] - if ap.connection_history.has(connection_details): - ap.connection_history.erase(connection_details) - ap.connection_history.push_front(connection_details) - if ap.connection_history.size() > 10: - ap.connection_history.resize(10) - ap.saveSettings() - - # Switch to the_entry - Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) - global.user = ap.getSaveFileName() - global.universe = "lingo" - global.map = "the_entry" - - unlocks.resetCollectables() - unlocks.resetData() - - ap.setup_keys() - - unlocks.loadCollectables() - unlocks.loadData() - unlocks.unlockKey("capslock", 1) - - if ap.shuffle_worldports: - settings.worldport_fades = "default" - else: - settings.worldport_fades = "never" - - clearResourceCache("res://objects/meshes/gridDoor.tscn") - clearResourceCache("res://objects/nodes/collectable.tscn") - clearResourceCache("res://objects/nodes/door.tscn") - clearResourceCache("res://objects/nodes/keyHolder.tscn") - clearResourceCache("res://objects/nodes/listeners/animationListener.tscn") - clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn") - clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") - clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") - clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn") - clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") - clearResourceCache("res://objects/nodes/panel.tscn") - clearResourceCache("res://objects/nodes/player.tscn") - clearResourceCache("res://objects/nodes/saver.tscn") - clearResourceCache("res://objects/nodes/teleport.tscn") - clearResourceCache("res://objects/nodes/worldport.tscn") - clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") - - var paintings_dir = DirAccess.open("res://objects/meshes/paintings") - if paintings_dir: - paintings_dir.list_dir_begin() - var file_name = paintings_dir.get_next() - while file_name != "": - if not paintings_dir.current_is_dir() and file_name.ends_with(".tscn"): - clearResourceCache("res://objects/meshes/paintings/" + file_name) - file_name = paintings_dir.get_next() - - switcher.switch_map.call_deferred("res://objects/scenes/the_entry.tscn") - - -func connectionUnsuccessful(error_message): - $Panel/connect_button.disabled = false - - var popup = $Panel/AcceptDialog - popup.title = "Could not connect to Archipelago" - popup.dialog_text = error_message - popup.exclusive = true - popup.get_ok_button().visible = true - popup.popup_centered() - - $Panel/connect_button.disabled = false - - -func versionMismatchDeclined(): - $Panel/AcceptDialog.hide() - $Panel/connect_button.disabled = false - - -func historySelected(index): - var ap = global.get_node("Archipelago") - var details = ap.connection_history[index] - - $Panel/server_box.text = details[0] - $Panel/player_box.text = details[1] - $Panel/password_box.text = details[2] - - -func clearResourceCache(path): - ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE) diff --git a/client/Archipelago/teleport.gd b/client/Archipelago/teleport.gd deleted file mode 100644 index 428d50b..0000000 --- a/client/Archipelago/teleport.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends "res://scripts/nodes/teleport.gd" - -var item_id -var item_amount - - -func _ready(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - var gamedata = global.get_node("Gamedata") - var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) - if door_id != null: - var ap = global.get_node("Archipelago") - var item_lock = ap.get_item_id_for_door(door_id) - - if item_lock != null: - item_id = item_lock[0] - item_amount = item_lock[1] - - self.senders = [] - self.senderGroup = [] - self.nested = false - self.complete_at = 0 - self.max_length = 0 - self.excludeSenders = [] - - call_deferred("_readier") - - super._ready() - - -func _readier(): - var ap = global.get_node("Archipelago") - - if ap.client.getItemAmount(item_id) >= item_amount: - handleTriggered() diff --git a/client/Archipelago/teleportListener.gd b/client/Archipelago/teleportListener.gd deleted file mode 100644 index 6f363af..0000000 --- a/client/Archipelago/teleportListener.gd +++ /dev/null @@ -1,49 +0,0 @@ -extends "res://scripts/nodes/listeners/teleportListener.gd" - -var item_id -var item_amount - - -func _ready(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - if ( - global.map == "daedalus" - and ( - node_path == "Components/Triggers/teleportListenerConnections" - or node_path == "Components/Triggers/teleportListenerConnections2" - ) - ): - # Effectively disable these. - teleport_point = target_path.position - return - - var gamedata = global.get_node("Gamedata") - var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) - if door_id != null: - var ap = global.get_node("Archipelago") - var item_lock = ap.get_item_id_for_door(door_id) - - if item_lock != null: - item_id = item_lock[0] - item_amount = item_lock[1] - - self.senders = [] - self.senderGroup = [] - self.nested = false - self.complete_at = 0 - self.max_length = 0 - self.excludeSenders = [] - - call_deferred("_readier") - - super._ready() - - -func _readier(): - var ap = global.get_node("Archipelago") - - if ap.client.getItemAmount(item_id) >= item_amount: - handleTriggered() diff --git a/client/Archipelago/textclient.gd b/client/Archipelago/textclient.gd deleted file mode 100644 index 26831b4..0000000 --- a/client/Archipelago/textclient.gd +++ /dev/null @@ -1,95 +0,0 @@ -extends CanvasLayer - -var panel -var label -var entry -var is_open = false - - -func _ready(): - process_mode = ProcessMode.PROCESS_MODE_ALWAYS - layer = 2 - - panel = Panel.new() - panel.set_name("Panel") - panel.offset_left = 100 - panel.offset_right = 1820 - panel.offset_top = 100 - panel.offset_bottom = 980 - panel.visible = false - add_child(panel) - - label = RichTextLabel.new() - label.set_name("Label") - label.offset_left = 80 - label.offset_right = 1640 - label.offset_top = 80 - label.offset_bottom = 720 - label.scroll_following = true - label.selection_enabled = true - panel.add_child(label) - - label.push_font(load("res://assets/fonts/Lingo2.ttf")) - label.push_font_size(36) - - var entry_style = StyleBoxFlat.new() - entry_style.bg_color = Color(0.9, 0.9, 0.9, 1) - - entry = LineEdit.new() - entry.set_name("Entry") - entry.offset_left = 80 - entry.offset_right = 1640 - entry.offset_top = 760 - entry.offset_bottom = 840 - entry.add_theme_font_override("font", load("res://assets/fonts/Lingo2.ttf")) - entry.add_theme_font_size_override("font_size", 36) - entry.add_theme_color_override("font_color", Color(0, 0, 0, 1)) - entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1)) - entry.add_theme_stylebox_override("focus", entry_style) - panel.add_child(entry) - entry.connect("text_submitted", text_entered) - - -func _input(event): - if global.loaded and event is InputEventKey and event.pressed: - if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT): - if !get_tree().paused: - is_open = true - get_tree().paused = true - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - panel.visible = true - entry.grab_focus() - get_viewport().set_input_as_handled() - else: - dismiss() - elif event.keycode == KEY_ESCAPE: - if is_open: - dismiss() - get_viewport().set_input_as_handled() - - -func dismiss(): - if is_open: - get_tree().paused = false - Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) - panel.visible = false - is_open = false - - -func parse_printjson(text): - label.append_text("[p]" + text + "[/p]") - - -func text_entered(text): - var ap = global.get_node("Archipelago") - var cmd = text.trim_suffix("\n") - entry.text = "" - if OS.is_debug_build(): - if cmd.begins_with("/tp_map "): - var new_map = cmd.substr(8) - global.map = new_map - global.sets_entry_point = false - switcher.switch_map("res://objects/scenes/%s.tscn" % new_map) - return - - ap.client.say(cmd) diff --git a/client/Archipelago/vendor/LICENSE b/client/Archipelago/vendor/LICENSE deleted file mode 100644 index 115ba15..0000000 --- a/client/Archipelago/vendor/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Xavier Sellier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/client/Archipelago/vendor/uuid.gd b/client/Archipelago/vendor/uuid.gd deleted file mode 100644 index b63fa04..0000000 --- a/client/Archipelago/vendor/uuid.gd +++ /dev/null @@ -1,195 +0,0 @@ -# Note: The code might not be as pretty it could be, since it's written -# in a way that maximizes performance. Methods are inlined and loops are avoided. -extends Node - -const BYTE_MASK: int = 0b11111111 - - -static func uuidbin(): - randomize() - # 16 random bytes with the bytes on index 6 and 8 modified - return [ - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - ((randi() & BYTE_MASK) & 0x0f) | 0x40, - randi() & BYTE_MASK, - ((randi() & BYTE_MASK) & 0x3f) | 0x80, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - ] - - -static func uuidbinrng(rng: RandomNumberGenerator): - rng.randomize() - return [ - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, - rng.randi() & BYTE_MASK, - ((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - ] - - -static func v4(): - # 16 random bytes with the bytes on index 6 and 8 modified - var b = uuidbin() - - return ( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" - % [ - # low - b[0], - b[1], - b[2], - b[3], - # mid - b[4], - b[5], - # hi - b[6], - b[7], - # clock - b[8], - b[9], - # clock - b[10], - b[11], - b[12], - b[13], - b[14], - b[15] - ] - ) - - -static func v4_rng(rng: RandomNumberGenerator): - # 16 random bytes with the bytes on index 6 and 8 modified - var b = uuidbinrng(rng) - - return ( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" - % [ - # low - b[0], - b[1], - b[2], - b[3], - # mid - b[4], - b[5], - # hi - b[6], - b[7], - # clock - b[8], - b[9], - # clock - b[10], - b[11], - b[12], - b[13], - b[14], - b[15] - ] - ) - - -var _uuid: Array - - -func _init(rng := RandomNumberGenerator.new()) -> void: - _uuid = uuidbinrng(rng) - - -func as_array() -> Array: - return _uuid.duplicate() - - -func as_dict(big_endian := true) -> Dictionary: - if big_endian: - return { - "low": (_uuid[0] << 24) + (_uuid[1] << 16) + (_uuid[2] << 8) + _uuid[3], - "mid": (_uuid[4] << 8) + _uuid[5], - "hi": (_uuid[6] << 8) + _uuid[7], - "clock": (_uuid[8] << 8) + _uuid[9], - "node": - ( - (_uuid[10] << 40) - + (_uuid[11] << 32) - + (_uuid[12] << 24) - + (_uuid[13] << 16) - + (_uuid[14] << 8) - + _uuid[15] - ) - } - else: - return { - "low": _uuid[0] + (_uuid[1] << 8) + (_uuid[2] << 16) + (_uuid[3] << 24), - "mid": _uuid[4] + (_uuid[5] << 8), - "hi": _uuid[6] + (_uuid[7] << 8), - "clock": _uuid[8] + (_uuid[9] << 8), - "node": - ( - _uuid[10] - + (_uuid[11] << 8) - + (_uuid[12] << 16) - + (_uuid[13] << 24) - + (_uuid[14] << 32) - + (_uuid[15] << 40) - ) - } - - -func as_string() -> String: - return ( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" - % [ - # low - _uuid[0], - _uuid[1], - _uuid[2], - _uuid[3], - # mid - _uuid[4], - _uuid[5], - # hi - _uuid[6], - _uuid[7], - # clock - _uuid[8], - _uuid[9], - # node - _uuid[10], - _uuid[11], - _uuid[12], - _uuid[13], - _uuid[14], - _uuid[15] - ] - ) - - -func is_equal(other) -> bool: - # Godot Engine compares Array recursively - # There's no need for custom comparison here. - return _uuid == other._uuid diff --git a/client/Archipelago/victoryListener.gd b/client/Archipelago/victoryListener.gd deleted file mode 100644 index e9089d7..0000000 --- a/client/Archipelago/victoryListener.gd +++ /dev/null @@ -1,20 +0,0 @@ -extends Receiver - - -func _ready(): - super._ready() - - -func handleTriggered(): - triggered += 1 - if triggered >= total: - var ap = global.get_node("Archipelago") - ap.client.completedGoal() - - global.get_node("Messages").showMessage("You have completed your goal!") - - -func handleUntriggered(): - triggered -= 1 - if triggered < total: - pass diff --git a/client/Archipelago/visibilityListener.gd b/client/Archipelago/visibilityListener.gd deleted file mode 100644 index 5ea17a0..0000000 --- a/client/Archipelago/visibilityListener.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends "res://scripts/nodes/listeners/visibilityListener.gd" - -var item_id -var item_amount - - -func _ready(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - var gamedata = global.get_node("Gamedata") - var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) - if door_id != null: - var ap = global.get_node("Archipelago") - var item_lock = ap.get_item_id_for_door(door_id) - - if item_lock != null: - item_id = item_lock[0] - item_amount = item_lock[1] - - self.senders = [] - self.senderGroup = [] - self.nested = false - self.complete_at = 0 - self.max_length = 0 - self.excludeSenders = [] - - call_deferred("_readier") - - super._ready() - - -func _readier(): - var ap = global.get_node("Archipelago") - - if ap.client.getItemAmount(item_id) >= item_amount: - handleTriggered() diff --git a/client/Archipelago/worldport.gd b/client/Archipelago/worldport.gd deleted file mode 100644 index cdca248..0000000 --- a/client/Archipelago/worldport.gd +++ /dev/null @@ -1,53 +0,0 @@ -extends "res://scripts/nodes/worldport.gd" - -var absolute_rotation = false -var target_rotation = 0 - - -func _ready(): - var node_path = String( - get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() - ) - - var ap = global.get_node("Archipelago") - - if ap.shuffle_worldports: - var gamedata = global.get_node("Gamedata") - var port_id = gamedata.get_port_for_map_node_path(global.map, node_path) - if port_id != null: - if port_id in ap.port_pairings: - var target_port = gamedata.objects.get_ports()[ap.port_pairings[port_id]] - var target_room = gamedata.objects.get_rooms()[target_port.get_room_id()] - var target_map = gamedata.objects.get_maps()[target_room.get_map_id()] - - exit = target_map.get_name() - entry_point.x = target_port.get_destination().get_x() - entry_point.y = target_port.get_destination().get_y() - entry_point.z = target_port.get_destination().get_z() - absolute_rotation = true - target_rotation = target_port.get_rotation() - sets_entry_point = true - invisible = false - fades = true - - if global.map == "icarus" and exit == "daedalus": - if not ap.daedalus_roof_access: - entry_point = Vector3(58, 10, 0) - - super._ready() - - -func bodyEntered(body): - if body.is_in_group("player"): - if absolute_rotation: - entry_rotate.y = target_rotation - body.rotation_degrees.y - - super.bodyEntered(body) - - -func changeScene(): - var player = get_tree().get_root().get_node("scene/player") - if player != null: - player.playable = false - - super.changeScene() diff --git a/client/Archipelago/worldportListener.gd b/client/Archipelago/worldportListener.gd deleted file mode 100644 index 5c2faff..0000000 --- a/client/Archipelago/worldportListener.gd +++ /dev/null @@ -1,8 +0,0 @@ -extends "res://scripts/nodes/listeners/worldportListener.gd" - - -func handleTriggered(): - if exit == "menus/credits": - return - - super.handleTriggered() -- cgit 1.4.1