#include #include #include #include #include #include #include #include #include #include "proto/human.pb.h" #include "util/ids_yaml_format.h" #include "util/naming.h" namespace com::fourisland::lingo2_archipelago { namespace { template T ReadMessageFromFile(const std::string& path) { std::cout << "Processing " << path << std::endl; std::ifstream file(path); std::stringstream buffer; buffer << file.rdbuf(); T message; google::protobuf::TextFormat::ParseFromString(buffer.str(), &message); return message; } class AssignIds { public: AssignIds(const std::string& mapdir) : mapdir_(mapdir) {} void Run() { std::filesystem::path datadir_path = mapdir_; std::filesystem::path ids_path = datadir_path / "ids.yaml"; ReadIds(ids_path); ProcessMaps(datadir_path); ProcessSpecialIds(); ProcessProgressivesFile(datadir_path / "progressives.txtpb"); ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb"); ProcessGlobalMetadataFile(datadir_path / "metadata.txtpb"); WriteIds(ids_path); std::cout << "Next ID: " << next_id_ << std::endl; } void ReadIds(std::filesystem::path path) { if (!std::filesystem::exists(path)) { return; } id_mappings_ = ReadIdsFromYaml(path.string()); for (const auto& [_, map] : id_mappings_.maps()) { UpdateNextId(map.doors()); for (const auto& [_, room] : map.rooms()) { UpdateNextId(room.panels()); UpdateNextId(room.masteries()); UpdateNextId(room.keyholders()); UpdateNextId(room.ports()); } } UpdateNextId(id_mappings_.special()); UpdateNextId(id_mappings_.letters()); UpdateNextId(id_mappings_.endings()); UpdateNextId(id_mappings_.progressives()); UpdateNextId(id_mappings_.door_groups()); next_id_++; } void WriteIds(std::filesystem::path path) { WriteIdsAsYaml(output_, path.string()); } void ProcessMaps(std::filesystem::path path) { std::filesystem::path maps_dir = path / "maps"; for (auto const& dir_entry : std::filesystem::directory_iterator(maps_dir)) { ProcessMap(dir_entry.path()); } } void ProcessMap(std::filesystem::path path) { std::string map_name = path.filename().string(); ProcessDoorsFile(path / "doors.txtpb", map_name); ProcessRooms(path / "rooms", map_name); } void ProcessDoorsFile(std::filesystem::path path, const std::string& current_map_name) { if (!std::filesystem::exists(path)) { return; } auto doors = ReadMessageFromFile(path.string()); for (const HumanDoor& door : doors.doors()) { ProcessDoor(door, current_map_name); } } void ProcessDoor(const HumanDoor& h_door, const std::string& current_map_name) { if (h_door.type() == DoorType::EVENT && !h_door.latch() && !h_door.legacy_location()) { return; } auto& maps = *output_.mutable_maps(); auto& doors = *maps[current_map_name].mutable_doors(); if (!id_mappings_.maps().contains(current_map_name) || !id_mappings_.maps() .at(current_map_name) .doors() .contains(h_door.name())) { doors[h_door.name()] = next_id_++; } else { doors[h_door.name()] = id_mappings_.maps().at(current_map_name).doors().at(h_door.name()); } } void ProcessRooms(std::filesystem::path path, const std::string& current_map_name) { for (auto const& dir_entry : std::filesystem::directory_iterator(path)) { auto room = ReadMessageFromFile(dir_entry.path().string()); ProcessRoom(room, current_map_name); } } void ProcessRoom(const HumanRoom& h_room, const std::string& current_map_name) { for (const HumanPanel& h_panel : h_room.panels()) { auto& maps = *output_.mutable_maps(); auto& rooms = *maps[current_map_name].mutable_rooms(); auto& panels = *rooms[h_room.name()].mutable_panels(); if (!id_mappings_.maps().contains(current_map_name) || !id_mappings_.maps() .at(current_map_name) .rooms() .contains(h_room.name()) || !id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .panels() .contains(h_panel.name())) { panels[h_panel.name()] = next_id_++; } else { panels[h_panel.name()] = id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .panels() .at(h_panel.name()); } } for (const HumanLetter& h_letter : h_room.letters()) { std::string lettername = GetLetterName(h_letter.key(), h_letter.level2()); auto& letters = *output_.mutable_letters(); if (!id_mappings_.letters().contains(lettername)) { letters[lettername] = next_id_++; } else { letters[lettername] = id_mappings_.letters().at(lettername); } } for (const HumanMastery& h_mastery : h_room.masteries()) { auto& maps = *output_.mutable_maps(); auto& rooms = *maps[current_map_name].mutable_rooms(); auto& masteries = *rooms[h_room.name()].mutable_masteries(); if (!id_mappings_.maps().contains(current_map_name) || !id_mappings_.maps() .at(current_map_name) .rooms() .contains(h_room.name()) || !id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .masteries() .contains(h_mastery.name())) { masteries[h_mastery.name()] = next_id_++; } else { masteries[h_mastery.name()] = id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .masteries() .at(h_mastery.name()); } } for (const HumanEnding& h_ending : h_room.endings()) { auto& endings = *output_.mutable_endings(); if (!id_mappings_.endings().contains(h_ending.name())) { endings[h_ending.name()] = next_id_++; } else { endings[h_ending.name()] = id_mappings_.endings().at(h_ending.name()); } } for (const HumanKeyholder& h_keyholder : h_room.keyholders()) { if (!h_keyholder.has_key()) { continue; } auto& maps = *output_.mutable_maps(); auto& rooms = *maps[current_map_name].mutable_rooms(); auto& keyholders = *rooms[h_room.name()].mutable_keyholders(); if (!id_mappings_.maps().contains(current_map_name) || !id_mappings_.maps() .at(current_map_name) .rooms() .contains(h_room.name()) || !id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .keyholders() .contains(h_keyholder.name())) { keyholders[h_keyholder.name()] = next_id_++; } else { keyholders[h_keyholder.name()] = id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .keyholders() .at(h_keyholder.name()); } } for (const HumanPort& h_port : h_room.ports()) { if (h_port.no_shuffle()) { continue; } auto& maps = *output_.mutable_maps(); auto& rooms = *maps[current_map_name].mutable_rooms(); auto& ports = *rooms[h_room.name()].mutable_ports(); if (!id_mappings_.maps().contains(current_map_name) || !id_mappings_.maps() .at(current_map_name) .rooms() .contains(h_room.name()) || !id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .ports() .contains(h_port.name())) { ports[h_port.name()] = next_id_++; } else { ports[h_port.name()] = id_mappings_.maps() .at(current_map_name) .rooms() .at(h_room.name()) .ports() .at(h_port.name()); } } } void ProcessSpecialIds() { auto& specials = *output_.mutable_special(); for (const auto& [special_name, ap_id] : id_mappings_.special()) { specials[special_name] = ap_id; } } void ProcessProgressivesFile(std::filesystem::path path) { if (!std::filesystem::exists(path)) { return; } auto h_progs = ReadMessageFromFile(path.string()); auto& progs = *output_.mutable_progressives(); for (const HumanProgressive& h_prog : h_progs.progressives()) { if (!id_mappings_.progressives().contains(h_prog.name())) { progs[h_prog.name()] = next_id_++; } else { progs[h_prog.name()] = id_mappings_.progressives().at(h_prog.name()); } } } void ProcessDoorGroupsFile(std::filesystem::path path) { if (!std::filesystem::exists(path)) { return; } auto h_groups = ReadMessageFromFile(path.string()); auto& groups = *output_.mutable_door_groups(); for (const HumanDoorGroup& h_group : h_groups.door_groups()) { if (!id_mappings_.door_groups().contains(h_group.name())) { groups[h_group.name()] = next_id_++; } else { groups[h_group.name()] = id_mappings_.door_groups().at(h_group.name()); } } } void ProcessGlobalMetadataFile(std::filesystem::path path) { if (!std::filesystem::exists(path)) { return; } auto h_metadata = ReadMessageFromFile(path.string()); auto& specials = *output_.mutable_special(); for (const std::string& h_special : h_metadata.special_names()) { if (!id_mappings_.special().contains(h_special)) { specials[h_special] = next_id_++; } else { specials[h_special] = id_mappings_.special().at(h_special); } } } private: void UpdateNextId(const google::protobuf::Map& ids) { for (const auto& [_, id] : ids) { if (id > next_id_) { next_id_ = id; } } } std::string mapdir_; uint64_t next_id_ = 1; IdMappings id_mappings_; IdMappings output_; }; } // namespace } // namespace com::fourisland::lingo2_archipelago int main(int argc, char** argv) { if (argc != 2) { std::cout << "Incorrect argument count." << std::endl; std::cout << "Usage: assign_ids [path to map directory]" << std::endl; return 1; } std::string mapdir = argv[1]; com::fourisland::lingo2_archipelago::AssignIds assign_ids(mapdir); assign_ids.Run(); return 0; }erimental-panels&id=92b1c91d7d40dbb5d301d896c542d03b4b374075'>92b1c91 ^
1
2
3
4
5
6
7
8





                  
                           
                                                                           


                                                                                               























                                                                                          




                                                                                                 

         
 

                     
                  



                           







                                         
                                   
                           
                                 
                    
 
                      
                          
                                      
                                          

                                           
 

                                                          

                                                                   
                                                      
                          
              


                 
                             

                           
                           
                        
                       
                                                          

                          
                                         
                             
                              
                              
                                    
                  


                       
                        
                       
                              
                           
                          
                     
 
                        
                     
                       
                           
 
















                                                                   
                                   


                                               


              
                                                             


                                                               



                                                                     
                    
                               
                              
                           
                        


                





                                                                                      
 



                                                                                                                                                

 
                                
                                          
                      





                                                                                 


                                   
                        

 




                                      












                                                                                   
                                                    
                                                            
 


                                                                            

                                                                                                                    



                                                                 


                                                                              
                             

                                               
                                          

                                                                                    
                                      






                                                                              
 







                                                                         


                                                                                      



                                                                                               

                                                                                    



                                                                                 
                                                               
                                                                                   



                                                                             

                                                                                            

                                                                                          

                                                                                        






                                                                                                      
 

                                         
                                                                                          










                                                                        


                                                                    

                                     

                                                       
                                                



































                                                                                                                                                               


                                                                 
                                            
                                 

                                                     
                                                                                                                      
                                     



                                                                                      

                                                                               


                                                 
 



















                                                                                                 

                                                                                   
                                                                                              





                                                                              



                                                                                                        


                                                                 


                                                                                                                        
 






                                                                    
                                                                                                      



                                                                                                             
                                                                                                   





                                                                                                     



                              













                                                            
                                                               

                                  

 


                                                     
                                            







                                              
                                                  



                                  



                                            
                       

                                     










                                                                             

                                             






                                                                                                                                                                        



                                                                

                                                      






                                                                         
                                

                                                                                  




                                                                
                                 



                                                                                 


                                                                                         


                                                                                    


                     

                                                          














                                                                                    

 



                                                             



                                      
                          



                                                                               

 

                          






                                                                                        


         


                                                                           


                                                                              
 

                          

                                             


                                                   
                                        
                                                                                             
 


                                                                                      
                                
                                    

 
                                           






                                                  

                           




                                                  
                                                       
                                                                               
                                                                       

                                                              
                                                           
                                                                                                      
                                                                                                     
 
                                                                               







                                                                                              
 
                                   








                                                                                                                 
 





                                                                      
                                                                             
                                                    


                                      


                                                                                                        
 



                                                                

                                                        
                                                                                      
                                 
                                                                                                         
                     


                                                                                                              
 




                                                                                       

                                                          

                                              
 

                                       

                         
                                                   


                                 
                                                           

 



                                           






                               

                       
 









                                        
extends Node

