about summary refs log tree commit diff stats
path: root/Archipelago
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2023-04-16 16:09:37 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2023-04-16 16:09:37 -0400
commit7aa62e5c0ac0d86e5aed2ead2a7116ea0edbffde (patch)
tree2e35e9166e01ead9e0bdddb1db9c3a18a2fa6266 /Archipelago
parent36eee0423e7f29e352c9c44d0ebb592007ec7436 (diff)
downloadlingo-archipelago-7aa62e5c0ac0d86e5aed2ead2a7116ea0edbffde.tar.gz
lingo-archipelago-7aa62e5c0ac0d86e5aed2ead2a7116ea0edbffde.tar.bz2
lingo-archipelago-7aa62e5c0ac0d86e5aed2ead2a7116ea0edbffde.zip
Implemented color shuffle
Diffstat (limited to 'Archipelago')
-rw-r--r--Archipelago/client.gd20
-rw-r--r--Archipelago/doorControl.gd3
-rw-r--r--Archipelago/load.gd12
-rw-r--r--Archipelago/painting_eye.gd3
-rw-r--r--Archipelago/panel.gd39
-rw-r--r--Archipelago/settings_screen.gd5
6 files changed, 78 insertions, 4 deletions
diff --git a/Archipelago/client.gd b/Archipelago/client.gd index 5b4d81e..8f20d0a 100644 --- a/Archipelago/client.gd +++ b/Archipelago/client.gd
@@ -6,6 +6,9 @@ var ap_pass = ""
6 6
7const ap_version = {"major": 0, "minor": 4, "build": 0, "class": "Version"} 7const ap_version = {"major": 0, "minor": 4, "build": 0, "class": "Version"}
8const orange_tower = ["Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh"] 8const orange_tower = ["Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh"]
9const color_items = [
10 "White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow"
11]
9 12
10var _client = WebSocketClient.new() 13var _client = WebSocketClient.new()
11var _last_state = WebSocketPeer.STATE_CLOSED 14var _last_state = WebSocketPeer.STATE_CLOSED
@@ -36,14 +39,18 @@ var _panel_ids_by_location = {}
36var _localdata_file = "" 39var _localdata_file = ""
37var _death_link = false 40var _death_link = false
38var _victory_condition = 0 # THE END, THE MASTER 41var _victory_condition = 0 # THE END, THE MASTER
42var _door_shuffle = false
43var _color_shuffle = false
39 44
40var _map_loaded = false 45var _map_loaded = false
41var _held_items = [] 46var _held_items = []
42var _held_locations = [] 47var _held_locations = []
43var _last_new_item = -1 48var _last_new_item = -1
44var _tower_floors = 0 49var _tower_floors = 0
50var _has_colors = ["white"]
45 51
46signal client_connected 52signal client_connected
53signal evaluate_solvability
47 54
48 55
49func _init(): 56func _init():
@@ -154,6 +161,10 @@ func _on_data():
154 161
155 if _slot_data.has("victory_condition"): 162 if _slot_data.has("victory_condition"):
156 _victory_condition = _slot_data["victory_condition"] 163 _victory_condition = _slot_data["victory_condition"]
164 if _slot_data.has("shuffle_colors"):
165 _color_shuffle = _slot_data["shuffle_colors"]
166 if _slot_data.has("shuffle_doors"):
167 _door_shuffle = (_slot_data["shuffle_doors"] > 0)
157 168
158 _localdata_file = "user://archipelago/%s_%d" % [_seed, _slot] 169 _localdata_file = "user://archipelago/%s_%d" % [_seed, _slot]
159 var ap_file = File.new() 170 var ap_file = File.new()
@@ -365,6 +376,9 @@ func completedGoal():
365 376
366func mapFinishedLoading(): 377func mapFinishedLoading():
367 if !_map_loaded: 378 if !_map_loaded:
379 _has_colors = ["white"]
380 emit_signal("evaluate_solvability")
381
368 for item in _held_items: 382 for item in _held_items:
369 processItem(item["item"], item["index"], item["from"]) 383 processItem(item["item"], item["index"], item["from"])
370 384
@@ -396,6 +410,12 @@ func processItem(item, index, from):
396 processItem(_item_name_to_id[subitem_name], null, null) 410 processItem(_item_name_to_id[subitem_name], null, null)
397 _tower_floors += 1 411 _tower_floors += 1
398 412
413 if _color_shuffle and color_items.has(_item_id_to_name[item]):
414 var lcol = _item_id_to_name[item].to_lower()
415 if not _has_colors.has(lcol):
416 _has_colors.append(lcol)
417 emit_signal("evaluate_solvability")
418
399 # Show a message about the item if it's new. 419 # Show a message about the item if it's new.
400 if index != null and index > _last_new_item: 420 if index != null and index > _last_new_item:
401 _last_new_item = index 421 _last_new_item = index
diff --git a/Archipelago/doorControl.gd b/Archipelago/doorControl.gd index 011b0e0..f64f9e2 100644 --- a/Archipelago/doorControl.gd +++ b/Archipelago/doorControl.gd
@@ -2,9 +2,8 @@ extends "res://scripts/doorControl.gd"
2 2
3 3
4func handle_correct(): 4func handle_correct():
5 # TODO: Right now we are just assuming that door shuffle is on.
6 var apclient = global.get_node("Archipelago") 5 var apclient = global.get_node("Archipelago")
7 if apclient.doorIsVanilla(self.get_parent().name + "/" + self.name): 6 if not apclient._door_shuffle or apclient.doorIsVanilla(self.get_parent().name + "/" + self.name):
8 .handle_correct() 7 .handle_correct()
9 8
10 9
diff --git a/Archipelago/load.gd b/Archipelago/load.gd index 7b7e648..f9dc65f 100644 --- a/Archipelago/load.gd +++ b/Archipelago/load.gd
@@ -38,6 +38,18 @@ func _load():
38 "answer_correct", location, "handle_correct" 38 "answer_correct", location, "handle_correct"
39 ) 39 )
40 40
41 # Attach a script to every panel so that we can do things like conditionally
42 # disable them.
43 var gamedata = apclient.get_node("Gamedata")
44 var panel_script = ResourceLoader.load("user://maps/Archipelago/panel.gd")
45 for panel in gamedata.panels:
46 var panel_node = panels_parent.get_node(panel["id"])
47 var script_instance = panel_script.new()
48 script_instance.name = "AP_Panel"
49 script_instance.data = panel
50 panel_node.add_child(script_instance)
51 apclient.connect("evaluate_solvability", script_instance, "evaluate_solvability")
52
41 # Hook up the goal panel. 53 # Hook up the goal panel.
42 if apclient._victory_condition == 1: 54 if apclient._victory_condition == 1:
43 var the_master = self.get_node("Panels/Countdown Panels/Panel_master_master") 55 var the_master = self.get_node("Panels/Countdown Panels/Panel_master_master")
diff --git a/Archipelago/painting_eye.gd b/Archipelago/painting_eye.gd index 86e0ce9..53d42b5 100644 --- a/Archipelago/painting_eye.gd +++ b/Archipelago/painting_eye.gd
@@ -2,9 +2,8 @@ extends "res://scripts/painting_eye.gd"
2 2
3 3
4func _answer_correct(): 4func _answer_correct():
5 # TODO: Right now we are just assuming that door shuffle is on.
6 var apclient = global.get_node("Archipelago") 5 var apclient = global.get_node("Archipelago")
7 if apclient.paintingIsVanilla(self.name): 6 if not apclient._door_shuffle or apclient.paintingIsVanilla(self.name):
8 ._answer_correct() 7 ._answer_correct()
9 8
10 9
diff --git a/Archipelago/panel.gd b/Archipelago/panel.gd new file mode 100644 index 0000000..6ec5e14 --- /dev/null +++ b/Archipelago/panel.gd
@@ -0,0 +1,39 @@
1extends Node
2
3var data = {}
4var orig_text = ""
5var orig_color = Color(0, 0, 0, 0)
6
7
8func _ready():
9 orig_text = self.get_parent().get_node("Viewport/GUI/Panel/Label").text
10 orig_color = self.get_parent().get_node("Quad").get_surface_material(0).albedo_color
11
12
13func evaluate_solvability():
14 var apclient = global.get_node("Archipelago")
15
16 var solvable = true
17 var missing = []
18
19 if apclient._color_shuffle:
20 for color in data["color"]:
21 if not apclient._has_colors.has(color):
22 missing.append(color)
23 solvable = false
24
25 if solvable:
26 self.get_parent().get_node("Viewport/GUI/Panel/Label").text = orig_text
27 self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = true
28 self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = orig_color
29 else:
30 var missing_text = "Missing: "
31 for thing in missing:
32 missing_text += thing + ",\n"
33 missing_text = missing_text.left(missing_text.length() - 2)
34
35 self.get_parent().get_node("Viewport/GUI/Panel/Label").text = missing_text
36 self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = false
37 self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = Color(
38 0.7, 0.2, 0.2
39 )
diff --git a/Archipelago/settings_screen.gd b/Archipelago/settings_screen.gd index 06bebd1..6700b9c 100644 --- a/Archipelago/settings_screen.gd +++ b/Archipelago/settings_screen.gd
@@ -13,6 +13,11 @@ func _ready():
13 apclient_instance.name = "Archipelago" 13 apclient_instance.name = "Archipelago"
14 global.add_child(apclient_instance) 14 global.add_child(apclient_instance)
15 15
16 var apdata = ResourceLoader.load("user://maps/Archipelago/gamedata.gd")
17 var apdata_instance = apdata.new()
18 apdata_instance.name = "Gamedata"
19 apclient_instance.add_child(apdata_instance)
20
16 # Let's also inject any scripts we need to inject now. 21 # Let's also inject any scripts we need to inject now.
17 installScriptExtension("user://maps/Archipelago/doorControl.gd") 22 installScriptExtension("user://maps/Archipelago/doorControl.gd")
18 installScriptExtension("user://maps/Archipelago/load.gd") 23 installScriptExtension("user://maps/Archipelago/load.gd")
class="s">"data"]["games"].keys(): _datapackages[game] = message["data"]["games"][game] saveSettings() if !_pending_packages.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"] _death_link = _slot_data.has("death_link") and _slot_data["death_link"] if _death_link: sendConnectUpdate(["DeathLink"]) if _slot_data.has("victory_condition"): _victory_condition = _slot_data["victory_condition"] if _slot_data.has("shuffle_colors"): _color_shuffle = _slot_data["shuffle_colors"] if _slot_data.has("shuffle_doors"): _door_shuffle = (_slot_data["shuffle_doors"] > 0) if _slot_data.has("shuffle_paintings"): _painting_shuffle = _slot_data["shuffle_paintings"] if _slot_data.has("shuffle_panels"): _panel_shuffle = _slot_data["shuffle_panels"] if _slot_data.has("seed"): _slot_seed = _slot_data["seed"] if _slot_data.has("painting_entrance_to_exit"): _paintings_mapping = _slot_data["painting_entrance_to_exit"] if _slot_data.has("mastery_achievements"): _mastery_achievements = _slot_data["mastery_achievements"] if _slot_data.has("level_2_requirement"): _level_2_requirement = _slot_data["level_2_requirement"] if _slot_data.has("location_checks"): if _slot_data["location_checks"] == kCLASSIFICATION_REMOTE_NORMAL: _location_classification_bit = kCLASSIFICATION_LOCAL_NORMAL elif _slot_data["location_checks"] == kCLASSIFICATION_REMOTE_REDUCED: _location_classification_bit = kCLASSIFICATION_LOCAL_REDUCED elif _slot_data["location_checks"] == kCLASSIFICATION_REMOTE_INSANITY: _location_classification_bit = kCLASSIFICATION_LOCAL_INSANITY if _slot_data.has("early_color_hallways"): _early_color_hallways = _slot_data["early_color_hallways"] _puzzle_skips = 0 _localdata_file = "user://archipelago_data/%s_%d" % [_seed, _slot] var ap_file = File.new() if ap_file.file_exists(_localdata_file): ap_file.open(_localdata_file, File.READ) var localdata = ap_file.get_var(true) ap_file.close() if localdata.size() > 0: _last_new_item = localdata[0] else: _last_new_item = -1 if localdata.size() > 1: _puzzle_skips = localdata[1] requestSync() emit_signal("client_connected") 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. Please report it to the lingo-archipelago GitHub." if submsg != "": if error_message != "": error_message += " " error_message += submsg if error_message == "": error_message = "Unknown error." _initiated_disconnect = true _client.disconnect_from_host() 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"]: if _map_loaded: processItem(item["item"], message["index"] + i, item["player"], item["flags"]) else: _held_items.append( { "item": item["item"], "index": message["index"] + i, "from": item["player"], "flags": item["flags"] } ) i += 1 elif cmd == "PrintJSON": if ( !message.has("receiving") or !message.has("item") or message["item"]["player"] != _slot ): continue var item_name = "Unknown" if _item_id_to_name.has(message["item"]["item"]): item_name = _item_id_to_name[message["item"]["item"]] var location_name = "Unknown" if _location_id_to_name.has(message["item"]["location"]): location_name = _location_id_to_name[message["item"]["location"]] var player_name = "Unknown" if _player_name_by_slot.has(message["receiving"]): player_name = _player_name_by_slot[message["receiving"]] var item_color = colorForItemType(message["item"]["flags"]) var messages_node = get_tree().get_root().get_node("Spatial/Messages") if message["type"] == "Hint": var is_for = "" if message["receiving"] != _slot: is_for = " for %s" % player_name if !message.has("found") || !message["found"]: messages_node.showMessage( ( "Hint: [color=%s]%s[/color]%s is on %s" % [item_color, item_name, is_for, location_name] ) ) else: if message["receiving"] != _slot: messages_node.showMessage( "Sent [color=%s]%s[/color] to %s" % [item_color, item_name, player_name] ) elif cmd == "Bounced": if ( _death_link and message.has("tags") and message.has("data") and message["tags"].has("DeathLink") ): var messages_node = get_tree().get_root().get_node("Spatial/Messages") var first_sentence = "Received Death" var second_sentence = "" if message["data"].has("source"): first_sentence = "Received Death from %s" % message["data"]["source"] if message["data"].has("cause") and message["data"]["cause"] != "": second_sentence = ". Reason: %s" % message["data"]["cause"] messages_node.showMessage(first_sentence + second_sentence) # Return the player home. get_tree().get_root().get_node("Spatial/player/pause_menu")._reload() func _process(_delta): if _should_process: _client.poll() func saveSettings(): # Save the AP settings to disk. var dir = Directory.new() var path = "user://settings" if dir.dir_exists(path): pass else: dir.make_dir(path) var file = File.new() file.open("user://settings/archipelago", File.WRITE) var data = [ap_server, ap_user, ap_pass, _datapackages] file.store_var(data, true) file.close() func saveLocaldata(): # Save the MW/slot specific settings to disk. var dir = Directory.new() var path = "user://archipelago_data" if dir.dir_exists(path): pass else: dir.make_dir(path) var file = File.new() file.open(_localdata_file, File.WRITE) var data = [_last_new_item, _puzzle_skips] file.store_var(data, true) file.close() func getSaveFileName(): return "zzAP_%s_%d" % [_seed, _slot] func connectToServer(): _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 = _client.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: " + err) return _should_process = true emit_signal("connect_status", "Connecting...") func sendMessage(msg): var payload = JSON.print(msg) _client.get_peer(1).set_write_mode(WebSocketPeer.WRITE_MODE_TEXT) _client.get_peer(1).put_packet(payload.to_utf8()) 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 package in _datapackages.values(): for name in package["item_name_to_id"].keys(): _item_id_to_name[package["item_name_to_id"][name]] = name for name in package["location_name_to_id"].keys(): _location_id_to_name[package["location_name_to_id"][name]] = name if _datapackages.has("Lingo"): _item_name_to_id = _datapackages["Lingo"]["item_name_to_id"] _location_name_to_id = _datapackages["Lingo"]["location_name_to_id"] func connectToRoom(): emit_signal("connect_status", "Authenticating...") sendMessage( [ { "cmd": "Connect", "password": ap_pass, "game": "Lingo", "name": ap_user, "uuid": uuid_util.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): if _map_loaded: sendMessage([{"cmd": "LocationChecks", "locations": [loc_id]}]) else: _held_locations.append(loc_id) func setValue(key, value): sendMessage( [ { "cmd": "Set", "key": "Lingo_%d_%s" % [_slot, key], "operations": [{"operation": "replace", "value": value}] } ] ) func completedGoal(): sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL var messages_node = get_tree().get_root().get_node("Spatial/Messages") messages_node.showMessage("You have completed your goal!") func mapFinishedLoading(): if !_map_loaded: _received_indexes.clear() _progressive_progress.clear() _has_colors = ["white"] emit_signal("evaluate_solvability") for item in _held_items: processItem(item["item"], item["index"], item["from"], item["flags"]) sendMessage([{"cmd": "LocationChecks", "locations": _held_locations}]) _map_loaded = true _held_items = [] _held_locations = [] func processItem(item, index, from, flags): if index != null: if _received_indexes.has(index): # Do not re-process items. return _received_indexes.append(index) global._print(item) var gamedata = $Gamedata var item_name = "Unknown" if _item_id_to_name.has(item): item_name = _item_id_to_name[item] if gamedata.door_ids_by_item_id.has(int(item)): var doorsNode = get_tree().get_root().get_node("Spatial/Doors") for door_id in gamedata.door_ids_by_item_id[int(item)]: doorsNode.get_node(door_id).openDoor() if gamedata.painting_ids_by_item_id.has(int(item)): var real_parent_node = get_tree().get_root().get_node("Spatial/Decorations/Paintings") var fake_parent_node = get_tree().get_root().get_node_or_null("Spatial/AP_Paintings") for painting_id in gamedata.painting_ids_by_item_id[int(item)]: var painting_node = real_parent_node.get_node_or_null(painting_id) if painting_node != null: painting_node.movePainting() if _painting_shuffle: painting_node = fake_parent_node.get_node_or_null(painting_id) if painting_node != null: painting_node.get_node("Script").movePainting() # Handle progressive items. if item_name in progressive_items.keys(): if not item_name in _progressive_progress: _progressive_progress[item_name] = 0 if _progressive_progress[item_name] < progressive_items[item_name].size(): var subitem_name = progressive_items[item_name][_progressive_progress[item_name]]["item"] global._print(subitem_name) processItem(_item_name_to_id[subitem_name], null, null, null) _progressive_progress[item_name] += 1 if _color_shuffle and color_items.has(_item_id_to_name[item]): var lcol = _item_id_to_name[item].to_lower() if not _has_colors.has(lcol): _has_colors.append(lcol) emit_signal("evaluate_solvability") # Show a message about the item if it's new. Also apply effects here. if index != null and index > _last_new_item: _last_new_item = index saveLocaldata() if item_name in progressive_items: var subitem = progressive_items[item_name][_progressive_progress[item_name] - 1] item_name += " (%s)" % subitem["display"] var player_name = "Unknown" if _player_name_by_slot.has(from): player_name = _player_name_by_slot[from] var item_color = colorForItemType(flags) var messages_node = get_tree().get_root().get_node("Spatial/Messages") if from == _slot: messages_node.showMessage("Found [color=%s]%s[/color]" % [item_color, item_name]) else: messages_node.showMessage( "Received [color=%s]%s[/color] from %s" % [item_color, item_name, player_name] ) var effects_node = get_tree().get_root().get_node("Spatial/AP_Effects") if item_name == "Slowness Trap": effects_node.trigger_slowness_trap() if item_name == "Iceland Trap": effects_node.trigger_iceland_trap() if item_name == "Atbash Trap": effects_node.trigger_atbash_trap() if item_name == "Puzzle Skip": _puzzle_skips += 1 saveLocaldata() func doorIsVanilla(door): return !$Gamedata.mentioned_doors.has(door) func paintingIsVanilla(painting): return !$Gamedata.mentioned_paintings.has(painting) func evaluateSolvability(): emit_signal("evaluate_solvability") func getAvailablePuzzleSkips(): return _puzzle_skips func usePuzzleSkip(): _puzzle_skips -= 1 saveLocaldata() func colorForItemType(flags): var int_flags = int(flags) if int_flags & 1: # progression return "#bc51e0" elif int_flags & 2: # useful return "#2b67ff" elif int_flags & 4: # trap return "#d63a22" else: # filler return "#14de9e"