var ap_server = ""
var ap_user = ""
var ap_pass = ""

const my_version = "0.11.1"
const ap_version = {"major": 0, "minor": 4, "build": 0, "class": "Version"}
const color_items = [
	"White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow"
]
const progressive_items = {
	"Progressive Orange Tower":
	[
		{"item": "Orange Tower - Second Floor", "display": "Second Floor"},
		{"item": "Orange Tower - Third Floor", "display": "Third Floor"},
		{"item": "Orange Tower - Fourth Floor", "display": "Fourth Floor"},
		{"item": "Orange Tower - Fifth Floor", "display": "Fifth Floor"},
		{"item": "Orange Tower - Sixth Floor", "display": "Sixth Floor"},
		{"item": "Orange Tower - Seventh Floor", "display": "Seventh Floor"},
	],
	"Progressive Art Gallery":
	[
		{"item": "Art Gallery - Second Floor", "display": "Second Floor"},
		{"item": "Art Gallery - Third Floor", "display": "Third Floor"},
		{"item": "Art Gallery - Fourth Floor", "display": "Fourth Floor"},
		{"item": "Art Gallery - Fifth Floor", "display": "Fifth Floor"},
		{"item": "Art Gallery - Exit", "display": "Exit"},
	],
	"Progressive Hallway Room":
	[
		{"item": "Outside The Agreeable - Hallway Door", "display": "First Door"},
		{"item": "Hallway Room (2) - Exit", "display": "Second Door"},
		{"item": "Hallway Room (3) - Exit", "display": "Third Door"},
		{"item": "Hallway Room (4) - Exit", "display": "Fourth Door"},
	],
	"Progressive Fearless":
	[
		{"item": "The Fearless (First Floor) - Second Floor", "display": "Second Floor"},
		{"item": "The Fearless (Second Floor) - Third Floor", "display": "Third Floor"},
	]
}

const kTHE_END = 0
const kTHE_MASTER = 1
const kLEVEL_2 = 2

const kNO_PANEL_SHUFFLE = 0
const kREARRANGE_PANELS = 1

const kCLASSIFICATION_LOCAL_NORMAL = 1
const kCLASSIFICATION_LOCAL_REDUCED = 2
const kCLASSIFICATION_LOCAL_INSANITY = 4

const kCLASSIFICATION_REMOTE_NORMAL = 0
const kCLASSIFICATION_REMOTE_REDUCED = 1
const kCLASSIFICATION_REMOTE_INSANITY = 2

var _client = WebSocketClient.new()
var _should_process = false
var _initiated_disconnect = false
var _try_wss = 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 only
var _location_name_to_id = {}  # LINGO only

var _remote_version = {"major": 0, "minor": 0, "build": 0}

const uuid_util = preload("user://maps/Archipelago/vendor/uuid.gd")

# TODO: caching per MW/slot, reset between connections
var _authenticated = false
var _seed = ""
var _team = 0
var _slot = 0
var _players = []
var _player_name_by_slot = {}
var _checked_locations = []
var _slot_data = {}
var _paintings_mapping = {}
var _localdata_file = ""
var _death_link = false
var _victory_condition = 0  # THE END, THE MASTER, LEVEL 2
var _door_shuffle = false
var _color_shuffle = false
var _panel_shuffle = 0  # none, rearrange
var _painting_shuffle = false
var _mastery_achievements = 21
var _level_2_requirement = 223
var _location_classification_bit = 0
var _slot_seed = 0

var _map_loaded = false
var _held_items = []
var _held_locations = []
var _last_new_item = -1
var _progressive_progress = {}
var _has_colors = ["white"]
var _received_indexes = []
var _puzzle_skips = 0

signal could_not_connect
signal connect_status
signal client_connected
signal evaluate_solvability


func _init():
	global._print("Instantiated APClient")

	# Read AP settings from file, if there are any
	var file = File.new()
	if file.file_exists("user://settings/archipelago"):
		file.open("user://settings/archipelago", File.READ)
		var data = file.get_var(true)
		file.close()

		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:
			_datapackages = data[3]

		processDatapackages()


func _ready():
	_client.connect("connection_closed", self, "_closed")
	_client.connect("connection_failed", self, "_closed")
	_client.connect("server_disconnected", self, "_closed")
	_client.connect("connection_error", self, "_errored")
	_client.connect("connection_established", self, "_connected")
	_client.connect("data_received", self, "_on_data")


func _reset_state():
	_should_process = false
	_authenticated = false
	_map_loaded = false
	_try_wss = false


func _errored():
	if _try_wss:
		global._print("Could not connect to AP with ws://, now trying wss://")
		connectToServer()
	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
	_client.disconnect_from_host()


func _on_data():
	var packet = _client.get_peer(1).get_packet()
	global._print("Got data from server: " + packet.get_string_from_utf8())
	var data = JSON.parse(packet.get_string_from_utf8())
	if data.error != OK:
		global._print("Error parsing packet from AP: " + data.error_string)
		return

	for message in data.result:
		var cmd = message["cmd"]
		global._print("Received command: " + cmd)

		if cmd == "RoomInfo":
			_seed = message["seed_name"]
			_remote_version = message["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.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]
			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

			_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"