diff --git a/.gitignore b/.gitignore index d9aac21..55bb164 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ export_presets.cfg .mono/ data_*/ mono_crash.*.json +.vscode/ \ No newline at end of file diff --git a/Client.gd b/Client.gd deleted file mode 100644 index 3d98335..0000000 --- a/Client.gd +++ /dev/null @@ -1,634 +0,0 @@ -extends Control -class_name NakamaMultiplayer - -var session : NakamaSession # this is the session -var client : NakamaClient # this is the client {session} -var socket : NakamaSocket # connection to nakama -var createdMatch -var multiplayerBridge : NakamaMultiplayerBridge - -var selectedGroup -var currentChannel -var chatChannels := {} - -static var Players = {} - -var party - -signal OnStartGame() - -# Called when the node enters the scene tree for the first time. -func _ready(): - client = Nakama.create_client("AGgpLqM45wdkEIH4PMvs5c90T6HxtrBV", "127.0.0.1", 7350, "https") - - pass # Replace with function body. - -func updateUserInfo(username, displayname, avaterurl = "", language = "en", location = "us", timezone = "est"): - await client.update_account_async(session, username, displayname, avaterurl, language, location, timezone) - -func onMatchPresence(presence : NakamaRTAPI.MatchPresenceEvent): - print(presence) - -func onMatchState(state : NakamaRTAPI.MatchData): - print("data is : " + str(state.data)) - -func onSocketConnected(): - print("Socket Connected") - -func onSocketClosed(): - print("Socket Closed") - -func onSocketReceivedError(err): - print("Socket Error:" + str(err)) - -# Called every frame. 'delta' is the elapsed time since the previous frame. -func _process(delta): - pass - - -func _on_login_button_button_down(): - session = await client.authenticate_email_async($Panel2/EmailInput.text , $Panel2/PasswordInput.text,) - - #var deviceid = OS.get_unique_id() - #session = await client.authenticate_device_async(deviceid) - socket = Nakama.create_socket_from(client) - - await socket.connect_async(session) - - socket.connected.connect(onSocketConnected) - socket.closed.connect(onSocketClosed) - socket.received_error.connect(onSocketReceivedError) - - socket.received_match_presence.connect(onMatchPresence) - socket.received_match_state.connect(onMatchState) - - socket.received_channel_message.connect(onChannelMessage) - - socket.received_party_presence.connect(onPartyPresence) - - #updateUserInfo("test", "testDisplay") - - #var account = await client.get_account_async(session) - # - #$Panel/UserAccountText.text = account.user.username - #$Panel/DisplayNameText.text = account.user.display_name - - setupMultiplayerBridge() - subToFriendChannels() - pass # Replace with function body. - - - -func setupMultiplayerBridge(): - multiplayerBridge = NakamaMultiplayerBridge.new(socket) - multiplayerBridge.match_join_error.connect(onMatchJoinError) - var multiplayer = get_tree().get_multiplayer() - multiplayer.set_multiplayer_peer(multiplayerBridge.multiplayer_peer) - multiplayer.peer_connected.connect(onPeerConnected) - multiplayer.peer_disconnected.connect(onPeerDisconnected) - -func onPeerConnected(id): - print("Peer connected id is : " + str(id)) - - if !Players.has(id): - Players[id] = { - "name" : id, - "ready" : 0 - } - if !Players.has(multiplayer.get_unique_id()): - Players[multiplayer.get_unique_id()]= { - "name" : multiplayer.get_unique_id(), - "ready" : 0 - } - print(Players) - -func onPeerDisconnected(id): - print("Peer disconnected id is : " + str(id)) - -func onMatchJoinError(error): - print("Unable to join match: " + error.message) - -func onMatchJoin(): - print("joined Match with id: " + multiplayerBridge.match_id) -func _on_store_data_button_down(): - var saveGame = { - "name" : "username", - "items" : [{ - "id" : 1, - "name" : "gun", - "ammo" : 10 - }, - { - "id" : 2, - "name" : "sword", - "ammo" : 0 - }], - "level" : 10 - } - var data = JSON.stringify(saveGame) - var result = await client.write_storage_objects_async(session, [ - NakamaWriteStorageObject.new("saves", "savegame2", 1, 1, data , "") - ]) - - if result.is_exception(): - print("error" + str(result)) - return - print("Stored data successfully!") - pass # Replace with function body. - - -func _on_get_data_button_down(): - var result = await client.read_storage_objects_async(session, [ - NakamaStorageObjectId.new("saves", "savegame", session.user_id) - ]) - - if result.is_exception(): - print("error" + str(result)) - return - for i in result.objects: - print(i.value) - pass # Replace with function body. - - -func _on_list_data_button_down(): - var dataList = await client.list_storage_objects_async(session, "saves",session.user_id, 5) - for i in dataList.objects: - print(i) - pass # Replace with function body. - - -func _on_join_create_match_button_down(): - multiplayerBridge.join_named_match($Panel3/MatchName.text) - - #createdMatch = await socket.create_match_async($Panel3/MatchName.text) - #if createdMatch.is_exception(): - #print("Failed to create match " + str(createdMatch)) - #return - # - #print("Created match :" + str(createdMatch.match_id)) - pass # Replace with function body. - - -func _on_ping_button_down(): - #sendData.rpc("hello world") - var data = {"hello" : "world"} - socket.send_match_state_async(createdMatch.match_id, 1, JSON.stringify(data)) - pass # Replace with function body. - -@rpc("any_peer") -func sendData(message): - print(message) - - -func _on_matchmaking_button_down(): - var query = "+properties.region:US +properties.rank:>=4 +properties.rank:<=10" - var stringP = {"region" : "US"} - var numberP = { "rank": 6} - - var ticket = await socket.add_matchmaker_async(query,2, 4, stringP, numberP) - - if ticket.is_exception(): - print("failed to matchmake : " + str(ticket)) - return - - print("match ticket number : " + str(ticket)) - - socket.received_matchmaker_matched.connect(onMatchMakerMatched) - pass # Replace with function body. - -func onMatchMakerMatched(matched : NakamaRTAPI.MatchmakerMatched): - var joinedMatch = await socket.join_matched_async(matched) - createdMatch = joinedMatch - -######### Friends -func _on_add_friend_button_down(): - var id = [$Panel4/AddFriendText.text] - - var result = await client.add_friends_async(session, null, id) - pass # Replace with function body. - - -func _on_get_friends_button_down(): - var result = await client.list_friends_async(session) - - for i in result.friends: - var container = HBoxContainer.new() - var currentlabel = Label.new() - currentlabel.text = i.user.display_name - container.add_child(currentlabel) - print(i) - var currentButton = Button.new() - container.add_child(currentButton) - currentButton.text = "Trade" - #currentButton.text = "Invite" - currentButton.button_down.connect(onTrade.bind(i)) - #currentButton.button_down.connect(onInviteToParty.bind(i)) - $Panel4/Panel4/VBoxContainer.add_child(container) - - pass # Replace with function body. - - -func _on_remove_friend_button_down(): - var result = await client.delete_friends_async(session,[], [$Panel4/AddFriendText.text]) - pass # Replace with function body. - - -func _on_block_friends_button_down(): - var result = await client.block_friends_async(session,[], [$Panel4/AddFriendText.text]) - pass # Replace with function body. - - -func _on_create_group_button_down(): - var group = await client.create_group_async(session, $Panel6/GroupName.text, $Panel6/GroupDesc.text, "" , "en", true, 32) - print(group) - pass # Replace with function body. - - -func _on_get_group_memebers_button_down(): - var result = await client.list_group_users_async(session, $Panel5/GroupName.text) - - for i in result.group_users: - var currentlabel = Label.new() - currentlabel.text = i.user.display_name - $Panel5/Panel4/GroupVBox.add_child(i.user.username) - print("users in group " + $Panel5/GroupName.text + i.user.username) - pass # Replace with function body. - - -func _on_button_button_down(): - Ready.rpc(multiplayer.get_unique_id()) - pass # Replace with function body. - -@rpc("any_peer", "call_local") -func Ready(id): - Players[id].ready = 1 - if multiplayer.is_server(): - var readyPlayers = 0 - for i in Players: - if Players[i].ready == 1: - readyPlayers += 1 - if readyPlayers == Players.size(): - StartGame.rpc() - -@rpc("any_peer", "call_local") -func StartGame(): - OnStartGame.emit() - hide() - pass - -####### Group -func _on_add_user_to_group_button_down(): - await client.join_group_async(session, selectedGroup.id) - pass # Replace with function body. - - -func _on_add_user_to_group_2_button_down(): - var users = await client.list_group_users_async(session,selectedGroup.id, 3) - - for user in users.group_users: - var u = user.user as NakamaAPI.ApiUser - await client.add_group_users_async(session, selectedGroup.id, [u.id]) - pass # Replace with function body. - - -func _on_check_button_toggled(toggled_on): - await client.update_group_async(session, selectedGroup.id, "Strong Gamers", "we are the strong gamers!", null, "en", toggled_on) - pass # Replace with function body. - - -func _on_list_groups_button_down(): - var limit = 10 - var result = await client.list_groups_async(session, $Panel6/GroupQuery.text, limit, null, null, null) - - for group in result.groups: - var vbox = VBoxContainer.new() - var hbox = HBoxContainer.new() - - var namelabel = Label.new() - namelabel.text = group.name - hbox.add_child(namelabel) - var button = Button.new() - button.button_down.connect(onGroupSelectButton.bind(group)) - button.text = "Select Group" - hbox.add_child(button) - vbox.add_child(hbox) - $Panel6/Panel/VBoxContainer.add_child(vbox) - pass # Replace with function body. - -func onGroupSelectButton(group): - selectedGroup = group - - - - - -func _on_promote_user_button_down(): - var result : NakamaAPI.ApiUsers = await client.get_users_async(session, [],[$Panel6/UserToManage.text], null) - for u in result.users: - await client.promote_group_users_async(session, selectedGroup.id, [u.id]) - pass # Replace with function body. - - -func _on_demote_user_button_down(): - var result : NakamaAPI.ApiUsers = await client.get_users_async(session, [],[$Panel6/UserToManage.text], null) - for u in result.users: - await client.demote_group_users_async(session, selectedGroup.id, [u.id]) - pass # Replace with function body. - - -func _on_kick_user_button_down(): - var result : NakamaAPI.ApiUsers = await client.get_users_async(session, [],[$Panel6/UserToManage.text], null) - for u in result.users: - await client.kick_group_users_async(session, selectedGroup.id, [u.id]) - pass # Replace with function body. - - - -func _on_leave_group_button_down(): - await client.leave_group_async(session, selectedGroup.id) - pass # Replace with function body. - - -func _on_delete_group_button_down(): - await client.delete_group_async(session, selectedGroup.id) - pass # Replace with function body. - -########## Chat Room Code -func _on_join_chat_room_button_down(): - var type = NakamaSocket.ChannelType.Room - currentChannel = await socket.join_chat_async($Panel7/ChatName.text, type, false, false) - - print("channel id: " + currentChannel.id) - pass # Replace with function body. - -func onChannelMessage(message : NakamaAPI.ApiChannelMessage): - var content = JSON.parse_string(message.content) - if content.type == 0: - $Panel7/Chat/TabContainer.get_node(content.id).text += message.username + ": " + str(content.message) + "\n" - elif content.type == 1 && party == null: - $Panel8/Panel2.show() - party = {"id" : content.partyID} - $Panel8/Panel2/Label.text = str(content.message) - pass - -func _on_submit_chat_button_down(): - await socket.write_chat_message_async(currentChannel.id, { - "message" : $Panel7/Chat/ChatText.text, - "id" : chatChannels[currentChannel.id].label, - "type" : 0 - }) - pass # Replace with function body. - - -func _on_join_group_chat_room_button_down(): - var type = NakamaSocket.ChannelType.Group - currentChannel = await socket.join_chat_async(selectedGroup.id, type, true, false) - - print("channel id: " + currentChannel.id) - chatChannels[selectedGroup.id] = { - "channel" : currentChannel, - "label" : "Group Chat" - } - var currentEdit = TextEdit.new() - currentEdit.name = "currentGroup" - $Panel7/Chat/TabContainer.add_child(currentEdit) - currentEdit.text = await listMessages(currentChannel) - $Panel7/Chat/TabContainer.tab_changed.connect(onChatTabChanged.bind(selectedGroup.id)) - - pass # Replace with function body. - -func onChatTabChanged(index, channelID): - currentChannel = chatChannels[channelID].channel - pass - -func listMessages(currentChannel): - - var result = await client.list_channel_messages_async(session, currentChannel.id, 100, true) - var text = "" - for message in result.messages: - if(message.content != "{}"): - var content = JSON.parse_string(message.content) - - text += message.username + ": " + str(content.message) + "\n" - return text - -func subToFriendChannels(): - var result = await client.list_friends_async(session) - for i in result.friends: - var type = NakamaSocket.ChannelType.DirectMessage - var channel = await socket.join_chat_async(i.user.id, type, true, false) - chatChannels[channel.id] = { - "channel" : channel, - "label" : i.user.username - } - var currentEdit = TextEdit.new() - currentEdit.name = i.user.username - $Panel7/Chat/TabContainer.add_child(currentEdit) - currentEdit.text = await listMessages(channel) - $Panel7/Chat/TabContainer.tab_changed.connect(onChatTabChanged.bind(channel.id)) - -func _on_join_direct_chat_button_down(): - var type = NakamaSocket.ChannelType.DirectMessage - var usersResult = await client.get_users_async(session, [], [$Panel7/ChatName.text]) - if usersResult.users.size() > 0: - currentChannel = await socket.join_chat_async(usersResult.users[0].id, type, true, false) - - print("channel id: " + currentChannel.id) - - var result = await client.list_channel_messages_async(session, currentChannel.id, 100, true) - - for message in result.messages: - if(message.content != "{}"): - var content = JSON.parse_string(message.content) - - $Panel7/Chat/ChatTextBox.text += message.username + ": " + str(content.message) + "\n" - - - pass # Replace with function body. - -###### Party System - -func _on_create_party_button_down(): - party = await socket.create_party_async(false, 2) - - pass # Replace with function body. - -func onInviteToParty(friend): - var channel = await socket.join_chat_async(friend.user.id, NakamaSocket.ChannelType.DirectMessage) - var ack = await socket.write_chat_message_async(channel.id, { - "message" : "Join Party with " + session.username, - "partyID" : party.party_id, - "type" : 1 - } - ) - pass - - -func _on_join_party_yes_button_down(): - var result = await socket.join_party_async(party.id) - if result.is_exception(): - print("failed to join party") - $Panel8/Panel2.hide() - pass # Replace with function body. - - -func _on_join_party_no_button_down(): - $Panel8/Panel2.hide() - pass # Replace with function body. - -func onPartyPresence(presence : NakamaRTAPI.PartyPresenceEvent): - print("JOINED PARTY " + presence.party_id) - -############### RPC TRADE SYSTEM -func _on_ping_rpc_button_down(): - var item = { - "name" = "sword", - "type" = "Weapon", - "rarity" = "common" - } - var rpcReturn = await client.rpc_async(session, "addItemToInventory", JSON.stringify(item)) - print(rpcReturn) - pass # Replace with function body. - - -func _on_get_inventory_button_down(): - var inventory = await getInventory(session.user_id) - removeMyChildren($TradeSystem/Panel/VBoxContainer) - - for i in inventory: - var button = Button.new() - button.name = i.name - button.text = i.name - $TradeSystem/Panel/VBoxContainer.add_child(button) - button.button_down.connect(setItemForTrade.bind(i, button, true)) - var stylebox = StyleBoxFlat.new() - button.add_theme_stylebox_override("normal", stylebox) - - pass # Replace with function body. - -func getInventory(id): - var result = await client.rpc_async(session, "getInventory", JSON.stringify({"id" : id})) - var inventory = JSON.parse_string(result.payload) - return inventory - pass - - -func onTrade(friend): - var inventory = await getInventory(friend.user.id) - PlayerToTradeWith = friend - removeMyChildren($TradeSystem/Panel/VBoxContainer2) - - for i in inventory: - var button = Button.new() - button.name = i.name - button.text = i.name - button.button_down.connect(setItemForTrade.bind(i, button, false)) - $TradeSystem/Panel/VBoxContainer2.add_child(button) - var stylebox = StyleBoxFlat.new() - button.add_theme_stylebox_override("normal", stylebox) - pass - -var TradeItems = [] -var ItemsToTradeFor = [] -var PlayerToTradeWith -var currentTradeOffer - -func setItemForTrade(item, button : Button, player: bool): - var items - if player: - items = TradeItems - else: - items = ItemsToTradeFor - - if(!items.has(item)): - items.append(item) - var stylebox = StyleBoxFlat.new() - stylebox.bg_color = Color.GREEN - button.add_theme_stylebox_override("normal", stylebox) - else: - items.erase(item) - var stylebox = StyleBoxFlat.new() - button.add_theme_stylebox_override("normal", stylebox) - - if player: - TradeItems = items - else: - ItemsToTradeFor = items - - -func _on_send_trade_offer_button_down(): - var receiverID = PlayerToTradeWith.user.id - var offerItems = TradeItems - var requestedItems = ItemsToTradeFor - - var payload = { - "recieverid" : receiverID, - "offerItems" : offerItems, - "requestedItems" : requestedItems - } - if offerItems == [] || requestedItems == []: - print("cannot send empty offer") - return - - var result = await client.rpc_async(session, "createTradeOffer", JSON.stringify(payload)) - print(result) - pass # Replace with function body. - - -func _on_accept_trade_offer_button_down(): - var payload = {"offerID" : currentTradeOffer.offerid} - var result = await client.rpc_async(session, "acceptTradeOffer", JSON.stringify(payload)) - - var response = JSON.parse_string(result.payload) - if result.exception != null: - print(result.exception.message) - else: - print("accepted trade offer " + response.result) - pass # Replace with function body. - - -func _on_get_trade_offers_button_down(): - var tradeOffers = await getTradeOffers() - - for i in tradeOffers: - var button = Button.new() - var id = await client.get_users_async(session, [i.senderid], null) - button.text = id.users[0].display_name - button.button_down.connect(setTradeOffers.bind(i)) - $TradeSystem/TradeOffers/VBoxContainer.add_child(button) - pass # Replace with function body. - -func setTradeOffers(offer): - removeMyChildren($TradeSystem/Panel/VBoxContainer) - removeMyChildren($TradeSystem/Panel/VBoxContainer2) - - for i in offer.requestedItems: - var button = Button.new() - button.text = i.name - $TradeSystem/Panel/VBoxContainer.add_child(button) - for i in offer.offerItems: - var button = Button.new() - button.text = i.name - $TradeSystem/Panel/VBoxContainer2.add_child(button) - - currentTradeOffer = offer - -func removeMyChildren(node): - for i in node.get_children(): - i.queue_free() - -func getTradeOffers(): - var result = await client.rpc_async(session, "getTradeOffers", "{}") - - var tradeOffers = JSON.parse_string(result.payload) - - return tradeOffers - - -func _on_cancel_trade_offer_button_down(): - var payload = {"offerID" : currentTradeOffer.offerid} - var result = await client.rpc_async(session, "cancelTradeOffer", JSON.stringify(payload)) - - var response = JSON.parse_string(result.payload) - print("Canceled trade offer " + response.result) - pass # Replace with function body. diff --git a/Game/GameManager/GameManager.gd b/Game/GameManager/GameManager.gd new file mode 100644 index 0000000..35ae3b8 --- /dev/null +++ b/Game/GameManager/GameManager.gd @@ -0,0 +1,43 @@ +extends Node2D + +@export var multiplayerScene : PackedScene +@onready var main_menu: NakamaMultiplayer = %MainMenu +@onready var multiplayer_scene: Node2D = $MultiplayerScene + +var multiplayer_scene_instance : Level + +var match_ended : bool = false + +func _ready(): + main_menu.OnStartGame.connect(on_start_game) + +func on_start_game(): + main_menu.visible = false + multiplayer_scene_instance = multiplayerScene.instantiate() + multiplayer_scene_instance.debug_mode = false + + multiplayer_scene.add_child(multiplayer_scene_instance) + multiplayer_scene_instance.level_completed.connect(player_finished) + +func player_finished() -> void: + _player_finished.rpc() + +@rpc("any_peer", "call_local") +func _player_finished() -> void: + + var winner_id : int = multiplayer.get_remote_sender_id() + match_ended = true + + main_menu.visible = true + + if multiplayer_scene_instance: + multiplayer_scene_instance.queue_free() + multiplayer_scene_instance = null + + if multiplayer.is_server(): + main_menu.match_ended(winner_id) + +@rpc("authority", "call_remote") +func _end_match() -> void: + pass + diff --git a/Game/GameManager/GameManager.gd.uid b/Game/GameManager/GameManager.gd.uid new file mode 100644 index 0000000..8545004 --- /dev/null +++ b/Game/GameManager/GameManager.gd.uid @@ -0,0 +1 @@ +uid://cjlqh4uje7qp5 diff --git a/Game/GameManager/game_manager.tscn b/Game/GameManager/game_manager.tscn new file mode 100644 index 0000000..61a4302 --- /dev/null +++ b/Game/GameManager/game_manager.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=4 format=3 uid="uid://ci550n7xhfye1"] + +[ext_resource type="Script" uid="uid://cjlqh4uje7qp5" path="res://Game/GameManager/GameManager.gd" id="1_c0vjc"] +[ext_resource type="PackedScene" uid="uid://ch4g8yusgdhm2" path="res://Game/Maps/Test1/Test1.tscn" id="2_sjivl"] +[ext_resource type="PackedScene" uid="uid://p2ptqa1u1n1k" path="res://MainMenu/MainMenu.tscn" id="2_ysfsu"] + +[node name="GameManager" type="Node2D"] +script = ExtResource("1_c0vjc") +multiplayerScene = ExtResource("2_sjivl") + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="MainMenu" parent="CanvasLayer" instance=ExtResource("2_ysfsu")] +unique_name_in_owner = true + +[node name="MultiplayerScene" type="Node2D" parent="."] diff --git a/Game/Maps/MapTemplate.gd b/Game/Maps/MapTemplate.gd new file mode 100644 index 0000000..5935428 --- /dev/null +++ b/Game/Maps/MapTemplate.gd @@ -0,0 +1,85 @@ +extends Node2D +class_name Level + +signal level_completed() + +var spawnpoints : Array[Node] +@export var playerScene : PackedScene +@export var debug_mode : bool +@export var end_flag : Area2D +@export var level_void : Area2D + +@onready var camera: Camera2D = %Camera + +func _ready() -> void: + + end_flag.body_entered.connect(_end_level) + level_void.body_entered.connect(_respawn_player) + + spawnpoints = get_tree().get_nodes_in_group("SpawnPoint") + + if debug_mode: + _spawn_debug_player() + return + + if multiplayer.is_server(): + print("sou o server") + var index = 0 + var players : Array = NakamaManager.Players.values() + print("players: ", players) + players.sort() + + for player in players: + + var data : Dictionary = { + "index": index, + "id" : player.name, + } + _spawn_player.rpc(data) + + index += 1 + else: + print("não sou o server") + +func cleanup_multiplayer(): + """Call this before freeing the level to properly cleanup multiplayer nodes""" + var players = get_tree().get_nodes_in_group(Player.GROUP_NAME) + + for player in players: + if player.has_method("set_multiplayer_authority"): + player.set_multiplayer_authority(1, false) + + var sync = player.get_node_or_null("MultiplayerSynchronizer") + if sync: + sync.enabled = false + +func _spawn_debug_player() -> void: + var instancedPlayer = playerScene.instantiate() + instancedPlayer.name = "Player" + instancedPlayer.global_position = spawnpoints[0].global_position + + add_child(instancedPlayer) + + instancedPlayer.setup_camera(camera) + +@rpc("any_peer","call_local") +func _spawn_player(data: Dictionary) -> void: + var instancedPlayer = playerScene.instantiate() + instancedPlayer.name = str(data.id) + instancedPlayer.global_position = spawnpoints[data.index].global_position + + add_child(instancedPlayer) + instancedPlayer.set_multiplayer_authority(data.id) + if _is_authority(data.id): + + instancedPlayer.setup_camera(camera) + +func _is_authority(user_id: int) -> bool: + return user_id == multiplayer.get_unique_id() + +func _respawn_player(player: CharacterBody2D) -> void: + player.global_position = spawnpoints.pick_random().global_position + +func _end_level(body: CharacterBody2D) -> void: + var _player_name = body.name + level_completed.emit() diff --git a/Game/Maps/MapTemplate.gd.uid b/Game/Maps/MapTemplate.gd.uid new file mode 100644 index 0000000..fb3e930 --- /dev/null +++ b/Game/Maps/MapTemplate.gd.uid @@ -0,0 +1 @@ +uid://dqxptyd78e25 diff --git a/Game/Maps/Test1/Preview.png b/Game/Maps/Test1/Preview.png new file mode 100644 index 0000000..f85f1b5 Binary files /dev/null and b/Game/Maps/Test1/Preview.png differ diff --git a/Game/Maps/Test1/Preview.png.import b/Game/Maps/Test1/Preview.png.import new file mode 100644 index 0000000..aa28c70 --- /dev/null +++ b/Game/Maps/Test1/Preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b15cpijfksywl" +path="res://.godot/imported/Preview.png-d07bc505fab81653af934883c2185d6a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Game/Maps/Test1/Preview.png" +dest_files=["res://.godot/imported/Preview.png-d07bc505fab81653af934883c2185d6a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Game/Maps/Test1/Test1.tscn b/Game/Maps/Test1/Test1.tscn new file mode 100644 index 0000000..521e924 --- /dev/null +++ b/Game/Maps/Test1/Test1.tscn @@ -0,0 +1,1631 @@ +[gd_scene load_steps=9 format=4 uid="uid://ch4g8yusgdhm2"] + +[ext_resource type="Texture2D" uid="uid://bkpla8vcdb8o" path="res://Texture/TX Tileset Ground.png" id="1_iivnr"] +[ext_resource type="Script" uid="uid://dqxptyd78e25" path="res://Game/Maps/MapTemplate.gd" id="1_u3ux4"] +[ext_resource type="PackedScene" uid="uid://cexkbcrii6gsh" path="res://Game/Player/player.tscn" id="3_cbm6k"] +[ext_resource type="Texture2D" uid="uid://b5644j1scdp4u" path="res://icon.svg" id="4_37esa"] + +[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_iv3md"] +texture = ExtResource("1_iivnr") +texture_region_size = Vector2i(32, 32) +0:0/0 = 0 +0:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:0/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +&"type": 0 +} +1:0/0 = 0 +1:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +1:0/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +2:0/0 = 0 +2:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:0/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +&"type": 0 +} +4:0/0 = 0 +4:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:0/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +&"type": 0 +} +5:0/0 = 0 +5:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:0/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +&"type": 0 +} +7:0/0 = 0 +7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:0/0/metadata/_better_terrain = { +4: [0], +&"type": 0 +} +9:0/0 = 0 +9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:0/0/metadata/_better_terrain = { +0: [0], +&"type": 0 +} +10:0/0 = 0 +10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:0/0/metadata/_better_terrain = { +8: [0], +&"type": 0 +} +12:0/0 = 0 +12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:0/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +&"type": 0 +} +13:0/0 = 0 +13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:0/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +&"type": 0 +} +0:1/0 = 0 +0:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:1/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +1:1/0 = 0 +1:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +1:1/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +2:1/0 = 0 +2:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:1/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +4:1/0 = 0 +4:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:1/0/metadata/_better_terrain = { +0: [0], +12: [0], +15: [0], +&"type": 0 +} +5:1/0 = 0 +5:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:1/0/metadata/_better_terrain = { +8: [0], +11: [0], +12: [0], +&"type": 0 +} +7:1/0 = 0 +7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:1/0/metadata/_better_terrain = { +12: [0], +&"type": 0 +} +12:1/0 = 0 +12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:1/0/metadata/_better_terrain = { +0: [0], +12: [0], +15: [0], +&"type": 0 +} +13:1/0 = 0 +13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:1/0/metadata/_better_terrain = { +8: [0], +11: [0], +12: [0], +&"type": 0 +} +0:2/0 = 0 +0:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:2/0/metadata/_better_terrain = { +0: [0], +12: [0], +15: [0], +&"type": 0 +} +1:2/0 = 0 +1:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +1:2/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +2:2/0 = 0 +2:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:2/0/metadata/_better_terrain = { +8: [0], +11: [0], +12: [0], +&"type": 0 +} +4:3/0 = 0 +4:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:3/0/metadata/_better_terrain = { +&"type": 0 +} +6:3/0 = 0 +6:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:3/0/metadata/_better_terrain = { +0: [0], +&"type": 0 +} +7:3/0 = 0 +7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:3/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +8:3/0 = 0 +8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:3/0/metadata/_better_terrain = { +0: [0], +4: [0], +8: [0], +&"type": 0 +} +9:3/0 = 0 +9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:3/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +10:3/0 = 0 +10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:3/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +8: [0], +&"type": 0 +} +11:3/0 = 0 +11:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:3/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +12:3/0 = 0 +12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:3/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +13:3/0 = 0 +13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:3/0/metadata/_better_terrain = { +0: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +14:3/0 = 0 +14:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:3/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +15:3/0 = 0 +15:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:3/0/metadata/_better_terrain = { +4: [0], +8: [0], +&"type": 0 +} +0:4/0 = 0 +0:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:4/0/metadata/_better_terrain = { +0: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +1:4/0 = 0 +1:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +1:4/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +2:4/0 = 0 +2:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:4/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:4/0 = 0 +8:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:4/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +10:4/0 = 0 +10:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:4/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +11:4/0 = 0 +11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:4/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +12:4/0 = 0 +12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:4/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +13:4/0 = 0 +13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:4/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +15:4/0 = 0 +15:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:4/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +0:5/0 = 0 +0:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:5/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +2:5/0 = 0 +2:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +4:5/0 = 0 +4:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:5/0/metadata/_better_terrain = { +4: [0], +&"type": 0 +} +6:5/0 = 0 +6:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +&"type": 0 +} +7:5/0 = 0 +7:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +8:5/0 = 0 +8:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:5/0/metadata/_better_terrain = { +0: [0], +4: [0], +7: [0], +8: [0], +12: [0], +&"type": 0 +} +9:5/0 = 0 +9:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:5/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +10:5/0 = 0 +10:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +8: [0], +12: [0], +15: [0], +&"type": 0 +} +11:5/0 = 0 +11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +12:5/0 = 0 +12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +13:5/0 = 0 +13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +14:5/0 = 0 +14:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:5/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +15:5/0 = 0 +15:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:5/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +12: [0], +&"type": 0 +} +0:6/0 = 0 +0:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +1:6/0 = 0 +1:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +1:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +2:6/0 = 0 +2:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +12: [0], +15: [0], +&"type": 0 +} +4:6/0 = 0 +4:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:6/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +6:6/0 = 0 +6:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +7:6/0 = 0 +7:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:6/0 = 0 +8:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:6/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +10:6/0 = 0 +10:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +11:6/0 = 0 +11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +12:6/0 = 0 +12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +13:6/0 = 0 +13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +14:6/0 = 0 +14:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:6/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +15:6/0 = 0 +15:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:6/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +4:7/0 = 0 +4:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:7/0/metadata/_better_terrain = { +0: [0], +4: [0], +12: [0], +&"type": 0 +} +5:7/0 = 0 +5:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:7/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +6:7/0 = 0 +6:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:7/0/metadata/_better_terrain = { +0: [0], +4: [0], +8: [0], +12: [0], +15: [0], +&"type": 0 +} +7:7/0 = 0 +7:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:7/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:7/0 = 0 +8:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:7/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +9:7/0 = 0 +9:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:7/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +10:7/0 = 0 +10:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:7/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +12: [0], +15: [0], +&"type": 0 +} +11:7/0 = 0 +11:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:7/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +12:7/0 = 0 +12:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:7/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +13:7/0 = 0 +13:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:7/0/metadata/_better_terrain = { +0: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +14:7/0 = 0 +14:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:7/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +15:7/0 = 0 +15:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:7/0/metadata/_better_terrain = { +4: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +0:8/0 = 0 +0:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:8/0/metadata/_better_terrain = { +4: [0], +&"type": 0 +} +2:8/0 = 0 +2:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:8/0/metadata/_better_terrain = { +&"type": 0 +} +4:8/0 = 0 +4:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:8/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +6:8/0 = 0 +6:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:8/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +8:8/0 = 0 +8:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:8/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +9:8/0 = 0 +9:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:8/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +10:8/0 = 0 +10:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:8/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +11:8/0 = 0 +11:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:8/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +12:8/0 = 0 +12:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:8/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +13:8/0 = 0 +13:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:8/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +15:8/0 = 0 +15:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:8/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +0:9/0 = 0 +0:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:9/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +4:9/0 = 0 +4:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:9/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +&"type": 0 +} +5:9/0 = 0 +5:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:9/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +6:9/0 = 0 +6:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:9/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +12: [0], +&"type": 0 +} +7:9/0 = 0 +7:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:9/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +8:9/0 = 0 +8:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:9/0/metadata/_better_terrain = { +0: [0], +4: [0], +7: [0], +8: [0], +12: [0], +15: [0], +&"type": 0 +} +9:9/0 = 0 +9:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:9/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +10:9/0 = 0 +10:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:9/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +11:9/0 = 0 +11:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:9/0/metadata/_better_terrain = { +0: [0], +4: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +12:9/0 = 0 +12:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:9/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +13:9/0 = 0 +13:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:9/0/metadata/_better_terrain = { +0: [0], +4: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +14:9/0 = 0 +14:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:9/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +15:9/0 = 0 +15:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:9/0/metadata/_better_terrain = { +4: [0], +8: [0], +12: [0], +&"type": 0 +} +0:10/0 = 0 +0:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:10/0/metadata/_better_terrain = { +12: [0], +&"type": 0 +} +4:10/0 = 0 +4:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:10/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +5:10/0 = 0 +5:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:10/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +6:10/0 = 0 +6:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:10/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +7:10/0 = 0 +7:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:10/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:10/0 = 0 +8:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:10/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +11:10/0 = 0 +11:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:10/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +13:10/0 = 0 +13:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:10/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +15:10/0 = 0 +15:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:10/0/metadata/_better_terrain = { +12: [0], +&"type": 0 +} +4:11/0 = 0 +4:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:11/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +5:11/0 = 0 +5:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:11/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +6:11/0 = 0 +6:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:11/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +7:11/0 = 0 +7:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:11/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:11/0 = 0 +8:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:11/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +10:11/0 = 0 +10:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:11/0/metadata/_better_terrain = { +0: [0], +4: [0], +&"type": 0 +} +11:11/0 = 0 +11:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:11/0/metadata/_better_terrain = { +8: [0], +12: [0], +&"type": 0 +} +13:11/0 = 0 +13:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:11/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +15:11/0 = 0 +15:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:12/0 = 0 +0:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:12/0/metadata/_better_terrain = { +0: [0], +&"type": 0 +} +1:12/0 = 0 +1:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +1:12/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +2:12/0 = 0 +2:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:12/0/metadata/_better_terrain = { +8: [0], +&"type": 0 +} +4:12/0 = 0 +4:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:12/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +5:12/0 = 0 +5:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:12/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +6:12/0 = 0 +6:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:12/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +7:12/0 = 0 +7:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:12/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:12/0 = 0 +8:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:12/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +10:12/0 = 0 +10:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:12/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +13:12/0 = 0 +13:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:12/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +4:13/0 = 0 +4:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:13/0/metadata/_better_terrain = { +0: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +5:13/0 = 0 +5:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:13/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +6:13/0 = 0 +6:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:13/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +7:13/0 = 0 +7:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:13/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:13/0 = 0 +8:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:13/0/metadata/_better_terrain = { +0: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +9:13/0 = 0 +9:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:13/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +10:13/0 = 0 +10:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:13/0/metadata/_better_terrain = { +0: [0], +4: [0], +8: [0], +12: [0], +&"type": 0 +} +11:13/0 = 0 +11:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:13/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +12:13/0 = 0 +12:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +12:13/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +13:13/0 = 0 +13:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:13/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +8: [0], +12: [0], +&"type": 0 +} +14:13/0 = 0 +14:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:13/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +&"type": 0 +} +15:13/0 = 0 +15:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:13/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +&"type": 0 +} +0:14/0 = 0 +0:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +0:14/0/metadata/_better_terrain = { +0: [0], +&"type": 0 +} +1:14/0 = 0 +1:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +1:14/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +2:14/0 = 0 +2:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +2:14/0/metadata/_better_terrain = { +8: [0], +&"type": 0 +} +4:14/0 = 0 +4:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:14/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +6:14/0 = 0 +6:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:14/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +7:14/0 = 0 +7:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:14/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:14/0 = 0 +8:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:14/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +10:14/0 = 0 +10:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:14/0/metadata/_better_terrain = { +4: [0], +12: [0], +&"type": 0 +} +13:14/0 = 0 +13:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:14/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +12: [0], +15: [0], +&"type": 0 +} +14:14/0 = 0 +14:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:14/0/metadata/_better_terrain = { +0: [0], +3: [0], +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +15:14/0 = 0 +15:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:14/0/metadata/_better_terrain = { +4: [0], +7: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +4:15/0 = 0 +4:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +4:15/0/metadata/_better_terrain = { +0: [0], +12: [0], +&"type": 0 +} +5:15/0 = 0 +5:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +5:15/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +6:15/0 = 0 +6:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +6:15/0/metadata/_better_terrain = { +0: [0], +8: [0], +12: [0], +15: [0], +&"type": 0 +} +7:15/0 = 0 +7:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +7:15/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +8:15/0 = 0 +8:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +8:15/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +&"type": 0 +} +9:15/0 = 0 +9:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +9:15/0/metadata/_better_terrain = { +0: [0], +8: [0], +&"type": 0 +} +10:15/0 = 0 +10:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +10:15/0/metadata/_better_terrain = { +0: [0], +8: [0], +12: [0], +&"type": 0 +} +11:15/0 = 0 +11:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +11:15/0/metadata/_better_terrain = { +8: [0], +&"type": 0 +} +13:15/0 = 0 +13:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +13:15/0/metadata/_better_terrain = { +0: [0], +12: [0], +15: [0], +&"type": 0 +} +14:15/0 = 0 +14:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +14:15/0/metadata/_better_terrain = { +0: [0], +8: [0], +11: [0], +12: [0], +15: [0], +&"type": 0 +} +15:15/0 = 0 +15:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) +15:15/0/metadata/_better_terrain = { +8: [0], +11: [0], +12: [0], +&"type": 0 +} + +[sub_resource type="TileSet" id="TileSet_37esa"] +tile_size = Vector2i(32, 32) +physics_layer_0/collision_layer = 1 +terrain_set_0/mode = 0 +terrain_set_0/terrain_0/name = "Terrain 0" +terrain_set_0/terrain_0/color = Color(0.5, 0.34375, 0.25, 1) +sources/0 = SubResource("TileSetAtlasSource_iv3md") +metadata/_better_terrain = { +&"decoration": ["Decoration", Color(0.411765, 0.411765, 0.411765, 1), 3, [], { +&"path": "res://addons/better-terrain/icons/Decoration.svg" +}], +&"terrains": [["New terrain", Color(0.656774, 0.636635, 0.452219, 1), 0, [], { +&"coord": Vector2i(1, 1), +&"source_id": 0 +}]], +&"version": "0.2" +} + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_37esa"] +size = Vector2(69, 72) + +[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_37esa"] + +[node name="Level" type="Node2D" node_paths=PackedStringArray("end_flag", "level_void")] +script = ExtResource("1_u3ux4") +playerScene = ExtResource("3_cbm6k") +end_flag = NodePath("FimFase") +level_void = NodePath("Void") + +[node name="TileMapLayer" type="TileMapLayer" parent="."] +tile_map_data = PackedByteArray("AAAKAA0AAAAGAA4AAAAKAAwAAAAGAAUAAAALAAwAAAAJAAcAAAAMAAwAAAALAAMAAAAMAA0AAAAHAAwAAAANAA0AAAAMAAcAAAAOAA0AAAAGAAsAAAAPAA0AAAABAAEAAAAQAA0AAAAMAAQAAAARAA0AAAAFAAsAAAASAA0AAAABAAEAAAATAA0AAAAHAAoAAAAUAA0AAAALAAQAAAAVAA0AAAAHAA0AAAAWAA0AAAAOAAYAAAAXAA0AAAAHAA0AAAAYAA0AAAAIAAwAAAAYAAwAAAAFAAAAAAAXAAwAAAAMAAMAAAAWAAwAAAAOAA0AAAAVAAwAAAALAAMAAAAUAAwAAAALAAMAAAATAAwAAAAOAAUAAAASAAwAAAABAAAAAAARAAwAAAABAAYAAAAQAAwAAAAOAA0AAAAPAAwAAAAJAAcAAAAOAAwAAAAJAAcAAAANAAwAAAAJAAcAAAALAA0AAAALAAcAAAALAA4AAAAGAA0AAAALAA8AAAAAAAIAAAAMAA8AAAAHAA8AAAANAA8AAAAHAAcAAAAOAA8AAAAKAAkAAAAOAA4AAAAOAA4AAAAPAA4AAAAMAAUAAAAQAA4AAAAAAAQAAAARAA4AAAAMAAkAAAASAA4AAAAFAA0AAAATAA4AAAAKAAkAAAAUAA4AAAABAAIAAAAQAA8AAAANAAEAAAAPAA8AAAAOAA8AAAAMAA4AAAAHAAYAAAANAA4AAAAOAA4AAAAVAA4AAAAOAAcAAAAWAA4AAAABAAIAAAAXAA4AAAABAAIAAAAYAA4AAAACAAIAAAAjAA0AAAAOAA0AAAAkAA0AAAAOAAUAAAAlAA0AAAAMAAMAAAAlAA4AAAAMAAYAAAAmAA4AAAALAAQAAAAmAA8AAAACAAQAAAAmABAAAAAAAAIAAAAnABAAAAAGAA0AAAAnABEAAAAEAAEAAAAoABEAAAAHAA8AAAAoABAAAAAMAAgAAAApABAAAAAAAAQAAAAqABAAAAAKAAkAAAAqAA8AAAALAAgAAAAqAA4AAAAHAA4AAAApAA4AAAAHAAsAAAAoAA4AAAAGAAsAAAAoAA0AAAABAAYAAAAnAA0AAAAHAAUAAAAmAA0AAAABAAYAAAAkAA4AAAACAAQAAAAkAA8AAAAEAAEAAAAlAA8AAAABAAIAAAAnAA4AAAAFAAoAAAAoAA8AAAAHAAwAAAApAA8AAAALAAcAAAAnAA8AAAALAAgAAAAjAA4AAAABAAQAAAAyAA0AAAALAAMAAAAxAA0AAAABAAAAAAAwAA0AAAAMAAAAAAAwAA4AAAAAAAIAAAAxAA4AAAACAAQAAAAyAA4AAAANAAcAAAArABAAAAACAAIAAAArAA8AAAAIAAwAAAArAA4AAAAIAA4AAAArAA0AAAACAAAAAAAqAA0AAAAOAA0AAAA+AA0AAAAMAAMAAAA/AA0AAAAFAAkAAABAAA0AAAACAAAAAABAAA4AAAAPAA8AAAA/AA4AAAAHAAcAAAA+AA4AAAAKAAkAAABFAAgAAAACAAIAAABEAAgAAAAHAAcAAABEAAcAAAAMAAMAAABFAAcAAAAPAA0AAAA6AAUAAAAMAAAAAAA7AAUAAAABAAYAAAA7AAYAAAABAAQAAAA6AAYAAAAEAAEAAAA0AAMAAAAPAA0AAAA0AAQAAAANAAEAAAAzAAQAAAAHAA8AAAAzAAMAAAAMAAMAAAAsAAIAAAAFAAAAAAArAAIAAAAMAAMAAAAqAAIAAAAMAAAAAAAlAAMAAAACAAIAAAAlAAIAAAANAAMAAAAmAAIAAAALAA8AAAAkAAIAAAAOAA0AAAAeAAEAAAAKAAAAAAAdAAEAAAABAAwAAAAWAAEAAAALAA8AAAAVAAEAAAANAAMAAAAUAAEAAAALAAMAAAAVAAIAAAAPAA8AAAATAAEAAAAOAAUAAAASAAEAAAAFAAkAAAARAAEAAAAKAAMAAAAQAAEAAAAMAA0AAAATAAIAAAAHAA8AAAASAAIAAAAHAA8AAAARAAIAAAAEAAEAAAAPAAEAAAAFAA8AAAAOAAEAAAAOAAkAAAANAAEAAAANAAMAAAAMAAEAAAAFAAkAAAALAAEAAAAKAAMAAAAKAAEAAAAGAAMAAAANAAIAAAAPAA8AAAAMAAIAAAAOAAcAAAALAAIAAAAAAAIAAABFAA8AAAAJAAAAAABGAA8AAAAJAAMAAABHAA8AAAAJAA0AAABIAA8AAAAKAAMAAABJAA8AAAAPAA0AAABIABAAAAAEAAEAAABJABAAAAANAAEAAABMAAkAAAAKAAAAAABLAAkAAAABAAwAAAA9AA0AAAAHAAUAAAA9AA4AAAAKAAkAAABLAA4AAAAJAAAAAABMAA4AAAAHAAMAAABNAA4AAAAFAAcAAABOAA4AAAAKAAAAAABQAA0AAAAJAAAAAABRAA0AAAALAA8AAABVAAwAAAALAAsAAABVAAsAAAAKAAsAAABWAAsAAAALAAsAAABWAAoAAAAHAAAAAABSAAgAAAAKAAAAAAAiAA4AAAAKAAkAAAAiAA0AAAAMAAMAAAAhAA0AAAAJAAcAAAAhAA4AAAAHAAcAAAAgAA0AAAAAAAAAAAAxAA8AAAANAA8AAABKAAkAAAAAAA4AAAAsAAMAAAAFAAEAAABDAAcAAAAEAAAAAABDAAgAAAANAA8AAAA8AAUAAAAFAAAAAAA8AAYAAAANAAEAAAAzAA0AAAANAAAAAAAzAA4AAAAPAA8AAAA8AA0AAAAGAAUAAAA8AA4AAAAEAAEAAAAyAAMAAAAAAAAAAAAyAAQAAAAEAAEAAAArAAMAAAAOAAcAAAAqAAMAAAANAA8AAAAjAAMAAAAMAAEAAAAkAAMAAAAJAAkAAAAjAAIAAAAGAAUAAAAcAAEAAAAFAA8AAAAbAAEAAAAGAAMAAABRAAgAAAAAAAwAAAAKAA4AAAANAA8AAAAgAA4AAAANAA8AAAApABEAAAACAAIAAAApAA0AAAAFAAkAAAAyAA8AAAACAAIAAABUAAwAAAAAAAwAAAAUAAIAAAAMAAkAAAA=") +tile_set = SubResource("TileSet_37esa") + +[node name="SpawnPoint" type="Node2D" parent="." groups=["SpawnPoint"]] +visible = false +position = Vector2(361, 328) + +[node name="SpawnPoint4" type="Node2D" parent="." groups=["SpawnPoint"]] +visible = false +position = Vector2(491, 340) + +[node name="SpawnPoint2" type="Node2D" parent="." groups=["SpawnPoint"]] +position = Vector2(762, 337) + +[node name="SpawnPoint3" type="Node2D" parent="." groups=["SpawnPoint"]] +visible = false +position = Vector2(636, 338) + +[node name="Camera" type="Camera2D" parent="."] +unique_name_in_owner = true +visible = false + +[node name="FimFase" type="Area2D" parent="."] +position = Vector2(362, -22) +collision_layer = 0 +collision_mask = 2 + +[node name="Sprite2D" type="Sprite2D" parent="FimFase"] +modulate = Color(0.953388, 0, 0.0774472, 1) +position = Vector2(21, 2) +scale = Vector2(0.527344, 0.292969) +texture = ExtResource("4_37esa") + +[node name="Sprite2D2" type="Sprite2D" parent="FimFase"] +modulate = Color(0.248135, 0.175592, 0.0765821, 1) +position = Vector2(-10, 30) +scale = Vector2(0.0527343, 0.386719) +texture = ExtResource("4_37esa") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="FimFase"] +position = Vector2(20.5, 18) +shape = SubResource("RectangleShape2D_37esa") + +[node name="Void" type="Area2D" parent="."] +position = Vector2(924, 975) +collision_layer = 0 +collision_mask = 2 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Void"] +shape = SubResource("WorldBoundaryShape2D_37esa") diff --git a/Game/Maps/Test1/config.cfg b/Game/Maps/Test1/config.cfg new file mode 100644 index 0000000..0f2fc67 --- /dev/null +++ b/Game/Maps/Test1/config.cfg @@ -0,0 +1,4 @@ +[metadata] +name="Mapa Teste" +preview_texture = "Preview.png" +main_scene = "Test1.tscn" diff --git a/Game/Player/Player.gd b/Game/Player/Player.gd new file mode 100644 index 0000000..2572c6d --- /dev/null +++ b/Game/Player/Player.gd @@ -0,0 +1,37 @@ +extends CharacterBody2D +class_name Player + +const GROUP_NAME : String = "Player" + +const SPEED = 300.0 +const JUMP_VELOCITY = -400.0 + +@onready var multiplayer_synchronizer: MultiplayerSynchronizer = $MultiplayerSynchronizer +@onready var remote_transform_2d: RemoteTransform2D = $RemoteTransform2D +# Get the gravity from the project settings to be synced with RigidBody nodes. +var gravity = ProjectSettings.get_setting("physics/2d/default_gravity") + +func _ready() -> void: + + add_to_group(GROUP_NAME) + +func _physics_process(delta): + if not is_on_floor(): + velocity.y += gravity * delta + + if Input.is_action_just_pressed("ui_accept") and is_on_floor(): + velocity.y = JUMP_VELOCITY + + var direction = Input.get_axis("ui_left", "ui_right") + if direction: + velocity.x = direction * SPEED + else: + velocity.x = move_toward(velocity.x, 0, SPEED) + + move_and_slide() + +func disable_sync() -> void: + multiplayer_synchronizer = null + +func setup_camera(camera: Camera2D) -> void: + remote_transform_2d.remote_path = camera.get_path() diff --git a/Game/Player/Player.gd.uid b/Game/Player/Player.gd.uid new file mode 100644 index 0000000..b07a784 --- /dev/null +++ b/Game/Player/Player.gd.uid @@ -0,0 +1 @@ +uid://cygajtukuw8mq diff --git a/Game/Player/player.tscn b/Game/Player/player.tscn new file mode 100644 index 0000000..183a62b --- /dev/null +++ b/Game/Player/player.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=5 format=3 uid="uid://cexkbcrii6gsh"] + +[ext_resource type="Script" uid="uid://cygajtukuw8mq" path="res://Game/Player/Player.gd" id="1_24elr"] +[ext_resource type="Texture2D" uid="uid://b5644j1scdp4u" path="res://icon.svg" id="2_ygr70"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_cm78x"] +size = Vector2(48, 49) + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_s3asc"] +properties/0/path = NodePath(".:position") +properties/0/spawn = true +properties/0/replication_mode = 1 + +[node name="Player" type="CharacterBody2D"] +collision_layer = 3 +script = ExtResource("1_24elr") + +[node name="Sprite2D" type="Sprite2D" parent="."] +position = Vector2(-1, 0.499998) +scale = Vector2(0.375, 0.367187) +texture = ExtResource("2_ygr70") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(-1, 0.5) +shape = SubResource("RectangleShape2D_cm78x") + +[node name="RemoteTransform2D" type="RemoteTransform2D" parent="."] + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_s3asc") diff --git a/GameManager.gd b/GameManager.gd deleted file mode 100644 index c3ff89d..0000000 --- a/GameManager.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends Node2D -@export var multiplayerScene : PackedScene - -# Called when the node enters the scene tree for the first time. -func _ready(): - $UI.OnStartGame.connect(onStartGame) - pass # Replace with function body. - - -# Called every frame. 'delta' is the elapsed time since the previous frame. -func _process(delta): - pass - -func onStartGame(): - $MultiplayerScene.add_child(multiplayerScene.instantiate()) diff --git a/MainMenu/Authentication/LoginPanel/LoginPanel.gd b/MainMenu/Authentication/LoginPanel/LoginPanel.gd new file mode 100644 index 0000000..53bd89e --- /dev/null +++ b/MainMenu/Authentication/LoginPanel/LoginPanel.gd @@ -0,0 +1,23 @@ +extends PanelContainer + +signal login(email: String, password: String) + +@onready var email_input : LineEdit = %EmailInput +@onready var password_input : LineEdit = %PasswordInput +@onready var show_hide : Button = %ShowHide + +func _on_login_button_pressed() -> void: + + var email : String = email_input.placeholder_text.strip_edges() \ + if email_input.text.is_empty() \ + else email_input.text.strip_edges() + + var password : String = password_input.placeholder_text.strip_edges() \ + if password_input.text.is_empty() \ + else password_input.text.strip_edges() + + login.emit(email, password) + +func _on_show_hide_toggled(toggled_on: bool) -> void: + password_input.secret = toggled_on + show_hide.text = "Show" if toggled_on else "Hide" diff --git a/MainMenu/Authentication/LoginPanel/LoginPanel.gd.uid b/MainMenu/Authentication/LoginPanel/LoginPanel.gd.uid new file mode 100644 index 0000000..ecc843a --- /dev/null +++ b/MainMenu/Authentication/LoginPanel/LoginPanel.gd.uid @@ -0,0 +1 @@ +uid://brhcnk2jvvsf diff --git a/MainMenu/Authentication/LoginPanel/LoginPanel.tscn b/MainMenu/Authentication/LoginPanel/LoginPanel.tscn new file mode 100644 index 0000000..575cb46 --- /dev/null +++ b/MainMenu/Authentication/LoginPanel/LoginPanel.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=2 format=3 uid="uid://dd2ekb3yor3hh"] + +[ext_resource type="Script" uid="uid://brhcnk2jvvsf" path="res://MainMenu/Authentication/LoginPanel/LoginPanel.gd" id="1_vpvtg"] + +[node name="LoginPanel" type="PanelContainer"] +size_flags_horizontal = 4 +size_flags_vertical = 4 +script = ExtResource("1_vpvtg") + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme_override_constants/separation = 15 + +[node name="Title" type="Label" parent="VBox"] +layout_mode = 2 +text = "Login" +horizontal_alignment = 1 + +[node name="Grid" type="GridContainer" parent="VBox"] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_constants/h_separation = 25 +theme_override_constants/v_separation = 15 +columns = 2 + +[node name="Label" type="Label" parent="VBox/Grid"] +layout_mode = 2 +text = "Email" + +[node name="EmailInput" type="LineEdit" parent="VBox/Grid"] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +placeholder_text = "test@gmail.com" + +[node name="Label2" type="Label" parent="VBox/Grid"] +layout_mode = 2 +text = "Password" + +[node name="PasswordField" type="HBoxContainer" parent="VBox/Grid"] +layout_mode = 2 + +[node name="PasswordInput" type="LineEdit" parent="VBox/Grid/PasswordField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "password" +secret = true + +[node name="ShowHide" type="Button" parent="VBox/Grid/PasswordField"] +unique_name_in_owner = true +layout_mode = 2 +toggle_mode = true +button_pressed = true +text = "Show" + +[node name="LoginButton" type="Button" parent="VBox"] +layout_mode = 2 +text = "Submit" + +[connection signal="toggled" from="VBox/Grid/PasswordField/ShowHide" to="." method="_on_show_hide_toggled"] +[connection signal="pressed" from="VBox/LoginButton" to="." method="_on_login_button_pressed"] diff --git a/MainMenu/Authentication/RegisterPanel/RegisterPanel.gd b/MainMenu/Authentication/RegisterPanel/RegisterPanel.gd new file mode 100644 index 0000000..f5f11ff --- /dev/null +++ b/MainMenu/Authentication/RegisterPanel/RegisterPanel.gd @@ -0,0 +1,39 @@ +extends PanelContainer + +signal register_account(username: String, + email: String, + password: String) + +@onready var username_input : LineEdit = %UsernameInput +@onready var email_input : LineEdit = %EmailInput +@onready var password_input : LineEdit = %PasswordInput +@onready var confirm_password_input : LineEdit = %ConfirmPasswordField +@onready var show_hide : Button = %ShowHide + + +func _on_show_hide_toggled(toggled_on: bool) -> void: + password_input.secret = toggled_on + show_hide.text = "Show" if toggled_on else "Hide" + +func _on_register_button_pressed() -> void: + + var username : String = username_input.placeholder_text.strip_edges() \ + if username_input.text.is_empty() \ + else username_input.text.strip_edges() + + var email : String = email_input.placeholder_text.strip_edges() \ + if email_input.text.is_empty() \ + else email_input.text.strip_edges() + + var password : String = password_input.placeholder_text.strip_edges() \ + if password_input.text.is_empty() \ + else password_input.text.strip_edges() + + var confirm_password : String = confirm_password_input.placeholder_text.strip_edges() \ + if confirm_password_input.text.is_empty() \ + else confirm_password_input.text.strip_edges() + + if password != confirm_password: + return + + register_account.emit(username, email, password) diff --git a/MainMenu/Authentication/RegisterPanel/RegisterPanel.gd.uid b/MainMenu/Authentication/RegisterPanel/RegisterPanel.gd.uid new file mode 100644 index 0000000..9e082c5 --- /dev/null +++ b/MainMenu/Authentication/RegisterPanel/RegisterPanel.gd.uid @@ -0,0 +1 @@ +uid://0jxqc0dor0yj diff --git a/MainMenu/Authentication/RegisterPanel/RegisterPanel.tscn b/MainMenu/Authentication/RegisterPanel/RegisterPanel.tscn new file mode 100644 index 0000000..a100f88 --- /dev/null +++ b/MainMenu/Authentication/RegisterPanel/RegisterPanel.tscn @@ -0,0 +1,83 @@ +[gd_scene load_steps=2 format=3 uid="uid://xq36w5vrpfwb"] + +[ext_resource type="Script" uid="uid://0jxqc0dor0yj" path="res://MainMenu/Authentication/RegisterPanel/RegisterPanel.gd" id="1_ulfat"] + +[node name="RegisterPanel" type="PanelContainer"] +size_flags_horizontal = 0 +script = ExtResource("1_ulfat") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 25 + +[node name="Label" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "Register Account" +horizontal_alignment = 1 + +[node name="Grid" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_constants/h_separation = 25 +theme_override_constants/v_separation = 15 +columns = 2 + +[node name="UsernameLabel" type="Label" parent="VBoxContainer/Grid"] +layout_mode = 2 +text = "Username" + +[node name="UsernameInput" type="LineEdit" parent="VBoxContainer/Grid"] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +placeholder_text = "user" + +[node name="EmailLabel" type="Label" parent="VBoxContainer/Grid"] +layout_mode = 2 +text = "Email" + +[node name="EmailInput" type="LineEdit" parent="VBoxContainer/Grid"] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +placeholder_text = "test@gmail.com" + +[node name="PasswordLabel" type="Label" parent="VBoxContainer/Grid"] +layout_mode = 2 +text = "Password" + +[node name="PasswordField" type="HBoxContainer" parent="VBoxContainer/Grid"] +layout_mode = 2 + +[node name="PasswordInput" type="LineEdit" parent="VBoxContainer/Grid/PasswordField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "password" +secret = true + +[node name="ShowHide" type="Button" parent="VBoxContainer/Grid/PasswordField"] +unique_name_in_owner = true +layout_mode = 2 +toggle_mode = true +button_pressed = true +text = "Show" + +[node name="ConfirmPasswordLabel" type="Label" parent="VBoxContainer/Grid"] +layout_mode = 2 +text = "Confirm Password" + +[node name="ConfirmPasswordField" type="LineEdit" parent="VBoxContainer/Grid"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "password" +secret = true + +[node name="RegisterButton" type="Button" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 4 +text = "Register" + +[connection signal="toggled" from="VBoxContainer/Grid/PasswordField/ShowHide" to="." method="_on_show_hide_toggled"] +[connection signal="pressed" from="VBoxContainer/RegisterButton" to="." method="_on_register_button_pressed"] diff --git a/MainMenu/Lobby/Chat/Chat.gd b/MainMenu/Lobby/Chat/Chat.gd new file mode 100644 index 0000000..c639684 --- /dev/null +++ b/MainMenu/Lobby/Chat/Chat.gd @@ -0,0 +1,310 @@ +## Chat system for managing direct messages, group chats, and room chats. +## Handles message sending, receiving, and display for various chat types. +extends PanelContainer + +#region Node References + +@onready var chat_name: LineEdit = %ChatName +@onready var username_container: TabContainer = %UsernameContainer +@onready var chat_text_line_edit: LineEdit = %ChatTextLineEdit + +#endregion + +#region State Variables + +## The currently active chat channel +var current_channel + +## Dictionary mapping tab indices to channel data +var chat_channels := {} + +## Dictionary mapping group IDs to chat names +var group_chats := {} + +## Dictionary mapping usernames to display names +var players_username_display_name := {} + +#endregion + +#region Room Chat + +func _ready() -> void: + NakamaManager.user_logged_in.connect(_user_logged_in) + +func _user_logged_in() -> void: + NakamaManager.channel_message_received.connect(_on_channel_message) + _sub_to_friends_channel() + +## Button callback to join a chat room +func _on_join_chat_room_button_down() -> void: + _create_chat_channel(chat_name.text.strip_edges()) + +## Create and join a new chat room channel +## [br][br] +## [param new_chat_name]: The name of the chat room to create/join +func _create_chat_channel(new_chat_name: String) -> void: + var type = NakamaSocket.ChannelType.Room + current_channel = await NakamaManager.socket.join_chat_async(new_chat_name, + type, + false, + false) + +## Called whenever a new message is received from the server +## Handles displaying the message in the appropriate chat tab +## [br][br] +## [param message]: The channel message received from Nakama +func _on_channel_message(message: NakamaAPI.ApiChannelMessage) -> void: + var content = JSON.parse_string(message.content) + if content.type == 0: + + var chat_id = message.username + var display_name : String = "" + var current_conversation : RichTextLabel = null + + if players_username_display_name.has(chat_id): + + display_name = players_username_display_name[chat_id] + current_conversation = username_container.get_node(display_name) + elif group_chats.has(chat_id): + + # Fetch sender info (single message, so single fetch is acceptable here) + var sender_info_json : NakamaAPI.ApiUsers = \ + await NakamaManager.get_users([message.sender_id]) + + if not sender_info_json.is_exception() and sender_info_json.users and sender_info_json.users.size() > 0: + var curr_user: NakamaAPI.ApiUser = sender_info_json.users[0] + display_name = curr_user.display_name if curr_user.display_name != "" else curr_user.username + else: + display_name = "Unknown" + + current_conversation = username_container.get_node(group_chats[chat_id]) + else: + return + + var new_channel_message: String = "%s: " % [display_name] + + if content.has("party_id"): + new_channel_message += "[url={%s}]%s[/url]\n" % [content.party_id, content.message] + else: + new_channel_message += str(content.message) + "\n" + + current_conversation.text += new_channel_message + # + #elif content.type == 1 && party == null: + # + #channel_message_panel.show() + #party = {"id" : content.partyID} + #channel_message_label.text = str(content.message) + +func _on_submit_chat_button_down(): + + var text : String = chat_text_line_edit.text + + if text.is_empty(): + NotificationContainer.create_notification("Erro! Mensagem vazia!", + NotificationContainer.NotificationType.ERROR) + return + + #var current_username: String = chatChannels[currentChannel.id].channel.self_presence.username + #var current_tab : int = username_container.current_tab + var new_message : String = chat_text_line_edit.text.strip_edges() + + chat_text_line_edit.text = "" + + print("\n\nchatChannels: ", chat_channels) + + var id = current_channel.id + + if not current_channel.group_id.is_empty(): + id = current_channel.group_id + + await NakamaManager.socket.write_chat_message_async(current_channel.id, { + "message" : new_message, + "id" : id, + "type" : 0 + }) + + var new_channel_message: String = "%s: " % [NakamaManager.current_user.display_name] + + new_channel_message += text + "\n" + + username_container.get_child(username_container.current_tab).text += new_channel_message + +func _on_join_group_chat_room_button_down(group_id: String, group_name : String) -> void: + + var type = NakamaSocket.ChannelType.Group + current_channel = await NakamaManager.socket.join_chat_async(group_id, type, true, false) + + if current_channel.is_exception(): + get_parent().invoke_popup("Erro", "Não foi possível entrar nesse chat de grupo") + return + + var group_chat_name : String = "%s's Chat" % [group_name] + + var currentEdit = RichTextLabel.new() + currentEdit.scroll_active = true + currentEdit.fit_content = true + currentEdit.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + currentEdit.bbcode_enabled = true + currentEdit.meta_clicked.connect(group_chat_meta_clicked) + + if username_container.has_node(group_chat_name): + get_parent().invoke_popup("Erro", "Você já está na conversa de grupo") + return + + currentEdit.name = group_chat_name + username_container.add_child(currentEdit) + currentEdit.text = await list_messages(current_channel) + + if not username_container.tab_changed.is_connected(_on_chat_tab_changed): + username_container.tab_changed.connect(_on_chat_tab_changed) + + print("channel id: " + current_channel.id) + #current_channel.group_id + chat_channels[username_container.get_child_count()-1] = { + "channel" : current_channel, + "label" : group_chat_name + } + group_chats[group_id] = group_chat_name + +## Called when user switches between chat tabs +## Updates the current channel reference +## [br][br] +## [param index]: The tab index that was selected +func _on_chat_tab_changed(index: int) -> void: + + if index == 0: + current_channel = null + return + + current_channel = chat_channels[index].channel + +## Load and format chat messages from a channel +## Uses batch fetching to optimize performance +## [br][br] +## [param channel]: The channel to load messages from +## [br]Returns: Formatted BBCode string with all messages +func list_messages(channel): + + var result = \ + await NakamaManager.client.list_channel_messages_async(NakamaManager.session, channel.id, 100, true) + + # Collect all unique sender IDs first + var sender_ids: Array[String] = [] + for message in result.messages: + if message.sender_id and message.sender_id != "" and message.sender_id not in sender_ids: + sender_ids.append(message.sender_id) + + # Fetch all users in ONE batch request + var users_map: Dictionary = {} + if sender_ids.size() > 0: + var sender_info_json: NakamaAPI.ApiUsers = \ + await NakamaManager.client.get_users_async(NakamaManager.session, PackedStringArray(sender_ids)) + + if not sender_info_json.is_exception() and sender_info_json.users: + for user in sender_info_json.users: + var api_user: NakamaAPI.ApiUser = user + users_map[api_user.id] = api_user + + # Build the text with pre-fetched user data + var text = "" + for message in result.messages: + if message.content != "{}": + var content = JSON.parse_string(message.content) + + # Get user info from the map + var display_name = "Unknown" + if message.sender_id in users_map: + var user_info: NakamaAPI.ApiUser = users_map[message.sender_id] + display_name = user_info.display_name if user_info.display_name != "" else user_info.username + + var new_channel_message: String = "%s: " % [display_name] + + if content.has("party_id"): + new_channel_message += "[url={%s}]%s[/url]\n" % [content.party_id, content.message] + else: + new_channel_message += str(content.message) + "\n" + + text += new_channel_message + return text + +func _sub_to_friends_channel(): + var result = await NakamaManager.client.list_friends_async(NakamaManager.session) + + for i in result.friends: + + var type = NakamaSocket.ChannelType.DirectMessage + var channel = await NakamaManager.socket.join_chat_async(i.user.id, type, true, false) + + # Adicionando uma nova tab de conversa entre jogadores + + var currentEdit = RichTextLabel.new() + currentEdit.fit_content = true + currentEdit.scroll_active = true + currentEdit.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + currentEdit.bbcode_enabled = true + currentEdit.meta_clicked.connect(user_chat_meta_clicked) + currentEdit.name = i.user.display_name + + + username_container.add_child(currentEdit) + players_username_display_name[i.user.username] = i.user.display_name + currentEdit.text = await list_messages(channel) + + if not username_container.tab_changed.is_connected(_on_chat_tab_changed): + username_container.tab_changed.connect(_on_chat_tab_changed) + + chat_channels[username_container.get_child_count()-1] = { + "channel" : channel, + "label" : i.user.username + } + +## Button callback to join a direct message chat with a user +func _on_join_direct_chat_button_down(): + var type = NakamaSocket.ChannelType.DirectMessage + var usersResult = await NakamaManager.client.get_users_async(NakamaManager.session, [], [chat_name.text]) + if usersResult.users.size() > 0: + current_channel = await NakamaManager.socket.join_chat_async(usersResult.users[0].id, type, true, false) + + var result = await NakamaManager.client.list_channel_messages_async(NakamaManager.session, current_channel.id, 100, true) + + for message in result.messages: + if(message.content != "{}"): + var content = JSON.parse_string(message.content) + + var new_channel_message: String = "%s: " % [message.username] + + if content.has("party_id"): + new_channel_message += "[url={%s}]%s[/url]\n" % [content.party_id, content.message] + else: + new_channel_message += str(content.message) + "\n" + + chat_text_line_edit.text += new_channel_message + +func invite_friend(id: String, username: String, party_id : String) -> void: + var channel_type = NakamaSocket.ChannelType.DirectMessage + var content = { + "message": "Hey %s, wanna join the party?" % [username], + party_id : party_id, + } + + var user_channel = await NakamaManager.socket.join_chat_async(id, channel_type, true, false) + await NakamaManager.socket.write_chat_message_async(user_channel.id, content) + +#endregion + +#region Event Handlers + +## Called when a clickable link in a group chat message is clicked +func group_chat_meta_clicked(_meta: Variant, _group_id: String) -> void: + pass + +## Called when a clickable link in a user chat message is clicked +func user_chat_meta_clicked(meta: Variant, username: String) -> void: + print("clicked! meta: ", meta, " username: ", username) + +## Called when a clickable username is clicked +func _on_username_meta_clicked(_meta: Variant) -> void: + pass + +#endregion diff --git a/MainMenu/Lobby/Chat/Chat.gd.uid b/MainMenu/Lobby/Chat/Chat.gd.uid new file mode 100644 index 0000000..84ae1a6 --- /dev/null +++ b/MainMenu/Lobby/Chat/Chat.gd.uid @@ -0,0 +1 @@ +uid://c4qs6u7qv8wmf diff --git a/MainMenu/Lobby/Chat/ChatTab.tscn b/MainMenu/Lobby/Chat/ChatTab.tscn new file mode 100644 index 0000000..f638f2c --- /dev/null +++ b/MainMenu/Lobby/Chat/ChatTab.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=2 format=3 uid="uid://ddsbx05whfylu"] + +[ext_resource type="Script" uid="uid://c4qs6u7qv8wmf" path="res://MainMenu/Lobby/Chat/Chat.gd" id="1_8exd6"] + +[node name="Chat" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_8exd6") +metadata/_tab_index = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Chat" type="PanelContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Chat"] +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/Chat/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="UsernameContainer" type="TabContainer" parent="VBoxContainer/Chat/VBoxContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +current_tab = 0 + +[node name="username" type="RichTextLabel" parent="VBoxContainer/Chat/VBoxContainer/ScrollContainer/UsernameContainer"] +layout_mode = 2 +bbcode_enabled = true +fit_content = true +metadata/_tab_index = 0 + +[node name="ChatTextLineEdit" type="LineEdit" parent="VBoxContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Chat Text Here" + +[node name="SubmitChat" type="Button" parent="VBoxContainer/Chat/VBoxContainer"] +layout_mode = 2 +text = ">" + +[connection signal="meta_clicked" from="VBoxContainer/Chat/VBoxContainer/ScrollContainer/UsernameContainer/username" to="." method="_on_username_meta_clicked"] +[connection signal="pressed" from="VBoxContainer/Chat/VBoxContainer/SubmitChat" to="." method="_on_submit_chat_button_down"] diff --git a/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.gd b/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.gd new file mode 100644 index 0000000..bb6e8f2 --- /dev/null +++ b/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.gd @@ -0,0 +1,29 @@ +extends PanelContainer +class_name FriendHBoxContainer + +var callable_arrays : Array[Callable] = [] + +@onready var menu_button: MenuButton = $MenuButton + +func set_friend(p_text: String, + trade_callable: Callable, + chat_callable: Callable, + delete_callable: Callable, + block_callable: Callable, + party_callable) -> void: + + menu_button.text = p_text + + menu_button.get_popup().id_pressed.connect(_popup_menu_pressed) + + callable_arrays.append_array([ + trade_callable, + chat_callable, + block_callable, + delete_callable, + party_callable + ]) + +func _popup_menu_pressed(id: int) -> void: + menu_button.get_popup().hide() + callable_arrays[id].call() diff --git a/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.gd.uid b/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.gd.uid new file mode 100644 index 0000000..85d7d87 --- /dev/null +++ b/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.gd.uid @@ -0,0 +1 @@ +uid://c67ox75qw64tx diff --git a/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.tscn b/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.tscn new file mode 100644 index 0000000..adbd3e5 --- /dev/null +++ b/MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=2 format=3 uid="uid://c25altqf87k2j"] + +[ext_resource type="Script" uid="uid://c67ox75qw64tx" path="res://MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.gd" id="1_xe2aw"] + +[node name="FriendHBoxContainer" type="PanelContainer"] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 8.0 +grow_horizontal = 2 +size_flags_vertical = 0 +script = ExtResource("1_xe2aw") + +[node name="MenuButton" type="MenuButton" parent="."] +layout_mode = 2 +size_flags_vertical = 0 +item_count = 5 +popup/item_0/text = "Trade" +popup/item_0/id = 0 +popup/item_1/text = "Chat" +popup/item_1/id = 1 +popup/item_2/text = "Block" +popup/item_2/id = 2 +popup/item_3/text = "Delete" +popup/item_3/id = 3 +popup/item_4/text = "Invite to Party" +popup/item_4/id = 4 diff --git a/MainMenu/Lobby/Friends/FriendsList/FriendsList.gd b/MainMenu/Lobby/Friends/FriendsList/FriendsList.gd new file mode 100644 index 0000000..6252750 --- /dev/null +++ b/MainMenu/Lobby/Friends/FriendsList/FriendsList.gd @@ -0,0 +1,94 @@ +extends PanelContainer + +signal trade_with(friend_id: String, friend_username: String) +signal chat_with_player(friend_id: String, friend_username: String) +signal invite_friend_to_party(friend_id: String, friend_username: String) + +@export var friends_packed_scene : PackedScene + +@onready var friends_container: VBoxContainer = %FriendsContainer +@onready var add_friend_text: LineEdit = %AddFriendText +@onready var add_friend: Button = %AddFriend + +func _ready() -> void: + NakamaManager.user_logged_in.connect(update_friends_list) + +func update_friends_list() -> void: + var result = await NakamaManager.client.list_friends_async(NakamaManager.session) + + clear_box(friends_container) + + for i in result.friends: + + var friends_hbox : FriendHBoxContainer = friends_packed_scene.instantiate() + + friends_container.add_child(friends_hbox) + friends_hbox.set_friend(i.user.display_name, + _on_trade.bind(i.user.id, i.user.display_name), + _create_chat_player.bind(i.user.id), + remove_friend_by_username.bind(i.user.id, i.user.display_name), + block_friend_by_username.bind(i.user.id, i.user.display_name), + _invite_friend_to_party_by_username.bind(i.user.id, i.user.display_name)) + +func _on_trade(id: String, username: String) -> void: + trade_with.emit(id , username) + +func _create_chat_player(id: String) -> void: + chat_with_player.emit(id) + +func _invite_friend_to_party_by_username(id: String, username: String) -> void: + invite_friend_to_party.emit(id, username) + +func clear_box(box: BoxContainer) -> void: + + for child in box.get_children(): + child.queue_free() + child = null + +func remove_friend_by_username(id: String, username: String) -> void: + + var result = await NakamaManager.client.delete_friends_async(NakamaManager.session,[id]) + + if result.is_exception(): + NotificationContainer.create_notification( + "Error! Não foi possível bloquear esse usuario", + NotificationContainer.NotificationType.ERROR + ) + else: + NotificationContainer.create_notification( + "Usuário bloqueado com sucesso!" % username, + ) + update_friends_list() + + +func block_friend_by_username(id: String, username: String) -> void: + var result = await NakamaManager.client.block_friends_async(NakamaManager.session,[id]) + + if result.is_exception(): + NotificationContainer.create_notification( + "Error! Não foi possível bloquear esse usuario", + NotificationContainer.NotificationType.ERROR + ) + else: + NotificationContainer.create_notification( + "Usuário bloqueado com sucesso!" % username, + ) + update_friends_list() + + +func _on_add_friend_pressed() -> void: + var id = [add_friend_text.text.strip_edges()] + + var result = await NakamaManager.client.add_friends_async(NakamaManager.session, null, id) + + if result.is_exception(): + NotificationContainer.create_notification( + "Error! Não foi possível adicionar esse usuário", + NotificationContainer.NotificationType.ERROR + ) + else: + NotificationContainer.create_notification( + "Usuário %s adicionado com sucesso!" % id, + ) + + update_friends_list() diff --git a/MainMenu/Lobby/Friends/FriendsList/FriendsList.gd.uid b/MainMenu/Lobby/Friends/FriendsList/FriendsList.gd.uid new file mode 100644 index 0000000..a736889 --- /dev/null +++ b/MainMenu/Lobby/Friends/FriendsList/FriendsList.gd.uid @@ -0,0 +1 @@ +uid://bd320xu5xgu7q diff --git a/MainMenu/Lobby/Friends/FriendsList/FriendsList.tscn b/MainMenu/Lobby/Friends/FriendsList/FriendsList.tscn new file mode 100644 index 0000000..d44b4da --- /dev/null +++ b/MainMenu/Lobby/Friends/FriendsList/FriendsList.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=5 format=3 uid="uid://bt0llee0anwkk"] + +[ext_resource type="Script" uid="uid://bd320xu5xgu7q" path="res://MainMenu/Lobby/Friends/FriendsList/FriendsList.gd" id="1_tucqs"] +[ext_resource type="PackedScene" uid="uid://c25altqf87k2j" path="res://MainMenu/Lobby/Friends/FriendHBoxContainer/FriendHBoxContainer.tscn" id="2_rwe42"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tmgb8"] +content_margin_left = 7.0 +content_margin_top = 7.0 +content_margin_right = 7.0 +content_margin_bottom = 7.0 +bg_color = Color(0.315076, 0.398015, 0.42524, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u303v"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.139728, 0.149761, 0.185489, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 + +[node name="FriendsBox" type="PanelContainer"] +custom_minimum_size = Vector2(250, 0) +anchors_preset = 9 +anchor_bottom = 1.0 +offset_right = 166.0 +grow_vertical = 2 +size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_tmgb8") +script = ExtResource("1_tucqs") +friends_packed_scene = ExtResource("2_rwe42") + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="AddFriendText" type="LineEdit" parent="VBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Friend's name" +alignment = 1 + +[node name="Panel" type="PanelContainer" parent="VBox"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_u303v") + +[node name="_" type="VBoxContainer" parent="VBox/Panel"] +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="VBox/Panel/_"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="FriendsContainer" type="VBoxContainer" parent="VBox/Panel/_/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBox/Panel/_"] +layout_mode = 2 +size_flags_vertical = 8 + +[node name="LineEdit" type="LineEdit" parent="VBox/Panel/_/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Add Friend" + +[node name="AddFriend" type="Button" parent="VBox/Panel/_/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "+" + +[connection signal="pressed" from="VBox/Panel/_/HBoxContainer/AddFriend" to="." method="_on_add_friend_pressed"] diff --git a/MainMenu/Lobby/Friends/GroupButton/GroupButton.gd b/MainMenu/Lobby/Friends/GroupButton/GroupButton.gd new file mode 100644 index 0000000..1d43ec9 --- /dev/null +++ b/MainMenu/Lobby/Friends/GroupButton/GroupButton.gd @@ -0,0 +1,27 @@ +extends PanelContainer +class_name GroupButton + +var callable_arrays : Array[Callable] = [] +var group_id: String = "" + +@onready var menu_button: MenuButton = $MenuButton + +func set_group( + p_group_id : String, + p_text: String, + chat_callable: Callable, + leave_callable: Callable) -> void: + + group_id = p_group_id + menu_button.text = p_text + + menu_button.get_popup().id_pressed.connect(_popup_menu_pressed) + + callable_arrays.append_array([ + chat_callable, + leave_callable + ]) + +func _popup_menu_pressed(id: int) -> void: + menu_button.get_popup().hide() + callable_arrays[id].call(id) diff --git a/MainMenu/Lobby/Friends/GroupButton/GroupButton.gd.uid b/MainMenu/Lobby/Friends/GroupButton/GroupButton.gd.uid new file mode 100644 index 0000000..5c57387 --- /dev/null +++ b/MainMenu/Lobby/Friends/GroupButton/GroupButton.gd.uid @@ -0,0 +1 @@ +uid://dvysvcse67r1b diff --git a/MainMenu/Lobby/Friends/GroupButton/GroupButton.tscn b/MainMenu/Lobby/Friends/GroupButton/GroupButton.tscn new file mode 100644 index 0000000..c52b3fd --- /dev/null +++ b/MainMenu/Lobby/Friends/GroupButton/GroupButton.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=2 format=3 uid="uid://ka3pmbwfs0ei"] + +[ext_resource type="Script" uid="uid://dvysvcse67r1b" path="res://MainMenu/Lobby/Friends/GroupButton/GroupButton.gd" id="1_xbub6"] + +[node name="GroupButton" type="PanelContainer"] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 8.0 +grow_horizontal = 2 +size_flags_vertical = 0 +script = ExtResource("1_xbub6") + +[node name="MenuButton" type="MenuButton" parent="."] +layout_mode = 2 +size_flags_vertical = 0 +item_count = 2 +popup/item_0/text = "Chat" +popup/item_0/id = 0 +popup/item_1/text = "Leave" +popup/item_1/id = 1 diff --git a/MainMenu/Lobby/Friends/GroupsList/GroupsList.gd b/MainMenu/Lobby/Friends/GroupsList/GroupsList.gd new file mode 100644 index 0000000..e395f4a --- /dev/null +++ b/MainMenu/Lobby/Friends/GroupsList/GroupsList.gd @@ -0,0 +1,45 @@ +extends PanelContainer + +signal player_wants_to_chat_with_group(id: String) + +@export var groups_packed_scene : PackedScene + +@onready var search_group: LineEdit = %SearchGroup +@onready var add_group_line_edit: LineEdit = %AddGroupLineEdit +@onready var add_group: Button = %AddGroup +@onready var groups_container: VBoxContainer = %GroupsContainer + +func _ready() -> void: + NakamaManager.user_logged_in.connect(update_friends_list) + +func update_friends_list() -> void: + clear_box(groups_container) + var result_group_information = await NakamaManager.client.list_groups_async(NakamaManager.session, search_group.text, 1) + + if result_group_information.groups.size() == 0: + return + + #selected_group_name.text = "Group name: %s" % group.name + #selected_group_description.text = "Group description: %s" % group.description + #selected_group_id.text = "Group ID: %s" % group.id + #selected_group_is_open.text = "Is open: %s" % str(group.open) + + for i in result_group_information.groups: + + var group_button_instance = groups_packed_scene.instantiate() + + groups_container.add_child(group_button_instance) + group_button_instance.set_group(i.id, i.name, chat_with_group, leave_group) + +func clear_box(box: BoxContainer) -> void: + + for child in box.get_children(): + child.queue_free() + child = null + +func leave_group(id: String) -> void: + NakamaManager.leave_group(id) + update_friends_list() + +func chat_with_group(id: String) -> void: + player_wants_to_chat_with_group.emit(id) diff --git a/MainMenu/Lobby/Friends/GroupsList/GroupsList.gd.uid b/MainMenu/Lobby/Friends/GroupsList/GroupsList.gd.uid new file mode 100644 index 0000000..ade8b3e --- /dev/null +++ b/MainMenu/Lobby/Friends/GroupsList/GroupsList.gd.uid @@ -0,0 +1 @@ +uid://bno2pwxdom87v diff --git a/MainMenu/Lobby/Friends/GroupsList/GroupsList.tscn b/MainMenu/Lobby/Friends/GroupsList/GroupsList.tscn new file mode 100644 index 0000000..553ef50 --- /dev/null +++ b/MainMenu/Lobby/Friends/GroupsList/GroupsList.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=5 format=3 uid="uid://dfq85wky0ibpc"] + +[ext_resource type="Script" uid="uid://bno2pwxdom87v" path="res://MainMenu/Lobby/Friends/GroupsList/GroupsList.gd" id="1_okhtm"] +[ext_resource type="PackedScene" uid="uid://ka3pmbwfs0ei" path="res://MainMenu/Lobby/Friends/GroupButton/GroupButton.tscn" id="2_ve4n6"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tmgb8"] +content_margin_left = 7.0 +content_margin_top = 7.0 +content_margin_right = 7.0 +content_margin_bottom = 7.0 +bg_color = Color(0.315076, 0.398015, 0.42524, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u303v"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.139728, 0.149761, 0.185489, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 + +[node name="Groups" type="PanelContainer"] +custom_minimum_size = Vector2(250, 0) +anchors_preset = 9 +anchor_bottom = 1.0 +offset_right = 166.0 +grow_vertical = 2 +size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_tmgb8") +script = ExtResource("1_okhtm") +groups_packed_scene = ExtResource("2_ve4n6") + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="SearchGroup" type="LineEdit" parent="VBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Group's name" +alignment = 1 + +[node name="Panel" type="PanelContainer" parent="VBox"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_u303v") + +[node name="_" type="VBoxContainer" parent="VBox/Panel"] +layout_mode = 2 + +[node name="_" type="ScrollContainer" parent="VBox/Panel/_"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="GroupsContainer" type="VBoxContainer" parent="VBox/Panel/_/_"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="AddGroup" type="HBoxContainer" parent="VBox/Panel/_"] +layout_mode = 2 + +[node name="AddGroupLineEdit" type="LineEdit" parent="VBox/Panel/_/AddGroup"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Add group" + +[node name="AddGroup" type="Button" parent="VBox/Panel/_/AddGroup"] +unique_name_in_owner = true +layout_mode = 2 +text = "+" + +[connection signal="pressed" from="VBox/Panel/_/AddGroup/AddGroup" to="." method="_on_add_friend_pressed"] diff --git a/MainMenu/Lobby/Leaderboard/Leaderboard.tscn b/MainMenu/Lobby/Leaderboard/Leaderboard.tscn new file mode 100644 index 0000000..82ab655 --- /dev/null +++ b/MainMenu/Lobby/Leaderboard/Leaderboard.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=3 format=3 uid="uid://cu2rcrnq0w6n5"] + +[ext_resource type="Script" uid="uid://mayyjvfg1lla" path="res://MainMenu/Lobby/Leaderboard/leaderboard.gd" id="1_y7qe5"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i8gjh"] +bg_color = Color(0.00566115, 0.0056611523, 0.005661145, 1) +expand_margin_left = 15.0 +expand_margin_top = 15.0 +expand_margin_right = 15.0 +expand_margin_bottom = 15.0 + +[node name="Leaderboard" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_y7qe5") +metadata/_tab_index = 7 + +[node name="_" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Title" type="Label" parent="_"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Placar" + +[node name="Options" type="HBoxContainer" parent="_"] +layout_mode = 2 + +[node name="MapSelectorLabel" type="Label" parent="_/Options"] +layout_mode = 2 +text = "Selecione o mapa" + +[node name="MapSelectorOptionButton" type="OptionButton" parent="_/Options"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="UsertTypeLabel" type="Label" parent="_/Options"] +layout_mode = 2 +text = "Tipo de usuários" + +[node name="UserTypeOptionButton" type="OptionButton" parent="_/Options"] +unique_name_in_owner = true +layout_mode = 2 +selected = 0 +item_count = 3 +popup/item_0/text = "Todos" +popup/item_0/id = 0 +popup/item_1/text = "Amigos" +popup/item_1/id = 1 +popup/item_2/text = "Grupos" +popup/item_2/id = 2 + +[node name="UpdateLeaderboard" type="Button" parent="_/Options"] +layout_mode = 2 +size_flags_horizontal = 10 +text = "Atualizar Placar" + +[node name="Scoring" type="ScrollContainer" parent="_"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_i8gjh") + +[node name="ScoringLabel" type="Label" parent="_/Scoring"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[connection signal="pressed" from="_/Options/UpdateLeaderboard" to="." method="_on_update_leaderboard_pressed"] diff --git a/MainMenu/Lobby/Leaderboard/leaderboard.gd b/MainMenu/Lobby/Leaderboard/leaderboard.gd new file mode 100644 index 0000000..78bf77a --- /dev/null +++ b/MainMenu/Lobby/Leaderboard/leaderboard.gd @@ -0,0 +1,18 @@ +extends PanelContainer + +@onready var map_selector_option_button: OptionButton = %MapSelectorOptionButton +@onready var user_type_option_button: OptionButton = %UserTypeOptionButton +@onready var scoring_label: Label = %ScoringLabel + +func _ready() -> void: + NakamaManager.user_logged_in.connect(_user_logged_in) + +func _user_logged_in() -> void: + + var score = 1 + var subscore = 0 + var metadata = { "map": "space_station" } + var record : NakamaAPI.ApiLeaderboardRecord = await NakamaManager.write_leaderboard("weekly_imposter_wins", score, subscore, metadata) + +func _on_update_leaderboard_pressed() -> void: + pass # Replace with function body. diff --git a/MainMenu/Lobby/Leaderboard/leaderboard.gd.uid b/MainMenu/Lobby/Leaderboard/leaderboard.gd.uid new file mode 100644 index 0000000..564290a --- /dev/null +++ b/MainMenu/Lobby/Leaderboard/leaderboard.gd.uid @@ -0,0 +1 @@ +uid://mayyjvfg1lla diff --git a/MainMenu/Lobby/MatchFinder/MapIcon.gd b/MainMenu/Lobby/MatchFinder/MapIcon.gd new file mode 100644 index 0000000..cd1ecf9 --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MapIcon.gd @@ -0,0 +1,19 @@ +extends PanelContainer +class_name MapIcon + +signal map_selected() + +@onready var map_title: Label = %MapTitle +@onready var map_preview: TextureButton = %MapPreview + +func configure(title: String, texture: Texture) -> void: + + if title and not title.is_empty(): + map_title.text = title + + if texture: + map_preview.texture_normal = texture + + +func _on_button_pressed() -> void: + map_selected.emit() diff --git a/MainMenu/Lobby/MatchFinder/MapIcon.gd.uid b/MainMenu/Lobby/MatchFinder/MapIcon.gd.uid new file mode 100644 index 0000000..83dd98b --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MapIcon.gd.uid @@ -0,0 +1 @@ +uid://cmmwnemc3r6nd diff --git a/MainMenu/Lobby/MatchFinder/MapIcon.tscn b/MainMenu/Lobby/MatchFinder/MapIcon.tscn new file mode 100644 index 0000000..6459af7 --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MapIcon.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=2 format=3 uid="uid://hvsilsjx00kd"] + +[ext_resource type="Script" uid="uid://cmmwnemc3r6nd" path="res://MainMenu/Lobby/MatchFinder/MapIcon.gd" id="1_k3yrh"] + +[node name="MapIcon" type="PanelContainer"] +custom_minimum_size = Vector2(200, 0) +script = ExtResource("1_k3yrh") + +[node name="_" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="MapTitle" type="Label" parent="_"] +unique_name_in_owner = true +layout_mode = 2 +text = "NOT DEFINED" +horizontal_alignment = 1 + +[node name="MapPreview" type="TextureButton" parent="_"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +stretch_mode = 5 + +[connection signal="pressed" from="_/MapPreview" to="." method="_on_button_pressed"] diff --git a/MainMenu/Lobby/MatchFinder/MapSelector.gd b/MainMenu/Lobby/MatchFinder/MapSelector.gd new file mode 100644 index 0000000..e366e38 --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MapSelector.gd @@ -0,0 +1,75 @@ +extends ScrollContainer + +signal map_selected(map_name: String, map_scene: String) + +const CONFIG_FILE_NAME = "config.cfg" + +@onready var maps: HBoxContainer = %Maps +@export var map_icon_scene : PackedScene +@export_dir var maps_directory : String + +func _ready() -> void: + + var maps_dir = DirAccess.open(maps_directory) + if maps_dir: + maps_dir.list_dir_begin() + var file_name = maps_dir.get_next() + while file_name != "": + if maps_dir.current_is_dir(): + + var current_path : String = "%s/%s" % [maps_directory, file_name] + + var config_file = ConfigFile.new() + var err = config_file.load("%s/%s" % [current_path, CONFIG_FILE_NAME]) + + if err != OK: + printerr("Nao existe config file para esse mapa!") + file_name = maps_dir.get_next() + continue + + var map_config = _get_map_config_options(config_file, current_path) + if map_config.is_empty(): + file_name = maps_dir.get_next() + continue + + var map_icon_instance : MapIcon = map_icon_scene.instantiate() + + maps.add_child(map_icon_instance) + map_icon_instance.configure.call_deferred(map_config.map_name, map_config.map_preview_texture) + + map_icon_instance.map_selected.connect(_map_selected.bind(map_config.map_name, map_config.map_scene)) + + + file_name = maps_dir.get_next() + else: + print("An error occurred when trying to access the path.") + + +func _map_selected(map_name: String, map_scene: String) -> void: + map_selected.emit(map_name, map_scene) + +func _get_map_config_options(config_file: ConfigFile, current_path: String) -> Dictionary: + + var map_config : Dictionary + + var map_name : String = config_file.get_value("metadata", "name") + var map_preview_texture : String = config_file.get_value("metadata", "preview_texture") + var map_scene : String = config_file.get_value("metadata", "main_scene", "") + + if map_scene.is_empty(): + printerr("Nao existe \"main_scene\" configurado para esse mapa!") + return {} + + var map_preview_texture_resource : Texture = null + var map_preview_full_path = "%s/%s" % [current_path, map_preview_texture] + if ResourceLoader.exists(map_preview_full_path): + map_preview_texture_resource = load(map_preview_full_path) + + map_config = { + "map_name" : map_name, + "map_preview_texture" : map_preview_texture_resource, + "map_scene" : "%s/%s" % [current_path, map_scene] + } + + return map_config + diff --git a/MainMenu/Lobby/MatchFinder/MapSelector.gd.uid b/MainMenu/Lobby/MatchFinder/MapSelector.gd.uid new file mode 100644 index 0000000..2ac1dc9 --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MapSelector.gd.uid @@ -0,0 +1 @@ +uid://d0tydf1aeocjc diff --git a/MainMenu/Lobby/MatchFinder/MapSelector.tscn b/MainMenu/Lobby/MatchFinder/MapSelector.tscn new file mode 100644 index 0000000..4e68232 --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MapSelector.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=3 format=3 uid="uid://lowp636hjckg"] + +[ext_resource type="Script" uid="uid://d0tydf1aeocjc" path="res://MainMenu/Lobby/MatchFinder/MapSelector.gd" id="1_0jqs4"] +[ext_resource type="PackedScene" uid="uid://hvsilsjx00kd" path="res://MainMenu/Lobby/MatchFinder/MapIcon.tscn" id="2_3t4f3"] + +[node name="MapSelector" type="ScrollContainer"] +custom_minimum_size = Vector2(200, 200) +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 200.0 +grow_horizontal = 2 +script = ExtResource("1_0jqs4") +map_icon_scene = ExtResource("2_3t4f3") +maps_directory = "res://Game/Maps" + +[node name="Maps" type="HBoxContainer" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 diff --git a/MainMenu/Lobby/MatchFinder/MatchFinder.gd b/MainMenu/Lobby/MatchFinder/MatchFinder.gd new file mode 100644 index 0000000..f5c2cf4 --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MatchFinder.gd @@ -0,0 +1,260 @@ +extends PanelContainer + +signal start_game() + +@export var notification_container : NotificationContainer + +@onready var matchmaking_finder: VBoxContainer = %MatchmakingFinder +@onready var match_name_field: LineEdit = %MatchNameField +@onready var matchmaking_button: Button = %MatchmakingButton +@onready var your_match_info: VBoxContainer = %YourMatchInfo +@onready var match_name_label: Label = %MatchNameLabel +@onready var match_id_label: Label = %MatchIDLabel +@onready var matchmaking_players: VBoxContainer = %MatchmakingPlayers +@onready var select_match_label: Label = %SelectMatchLabel +@onready var map_selector: ScrollContainer = %MapSelector +@onready var map_selection_vbox: VBoxContainer = %MapSelectionVbox +@onready var start_game_button: Button = %StartGameButton + +@onready var how_many_players_confirmed: Label = %HowManyPlayersConfirmed +@onready var confirmation_progress_bar: ProgressBar = %ConfirmationProgressBar + +var num_players : int = 0 +var confirmed_players : int = 0 + +var players : Dictionary[int, bool] = {} + +var createdMatch : NakamaRTAPI.Match +var matchmakingTicket + +var match_scene_selected : String = "" + +func _ready() -> void: + your_match_info.visible = false + matchmaking_finder.visible = true + + NakamaManager.peer_connnected_in_match.connect(_peer_connected) + NakamaManager.peer_disconnnected_in_match.connect(_peer_connected) + +func _update_players_confirmed() -> void: + + how_many_players_confirmed.text = "%d/%d" % [confirmed_players, num_players] + confirmation_progress_bar.value = confirmed_players + confirmation_progress_bar.max_value = num_players + +func _on_join_create_match_button_down(): + + var match_name_query : String = match_name_field.text.strip_edges() + + NakamaManager.multiplayerBridge.join_named_match(match_name_query) + + createdMatch = await NakamaManager.socket.create_match_async(match_name_query) + + if createdMatch.is_exception(): + notification_container.create_notification(tr("Failed to create match ") + str(createdMatch.match_id), + NotificationContainer.NotificationType.ERROR) + return + + notification_container.create_notification(tr("Created match :") + str(createdMatch.match_id), + NotificationContainer.NotificationType.OK) + + if multiplayer.is_server(): + num_players = 1 + _update_players_confirmed() + map_selection_vbox.visible = true + start_game_button.disabled = true + + else: + map_selection_vbox.visible = false + start_game_button.disabled = false + + _show_match_found(match_name_query) + +func _show_match_found(match_name_query: String) -> void: + your_match_info.visible = true + matchmaking_finder.visible = false + + match_name_field.text = "" + + match_name_label.text = "Match name: %s" % match_name_query + match_id_label.text = "Match id: %s" % createdMatch.match_id + + if not NakamaManager.socket.received_match_presence.is_connected(_match_status_updated): + NakamaManager.socket.received_match_presence.connect(_match_status_updated) + + _add_player_to_current_matchmaking(NakamaManager.current_user.display_name) + +func _peer_connected(id: int) -> void: + + if not multiplayer.is_server(): + return + + var players_usernames : Array[String] = [] + matchmaking_players.get_children().map(func(a): players_usernames.append(a.name)) + _update_num_players.rpc(NakamaManager.Players.size() - 1) + _update_current_players.rpc_id(id, players_usernames) + +@rpc("any_peer", "call_local") +func _update_num_players(p_num_players: int) -> void: + num_players = p_num_players + _update_players_confirmed() + +func _peer_disconnected(user, _id: int) -> void: + if not multiplayer.is_server(): + return + + _remover_player_from_list.rpc(user.display_name) + +@rpc("any_peer", "call_local") +func _remover_player_from_list(display_name: String) -> void: + + for player in matchmaking_players.get_children(): + if player.name == display_name: + player.queue_free() + player = null + +@rpc("any_peer", "reliable", "call_local") +func _update_current_players(usernames: Array[String]) -> void: + _update_players_confirmed() + for username in usernames: + _add_player_to_current_matchmaking(username) + +func _match_status_updated(param) -> void: + + if not param.joins.is_empty(): + var user_ids : Array = [] + param.joins.map(func(a): user_ids.append(a.user_id)) + var query = await NakamaManager.client.get_users_async(NakamaManager.session, user_ids) + + for user in query.users: + + _add_player_to_current_matchmaking(user.display_name) + + if not param.leaves.is_empty(): + for user_presence in param.leaves: + + var player_label_node = matchmaking_players.get_node_or_null(user_presence.username) + if not player_label_node: + continue + + player_label_node.queue_free() + player_label_node = null + + #print("username %s has left" % user_presence.username) + +func _add_player_to_current_matchmaking(display_name: String) -> void: + + if matchmaking_players.has_node(display_name): + return + + var label : Label = Label.new() + label.name = display_name + label.text = display_name + + matchmaking_players.add_child(label) + +func _on_matchmaking_button_down(): + + if matchmakingTicket: + + var removed : NakamaAsyncResult = await NakamaManager.socket.remove_matchmaker_async(matchmakingTicket.ticket) + + if removed.is_exception(): + notification_container.create_notification(tr("Falha ao deletar ticket de matchmaking") + str(matchmakingTicket.ticket), + NotificationContainer.NotificationType.ERROR) + return + + notification_container.create_notification(tr("Ticket removido!"), + NotificationContainer.NotificationType.OK) + + matchmaking_button.text = tr("Start Matchmaking") + matchmakingTicket = null + else: + + #var query = "+properties.region:US +properties.rank:>=4 +properties.rank:<=10" + #var stringP = {"region" : "US"} + #var numberP = { "rank": 6} + var query = "*" + var min_count = 2 + var max_count = 4 + + matchmakingTicket = await NakamaManager.socket.add_matchmaker_async(query,min_count, max_count) + + if matchmakingTicket.is_exception(): + notification_container.create_notification(tr("failed to matchmake : ") + str(matchmakingTicket.ticket), + NotificationContainer.NotificationType.ERROR) + return + + notification_container.create_notification(tr("match ticket number : ") + str(matchmakingTicket.ticket), + NotificationContainer.NotificationType.WARNING) + + if not NakamaManager.socket.received_matchmaker_matched.is_connected(onMatchMakerMatched): + NakamaManager.socket.received_matchmaker_matched.connect(onMatchMakerMatched) + + matchmaking_button.text = tr("Stop Matchmaking") + +func onMatchMakerMatched(matched : NakamaRTAPI.MatchmakerMatched): + var joinedMatch = await NakamaManager.socket.join_matched_async(matched) + createdMatch = joinedMatch + + notification_container.create_notification(tr("Partida encontrada!"), + NotificationContainer.NotificationType.OK) + _show_match_found("nome da partida") + +func _on_close_quit_match_pressed() -> void: + if not createdMatch: + return + + var result = await NakamaManager.socket.leave_match_async(createdMatch.match_id) + + if result.is_exception(): + notification_container.create_notification("Não foi possível sair da partida", + NotificationContainer.NotificationType.ERROR ) + + notification_container.create_notification("Você saiu da partida com sucesso", + NotificationContainer.NotificationType.OK ) + + your_match_info.visible = false + matchmaking_finder.visible = true + + if start_game_button.pressed.is_connected(_on_start_game): + start_game_button.pressed.disconnect(_on_start_game) + +func _on_start_game() -> void: + + if multiplayer.is_server(): + start_game.emit() + else: + Ready.rpc() + + +@rpc("any_peer", "call_local") +func Ready(): + + var id = multiplayer.get_remote_sender_id() + + if players.has(id): + var player_confirmed = players[id] + players[id] = not player_confirmed + else: + players[id] = true + + confirmed_players = 0 + for i in players: + if players[i]: + confirmed_players += 1 + + if multiplayer.is_server(): + start_game_button.disabled = confirmed_players != num_players + + _update_players_confirmed() + +func _on_map_selector_map_selected(map_name: String, map_scene: String) -> void: + select_match_label.text = "Selected map: %s" % [map_name] + match_scene_selected = map_scene + +@rpc("any_peer","call_local") +func reset_confirmation() -> void: + confirmed_players = 0 + _update_players_confirmed() + start_game_button.button_pressed = false diff --git a/MainMenu/Lobby/MatchFinder/MatchFinder.gd.uid b/MainMenu/Lobby/MatchFinder/MatchFinder.gd.uid new file mode 100644 index 0000000..1ba624c --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MatchFinder.gd.uid @@ -0,0 +1 @@ +uid://cigwov7t2y0qe diff --git a/MainMenu/Lobby/MatchFinder/MatchFinder.tscn b/MainMenu/Lobby/MatchFinder/MatchFinder.tscn new file mode 100644 index 0000000..11ce7a4 --- /dev/null +++ b/MainMenu/Lobby/MatchFinder/MatchFinder.tscn @@ -0,0 +1,144 @@ +[gd_scene load_steps=4 format=3 uid="uid://ctlppxshfqr7i"] + +[ext_resource type="Script" uid="uid://cigwov7t2y0qe" path="res://MainMenu/Lobby/MatchFinder/MatchFinder.gd" id="1_ub7sj"] +[ext_resource type="PackedScene" uid="uid://lowp636hjckg" path="res://MainMenu/Lobby/MatchFinder/MapSelector.tscn" id="2_ii3i2"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bsanu"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.051971, 0.051971, 0.0519709, 1) + +[node name="Match Finder" type="PanelContainer"] +custom_minimum_size = Vector2(350, 0) +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 0 +script = ExtResource("1_ub7sj") +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="MatchmakingFinder" type="VBoxContainer" parent="VBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="Title" type="Label" parent="VBoxContainer/MatchmakingFinder"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Join/Create Match" + +[node name="MatchField" type="HBoxContainer" parent="VBoxContainer/MatchmakingFinder"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/MatchmakingFinder/MatchField"] +layout_mode = 2 +text = "Match name" + +[node name="MatchNameField" type="LineEdit" parent="VBoxContainer/MatchmakingFinder/MatchField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="JoinCreateMatch" type="Button" parent="VBoxContainer/MatchmakingFinder"] +layout_mode = 2 +text = "Join/Create" + +[node name="Title2" type="Label" parent="VBoxContainer/MatchmakingFinder"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Find Match" + +[node name="MatchmakingButton" type="Button" parent="VBoxContainer/MatchmakingFinder"] +unique_name_in_owner = true +layout_mode = 2 +text = "Start Matchmaking" + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="YourMatchInfo" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MatchInfoLabel" type="Label" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Your match" + +[node name="MatchNameLabel" type="Label" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +unique_name_in_owner = true +layout_mode = 2 +text = "Match name: %s" + +[node name="MatchIDLabel" type="Label" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +unique_name_in_owner = true +layout_mode = 2 +text = "Match name: %s" + +[node name="PlayersListLabel" type="Label" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Players" + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +custom_minimum_size = Vector2(0, 250) +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_bsanu") + +[node name="MatchmakingPlayers" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/YourMatchInfo/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MapSelectionVbox" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="SelectMatchLabel" type="Label" parent="VBoxContainer/ScrollContainer/YourMatchInfo/MapSelectionVbox"] +unique_name_in_owner = true +layout_mode = 2 +text = "Please select a map..." + +[node name="MapSelector" parent="VBoxContainer/ScrollContainer/YourMatchInfo/MapSelectionVbox" instance=ExtResource("2_ii3i2")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HowManyPlayersConfirmed" type="Label" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +unique_name_in_owner = true +layout_mode = 2 +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ConfirmationProgressBar" type="ProgressBar" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 25) +layout_mode = 2 +show_percentage = false + +[node name="StartGameButton" type="Button" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +unique_name_in_owner = true +layout_mode = 2 +text = "Start Game" + +[node name="Close_Quit" type="Button" parent="VBoxContainer/ScrollContainer/YourMatchInfo"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Close/Quit Match" + +[connection signal="pressed" from="VBoxContainer/MatchmakingFinder/JoinCreateMatch" to="." method="_on_join_create_match_button_down"] +[connection signal="pressed" from="VBoxContainer/MatchmakingFinder/MatchmakingButton" to="." method="_on_matchmaking_button_down"] +[connection signal="map_selected" from="VBoxContainer/ScrollContainer/YourMatchInfo/MapSelectionVbox/MapSelector" to="." method="_on_map_selector_map_selected"] +[connection signal="pressed" from="VBoxContainer/ScrollContainer/YourMatchInfo/StartGameButton" to="." method="_on_start_game"] +[connection signal="pressed" from="VBoxContainer/ScrollContainer/YourMatchInfo/Close_Quit" to="." method="_on_close_quit_match_pressed"] diff --git a/MainMenu/Lobby/Notifications/NotificationBubble.gd b/MainMenu/Lobby/Notifications/NotificationBubble.gd new file mode 100644 index 0000000..1ba35bc --- /dev/null +++ b/MainMenu/Lobby/Notifications/NotificationBubble.gd @@ -0,0 +1,113 @@ +extends PanelContainer +class_name NotificationBubble + +## Notification codes from Nakama server +enum NotificationCode { + RESERVED = 0, + MESSAGE_RECEIVED = -1, ## Message received from user X while offline or not in channel + FRIEND_REQUEST = -2, ## User X wants to add you as a friend + FRIEND_ACCEPTED = -3, ## User X accepted your friend invite + GROUP_ACCEPTED = -4, ## You've been accepted to X group + GROUP_JOIN_REQUEST = -5, ## User X wants to join your group + FRIEND_ONLINE = -6, ## Your friend X has just joined the game + SOCKET_CLOSED = -7, ## Final notifications to sockets closed via the single_socket configuration + BANNED = -8 ## You've been banned +} + +signal delete_notification(notification_id) + +@onready var subject: Label = %Subject +@onready var timestamp: Label = %Timestamp +@onready var notification_type: Label = %NotificationType + +var notification_id: String = "" +var formatted_time = null # TimeUtils.FormattedTime + +func setup(notif: NakamaAPI.ApiNotification, users_map: Dictionary = {}) -> void: + notification_id = notif.id + + # Parse and format timestamp + formatted_time = TimeUtils.parse_nakama_timestamp(notif.create_time) + if formatted_time: + timestamp.text = formatted_time.get_relative_time() + # Set tooltip to show full datetime on hover + timestamp.tooltip_text = formatted_time.get_full_datetime() + else: + timestamp.text = notif.create_time + timestamp.tooltip_text = notif.create_time + + notification_type.text = _get_notification_type_text(notif.code) + + # Get sender display name from users_map (already fetched in batch) + if notif.sender_id and notif.sender_id != "": + _set_sender_display_name(notif.sender_id, notif.subject, users_map) + else: + # Server notification (no sender) + subject.text = notif.subject + +## Set the sender's display name using pre-fetched user data +func _set_sender_display_name(sender_id: String, fallback_subject: String, users_map: Dictionary) -> void: + if sender_id in users_map: + var sender_user: NakamaAPI.ApiUser = users_map[sender_id] + var display_name = sender_user.display_name if sender_user.display_name != "" else sender_user.username + + # Update the subject to include the display name + subject.text = fallback_subject.replace(sender_user.username, display_name) + else: + # Fallback if user not found in map + subject.text = fallback_subject + +## Get a human-readable notification type text +func _get_notification_type_text(code: int) -> String: + match code: + NotificationCode.MESSAGE_RECEIVED: + return "📨 Message Received" + NotificationCode.FRIEND_REQUEST: + return "👤 Friend Request" + NotificationCode.FRIEND_ACCEPTED: + return "✅ Friend Accepted" + NotificationCode.GROUP_ACCEPTED: + return "🎉 Group Accepted" + NotificationCode.GROUP_JOIN_REQUEST: + return "👥 Group Join Request" + NotificationCode.FRIEND_ONLINE: + return "🟢 Friend Online" + NotificationCode.SOCKET_CLOSED: + return "🔌 Socket Closed" + NotificationCode.BANNED: + return "🚫 Banned" + _: + return "📢 Notification (Code: %d)" % code + +func _on_delete_pressed() -> void: + delete_notification.emit(notification_id) + +## Update the timestamp display format +## Available formats: +## - "relative": "5 minutes ago" (default) +## - "full": "Oct 20, 2025 11:59 PM" +## - "short": "Oct 20, 2025" +## - "time": "11:59 PM" +## - "iso": "2025-10-20T23:59:20Z" +## - custom: Use custom format string (e.g., "%Y-%m-%d %H:%M") +func set_timestamp_format(format: String = "relative") -> void: + if not formatted_time: + return + + match format: + "relative": + timestamp.text = formatted_time.get_relative_time() + "full": + timestamp.text = formatted_time.get_full_datetime() + "short": + timestamp.text = formatted_time.get_short_date() + "time": + timestamp.text = formatted_time.get_time_only() + "iso": + timestamp.text = formatted_time.get_iso_format() + _: + # Custom format + timestamp.text = formatted_time.get_custom_format(format) + + # Always keep the tooltip showing the full datetime + timestamp.tooltip_text = formatted_time.get_full_datetime() diff --git a/MainMenu/Lobby/Notifications/NotificationBubble.gd.uid b/MainMenu/Lobby/Notifications/NotificationBubble.gd.uid new file mode 100644 index 0000000..db440b3 --- /dev/null +++ b/MainMenu/Lobby/Notifications/NotificationBubble.gd.uid @@ -0,0 +1 @@ +uid://di2wu5vgh2rya diff --git a/MainMenu/Lobby/Notifications/NotificationBubble.tscn b/MainMenu/Lobby/Notifications/NotificationBubble.tscn new file mode 100644 index 0000000..dbee37f --- /dev/null +++ b/MainMenu/Lobby/Notifications/NotificationBubble.tscn @@ -0,0 +1,43 @@ +[gd_scene load_steps=3 format=3 uid="uid://bq6b5e7e6whsj"] + +[ext_resource type="Script" uid="uid://di2wu5vgh2rya" path="res://MainMenu/Lobby/Notifications/NotificationBubble.gd" id="1_obb8g"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_obb8g"] +bg_color = Color(0.11489687, 0.1148968, 0.11489678, 1) + +[node name="NotificationBubble" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_obb8g") +script = ExtResource("1_obb8g") + +[node name="_" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="NotificationInformation" type="VBoxContainer" parent="_"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Subject" type="Label" parent="_/NotificationInformation"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Timestamp" type="Label" parent="_/NotificationInformation"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 10 +mouse_filter = 0 + +[node name="NotificationType" type="Label" parent="_/NotificationInformation"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Delete" type="Button" parent="_"] +layout_mode = 2 +text = "Deletar" + +[connection signal="pressed" from="_/Delete" to="." method="_on_delete_pressed"] diff --git a/MainMenu/Lobby/Notifications/Notifications.gd b/MainMenu/Lobby/Notifications/Notifications.gd new file mode 100644 index 0000000..c226575 --- /dev/null +++ b/MainMenu/Lobby/Notifications/Notifications.gd @@ -0,0 +1,136 @@ +extends PanelContainer + +@export var notification_bubble_scene : PackedScene + +@onready var notifications_v_container: VBoxContainer = %NotificationsVContainer + +## The cacheable cursor for pagination +var cacheable_cursor: String = "" + +## Track loaded notification IDs to avoid duplicates +var loaded_notification_ids: Array[String] = [] + +func _ready() -> void: + NakamaManager.user_logged_in.connect(_user_logged_in) + +func _user_logged_in() -> void: + # Load notifications when the panel is ready + await load_notifications() + + # Listen for real-time notifications through the NakamaManager proxy + if NakamaManager.is_socket_connected(): + NakamaManager.notification_received.connect(_on_notification_received) + +## Load notifications from the server with pagination support +func load_notifications(limit: int = 100) -> void: + var cursor_value = cacheable_cursor if cacheable_cursor != "" else "" + var result: NakamaAPI.ApiNotificationList = await NakamaManager.list_notifications(limit, cursor_value) + + if result.is_exception(): + print("Failed to load notifications: %s" % result) + return + + # Collect all unique sender IDs first + var sender_ids: Array[String] = [] + for notif in result.notifications: + if notif.sender_id and notif.sender_id != "" and notif.sender_id not in sender_ids: + sender_ids.append(notif.sender_id) + + # Fetch all users in ONE batch request + var users_map: Dictionary = {} + if sender_ids.size() > 0: + var users_result = await NakamaManager.get_users(PackedStringArray(sender_ids)) + if not users_result.is_exception() and users_result.users: + for user in users_result.users: + var api_user: NakamaAPI.ApiUser = user + users_map[api_user.id] = api_user + + # Process the notifications (in reverse order to show newest first) + for i in range(result.notifications.size() - 1, -1, -1): + var notif = result.notifications[i] + _add_notification_to_ui(notif, users_map) + + # Update the cursor for pagination + if result.cacheable_cursor: + cacheable_cursor = result.cacheable_cursor + + #print("Loaded %d notifications" % result.notifications.size()) + +## Load more notifications (for infinite scroll or "Load More" button) +func load_more_notifications(limit: int = 100) -> void: + if cacheable_cursor == "": + print("No more notifications to load") + return + + await load_notifications(limit) + +## Handle real-time notification received while connected +func _on_notification_received(notif: NakamaAPI.ApiNotification) -> void: + print("Received notification: %s - %s" % [notif.subject, notif.content]) + # For real-time notifications, fetch user individually (only one user) + var users_map: Dictionary = {} + if notif.sender_id and notif.sender_id != "": + var users_result = await NakamaManager.get_users([notif.sender_id]) + if not users_result.is_exception() and users_result.users and users_result.users.size() > 0: + var api_user: NakamaAPI.ApiUser = users_result.users[0] + users_map[api_user.id] = api_user + _add_notification_to_ui(notif, users_map) + +## Add a notification to the UI +func _add_notification_to_ui(notif: NakamaAPI.ApiNotification, users_map: Dictionary = {}) -> void: + # Avoid duplicate notifications + if notif.id in loaded_notification_ids: + return + + loaded_notification_ids.append(notif.id) + + _create_notification_item(notif, users_map) + +## Create a notification UI element +## Override this function to customize how notifications are displayed +func _create_notification_item(notif: NakamaAPI.ApiNotification, users_map: Dictionary = {}) -> void: + + var notification_bubble_instance : NotificationBubble = notification_bubble_scene.instantiate() + + #delete_button.pressed.connect(_on_delete_notification.bind(notif.id, panel)) + notifications_v_container.add_child(notification_bubble_instance) + + notification_bubble_instance.setup(notif, users_map) + notification_bubble_instance.delete_notification.connect(_on_delete_notification.bind(notification_bubble_instance)) + + +## Delete a notification +func _on_delete_notification(notification_id: String, ui_element: Control) -> void: + var delete_result: NakamaAsyncResult = await NakamaManager.delete_notifications([notification_id]) + + if delete_result.is_exception(): + print("Failed to delete notification: %s" % delete_result) + return + + notifications_v_container.remove_child(ui_element) + ui_element.queue_free() + + # Remove from loaded IDs + loaded_notification_ids.erase(notification_id) + + print("Notification deleted successfully") + +## Clear all notifications (UI only, doesn't delete from server) +func clear_ui() -> void: + for child in notifications_v_container.get_children(): + child.queue_free() + loaded_notification_ids.clear() + +## Delete all notifications from server and clear UI +func delete_all_notifications() -> void: + if loaded_notification_ids.is_empty(): + return + + var delete_result: NakamaAsyncResult = await NakamaManager.delete_notifications(PackedStringArray(loaded_notification_ids)) + + if delete_result.is_exception(): + print("Failed to delete all notifications: %s" % delete_result) + return + + clear_ui() + print("All notifications deleted successfully") diff --git a/MainMenu/Lobby/Notifications/Notifications.gd.uid b/MainMenu/Lobby/Notifications/Notifications.gd.uid new file mode 100644 index 0000000..5416e9d --- /dev/null +++ b/MainMenu/Lobby/Notifications/Notifications.gd.uid @@ -0,0 +1 @@ +uid://dye86sse5fh3c diff --git a/MainMenu/Lobby/Notifications/Notifications.tscn b/MainMenu/Lobby/Notifications/Notifications.tscn new file mode 100644 index 0000000..10405d6 --- /dev/null +++ b/MainMenu/Lobby/Notifications/Notifications.tscn @@ -0,0 +1,41 @@ +[gd_scene load_steps=3 format=3 uid="uid://wm1fd83jgjwp"] + +[ext_resource type="Script" uid="uid://dye86sse5fh3c" path="res://MainMenu/Lobby/Notifications/Notifications.gd" id="1_mcsnq"] +[ext_resource type="PackedScene" uid="uid://bq6b5e7e6whsj" path="res://MainMenu/Lobby/Notifications/NotificationBubble.tscn" id="2_glg2d"] + +[node name="Notifications" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_mcsnq") +notification_bubble_scene = ExtResource("2_glg2d") +metadata/_tab_index = 7 + +[node name="_" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="TopBar" type="HBoxContainer" parent="_"] +layout_mode = 2 + +[node name="Title" type="Label" parent="_/TopBar"] +layout_mode = 2 +text = "Notifications" + +[node name="DeleteNotifications" type="Button" parent="_/TopBar"] +layout_mode = 2 +size_flags_horizontal = 10 +text = "Deletar Notificações" + +[node name="ScrollContainer" type="ScrollContainer" parent="_"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="NotificationsVContainer" type="VBoxContainer" parent="_/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[connection signal="pressed" from="_/TopBar/DeleteNotifications" to="." method="delete_all_notifications"] diff --git a/MainMenu/Lobby/Party/PartyScreen.gd b/MainMenu/Lobby/Party/PartyScreen.gd new file mode 100644 index 0000000..bb4f076 --- /dev/null +++ b/MainMenu/Lobby/Party/PartyScreen.gd @@ -0,0 +1,29 @@ +extends PanelContainer + +var party : NakamaRTAPI.Party = null + +@onready var party_creator: PanelContainer = %PartyCreator +@onready var party_is_open_check_box: CheckBox = %PartyIsOpenCheckBox +@onready var party_information: PanelContainer = %PartyInformation +@onready var party_id_line_edit: LineEdit = %PartyIDLineEdit + +func _ready() -> void: + toggle_party_sections_visibility() + +func _on_create_party_button_pressed() -> void: + party = await NakamaManager.create_party(party_is_open_check_box.button_pressed) + toggle_party_sections_visibility() + +func create_party() -> void: + party = await NakamaManager.create_party(party_is_open_check_box.button_pressed) + toggle_party_sections_visibility() + +func toggle_party_sections_visibility() -> void: + party_creator.visible = party == null + party_information.visible = party != null + + if party_information.visible: + update_party_information() + +func update_party_information() -> void: + party_id_line_edit.text = party.party_id diff --git a/MainMenu/Lobby/Party/PartyScreen.gd.uid b/MainMenu/Lobby/Party/PartyScreen.gd.uid new file mode 100644 index 0000000..9643fb9 --- /dev/null +++ b/MainMenu/Lobby/Party/PartyScreen.gd.uid @@ -0,0 +1 @@ +uid://cfqnrcgo6yhft diff --git a/MainMenu/Lobby/Party/PartyScreen.tscn b/MainMenu/Lobby/Party/PartyScreen.tscn new file mode 100644 index 0000000..afd7023 --- /dev/null +++ b/MainMenu/Lobby/Party/PartyScreen.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=2 format=3 uid="uid://bdw5y4b5om737"] + +[ext_resource type="Script" uid="uid://cfqnrcgo6yhft" path="res://MainMenu/Lobby/Party/PartyScreen.gd" id="1_743ye"] + +[node name="PartyScreen" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_743ye") +metadata/_tab_index = 5 + +[node name="_" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="PartyCreator" type="PanelContainer" parent="_"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="_/PartyCreator"] +layout_mode = 2 + +[node name="Title" type="Label" parent="_/PartyCreator/_"] +layout_mode = 2 +theme_override_font_sizes/font_size = 48 +text = "Create Party" + +[node name="PartyIsOpenCheckBox" type="CheckBox" parent="_/PartyCreator/_"] +unique_name_in_owner = true +layout_mode = 2 +text = " Party is open" + +[node name="CreatePartyButton" type="Button" parent="_/PartyCreator/_"] +layout_mode = 2 +text = "Create Party" + +[node name="PartyInformation" type="PanelContainer" parent="_"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="_/PartyInformation"] +layout_mode = 2 + +[node name="Title" type="Label" parent="_/PartyInformation/_"] +layout_mode = 2 +theme_override_font_sizes/font_size = 48 +text = "Your Party" + +[node name="PartyIDField" type="HBoxContainer" parent="_/PartyInformation/_"] +layout_mode = 2 + +[node name="Label" type="Label" parent="_/PartyInformation/_/PartyIDField"] +layout_mode = 2 +text = "Party ID: " + +[node name="PartyIDLineEdit" type="LineEdit" parent="_/PartyInformation/_/PartyIDField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +editable = false + +[connection signal="pressed" from="_/PartyCreator/_/PartyIsOpenCheckBox" to="." method="_on_party_is_open_check_box_pressed"] +[connection signal="pressed" from="_/PartyCreator/_/CreatePartyButton" to="." method="_on_create_party_button_pressed"] diff --git a/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.gd b/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.gd new file mode 100644 index 0000000..184ff49 --- /dev/null +++ b/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.gd @@ -0,0 +1,41 @@ +extends PanelContainer + +signal chat_with_group(id: String) +signal chat_with_friend(id: String, username: String) +signal invite_friend_to_party(id: String, username: String) +signal invite_friend_to_trade(id: String, username: String) + +@onready var username = %UserAccountText +@onready var display_name = %DisplayNameText +@onready var email = %EmailText + +@onready var grid = $VBox/Grid +@onready var button = $VBox/HBox/Button + +func update_user_info(user: NakamaAPI.ApiUser, + p_email: String = "") -> void: + username.text = user.username + display_name.text = user.display_name + email.text = p_email + +func _on_button_pressed() -> void: + grid.visible = not grid.visible + button.text = "Hide" if grid.visible else "Show" + +func _on_copy_user_pressed() -> void: + DisplayServer.clipboard_set(username.text) + +func _on_copy_email_pressed() -> void: + DisplayServer.clipboard_set(email.text) + +func _on_groups_player_wants_to_chat_with_group(id: String) -> void: + chat_with_group.emit(id) + +func _on_friends_chat_with_player(friend_id: String, friend_username: String) -> void: + chat_with_friend.emit(friend_id, friend_username) + +func _on_friends_invite_friend_to_party(friend_id: String, friend_username: String) -> void: + invite_friend_to_party.emit(friend_id, friend_username) + +func _on_friends_trade_with(friend_id: String, friend_username: String) -> void: + invite_friend_to_trade.emit(friend_id, friend_username) diff --git a/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.gd.uid b/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.gd.uid new file mode 100644 index 0000000..667cfe4 --- /dev/null +++ b/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.gd.uid @@ -0,0 +1 @@ +uid://c3sqlq6m122n3 diff --git a/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.tscn b/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.tscn new file mode 100644 index 0000000..fa62b41 --- /dev/null +++ b/MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.tscn @@ -0,0 +1,115 @@ +[gd_scene load_steps=8 format=3 uid="uid://cvedwg86rsyp2"] + +[ext_resource type="Script" uid="uid://c3sqlq6m122n3" path="res://MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.gd" id="1_gldpw"] +[ext_resource type="StyleBox" uid="uid://dqy54jim8vxkk" path="res://Styles/DarkBackgroundLabel.tres" id="1_k82p0"] +[ext_resource type="PackedScene" uid="uid://bt0llee0anwkk" path="res://MainMenu/Lobby/Friends/FriendsList/FriendsList.tscn" id="3_b2vub"] +[ext_resource type="PackedScene" uid="uid://dfq85wky0ibpc" path="res://MainMenu/Lobby/Friends/GroupsList/GroupsList.tscn" id="4_om642"] + +[sub_resource type="Theme" id="Theme_1nxb0"] +Label/font_sizes/font_size = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pcwbd"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.172297, 0.265428, 0.259591, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 +shadow_size = 7 + +[sub_resource type="LabelSettings" id="LabelSettings_j2bj2"] +font_size = 18 + +[node name="UserInformationDisplay" type="PanelContainer"] +anchors_preset = 9 +anchor_bottom = 1.0 +offset_right = 357.0 +grow_vertical = 2 +size_flags_vertical = 3 +theme = SubResource("Theme_1nxb0") +theme_override_styles/panel = SubResource("StyleBoxFlat_pcwbd") +script = ExtResource("1_gldpw") + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 15 + +[node name="HBox" type="HBoxContainer" parent="VBox"] +layout_mode = 2 +theme_override_constants/separation = 25 + +[node name="DisplayNameText" type="Label" parent="VBox/HBox"] +unique_name_in_owner = true +layout_mode = 2 +text = "Informações do jogador" +label_settings = SubResource("LabelSettings_j2bj2") + +[node name="Button" type="Button" parent="VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 10 +text = "Hide" + +[node name="Grid" type="GridContainer" parent="VBox"] +layout_mode = 2 +theme_override_constants/h_separation = 25 +theme_override_constants/v_separation = 5 +columns = 3 + +[node name="UserAccountLabel" type="Label" parent="VBox/Grid"] +custom_minimum_size = Vector2(75, 0) +layout_mode = 2 +text = "Username" + +[node name="UserAccountText" type="LineEdit" parent="VBox/Grid"] +unique_name_in_owner = true +custom_minimum_size = Vector2(155, 0) +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/normal = ExtResource("1_k82p0") +editable = false + +[node name="CopyUser" type="Button" parent="VBox/Grid"] +layout_mode = 2 +text = "Copy" + +[node name="EmailLabel" type="Label" parent="VBox/Grid"] +layout_mode = 2 +text = "Email" + +[node name="EmailText" type="LineEdit" parent="VBox/Grid"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/normal = ExtResource("1_k82p0") +editable = false + +[node name="CopyEmail" type="Button" parent="VBox/Grid"] +layout_mode = 2 +text = "Copy" + +[node name="Tabs" type="TabContainer" parent="VBox"] +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 1 + +[node name="Friends" parent="VBox/Tabs" instance=ExtResource("3_b2vub")] +unique_name_in_owner = true +visible = false +layout_mode = 2 +size_flags_vertical = 3 +metadata/_tab_index = 0 + +[node name="Groups" parent="VBox/Tabs" instance=ExtResource("4_om642")] +layout_mode = 2 +metadata/_tab_index = 1 + +[connection signal="pressed" from="VBox/HBox/Button" to="." method="_on_button_pressed"] +[connection signal="pressed" from="VBox/Grid/CopyUser" to="." method="_on_copy_user_pressed"] +[connection signal="pressed" from="VBox/Grid/CopyEmail" to="." method="_on_copy_email_pressed"] +[connection signal="chat_with_player" from="VBox/Tabs/Friends" to="." method="_on_friends_chat_with_player"] +[connection signal="invite_friend_to_party" from="VBox/Tabs/Friends" to="." method="_on_friends_invite_friend_to_party"] +[connection signal="trade_with" from="VBox/Tabs/Friends" to="." method="_on_friends_trade_with"] +[connection signal="player_wants_to_chat_with_group" from="VBox/Tabs/Groups" to="." method="_on_groups_player_wants_to_chat_with_group"] diff --git a/MainMenu/MainMenu.gd b/MainMenu/MainMenu.gd new file mode 100644 index 0000000..ea4406f --- /dev/null +++ b/MainMenu/MainMenu.gd @@ -0,0 +1,708 @@ +## Classe do Lobby Multiplayer que eu fiz do Nakama +extends Control +class_name NakamaMultiplayer + +enum UserState { + SuperAdmin, + Admin, + Member, + JoinRequest +} +var selectedGroup +var selectedGroupState : UserState + + + +var available_groups_to_current_user : Array +var available_users_in_current_group : Array + +signal OnStartGame() + +@onready var lobby_container : TabContainer = %LobbyContainer +@onready var authentication : CenterContainer = $Authentication +@onready var chat_tab: PanelContainer = %ChatTab + +@onready var group_name : LineEdit = %GroupName +@onready var group_desc : LineEdit = %GroupDesc +@onready var group_query : LineEdit = %GroupQuery +@onready var groups_query_vbox : VBoxContainer = %GroupsQueryVBox +@onready var trade_vbox1 : VBoxContainer = %TradeVbox1 +@onready var trade_vbox2 : VBoxContainer = %TradeVBox2 +@onready var popup : Window = $Popup +@onready var join_group_popup : Window = $JoinGroupChat +@onready var party: PanelContainer = %Party + +@onready var user_information_display : PanelContainer = %UserInformationDisplay +@onready var group_listing_slider: HSlider = %GroupListingSlider +@onready var group_listing_selected_label: Label = %GroupListingSelectedLabel +@onready var groups_available_to_user: OptionButton = %GroupsAvailableToUser +@onready var group_users_option_button: OptionButton = %GroupUsers +@onready var group_member_status: Label = %GroupMemberStatus +@onready var joined_member_vbox: VBoxContainer = %JoinedMemberVbox +@onready var accept_join_request: Button = %AcceptJoinRequest +@onready var pending_to_join_group_label: Label = %PendingToJoinGroupLabel +@onready var pending_to_join_section: VBoxContainer = %PendingToJoinSection +@onready var close_open_group: CheckBox = %CloseOpenGroup +@onready var delete_group: Button = %DeleteGroup + +@onready var collection_line_edit: LineEdit = %CollectionLineEdit +@onready var key_line_edit: LineEdit = %KeyLineEdit +@onready var data_from_store_label: Label = %DataFromStoreLabel + +@onready var match_finder: PanelContainer = %"Match Finder" + +func _ready(): + + popup.visible = false + pending_to_join_section.visible = false + + NakamaManager.start_client() + + lobby_container.visible = false + authentication.visible = true + user_information_display.visible = false + + var args = OS.get_cmdline_args() + print("args: ", args) + match_finder.start_game.connect(_on_start_game) + + if args.size() <= 2: + return + + var current_size = DisplayServer.window_get_size() + + # Calculate the new size by dividing the current width and height by 2 + var new_size = Vector2i(current_size.x / 2, current_size.y / 2) + + # Set the new window size + DisplayServer.window_set_size(new_size) + + _on_login_pressed(args[2].strip_edges(), args[3].strip_edges()) + +#region Login/Register +func updateUserInfo(username: String, + displayname : String, + avaterurl : String = "", + language : String = "en", + location : String = "us", + timezone : String = "est"): + await NakamaManager.update_account(username, + displayname, + avaterurl, + language, + location, + timezone) + + +func _on_register_account_pressed(username: String, + email: String, + password: String) -> void: + + await NakamaManager.register(email, password, username) + + connect_user_to_lobby(email) + +func _on_login_pressed(email: String, password: String) -> void: + + await NakamaManager.login(email, password) + + connect_user_to_lobby(email) + +func connect_user_to_lobby(email: String = "") -> void: + + var user = NakamaManager.current_user + + lobby_container.visible = true + authentication.visible = false + + user_information_display.update_user_info(user, email) + user_information_display.visible = true + + #updateUserInfo("test", "testDisplay") + + #var account = await NakamaManager.client.get_account_async(NakamaManager.session) + # + #$Panel/UserAccountText.text = account.user.username + #$Panel/DisplayNameText.text = account.user.display_name + #update_friends_list() +#endregion + +#region NakamaStorage + +@rpc("any_peer") +func sendData(message): + print(message) + +func _on_store_data_button_down(): + + if collection_line_edit.text.is_empty(): + NotificationContainer.create_notification("Collection line edit vazio!", NotificationContainer.NotificationType.ERROR ) + return + + if key_line_edit.text.is_empty(): + NotificationContainer.create_notification("Key line edit vazio!", NotificationContainer.NotificationType.ERROR ) + return + + var saveGame = { + "name" : "username", + "items" : [{ + "id" : 1, + "name" : "gun", + "ammo" : 10 + }, + { + "id" : 2, + "name" : "sword", + "ammo" : 0 + }], + "level" : 10 + } + var data = JSON.stringify(saveGame) + + var can_read = 1 + var can_write = 1 + + var result = await NakamaManager.client.write_storage_objects_async(NakamaManager.session, [ + NakamaWriteStorageObject.new(collection_line_edit.text.strip_edges(), key_line_edit.text.strip_edges(), can_read, can_write, data , "") + ]) + + if result.is_exception(): + NotificationContainer.create_notification("error %s" % str(result), NotificationContainer.NotificationType.ERROR ) + else: + NotificationContainer.create_notification("Objeto armazenado no banco de dados com sucesso!", NotificationContainer.NotificationType.OK) + + +func _on_get_data_button_down(): + + if collection_line_edit.text.is_empty(): + NotificationContainer.create_notification("Collection line edit vazio!", NotificationContainer.NotificationType.ERROR ) + return + + if key_line_edit.text.is_empty(): + NotificationContainer.create_notification("Key line edit vazio!", NotificationContainer.NotificationType.ERROR ) + return + + var result = await NakamaManager.client.read_storage_objects_async(NakamaManager.session, [ + NakamaStorageObjectId.new(collection_line_edit.text.strip_edges(), key_line_edit.text.strip_edges(), NakamaManager.session.user_id) + ]) + + if result.is_exception(): + NotificationContainer.create_notification("error %s" % str(result), NotificationContainer.NotificationType.ERROR ) + return + + data_from_store_label.text = "" + + for i in result.objects: + data_from_store_label.text += i.value + "\n" + + + +func _on_list_data_button_down(): + + if collection_line_edit.text.is_empty(): + NotificationContainer.create_notification("Collection line edit vazio!", NotificationContainer.NotificationType.ERROR ) + return + + data_from_store_label.text = "" + + var dataList = await NakamaManager.client.list_storage_objects_async(NakamaManager.session, collection_line_edit.text.strip_edges() ,NakamaManager.session.user_id, 5) + for i in dataList.objects: + data_from_store_label.text += str(i) + "\n" + +#endregion + +#region Friends + +func _on_create_group_button_down(): + var group = await NakamaManager.client.create_group_async(NakamaManager.session, group_name.text, group_desc.text, "" , "en", true, 32) + print(group) + + + #print("users in group " + group_name2.text + i.user.username) + + selectedGroup = group + +func _on_start_game(): + _start_for_everyone.rpc() + +@rpc("any_peer","call_local") +func _start_for_everyone() -> void: + OnStartGame.emit() + hide() + + +#endregion + +#region Group +func _on_add_user_to_group_button_down(group): + var result = await NakamaManager.client.join_group_async(NakamaManager.session, group.id) + + if result.is_exception(): + NotificationContainer.create_notification( + tr("Erro! Não foi possível entrar nesse grupo"), + NotificationContainer.NotificationType.ERROR + ) + + else: + NotificationContainer.create_notification( + tr("Solicitação para entrar no grupo \"%s\" enviada!" % [group.name]), + NotificationContainer.NotificationType.OK + ) + +func _update_close_group_text(toggled: bool) -> void: + + if toggled: + close_open_group.text = "Open Group" + else: + close_open_group.text = "Close Group" + +func _on_delete_group_pressed() -> void: + pass # Replace with function body. + +func _on_add_user_to_group_2_button_down(): + + if selectedGroup == null or (selectedGroup and not "id" in selectedGroup): + NotificationContainer.create_notification("Nenhum grupo foi selecionado", NotificationContainer.NotificationType.ERROR ) + return + + var users = await NakamaManager.client.list_group_users_async(NakamaManager.session,selectedGroup.id, UserState.JoinRequest) + + for user in users.group_users: + var u = user.user as NakamaAPI.ApiUser + await NakamaManager.client.add_group_users_async(NakamaManager.session, selectedGroup.id, [u.id]) + +#func _on_check_button_toggled(toggled_on): +# await NakamaManager.client.update_group_async(NakamaManager.session, selectedGroup.id, "Strong Gamers", "we are the strong gamers!", null, "en", toggled_on) +# pass # Replace with function body. + +func _on_list_groups_button_down(): + var limit = group_listing_slider.value + var result = await NakamaManager.client.list_groups_async(NakamaManager.session, group_query.text.strip_edges(), limit, null, null, null) + + print("\ngroups: ", result.groups,"\n") + + if result.groups.size() == 0: + NotificationContainer.create_notification("O grupo \"%s\" não existe!" % group_query.text.strip_edges(), NotificationContainer.NotificationType.ERROR ) + return + + removeMyChildren(groups_query_vbox) + + for group in result.groups: + + var vbox = VBoxContainer.new() + var hbox = HBoxContainer.new() + + var namelabel = Label.new() + + namelabel.text = group.name + hbox.add_child(namelabel) + + var button = Button.new() + button.button_down.connect(_on_add_user_to_group_button_down.bind(group)) + button.text = tr("Join group") + + hbox.add_child(button) + vbox.add_child(hbox) + + groups_query_vbox.add_child(vbox) + +func match_ended(_match_winner_id: int) -> void: + match_finder.reset_confirmation.rpc() + +func get_selected_user(): + var selected_user_idx = group_users_option_button.selected + var selected_user_id = available_users_in_current_group[selected_user_idx].user.id + + var result : NakamaAPI.ApiUsers = await NakamaManager.client.get_users_async(NakamaManager.session, [selected_user_id],[], null) + + return result + +func _on_promote_user_button_down(): + + var result = await get_selected_user() + var user_has_been_promoted = true + + for u in result.users: + var promote_result = await NakamaManager.client.promote_group_users_async(NakamaManager.session, selectedGroup.id, [u.id]) + + if promote_result.is_exception(): + NotificationContainer.create_notification(promote_result.exception.message, + NotificationContainer.NotificationType.ERROR) + user_has_been_promoted = false + + if user_has_been_promoted: + NotificationContainer.create_notification("User successfully promoted", + NotificationContainer.NotificationType.OK) + + +func _on_demote_user_button_down(): + var result = await get_selected_user() + + var user_has_been_demoted = true + + for u in result.users: + var demote_result = await NakamaManager.client.demote_group_users_async(NakamaManager.session, selectedGroup.id, [u.id]) + + if demote_result.is_exception(): + NotificationContainer.create_notification(demote_result.exception.message, + NotificationContainer.NotificationType.ERROR) + user_has_been_demoted = false + + if user_has_been_demoted: + NotificationContainer.create_notification("User successfully demoted", + NotificationContainer.NotificationType.OK) + +func _on_kick_user_button_down(): + + var result = await get_selected_user() + + var user_has_been_kicked = true + + for u in result.users: + var kick_result = await NakamaManager.client.kick_group_users_async(NakamaManager.session, selectedGroup.id, [u.id]) + + if kick_result.is_exception(): + NotificationContainer.create_notification(kick_result.exception.message, + NotificationContainer.NotificationType.ERROR) + user_has_been_kicked = false + + if user_has_been_kicked: + NotificationContainer.create_notification("User successfully kicked", + NotificationContainer.NotificationType.OK) + _on_groups_available_to_user_item_selected(0) + +func _on_leave_group_button_down(): + var result : NakamaAsyncResult = await NakamaManager.client.leave_group_async(NakamaManager.session, selectedGroup.id) + + if result.is_exception(): + NotificationContainer.create_notification("Cannot leave group", + NotificationContainer.NotificationType.ERROR) + else: + NotificationContainer.create_notification("Successfully left group", + NotificationContainer.NotificationType.OK) + +func _on_delete_group_button_down(): + var result : NakamaAsyncResult = await NakamaManager.client.delete_group_async(NakamaManager.session, selectedGroup.id) + + if result.is_exception(): + NotificationContainer.create_notification("Cannot delete group", + NotificationContainer.NotificationType.ERROR) + else: + NotificationContainer.create_notification("Successfully deleted group", + NotificationContainer.NotificationType.OK) + +func _on_group_listing_slider_value_changed(value: int) -> void: + group_listing_selected_label.text = "%d" % value + +func _on_update_available_groups_pressed() -> void: + + var result = await NakamaManager.client.list_user_groups_async(NakamaManager.session, NakamaManager.current_user.id) + + groups_available_to_user.clear() + + available_groups_to_current_user = result.user_groups + + for query_result in result.user_groups: + if query_result.state != UserState.JoinRequest: + groups_available_to_user.add_item(query_result.group.name) + + _on_groups_available_to_user_item_selected(0) + +func _is_state_admin(state: UserState) -> bool: + return state == UserState.Admin or state == UserState.SuperAdmin + +func _on_groups_available_to_user_item_selected(index: int) -> void: + var selected_id = available_groups_to_current_user[index].group.id + + selectedGroup = available_groups_to_current_user[index].group + + print("\n\nselectedGroup.open = ", selectedGroup.open, "\n\n") + + close_open_group.button_pressed = not selectedGroup.open + _update_close_group_text(selectedGroup.open) + + var result = await NakamaManager.client.list_group_users_async(NakamaManager.session, selected_id) + + group_users_option_button.clear() + + available_users_in_current_group = result.group_users + + for query_results in result.group_users: + + var user = query_results.user + + group_users_option_button.add_item(user.display_name) + + if user.id == NakamaManager.current_user.id: + selectedGroupState = query_results.state + + var is_admin : bool = _is_state_admin(selectedGroupState) + + close_open_group.visible = is_admin + delete_group.visible = is_admin + + close_open_group.button_pressed = selectedGroup.open + + _update_close_group_text(close_open_group.button_pressed) + + _on_group_users_item_selected(0) + _update_pending_members_to_join_group() + +func _update_pending_members_to_join_group() -> void: + + var users = await NakamaManager.client.list_group_users_async(NakamaManager.session, selectedGroup.id, 3) + + pending_to_join_group_label.text = "" + + for user in users.group_users: + var u = user.user as NakamaAPI.ApiUser + pending_to_join_group_label.text += "%s\n" % u.display_name + + pending_to_join_section.visible = not pending_to_join_group_label.text.is_empty() + +func _on_group_users_item_selected(index: int) -> void: + var user = available_users_in_current_group[index] + + group_member_status.text = "Status: %s" % user_status_to_string(user.state) + + if user.state >= UserState.SuperAdmin and user.state < UserState.JoinRequest: + + joined_member_vbox.visible = _can_manage_other_user(user.state, selectedGroupState) + accept_join_request.visible = false + elif user.state == UserState.JoinRequest: + joined_member_vbox.visible = false + accept_join_request.visible = true + +func _can_manage_other_user(user_state: UserState, current_user_state: UserState) -> bool: + + var result : bool = true + + if current_user_state == UserState.Admin: + result = user_state == UserState.Member + elif current_user_state == UserState.Member or current_user_state == UserState.JoinRequest: + result = false + + return result + + +func user_status_to_string(state: int) -> String: + + var state_string := "" + + match state: + UserState.SuperAdmin: + state_string = "Superadmin" + UserState.Admin: + state_string = "Admin" + UserState.Member: + state_string = "Member" + UserState.JoinRequest: + state_string = "Join Request" + _: + state_string = "Undefined" + + return state_string + +func _on_accept_join_request_pressed() -> void: + var users = await NakamaManager.client.list_group_users_async(NakamaManager.session,selectedGroup.id, UserState.JoinRequest) + + for query_result in users.group_users: + var u = query_result.user as NakamaAPI.ApiUser + + await NakamaManager.client.add_group_users_async(NakamaManager.session, selectedGroup.id, [u.id]) + + _on_groups_available_to_user_item_selected(groups_available_to_user.selected) + +func _on_accept_all_pending_requests_pressed() -> void: + var users = await NakamaManager.client.list_group_users_async(NakamaManager.session,selectedGroup.id, UserState.JoinRequest) + + for user in users.group_users: + var u = user.user as NakamaAPI.ApiUser + await NakamaManager.client.add_group_users_async(NakamaManager.session, selectedGroup.id, [u.id]) + +#endregion + +#region RPC TRADE SYSTEM +func _on_ping_rpc_button_down(): + var item = { + "name" = "sword", + "type" = "Weapon", + "rarity" = "common" + } + var rpcReturn = await NakamaManager.client.rpc_async(NakamaManager.session, "addItemToInventory", JSON.stringify(item)) + print(rpcReturn) + + +func _on_get_inventory_button_down(): + var inventory = await getInventory(NakamaManager.session.user_id) + removeMyChildren(trade_vbox1) + + if not inventory: + return + + for i in inventory: + var button = Button.new() + button.name = i.name + button.text = i.name + trade_vbox1.add_child(button) + button.button_down.connect(setItemForTrade.bind(i, button, true)) + var stylebox = StyleBoxFlat.new() + button.add_theme_stylebox_override("normal", stylebox) + +func getInventory(id): + var result = \ + await NakamaManager.client.rpc_async(NakamaManager.session, "getInventory", JSON.stringify({"id" : id})) + + if result is NakamaException: + return null + + var inventory = JSON.parse_string(result.payload) + return inventory + +func onTrade(friend): + var inventory = await getInventory(friend.user.id) + PlayerToTradeWith = friend + removeMyChildren(trade_vbox2) + + for i in inventory: + var button = Button.new() + button.name = i.name + button.text = i.name + button.button_down.connect(setItemForTrade.bind(i, button, false)) + trade_vbox2.add_child(button) + var stylebox = StyleBoxFlat.new() + button.add_theme_stylebox_override("normal", stylebox) + pass + +var TradeItems = [] +var ItemsToTradeFor = [] +var PlayerToTradeWith +var currentTradeOffer + +func setItemForTrade(item, button : Button, player: bool): + var items + if player: + items = TradeItems + else: + items = ItemsToTradeFor + + if(!items.has(item)): + items.append(item) + var stylebox = StyleBoxFlat.new() + stylebox.bg_color = Color.GREEN + button.add_theme_stylebox_override("normal", stylebox) + else: + items.erase(item) + var stylebox = StyleBoxFlat.new() + button.add_theme_stylebox_override("normal", stylebox) + + if player: + TradeItems = items + else: + ItemsToTradeFor = items + + +func _on_send_trade_offer_button_down(): + var receiverID = PlayerToTradeWith.user.id + var offerItems = TradeItems + var requestedItems = ItemsToTradeFor + + var payload = { + "recieverid" : receiverID, + "offerItems" : offerItems, + "requestedItems" : requestedItems + } + if offerItems == [] || requestedItems == []: + print("cannot send empty offer") + return + + var result = await NakamaManager.client.rpc_async(NakamaManager.session, "createTradeOffer", JSON.stringify(payload)) + print(result) + pass # Replace with function body. + + +func _on_accept_trade_offer_button_down(): + var payload = {"offerID" : currentTradeOffer.offerid} + var result = await NakamaManager.client.rpc_async(NakamaManager.session, "acceptTradeOffer", JSON.stringify(payload)) + + var response = JSON.parse_string(result.payload) + if result.exception != null: + print(result.exception.message) + else: + print("accepted trade offer " + response.result) + pass # Replace with function body. + + +func _on_get_trade_offers_button_down(): + var tradeOffers = await getTradeOffers() + + for i in tradeOffers: + var button = Button.new() + var id = await NakamaManager.client.get_users_async(NakamaManager.session, [i.senderid], null) + button.text = id.users[0].display_name + button.button_down.connect(setTradeOffers.bind(i)) + trade_vbox1.add_child(button) + pass # Replace with function body. + +func setTradeOffers(offer): + removeMyChildren(trade_vbox1) + removeMyChildren(trade_vbox2) + + for i in offer.requestedItems: + var button = Button.new() + button.text = i.name + trade_vbox1.add_child(button) + for i in offer.offerItems: + var button = Button.new() + button.text = i.name + trade_vbox2.add_child(button) + + currentTradeOffer = offer + +func removeMyChildren(node): + for i in node.get_children(): + i.queue_free() + +func getTradeOffers(): + var result = await NakamaManager.client.rpc_async(NakamaManager.session, "getTradeOffers", "{}") + + var tradeOffers = JSON.parse_string(result.payload) + + return tradeOffers + + +func _on_cancel_trade_offer_button_down(): + var payload = {"offerID" : currentTradeOffer.offerid} + var result = await NakamaManager.client.rpc_async(NakamaManager.session, "cancelTradeOffer", JSON.stringify(payload)) + + var response = JSON.parse_string(result.payload) + print("Canceled trade offer " + response.result) + pass # Replace with function body. + +#endregion + +#region Popup + +func invoke_popup(title: String, message: String) -> void: + popup.configure_text(title, message) + popup.popup_centered() + +#endregion + + +func _on_close_open_group_pressed() -> void: + print("\n\nfechar grupo?: ", close_open_group.button_pressed, "\n\n") + + await NakamaManager.client.update_group_async(NakamaManager.session, selectedGroup.id, null, null, null, null, close_open_group.button_pressed) + + _update_close_group_text(close_open_group.button_pressed) + +func _on_user_information_display_invite_friend_to_party(id: String, username: String) -> void: + + var party_id : String = NakamaManager.get_party_id() + + if NakamaManager.is_in_party(): + party.create_party() + party_id = NakamaManager.get_party_id() + + chat_tab.invite_friend(id, username, party_id) diff --git a/MainMenu/MainMenu.gd.uid b/MainMenu/MainMenu.gd.uid new file mode 100644 index 0000000..7401361 --- /dev/null +++ b/MainMenu/MainMenu.gd.uid @@ -0,0 +1 @@ +uid://bkso6trqnr35v diff --git a/MainMenu/MainMenu.tscn b/MainMenu/MainMenu.tscn new file mode 100644 index 0000000..7dec0b7 --- /dev/null +++ b/MainMenu/MainMenu.tscn @@ -0,0 +1,590 @@ +[gd_scene load_steps=15 format=3 uid="uid://p2ptqa1u1n1k"] + +[ext_resource type="Script" uid="uid://bkso6trqnr35v" path="res://MainMenu/MainMenu.gd" id="1_mtcsr"] +[ext_resource type="PackedScene" uid="uid://ctlppxshfqr7i" path="res://MainMenu/Lobby/MatchFinder/MatchFinder.tscn" id="2_bsanu"] +[ext_resource type="PackedScene" uid="uid://cvedwg86rsyp2" path="res://MainMenu/Lobby/UserInformationDisplay/UserInformationDisplay.tscn" id="2_qmkq0"] +[ext_resource type="PackedScene" uid="uid://cu2rcrnq0w6n5" path="res://MainMenu/Lobby/Leaderboard/Leaderboard.tscn" id="3_kftou"] +[ext_resource type="PackedScene" uid="uid://bdw5y4b5om737" path="res://MainMenu/Lobby/Party/PartyScreen.tscn" id="3_nre7b"] +[ext_resource type="PackedScene" uid="uid://ddsbx05whfylu" path="res://MainMenu/Lobby/Chat/ChatTab.tscn" id="3_nxm3p"] +[ext_resource type="PackedScene" uid="uid://dd2ekb3yor3hh" path="res://MainMenu/Authentication/LoginPanel/LoginPanel.tscn" id="4_4xlfs"] +[ext_resource type="PackedScene" uid="uid://xq36w5vrpfwb" path="res://MainMenu/Authentication/RegisterPanel/RegisterPanel.tscn" id="5_wys0s"] +[ext_resource type="PackedScene" uid="uid://wm1fd83jgjwp" path="res://MainMenu/Lobby/Notifications/Notifications.tscn" id="6_nxm3p"] +[ext_resource type="PackedScene" uid="uid://32gcwea4fq4t" path="res://Popup/Message/PopupBox.tscn" id="7_tv77q"] +[ext_resource type="PackedScene" uid="uid://cx38uabenhpce" path="res://Popup/JoinGroupChat/JoinGroupChat.tscn" id="8_ltd8p"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6qptp"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.140447, 0.140447, 0.140447, 1) + +[sub_resource type="Theme" id="Theme_k1pkr"] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_6qptp") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3clmd"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.0728426, 0.0728426, 0.0728426, 1) + +[node name="Game" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_k1pkr") +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +script = ExtResource("1_mtcsr") +metadata/_edit_horizontal_guides_ = [-247.0] + +[node name="HBox" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="LobbyContainer" type="TabContainer" parent="HBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +current_tab = 4 + +[node name="Match Finder" parent="HBox/LobbyContainer" instance=ExtResource("2_bsanu")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="TradeSystem" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +text = "Inventory" + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +columns = 3 + +[node name="AddItemToInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Ping RPC" + +[node name="GetInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Inventory" + +[node name="SendTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Send Trade Offer" + +[node name="GetTradeOffers" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Trade Offers" + +[node name="AcceptTradeOffer2" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Accept Trade Offer" + +[node name="CancelTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Cancel Trade Offer" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel"] +layout_mode = 2 + +[node name="Panel" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Panel2" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="FriendNameLabel" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "Other User: " + +[node name="Label2" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "My Inventory" + +[node name="TradeVbox1" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeVBox2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeOffers" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 +text = "Trade Offers" + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 + +[node name="Data Panel" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 0 +metadata/_tab_index = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Data Panel"] +layout_mode = 2 + +[node name="CollectionField" type="HBoxContainer" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/CollectionField"] +layout_mode = 2 +text = "Collection name: " + +[node name="CollectionLineEdit" type="LineEdit" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/CollectionField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="KeyField" type="HBoxContainer" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/KeyField"] +layout_mode = 2 +text = "Key Field" + +[node name="KeyLineEdit" type="LineEdit" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/KeyField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="StoreData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Store Data" + +[node name="GetData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Get Data from Store" + +[node name="ListData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "List Data from Store" + +[node name="DataFromStore" type="PanelContainer" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_3clmd") + +[node name="DataFromStoreLabel" type="Label" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/DataFromStore"] +unique_name_in_owner = true +layout_mode = 2 +autowrap_mode = 3 + +[node name="Group Manager" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 3 + +[node name="TabContainer" type="TabContainer" parent="HBox/LobbyContainer/Group Manager"] +layout_mode = 2 +current_tab = 2 + +[node name="Create Group" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group"] +layout_mode = 2 +theme_override_constants/separation = 25 + +[node name="CreateGroupGrid" type="GridContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +theme_override_constants/h_separation = 15 +theme_override_constants/v_separation = 15 +columns = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Name" + +[node name="GroupName" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupDescLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +text = "Description" + +[node name="GroupDesc" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CreateGroup2" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Create Group" + +[node name="Group Query" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 1 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query"] +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 15 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +layout_mode = 2 +text = "Group Name: " + +[node name="GroupQuery" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Group Query" + +[node name="HBoxContainer2" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="List Groups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "List Groups" + +[node name="GroupListingLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "Group listing limit:" +horizontal_alignment = 1 + +[node name="GroupListingSlider" type="HSlider" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +min_value = 1.0 +max_value = 25.0 +value = 10.0 + +[node name="GroupListingSelectedLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +text = "10" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel"] +custom_minimum_size = Vector2(0, 75) +layout_mode = 2 + +[node name="GroupsQueryVBox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Manage Groups" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 5 +metadata/_tab_index = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="UpdateAvailableGroups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +text = "Update groups" + +[node name="SelectAGroup" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup"] +layout_mode = 2 +text = "Selected Group: " + +[node name="GroupsAvailableToUser" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HSeparator4" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="SelectAMember" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember"] +layout_mode = 2 +text = "Manage Member:" + +[node name="GroupUsers" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupMemberStatus" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="JoinedMemberVbox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="PromoteDemote" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 + +[node name="PromoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Promote" + +[node name="DemoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Demote" + +[node name="KickUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Kick" + +[node name="AcceptJoinRequest" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Accept Join Request" + +[node name="HSeparator2" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="PendingToJoinSection" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 15 + +[node name="SectionTitle" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Pending to Join:" + +[node name="_" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="PendingToJoinGroupLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection/_"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="AcceptAllPendingRequests" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Accept All Pending Requests" + +[node name="HSeparator3" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +text = "Group Information" + +[node name="CloseOpenGroup" type="CheckBox" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Close Group" + +[node name="LeaveGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Leave Group" + +[node name="DeleteGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Delete Group" + +[node name="ChatTab" parent="HBox/LobbyContainer" instance=ExtResource("3_nxm3p")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Party" parent="HBox/LobbyContainer" instance=ExtResource("3_nre7b")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="Leaderboard" parent="HBox/LobbyContainer" instance=ExtResource("3_kftou")] +visible = false +layout_mode = 2 +metadata/_tab_index = 6 + +[node name="Notifications" parent="HBox/LobbyContainer" instance=ExtResource("6_nxm3p")] +visible = false +layout_mode = 2 + +[node name="UserInformationDisplay" parent="HBox" instance=ExtResource("2_qmkq0")] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 105) +layout_mode = 2 +size_flags_vertical = 1 + +[node name="Authentication" type="CenterContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="Tabs" type="TabContainer" parent="Authentication"] +layout_mode = 2 +current_tab = 0 + +[node name="Login" parent="Authentication/Tabs" instance=ExtResource("4_4xlfs")] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="Register" parent="Authentication/Tabs" instance=ExtResource("5_wys0s")] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="Popup" parent="." instance=ExtResource("7_tv77q")] +position = Vector2i(0, 36) +visible = false + +[node name="CreateChatRoom" type="Window" parent="."] +title = "Create Chat Room" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="CreateChatRoom"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="CreateChatRoom/PanelContainer"] +layout_mode = 2 + +[node name="UsernameField" type="HBoxContainer" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 + +[node name="Label" type="Label" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +text = "Username" + +[node name="LineEdit" type="LineEdit" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 +text = "Create" + +[node name="JoinGroupChat" parent="." instance=ExtResource("8_ltd8p")] +visible = false + +[node name="DirectChat" type="Window" parent="."] +auto_translate_mode = 1 +title = "Join Group Chat" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="DirectChat"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="DirectChat/PanelContainer"] +layout_mode = 2 + +[node name="User" type="HBoxContainer" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 + +[node name="SelectAFriend" type="Label" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +text = "Select a Friend" + +[node name="OptionButton" type="OptionButton" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 +text = "Join" + +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AddItemToInventory" to="." method="_on_ping_rpc_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetInventory" to="." method="_on_get_inventory_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/SendTradeOffer" to="." method="_on_send_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetTradeOffers" to="." method="_on_get_trade_offers_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AcceptTradeOffer2" to="." method="_on_accept_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/CancelTradeOffer" to="." method="_on_cancel_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/StoreData" to="." method="_on_store_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/GetData" to="." method="_on_get_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/ListData" to="." method="_on_list_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroup2" to="." method="_on_create_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/List Groups" to="." method="_on_list_groups_button_down"] +[connection signal="value_changed" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/GroupListingSlider" to="." method="_on_group_listing_slider_value_changed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/UpdateAvailableGroups" to="." method="_on_update_available_groups_pressed"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup/GroupsAvailableToUser" to="." method="_on_groups_available_to_user_item_selected"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember/GroupUsers" to="." method="_on_group_users_item_selected"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote/PromoteUser" to="." method="_on_promote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote/DemoteUser" to="." method="_on_demote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/KickUser" to="." method="_on_kick_user_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/AcceptJoinRequest" to="." method="_on_accept_join_request_pressed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection/AcceptAllPendingRequests" to="." method="_on_accept_all_pending_requests_pressed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/CloseOpenGroup" to="." method="_on_close_open_group_pressed"] +[connection signal="toggled" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/CloseOpenGroup" to="." method="_on_close_open_group_toggled"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/LeaveGroup" to="." method="_on_leave_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/DeleteGroup" to="." method="_on_delete_group_button_down"] +[connection signal="invite_friend_to_party" from="HBox/UserInformationDisplay" to="." method="_on_user_information_display_invite_friend_to_party"] +[connection signal="login" from="Authentication/Tabs/Login" to="." method="_on_login_pressed"] +[connection signal="register_account" from="Authentication/Tabs/Register" to="." method="_on_register_account_pressed"] diff --git a/MainMenu/MainMenu.tscn177052401.tmp b/MainMenu/MainMenu.tscn177052401.tmp new file mode 100644 index 0000000..915a46d --- /dev/null +++ b/MainMenu/MainMenu.tscn177052401.tmp @@ -0,0 +1,811 @@ +[gd_scene load_steps=12 format=3 uid="uid://p2ptqa1u1n1k"] + +[ext_resource type="Script" uid="uid://bkso6trqnr35v" path="res://MainMenu/MainMenu.gd" id="1_mtcsr"] +[ext_resource type="PackedScene" uid="uid://bg6xgsfypi4gr" path="res://NotificationContainer/NotificationContainer.tscn" id="2_lanui"] +[ext_resource type="PackedScene" uid="uid://cvedwg86rsyp2" path="res://Lobby/UserInformationDisplay/UserInformationDisplay.tscn" id="2_qmkq0"] +[ext_resource type="PackedScene" uid="uid://dd2ekb3yor3hh" path="res://MainMenu/Authentication/LoginPanel/LoginPanel.tscn" id="4_4xlfs"] +[ext_resource type="PackedScene" uid="uid://xq36w5vrpfwb" path="res://MainMenu/Authentication/RegisterPanel/RegisterPanel.tscn" id="5_wys0s"] +[ext_resource type="PackedScene" uid="uid://32gcwea4fq4t" path="res://Popup/Message/PopupBox.tscn" id="7_tv77q"] +[ext_resource type="PackedScene" uid="uid://cx38uabenhpce" path="res://Popup/JoinGroupChat/JoinGroupChat.tscn" id="8_ltd8p"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6qptp"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.140447, 0.140447, 0.140447, 1) + +[sub_resource type="Theme" id="Theme_k1pkr"] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_6qptp") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3clmd"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.0728426, 0.0728426, 0.0728426, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tv77q"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 5.0 +bg_color = Color(0.0728426, 0.0728427, 0.0728426, 1) + +[node name="Game" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_k1pkr") +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +script = ExtResource("1_mtcsr") + +[node name="HBox" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="LobbyContainer" type="TabContainer" parent="HBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +current_tab = 2 + +[node name="Match Finder" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +custom_minimum_size = Vector2(350, 0) +layout_mode = 2 +size_flags_horizontal = 0 +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Match Finder"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +layout_mode = 2 +text = "Name Of Match" + +[node name="MatchName" type="LineEdit" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +columns = 2 + +[node name="JoinCreateMatch" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Join/Create" + +[node name="Ping" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Ping" + +[node name="Matchmaking" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Start Matchmaking" + +[node name="Button" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Start Game" + +[node name="TradeSystem" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +text = "Inventory" + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +columns = 3 + +[node name="AddItemToInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Ping RPC" + +[node name="GetInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Inventory" + +[node name="SendTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Send Trade Offer" + +[node name="GetTradeOffers" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Trade Offers" + +[node name="AcceptTradeOffer2" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Accept Trade Offer" + +[node name="CancelTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Cancel Trade Offer" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel"] +layout_mode = 2 + +[node name="Panel" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Panel2" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="FriendNameLabel" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "Other User: " + +[node name="Label2" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "My Inventory" + +[node name="TradeVbox1" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeVBox2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeOffers" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 +text = "Trade Offers" + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 + +[node name="Data Panel" type="PanelContainer" parent="HBox/LobbyContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +metadata/_tab_index = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Data Panel"] +layout_mode = 2 + +[node name="CollectionField" type="HBoxContainer" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/CollectionField"] +layout_mode = 2 +text = "Collection name: " + +[node name="CollectionLineEdit" type="LineEdit" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/CollectionField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="KeyField" type="HBoxContainer" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/KeyField"] +layout_mode = 2 +text = "Key Field" + +[node name="KeyLineEdit" type="LineEdit" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/KeyField"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="StoreData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Store Data" + +[node name="GetData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Get Data from Store" + +[node name="ListData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "List Data from Store" + +[node name="DataFromStore" type="PanelContainer" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_3clmd") + +[node name="DataFromStoreLabel" type="Label" parent="HBox/LobbyContainer/Data Panel/VBoxContainer/DataFromStore"] +unique_name_in_owner = true +layout_mode = 2 +autowrap_mode = 3 + +[node name="Group Admin Panel" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +metadata/_tab_index = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel"] +layout_mode = 2 + +[node name="GroupName2" type="LineEdit" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Name of group" + +[node name="GetGroupInformation" type="Button" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +layout_mode = 2 +text = "Select Group" + +[node name="DeleteSelectedGroup" type="Button" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +layout_mode = 2 +text = "Delete Group" + +[node name="SelectedGroup" type="PanelContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup"] +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="SelectedGroupName" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group name:" + +[node name="SelectedGroupDescription" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group description:" + +[node name="SelectedGroupID" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group ID:" + +[node name="SelectedGroupIsOpen" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Is open:" + +[node name="GroupMembers" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Group Members:" + +[node name="PanelContainer" type="PanelContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_tv77q") + +[node name="GroupMembersList" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_/PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Group Manager" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 4 + +[node name="TabContainer" type="TabContainer" parent="HBox/LobbyContainer/Group Manager"] +layout_mode = 2 +current_tab = 2 + +[node name="Create Group" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group"] +layout_mode = 2 +theme_override_constants/separation = 25 + +[node name="CreateGroupGrid" type="GridContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +theme_override_constants/h_separation = 15 +theme_override_constants/v_separation = 15 +columns = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Name" + +[node name="GroupName" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupDescLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +text = "Description" + +[node name="GroupDesc" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CreateGroup2" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Create Group" + +[node name="Group Query" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 1 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query"] +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 15 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +layout_mode = 2 +text = "Group Name: " + +[node name="GroupQuery" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Group Query" + +[node name="HBoxContainer2" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="List Groups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "List Groups" + +[node name="GroupListingLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "Group listing limit:" +horizontal_alignment = 1 + +[node name="GroupListingSlider" type="HSlider" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +min_value = 1.0 +max_value = 25.0 +value = 10.0 + +[node name="GroupListingSelectedLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +text = "10" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel"] +custom_minimum_size = Vector2(0, 75) +layout_mode = 2 + +[node name="GroupsQueryVBox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Manage Groups" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 5 +metadata/_tab_index = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="UpdateAvailableGroups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +text = "Update groups" + +[node name="SelectAGroup" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup"] +layout_mode = 2 +text = "Selected Group: " + +[node name="GroupsAvailableToUser" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HSeparator4" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="SelectAMember" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember"] +layout_mode = 2 +text = "Manage Member:" + +[node name="GroupUsers" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupMemberStatus" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="JoinedMemberVbox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="PromoteDemote" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 + +[node name="PromoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Promote" + +[node name="DemoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Demote" + +[node name="KickUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Kick" + +[node name="AcceptJoinRequest" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Accept Join Request" + +[node name="HSeparator2" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="PendingToJoinSection" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 15 + +[node name="SectionTitle" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Pending to Join:" + +[node name="_" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="PendingToJoinGroupLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection/_"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="AcceptAllPendingRequests" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Accept All Pending Requests" + +[node name="HSeparator3" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +text = "Group Information" + +[node name="CloseOpenGroup" type="CheckBox" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Close Group" + +[node name="LeaveGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Leave Group" + +[node name="DeleteGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Delete Group" + +[node name="Chat" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Chat"] +layout_mode = 2 + +[node name="ChatName" type="LineEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +placeholder_text = "Chat Name" + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 + +[node name="CreateChatRoom" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Create Chat room" + +[node name="JoinGroupChatRoom" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Join Group Chat" + +[node name="JoinDirectChat" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 +text = "Join Direct Chat" + +[node name="Chat" type="PanelContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat"] +layout_mode = 2 + +[node name="UsernameContainer" type="TabContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="username" type="TextEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer/UsernameContainer"] +layout_mode = 2 +editable = false +wrap_mode = 1 +metadata/_tab_index = 0 + +[node name="ChatTextLineEdit" type="LineEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Chat Text Here" + +[node name="SubmitChat" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +layout_mode = 2 +text = ">" + +[node name="CreateParty" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 6 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty"] +layout_mode = 2 + +[node name="PartyIsOpenCheckBox" type="CheckBox" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = " Party is open" + +[node name="CreateParty" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +layout_mode = 2 +text = "Create Party" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/Panel"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/Panel/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Party ID: " + +[node name="LineEdit" type="LineEdit" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/Panel/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +text = "Join Party" + +[node name="ChannelMessagePanel" type="PanelContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel"] +layout_mode = 2 + +[node name="ChannelMessageLabel" type="Label" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Join Party?" + +[node name="JoinPartyNo" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +layout_mode = 2 +text = "No" + +[node name="JoinPartyYes" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +layout_mode = 2 +text = "Yes" + +[node name="UserInformationDisplay" parent="HBox" instance=ExtResource("2_qmkq0")] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 105) +layout_mode = 2 +size_flags_vertical = 1 + +[node name="Authentication" type="CenterContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="Tabs" type="TabContainer" parent="Authentication"] +layout_mode = 2 +current_tab = 0 + +[node name="Login" parent="Authentication/Tabs" instance=ExtResource("4_4xlfs")] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="Register" parent="Authentication/Tabs" instance=ExtResource("5_wys0s")] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="NotificationContainer" parent="." instance=ExtResource("2_lanui")] +layout_mode = 2 +size_flags_vertical = 8 + +[node name="Popup" parent="." instance=ExtResource("7_tv77q")] +position = Vector2i(0, 36) +visible = false + +[node name="CreateChatRoom" type="Window" parent="."] +title = "Create Chat Room" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="CreateChatRoom"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="CreateChatRoom/PanelContainer"] +layout_mode = 2 + +[node name="UsernameField" type="HBoxContainer" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 + +[node name="Label" type="Label" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +text = "Username" + +[node name="LineEdit" type="LineEdit" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 +text = "Create" + +[node name="JoinGroupChat" parent="." instance=ExtResource("8_ltd8p")] +visible = false + +[node name="DirectChat" type="Window" parent="."] +auto_translate_mode = 1 +title = "Join Group Chat" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="DirectChat"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="DirectChat/PanelContainer"] +layout_mode = 2 + +[node name="User" type="HBoxContainer" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 + +[node name="SelectAFriend" type="Label" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +text = "Select a Friend" + +[node name="OptionButton" type="OptionButton" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 +text = "Join" + +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/JoinCreateMatch" to="." method="_on_join_create_match_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Ping" to="." method="_on_ping_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Matchmaking" to="." method="_on_matchmaking_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Button" to="." method="_on_button_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AddItemToInventory" to="." method="_on_ping_rpc_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetInventory" to="." method="_on_get_inventory_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/SendTradeOffer" to="." method="_on_send_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetTradeOffers" to="." method="_on_get_trade_offers_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AcceptTradeOffer2" to="." method="_on_accept_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/CancelTradeOffer" to="." method="_on_cancel_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/StoreData" to="." method="_on_store_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/GetData" to="." method="_on_get_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/ListData" to="." method="_on_list_data_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/GetGroupInformation" to="." method="_on_get_group_information_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/DeleteSelectedGroup" to="." method="_on_delete_group_pressed"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroup2" to="." method="_on_create_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/List Groups" to="." method="_on_list_groups_button_down"] +[connection signal="value_changed" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/GroupListingSlider" to="." method="_on_group_listing_slider_value_changed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/UpdateAvailableGroups" to="." method="_on_update_available_groups_pressed"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup/GroupsAvailableToUser" to="." method="_on_groups_available_to_user_item_selected"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember/GroupUsers" to="." method="_on_group_users_item_selected"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote/PromoteUser" to="." method="_on_promote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote/DemoteUser" to="." method="_on_demote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/KickUser" to="." method="_on_kick_user_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/AcceptJoinRequest" to="." method="_on_accept_join_request_pressed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection/AcceptAllPendingRequests" to="." method="_on_accept_all_pending_requests_pressed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/CloseOpenGroup" to="." method="_on_close_open_group_pressed"] +[connection signal="toggled" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/CloseOpenGroup" to="." method="_on_close_open_group_toggled"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/LeaveGroup" to="." method="_on_leave_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/DeleteGroup" to="." method="_on_delete_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer/CreateChatRoom" to="." method="_on_join_chat_room_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer/JoinGroupChatRoom" to="." method="_on_join_group_chat_room_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/JoinDirectChat" to="." method="_on_join_direct_chat_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer/SubmitChat" to="." method="_on_submit_chat_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/CreateParty" to="." method="_on_create_party_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer/JoinPartyNo" to="." method="_on_join_party_no_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer/JoinPartyYes" to="." method="_on_join_party_yes_button_down"] +[connection signal="login" from="Authentication/Tabs/Login" to="." method="_on_login_pressed"] +[connection signal="register_account" from="Authentication/Tabs/Register" to="." method="_on_register_account_pressed"] diff --git a/MainMenu/MainMenu.tscn4027015678.tmp b/MainMenu/MainMenu.tscn4027015678.tmp new file mode 100644 index 0000000..0087086 --- /dev/null +++ b/MainMenu/MainMenu.tscn4027015678.tmp @@ -0,0 +1,852 @@ +[gd_scene load_steps=13 format=3 uid="uid://p2ptqa1u1n1k"] + +[ext_resource type="Script" uid="uid://bkso6trqnr35v" path="res://MainMenu/MainMenu.gd" id="1_mtcsr"] +[ext_resource type="PackedScene" uid="uid://bg6xgsfypi4gr" path="res://NotificationContainer/NotificationContainer.tscn" id="2_lanui"] +[ext_resource type="PackedScene" uid="uid://cvedwg86rsyp2" path="res://Lobby/UserInformationDisplay/UserInformationDisplay.tscn" id="2_qmkq0"] +[ext_resource type="PackedScene" uid="uid://dd2ekb3yor3hh" path="res://MainMenu/Authentication/LoginPanel/LoginPanel.tscn" id="4_4xlfs"] +[ext_resource type="PackedScene" uid="uid://xq36w5vrpfwb" path="res://MainMenu/Authentication/RegisterPanel/RegisterPanel.tscn" id="5_wys0s"] +[ext_resource type="PackedScene" uid="uid://32gcwea4fq4t" path="res://Popup/Message/PopupBox.tscn" id="7_tv77q"] +[ext_resource type="PackedScene" uid="uid://cx38uabenhpce" path="res://Popup/JoinGroupChat/JoinGroupChat.tscn" id="8_ltd8p"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6qptp"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.140447, 0.140447, 0.140447, 1) + +[sub_resource type="Theme" id="Theme_k1pkr"] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_6qptp") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tmgb8"] +content_margin_left = 7.0 +content_margin_top = 7.0 +content_margin_right = 7.0 +content_margin_bottom = 7.0 +bg_color = Color(0.315076, 0.398015, 0.42524, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u303v"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.139728, 0.149761, 0.185489, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tv77q"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 5.0 +bg_color = Color(0.0728426, 0.0728427, 0.0728426, 1) + +[node name="Game" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_k1pkr") +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +script = ExtResource("1_mtcsr") + +[node name="HBox" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="LobbyContainer" type="TabContainer" parent="HBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +current_tab = 0 + +[node name="Friend List" type="PanelContainer" parent="HBox/LobbyContainer"] +layout_mode = 2 +size_flags_vertical = 0 +metadata/_tab_index = 0 + +[node name="HBoxContainer" type="HSplitContainer" parent="HBox/LobbyContainer/Friend List"] +layout_mode = 2 + +[node name="VBox" type="VBoxContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox"] +layout_mode = 2 +size_flags_vertical = 0 +columns = 2 + +[node name="BlockFriends" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 0 +text = "Block Friend" + +[node name="RemoveFriend" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer"] +layout_mode = 2 +text = "Delete friend" + +[node name="GetFriends" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer"] +layout_mode = 2 +text = "Update Friends List" + +[node name="FriendsBox" type="PanelContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer"] +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_tmgb8") + +[node name="VBox" type="VBoxContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox"] +layout_mode = 2 + +[node name="RichTextLabel" type="Label" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox/HBoxContainer"] +layout_mode = 2 +text = "Friends" + +[node name="AddFriendText" type="LineEdit" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Name of username" +alignment = 1 + +[node name="AddFriend" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "+" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_u303v") + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox/Panel"] +layout_mode = 2 + +[node name="FriendsContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox/Panel/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Match Finder" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +custom_minimum_size = Vector2(350, 0) +layout_mode = 2 +size_flags_horizontal = 0 +metadata/_tab_index = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Match Finder"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +layout_mode = 2 +text = "Name Of Match" + +[node name="MatchName" type="LineEdit" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +columns = 2 + +[node name="JoinCreateMatch" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Join/Create" + +[node name="Ping" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Ping" + +[node name="Matchmaking" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Start Matchmaking" + +[node name="Button" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Start Game" + +[node name="TradeSystem" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +text = "Inventory" + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +columns = 3 + +[node name="AddItemToInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Ping RPC" + +[node name="GetInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Inventory" + +[node name="SendTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Send Trade Offer" + +[node name="GetTradeOffers" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Trade Offers" + +[node name="AcceptTradeOffer2" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Accept Trade Offer" + +[node name="CancelTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Cancel Trade Offer" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel"] +layout_mode = 2 + +[node name="Panel" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Panel2" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="FriendNameLabel" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "Other User: " + +[node name="Label2" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "My Inventory" + +[node name="TradeVbox1" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeVBox2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeOffers" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 +text = "Trade Offers" + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 + +[node name="Data Panel" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 0 +metadata/_tab_index = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Data Panel"] +layout_mode = 2 + +[node name="StoreData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Store Data" + +[node name="GetData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Get Data from Store" + +[node name="ListData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "List Data from Store" + +[node name="Group Admin Panel" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +metadata/_tab_index = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel"] +layout_mode = 2 + +[node name="GroupName2" type="LineEdit" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Name of group" + +[node name="GetGroupInformation" type="Button" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +layout_mode = 2 +text = "Select Group" + +[node name="DeleteSelectedGroup" type="Button" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +layout_mode = 2 +text = "Delete Group" + +[node name="SelectedGroup" type="PanelContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup"] +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="SelectedGroupName" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group name:" + +[node name="SelectedGroupDescription" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group description:" + +[node name="SelectedGroupID" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group ID:" + +[node name="SelectedGroupIsOpen" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Is open:" + +[node name="GroupMembers" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Group Members:" + +[node name="PanelContainer" type="PanelContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_tv77q") + +[node name="GroupMembersList" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_/PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Group Manager" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 5 + +[node name="TabContainer" type="TabContainer" parent="HBox/LobbyContainer/Group Manager"] +layout_mode = 2 +current_tab = 2 + +[node name="Create Group" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group"] +layout_mode = 2 +theme_override_constants/separation = 25 + +[node name="CreateGroupGrid" type="GridContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +theme_override_constants/h_separation = 15 +theme_override_constants/v_separation = 15 +columns = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Name" + +[node name="GroupName" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupDescLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +text = "Description" + +[node name="GroupDesc" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CreateGroup2" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Create Group" + +[node name="Group Query" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 1 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query"] +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 15 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +layout_mode = 2 +text = "Group Name: " + +[node name="GroupQuery" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Group Query" + +[node name="HBoxContainer2" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="List Groups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "List Groups" + +[node name="GroupListingLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "Group listing limit:" +horizontal_alignment = 1 + +[node name="GroupListingSlider" type="HSlider" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +min_value = 1.0 +max_value = 25.0 +value = 10.0 + +[node name="GroupListingSelectedLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +text = "10" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel"] +custom_minimum_size = Vector2(0, 75) +layout_mode = 2 + +[node name="GroupsQueryVBox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Manage Groups" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 5 +metadata/_tab_index = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="UpdateAvailableGroups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +text = "Update groups" + +[node name="SelectAGroup" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup"] +layout_mode = 2 +text = "Selected Group: " + +[node name="GroupsAvailableToUser" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HSeparator4" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="SelectAMember" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember"] +layout_mode = 2 +text = "Manage Member:" + +[node name="GroupUsers" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupMemberStatus" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="JoinedMemberVbox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="PromoteDemote" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 + +[node name="PromoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Promote" + +[node name="DemoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Demote" + +[node name="KickUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Kick" + +[node name="AcceptJoinRequest" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Accept Join Request" + +[node name="HSeparator2" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="PendingToJoinSection" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 15 + +[node name="SectionTitle" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Pending to Join:" + +[node name="_" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="PendingToJoinGroupLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection/_"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="AcceptAllPendingRequests" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Accept All Pending Requests" + +[node name="HSeparator3" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +text = "Group Information" + +[node name="CloseOpenGroup" type="CheckBox" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Close Group" + +[node name="LeaveGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Leave Group" + +[node name="DeleteGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Delete Group" + +[node name="Chat" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 6 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Chat"] +layout_mode = 2 + +[node name="ChatName" type="LineEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +placeholder_text = "Chat Name" + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 + +[node name="CreateChatRoom" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Create Chat room" + +[node name="JoinGroupChatRoom" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Join Group Chat" + +[node name="JoinDirectChat" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 +text = "Join Direct Chat" + +[node name="Chat" type="PanelContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat"] +layout_mode = 2 + +[node name="UsernameContainer" type="TabContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="username" type="TextEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer/UsernameContainer"] +layout_mode = 2 +editable = false +wrap_mode = 1 +metadata/_tab_index = 0 + +[node name="ChatTextLineEdit" type="LineEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Chat Text Here" + +[node name="SubmitChat" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +layout_mode = 2 +text = ">" + +[node name="CreateParty" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 7 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty"] +layout_mode = 2 + +[node name="CreateParty" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +layout_mode = 2 +text = "Create Party" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/Panel"] +layout_mode = 2 + +[node name="ChannelMessagePanel" type="PanelContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel"] +layout_mode = 2 + +[node name="ChannelMessageLabel" type="Label" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Join Party?" + +[node name="JoinPartyNo" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +layout_mode = 2 +text = "No" + +[node name="JoinPartyYes" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +layout_mode = 2 +text = "Yes" + +[node name="UserInformationDisplay" parent="HBox" instance=ExtResource("2_qmkq0")] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 105) +layout_mode = 2 +size_flags_vertical = 1 + +[node name="Authentication" type="CenterContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="Tabs" type="TabContainer" parent="Authentication"] +layout_mode = 2 +current_tab = 0 + +[node name="Login" parent="Authentication/Tabs" instance=ExtResource("4_4xlfs")] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="Register" parent="Authentication/Tabs" instance=ExtResource("5_wys0s")] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="NotificationContainer" parent="." instance=ExtResource("2_lanui")] +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +size_flags_vertical = 8 + +[node name="Popup" parent="." instance=ExtResource("7_tv77q")] +position = Vector2i(0, 36) +visible = false + +[node name="CreateChatRoom" type="Window" parent="."] +title = "Create Chat Room" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="CreateChatRoom"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="CreateChatRoom/PanelContainer"] +layout_mode = 2 + +[node name="UsernameField" type="HBoxContainer" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 + +[node name="Label" type="Label" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +text = "Username" + +[node name="LineEdit" type="LineEdit" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 +text = "Create" + +[node name="JoinGroupChat" parent="." instance=ExtResource("8_ltd8p")] +visible = false + +[node name="DirectChat" type="Window" parent="."] +auto_translate_mode = 1 +title = "Join Group Chat" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="DirectChat"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="DirectChat/PanelContainer"] +layout_mode = 2 + +[node name="User" type="HBoxContainer" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 + +[node name="SelectAFriend" type="Label" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +text = "Select a Friend" + +[node name="OptionButton" type="OptionButton" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 +text = "Join" + +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer/BlockFriends" to="." method="_on_block_friends_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer/RemoveFriend" to="." method="_on_remove_friend_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer/GetFriends" to="." method="_on_get_friends_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox/HBoxContainer/AddFriend" to="." method="_on_add_friend_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/JoinCreateMatch" to="." method="_on_join_create_match_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Ping" to="." method="_on_ping_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Matchmaking" to="." method="_on_matchmaking_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Button" to="." method="_on_button_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AddItemToInventory" to="." method="_on_ping_rpc_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetInventory" to="." method="_on_get_inventory_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/SendTradeOffer" to="." method="_on_send_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetTradeOffers" to="." method="_on_get_trade_offers_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AcceptTradeOffer2" to="." method="_on_accept_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/CancelTradeOffer" to="." method="_on_cancel_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/StoreData" to="." method="_on_store_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/GetData" to="." method="_on_get_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/ListData" to="." method="_on_list_data_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/GetGroupInformation" to="." method="_on_get_group_information_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/DeleteSelectedGroup" to="." method="_on_delete_group_pressed"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroup2" to="." method="_on_create_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/List Groups" to="." method="_on_list_groups_button_down"] +[connection signal="value_changed" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/GroupListingSlider" to="." method="_on_group_listing_slider_value_changed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/UpdateAvailableGroups" to="." method="_on_update_available_groups_pressed"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAGroup/GroupsAvailableToUser" to="." method="_on_groups_available_to_user_item_selected"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/SelectAMember/GroupUsers" to="." method="_on_group_users_item_selected"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote/PromoteUser" to="." method="_on_promote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/PromoteDemote/DemoteUser" to="." method="_on_demote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/JoinedMemberVbox/KickUser" to="." method="_on_kick_user_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/AcceptJoinRequest" to="." method="_on_accept_join_request_pressed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/PendingToJoinSection/AcceptAllPendingRequests" to="." method="_on_accept_all_pending_requests_pressed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/CloseOpenGroup" to="." method="_on_close_open_group_pressed"] +[connection signal="toggled" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/CloseOpenGroup" to="." method="_on_close_open_group_toggled"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/LeaveGroup" to="." method="_on_leave_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/ScrollContainer/VBoxContainer/DeleteGroup" to="." method="_on_delete_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer/CreateChatRoom" to="." method="_on_join_chat_room_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer/JoinGroupChatRoom" to="." method="_on_join_group_chat_room_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/JoinDirectChat" to="." method="_on_join_direct_chat_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer/SubmitChat" to="." method="_on_submit_chat_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/CreateParty" to="." method="_on_create_party_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer/JoinPartyNo" to="." method="_on_join_party_no_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer/JoinPartyYes" to="." method="_on_join_party_yes_button_down"] +[connection signal="login" from="Authentication/Tabs/Login" to="." method="_on_login_pressed"] +[connection signal="register_account" from="Authentication/Tabs/Register" to="." method="_on_register_account_pressed"] diff --git a/Nakama/NakamaManager.gd b/Nakama/NakamaManager.gd new file mode 100644 index 0000000..44806d6 --- /dev/null +++ b/Nakama/NakamaManager.gd @@ -0,0 +1,940 @@ +extends Node + +signal user_logged_in() +signal peer_connnected_in_match(id: int) +signal peer_disconnnected_in_match(id: int) + +# Proxy signals for socket events +signal notification_received(notif: NakamaAPI.ApiNotification) +signal channel_message_received(message: NakamaAPI.ApiChannelMessage) +signal match_presence_received(presence: NakamaRTAPI.MatchPresenceEvent) +signal match_state_received(state: NakamaRTAPI.MatchData) +signal party_presence_received(presence: NakamaRTAPI.PartyPresenceEvent) +signal socket_connected() +signal socket_closed() +signal socket_error_received(error) + +enum ClientScheme { + HTTP, + HTTPS +} + +var Players = {} + +@export var server_key : String = "defaultkey" +@export var server_ip : String = "127.0.0.1" +@export var server_port : int = 7350 +@export var client_scheme : ClientScheme + +@export var max_party_count : int + +var session : NakamaSession # this is the session +var client : NakamaClient # this is the client {session} +var socket : NakamaSocket # connection to nakama +var multiplayerBridge : NakamaMultiplayerBridge + +var party : NakamaRTAPI.Party + +var current_user : NakamaAPI.ApiUser = null + +func start_client() -> void: + client = Nakama.create_client(server_key, + server_ip, + server_port, + get_client_scheme()) + +func register(email: String, password: String, username: String) -> void: + session = await client.authenticate_email_async(email , password, null, true) + + if NakamaManager.session.is_valid(): + NotificationContainer.create_notification("Registrado com sucesso!") + elif NakamaManager.session.is_exception(): + var exception : NakamaException = NakamaManager.session.get_exception() + NotificationContainer.handle_exception(exception.status_code) + + await client.update_account_async(session, null, username) + + var users = await NakamaManager.client.get_users_async(NakamaManager.session, [NakamaManager.session.user_id]) + + current_user = NakamaAPI.ApiUser.new() + + if users.users and users.users.size() > 0: + current_user = users.users[0] as NakamaAPI.ApiUser + + start_socket() + +func login(email: String, password: String) -> void: + session = await client.authenticate_email_async(email , password, null, false) + + if session.is_valid(): + NotificationContainer.create_notification("Logado com sucesso!") + elif session.is_exception(): + var exception : NakamaException = session.get_exception() + NotificationContainer.handle_exception(exception.status_code) + + var users = await client.get_users_async(session, [session.user_id]) + + current_user = NakamaAPI.ApiUser.new() + + if users.users and users.users.size() > 0: + current_user = users.users[0] as NakamaAPI.ApiUser + + + await start_socket() + +func start_socket() -> void: + + socket = Nakama.create_socket_from(client) + + await socket.connect_async(session) + + socket.connected.connect(onSocketConnected) + socket.closed.connect(onSocketClosed) + socket.received_error.connect(onSocketReceivedError) + + socket.received_match_presence.connect(onMatchPresence) + socket.received_match_state.connect(onMatchState) + + socket.received_channel_message.connect(onChannelMessage) + socket.received_party_presence.connect(onPartyPresence) + + socket.received_notification.connect(_received_notification) + + setup_multiplayer_bridge() + + user_logged_in.emit() + +func is_authority() -> bool: + return multiplayer.get_unique_id() == 1 + +func is_in_party() -> bool: + return party != null + +func create_party(is_open: bool) -> NakamaRTAPI.Party: + party = await socket.create_party_async(is_open, max_party_count) + + return party + +func get_party_id() -> String: + return party.party_id if party else "" + +func setup_multiplayer_bridge(): + multiplayerBridge = NakamaMultiplayerBridge.new(NakamaManager.socket) + multiplayerBridge.match_join_error.connect(onMatchJoinError) + + multiplayer.multiplayer_peer = multiplayerBridge.multiplayer_peer + + multiplayer.peer_connected.connect(onPeerConnected) + multiplayer.peer_disconnected.connect(onPeerDisconnected) + +func onMatchJoinError(error): + print("Unable to join match: " + error.message) + +func onMatchJoin(): + print("joined Match with id: " + NakamaManager.multiplayerBridge.match_id) + +func onMatchPresence(presence : NakamaRTAPI.MatchPresenceEvent): + match_presence_received.emit(presence) + +func onPeerConnected(id: int): + print("Peer connected id is : " + str(id)) + if id == 0: + printerr("Nao foi adicionado id 0") + return + + if !Players.has(id): + Players[id] = { + "name" : id, + } + if !Players.has(multiplayer.get_unique_id()): + Players[multiplayer.get_unique_id()]= { + "name" : multiplayer.get_unique_id(), + } + + peer_connnected_in_match.emit(id) + #update_peer_in_network.rpc_id(id) + +@rpc("any_peer") +func update_peer_in_network() -> void: + + var multiplayer_id = multiplayer.get_unique_id() + _update_peer_in_network.rpc(multiplayer_id, current_user.id) + +@rpc("any_peer","call_local") +func _update_peer_in_network(multiplayer_id, nakama_id) -> void: + + var query = await client.get_users_async(session, [nakama_id]) + + var user = query.users[0] + + Players[multiplayer_id] = user + +func onPeerDisconnected(id): + print("Peer disconnected id is : " + str(id)) + var user = Players[id] + peer_disconnnected_in_match.emit(user, id) + +func onMatchState(state : NakamaRTAPI.MatchData): + match_state_received.emit(state) + +func onSocketConnected(): + print("Socket Connected") + socket_connected.emit() + +func onSocketClosed(): + print("Socket Closed") + socket_closed.emit() + +func onSocketReceivedError(err): + print("Socket Error:" + str(err)) + socket_error_received.emit(err) + +func onPartyPresence(presence : NakamaRTAPI.PartyPresenceEvent): + print("JOINED PARTY " + presence.party_id) + party_presence_received.emit(presence) + +func _received_notification(p_notification: NakamaAPI.ApiNotification) -> void: + + # Emit proxy signal for external listeners + notification_received.emit(p_notification) + + # Also show in notification container + var notification_type = \ + NotificationContainer.nakama_notification_code_to_notification(p_notification.code) + + NotificationContainer.create_notification( + p_notification.subject, + notification_type + ) + +## Toda vez que uma nova mensagem for mandada para o servidor +## essa função será invocada +func onChannelMessage(message : NakamaAPI.ApiChannelMessage) -> void: + channel_message_received.emit(message) + #var content = JSON.parse_string(message.content) + #if content.type == 0: + # + #var chat_id = content.id + #var display_name : String = "" + #var current_conversation : TextEdit = null + # + #if players_username_display_name.has(chat_id): + # + #display_name = players_username_display_name[chat_id] + #current_conversation = username_container.get_node(display_name) + #elif group_chats.has(chat_id): + # + #var sender_info_json : NakamaAPI.ApiUsers = \ + #await NakamaManager.client.get_users_async(NakamaManager.session, [message.sender_id]) + #var all_user_info = sender_info_json.serialize() + #var curr_user = all_user_info.users[0] +# + #display_name = curr_user.display_name + #current_conversation = username_container.get_node(group_chats[chat_id]) + #else: + #return + # + #current_conversation.text += display_name + ": " + str(content.message) + "\n" + #current_conversation.scroll_vertical = current_conversation.text.count("\n") + # + #elif content.type == 1 && party == null: + # + #channel_message_panel.show() + #party = {"id" : content.partyID} + #channel_message_label.text = str(content.message) + +func get_client_scheme() -> String: + + match client_scheme: + ClientScheme.HTTP: + return "http" + ClientScheme.HTTPS: + return "https" + + return "http" + +# Update the current user's account on the server. +# @param p_username - The new username for the user. +# @param p_display_name - A new display name for the user. +# @param p_avatar_url - A new avatar url for the user. +# @param p_lang_tag - A new language tag in BCP-47 format for the user. +# @param p_location - A new location for the user. +# @param p_timezone - New timezone information for the user. +# Returns a task which represents the asynchronous operation. +func update_account(username = null, + display_name = null, + avatar_url = null, + lang_tag = null, + location = null, + timezone = null) -> NakamaAsyncResult: + return await client.update_account_async(session, + username, + display_name, + avatar_url, + lang_tag, + location, + timezone) + +func leave_group(id: String) -> NakamaAsyncResult: + return await client.leave_group(session, id) + + +func write_leaderboard(leaderboard_name: String, + score: float, + subscore: float, + metadata: Dictionary) -> NakamaAPI.ApiLeaderboardRecord: + return await client.write_leaderboard_record_async(session, + leaderboard_name, + score, + subscore, + JSON.stringify(metadata)) + +func is_match_created_by_user(created_match : NakamaRTAPI.Match) -> bool: + return created_match.presences[0].user_id == current_user.id + +#region Friend Management + +## Add one or more friends by id or username. +## [br][br] +## [param ids]: The ids of the users to add or invite as friends. +## [param usernames]: The usernames of the users to add as friends. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func add_friends(ids: PackedStringArray = [], usernames: PackedStringArray = []) -> NakamaAsyncResult: + return await client.add_friends_async(session, ids, usernames) + +## Delete one or more friends by id or username. +## [br][br] +## [param ids]: The ids of the users to delete as friends. +## [param usernames]: The usernames of the users to delete as friends. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func delete_friends(ids: PackedStringArray, usernames: PackedStringArray = []) -> NakamaAsyncResult: + return await client.delete_friends_async(session, ids, usernames) + +## Block one or more friends by id or username. +## [br][br] +## [param ids]: The ids of the users to block. +## [param usernames]: The usernames of the users to block. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func block_friends(ids: PackedStringArray, usernames: PackedStringArray = []) -> NakamaAsyncResult: + return await client.block_friends_async(session, ids, usernames) + +## List all friends for the current user. +## [br][br] +## [param state]: Filter by friend state (optional). +## [param limit]: Maximum number of records to return (optional). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiFriendList containing the user's friends. +func list_friends(state = null, limit = null, cursor = null): + return await client.list_friends_async(session, state, limit, cursor) + +## Import Facebook friends and add them to the user's account. +## [br][br] +## [param token]: The Facebook access token. +## [param reset]: Whether to reset the friend list (optional). +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func import_facebook_friends(token: String, reset = null) -> NakamaAsyncResult: + return await client.import_facebook_friends_async(session, token, reset) + +## Import Steam friends and add them to the user's account. +## [br][br] +## [param token]: The Steam access token. +## [param reset]: Whether to reset the friend list (optional). +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func import_steam_friends(token: String, reset = null): + return await client.import_steam_friends_async(session, token, reset) + +#endregion + +#region Group Management + +## Create a new group with the specified parameters. +## [br][br] +## [param group_name]: The name of the group. +## [param description]: A description for the group (default: empty). +## [param avatar_url]: An avatar URL for the group (optional). +## [param lang_tag]: A language tag for the group (optional). +## [param open]: Whether the group is open for anyone to join (default: true). +## [param max_count]: Maximum number of members allowed (default: 100). +## [br][br] +## Returns a NakamaAPI.ApiGroup representing the created group. +func create_group(group_name: String, description: String = "", avatar_url = null, lang_tag = null, open: bool = true, max_count: int = 100): + return await client.create_group_async(session, group_name, description, avatar_url, lang_tag, open, max_count) + +## Delete a group by ID. Only group owners can delete groups. +## [br][br] +## [param group_id]: The ID of the group to delete. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func delete_group(group_id: String) -> NakamaAsyncResult: + return await client.delete_group_async(session, group_id) + +## Join an existing group by ID. +## [br][br] +## [param group_id]: The ID of the group to join. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func join_group(group_id: String) -> NakamaAsyncResult: + return await client.join_group_async(session, group_id) + +## List and filter groups based on various criteria. +## [br][br] +## [param group_name]: Filter by group name (optional). +## [param limit]: Maximum number of groups to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [param lang_tag]: Filter by language tag (optional). +## [param members]: Filter by member count (optional). +## [param open]: Filter by open status (optional). +## [br][br] +## Returns a NakamaAPI.ApiGroupList containing matching groups. +func list_groups(group_name = null, limit: int = 10, cursor = null, lang_tag = null, members = null, open = null): + return await client.list_groups_async(session, group_name, limit, cursor, lang_tag, members, open) + +## List all groups a specific user is a member of. +## [br][br] +## [param user_id]: The ID of the user. +## [param state]: Filter by membership state (optional). +## [param limit]: Maximum number of groups to return (optional). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiUserGroupList containing the user's groups. +func list_user_groups(user_id: String, state = null, limit = null, cursor = null): + return await client.list_user_groups_async(session, user_id, state, limit, cursor) + +## List all users that are members of a specific group. +## [br][br] +## [param group_id]: The ID of the group. +## [param state]: Filter by user state in the group (optional). +## [param limit]: Maximum number of users to return (optional). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiGroupUserList containing the group's members. +func list_group_users(group_id: String, state = null, limit = null, cursor = null): + return await client.list_group_users_async(session, group_id, state, limit, cursor) + +## Update properties of an existing group. +## [br][br] +## [param group_id]: The ID of the group to update. +## [param group_name]: New name for the group (optional). +## [param description]: New description for the group (optional). +## [param avatar_url]: New avatar URL for the group (optional). +## [param lang_tag]: New language tag for the group (optional). +## [param open]: New open status for the group (optional). +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func update_group(group_id: String, group_name = null, description = null, avatar_url = null, lang_tag = null, open = null) -> NakamaAsyncResult: + return await client.update_group_async(session, group_id, group_name, description, avatar_url, lang_tag, open) + +## Add one or more users to a group. +## [br][br] +## [param group_id]: The ID of the group. +## [param ids]: Array of user IDs to add to the group. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func add_group_users(group_id: String, ids: PackedStringArray) -> NakamaAsyncResult: + return await client.add_group_users_async(session, group_id, ids) + +## Kick one or more users from a group. +## [br][br] +## [param group_id]: The ID of the group. +## [param ids]: Array of user IDs to kick from the group. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func kick_group_users(group_id: String, ids: PackedStringArray) -> NakamaAsyncResult: + return await client.kick_group_users_async(session, group_id, ids) + +## Promote one or more users to administrators in a group. +## [br][br] +## [param group_id]: The ID of the group. +## [param ids]: Array of user IDs to promote. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func promote_group_users(group_id: String, ids: PackedStringArray) -> NakamaAsyncResult: + return await client.promote_group_users_async(session, group_id, ids) + +## Demote one or more administrators to regular members in a group. +## [br][br] +## [param group_id]: The ID of the group. +## [param user_ids]: Array of user IDs to demote. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func demote_group_users(group_id: String, user_ids: Array): + return await client.demote_group_users_async(session, group_id, user_ids) + +#endregion + +#region Leaderboard Management + +## List records from a leaderboard with optional filtering. +## [br][br] +## [param leaderboard_id]: The ID of the leaderboard. +## [param owner_ids]: Filter by owner IDs (optional). +## [param expiry]: Filter by expiry time (optional). +## [param limit]: Maximum number of records to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiLeaderboardRecordList containing the leaderboard records. +func list_leaderboard_records(leaderboard_id: String, owner_ids = null, expiry = null, limit: int = 10, cursor = null): + return await client.list_leaderboard_records_async(session, leaderboard_id, owner_ids, expiry, limit, cursor) + +## List leaderboard records around a specific owner. +## [br][br] +## [param leaderboard_id]: The ID of the leaderboard. +## [param owner_id]: The ID of the owner to center the results around. +## [param expiry]: Filter by expiry time (optional). +## [param limit]: Maximum number of records to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiLeaderboardRecordList centered around the owner. +func list_leaderboard_records_around_owner(leaderboard_id: String, owner_id: String, expiry = null, limit: int = 10, cursor = null): + return await client.list_leaderboard_records_around_owner_async(session, leaderboard_id, owner_id, expiry, limit, cursor) + +## Delete the current user's record from a leaderboard. +## [br][br] +## [param leaderboard_id]: The ID of the leaderboard. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func delete_leaderboard_record(leaderboard_id: String) -> NakamaAsyncResult: + return await client.delete_leaderboard_record_async(session, leaderboard_id) + +#endregion + +#region Tournament Management + +## Join a tournament by ID. +## [br][br] +## [param tournament_id]: The ID of the tournament to join. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func join_tournament(tournament_id: String) -> NakamaAsyncResult: + return await client.join_tournament_async(session, tournament_id) + +## List available tournaments with filtering options. +## [br][br] +## [param category_start]: Filter by category start range. +## [param category_end]: Filter by category end range. +## [param start_time]: Filter by start time. +## [param end_time]: Filter by end time. +## [param limit]: Maximum number of tournaments to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiTournamentList containing available tournaments. +func list_tournaments(category_start: int, category_end: int, start_time: int, end_time: int, limit: int = 10, cursor = null): + return await client.list_tournaments_async(session, category_start, category_end, start_time, end_time, limit, cursor) + +## List records from a tournament with optional filtering. +## [br][br] +## [param tournament_id]: The ID of the tournament. +## [param owner_ids]: Filter by owner IDs (optional). +## [param limit]: Maximum number of records to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [param expiry]: Filter by expiry time (optional). +## [br][br] +## Returns a NakamaAPI.ApiTournamentRecordList containing tournament records. +func list_tournament_records(tournament_id: String, owner_ids = null, limit: int = 10, cursor = null, expiry = null): + return await client.list_tournament_records_async(session, tournament_id, owner_ids, limit, cursor, expiry) + +## List tournament records around a specific owner. +## [br][br] +## [param tournament_id]: The ID of the tournament. +## [param owner_id]: The ID of the owner to center the results around. +## [param limit]: Maximum number of records to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [param expiry]: Filter by expiry time (optional). +## [br][br] +## Returns a NakamaAPI.ApiTournamentRecordList centered around the owner. +func list_tournament_records_around_owner(tournament_id: String, owner_id: String, limit: int = 10, cursor = null, expiry = null): + return await client.list_tournament_records_around_owner_async(session, tournament_id, owner_id, limit, cursor, expiry) + +## Submit a score record to a tournament. +## [br][br] +## [param tournament_id]: The ID of the tournament. +## [param score]: The score value to submit. +## [param subscore]: An optional secondary score for tie-breaking (default: 0). +## [param metadata]: Optional metadata to attach to the record. +## [br][br] +## Returns a NakamaAPI.ApiLeaderboardRecord representing the submitted record. +func write_tournament_record(tournament_id: String, score: int, subscore: int = 0, metadata = null): + return await client.write_tournament_record_async(session, tournament_id, score, subscore, metadata) + +#endregion + +#region Storage Management + +## Read one or more storage objects by their IDs. +## [br][br] +## [param ids]: Array of storage object IDs to read. Each ID should contain collection, key, and user_id. +## [br][br] +## Returns a NakamaAPI.ApiStorageObjects containing the requested objects. +func read_storage_objects(ids: Array): + return await client.read_storage_objects_async(session, ids) + +## Write one or more storage objects. +## [br][br] +## [param objects]: Array of storage objects to write. Each object should specify collection, key, value, etc. +## [br][br] +## Returns a NakamaAPI.ApiStorageObjectAcks containing acknowledgments of the written objects. +func write_storage_objects(objects: Array): + return await client.write_storage_objects_async(session, objects) + +## Delete one or more storage objects by their IDs. +## [br][br] +## [param ids]: Array of storage object IDs to delete. Each ID should contain collection, key, and user_id. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func delete_storage_objects(ids: Array) -> NakamaAsyncResult: + return await client.delete_storage_objects_async(session, ids) + +## List storage objects in a collection with optional filtering. +## [br][br] +## [param collection]: The collection to list objects from. +## [param user_id]: Filter by user ID (default: empty for current user). +## [param limit]: Maximum number of objects to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiStorageObjectList containing the objects. +func list_storage_objects(collection: String, user_id: String = "", limit: int = 10, cursor = null): + return await client.list_storage_objects_async(session, collection, user_id, limit, cursor) + +## List storage objects for a specific user in a collection. +## [br][br] +## [param collection]: The collection to list objects from. +## [param user_id]: The ID of the user whose objects to list. +## [param limit]: Maximum number of objects to return. +## [param cursor]: Pagination cursor from previous request. +## [br][br] +## Returns a NakamaAPI.ApiStorageObjectList containing the user's objects. +func list_users_storage_objects(collection: String, user_id: String, limit: int, cursor: String): + return await client.list_users_storage_objects_async(session, collection, user_id, limit, cursor) + +#endregion + +#region Match Management + +## List available matches based on filtering criteria. +## [br][br] +## [param min_size]: Minimum number of match participants. +## [param max_size]: Maximum number of match participants. +## [param limit]: Maximum number of matches to return. +## [param authoritative]: Filter for authoritative matches. +## [param label]: Filter by match label. +## [param query]: Additional query string for filtering. +## [br][br] +## Returns a NakamaAPI.ApiMatchList containing available matches. +func list_matches(min_size: int, max_size: int, limit: int, authoritative: bool, label: String, query: String): + return await client.list_matches_async(session, min_size, max_size, limit, authoritative, label, query) + +#endregion + +#region Notification Management + +## List notifications for the current user. +## [br][br] +## [param limit]: Maximum number of notifications to return (default: 10). +## [param cacheable_cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiNotificationList containing the user's notifications. +func list_notifications(limit: int = 10, cacheable_cursor = null): + return await client.list_notifications_async(session, limit, cacheable_cursor) + +## Delete one or more notifications by their IDs. +## [br][br] +## [param ids]: Array of notification IDs to delete. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func delete_notifications(ids: PackedStringArray) -> NakamaAsyncResult: + return await client.delete_notifications_async(session, ids) + +#endregion + +#region Account Management + +## Get the current user's account information. +## [br][br] +## Returns a NakamaAPI.ApiAccount containing the account details. +func get_account(): + return await client.get_account_async(session) + +## Get information about one or more users. +## [br][br] +## [param ids]: Array of user IDs to fetch. +## [param usernames]: Array of usernames to fetch (optional). +## [param facebook_ids]: Array of Facebook IDs to fetch (optional). +## [br][br] +## Returns a NakamaAPI.ApiUsers containing the user information. +func get_users(ids: PackedStringArray, usernames = null, facebook_ids = null): + return await client.get_users_async(session, ids, usernames, facebook_ids) + +## Delete the current user's account permanently. +## [br][br] +## [b]Warning:[/b] This action is irreversible! +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func delete_account() -> NakamaAsyncResult: + return await client.delete_account_async(session) + +#endregion + +#region Social Authentication Links + +## Link a custom ID to the current user's account. +## [br][br] +## [param id]: The custom ID to link. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func link_custom(id: String) -> NakamaAsyncResult: + return await client.link_custom_async(session, id) + +## Link a device ID to the current user's account. +## [br][br] +## [param id]: The device ID to link. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func link_device(id: String) -> NakamaAsyncResult: + return await client.link_device_async(session, id) + +## Link an email and password to the current user's account. +## [br][br] +## [param email]: The email address to link. +## [param password]: The password to associate with the email. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func link_email(email: String, password: String) -> NakamaAsyncResult: + return await client.link_email_async(session, email, password) + +## Link a Facebook account to the current user's account. +## [br][br] +## [param token]: The Facebook access token. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func link_facebook(token: String) -> NakamaAsyncResult: + return await client.link_facebook_async(session, token) + +## Link a Google account to the current user's account. +## [br][br] +## [param token]: The Google access token. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func link_google(token: String) -> NakamaAsyncResult: + return await client.link_google_async(session, token) + +## Link a Steam account to the current user's account. +## [br][br] +## [param token]: The Steam access token. +## [param sync]: Whether to sync Steam profile data (default: false). +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func link_steam(token: String, sync: bool = false) -> NakamaAsyncResult: + return await client.link_steam_async(session, token, sync) + +## Link an Apple account to the current user's account. +## [br][br] +## [param token]: The Apple ID token. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func link_apple(token: String) -> NakamaAsyncResult: + return await client.link_apple_async(session, token) + +## Unlink a custom ID from the current user's account. +## [br][br] +## [param id]: The custom ID to unlink. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func unlink_custom(id: String) -> NakamaAsyncResult: + return await client.unlink_custom_async(session, id) + +## Unlink a device ID from the current user's account. +## [br][br] +## [param id]: The device ID to unlink. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func unlink_device(id: String) -> NakamaAsyncResult: + return await client.unlink_device_async(session, id) + +## Unlink an email from the current user's account. +## [br][br] +## [param email]: The email address to unlink. +## [param password]: The password associated with the email. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func unlink_email(email: String, password: String) -> NakamaAsyncResult: + return await client.unlink_email_async(session, email, password) + +## Unlink a Facebook account from the current user's account. +## [br][br] +## [param token]: The Facebook access token. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func unlink_facebook(token: String) -> NakamaAsyncResult: + return await client.unlink_facebook_async(session, token) + +## Unlink a Google account from the current user's account. +## [br][br] +## [param token]: The Google access token. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func unlink_google(token: String) -> NakamaAsyncResult: + return await client.unlink_google_async(session, token) + +## Unlink a Steam account from the current user's account. +## [br][br] +## [param token]: The Steam access token. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func unlink_steam(token: String) -> NakamaAsyncResult: + return await client.unlink_steam_async(session, token) + +## Unlink an Apple account from the current user's account. +## [br][br] +## [param token]: The Apple ID token. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func unlink_apple(token: String) -> NakamaAsyncResult: + return await client.unlink_apple_async(session, token) + +#endregion + +#region RPC and Session Management + +## Execute a remote procedure call (RPC) on the server. +## [br][br] +## [param id]: The ID/name of the RPC function to call. +## [param payload]: Optional payload data to send with the RPC call. +## [br][br] +## Returns a NakamaAPI.ApiRpc containing the RPC response. +func rpc_func(id: String, payload = null): + return await client.rpc_async(session, id, payload) + +## Logout and invalidate the current session. +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func session_logout() -> NakamaAsyncResult: + return await client.session_logout_async(session) + +## Refresh the current session to extend its validity. +## [br][br] +## [param vars]: Optional variables to include in the refreshed session. +## [br][br] +## Returns a new NakamaSession with updated expiration time. +func session_refresh(vars = null) -> NakamaSession: + return await client.session_refresh_async(session, vars) + +#endregion + +#region In-App Purchases and Subscriptions + +## Validate an Apple in-app purchase receipt. +## [br][br] +## [param receipt]: The Apple receipt data to validate. +## [br][br] +## Returns a NakamaAPI.ApiValidatePurchaseResponse containing the validation result. +func validate_purchase_apple(receipt: String): + return await client.validate_purchase_apple_async(session, receipt) + +## Validate a Google Play in-app purchase receipt. +## [br][br] +## [param receipt]: The Google Play receipt data to validate. +## [br][br] +## Returns a NakamaAPI.ApiValidatePurchaseResponse containing the validation result. +func validate_purchase_google(receipt: String): + return await client.validate_purchase_google_async(session, receipt) + +## Validate a Huawei in-app purchase receipt. +## [br][br] +## [param receipt]: The Huawei receipt data to validate. +## [param signature]: The purchase signature from Huawei. +## [br][br] +## Returns a NakamaAPI.ApiValidatePurchaseResponse containing the validation result. +func validate_purchase_huawei(receipt: String, signature: String): + return await client.validate_purchase_huawei_async(session, receipt, signature) + +## Get information about a subscription by product ID. +## [br][br] +## [param product_id]: The product ID of the subscription. +## [br][br] +## Returns a NakamaAPI.ApiValidatedSubscription containing subscription details. +func get_subscription(product_id: String): + return await client.get_subscription_async(session, product_id) + +## List all active subscriptions for the current user. +## [br][br] +## [param limit]: Maximum number of subscriptions to return (default: 10). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiSubscriptionList containing the user's subscriptions. +func list_subscriptions(limit: int = 10, cursor = null): + return await client.list_subscriptions_async(session, limit, cursor) + +## Validate an Apple subscription receipt. +## [br][br] +## [param receipt]: The Apple subscription receipt data. +## [param persist]: Whether to persist the subscription (default: true). +## [br][br] +## Returns a NakamaAPI.ApiValidateSubscriptionResponse containing the validation result. +func validate_subscription_apple(receipt: String, persist: bool = true): + return await client.validate_subscription_apple_async(session, receipt, persist) + +## Validate a Google Play subscription receipt. +## [br][br] +## [param receipt]: The Google Play subscription receipt data. +## [param persist]: Whether to persist the subscription (default: true). +## [br][br] +## Returns a NakamaAPI.ApiValidateSubscriptionResponse containing the validation result. +func validate_subscription_google(receipt: String, persist: bool = true): + return await client.validate_subscription_google_async(session, receipt, persist) + +#endregion + +#region Events and Channel Messages + +## Send a custom analytics event to the server. +## [br][br] +## [param event_name]: The name of the event to send. +## [param properties]: Dictionary of properties/metadata to attach to the event (default: empty). +## [br][br] +## Returns a NakamaAsyncResult which represents the asynchronous operation. +func send_event(event_name: String, properties: Dictionary = {}) -> NakamaAsyncResult: + return await client.event_async(session, event_name, properties) + +## List messages from a specific channel. +## [br][br] +## [param channel_id]: The ID of the channel to list messages from. +## [param limit]: Maximum number of messages to return (default: 1). +## [param forward]: Whether to list messages in chronological order (default: true). +## [param cursor]: Pagination cursor from previous request (optional). +## [br][br] +## Returns a NakamaAPI.ApiChannelMessageList containing the channel messages. +func list_channel_messages(channel_id: String, limit: int = 1, forward: bool = true, cursor = null): + return await client.list_channel_messages_async(session, channel_id, limit, forward, cursor) + +#endregion + +#region Socket Helper Functions + +## Check if the socket is connected. +## [br][br] +## Returns true if socket exists and is connected, false otherwise. +func is_socket_connected() -> bool: + return socket != null and socket.is_connected_to_host() + +## Check if the socket exists (may not be connected yet). +## [br][br] +## Returns true if socket has been created, false otherwise. +func has_socket() -> bool: + return socket != null + +## Get the current socket connection state. +## [br][br] +## Returns the WebSocketPeer state if socket exists, otherwise returns CLOSED state. +func get_socket_state(): + if socket and socket.socket: + return socket.socket.get_ready_state() + return WebSocketPeer.STATE_CLOSED + +#endregion + diff --git a/Nakama/NakamaManager.gd.uid b/Nakama/NakamaManager.gd.uid new file mode 100644 index 0000000..0a8e5d3 --- /dev/null +++ b/Nakama/NakamaManager.gd.uid @@ -0,0 +1 @@ +uid://wpi81wo6ee7m diff --git a/Nakama/NakamaManager.tscn b/Nakama/NakamaManager.tscn new file mode 100644 index 0000000..f9baf8f --- /dev/null +++ b/Nakama/NakamaManager.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bt7i7jg8un7h0"] + +[ext_resource type="Script" uid="uid://wpi81wo6ee7m" path="res://Nakama/NakamaManager.gd" id="1_2fvo7"] + +[node name="NakamaManager" type="Node"] +script = ExtResource("1_2fvo7") diff --git a/Nakama/TimeUtils.gd b/Nakama/TimeUtils.gd new file mode 100644 index 0000000..e0f81e3 --- /dev/null +++ b/Nakama/TimeUtils.gd @@ -0,0 +1,191 @@ +extends RefCounted +class_name TimeUtils + +## A utility class for parsing and formatting timestamps from Nakama. +## [br][br] +## Nakama uses ISO 8601 format (e.g., "2025-10-20T23:59:20Z") +## [br][br] +## [b]Usage Examples:[/b] +## [codeblock] +## # Parse a timestamp +## var formatted = TimeUtils.parse_nakama_timestamp("2025-10-20T23:59:20Z") +## +## # Get relative time: "5 minutes ago" +## var relative = formatted.get_relative_time() +## +## # Get full datetime: "Oct 20, 2025 11:59 PM" +## var full = formatted.get_full_datetime() +## +## # Get custom format +## var custom = formatted.get_custom_format("%Y-%m-%d at %I:%M %p") +## # Result: "2025-10-20 at 11:59 PM" +## +## # Quick helpers +## var relative_quick = TimeUtils.get_relative_time("2025-10-20T23:59:20Z") +## var full_quick = TimeUtils.get_full_datetime("2025-10-20T23:59:20Z") +## [/codeblock] + +## Represents a parsed timestamp with various formatting options +class FormattedTime: + var unix_time: int + var datetime_dict: Dictionary + + func _init(p_unix_time: int, p_datetime_dict: Dictionary): + unix_time = p_unix_time + datetime_dict = p_datetime_dict + + ## Get time in "X minutes/hours/days ago" format + func get_relative_time() -> String: + var current_time = Time.get_unix_time_from_system() + var diff = current_time - unix_time + + if diff < 60: + return "Just now" + elif diff < 3600: # Less than 1 hour + var minutes = int(diff / 60) + return "%d minute%s ago" % [minutes, "s" if minutes != 1 else ""] + elif diff < 86400: # Less than 1 day + var hours = int(diff / 3600) + return "%d hour%s ago" % [hours, "s" if hours != 1 else ""] + elif diff < 604800: # Less than 1 week + var days = int(diff / 86400) + return "%d day%s ago" % [days, "s" if days != 1 else ""] + elif diff < 2592000: # Less than 30 days + var weeks = int(diff / 604800) + return "%d week%s ago" % [weeks, "s" if weeks != 1 else ""] + elif diff < 31536000: # Less than 1 year + var months = int(diff / 2592000) + return "%d month%s ago" % [months, "s" if months != 1 else ""] + else: + var years = int(diff / 31536000) + return "%d year%s ago" % [years, "s" if years != 1 else ""] + + ## Get formatted date and time: "Oct 20, 2025 11:59 PM" + func get_full_datetime() -> String: + var month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + var month = month_names[datetime_dict.month - 1] + var hour = datetime_dict.hour + var period = "AM" + + if hour >= 12: + period = "PM" + if hour > 12: + hour -= 12 + elif hour == 0: + hour = 12 + + return "%s %d, %d %d:%02d %s" % [ + month, + datetime_dict.day, + datetime_dict.year, + hour, + datetime_dict.minute, + period + ] + + ## Get short date format: "Oct 20, 2025" + func get_short_date() -> String: + var month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + var month = month_names[datetime_dict.month - 1] + return "%s %d, %d" % [month, datetime_dict.day, datetime_dict.year] + + ## Get time only: "11:59 PM" + func get_time_only() -> String: + var hour = datetime_dict.hour + var period = "AM" + + if hour >= 12: + period = "PM" + if hour > 12: + hour -= 12 + elif hour == 0: + hour = 12 + + return "%d:%02d %s" % [hour, datetime_dict.minute, period] + + ## Get ISO format: "2025-10-20T23:59:20Z" + func get_iso_format() -> String: + return "%04d-%02d-%02dT%02d:%02d:%02dZ" % [ + datetime_dict.year, + datetime_dict.month, + datetime_dict.day, + datetime_dict.hour, + datetime_dict.minute, + datetime_dict.second + ] + + ## Get custom format using strftime-style format codes + ## Available codes: + ## %Y - Year (4 digits), %y - Year (2 digits) + ## %m - Month (01-12), %d - Day (01-31) + ## %H - Hour 24h (00-23), %I - Hour 12h (01-12) + ## %M - Minute (00-59), %S - Second (00-59) + ## %p - AM/PM + func get_custom_format(format_string: String) -> String: + var result = format_string + var hour_12 = datetime_dict.hour + var period = "AM" + + if hour_12 >= 12: + period = "PM" + if hour_12 > 12: + hour_12 -= 12 + elif hour_12 == 0: + hour_12 = 12 + + result = result.replace("%Y", "%04d" % datetime_dict.year) + result = result.replace("%y", "%02d" % (datetime_dict.year % 100)) + result = result.replace("%m", "%02d" % datetime_dict.month) + result = result.replace("%d", "%02d" % datetime_dict.day) + result = result.replace("%H", "%02d" % datetime_dict.hour) + result = result.replace("%I", "%02d" % hour_12) + result = result.replace("%M", "%02d" % datetime_dict.minute) + result = result.replace("%S", "%02d" % datetime_dict.second) + result = result.replace("%p", period) + + return result + +## Parse an ISO 8601 timestamp string from Nakama into a FormattedTime object +## Example input: "2025-10-20T23:59:20Z" +static func parse_nakama_timestamp(timestamp: String) -> FormattedTime: + # Remove the 'Z' suffix if present + var clean_timestamp = timestamp.replace("Z", "") + + # Split date and time + var parts = clean_timestamp.split("T") + if parts.size() != 2: + push_error("Invalid timestamp format: " + timestamp) + return null + + var date_parts = parts[0].split("-") + var time_parts = parts[1].split(":") + + if date_parts.size() != 3 or time_parts.size() != 3: + push_error("Invalid timestamp format: " + timestamp) + return null + + var datetime_dict = { + "year": int(date_parts[0]), + "month": int(date_parts[1]), + "day": int(date_parts[2]), + "hour": int(time_parts[0]), + "minute": int(time_parts[1]), + "second": int(time_parts[2]) + } + + # Convert to Unix timestamp + var unix_time = Time.get_unix_time_from_datetime_dict(datetime_dict) + + return FormattedTime.new(unix_time, datetime_dict) + +## Quick helper to get relative time from a Nakama timestamp string +static func get_relative_time(timestamp: String) -> String: + var formatted = parse_nakama_timestamp(timestamp) + return formatted.get_relative_time() if formatted else "Unknown" + +## Quick helper to get full datetime from a Nakama timestamp string +static func get_full_datetime(timestamp: String) -> String: + var formatted = parse_nakama_timestamp(timestamp) + return formatted.get_full_datetime() if formatted else "Unknown" diff --git a/Nakama/TimeUtils.gd.uid b/Nakama/TimeUtils.gd.uid new file mode 100644 index 0000000..3459561 --- /dev/null +++ b/Nakama/TimeUtils.gd.uid @@ -0,0 +1 @@ +uid://dmkfgv3ga682a diff --git a/NotificationContainer/NotificationContainer.gd b/NotificationContainer/NotificationContainer.gd new file mode 100644 index 0000000..98573ab --- /dev/null +++ b/NotificationContainer/NotificationContainer.gd @@ -0,0 +1,50 @@ +extends VBoxContainer + +enum NakamaExceptionError { + SERVER_ERROR = 13 +} + + +enum NotificationType { + WARNING, + OK, + ERROR +} + +@export var notification_label_scene : PackedScene +@export_range(2,15,0.1) var notification_time : float + +func create_notification(text: String, + notification_type: NotificationType = NotificationType.OK) -> void: + + var notification_label : NotificationLabel = notification_label_scene.instantiate() + + add_child(notification_label) + + notification_label.set_wait_time(notification_time) + + match notification_type: + NotificationType.OK: + notification_label.show_accept_label(text) + NotificationType.ERROR: + notification_label.show_error_label(text) + NotificationType.WARNING: + notification_label.show_warning_label(text) + +func handle_exception(status_code: int) -> void: + match status_code: + NakamaExceptionError.SERVER_ERROR: + create_notification("Falhou a conexão ao servidor!", + NotificationType.ERROR) + _: + create_notification("Erro credenciais incorretas!", + NotificationType.ERROR) + +static func nakama_notification_code_to_notification(nakama_code: int) -> NotificationType: + + var notification_code : NotificationType + + if nakama_code >= 0 and nakama_code <= 7: + notification_code = NotificationType.OK + + return notification_code diff --git a/NotificationContainer/NotificationContainer.gd.uid b/NotificationContainer/NotificationContainer.gd.uid new file mode 100644 index 0000000..c99c54d --- /dev/null +++ b/NotificationContainer/NotificationContainer.gd.uid @@ -0,0 +1 @@ +uid://cxuebji4wws4w diff --git a/NotificationContainer/NotificationContainer.tscn b/NotificationContainer/NotificationContainer.tscn new file mode 100644 index 0000000..6c36310 --- /dev/null +++ b/NotificationContainer/NotificationContainer.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=3 format=3 uid="uid://bg6xgsfypi4gr"] + +[ext_resource type="Script" uid="uid://cxuebji4wws4w" path="res://NotificationContainer/NotificationContainer.gd" id="1_n2vfd"] +[ext_resource type="PackedScene" uid="uid://drftmonotxben" path="res://NotificationLabel/NotificationLabel.tscn" id="2_15k87"] + +[node name="NotificationContainer" type="VBoxContainer"] +z_index = 1 +custom_minimum_size = Vector2(200, 0) +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 0 +grow_vertical = 2 +size_flags_horizontal = 8 +alignment = 2 +script = ExtResource("1_n2vfd") +notification_label_scene = ExtResource("2_15k87") +notification_time = 5.0 diff --git a/NotificationLabel/GreenDialog.tres b/NotificationLabel/GreenDialog.tres new file mode 100644 index 0000000..ef9f0ca --- /dev/null +++ b/NotificationLabel/GreenDialog.tres @@ -0,0 +1,13 @@ +[gd_resource type="StyleBoxFlat" format=3 uid="uid://dp30gd7s0wc2n"] + +[resource] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.364728, 0.679784, 0.469216, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 +shadow_size = 3 diff --git a/NotificationLabel/NotificationLabel.gd b/NotificationLabel/NotificationLabel.gd new file mode 100644 index 0000000..a03c3f1 --- /dev/null +++ b/NotificationLabel/NotificationLabel.gd @@ -0,0 +1,30 @@ +extends Label +class_name NotificationLabel + +@export var green_dialog : StyleBoxFlat +@export var red_dialog : StyleBoxFlat +@export var yellow_dialog : StyleBoxFlat + +@onready var timer : Timer = $Timer + +func set_wait_time(time: float) -> void: + timer.wait_time = time + +func show_accept_label(new_text: String) -> void: + add_theme_stylebox_override("normal", green_dialog) + text = new_text + timer.start() + +func show_error_label(new_text: String) -> void: + add_theme_stylebox_override("normal", red_dialog) + text = new_text + timer.start() + +func show_warning_label(new_text: String) -> void: + add_theme_stylebox_override("normal", yellow_dialog) + text = new_text + timer.start() + + +func _on_timer_timeout() -> void: + queue_free() diff --git a/NotificationLabel/NotificationLabel.gd.uid b/NotificationLabel/NotificationLabel.gd.uid new file mode 100644 index 0000000..687d6cf --- /dev/null +++ b/NotificationLabel/NotificationLabel.gd.uid @@ -0,0 +1 @@ +uid://c6xkdiek6s4qq diff --git a/NotificationLabel/NotificationLabel.tscn b/NotificationLabel/NotificationLabel.tscn new file mode 100644 index 0000000..ee2d465 --- /dev/null +++ b/NotificationLabel/NotificationLabel.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=5 format=3 uid="uid://drftmonotxben"] + +[ext_resource type="StyleBox" uid="uid://bmnm3alcou5sn" path="res://NotificationLabel/RedDialog.tres" id="1_4xtb5"] +[ext_resource type="Script" uid="uid://c6xkdiek6s4qq" path="res://NotificationLabel/NotificationLabel.gd" id="1_8sc04"] +[ext_resource type="StyleBox" uid="uid://dp30gd7s0wc2n" path="res://NotificationLabel/GreenDialog.tres" id="2_3k1ls"] +[ext_resource type="StyleBox" uid="uid://cjnvjuxnbj5jh" path="res://NotificationLabel/YellowDialog.tres" id="4_nq7i5"] + +[node name="NotificationLabel" type="Label"] +theme_override_styles/normal = ExtResource("2_3k1ls") +text = "Mensagem De Erro!" +script = ExtResource("1_8sc04") +green_dialog = ExtResource("2_3k1ls") +red_dialog = ExtResource("1_4xtb5") +yellow_dialog = ExtResource("4_nq7i5") + +[node name="Timer" type="Timer" parent="."] +wait_time = 5.0 + +[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"] diff --git a/NotificationLabel/RedDialog.tres b/NotificationLabel/RedDialog.tres new file mode 100644 index 0000000..f584c9a --- /dev/null +++ b/NotificationLabel/RedDialog.tres @@ -0,0 +1,13 @@ +[gd_resource type="StyleBoxFlat" format=3 uid="uid://bmnm3alcou5sn"] + +[resource] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.855473, 0.352022, 0.386703, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 +shadow_size = 3 diff --git a/NotificationLabel/YellowDialog.tres b/NotificationLabel/YellowDialog.tres new file mode 100644 index 0000000..d5d1f75 --- /dev/null +++ b/NotificationLabel/YellowDialog.tres @@ -0,0 +1,13 @@ +[gd_resource type="StyleBoxFlat" format=3 uid="uid://cjnvjuxnbj5jh"] + +[resource] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.812105, 0.740369, 0.228856, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 +shadow_size = 3 diff --git a/Player.gd b/Player.gd deleted file mode 100644 index 5b1f093..0000000 --- a/Player.gd +++ /dev/null @@ -1,34 +0,0 @@ -extends CharacterBody2D - - -const SPEED = 300.0 -const JUMP_VELOCITY = -400.0 - -# Get the gravity from the project settings to be synced with RigidBody nodes. -var gravity = ProjectSettings.get_setting("physics/2d/default_gravity") - - -func _physics_process(delta): - if name == str(multiplayer.get_unique_id()): - # Add the gravity. - if not is_on_floor(): - velocity.y += gravity * delta - - # Handle jump. - if Input.is_action_just_pressed("ui_accept") and is_on_floor(): - velocity.y = JUMP_VELOCITY - - # Get the input direction and handle the movement/deceleration. - # As good practice, you should replace UI actions with custom gameplay actions. - var direction = Input.get_axis("ui_left", "ui_right") - if direction: - velocity.x = direction * SPEED - else: - velocity.x = move_toward(velocity.x, 0, SPEED) - - move_and_slide() - syncPos.rpc(global_position) - -@rpc("any_peer") -func syncPos(p): - global_position = p diff --git a/Popup/JoinGroupChat/JoinGroupChat.gd b/Popup/JoinGroupChat/JoinGroupChat.gd new file mode 100644 index 0000000..6f9929e --- /dev/null +++ b/Popup/JoinGroupChat/JoinGroupChat.gd @@ -0,0 +1,26 @@ +extends Window +class_name JoinGroupChat + +@onready var groups_available: OptionButton = %GroupsAvailable + +var available_groups_to_current_user + +func _on_about_to_popup() -> void: + + groups_available.clear() + + var result = \ + await NakamaManager.client.list_user_groups_async(NakamaManager.session, NakamaManager.current_user.id) + + available_groups_to_current_user = result.user_groups + + for query_result in result.user_groups: + + groups_available.add_item(query_result.group.name) + +func _on_join_button_pressed() -> void: + var query = available_groups_to_current_user[groups_available.selected] + close_requested.emit(query.group) + +func _on_close_requested(_group) -> void: + hide() diff --git a/Popup/JoinGroupChat/JoinGroupChat.gd.uid b/Popup/JoinGroupChat/JoinGroupChat.gd.uid new file mode 100644 index 0000000..b4feaaf --- /dev/null +++ b/Popup/JoinGroupChat/JoinGroupChat.gd.uid @@ -0,0 +1 @@ +uid://b5meanblygfd8 diff --git a/Popup/JoinGroupChat/JoinGroupChat.tscn b/Popup/JoinGroupChat/JoinGroupChat.tscn new file mode 100644 index 0000000..f4cdc88 --- /dev/null +++ b/Popup/JoinGroupChat/JoinGroupChat.tscn @@ -0,0 +1,41 @@ +[gd_scene load_steps=2 format=3 uid="uid://cx38uabenhpce"] + +[ext_resource type="Script" uid="uid://b5meanblygfd8" path="res://Popup/JoinGroupChat/JoinGroupChat.gd" id="1_lnuc2"] + +[node name="JoinGroupChat" type="Window"] +auto_translate_mode = 1 +title = "Join Group Chat" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +min_size = Vector2i(250, 0) +script = ExtResource("1_lnuc2") + +[node name="PanelContainer" type="PanelContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="Group" type="HBoxContainer" parent="PanelContainer/_"] +layout_mode = 2 + +[node name="Select a Group" type="Label" parent="PanelContainer/_/Group"] +layout_mode = 2 +text = "Select a Group" + +[node name="GroupsAvailable" type="OptionButton" parent="PanelContainer/_/Group"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="JoinButton" type="Button" parent="PanelContainer/_"] +layout_mode = 2 +text = "Join" + +[connection signal="about_to_popup" from="." to="." method="_on_about_to_popup"] +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="pressed" from="PanelContainer/_/JoinButton" to="." method="_on_join_button_pressed"] diff --git a/Popup/Message/PopupBox.gd b/Popup/Message/PopupBox.gd new file mode 100644 index 0000000..ecb556b --- /dev/null +++ b/Popup/Message/PopupBox.gd @@ -0,0 +1,18 @@ +extends Window +class_name PopupBox + +@onready var description: Label = $"-/Description" +@onready var button: Button = $"-/Button" + +func configure_text(new_title: String, new_message: String) -> void: + description.text = new_message + title = new_title + +func _on_button_pressed() -> void: + close_requested.emit() + +func _on_about_to_popup() -> void: + pass # Replace with function body. + +func _on_close_requested() -> void: + hide() diff --git a/Popup/Message/PopupBox.gd.uid b/Popup/Message/PopupBox.gd.uid new file mode 100644 index 0000000..121a0d1 --- /dev/null +++ b/Popup/Message/PopupBox.gd.uid @@ -0,0 +1 @@ +uid://c4f6uiudk6wqv diff --git a/Popup/Message/PopupBox.tscn b/Popup/Message/PopupBox.tscn new file mode 100644 index 0000000..0aeb737 --- /dev/null +++ b/Popup/Message/PopupBox.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=2 format=3 uid="uid://32gcwea4fq4t"] + +[ext_resource type="Script" uid="uid://c4f6uiudk6wqv" path="res://Popup/Message/PopupBox.gd" id="1_de25d"] + +[node name="Popup" type="Window"] +auto_translate_mode = 1 +size = Vector2i(250, 150) +min_size = Vector2i(250, 150) +script = ExtResource("1_de25d") + +[node name="-" type="VBoxContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Description" type="Label" parent="-"] +layout_mode = 2 +size_flags_vertical = 3 +horizontal_alignment = 1 +vertical_alignment = 1 +autowrap_mode = 2 + +[node name="Button" type="Button" parent="-"] +layout_mode = 2 +text = "Ok" + +[connection signal="about_to_popup" from="." to="." method="_on_about_to_popup"] +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="pressed" from="-/Button" to="." method="_on_button_pressed"] diff --git a/SceneManager.gd b/SceneManager.gd deleted file mode 100644 index 7822bc0..0000000 --- a/SceneManager.gd +++ /dev/null @@ -1,25 +0,0 @@ -extends Node2D - -var spawnpoints -@export var playerScene : PackedScene -# Called when the node enters the scene tree for the first time. -func _ready(): - spawnpoints = get_tree().get_nodes_in_group("SpawnPoint") - var index = 0 - var keys = NakamaMultiplayer.Players.keys() - keys.sort() - for i in keys: - var instancedPlayer = playerScene.instantiate() - instancedPlayer.name = str(NakamaMultiplayer.Players[i].name) - - add_child(instancedPlayer) - - instancedPlayer.global_position = spawnpoints[index].global_position - - index += 1 - pass # Replace with function body. - - -# Called every frame. 'delta' is the elapsed time since the previous frame. -func _process(delta): - pass diff --git a/Styles/DarkBackgroundLabel.tres b/Styles/DarkBackgroundLabel.tres new file mode 100644 index 0000000..deff41c --- /dev/null +++ b/Styles/DarkBackgroundLabel.tres @@ -0,0 +1,12 @@ +[gd_resource type="StyleBoxFlat" format=3 uid="uid://dqy54jim8vxkk"] + +[resource] +content_margin_left = 6.0 +content_margin_top = 6.0 +content_margin_right = 6.0 +content_margin_bottom = 6.0 +bg_color = Color(0.0962047, 0.0962048, 0.0962047, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 diff --git a/Teste/GuguGlobal.gd b/Teste/GuguGlobal.gd new file mode 100644 index 0000000..34c2ae5 --- /dev/null +++ b/Teste/GuguGlobal.gd @@ -0,0 +1,3 @@ +extends Node + +var allison_oliveira : String = "Allison Oliveira" diff --git a/Teste/GuguGlobal.gd.uid b/Teste/GuguGlobal.gd.uid new file mode 100644 index 0000000..24277de --- /dev/null +++ b/Teste/GuguGlobal.gd.uid @@ -0,0 +1 @@ +uid://dodr6sp8j4ygm diff --git a/Teste/TesteParaoGugu.tscn b/Teste/TesteParaoGugu.tscn new file mode 100644 index 0000000..34f4a05 --- /dev/null +++ b/Teste/TesteParaoGugu.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://c3sg5fopn8y8x"] + +[ext_resource type="Script" uid="uid://f2tfbh5o4p87" path="res://Teste/teste_parao_gugu.gd" id="1_1sl0r"] +[ext_resource type="Script" uid="uid://dqgjbu04233l0" path="res://Teste/character_body.gd" id="2_d3y0o"] + +[node name="TesteParaoGugu" type="Node"] +script = ExtResource("1_1sl0r") + +[node name="Node1" type="Node" parent="."] + +[node name="CharacterBody" type="CharacterBody2D" parent="." groups=["Player"]] +script = ExtResource("2_d3y0o") diff --git a/Teste/character_body.gd b/Teste/character_body.gd new file mode 100644 index 0000000..ec32fab --- /dev/null +++ b/Teste/character_body.gd @@ -0,0 +1,26 @@ +extends CharacterBody2D +class_name PlayerGugu + +const SPEED = 300.0 +const JUMP_VELOCITY = -400.0 + +var ola_gugu = "teste" + +func _physics_process(delta: float) -> void: + # Add the gravity. + if not is_on_floor(): + velocity += get_gravity() * delta + + # Handle jump. + if Input.is_action_just_pressed("ui_accept") and is_on_floor(): + velocity.y = JUMP_VELOCITY + + # Get the input direction and handle the movement/deceleration. + # As good practice, you should replace UI actions with custom gameplay actions. + var direction := Input.get_axis("ui_left", "ui_right") + if direction: + velocity.x = direction * SPEED + else: + velocity.x = move_toward(velocity.x, 0, SPEED) + + move_and_slide() diff --git a/Teste/character_body.gd.uid b/Teste/character_body.gd.uid new file mode 100644 index 0000000..ccd8712 --- /dev/null +++ b/Teste/character_body.gd.uid @@ -0,0 +1 @@ +uid://dqgjbu04233l0 diff --git a/Teste/teste_parao_gugu.gd b/Teste/teste_parao_gugu.gd new file mode 100644 index 0000000..796ebb6 --- /dev/null +++ b/Teste/teste_parao_gugu.gd @@ -0,0 +1,10 @@ +extends Node + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + + var player = get_tree().get_first_node_in_group("Player") + + print("player" , player.ola_gugu) + player.ola_gugu = "deimox: boa noite" + print(player.ola_gugu) diff --git a/Teste/teste_parao_gugu.gd.uid b/Teste/teste_parao_gugu.gd.uid new file mode 100644 index 0000000..c0062cb --- /dev/null +++ b/Teste/teste_parao_gugu.gd.uid @@ -0,0 +1 @@ +uid://f2tfbh5o4p87 diff --git a/addons/better-terrain/BetterTerrain.cs b/addons/better-terrain/BetterTerrain.cs new file mode 100644 index 0000000..31f1677 --- /dev/null +++ b/addons/better-terrain/BetterTerrain.cs @@ -0,0 +1,258 @@ +using Godot; +using Godot.Collections; + +#nullable disable + +/* + +This is a lightweight wrapper for Better Terrain in C#. + +It is not a C# implementation, it merely provides a type safe interface to access +the BetterTerrain autoload from C#. If you are not using Godot in C#, you can ignore +this file. + +The interface is created for a specific tilemap node, which it uses to locate the +autoload, and to fill in as a parameter to simplify all the subsequent calls. +Very simple example: + +``` + BetterTerrain betterTerrain; + + public override void _Ready() + { + TileMapLayer tileMapLayer = GetNode("TileMapLayer"); + betterTerrain = new BetterTerrain(tm); + + var coordinates = new Vector2I(0, 0); + betterTerrain.SetCell(coordinates, 1); + betterTerrain.UpdateTerrainCell(coordinates); + } +``` + +The functions available are the same as BetterTerrain's, though the TileMapLayer or +TileSet parameters are automatically filled in. The help is not duplicated here, +refer to the GDScript version for specifics. + +*/ + +public class BetterTerrain +{ + public enum TerrainType + { + MatchTiles = 0, + MatchVertices = 1, + Category = 2, + Decoration = 3 + } + + public enum SymmetryType + { + None = 0, + Mirror = 1, // Horizontally mirror + Flip = 2, // Vertically flip + Reflect = 3, // All four reflections + RotateClockwise = 4, + RotateCounterClockwise = 5, + Rotate180 = 6, + RotateAll = 7, // All four rotated forms + All = 8 // All rotated and reflected forms + } + + private static readonly NodePath nodePath = new("/root/BetterTerrain"); + private readonly Node betterTerrain; + private readonly TileMapLayer tileMapLayer; + + public BetterTerrain(TileMapLayer tileMapLayer) + { + this.tileMapLayer = tileMapLayer; + betterTerrain = tileMapLayer.GetNode(nodePath); + } + + public Array> GetTerrainCategories() + { + return (Array>)betterTerrain.Call(MethodName.GetTerrainCategories, tileMapLayer.TileSet); + } + + public bool AddTerrain(string name, Color color, TerrainType type, Array categories = null, Godot.Collections.Dictionary icon = null) + { + categories ??= new Array(); + icon ??= new Godot.Collections.Dictionary(); + return (bool)betterTerrain.Call(MethodName.AddTerrain, tileMapLayer.TileSet, name, color, (int)type, categories, icon); + } + + public bool RemoveTerrain(int index) + { + return (bool)betterTerrain.Call(MethodName.RemoveTerrain, tileMapLayer.TileSet, index); + } + + public int TerrainCount() + { + return (int)betterTerrain.Call(MethodName.TerrainCount, tileMapLayer.TileSet); + } + + public Godot.Collections.Dictionary GetTerrain(int index) + { + return (Godot.Collections.Dictionary)betterTerrain.Call(MethodName.GetTerrain, tileMapLayer.TileSet, index); + } + + public bool SetTerrain(int index, string name, Color color, TerrainType type, Array categories = null, Godot.Collections.Dictionary icon = null) + { + categories ??= new Array(); + icon ??= new Godot.Collections.Dictionary(); + return (bool)betterTerrain.Call(MethodName.SetTerrain, tileMapLayer.TileSet, index, name, color, (int)type, categories, icon); + } + + public bool SwapTerrains(int index1, int index2) + { + return (bool)betterTerrain.Call(MethodName.SwapTerrains, tileMapLayer.TileSet, index1, index2); + } + + public bool SetTileTerrainType(TileData tileData, int type) + { + return (bool)betterTerrain.Call(MethodName.SetTileTerrainType, tileMapLayer.TileSet, tileData, type); + } + + public int GetTileTerrainType(TileData tileData) + { + return (int)betterTerrain.Call(MethodName.GetTileTerrainType, tileData); + } + + public bool SetTileSymmetryType(TileData tileData, SymmetryType type) + { + return (bool)betterTerrain.Call(MethodName.SetTileSymmetryType, tileMapLayer.TileSet, tileData, (int)type); + } + + public SymmetryType GetTileSymmetryType(TileData tileData) + { + return (SymmetryType)(int)betterTerrain.Call(MethodName.GetTileSymmetryType, tileData); + } + + public Array GetTilesInTerrain(int type) + { + return (Array)betterTerrain.Call(MethodName.GetTilesInTerrain, tileMapLayer.TileSet, type); + } + + public Array> GetTileSourcesInTerrain(int type) + { + return (Array>)betterTerrain.Call(MethodName.GetTileSourcesInTerrain, tileMapLayer.TileSet, type); + } + + public bool AddTilePeeringType(TileData tileData, TileSet.CellNeighbor peering, int type) + { + return (bool)betterTerrain.Call(MethodName.AddTilePeeringType, tileMapLayer.TileSet, tileData, (int)peering, type); + } + + public bool RemoveTilePeeringType(TileData tileData, TileSet.CellNeighbor peering, int type) + { + return (bool)betterTerrain.Call(MethodName.RemoveTilePeeringType, tileMapLayer.TileSet, tileData, (int)peering, type); + } + + public Array TilePeeringKeys(TileData tileData) + { + return (Array)betterTerrain.Call(MethodName.TilePeeringKeys, tileData); + } + + public Array TilePeeringTypes(TileData tileData, TileSet.CellNeighbor peering) + { + return (Array)betterTerrain.Call(MethodName.TilePeeringTypes, tileData, (int)peering); + } + + public Array TilePeeringForType(TileData tileData, int type) + { + return (Array)betterTerrain.Call(MethodName.TilePeeringForType, tileData, type); + } + + public bool SetCell(Vector2I coordinate, int type) + { + return (bool)betterTerrain.Call(MethodName.SetCell, tileMapLayer, coordinate, type); + } + + public bool SetCells(Array coordinates, int type) + { + return (bool)betterTerrain.Call(MethodName.SetCells, tileMapLayer, coordinates, type); + } + + public bool ReplaceCell(Vector2I coordinate, int type) + { + return (bool)betterTerrain.Call(MethodName.ReplaceCell, tileMapLayer, coordinate, type); + } + + public bool ReplaceCells(Array coordinates, int type) + { + return (bool)betterTerrain.Call(MethodName.ReplaceCells, tileMapLayer, coordinates, type); + } + + public int GetCell(Vector2I coordinate) + { + return (int)betterTerrain.Call(MethodName.GetCell, tileMapLayer, coordinate); + } + + public void UpdateTerrainCells(Array cells, bool updateSurroundingCells = true) + { + betterTerrain.Call(MethodName.UpdateTerrainCells, tileMapLayer, cells, updateSurroundingCells); + } + + public void UpdateTerrainCell(Vector2I cell, bool updateSurroundingCells = true) + { + betterTerrain.Call(MethodName.UpdateTerrainCell, tileMapLayer, cell, updateSurroundingCells); + } + + public void UpdateTerrainArea(Rect2I area, bool updateSurroundingCells = true) + { + betterTerrain.Call(MethodName.UpdateTerrainArea, tileMapLayer, area, updateSurroundingCells); + } + + public Godot.Collections.Dictionary CreateTerrainChangeset(Godot.Collections.Dictionary paint) + { + return (Godot.Collections.Dictionary)betterTerrain.Call(MethodName.CreateTerrainChangeset, tileMapLayer, paint); + } + + public bool IsTerrainChangesetReady(Godot.Collections.Dictionary changeset) + { + return (bool)betterTerrain.Call(MethodName.IsTerrainChangesetReady, changeset); + } + + public void WaitForTerrainChangeset(Godot.Collections.Dictionary changeset) + { + betterTerrain.Call(MethodName.WaitForTerrainChangeset, changeset); + } + + public void ApplyTerrainChangeset(Godot.Collections.Dictionary changeset) + { + betterTerrain.Call(MethodName.ApplyTerrainChangeset, changeset); + } + + private static class MethodName + { + public static readonly StringName GetTerrainCategories = "get_terrain_categories"; + public static readonly StringName AddTerrain = "add_terrain"; + public static readonly StringName RemoveTerrain = "remove_terrain"; + public static readonly StringName TerrainCount = "terrain_count"; + public static readonly StringName GetTerrain = "get_terrain"; + public static readonly StringName SetTerrain = "set_terrain"; + public static readonly StringName SwapTerrains = "swap_terrains"; + public static readonly StringName SetTileTerrainType = "set_tile_terrain_type"; + public static readonly StringName GetTileTerrainType = "get_tile_terrain_type"; + public static readonly StringName SetTileSymmetryType = "set_tile_symmetry_type"; + public static readonly StringName GetTileSymmetryType = "get_tile_symmetry_type"; + public static readonly StringName GetTilesInTerrain = "get_tiles_in_terrain"; + public static readonly StringName GetTileSourcesInTerrain = "get_tile_sources_in_terrain"; + public static readonly StringName AddTilePeeringType = "add_tile_peering_type"; + public static readonly StringName RemoveTilePeeringType = "remove_tile_peering_type"; + public static readonly StringName TilePeeringKeys = "tile_peering_keys"; + public static readonly StringName TilePeeringTypes = "tile_peering_types"; + public static readonly StringName TilePeeringForType = "tile_peering_for_type"; + public static readonly StringName SetCell = "set_cell"; + public static readonly StringName SetCells = "set_cells"; + public static readonly StringName ReplaceCell = "replace_cell"; + public static readonly StringName ReplaceCells = "replace_cells"; + public static readonly StringName GetCell = "get_cell"; + public static readonly StringName UpdateTerrainCells = "update_terrain_cells"; + public static readonly StringName UpdateTerrainCell = "update_terrain_cell"; + public static readonly StringName UpdateTerrainArea = "update_terrain_area"; + public static readonly StringName CreateTerrainChangeset = "create_terrain_changeset"; + public static readonly StringName IsTerrainChangesetReady = "is_terrain_changeset_ready"; + public static readonly StringName WaitForTerrainChangeset = "wait_for_terrain_changeset"; + public static readonly StringName ApplyTerrainChangeset = "apply_terrain_changeset"; + } +} diff --git a/addons/better-terrain/BetterTerrain.gd b/addons/better-terrain/BetterTerrain.gd new file mode 100644 index 0000000..8c53711 --- /dev/null +++ b/addons/better-terrain/BetterTerrain.gd @@ -0,0 +1,1160 @@ +@tool +extends Node + +## A [TileMapLayer] terrain / auto-tiling system. +## +## This is a drop-in replacement for Godot 4's tilemap terrain system, offering +## more versatile and straightforward autotiling. It can be used with any +## existing [TileMapLayer] or [TileSet], either through the editor plugin, or +## directly via code. +## [br][br] +## The [b]BetterTerrain[/b] class contains only static functions, each of which +## either takes a [TileMapLayer], a [TileSet], and sometimes a [TileData]. +## Meta-data is embedded inside the [TileSet] and the [TileData] types to store +## the terrain information. See [method Object.get_meta] for information. +## [br][br] +## Once terrain is set up, it can be written to the tilemap using [method set_cells]. +## Similar to Godot 3.x, setting the cells does not run the terrain solver, so once +## the cells have been set, you need to call an update function such as [method update_terrain_cells]. + + +## The meta-data key used to store terrain information. +const TERRAIN_META = &"_better_terrain" + +## The current version. Used to handle future upgrades. +const TERRAIN_SYSTEM_VERSION = "0.2" + +var _tile_cache = {} +var rng = RandomNumberGenerator.new() +var use_seed := true + +## A helper class that provides functions detailing valid peering bits and +## polygons for different tile types. +var data := load("res://addons/better-terrain/BetterTerrainData.gd"): + get: + return data + +enum TerrainType { + MATCH_TILES, ## Selects tiles by matching against adjacent tiles. + MATCH_VERTICES, ## Select tiles by analysing vertices, similar to wang-style tiles. + CATEGORY, ## Declares a matching type for more sophisticated rules. + DECORATION, ## Fills empty tiles by matching adjacent tiles + MAX, +} + +enum TileCategory { + EMPTY = -1, ## An empty cell, or a tile marked as decoration + NON_TERRAIN = -2, ## A non-empty cell that does not contain a terrain tile + ERROR = -3 +} + +enum SymmetryType { + NONE, + MIRROR, ## Horizontally mirror + FLIP, ## Vertically flip + REFLECT, ## All four reflections + ROTATE_CLOCKWISE, + ROTATE_COUNTER_CLOCKWISE, + ROTATE_180, + ROTATE_ALL, ## All four rotated forms + ALL ## All rotated and reflected forms +} + + +func _intersect(first: Array, second: Array) -> bool: + if first.size() > second.size(): + return _intersect(second, first) # Array 'has' is fast compared to gdscript loop + for f in first: + if second.has(f): + return true + return false + + +# Meta-data functions + +func _get_terrain_meta(ts: TileSet) -> Dictionary: + return ts.get_meta(TERRAIN_META) if ts and ts.has_meta(TERRAIN_META) else { + terrains = [], + decoration = ["Decoration", Color.DIM_GRAY, TerrainType.DECORATION, [], {path = "res://addons/better-terrain/icons/Decoration.svg"}], + version = TERRAIN_SYSTEM_VERSION + } + + +func _set_terrain_meta(ts: TileSet, meta : Dictionary) -> void: + ts.set_meta(TERRAIN_META, meta) + ts.emit_changed() + + +func _get_tile_meta(td: TileData) -> Dictionary: + return td.get_meta(TERRAIN_META) if td.has_meta(TERRAIN_META) else { + type = TileCategory.NON_TERRAIN + } + + +func _set_tile_meta(ts: TileSet, td: TileData, meta) -> void: + td.set_meta(TERRAIN_META, meta) + ts.emit_changed() + + +func _get_cache(ts: TileSet) -> Array: + if _tile_cache.has(ts): + return _tile_cache[ts] + + var cache := [] + if !ts: + return cache + _tile_cache[ts] = cache + + var watcher = Node.new() + watcher.set_script(load("res://addons/better-terrain/Watcher.gd")) + watcher.tileset = ts + watcher.trigger.connect(_purge_cache.bind(ts)) + add_child(watcher) + ts.changed.connect(watcher.activate) + + var types = {} + + var ts_meta := _get_terrain_meta(ts) + for t in ts_meta.terrains.size(): + var terrain = ts_meta.terrains[t] + var bits = terrain[3].duplicate() + bits.push_back(t) + types[t] = bits + cache.push_back([]) + + # Decoration + types[-1] = [TileCategory.EMPTY] + cache.push_back([[-1, Vector2.ZERO, -1, {}, 1.0]]) + + for s in ts.get_source_count(): + var source_id := ts.get_source_id(s) + var source := ts.get_source(source_id) as TileSetAtlasSource + if !source: + continue + source.changed.connect(watcher.activate) + for c in source.get_tiles_count(): + var coord := source.get_tile_id(c) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + var td := source.get_tile_data(coord, alternate) + var td_meta := _get_tile_meta(td) + if td_meta.type < TileCategory.EMPTY or td_meta.type >= cache.size(): + continue + + td.changed.connect(watcher.activate) + var peering := {} + for key in td_meta.keys(): + if !(key is int): + continue + + var targets := [] + for k in types: + if _intersect(types[k], td_meta[key]): + targets.push_back(k) + + peering[key] = targets + + # Decoration tiles without peering are skipped + if td_meta.type == TileCategory.EMPTY and !peering: + continue + + var symmetry = td_meta.get("symmetry", SymmetryType.NONE) + # Branch out no symmetry tiles early + if symmetry == SymmetryType.NONE: + cache[td_meta.type].push_back([source_id, coord, alternate, peering, td.probability]) + continue + + # calculate the symmetry order for this tile + var symmetry_order := 0 + for flags in data.symmetry_mapping[symmetry]: + var symmetric_peering = data.peering_bits_after_symmetry(peering, flags) + if symmetric_peering == peering: + symmetry_order += 1 + + var adjusted_probability = td.probability / symmetry_order + for flags in data.symmetry_mapping[symmetry]: + var symmetric_peering = data.peering_bits_after_symmetry(peering, flags) + cache[td_meta.type].push_back([source_id, coord, alternate | flags, symmetric_peering, adjusted_probability]) + + return cache + + +func _get_cache_terrain(ts_meta : Dictionary, index: int) -> Array: + # the cache and the terrains in ts_meta don't line up because + # decorations are cached too + if index < 0 or index >= ts_meta.terrains.size(): + return ts_meta.decoration + return ts_meta.terrains[index] + + +func _purge_cache(ts: TileSet) -> void: + _tile_cache.erase(ts) + for c in get_children(): + if c.tileset == ts: + c.tidy() + break + + +func _clear_invalid_peering_types(ts: TileSet) -> void: + var ts_meta := _get_terrain_meta(ts) + + var cache := _get_cache(ts) + for t in cache.size(): + var type = _get_cache_terrain(ts_meta, t)[2] + var valid_peering_types = data.get_terrain_peering_cells(ts, type) + + for c in cache[t]: + if c[0] < 0: + continue + var source := ts.get_source(c[0]) as TileSetAtlasSource + if !source: + continue + var td := source.get_tile_data(c[1], c[2]) + var td_meta := _get_tile_meta(td) + + for peering in c[3].keys(): + if valid_peering_types.has(peering): + continue + td_meta.erase(peering) + + _set_tile_meta(ts, td, td_meta) + + # Not strictly necessary + _purge_cache(ts) + + +func _has_invalid_peering_types(ts: TileSet) -> bool: + var ts_meta := _get_terrain_meta(ts) + + var cache := _get_cache(ts) + for t in cache.size(): + var type = _get_cache_terrain(ts_meta, t)[2] + var valid_peering_types = data.get_terrain_peering_cells(ts, type) + + for c in cache[t]: + for peering in c[3].keys(): + if !valid_peering_types.has(peering): + return true + + return false + + +func _update_terrain_data(ts: TileSet) -> void: + var ts_meta = _get_terrain_meta(ts) + var previous_version = ts_meta.get("version") + + # First release: no version info + if !ts_meta.has("version"): + ts_meta["version"] = "0.0" + + # 0.0 -> 0.1: add categories + if ts_meta.version == "0.0": + for t in ts_meta.terrains: + if t.size() == 3: + t.push_back([]) + ts_meta.version = "0.1" + + # 0.1 -> 0.2: add decoration tiles and terrain icons + if ts_meta.version == "0.1": + # Add terrain icon containers + for t in ts_meta.terrains: + if t.size() == 4: + t.push_back({}) + + # Add default decoration data + ts_meta["decoration"] = ["Decoration", Color.DIM_GRAY, TerrainType.DECORATION, [], {path = "res://addons/better-terrain/icons/Decoration.svg"}] + ts_meta.version = "0.2" + + if previous_version != ts_meta.version: + _set_terrain_meta(ts, ts_meta) + + +func _weighted_selection(choices: Array, apply_empty_probability: bool): + if choices.is_empty(): + return null + + var weight = choices.reduce(func(a, c): return a + c[4], 0.0) + + if apply_empty_probability and weight < 1.0 and rng.randf() > weight: + return [-1, Vector2.ZERO, -1, null, 1.0] + + if choices.size() == 1: + return choices[0] + + if weight == 0.0: + return choices[rng.randi() % choices.size()] + + var pick = rng.randf() * weight + for c in choices: + if pick < c[4]: + return c + pick -= c[4] + return choices.back() + + +func _weighted_selection_seeded(choices: Array, coord: Vector2i, apply_empty_probability: bool): + if use_seed: + rng.seed = hash(coord) + return _weighted_selection(choices, apply_empty_probability) + + +func _update_tile_tiles(tm: TileMapLayer, coord: Vector2i, types: Dictionary, cache: Array, apply_empty_probability: bool): + var type = types[coord] + + const reward := 3 + var penalty := -2000 if apply_empty_probability else -10 + + var best_score := -1000 # Impossibly bad score + var best := [] + for t in cache[type]: + var score := 0 + for peering in t[3]: + score += reward if t[3][peering].has(types[tm.get_neighbor_cell(coord, peering)]) else penalty + + if score > best_score: + best_score = score + best = [t] + elif score == best_score: + best.append(t) + + return _weighted_selection_seeded(best, coord, apply_empty_probability) + + +func _probe(tm: TileMapLayer, coord: Vector2i, peering: int, type: int, types: Dictionary) -> int: + var targets = data.associated_vertex_cells(tm, coord, peering) + targets = targets.map(func(c): return types[c]) + + var first = targets[0] + if targets.all(func(t): return t == first): + return first + + # if different, use the lowest non-same + targets = targets.filter(func(t): return t != type) + return targets.reduce(func(a, t): return min(a, t)) + + +func _update_tile_vertices(tm: TileMapLayer, coord: Vector2i, types: Dictionary, cache: Array): + var type = types[coord] + + const reward := 3 + const penalty := -10 + + var best_score := -1000 # Impossibly bad score + var best := [] + for t in cache[type]: + var score := 0 + for peering in t[3]: + score += reward if _probe(tm, coord, peering, type, types) in t[3][peering] else penalty + + if score > best_score: + best_score = score + best = [t] + elif score == best_score: + best.append(t) + + return _weighted_selection_seeded(best, coord, false) + + +func _update_tile_immediate(tm: TileMapLayer, coord: Vector2i, ts_meta: Dictionary, types: Dictionary, cache: Array) -> void: + var type = types[coord] + if type < TileCategory.EMPTY or type >= ts_meta.terrains.size(): + return + + var placement + var terrain = _get_cache_terrain(ts_meta, type) + if terrain[2] in [TerrainType.MATCH_TILES, TerrainType.DECORATION]: + placement = _update_tile_tiles(tm, coord, types, cache, terrain[2] == TerrainType.DECORATION) + elif terrain[2] == TerrainType.MATCH_VERTICES: + placement = _update_tile_vertices(tm, coord, types, cache) + else: + return + + if placement: + tm.set_cell(coord, placement[0], placement[1], placement[2]) + + +func _update_tile_deferred(tm: TileMapLayer, coord: Vector2i, ts_meta: Dictionary, types: Dictionary, cache: Array): + var type = types[coord] + if type >= TileCategory.EMPTY and type < ts_meta.terrains.size(): + var terrain = _get_cache_terrain(ts_meta, type) + if terrain[2] in [TerrainType.MATCH_TILES, TerrainType.DECORATION]: + return _update_tile_tiles(tm, coord, types, cache, terrain[2] == TerrainType.DECORATION) + elif terrain[2] == TerrainType.MATCH_VERTICES: + return _update_tile_vertices(tm, coord, types, cache) + return null + + +func _widen(tm: TileMapLayer, coords: Array) -> Array: + var result := {} + var peering_neighbors = data.get_terrain_peering_cells(tm.tile_set, TerrainType.MATCH_TILES) + for c in coords: + result[c] = true + var neighbors = data.neighboring_coords(tm, c, peering_neighbors) + for t in neighbors: + result[t] = true + return result.keys() + + +func _widen_with_exclusion(tm: TileMapLayer, coords: Array, exclusion: Rect2i) -> Array: + var result := {} + var peering_neighbors = data.get_terrain_peering_cells(tm.tile_set, TerrainType.MATCH_TILES) + for c in coords: + if !exclusion.has_point(c): + result[c] = true + var neighbors = data.neighboring_coords(tm, c, peering_neighbors) + for t in neighbors: + if !exclusion.has_point(t): + result[t] = true + return result.keys() + +# Terrains + +## Returns an [Array] of categories. These are the terrains in the [TileSet] which +## are marked with [enum TerrainType] of [code]CATEGORY[/code]. Each entry in the +## array is a [Dictionary] with [code]name[/code], [code]color[/code], and [code]id[/code]. +func get_terrain_categories(ts: TileSet) -> Array: + var result := [] + if !ts: + return result + + var ts_meta := _get_terrain_meta(ts) + for id in ts_meta.terrains.size(): + var t = ts_meta.terrains[id] + if t[2] == TerrainType.CATEGORY: + result.push_back({name = t[0], color = t[1], id = id}) + + return result + + +## Adds a new terrain to the [TileSet]. Returns [code]true[/code] if this is successful. +## [br][br] +## [code]type[/code] must be one of [enum TerrainType].[br] +## [code]categories[/code] is an indexed list of terrain categories that this terrain +## can match as. The indexes must be valid terrains of the CATEGORY type. +## [code]icon[/code] is a [Dictionary] with either a [code]path[/code] string pointing +## to a resource, or a [code]source_id[/code] [int] and a [code]coord[/code] [Vector2i]. +## The former takes priority if both are present. +func add_terrain(ts: TileSet, name: String, color: Color, type: int, categories: Array = [], icon: Dictionary = {}) -> bool: + if !ts or name.is_empty() or type < 0 or type == TerrainType.DECORATION or type >= TerrainType.MAX: + return false + + var ts_meta := _get_terrain_meta(ts) + + # check categories + if type == TerrainType.CATEGORY and !categories.is_empty(): + return false + for c in categories: + if c < 0 or c >= ts_meta.terrains.size() or ts_meta.terrains[c][2] != TerrainType.CATEGORY: + return false + + if icon and not (icon.has("path") or (icon.has("source_id") and icon.has("coord"))): + return false + + ts_meta.terrains.push_back([name, color, type, categories, icon]) + _set_terrain_meta(ts, ts_meta) + _purge_cache(ts) + return true + + +## Removes the terrain at [code]index[/code] from the [TileSet]. Returns [code]true[/code] +## if the deletion is successful. +func remove_terrain(ts: TileSet, index: int) -> bool: + if !ts or index < 0: + return false + + var ts_meta := _get_terrain_meta(ts) + if index >= ts_meta.terrains.size(): + return false + + if ts_meta.terrains[index][2] == TerrainType.CATEGORY: + for t in ts_meta.terrains: + t[3].erase(index) + + for s in ts.get_source_count(): + var source := ts.get_source(ts.get_source_id(s)) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + var td := source.get_tile_data(coord, alternate) + + var td_meta := _get_tile_meta(td) + if td_meta.type == TileCategory.NON_TERRAIN: + continue + + if td_meta.type == index: + _set_tile_meta(ts, td, null) + continue + + if td_meta.type > index: + td_meta.type -= 1 + + for peering in td_meta.keys(): + if !(peering is int): + continue + + var fixed_peering = [] + for p in td_meta[peering]: + if p < index: + fixed_peering.append(p) + elif p > index: + fixed_peering.append(p - 1) + + if fixed_peering.is_empty(): + td_meta.erase(peering) + else: + td_meta[peering] = fixed_peering + + _set_tile_meta(ts, td, td_meta) + + ts_meta.terrains.remove_at(index) + _set_terrain_meta(ts, ts_meta) + + _purge_cache(ts) + return true + + +## Returns the number of terrains in the [TileSet]. +func terrain_count(ts: TileSet) -> int: + if !ts: + return 0 + + var ts_meta := _get_terrain_meta(ts) + return ts_meta.terrains.size() + + +## Retrieves information about the terrain at [code]index[/code] in the [TileSet]. +## [br][br] +## Returns a [Dictionary] describing the terrain. If it succeeds, the key [code]valid[/code] +## will be set to [code]true[/code]. Other keys are [code]name[/code], [code]color[/code], +## [code]type[/code] (a [enum TerrainType]), [code]categories[/code] which is +## an [Array] of category type terrains that this terrain matches as, and +## [code]icon[/code] which is a [Dictionary] with a [code]path[/code] [String] or +## a [code]source_id[/code] [int] and [code]coord[/code] [Vector2i] +func get_terrain(ts: TileSet, index: int) -> Dictionary: + if !ts or index < TileCategory.EMPTY: + return {valid = false} + + var ts_meta := _get_terrain_meta(ts) + if index >= ts_meta.terrains.size(): + return {valid = false} + + var terrain := _get_cache_terrain(ts_meta, index) + return { + id = index, + name = terrain[0], + color = terrain[1], + type = terrain[2], + categories = terrain[3].duplicate(), + icon = terrain[4].duplicate(), + valid = true + } + + +## Updates the details of the terrain at [code]index[/code] in [TileSet]. Returns +## [code]true[/code] if this succeeds. +## [br][br] +## If supplied, the [code]categories[/code] must be a list of indexes to other [code]CATEGORY[/code] +## type terrains. +## [code]icon[/code] is a [Dictionary] with either a [code]path[/code] string pointing +## to a resource, or a [code]source_id[/code] [int] and a [code]coord[/code] [Vector2i]. +func set_terrain(ts: TileSet, index: int, name: String, color: Color, type: int, categories: Array = [], icon: Dictionary = {valid = false}) -> bool: + if !ts or name.is_empty() or index < 0 or type < 0 or type == TerrainType.DECORATION or type >= TerrainType.MAX: + return false + + var ts_meta := _get_terrain_meta(ts) + if index >= ts_meta.terrains.size(): + return false + + if type == TerrainType.CATEGORY and !categories.is_empty(): + return false + for c in categories: + if c < 0 or c == index or c >= ts_meta.terrains.size() or ts_meta.terrains[c][2] != TerrainType.CATEGORY: + return false + + var icon_valid = icon.get("valid", "true") + if icon_valid: + match icon: + {}, {"path"}, {"source_id", "coord"}: pass + _: return false + + if type != TerrainType.CATEGORY: + for t in ts_meta.terrains: + t[3].erase(index) + + ts_meta.terrains[index] = [name, color, type, categories, icon] + _set_terrain_meta(ts, ts_meta) + + _clear_invalid_peering_types(ts) + _purge_cache(ts) + return true + + +## Swaps the terrains at [code]index1[/code] and [code]index2[/code] in [TileSet]. +func swap_terrains(ts: TileSet, index1: int, index2: int) -> bool: + if !ts or index1 < 0 or index2 < 0 or index1 == index2: + return false + + var ts_meta := _get_terrain_meta(ts) + if index1 >= ts_meta.terrains.size() or index2 >= ts_meta.terrains.size(): + return false + + for t in ts_meta.terrains: + var has1 = t[3].has(index1) + var has2 = t[3].has(index2) + + if has1 and !has2: + t[3].erase(index1) + t[3].push_back(index2) + elif has2 and !has1: + t[3].erase(index2) + t[3].push_back(index1) + + for s in ts.get_source_count(): + var source := ts.get_source(ts.get_source_id(s)) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + var td := source.get_tile_data(coord, alternate) + + var td_meta := _get_tile_meta(td) + if td_meta.type == TileCategory.NON_TERRAIN: + continue + + if td_meta.type == index1: + td_meta.type = index2 + elif td_meta.type == index2: + td_meta.type = index1 + + for peering in td_meta.keys(): + if !(peering is int): + continue + + var fixed_peering = [] + for p in td_meta[peering]: + if p == index1: + fixed_peering.append(index2) + elif p == index2: + fixed_peering.append(index1) + else: + fixed_peering.append(p) + td_meta[peering] = fixed_peering + + _set_tile_meta(ts, td, td_meta) + + var temp = ts_meta.terrains[index1] + ts_meta.terrains[index1] = ts_meta.terrains[index2] + ts_meta.terrains[index2] = temp + _set_terrain_meta(ts, ts_meta) + + _purge_cache(ts) + return true + + +# Terrain tile data + +## For a tile in a [TileSet] as specified by [TileData], set the terrain associated +## with that tile to [code]type[/code], which is an index of an existing terrain. +## Returns [code]true[/code] on success. +func set_tile_terrain_type(ts: TileSet, td: TileData, type: int) -> bool: + if !ts or !td or type < TileCategory.NON_TERRAIN: + return false + + var td_meta = _get_tile_meta(td) + td_meta.type = type + if type == TileCategory.NON_TERRAIN: + td_meta = null + _set_tile_meta(ts, td, td_meta) + + _clear_invalid_peering_types(ts) + _purge_cache(ts) + return true + + +## Returns the terrain type associated with tile specified by [TileData]. Returns +## -1 if the tile has no associated terrain. +func get_tile_terrain_type(td: TileData) -> int: + if !td: + return TileCategory.ERROR + var td_meta := _get_tile_meta(td) + return td_meta.type + + +## For a tile represented by [TileData] [code]td[/code] in [TileSet] +## [code]ts[/code], sets [enum SymmetryType] [code]type[/code]. This controls +## how the tile is rotated/mirrored during placement. +func set_tile_symmetry_type(ts: TileSet, td: TileData, type: int) -> bool: + if !ts or !td or type < SymmetryType.NONE or type > SymmetryType.ALL: + return false + + var td_meta := _get_tile_meta(td) + if td_meta.type == TileCategory.NON_TERRAIN: + return false + + td_meta.symmetry = type + _set_tile_meta(ts, td, td_meta) + _purge_cache(ts) + return true + + +## For a tile [code]td[/code], returns the [enum SymmetryType] which that +## tile uses. +func get_tile_symmetry_type(td: TileData) -> int: + if !td: + return SymmetryType.NONE + + var td_meta := _get_tile_meta(td) + return td_meta.get("symmetry", SymmetryType.NONE) + + +## Returns an Array of all [TileData] tiles included in the specified +## terrain [code]type[/code] for the [TileSet] [code]ts[/code] +func get_tiles_in_terrain(ts: TileSet, type: int) -> Array[TileData]: + var result:Array[TileData] = [] + if !ts or type < TileCategory.EMPTY: + return result + + var cache := _get_cache(ts) + if type > cache.size(): + return result + + var tiles = cache[type] + if !tiles: + return result + for c in tiles: + if c[0] < 0: + continue + var source := ts.get_source(c[0]) as TileSetAtlasSource + var td := source.get_tile_data(c[1], c[2]) + result.push_back(td) + + return result + + +## Returns an [Array] of [Dictionary] items including information about each +## tile included in the specified terrain [code]type[/code] for +## the [TileSet] [code]ts[/code]. Each Dictionary item includes +## [TileSetAtlasSource] [code]source[/code], [TileData] [code]td[/code], +## [Vector2i] [code]coord[/code], and [int] [code]alt_id[/code]. +func get_tile_sources_in_terrain(ts: TileSet, type: int) -> Array[Dictionary]: + var result:Array[Dictionary] = [] + + var cache := _get_cache(ts) + var tiles = cache[type] + if !tiles: + return result + for c in tiles: + if c[0] < 0: + continue + var source := ts.get_source(c[0]) as TileSetAtlasSource + if not source: + continue + var td := source.get_tile_data(c[1], c[2]) + result.push_back({ + source = source, + td = td, + coord = c[1], + alt_id = c[2] + }) + + return result + + +## For a [TileSet]'s tile, specified by [TileData], add terrain [code]type[/code] +## (an index of a terrain) to match this tile in direction [code]peering[/code], +## which is of type [enum TileSet.CellNeighbor]. Returns [code]true[/code] on success. +func add_tile_peering_type(ts: TileSet, td: TileData, peering: int, type: int) -> bool: + if !ts or !td or peering < 0 or peering > 15 or type < TileCategory.EMPTY: + return false + + var ts_meta := _get_terrain_meta(ts) + var td_meta := _get_tile_meta(td) + if td_meta.type < TileCategory.EMPTY or td_meta.type >= ts_meta.terrains.size(): + return false + + if !td_meta.has(peering): + td_meta[peering] = [type] + elif !td_meta[peering].has(type): + td_meta[peering].append(type) + else: + return false + _set_tile_meta(ts, td, td_meta) + _purge_cache(ts) + return true + + +## For a [TileSet]'s tile, specified by [TileData], remove terrain [code]type[/code] +## from matching in direction [code]peering[/code], which is of type [enum TileSet.CellNeighbor]. +## Returns [code]true[/code] on success. +func remove_tile_peering_type(ts: TileSet, td: TileData, peering: int, type: int) -> bool: + if !ts or !td or peering < 0 or peering > 15 or type < TileCategory.EMPTY: + return false + + var td_meta := _get_tile_meta(td) + if !td_meta.has(peering): + return false + if !td_meta[peering].has(type): + return false + td_meta[peering].erase(type) + if td_meta[peering].is_empty(): + td_meta.erase(peering) + _set_tile_meta(ts, td, td_meta) + _purge_cache(ts) + return true + + +## For the tile specified by [TileData], return an [Array] of peering directions +## for which terrain matching is set up. These will be of type [enum TileSet.CellNeighbor]. +func tile_peering_keys(td: TileData) -> Array: + if !td: + return [] + + var td_meta := _get_tile_meta(td) + var result := [] + for k in td_meta: + if k is int: + result.append(k) + return result + + +## For the tile specified by [TileData], return the [Array] of terrains that match +## for the direction [code]peering[/code] which should be of type [enum TileSet.CellNeighbor]. +func tile_peering_types(td: TileData, peering: int) -> Array: + if !td or peering < 0 or peering > 15: + return [] + + var td_meta := _get_tile_meta(td) + return td_meta[peering].duplicate() if td_meta.has(peering) else [] + + +## For the tile specified by [TileData], return the [Array] of peering directions +## for the specified terrain type [code]type[/code]. +func tile_peering_for_type(td: TileData, type: int) -> Array: + if !td: + return [] + + var td_meta := _get_tile_meta(td) + var result := [] + var sides := tile_peering_keys(td) + for side in sides: + if td_meta[side].has(type): + result.push_back(side) + + result.sort() + return result + + +# Painting + +## Applies the terrain [code]type[/code] to the [TileMapLayer] for the [Vector2i] +## [code]coord[/code]. Returns [code]true[/code] if it succeeds. Use [method set_cells] +## to change multiple tiles at once. +## [br][br] +## Use terrain type -1 to erase cells. +func set_cell(tm: TileMapLayer, coord: Vector2i, type: int) -> bool: + if !tm or !tm.tile_set or type < TileCategory.EMPTY: + return false + + if type == TileCategory.EMPTY: + tm.erase_cell(coord) + return true + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var tile = cache[type].front() + tm.set_cell(coord, tile[0], tile[1], tile[2]) + return true + + +## Applies the terrain [code]type[/code] to the [TileMapLayer] for the +## [Vector2i] [code]coords[/code]. Returns [code]true[/code] if it succeeds. +## [br][br] +## Note that this does not cause the terrain solver to run, so this will just place +## an arbitrary terrain-associated tile in the given position. To run the solver, +## you must set the require cells, and then call either [method update_terrain_cell], +## [method update_terrain_cels], or [method update_terrain_area]. +## [br][br] +## If you want to prepare changes to the tiles in advance, you can use [method create_terrain_changeset] +## and the associated functions. +## [br][br] +## Use terrain type -1 to erase cells. +func set_cells(tm: TileMapLayer, coords: Array, type: int) -> bool: + if !tm or !tm.tile_set or type < TileCategory.EMPTY: + return false + + if type == TileCategory.EMPTY: + for c in coords: + tm.erase_cell(c) + return true + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var tile = cache[type].front() + for c in coords: + tm.set_cell(c, tile[0], tile[1], tile[2]) + return true + + +## Replaces an existing tile on the [TileMapLayer] for the [Vector2i] +## [code]coord[/code] with a new tile in the provided terrain [code]type[/code] +## *only if* there is a tile with a matching set of peering sides in this terrain. +## Returns [code]true[/code] if any tiles were changed. Use [method replace_cells] +## to replace multiple tiles at once. +func replace_cell(tm: TileMapLayer, coord: Vector2i, type: int) -> bool: + if !tm or !tm.tile_set or type < 0: + return false + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var td = tm.get_cell_tile_data(coord) + if !td: + return false + + var ts_meta := _get_terrain_meta(tm.tile_set) + var categories = ts_meta.terrains[type][3] + var check_types = [type] + categories + + for check_type in check_types: + var placed_peering = tile_peering_for_type(td, check_type) + for pt in get_tiles_in_terrain(tm.tile_set, type): + var check_peering := tile_peering_for_type(pt, check_type) + if placed_peering == check_peering: + var tile = cache[type].front() + tm.set_cell(coord, tile[0], tile[1], tile[2]) + return true + + return false + + +## Replaces existing tiles on the [TileMapLayer] for the [Vector2i] +## [code]coords[/code] with new tiles in the provided terrain [code]type[/code] +## *only if* there is a tile with a matching set of peering sides in this terrain +## for each tile. +## Returns [code]true[/code] if any tiles were changed. +func replace_cells(tm: TileMapLayer, coords: Array, type: int) -> bool: + if !tm or !tm.tile_set or type < 0: + return false + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var ts_meta := _get_terrain_meta(tm.tile_set) + var categories = ts_meta.terrains[type][3] + var check_types = [type] + categories + + var changed = false + var potential_tiles = get_tiles_in_terrain(tm.tile_set, type) + for c in coords: + var found = false + var td = tm.get_cell_tile_data(c) + if !td: + continue + for check_type in check_types: + var placed_peering = tile_peering_for_type(td, check_type) + for pt in potential_tiles: + var check_peering = tile_peering_for_type(pt, check_type) + if placed_peering == check_peering: + var tile = cache[type].front() + tm.set_cell(c, tile[0], tile[1], tile[2]) + changed = true + found = true + break + + if found: + break + + return changed + + +## Returns the terrain type detected in the [TileMapLayer] at specified [Vector2i] +## [code]coord[/code]. Returns -1 if tile is not valid or does not contain a +## tile associated with a terrain. +func get_cell(tm: TileMapLayer, coord: Vector2i) -> int: + if !tm or !tm.tile_set: + return TileCategory.ERROR + + if tm.get_cell_source_id(coord) == -1: + return TileCategory.EMPTY + + var t := tm.get_cell_tile_data(coord) + if !t: + return TileCategory.NON_TERRAIN + + return _get_tile_meta(t).type + + +## Runs the tile solving algorithm on the [TileMapLayer] for the given +## [Vector2i] coordinates in the [code]cells[/code] parameter. By default, +## the surrounding cells are also solved, but this can be adjusted by passing [code]false[/code] +## to the [code]and_surrounding_cells[/code] parameter. +## [br][br] +## See also [method update_terrain_area] and [method update_terrain_cell]. +func update_terrain_cells(tm: TileMapLayer, cells: Array, and_surrounding_cells := true) -> void: + if !tm or !tm.tile_set: + return + + if and_surrounding_cells: + cells = _widen(tm, cells) + var needed_cells := _widen(tm, cells) + + var types := {} + for c in needed_cells: + types[c] = get_cell(tm, c) + + var ts_meta := _get_terrain_meta(tm.tile_set) + var cache := _get_cache(tm.tile_set) + for c in cells: + _update_tile_immediate(tm, c, ts_meta, types, cache) + + +## Runs the tile solving algorithm on the [TileMapLayer] for the given [Vector2i] +## [code]cell[/code]. By default, the surrounding cells are also solved, but +## this can be adjusted by passing [code]false[/code] to the [code]and_surrounding_cells[/code] +## parameter. This calls through to [method update_terrain_cells]. +func update_terrain_cell(tm: TileMapLayer, cell: Vector2i, and_surrounding_cells := true) -> void: + update_terrain_cells(tm, [cell], and_surrounding_cells) + + +## Runs the tile solving algorithm on the [TileMapLayer] for the given [Rect2i] +## [code]area[/code]. By default, the surrounding cells are also solved, but +## this can be adjusted by passing [code]false[/code] to the [code]and_surrounding_cells[/code] +## parameter. +## [br][br] +## See also [method update_terrain_cells]. +func update_terrain_area(tm: TileMapLayer, area: Rect2i, and_surrounding_cells := true) -> void: + if !tm or !tm.tile_set: + return + + # Normalize area and extend so tiles cover inclusive space + area = area.abs() + area.size += Vector2i.ONE + + var edges = [] + for x in range(area.position.x, area.end.x): + edges.append(Vector2i(x, area.position.y)) + edges.append(Vector2i(x, area.end.y - 1)) + for y in range(area.position.y + 1, area.end.y - 1): + edges.append(Vector2i(area.position.x, y)) + edges.append(Vector2i(area.end.x - 1, y)) + + var additional_cells := [] + var needed_cells := _widen_with_exclusion(tm, edges, area) + + if and_surrounding_cells: + additional_cells = needed_cells + needed_cells = _widen_with_exclusion(tm, needed_cells, area) + + var types := {} + for y in range(area.position.y, area.end.y): + for x in range(area.position.x, area.end.x): + var coord = Vector2i(x, y) + types[coord] = get_cell(tm, coord) + for c in needed_cells: + types[c] = get_cell(tm, c) + + var ts_meta := _get_terrain_meta(tm.tile_set) + var cache := _get_cache(tm.tile_set) + for y in range(area.position.y, area.end.y): + for x in range(area.position.x, area.end.x): + var coord := Vector2i(x, y) + _update_tile_immediate(tm, coord, ts_meta, types, cache) + for c in additional_cells: + _update_tile_immediate(tm, c, ts_meta, types, cache) + + +## For a [TileMapLayer], create a changeset that will +## be calculated via a [WorkerThreadPool], so it will not delay processing the current +## frame or affect the framerate. +## [br][br] +## The [code]paint[/code] parameter must be a [Dictionary] with keys of type [Vector2i] +## representing map coordinates, and integer values representing terrain types. +## [br][br] +## Returns a [Dictionary] with internal details. See also [method is_terrain_changeset_ready], +## [method apply_terrain_changeset], and [method wait_for_terrain_changeset]. +func create_terrain_changeset(tm: TileMapLayer, paint: Dictionary) -> Dictionary: + # Force cache rebuild if required + var _cache := _get_cache(tm.tile_set) + + var cells := paint.keys() + var needed_cells := _widen(tm, cells) + + var types := {} + for c in needed_cells: + types[c] = paint[c] if paint.has(c) else get_cell(tm, c) + + var placements := [] + placements.resize(cells.size()) + + var ts_meta := _get_terrain_meta(tm.tile_set) + var work := func(n: int): + placements[n] = _update_tile_deferred(tm, cells[n], ts_meta, types, _cache) + + return { + "valid": true, + "tilemap": tm, + "cells": cells, + "placements": placements, + "group_id": WorkerThreadPool.add_group_task(work, cells.size(), -1, false, "BetterTerrain") + } + + +## Returns [code]true[/code] if a changeset created by [method create_terrain_changeset] +## has finished the threaded calculation and is ready to be applied by [method apply_terrain_changeset]. +## See also [method wait_for_terrain_changeset]. +func is_terrain_changeset_ready(change: Dictionary) -> bool: + if !change.has("group_id"): + return false + + return WorkerThreadPool.is_group_task_completed(change.group_id) + + +## Blocks until a changeset created by [method create_terrain_changeset] finishes. +## This is useful to tidy up threaded work in the event that a node is to be removed +## whilst still waiting on threads. +## [br][br] +## Usage example: +## [codeblock] +## func _exit_tree(): +## if changeset.valid: +## BetterTerrain.wait_for_terrain_changeset(changeset) +## [/codeblock] +func wait_for_terrain_changeset(change: Dictionary) -> void: + if change.has("group_id"): + WorkerThreadPool.wait_for_group_task_completion(change.group_id) + + +## Apply the changes in a changeset created by [method create_terrain_changeset] +## once it is confirmed by [method is_terrain_changeset_ready]. The changes will +## be applied to the [TileMapLayer] that the changeset was initialized with. +## [br][br] +## Completed changesets can be applied multiple times, and stored for as long as +## needed once calculated. +func apply_terrain_changeset(change: Dictionary) -> void: + for n in change.cells.size(): + var placement = change.placements[n] + if placement: + change.tilemap.set_cell(change.cells[n], placement[0], placement[1], placement[2]) diff --git a/addons/better-terrain/BetterTerrain.gd.uid b/addons/better-terrain/BetterTerrain.gd.uid new file mode 100644 index 0000000..5a5c3a3 --- /dev/null +++ b/addons/better-terrain/BetterTerrain.gd.uid @@ -0,0 +1 @@ +uid://cw2lm8r6b280f diff --git a/addons/better-terrain/BetterTerrainData.gd b/addons/better-terrain/BetterTerrainData.gd new file mode 100644 index 0000000..5d3bcd5 --- /dev/null +++ b/addons/better-terrain/BetterTerrainData.gd @@ -0,0 +1,598 @@ +@tool + +## Data functions for [TileSet] properties. +## +## This data class has functions for retrieving data regarding the mathematical +## properties of a tile set. + +const _terrain_peering_square_tiles : Array[int] = [0, 3, 4, 7, 8, 11, 12, 15] +const _terrain_peering_square_vertices : Array[int] = [3, 7, 11, 15] +const _terrain_peering_isometric_tiles : Array[int] = [1, 2, 5, 6, 9, 10, 13, 14] +const _terrain_peering_isometric_vertices : Array[int] = [1, 5, 9, 13] +const _terrain_peering_horiztonal_tiles : Array[int] = [0, 2, 6, 8, 10, 14] +const _terrain_peering_horiztonal_vertices : Array[int] = [3, 5, 7, 11, 13, 15] +const _terrain_peering_vertical_tiles : Array[int] = [2, 4, 6, 10, 12, 14] +const _terrain_peering_vertical_vertices : Array[int] = [1, 3, 7, 9, 11, 15] +const _terrain_peering_non_modifying : Array[int] = [] + +const _terrain_peering_hflip : Array[int] = [8, 9, 6, 7, 4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11] +const _terrain_peering_vflip : Array[int] = [0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3] +const _terrain_peering_transpose : Array[int] = [4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7] + +const symmetry_mapping := { + BetterTerrain.SymmetryType.NONE: [0], + BetterTerrain.SymmetryType.MIRROR: [0, TileSetAtlasSource.TRANSFORM_FLIP_H], + BetterTerrain.SymmetryType.FLIP: [0, TileSetAtlasSource.TRANSFORM_FLIP_V], + BetterTerrain.SymmetryType.REFLECT: [ + 0, + TileSetAtlasSource.TRANSFORM_FLIP_H, + TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V + ], + BetterTerrain.SymmetryType.ROTATE_CLOCKWISE: [0, TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE], + BetterTerrain.SymmetryType.ROTATE_COUNTER_CLOCKWISE: [0, TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE], + BetterTerrain.SymmetryType.ROTATE_180: [0, TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V], + BetterTerrain.SymmetryType.ROTATE_ALL: [ + 0, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE + ], + BetterTerrain.SymmetryType.ALL: [ + 0, + TileSetAtlasSource.TRANSFORM_FLIP_H, + TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE + ] +} + + +## Returns an [Array] of ints of type [enum TileSet.CellNeighbor] which represent +## the valid neighboring tiles for a terrain of [code]type[/code] in TileSet +static func get_terrain_peering_cells(ts: TileSet, type: int) -> Array[int]: + if !ts or type < 0 or type >= BetterTerrain.TerrainType.MAX: + return [] + + if type == BetterTerrain.TerrainType.CATEGORY: + return _terrain_peering_non_modifying + if type == BetterTerrain.TerrainType.DECORATION: + type = BetterTerrain.TerrainType.MATCH_TILES + + match [ts.tile_shape, type]: + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_square_tiles + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_square_vertices + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_isometric_tiles + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_isometric_vertices + + match [ts.tile_offset_axis, type]: + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_vertical_tiles + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_vertical_vertices + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_horiztonal_tiles + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_horiztonal_vertices + + return [] + + +## Returns true if [code]peering[/code] is a valid neighboring cell for a terrain of +## [code]type[/code] in [TileSet] +static func is_terrain_peering_cell(ts: TileSet, type: int, peering: int) -> bool: + return peering in get_terrain_peering_cells(ts, type) + + +static func _peering_polygon_square_tiles(peering: int) -> PackedVector2Array: + const t := 1.0 / 3.0 + var result : PackedVector2Array + match peering: + TileSet.CELL_NEIGHBOR_RIGHT_SIDE: result.append(Vector2(2*t, t)) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: result.append(Vector2(2*t, 2*t)) + TileSet.CELL_NEIGHBOR_BOTTOM_SIDE: result.append(Vector2(t, 2*t)) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: result.append(Vector2(0, 2*t)) + TileSet.CELL_NEIGHBOR_LEFT_SIDE: result.append(Vector2(0, t)) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: result.append(Vector2(0, 0)) + TileSet.CELL_NEIGHBOR_TOP_SIDE: result.append(Vector2(t, 0)) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: result.append(Vector2(2*t, 0)) + -1: result.append(Vector2(t, t)) + result.append(result[0] + Vector2(t, 0)) + result.append(result[0] + Vector2(t, t)) + result.append(result[0] + Vector2(0, t)) + return result + + +static func _peering_polygon_square_vertices(peering: int) -> PackedVector2Array: + const t := 1.0 / 2.0 + var result : PackedVector2Array + match peering: + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + result.append(Vector2(1, t)) + result.append(Vector2(1, 1)) + result.append(Vector2(t, 1)) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + result.append(Vector2(0, t)) + result.append(Vector2(t, 1)) + result.append(Vector2(0, 1)) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + result.append(Vector2(0, 0)) + result.append(Vector2(t, 0)) + result.append(Vector2(0, t)) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + result.append(Vector2(t, 0)) + result.append(Vector2(1, 0)) + result.append(Vector2(1, t)) + -1: + result.append(Vector2(t, 0)) + result.append(Vector2(1, t)) + result.append(Vector2(t, 1)) + result.append(Vector2(0, t)) + return result + + +static func _peering_polygon_isometric_tiles(peering: int) -> PackedVector2Array: + const t := 1.0 / 4.0 + match peering: + -1: return PackedVector2Array([Vector2(2 * t, t), Vector2(3 * t, 2 * t), Vector2(2 * t, 3 * t), Vector2(t, 2 * t)]) + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return PackedVector2Array([Vector2(3 * t, 2 * t), Vector2(1, t), Vector2(1, 3 * t)]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + return PackedVector2Array([Vector2(3 * t, 2 * t), Vector2(1, 3 * t), Vector2(3 * t, 1), Vector2(2 * t, 3 * t)]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return PackedVector2Array([Vector2(2 * t, 3 * t), Vector2(3 * t, 1), Vector2(t, 1)]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + return PackedVector2Array([Vector2(t, 2 * t), Vector2(2 * t, 3 * t), Vector2(t, 1), Vector2(0, 3 * t)]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return PackedVector2Array([Vector2(0, t), Vector2(t, 2 * t), Vector2(0, 3 * t)]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE: + return PackedVector2Array([Vector2(t, 0), Vector2(2 * t, t), Vector2(t, 2 * t), Vector2(0, t)]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return PackedVector2Array([Vector2(t, 0), Vector2(3 * t, 0), Vector2(2 * t, t)]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE: + return PackedVector2Array([Vector2(3 * t, 0), Vector2(1, t), Vector2(3 * t, 2 * t), Vector2(2 * t, t)]) + return PackedVector2Array() + + +static func _peering_polygon_isometric_vertices(peering: int) -> PackedVector2Array: + const t := 1.0 / 4.0 + const ttt := 3.0 * t + match peering: + -1: return PackedVector2Array([Vector2(t, t), Vector2(ttt, t), Vector2(ttt, ttt), Vector2(t, ttt)]) + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return PackedVector2Array([Vector2(ttt, t), Vector2(1, 0), Vector2(1, 1), Vector2(ttt, ttt)]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return PackedVector2Array([Vector2(t, ttt), Vector2(ttt, ttt), Vector2(1, 1), Vector2(0, 1)]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return PackedVector2Array([Vector2(0, 0), Vector2(t, t), Vector2(t, ttt), Vector2(0, 1)]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return PackedVector2Array([Vector2(0, 0), Vector2(1, 0), Vector2(ttt, t), Vector2(t, t)]) + return PackedVector2Array() + + +static func _peering_polygon_horizontal_tiles(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t, 2 * s), + Vector2(t + w, t - s), + Vector2(t + w, t + s), + Vector2(t, 6 * s), + Vector2(t - w, t + s), + Vector2(t - w, t - s) + ]) + TileSet.CELL_NEIGHBOR_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t + w, t - s), + Vector2(1, t - e), + Vector2(1, t + e), + Vector2(t + w, t + s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t + w, t + s), + Vector2(1, t + e), + Vector2(t, 1), + Vector2(t, 6 * s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t, 6 * s), + Vector2(t, 1), + Vector2(0, t + e), + Vector2(t - w, t + s) + ]) + TileSet.CELL_NEIGHBOR_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t - w, t + s), + Vector2(0, t + e), + Vector2(0, t - e), + Vector2(t - w, t - s) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t - w, t - s), + Vector2(0, t - e), + Vector2(t, 0), + Vector2(t, 2 * s) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t, 2 * s), + Vector2(t, 0), + Vector2(1, t - e), + Vector2(t + w, t - s) + ]) + return PackedVector2Array() + + +static func _peering_polygon_horizontal_vertices(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t - s, t - w), + Vector2(t + s, t - w), + Vector2(6 * s, t), + Vector2(t + s, t + w), + Vector2(t - s, t + w), + Vector2(2 * s, t) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(6 * s, t), + Vector2(1, t), + Vector2(1, t + e), + Vector2(t + e, 1 - s), + Vector2(t + s, t + w) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return PackedVector2Array([ + Vector2(t - s, t + w), + Vector2(t + s, t + w), + Vector2(t + e, 1 - s), + Vector2(t, 1), + Vector2(t - e, 1 - s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return PackedVector2Array([ + Vector2(0, t), + Vector2(2 * s, t), + Vector2(t - s, t + w), + Vector2(t - e, 1 - s), + Vector2(0, t + e) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return PackedVector2Array([ + Vector2(t - e, s), + Vector2(t - s, t - w), + Vector2(2 * s, t), + Vector2(0, t), + Vector2(0, t - e) + ]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return PackedVector2Array([ + Vector2(t, 0), + Vector2(t + e, s), + Vector2(t + s, t - w), + Vector2(t - s, t - w), + Vector2(t - e, s) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(t + e, s), + Vector2(1, t - e), + Vector2(1, t), + Vector2(6 * s, t), + Vector2(t + s, t - w) + ]) + return PackedVector2Array() + + +static func _peering_polygon_vertical_tiles(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t - s, t - w), + Vector2(t + s, t - w), + Vector2(6 * s, t), + Vector2(t + s, t + w), + Vector2(t - s, t + w), + Vector2(2 * s, t) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(6 * s, t), + Vector2(1, t), + Vector2(t + e, 1), + Vector2(t + s, t + w) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_SIDE: + return PackedVector2Array([ + Vector2(t - s, t + w), + Vector2(t + s, t + w), + Vector2(t + e, 1), + Vector2(t - e, 1) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + return PackedVector2Array([ + Vector2(0, t), + Vector2(2 * s, t), + Vector2(t - s, t + w), + Vector2(t - e, 1) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t - e, 0), + Vector2(t - s, t - w), + Vector2(2 * s, t), + Vector2(0, t) + ]) + TileSet.CELL_NEIGHBOR_TOP_SIDE: + return PackedVector2Array([ + Vector2(t - e, 0), + Vector2(t + e, 0), + Vector2(t + s, t - w), + Vector2(t - s, t - w) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t + e, 0), + Vector2(1, t), + Vector2(6 * s, t), + Vector2(t + s, t - w) + ]) + return PackedVector2Array() + + +static func _peering_polygon_vertical_vertices(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t, 2 * s), + Vector2(t + w, t - s), + Vector2(t + w, t + s), + Vector2(t, 6 * s), + Vector2(t - w, t + s), + Vector2(t - w, t - s) + ]) + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(1 - s, t - e), + Vector2(1, t), + Vector2(1 - s, t + e), + Vector2(t + w, t + s), + Vector2(t + w, t - s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(t + w, t + s), + Vector2(1 - s, t + e), + Vector2(t + e, 1), + Vector2(t, 1), + Vector2(t, 6 * s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return PackedVector2Array([ + Vector2(t - w, t + s), + Vector2(t, 6 * s), + Vector2(t, 1), + Vector2(t - e, 1), + Vector2(s, t + e) + ]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return PackedVector2Array([ + Vector2(s, t - e), + Vector2(t - w, t - s), + Vector2(t - w, t + s), + Vector2(s, t + e), + Vector2(0, t) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return PackedVector2Array([ + Vector2(t - e, 0), + Vector2(t, 0), + Vector2(t, 2 * s), + Vector2(t - w, t - s), + Vector2(s, t - e) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(t, 0), + Vector2(t + e, 0), + Vector2(1 - s, t - e), + Vector2(t + w, t - s), + Vector2(t, 2 * s) + ]) + return PackedVector2Array() + + +static func _peering_non_modifying() -> PackedVector2Array: + const t := 1.0 / 3.0 + return PackedVector2Array([ + Vector2(t, 0), + Vector2(2 * t, 0), + Vector2(1, t), + Vector2(1, 2 * t), + Vector2(2 * t, 1), + Vector2(t, 1), + Vector2(0, 2 * t), + Vector2(0, t) + ]) + + +## Returns a parameterized polygon (coordinated are between 0 and 1) for [code]peering[/code] +## direction for a terrain of [code]type[/code] in [TileSet] +static func peering_polygon(ts: TileSet, type: int, peering: int) -> PackedVector2Array: + if type == BetterTerrain.TerrainType.CATEGORY: + return _peering_non_modifying() + if type == BetterTerrain.TerrainType.DECORATION: + type = BetterTerrain.TerrainType.MATCH_TILES + + match [ts.tile_shape, type]: + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_square_tiles(peering) + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_square_vertices(peering) + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_isometric_tiles(peering) + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_isometric_vertices(peering) + + match [ts.tile_offset_axis, type]: + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_vertical_tiles(peering) + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_vertical_vertices(peering) + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_horizontal_tiles(peering) + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_horizontal_vertices(peering) + + return PackedVector2Array() + + +## Returns as polygon centered on 0, 0 which represents the shape of the cell of +## a tile from [TileSet]. +static func cell_polygon(ts: TileSet) -> PackedVector2Array: + const t := 1.0 / 2.0 + if ts.tile_shape in [TileSet.TILE_SHAPE_SQUARE, TileSet.TILE_SHAPE_HALF_OFFSET_SQUARE]: + return PackedVector2Array([Vector2(-t, -t), Vector2(t, -t), Vector2(t, t), Vector2(-t, t)]) + if ts.tile_shape == TileSet.TILE_SHAPE_ISOMETRIC: + return PackedVector2Array([Vector2(0, -t), Vector2(t, 0), Vector2(0, t), Vector2(-t, 0)]) + + const e := t - 1.0 / (2.0 * sqrt(3.0)) + if ts.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL: + return PackedVector2Array([ + Vector2(0, -t), + Vector2(t, -e), + Vector2(t, e), + Vector2(0, t), + Vector2(-t, e), + Vector2(-t, -e), + ]) + + return PackedVector2Array([ + Vector2(-t, 0), + Vector2(-e, -t), + Vector2(e, -t), + Vector2(t, 0), + Vector2(e, t), + Vector2(-e, t), + ]) + + +## Returns an [Array] of coordinated that neighbor [code]coord[/code] based on [code]peering[/code] +## [Array] of [enum TileSet.CellNeighbor] for a [TileSet]. +static func neighboring_coords(tm: TileMapLayer, coord: Vector2i, peerings: Array) -> Array: + return peerings.map(func(p): return tm.get_neighbor_cell(coord, p)) + + +## Returns an [Array] of coordinates which neighbor the vertex describe by [code]corner[/code] +## (which is of type [enum TileSet.CellNeighbor]) from [code]coord[/code] in [TileSet]. +static func associated_vertex_cells(tm: TileMapLayer, coord: Vector2i, corner: int) -> Array: + # get array of associated peering bits + if tm.tile_set.tile_shape in [TileSet.TILE_SHAPE_SQUARE, TileSet.TILE_SHAPE_ISOMETRIC]: + match corner: + # Square + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return neighboring_coords(tm, coord, [0, 3, 4]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return neighboring_coords(tm, coord, [4, 7, 8]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return neighboring_coords(tm, coord, [8, 11, 12]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return neighboring_coords(tm, coord, [12, 15, 0]) + # Isometric + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return neighboring_coords(tm, coord, [14, 1, 2]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return neighboring_coords(tm, coord, [2, 5, 6]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return neighboring_coords(tm, coord, [6, 9, 10]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return neighboring_coords(tm, coord, [10, 13, 14]) + + if tm.tile_set.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL: + match corner: + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return neighboring_coords(tm, coord, [0, 2]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return neighboring_coords(tm, coord, [2, 6]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return neighboring_coords(tm, coord, [6, 8]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return neighboring_coords(tm, coord, [8, 10]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return neighboring_coords(tm, coord, [10, 14]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return neighboring_coords(tm, coord, [14, 0]) + + # TileSet.TILE_OFFSET_AXIS_VERTICAL + match corner: + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return neighboring_coords(tm, coord, [14, 2]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return neighboring_coords(tm, coord, [2, 4]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return neighboring_coords(tm, coord, [4, 6]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return neighboring_coords(tm, coord, [6, 10]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return neighboring_coords(tm, coord, [10, 12]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return neighboring_coords(tm, coord, [12, 14]) + + return [] + + +## Returns an [Array] of [enum TileSet.CellNeighbor] suitable for flood filling +## an area in [TileSet]. +static func cells_adjacent_for_fill(ts: TileSet) -> Array[int]: + if ts.tile_shape == TileSet.TILE_SHAPE_SQUARE: + return [0, 4, 8, 12] + if ts.tile_shape == TileSet.TILE_SHAPE_ISOMETRIC: + return [2, 6, 10, 14] + if ts.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL: + return _terrain_peering_horiztonal_tiles + return _terrain_peering_vertical_tiles + + +static func peering_bit_after_symmetry(bit: int, altflags: int) -> int: + if altflags & TileSetAtlasSource.TRANSFORM_TRANSPOSE: + bit = _terrain_peering_transpose[bit] + if altflags & TileSetAtlasSource.TRANSFORM_FLIP_H: + bit = _terrain_peering_hflip[bit] + if altflags & TileSetAtlasSource.TRANSFORM_FLIP_V: + bit = _terrain_peering_vflip[bit] + return bit + + +static func peering_bits_after_symmetry(dict: Dictionary, altflags: int) -> Dictionary: + # rearrange dictionary keys based on altflags + var result := {} + for k in dict: + result[peering_bit_after_symmetry(k, altflags)] = dict[k] + return result diff --git a/addons/better-terrain/BetterTerrainData.gd.uid b/addons/better-terrain/BetterTerrainData.gd.uid new file mode 100644 index 0000000..f52eaec --- /dev/null +++ b/addons/better-terrain/BetterTerrainData.gd.uid @@ -0,0 +1 @@ +uid://ck1nsdje3l5mc diff --git a/addons/better-terrain/TerrainPlugin.gd b/addons/better-terrain/TerrainPlugin.gd new file mode 100644 index 0000000..81e63d2 --- /dev/null +++ b/addons/better-terrain/TerrainPlugin.gd @@ -0,0 +1,72 @@ +@tool +extends EditorPlugin + +const AUTOLOAD_NAME = "BetterTerrain" +var dock : Control +var button : Button + +func _enter_tree() -> void: + # Wait for autoloads to register + await get_tree().process_frame + + if !get_tree().root.get_node_or_null(^"BetterTerrain"): + # Autoload wasn't present on plugin init, which means plugin won't have loaded correctly + add_autoload_singleton(AUTOLOAD_NAME, "res://addons/better-terrain/BetterTerrain.gd") + ProjectSettings.save() + + var confirm = ConfirmationDialog.new() + confirm.dialog_text = "The editor needs to be restarted for Better Terrain to load correctly. Restart now? Note: Unsaved changes will be lost." + confirm.confirmed.connect(func(): + OS.set_restart_on_exit(true, ["-e"]) + get_tree().quit() + ) + get_editor_interface().popup_dialog_centered(confirm) + + dock = load("res://addons/better-terrain/editor/Dock.tscn").instantiate() + dock.update_overlay.connect(self.update_overlays) + get_editor_interface().get_editor_main_screen().mouse_exited.connect(dock.canvas_mouse_exit) + dock.undo_manager = get_undo_redo() + button = add_control_to_bottom_panel(dock, "Terrain") + button.toggled.connect(dock.about_to_be_visible) + dock.force_show_terrains.connect(button.toggled.emit.bind(true)) + button.visible = false + + +func _exit_tree() -> void: + remove_control_from_bottom_panel(dock) + dock.queue_free() + + +func _handles(object) -> bool: + return object is TileMapLayer or object is TileSet + + +func _make_visible(visible) -> void: + button.visible = visible + + +func _edit(object) -> void: + var new_tileset : TileSet = null + + if object is TileMapLayer: + dock.tilemap = object + new_tileset = object.tile_set + if object is TileSet: + new_tileset = object + + if dock.tileset != new_tileset: + dock.tiles_about_to_change() + dock.tileset = new_tileset + dock.tiles_changed() + + +func _forward_canvas_draw_over_viewport(overlay: Control) -> void: + if dock.visible: + dock.canvas_draw(overlay) + + +func _forward_canvas_gui_input(event: InputEvent) -> bool: + if !dock.visible: + return false + + return dock.canvas_input(event) diff --git a/addons/better-terrain/TerrainPlugin.gd.uid b/addons/better-terrain/TerrainPlugin.gd.uid new file mode 100644 index 0000000..5e768ea --- /dev/null +++ b/addons/better-terrain/TerrainPlugin.gd.uid @@ -0,0 +1 @@ +uid://c2sae6p4m6qn2 diff --git a/addons/better-terrain/Watcher.gd b/addons/better-terrain/Watcher.gd new file mode 100644 index 0000000..0c5fa51 --- /dev/null +++ b/addons/better-terrain/Watcher.gd @@ -0,0 +1,20 @@ +@tool +extends Node + +signal trigger +var complete := false +var tileset : TileSet + +func tidy() -> bool: + if complete: + return false + + complete = true + queue_free() + return true + + +func activate(): + if tidy(): + trigger.emit() + diff --git a/addons/better-terrain/Watcher.gd.uid b/addons/better-terrain/Watcher.gd.uid new file mode 100644 index 0000000..bfdf03c --- /dev/null +++ b/addons/better-terrain/Watcher.gd.uid @@ -0,0 +1 @@ +uid://bl00s5w02o4l7 diff --git a/addons/better-terrain/editor/Dock.gd b/addons/better-terrain/editor/Dock.gd new file mode 100644 index 0000000..c75ec7a --- /dev/null +++ b/addons/better-terrain/editor/Dock.gd @@ -0,0 +1,943 @@ +@tool +extends Control + +signal update_overlay +signal force_show_terrains + +# The maximum individual tiles the overlay will draw before shortcutting the display +# To prevent editor lag when drawing large rectangles or filling large areas +const MAX_CANVAS_RENDER_TILES = 1500 +const TERRAIN_PROPERTIES_SCENE := preload("res://addons/better-terrain/editor/TerrainProperties.tscn") +const TERRAIN_ENTRY_SCENE := preload("res://addons/better-terrain/editor/TerrainEntry.tscn") +const MIN_ZOOM_SETTING := "editor/better_terrain/min_zoom_amount" +const MAX_ZOOM_SETTING := "editor/better_terrain/max_zoom_amount" + + +# Buttons +@onready var draw_button: Button = $VBox/Toolbar/Draw +@onready var line_button: Button = $VBox/Toolbar/Line +@onready var rectangle_button: Button = $VBox/Toolbar/Rectangle +@onready var fill_button: Button = $VBox/Toolbar/Fill +@onready var replace_button: Button = $VBox/Toolbar/Replace + +@onready var paint_type: Button = $VBox/Toolbar/PaintType +@onready var paint_terrain: Button = $VBox/Toolbar/PaintTerrain +@onready var select_tiles: Button = $VBox/Toolbar/SelectTiles + +@onready var paint_symmetry: Button = $VBox/Toolbar/PaintSymmetry +@onready var symmetry_options: OptionButton = $VBox/Toolbar/SymmetryOptions + +@onready var shuffle_random: Button = $VBox/Toolbar/ShuffleRandom +@onready var zoom_slider_container: VBoxContainer = $VBox/Toolbar/ZoomContainer + +@onready var source_selector: MenuBar = $VBox/Toolbar/Sources +@onready var source_selector_popup: PopupMenu = $VBox/Toolbar/Sources/Sources + +@onready var clean_button: Button = $VBox/Toolbar/Clean +@onready var layer_up: Button = $VBox/Toolbar/LayerUp +@onready var layer_down: Button = $VBox/Toolbar/LayerDown +@onready var layer_highlight: Button = $VBox/Toolbar/LayerHighlight +@onready var layer_grid: Button = $VBox/Toolbar/LayerGrid + +@onready var grid_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/GridMode +@onready var quick_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/QuickMode + +@onready var edit_tool_buttons: HBoxContainer = $VBox/HSplit/Terrains/LowerToolbar/EditTools +@onready var add_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain +@onready var edit_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain +@onready var pick_icon_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon +@onready var move_up_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp +@onready var move_down_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown +@onready var remove_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain + +@onready var scroll_container: ScrollContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer +@onready var terrain_list: HFlowContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer/TerrainList +@onready var tile_view: Control = $VBox/HSplit/Panel/ScrollArea/TileView + + +var selected_entry := -2 + +var tilemap : TileMapLayer +var tileset : TileSet + +var undo_manager : EditorUndoRedoManager +var terrain_undo + +var draw_overlay := false +var initial_click : Vector2i +var prev_position : Vector2i +var current_position : Vector2i +var tileset_dirty := false +var zoom_slider : HSlider + +enum PaintMode { + NO_PAINT, + PAINT, + ERASE +} + +enum PaintAction { + NO_ACTION, + LINE, + RECT +} + +enum SourceSelectors { + ALL = 1000000, + NONE = 1000001, +} + +var paint_mode := PaintMode.NO_PAINT + +var paint_action := PaintAction.NO_ACTION + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + draw_button.icon = get_theme_icon("Edit", "EditorIcons") + line_button.icon = get_theme_icon("Line", "EditorIcons") + rectangle_button.icon = get_theme_icon("Rectangle", "EditorIcons") + fill_button.icon = get_theme_icon("Bucket", "EditorIcons") + select_tiles.icon = get_theme_icon("ToolSelect", "EditorIcons") + add_terrain_button.icon = get_theme_icon("Add", "EditorIcons") + edit_terrain_button.icon = get_theme_icon("Tools", "EditorIcons") + pick_icon_button.icon = get_theme_icon("ColorPick", "EditorIcons") + move_up_button.icon = get_theme_icon("ArrowUp", "EditorIcons") + move_down_button.icon = get_theme_icon("ArrowDown", "EditorIcons") + remove_terrain_button.icon = get_theme_icon("Remove", "EditorIcons") + grid_mode_button.icon = get_theme_icon("FileThumbnail", "EditorIcons") + quick_mode_button.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons") + layer_up.icon = get_theme_icon("MoveUp", "EditorIcons") + layer_down.icon = get_theme_icon("MoveDown", "EditorIcons") + layer_highlight.icon = get_theme_icon("TileMapHighlightSelected", "EditorIcons") + layer_grid.icon = get_theme_icon("Grid", "EditorIcons") + + select_tiles.button_group.pressed.connect(_on_bit_button_pressed) + + terrain_undo = load("res://addons/better-terrain/editor/TerrainUndo.gd").new() + add_child(terrain_undo) + tile_view.undo_manager = undo_manager + tile_view.terrain_undo = terrain_undo + + tile_view.paste_occurred.connect(_on_paste_occurred) + tile_view.change_zoom_level.connect(_on_change_zoom_level) + tile_view.terrain_updated.connect(_on_terrain_updated) + + # Zoom slider is manipulated by settings, make it at runtime + zoom_slider = HSlider.new() + zoom_slider.custom_minimum_size = Vector2(100, 0) + zoom_slider.value_changed.connect(tile_view._on_zoom_value_changed) + zoom_slider_container.add_child(zoom_slider) + + # Init settings if needed + if !ProjectSettings.has_setting(MIN_ZOOM_SETTING): + ProjectSettings.set(MIN_ZOOM_SETTING, 1.0) + ProjectSettings.add_property_info({ + "name": MIN_ZOOM_SETTING, + "type": TYPE_FLOAT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0.1,1.0,0.1" + }) + ProjectSettings.set_initial_value(MIN_ZOOM_SETTING, 1.0) + ProjectSettings.set_as_basic(MIN_ZOOM_SETTING, true) + + if !ProjectSettings.has_setting(MAX_ZOOM_SETTING): + ProjectSettings.set(MAX_ZOOM_SETTING, 8.0) + ProjectSettings.add_property_info({ + "name": MAX_ZOOM_SETTING, + "type": TYPE_FLOAT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "2.0,32.0,1.0" + }) + ProjectSettings.set_initial_value(MAX_ZOOM_SETTING, 8.0) + ProjectSettings.set_as_basic(MAX_ZOOM_SETTING, true) + ProjectSettings.set_order(MAX_ZOOM_SETTING, ProjectSettings.get_order(MIN_ZOOM_SETTING) + 1) + + ProjectSettings.settings_changed.connect(_on_adjust_settings) + _on_adjust_settings() + zoom_slider.value = 1.0 + + +func _process(delta): + scroll_container.scroll_horizontal = 0 + + +func _on_adjust_settings(): + zoom_slider.min_value = ProjectSettings.get_setting(MIN_ZOOM_SETTING, 1.0) + zoom_slider.max_value = ProjectSettings.get_setting(MAX_ZOOM_SETTING, 8.0) + zoom_slider.step = (zoom_slider.max_value - zoom_slider.min_value) / 100.0 + + +func _get_fill_cells(target: Vector2i) -> Array: + var pick := BetterTerrain.get_cell(tilemap, target) + var bounds := tilemap.get_used_rect() + var neighbors = BetterTerrain.data.cells_adjacent_for_fill(tileset) + + # No sets yet, so use a dictionary + var checked := {} + var pending := [target] + var goal := [] + + while !pending.is_empty(): + var p = pending.pop_front() + if checked.has(p): + continue + checked[p] = true + if !bounds.has_point(p) or BetterTerrain.get_cell(tilemap, p) != pick: + continue + + goal.append(p) + pending.append_array(BetterTerrain.data.neighboring_coords(tilemap, p, neighbors)) + + return goal + + +func tiles_about_to_change() -> void: + if tileset and tileset.changed.is_connected(queue_tiles_changed): + tileset.changed.disconnect(queue_tiles_changed) + + +func tiles_changed() -> void: + # ensure up to date + BetterTerrain._update_terrain_data(tileset) + + # clear terrains + for c in terrain_list.get_children(): + terrain_list.remove_child(c) + c.queue_free() + + # load terrains from tileset + var terrain_count := BetterTerrain.terrain_count(tileset) + var item_count = terrain_count + 1 + for i in terrain_count: + var terrain := BetterTerrain.get_terrain(tileset, i) + if i >= terrain_list.get_child_count(): + add_terrain_entry(terrain, i) + + if item_count > terrain_list.get_child_count(): + var terrain := BetterTerrain.get_terrain(tileset, BetterTerrain.TileCategory.EMPTY) + if terrain.valid: + add_terrain_entry(terrain, item_count - 1) + + while item_count < terrain_list.get_child_count(): + var child = terrain_list.get_child(terrain_list.get_child_count() - 1) + terrain_list.remove_child(child) + child.free() + + source_selector_popup.clear() + source_selector_popup.add_item("All", SourceSelectors.ALL) + source_selector_popup.add_item("None", SourceSelectors.NONE) + var source_count = tileset.get_source_count() if tileset else 0 + for s in source_count: + var source_id = tileset.get_source_id(s) + var source := tileset.get_source(source_id) + if !(source is TileSetAtlasSource): + continue + + var name := source.resource_name + if name.is_empty(): + var texture := (source as TileSetAtlasSource).texture + var texture_name := texture.resource_name if texture else "" + if !texture_name.is_empty(): + name = texture_name + else: + var texture_path := texture.resource_path if texture else "" + if !texture_path.is_empty(): + name = texture_path.get_file() + + if !name.is_empty(): + name += " " + name += " (ID: %d)" % source_id + + source_selector_popup.add_check_item(name, source_id) + + source_selector_popup.set_item_checked( + source_selector_popup.get_item_index(source_id), + not tile_view.disabled_sources.has(source_id) + ) + source_selector.visible = source_selector_popup.item_count > 3 # All, None and more than one source + + update_tile_view_paint() + tile_view.refresh_tileset(tileset) + + if tileset and !tileset.changed.is_connected(queue_tiles_changed): + tileset.changed.connect(queue_tiles_changed) + + clean_button.visible = BetterTerrain._has_invalid_peering_types(tileset) + + tileset_dirty = false + _on_grid_mode_pressed() + _on_quick_mode_pressed() + + +func about_to_be_visible(visible: bool) -> void: + if !visible: + return + + if tileset != tilemap.tile_set: + tiles_about_to_change() + tileset = tilemap.tile_set + tiles_changed() + + var settings := EditorInterface.get_editor_settings() + layer_highlight.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/highlight_selected_layer")) + layer_grid.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/display_grid")) + + +func queue_tiles_changed() -> void: + # Bring terrain data up to date with complex tileset changes + if !tileset or tileset_dirty: + return + + tileset_dirty = true + tiles_changed.call_deferred() + + +func _on_entry_select(index:int): + selected_entry = index + if selected_entry >= BetterTerrain.terrain_count(tileset): + selected_entry = BetterTerrain.TileCategory.EMPTY + for i in range(terrain_list.get_child_count()): + if i != index: + terrain_list.get_child(i).set_selected(false) + update_tile_view_paint() + + +func _on_clean_pressed() -> void: + var confirmed := [false] + var popup := ConfirmationDialog.new() + popup.dialog_text = tr("Tile set changes have caused terrain to become invalid. Remove invalid terrain data?") + popup.dialog_hide_on_ok = false + popup.confirmed.connect(func(): + confirmed[0] = true + popup.hide() + ) + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + popup.queue_free() + + if confirmed[0]: + undo_manager.create_action("Clean invalid terrain peering data", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(BetterTerrain, &"_clear_invalid_peering_types", tileset) + undo_manager.add_do_method(self, &"tiles_changed") + terrain_undo.create_peering_restore_point(undo_manager, tileset) + undo_manager.add_undo_method(self, &"tiles_changed") + undo_manager.commit_action() + + +func _on_grid_mode_pressed() -> void: + for c in terrain_list.get_children(): + c.grid_mode = grid_mode_button.button_pressed + c.update_style() + + +func _on_quick_mode_pressed() -> void: + edit_tool_buttons.visible = !quick_mode_button.button_pressed + for c in terrain_list.get_children(): + c.visible = !quick_mode_button.button_pressed or c.terrain.type in [BetterTerrain.TerrainType.MATCH_TILES, BetterTerrain.TerrainType.MATCH_VERTICES] + + +func update_tile_view_paint() -> void: + tile_view.paint = selected_entry + tile_view.queue_redraw() + + var editable = tile_view.paint != BetterTerrain.TileCategory.EMPTY + edit_terrain_button.disabled = !editable + move_up_button.disabled = !editable or tile_view.paint == 0 + move_down_button.disabled = !editable or tile_view.paint == BetterTerrain.terrain_count(tileset) - 1 + remove_terrain_button.disabled = !editable + pick_icon_button.disabled = !editable + + +func _on_add_terrain_pressed() -> void: + if !tileset: + return + + var popup := TERRAIN_PROPERTIES_SCENE.instantiate() + popup.set_category_data(BetterTerrain.get_terrain_categories(tileset)) + popup.terrain_name = "New terrain" + popup.terrain_color = Color.from_hsv(randf(), 0.3 + 0.7 * randf(), 0.6 + 0.4 * randf()) + popup.terrain_icon = "" + popup.terrain_type = 0 + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + if popup.accepted: + undo_manager.create_action("Add terrain type", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_add_terrain", popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon}) + undo_manager.add_undo_method(self, &"perform_remove_terrain", terrain_list.get_child_count() - 1) + undo_manager.commit_action() + popup.queue_free() + + +func _on_edit_terrain_pressed() -> void: + if !tileset: + return + + if selected_entry < 0: + return + + var t := BetterTerrain.get_terrain(tileset, selected_entry) + var categories = BetterTerrain.get_terrain_categories(tileset) + categories = categories.filter(func(x): return x.id != selected_entry) + + var popup := TERRAIN_PROPERTIES_SCENE.instantiate() + popup.set_category_data(categories) + + t.icon = t.icon.duplicate() + + popup.terrain_name = t.name + popup.terrain_type = t.type + popup.terrain_color = t.color + if t.has("icon") and t.icon.has("path"): + popup.terrain_icon = t.icon.path + popup.terrain_categories = t.categories + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + if popup.accepted: + undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_edit_terrain", selected_entry, popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon}) + undo_manager.add_undo_method(self, &"perform_edit_terrain", selected_entry, t.name, t.color, t.type, t.categories, t.icon) + if t.type != popup.terrain_type: + terrain_undo.create_terrain_type_restore_point(undo_manager, tileset) + terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry) + undo_manager.commit_action() + popup.queue_free() + + +func _on_pick_icon_pressed(): + if selected_entry < 0: + return + tile_view.pick_icon_terrain = selected_entry + + +func _on_pick_icon_focus_exited(): + tile_view.pick_icon_terrain_cancel = true + pick_icon_button.button_pressed = false + + +func _on_move_pressed(down: bool) -> void: + if !tileset: + return + + if selected_entry < 0: + return + + var index1 = selected_entry + var index2 = index1 + (1 if down else -1) + if index2 < 0 or index2 >= terrain_list.get_child_count(): + return + + undo_manager.create_action("Reorder terrains", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_swap_terrain", index1, index2) + undo_manager.add_undo_method(self, &"perform_swap_terrain", index1, index2) + undo_manager.commit_action() + + +func _on_remove_terrain_pressed() -> void: + if !tileset: + return + + if selected_entry < 0: + return + + # store confirmation in array to pass by ref + var t := BetterTerrain.get_terrain(tileset, selected_entry) + var confirmed := [false] + var popup := ConfirmationDialog.new() + popup.dialog_text = tr("Are you sure you want to remove {0}?").format([t.name]) + popup.dialog_hide_on_ok = false + popup.confirmed.connect(func(): + confirmed[0] = true + popup.hide() + ) + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + popup.queue_free() + + if confirmed[0]: + undo_manager.create_action("Remove terrain type", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_remove_terrain", selected_entry) + undo_manager.add_undo_method(self, &"perform_add_terrain", t.name, t.color, t.type, t.categories, t.icon) + for n in range(terrain_list.get_child_count() - 2, selected_entry, -1): + undo_manager.add_undo_method(self, &"perform_swap_terrain", n, n - 1) + if t.type == BetterTerrain.TerrainType.CATEGORY: + terrain_undo.create_terrain_type_restore_point(undo_manager, tileset) + terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry) + undo_manager.commit_action() + + +func add_terrain_entry(terrain:Dictionary, index:int = -1): + if index < 0: + index = terrain_list.get_child_count() + + var entry = TERRAIN_ENTRY_SCENE.instantiate() + entry.tileset = tileset + entry.terrain = terrain + entry.grid_mode = grid_mode_button.button_pressed + entry.select.connect(_on_entry_select) + + terrain_list.add_child(entry) + terrain_list.move_child(entry, index) + + +func remove_terrain_entry(index: int): + terrain_list.get_child(index).free() + for i in range(index, terrain_list.get_child_count()): + var child = terrain_list.get_child(i) + child.terrain = BetterTerrain.get_terrain(tileset, i) + child.update() + + +func perform_add_terrain(name: String, color: Color, type: int, categories: Array, icon:Dictionary = {}) -> void: + if BetterTerrain.add_terrain(tileset, name, color, type, categories, icon): + var index = BetterTerrain.terrain_count(tileset) - 1 + var terrain = BetterTerrain.get_terrain(tileset, index) + add_terrain_entry(terrain, index) + + +func perform_remove_terrain(index: int) -> void: + if index >= BetterTerrain.terrain_count(tileset): + return + if BetterTerrain.remove_terrain(tileset, index): + remove_terrain_entry(index) + update_tile_view_paint() + + +func perform_swap_terrain(index1: int, index2: int) -> void: + var lower := min(index1, index2) + var higher := max(index1, index2) + if lower >= terrain_list.get_child_count() or higher >= terrain_list.get_child_count(): + return + var item1 = terrain_list.get_child(lower) + var item2 = terrain_list.get_child(higher) + if BetterTerrain.swap_terrains(tileset, lower, higher): + terrain_list.move_child(item1, higher) + item1.terrain = BetterTerrain.get_terrain(tileset, higher) + item1.update() + item2.terrain = BetterTerrain.get_terrain(tileset, lower) + item2.update() + selected_entry = index2 + terrain_list.get_child(index2).set_selected(true) + update_tile_view_paint() + + +func perform_edit_terrain(index: int, name: String, color: Color, type: int, categories: Array, icon: Dictionary = {}) -> void: + if index >= terrain_list.get_child_count(): + return + var entry = terrain_list.get_child(index) + # don't overwrite empty icon + var valid_icon = icon + if icon.has("path") and icon.path.is_empty(): + var terrain = BetterTerrain.get_terrain(tileset, index) + valid_icon = terrain.icon + if BetterTerrain.set_terrain(tileset, index, name, color, type, categories, valid_icon): + entry.terrain = BetterTerrain.get_terrain(tileset, index) + entry.update() + tile_view.queue_redraw() + + +func _on_shuffle_random_pressed(): + BetterTerrain.use_seed = !shuffle_random.button_pressed + + +func _on_bit_button_pressed(button: BaseButton) -> void: + match select_tiles.button_group.get_pressed_button(): + select_tiles: tile_view.paint_mode = tile_view.PaintMode.SELECT + paint_type: tile_view.paint_mode = tile_view.PaintMode.PAINT_TYPE + paint_terrain: tile_view.paint_mode = tile_view.PaintMode.PAINT_PEERING + paint_symmetry: tile_view.paint_mode = tile_view.PaintMode.PAINT_SYMMETRY + _: tile_view.paint_mode = tile_view.PaintMode.NO_PAINT + tile_view.queue_redraw() + + symmetry_options.visible = paint_symmetry.button_pressed + + +func _on_symmetry_selected(index): + tile_view.paint_symmetry = index + + +func _on_paste_occurred(): + select_tiles.button_pressed = true + + +func _on_change_zoom_level(value): + zoom_slider.value = value + + +func _on_terrain_updated(index): + var entry = terrain_list.get_child(index) + entry.terrain = BetterTerrain.get_terrain(tileset, index) + entry.update() + + +func canvas_tilemap_transform() -> Transform2D: + var transform := tilemap.get_viewport_transform() * tilemap.global_transform + + # Handle subviewport + var editor_viewport := EditorInterface.get_editor_viewport_2d() + if tilemap.get_viewport() != editor_viewport: + var container = tilemap.get_viewport().get_parent() as SubViewportContainer + if container: + transform = editor_viewport.global_canvas_transform * container.get_transform() * transform + + return transform + + +func canvas_draw(overlay: Control) -> void: + if !draw_overlay: + return + + if selected_entry < 0: + return + + var type = selected_entry + var terrain := BetterTerrain.get_terrain(tileset, type) + if !terrain.valid: + return + + var tiles := [] + var transform := canvas_tilemap_transform() + + if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT: + var area := Rect2i(initial_click, current_position - initial_click).abs() + + # Shortcut fill for large areas + if area.size.x > 1 and area.size.y > 1 and area.size.x * area.size.y > MAX_CANVAS_RENDER_TILES: + var shortcut := PackedVector2Array([ + tilemap.map_to_local(area.position), + tilemap.map_to_local(Vector2i(area.end.x, area.position.y)), + tilemap.map_to_local(area.end), + tilemap.map_to_local(Vector2i(area.position.x, area.end.y)) + ]) + overlay.draw_colored_polygon(transform * shortcut, Color(terrain.color, 0.5)) + return + + for y in range(area.position.y, area.end.y + 1): + for x in range(area.position.x, area.end.x + 1): + tiles.append(Vector2i(x, y)) + elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT: + var cells := _get_tileset_line(initial_click, current_position, tileset) + var shape = BetterTerrain.data.cell_polygon(tileset) + for c in cells: + var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(c)) + overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5)) + elif fill_button.button_pressed: + tiles = _get_fill_cells(current_position) + if tiles.size() > MAX_CANVAS_RENDER_TILES: + tiles.resize(MAX_CANVAS_RENDER_TILES) + else: + tiles.append(current_position) + + var shape = BetterTerrain.data.cell_polygon(tileset) + for t in tiles: + var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(t)) + overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5)) + + +func canvas_input(event: InputEvent) -> bool: + if selected_entry < 0: + return false + + draw_overlay = true + if event is InputEventMouseMotion: + var tr := canvas_tilemap_transform() + var pos := tr.affine_inverse() * Vector2(event.position) + var event_position := tilemap.local_to_map(pos) + prev_position = current_position + if event_position == current_position: + return false + current_position = event_position + update_overlay.emit() + + var replace_mode = replace_button.button_pressed + + var released : bool = event is InputEventMouseButton and !event.pressed + if released: + terrain_undo.finish_action() + var type = selected_entry + if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT: + var area := Rect2i(initial_click, current_position - initial_click).abs() + # Fill from initial_target to target + undo_manager.create_action(tr("Draw terrain rectangle"), UndoRedo.MERGE_DISABLE, tilemap) + for y in range(area.position.y, area.end.y + 1): + for x in range(area.position.x, area.end.x + 1): + var coord := Vector2i(x, y) + if paint_mode == PaintMode.PAINT: + if replace_mode: + undo_manager.add_do_method(BetterTerrain, &"replace_cell", tilemap, coord, type) + else: + undo_manager.add_do_method(BetterTerrain, &"set_cell", tilemap, coord, type) + else: + undo_manager.add_do_method(tilemap, &"erase_cell", coord) + + undo_manager.add_do_method(BetterTerrain, &"update_terrain_area", tilemap, area) + terrain_undo.create_tile_restore_point_area(undo_manager, tilemap, area) + undo_manager.commit_action() + update_overlay.emit() + elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT: + undo_manager.create_action(tr("Draw terrain line"), UndoRedo.MERGE_DISABLE, tilemap) + var cells := _get_tileset_line(initial_click, current_position, tileset) + if paint_mode == PaintMode.PAINT: + if replace_mode: + undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type) + else: + undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type) + elif paint_mode == PaintMode.ERASE: + for c in cells: + undo_manager.add_do_method(tilemap, &"erase_cell", c) + undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells) + terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells) + undo_manager.commit_action() + update_overlay.emit() + + paint_mode = PaintMode.NO_PAINT + return true + + var clicked : bool = event is InputEventMouseButton and event.pressed + if clicked: + paint_mode = PaintMode.NO_PAINT + + if (event.is_command_or_control_pressed() and !event.shift_pressed): + var pick = BetterTerrain.get_cell(tilemap, current_position) + if pick >= 0: + terrain_list.get_children()[pick]._on_focus_entered() + #_on_entry_select(pick) + return true + + paint_action = PaintAction.NO_ACTION + if rectangle_button.button_pressed: + paint_action = PaintAction.RECT + elif line_button.button_pressed: + paint_action = PaintAction.LINE + elif draw_button.button_pressed: + if event.shift_pressed: + paint_action = PaintAction.LINE + if event.is_command_or_control_pressed(): + paint_action = PaintAction.RECT + + if event.button_index == MOUSE_BUTTON_LEFT: + paint_mode = PaintMode.PAINT + elif event.button_index == MOUSE_BUTTON_RIGHT: + paint_mode = PaintMode.ERASE + else: + return false + + if (clicked or event is InputEventMouseMotion) and paint_mode != PaintMode.NO_PAINT: + if clicked: + initial_click = current_position + terrain_undo.action_index += 1 + terrain_undo.action_count = 0 + var type = selected_entry + + if paint_action == PaintAction.LINE or paint_action == PaintAction.RECT: + # if painting as line, execution happens on release. + # prevent other painting actions from running. + pass + elif draw_button.button_pressed: + undo_manager.create_action(tr("Draw terrain") + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tilemap, true) + var cells := _get_tileset_line(prev_position, current_position, tileset) + if paint_mode == PaintMode.PAINT: + if replace_mode: + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"replace_cells", [tilemap, cells, type]) + else: + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_cells", [tilemap, cells, type]) + elif paint_mode == PaintMode.ERASE: + for c in cells: + terrain_undo.add_do_method(undo_manager, tilemap, &"erase_cell", [c]) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"update_terrain_cells", [tilemap, cells]) + terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells) + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif fill_button.button_pressed: + var cells := _get_fill_cells(current_position) + undo_manager.create_action(tr("Fill terrain"), UndoRedo.MERGE_DISABLE, tilemap) + if paint_mode == PaintMode.PAINT: + if replace_mode: + undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type) + else: + undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type) + elif paint_mode == PaintMode.ERASE: + for c in cells: + undo_manager.add_do_method(tilemap, &"erase_cell", c) + undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells) + terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells) + undo_manager.commit_action() + + update_overlay.emit() + return true + + return false + + +func canvas_mouse_exit() -> void: + draw_overlay = false + update_overlay.emit() + + +func _shortcut_input(event) -> void: + if event is InputEventKey: + if event.keycode == KEY_C and (event.is_command_or_control_pressed() and not event.echo): + get_viewport().set_input_as_handled() + tile_view.copy_selection() + if event.keycode == KEY_V and (event.is_command_or_control_pressed() and not event.echo): + get_viewport().set_input_as_handled() + tile_view.paste_selection() + + +## bresenham alg ported from Geometry2D::bresenham_line() +func _get_line(from:Vector2i, to:Vector2i) -> Array[Vector2i]: + if from == to: + return [to] + + var points:Array[Vector2i] = [] + var delta := (to - from).abs() * 2 + var step := (to - from).sign() + var current := from + + if delta.x > delta.y: + var err:int = delta.x / 2 + while current.x != to.x: + points.push_back(current); + err -= delta.y + if err < 0: + current.y += step.y + err += delta.x + current.x += step.x + else: + var err:int = delta.y / 2 + while current.y != to.y: + points.push_back(current) + err -= delta.x + if err < 0: + current.x += step.x + err += delta.y + current.y += step.y + + points.push_back(current); + return points; + + +## half-offset bresenham alg ported from TileMapEditor::get_line +func _get_tileset_line(from:Vector2i, to:Vector2i, tileset:TileSet) -> Array[Vector2i]: + if tileset.tile_shape == TileSet.TILE_SHAPE_SQUARE: + return _get_line(from, to) + + var points:Array[Vector2i] = [] + + var transposed := tileset.get_tile_offset_axis() == TileSet.TILE_OFFSET_AXIS_VERTICAL + if transposed: + from = Vector2i(from.y, from.x) + to = Vector2i(to.y, to.x) + + var delta:Vector2i = to - from + delta = Vector2i(2 * delta.x + abs(posmod(to.y, 2)) - abs(posmod(from.y, 2)), delta.y) + var sign:Vector2i = delta.sign() + + var current := from; + points.push_back(Vector2i(current.y, current.x) if transposed else current) + + var err := 0 + if abs(delta.y) < abs(delta.x): + var err_step:Vector2i = 3 * delta.abs() + while current != to: + err += err_step.y + if err > abs(delta.x): + if sign.x == 0: + current += Vector2i(sign.y, 0) + else: + current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y) + err -= err_step.x + else: + current += Vector2i(sign.x, 0) + err += err_step.y + points.push_back(Vector2i(current.y, current.x) if transposed else current) + else: + var err_step:Vector2i = delta.abs() + while current != to: + err += err_step.x + if err > 0: + if sign.x == 0: + current += Vector2i(0, sign.y) + else: + current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y) + err -= err_step.y; + else: + if sign.x == 0: + current += Vector2i(0, sign.y) + else: + current += Vector2i(-sign.x if bool(current.y % 2) != (sign.x > 0) else 0, sign.y) + err += err_step.y + points.push_back(Vector2i(current.y, current.x) if transposed else current) + + return points + + +func _on_terrain_enable_id_pressed(id): + if id in [SourceSelectors.ALL, SourceSelectors.NONE]: + for i in source_selector_popup.item_count: + if source_selector_popup.is_item_checkable(i): + source_selector_popup.set_item_checked(i, id == SourceSelectors.ALL) + else: + var index = source_selector_popup.get_item_index(id) + var checked = source_selector_popup.is_item_checked(index) + source_selector_popup.set_item_checked(index, !checked) + + var disabled_sources : Array[int] + for i in source_selector_popup.item_count: + if source_selector_popup.is_item_checkable(i) and !source_selector_popup.is_item_checked(i): + disabled_sources.append(source_selector_popup.get_item_id(i)) + tile_view.disabled_sources = disabled_sources + + +func corresponding_tilemap_editor_button(similar: Button) -> Button: + var editors = EditorInterface.get_base_control().find_children("*", "TileMapLayerEditor", true, false) + var tile_map_layer_editor = editors[0] + var buttons = tile_map_layer_editor.find_children("*", "Button", true, false) + for button: Button in buttons: + if button.icon == similar.icon: + return button + return null + + +func _on_layer_up_or_down_pressed(button: Button) -> void: + var matching_button = corresponding_tilemap_editor_button(button) + if !matching_button: + return + + # Major hack, to reduce flicker hide the tileset editor briefly + var editors = EditorInterface.get_base_control().find_children("*", "TileSetEditor", true, false) + var tile_set_editor = editors[0] + + matching_button.pressed.emit() + tile_set_editor.modulate = Color.TRANSPARENT + await get_tree().process_frame + await get_tree().process_frame + force_show_terrains.emit() + tile_set_editor.modulate = Color.WHITE + + + +func _on_layer_up_pressed() -> void: + _on_layer_up_or_down_pressed(layer_up) + + +func _on_layer_down_pressed() -> void: + _on_layer_up_or_down_pressed(layer_down) + + +func _on_layer_highlight_toggled(toggled: bool) -> void: + var settings = EditorInterface.get_editor_settings() + settings.set_setting("editors/tiles_editor/highlight_selected_layer", toggled) + + var highlight = corresponding_tilemap_editor_button(layer_highlight) + if highlight: + highlight.toggled.emit(toggled) + + +func _on_layer_grid_toggled(toggled: bool) -> void: + var settings = EditorInterface.get_editor_settings() + settings.set_setting("editors/tiles_editor/display_grid", toggled) + + var grid = corresponding_tilemap_editor_button(layer_grid) + if grid: + grid.toggled.emit(toggled) diff --git a/addons/better-terrain/editor/Dock.gd.uid b/addons/better-terrain/editor/Dock.gd.uid new file mode 100644 index 0000000..3f26a80 --- /dev/null +++ b/addons/better-terrain/editor/Dock.gd.uid @@ -0,0 +1 @@ +uid://dl11uwp4um4y3 diff --git a/addons/better-terrain/editor/Dock.tscn b/addons/better-terrain/editor/Dock.tscn new file mode 100644 index 0000000..f659343 --- /dev/null +++ b/addons/better-terrain/editor/Dock.tscn @@ -0,0 +1,405 @@ +[gd_scene load_steps=33 format=3 uid="uid://de8b6h6ieal7r"] + +[ext_resource type="Script" path="res://addons/better-terrain/editor/Dock.gd" id="1_raoha"] +[ext_resource type="Texture2D" uid="uid://c6lxq2y7mpb18" path="res://addons/better-terrain/icons/EditType.svg" id="2_cpm2t"] +[ext_resource type="Texture2D" uid="uid://y3xy6qdckht6" path="res://addons/better-terrain/icons/Replace.svg" id="2_fvmt6"] +[ext_resource type="Texture2D" uid="uid://bo2cjv08jkvf8" path="res://addons/better-terrain/icons/EditTerrain.svg" id="3_pqb1p"] +[ext_resource type="Texture2D" uid="uid://b0es228gfcykd" path="res://addons/better-terrain/icons/Warning.svg" id="4_6ahwe"] +[ext_resource type="Script" path="res://addons/better-terrain/editor/TileView.gd" id="4_nqppq"] +[ext_resource type="Texture2D" uid="uid://co6gwwmog0pjy" path="res://addons/better-terrain/icons/EditSymmetry.svg" id="5_kfjwu"] +[ext_resource type="Texture2D" uid="uid://cs4mdmluiydj6" path="res://addons/better-terrain/icons/ShuffleRandom.svg" id="5_n3owo"] +[ext_resource type="Texture2D" uid="uid://5hm3bfj3dvej" path="res://addons/better-terrain/icons/SymmetryMirror.svg" id="6_mofuh"] +[ext_resource type="Texture2D" uid="uid://dqmc1jp56or8m" path="res://addons/better-terrain/icons/SymmetryFlip.svg" id="7_ojxs0"] +[ext_resource type="Texture2D" uid="uid://cxoewno1cefua" path="res://addons/better-terrain/icons/SymmetryReflect.svg" id="8_8dhyg"] +[ext_resource type="Texture2D" uid="uid://baxhjy28r1iqj" path="res://addons/better-terrain/icons/SymmetryRotateClockwise.svg" id="9_tq76a"] +[ext_resource type="Texture2D" uid="uid://csbwdkr6bc2db" path="res://addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg" id="10_o5h1f"] +[ext_resource type="Texture2D" uid="uid://8mcycyl3e66r" path="res://addons/better-terrain/icons/SymmetryRotate180.svg" id="11_m6syp"] +[ext_resource type="Texture2D" uid="uid://b7fx4mk18lmls" path="res://addons/better-terrain/icons/SymmetryRotateAll.svg" id="12_11vru"] +[ext_resource type="Texture2D" uid="uid://cyjra4g05dwh" path="res://addons/better-terrain/icons/SymmetryAll.svg" id="13_lp5m2"] + +[sub_resource type="ButtonGroup" id="ButtonGroup_aon7c"] + +[sub_resource type="InputEventKey" id="InputEventKey_saph6"] +device = -1 +keycode = 68 +unicode = 100 + +[sub_resource type="Shortcut" id="Shortcut_3k2al"] +events = [SubResource("InputEventKey_saph6")] + +[sub_resource type="Image" id="Image_3r1gs"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_v6msm"] +image = SubResource("Image_3r1gs") + +[sub_resource type="InputEventKey" id="InputEventKey_q1v0d"] +device = -1 +keycode = 76 +unicode = 108 + +[sub_resource type="Shortcut" id="Shortcut_wc6bu"] +events = [SubResource("InputEventKey_q1v0d")] + +[sub_resource type="InputEventKey" id="InputEventKey_68n3h"] +device = -1 +keycode = 82 +unicode = 114 + +[sub_resource type="InputEventKey" id="InputEventKey_qcu1e"] +device = -1 +keycode = 67 +unicode = 99 + +[sub_resource type="Shortcut" id="Shortcut_tcjet"] +events = [SubResource("InputEventKey_68n3h"), SubResource("InputEventKey_qcu1e")] + +[sub_resource type="InputEventKey" id="InputEventKey_grxy4"] +device = -1 +keycode = 66 +unicode = 98 + +[sub_resource type="Shortcut" id="Shortcut_46fac"] +events = [SubResource("InputEventKey_grxy4")] + +[sub_resource type="InputEventKey" id="InputEventKey_xd61m"] +device = -1 +keycode = 80 +unicode = 112 + +[sub_resource type="Shortcut" id="Shortcut_uwwa1"] +events = [SubResource("InputEventKey_xd61m")] + +[sub_resource type="ButtonGroup" id="ButtonGroup_3wrxn"] +allow_unpress = true + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mpeb7"] +bg_color = Color(0, 0, 0, 0.4) + +[node name="Dock" type="Control" node_paths=PackedStringArray("shortcut_context")] +custom_minimum_size = Vector2(0, 100) +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +focus_mode = 2 +shortcut_context = NodePath(".") +script = ExtResource("1_raoha") + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Toolbar" type="HBoxContainer" parent="VBox"] +layout_mode = 2 + +[node name="Draw" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Draw terrain +Shift: Draw line. +Ctrl/Cmd+Shift: Draw rectangle." +toggle_mode = true +button_pressed = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_3k2al") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Line" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Draw line" +toggle_mode = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_wc6bu") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Rectangle" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Fill a rectangle of terrain" +toggle_mode = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_tcjet") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Fill" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Bucket fill terrain" +toggle_mode = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_46fac") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Replace" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Toggle replace mode" +toggle_mode = true +shortcut = SubResource("Shortcut_uwwa1") +icon = ExtResource("2_fvmt6") + +[node name="VSeparator" type="VSeparator" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="SelectTiles" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Select" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="PaintType" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Paint terrain types" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = ExtResource("2_cpm2t") +flat = true + +[node name="PaintTerrain" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Paint terrain connecting types" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = ExtResource("3_pqb1p") +flat = true + +[node name="PaintSymmetry" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Paint tile symmetry" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = ExtResource("5_kfjwu") +flat = true + +[node name="SymmetryOptions" type="OptionButton" parent="VBox/Toolbar"] +visible = false +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +selected = 0 +item_count = 9 +popup/item_0/text = "No symmetry" +popup/item_0/id = 8 +popup/item_1/text = "Mirror" +popup/item_1/icon = ExtResource("6_mofuh") +popup/item_1/id = 1 +popup/item_2/text = "Flip" +popup/item_2/icon = ExtResource("7_ojxs0") +popup/item_2/id = 1 +popup/item_3/text = "Reflect" +popup/item_3/icon = ExtResource("8_8dhyg") +popup/item_3/id = 2 +popup/item_4/text = "Rotate clockwise" +popup/item_4/icon = ExtResource("9_tq76a") +popup/item_4/id = 3 +popup/item_5/text = "Rotate counter-clockwise" +popup/item_5/icon = ExtResource("10_o5h1f") +popup/item_5/id = 4 +popup/item_6/text = "Rotate 180" +popup/item_6/icon = ExtResource("11_m6syp") +popup/item_6/id = 5 +popup/item_7/text = "All rotations" +popup/item_7/icon = ExtResource("12_11vru") +popup/item_7/id = 6 +popup/item_8/text = "All reflections & rotations" +popup/item_8/icon = ExtResource("13_lp5m2") +popup/item_8/id = 7 + +[node name="VSeparator3" type="VSeparator" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="ZoomContainer" type="VBoxContainer" parent="VBox/Toolbar"] +layout_mode = 2 +alignment = 1 + +[node name="Sources" type="MenuBar" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="Sources" type="PopupMenu" parent="VBox/Toolbar/Sources"] +hide_on_item_selection = false +hide_on_checkable_item_selection = false + +[node name="Spacer" type="Control" parent="VBox/Toolbar"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ShuffleRandom" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Shuffle random tiles each update" +toggle_mode = true +icon = ExtResource("5_n3owo") +flat = true + +[node name="Clean" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +text = "Clean tile data" +icon = ExtResource("4_6ahwe") + +[node name="VSeparator2" type="VSeparator" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="LayerUp" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Select previous layer" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="LayerDown" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Select next layer" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="LayerHighlight" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Highlight selected layer" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="LayerGrid" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Toggle grid visibility" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="HSplit" type="HSplitContainer" parent="VBox"] +layout_mode = 2 +size_flags_vertical = 3 +split_offset = 325 + +[node name="Terrains" type="VBoxContainer" parent="VBox/HSplit"] +layout_mode = 2 + +[node name="Panel" type="PanelContainer" parent="VBox/HSplit/Terrains"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_mpeb7") + +[node name="ScrollContainer" type="ScrollContainer" parent="VBox/HSplit/Terrains/Panel"] +layout_mode = 2 +horizontal_scroll_mode = 3 + +[node name="TerrainList" type="HFlowContainer" parent="VBox/HSplit/Terrains/Panel/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="LowerToolbar" type="HBoxContainer" parent="VBox/HSplit/Terrains"] +layout_mode = 2 + +[node name="GridMode" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar"] +layout_mode = 2 +tooltip_text = "Toggle grid view" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="QuickMode" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar"] +auto_translate_mode = 1 +layout_mode = 2 +tooltip_text = "Toggle quick mode. Only shows paintable terrain types." +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="VSeparator" type="VSeparator" parent="VBox/HSplit/Terrains/LowerToolbar"] +layout_mode = 2 + +[node name="EditTools" type="HBoxContainer" parent="VBox/HSplit/Terrains/LowerToolbar"] +layout_mode = 2 +size_flags_horizontal = 3 +alignment = 2 + +[node name="AddTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Add terrain type" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="EditTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Edit terrain type" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="PickIcon" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Pick terrain icon from tileset" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="MoveUp" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Move selected terrain up" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="MoveDown" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Move selected terrain down" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="RemoveTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Remove selected terrain type(s)" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Panel" type="Panel" parent="VBox/HSplit"] +custom_minimum_size = Vector2(0, 80) +layout_mode = 2 + +[node name="ScrollArea" type="ScrollContainer" parent="VBox/HSplit/Panel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 + +[node name="TileView" type="Control" parent="VBox/HSplit/Panel/ScrollArea"] +texture_filter = 1 +texture_repeat = 1 +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +focus_mode = 2 +script = ExtResource("4_nqppq") + +[connection signal="item_selected" from="VBox/Toolbar/SymmetryOptions" to="." method="_on_symmetry_selected"] +[connection signal="id_pressed" from="VBox/Toolbar/Sources/Sources" to="." method="_on_terrain_enable_id_pressed"] +[connection signal="pressed" from="VBox/Toolbar/ShuffleRandom" to="." method="_on_shuffle_random_pressed"] +[connection signal="pressed" from="VBox/Toolbar/Clean" to="." method="_on_clean_pressed"] +[connection signal="pressed" from="VBox/Toolbar/LayerUp" to="." method="_on_layer_up_pressed"] +[connection signal="pressed" from="VBox/Toolbar/LayerDown" to="." method="_on_layer_down_pressed"] +[connection signal="toggled" from="VBox/Toolbar/LayerHighlight" to="." method="_on_layer_highlight_toggled"] +[connection signal="toggled" from="VBox/Toolbar/LayerGrid" to="." method="_on_layer_grid_toggled"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/GridMode" to="." method="_on_grid_mode_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/QuickMode" to="." method="_on_quick_mode_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain" to="." method="_on_add_terrain_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain" to="." method="_on_edit_terrain_pressed"] +[connection signal="focus_exited" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon" to="." method="_on_pick_icon_focus_exited"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon" to="." method="_on_pick_icon_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp" to="." method="_on_move_pressed" binds= [false]] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown" to="." method="_on_move_pressed" binds= [true]] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain" to="." method="_on_remove_terrain_pressed"] +[connection signal="mouse_exited" from="VBox/HSplit/Panel/ScrollArea/TileView" to="VBox/HSplit/Panel/ScrollArea/TileView" method="clear_highlighted_tile"] diff --git a/addons/better-terrain/editor/TerrainEntry.gd b/addons/better-terrain/editor/TerrainEntry.gd new file mode 100644 index 0000000..a937f95 --- /dev/null +++ b/addons/better-terrain/editor/TerrainEntry.gd @@ -0,0 +1,185 @@ +@tool +extends PanelContainer + +signal select(index) + +@onready var color_panel := %Color +@onready var terrain_icon_slot := %TerrainIcon +@onready var type_icon_slot := %TypeIcon +@onready var type_icon_panel := %TerrainIconPanel +@onready var name_label := %Name +@onready var layout_container := %Layout +@onready var icon_layout_container := %IconLayout + +var selected := false + +var tileset:TileSet +var terrain:Dictionary + +var grid_mode := false +var color_style_list:StyleBoxFlat +var color_style_grid:StyleBoxFlat +var color_style_decoration:StyleBoxFlat + +var _terrain_texture:Texture2D +var _terrain_texture_rect:Rect2i +var _icon_draw_connected := false + + +func _ready(): + update() + +func update(): + if !terrain or !terrain.valid: + return + if !tileset: + return + + name_label.text = terrain.name + tooltip_text = "%s (%d)" % [terrain.name, terrain.id] + + color_style_list = color_panel.get_theme_stylebox("panel").duplicate() + color_style_grid = color_panel.get_theme_stylebox("panel").duplicate() + color_style_decoration = color_panel.get_theme_stylebox("panel").duplicate() + + color_style_list.bg_color = terrain.color + color_style_list.corner_radius_top_left = 8 + color_style_list.corner_radius_bottom_left = 8 + color_style_list.corner_radius_top_right = 0 + color_style_list.corner_radius_bottom_right = 0 + color_style_list.content_margin_left = -1 + color_style_list.content_margin_right = -1 + color_style_list.border_width_left = 0 + color_style_list.border_width_right = 0 + color_style_list.border_width_top = 0 + color_style_list.border_width_bottom = 0 + + color_style_grid.bg_color = terrain.color + color_style_grid.corner_radius_top_left = 6 + color_style_grid.corner_radius_bottom_left = 6 + color_style_grid.corner_radius_top_right = 6 + color_style_grid.corner_radius_bottom_right = 6 + color_style_grid.content_margin_left = -1 + color_style_grid.content_margin_right = -1 + color_style_grid.border_width_left = 0 + color_style_grid.border_width_right = 0 + color_style_grid.border_width_top = 0 + color_style_grid.border_width_bottom = 0 + + color_style_decoration.bg_color = terrain.color + color_style_decoration.corner_radius_top_left = 8 + color_style_decoration.corner_radius_bottom_left = 8 + color_style_decoration.corner_radius_top_right = 8 + color_style_decoration.corner_radius_bottom_right = 8 + color_style_decoration.content_margin_left = -1 + color_style_decoration.content_margin_right = -1 + color_style_decoration.border_width_left = 4 + color_style_decoration.border_width_right = 4 + color_style_decoration.border_width_top = 4 + color_style_decoration.border_width_bottom = 4 + + match terrain.type: + BetterTerrain.TerrainType.MATCH_TILES: + type_icon_slot.texture = load("res://addons/better-terrain/icons/MatchTiles.svg") + BetterTerrain.TerrainType.MATCH_VERTICES: + type_icon_slot.texture = load("res://addons/better-terrain/icons/MatchVertices.svg") + BetterTerrain.TerrainType.CATEGORY: + type_icon_slot.texture = load("res://addons/better-terrain/icons/NonModifying.svg") + BetterTerrain.TerrainType.DECORATION: + type_icon_slot.texture = load("res://addons/better-terrain/icons/Decoration.svg") + + var has_icon = false + if terrain.has("icon"): + if terrain.icon.has("path") and not terrain.icon.path.is_empty(): + terrain_icon_slot.texture = load(terrain.icon.path) + _terrain_texture = null + terrain_icon_slot.queue_redraw() + has_icon = true + elif terrain.icon.has("source_id") and tileset.has_source(terrain.icon.source_id): + var source := tileset.get_source(terrain.icon.source_id) as TileSetAtlasSource + var coord := terrain.icon.coord as Vector2i + var rect := source.get_tile_texture_region(coord, 0) + _terrain_texture = source.texture + _terrain_texture_rect = rect + terrain_icon_slot.queue_redraw() + has_icon = true + + if not has_icon: + var tiles = BetterTerrain.get_tile_sources_in_terrain(tileset, get_index()) + if tiles.size() > 0: + var source := tiles[0].source as TileSetAtlasSource + var coord := tiles[0].coord as Vector2i + var rect := source.get_tile_texture_region(coord, 0) + _terrain_texture = source.texture + _terrain_texture_rect = rect + terrain_icon_slot.queue_redraw() + + if _terrain_texture: + terrain_icon_slot.texture = null + + if not _icon_draw_connected: + terrain_icon_slot.connect("draw", func(): + if _terrain_texture: + terrain_icon_slot.draw_texture_rect_region(_terrain_texture, Rect2i(0,0, 44, 44), _terrain_texture_rect) + ) + _icon_draw_connected = true + + update_style() + + +func update_style(): + if terrain.type == BetterTerrain.TerrainType.DECORATION: + type_icon_panel.visible = false + color_panel.custom_minimum_size = Vector2i(52,52) + else: + type_icon_panel.visible = true + color_panel.custom_minimum_size = Vector2i(24,24) + + if grid_mode: + if terrain.type == BetterTerrain.TerrainType.DECORATION: + color_panel.add_theme_stylebox_override("panel", color_style_decoration) + color_panel.size_flags_vertical = Control.SIZE_FILL + icon_layout_container.size_flags_vertical = Control.SIZE_EXPAND_FILL + else: + color_panel.add_theme_stylebox_override("panel", color_style_grid) + color_panel.size_flags_vertical = Control.SIZE_SHRINK_BEGIN + icon_layout_container.size_flags_vertical = Control.SIZE_FILL + custom_minimum_size = Vector2(0, 60) + size_flags_horizontal = Control.SIZE_FILL + layout_container.vertical = true + name_label.visible = false + icon_layout_container.add_theme_constant_override("separation", -24) + else: + if terrain.type == BetterTerrain.TerrainType.DECORATION: + color_panel.add_theme_stylebox_override("panel", color_style_decoration) + else: + color_panel.add_theme_stylebox_override("panel", color_style_list) + icon_layout_container.size_flags_vertical = Control.SIZE_FILL + custom_minimum_size = Vector2(2000, 60) + size_flags_horizontal = Control.SIZE_EXPAND_FILL + layout_container.vertical = false + name_label.visible = true + color_panel.size_flags_vertical = Control.SIZE_FILL + icon_layout_container.add_theme_constant_override("separation", 4) + + +func set_selected(value:bool = true): + selected = value + if value: + select.emit(get_index()) + queue_redraw() + + +func _draw(): + if selected: + draw_rect(Rect2(Vector2.ZERO, get_rect().size), Color(0.15, 0.70, 1, 0.3)) + + +func _on_focus_entered(): + queue_redraw() + selected = true + select.emit(get_index()) + + +func _on_focus_exited(): + queue_redraw() diff --git a/addons/better-terrain/editor/TerrainEntry.gd.uid b/addons/better-terrain/editor/TerrainEntry.gd.uid new file mode 100644 index 0000000..cf08a9e --- /dev/null +++ b/addons/better-terrain/editor/TerrainEntry.gd.uid @@ -0,0 +1 @@ +uid://eg0oj6up0g0f diff --git a/addons/better-terrain/editor/TerrainEntry.tscn b/addons/better-terrain/editor/TerrainEntry.tscn new file mode 100644 index 0000000..3973bbf --- /dev/null +++ b/addons/better-terrain/editor/TerrainEntry.tscn @@ -0,0 +1,114 @@ +[gd_scene load_steps=8 format=3 uid="uid://u2y444hj182c"] + +[ext_resource type="Script" path="res://addons/better-terrain/editor/TerrainEntry.gd" id="1_o2na3"] +[ext_resource type="Texture2D" uid="uid://kmypxsqhynyv" path="res://addons/better-terrain/icons/Decoration.svg" id="2_ossyj"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3pdcc"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +draw_center = false + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dqhir"] +bg_color = Color(0.243, 0.816, 0.518, 1) +border_color = Color(0, 0, 0, 0.439216) +corner_radius_top_left = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rohyw"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 +bg_color = Color(0, 0, 0, 0.439216) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xa0fl"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0, 0, 0, 0.439216) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b4rkm"] +content_margin_left = 3.0 +bg_color = Color(0, 0, 0, 0.439216) +draw_center = false + +[node name="TerrainEntry" type="PanelContainer"] +custom_minimum_size = Vector2(60, 60) +offset_right = 200.0 +offset_bottom = 60.0 +size_flags_vertical = 3 +focus_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_3pdcc") +script = ExtResource("1_o2na3") + +[node name="Layout" type="BoxContainer" parent="."] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="IconLayout" type="HBoxContainer" parent="Layout"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="Color" type="PanelContainer" parent="Layout/IconLayout"] +unique_name_in_owner = true +z_index = 1 +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +mouse_filter = 1 +theme_override_styles/panel = SubResource("StyleBoxFlat_dqhir") + +[node name="PanelContainer" type="PanelContainer" parent="Layout/IconLayout/Color"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +mouse_filter = 1 +theme_override_styles/panel = SubResource("StyleBoxFlat_rohyw") + +[node name="TypeIcon" type="TextureRect" parent="Layout/IconLayout/Color/PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +texture = ExtResource("2_ossyj") + +[node name="TerrainIconPanel" type="PanelContainer" parent="Layout/IconLayout"] +unique_name_in_owner = true +custom_minimum_size = Vector2(52, 52) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +mouse_filter = 1 +theme_override_styles/panel = SubResource("StyleBoxFlat_xa0fl") + +[node name="TerrainIcon" type="TextureRect" parent="Layout/IconLayout/TerrainIconPanel"] +unique_name_in_owner = true +texture_filter = 1 +custom_minimum_size = Vector2(40, 40) +layout_mode = 2 +expand_mode = 4 +stretch_mode = 5 + +[node name="Name" type="Label" parent="Layout"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_b4rkm") +text = "New Terrain" +vertical_alignment = 1 +text_overrun_behavior = 3 + +[connection signal="focus_entered" from="." to="." method="_on_focus_entered"] +[connection signal="focus_exited" from="." to="." method="_on_focus_exited"] diff --git a/addons/better-terrain/editor/TerrainProperties.gd b/addons/better-terrain/editor/TerrainProperties.gd new file mode 100644 index 0000000..09b3170 --- /dev/null +++ b/addons/better-terrain/editor/TerrainProperties.gd @@ -0,0 +1,85 @@ +@tool +extends ConfirmationDialog + +var category_icon := load("res://addons/better-terrain/icons/NonModifying.svg") + +const CATEGORY_CHECK_ID = &"category_check_id" + +var accepted := false + +var terrain_name : String: + set(value): %NameEdit.text = value + get: return %NameEdit.text + +var terrain_color : Color: + set(value): %ColorPicker.color = value + get: return %ColorPicker.color + +var terrain_icon : String: + set(value): %IconEdit.text = value + get: return %IconEdit.text + +var terrain_type : int: + set(value): + %TypeOption.selected = value + _on_type_option_item_selected(value) + get: return %TypeOption.selected + +var terrain_categories : Array: set = set_categories, get = get_categories + + +# category is name, color, id +func set_category_data(options: Array) -> void: + if !options.is_empty(): + %CategoryLabel.show() + %CategoryContainer.show() + + for o in options: + var c = CheckBox.new() + c.text = o.name + c.icon = category_icon + c.add_theme_color_override(&"icon_normal_color", o.color) + c.add_theme_color_override(&"icon_disabled_color", Color(o.color, 0.4)) + c.add_theme_color_override(&"icon_focus_color", o.color) + c.add_theme_color_override(&"icon_hover_color", o.color) + c.add_theme_color_override(&"icon_hover_pressed_color", o.color) + c.add_theme_color_override(&"icon_normal_color", o.color) + c.add_theme_color_override(&"icon_pressed_color", o.color) + + c.set_meta(CATEGORY_CHECK_ID, o.id) + %CategoryLayout.add_child(c) + + +func set_categories(ids : Array): + for c in %CategoryLayout.get_children(): + c.button_pressed = c.get_meta(CATEGORY_CHECK_ID) in ids + + +func get_categories() -> Array: + var result := [] + if terrain_type == BetterTerrain.TerrainType.CATEGORY: + return result + for c in %CategoryLayout.get_children(): + if c.button_pressed: + result.push_back(c.get_meta(CATEGORY_CHECK_ID)) + return result + + +func _on_confirmed() -> void: + # confirm valid name + if terrain_name.is_empty(): + var dialog := AcceptDialog.new() + dialog.dialog_text = "Name cannot be empty" + EditorInterface.popup_dialog_centered(dialog) + await dialog.visibility_changed + dialog.queue_free() + return + + accepted = true + hide() + + +func _on_type_option_item_selected(index: int) -> void: + var categories_available = (index != BetterTerrain.TerrainType.CATEGORY) + for c in %CategoryLayout.get_children(): + c.disabled = !categories_available diff --git a/addons/better-terrain/editor/TerrainProperties.gd.uid b/addons/better-terrain/editor/TerrainProperties.gd.uid new file mode 100644 index 0000000..204b3b3 --- /dev/null +++ b/addons/better-terrain/editor/TerrainProperties.gd.uid @@ -0,0 +1 @@ +uid://bmoqtxvniuscv diff --git a/addons/better-terrain/editor/TerrainProperties.tscn b/addons/better-terrain/editor/TerrainProperties.tscn new file mode 100644 index 0000000..43fdd2b --- /dev/null +++ b/addons/better-terrain/editor/TerrainProperties.tscn @@ -0,0 +1,93 @@ +[gd_scene load_steps=5 format=3 uid="uid://fdjybw6e7whr"] + +[ext_resource type="Script" path="res://addons/better-terrain/editor/TerrainProperties.gd" id="1_52nx8"] +[ext_resource type="Texture2D" uid="uid://d1h1p7pcwdnjk" path="res://addons/better-terrain/icons/MatchTiles.svg" id="2_ncc5p"] +[ext_resource type="Texture2D" uid="uid://dfemy1g6okwlv" path="res://addons/better-terrain/icons/MatchVertices.svg" id="3_0nvmi"] +[ext_resource type="Texture2D" uid="uid://1yr6yruwl63u" path="res://addons/better-terrain/icons/NonModifying.svg" id="5_awp83"] + +[node name="TerrainProperties" type="ConfirmationDialog"] +title = "Edit terrain properties" +initial_position = 2 +size = Vector2i(317, 257) +visible = true +dialog_hide_on_ok = false +script = ExtResource("1_52nx8") + +[node name="GridContainer" type="GridContainer" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 309.0 +offset_bottom = 208.0 +columns = 2 + +[node name="NameLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Name" + +[node name="NameEdit" type="LineEdit" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Terrain name" + +[node name="ColorLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Color" + +[node name="ColorPicker" type="ColorPickerButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +color = Color(1, 0.262745, 0.498039, 1) +edit_alpha = false + +[node name="IconLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Icon" + +[node name="IconEdit" type="LineEdit" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Icon path (optional)" + +[node name="TypeLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Mode" + +[node name="TypeOption" type="OptionButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +item_count = 3 +popup/item_0/text = "Match tiles" +popup/item_0/icon = ExtResource("2_ncc5p") +popup/item_1/text = "Match vertices" +popup/item_1/icon = ExtResource("3_0nvmi") +popup/item_1/id = 1 +popup/item_2/text = "Category" +popup/item_2/icon = ExtResource("5_awp83") +popup/item_2/id = 2 + +[node name="CategoryLabel" type="Label" parent="GridContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +size_flags_vertical = 1 +text = "Categories" + +[node name="CategoryContainer" type="ScrollContainer" parent="GridContainer"] +unique_name_in_owner = true +visible = false +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +size_flags_vertical = 3 + +[node name="CategoryLayout" type="VBoxContainer" parent="GridContainer/CategoryContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +size_flags_vertical = 3 + +[connection signal="confirmed" from="." to="." method="_on_confirmed"] +[connection signal="item_selected" from="GridContainer/TypeOption" to="." method="_on_type_option_item_selected"] diff --git a/addons/better-terrain/editor/TerrainUndo.gd b/addons/better-terrain/editor/TerrainUndo.gd new file mode 100644 index 0000000..201d7df --- /dev/null +++ b/addons/better-terrain/editor/TerrainUndo.gd @@ -0,0 +1,190 @@ +@tool +extends Node + +var action_index := 0 +var action_count := 0 +var _current_action_index := 0 +var _current_action_count := 0 + +func create_tile_restore_point(undo_manager: EditorUndoRedoManager, tm: TileMapLayer, cells: Array, and_surrounding_cells: bool = true) -> void: + if and_surrounding_cells: + cells = BetterTerrain._widen(tm, cells) + + var restore := [] + for c in cells: + restore.append([ + c, + tm.get_cell_source_id(c), + tm.get_cell_atlas_coords(c), + tm.get_cell_alternative_tile(c) + ]) + + undo_manager.add_undo_method(self, &"restore_tiles", tm, restore) + + +func create_tile_restore_point_area(undo_manager: EditorUndoRedoManager, tm: TileMapLayer, area: Rect2i, and_surrounding_cells: bool = true) -> void: + area.end += Vector2i.ONE + + var restore := [] + for y in range(area.position.y, area.end.y): + for x in range(area.position.x, area.end.x): + var c := Vector2i(x, y) + restore.append([ + c, + tm.get_cell_source_id(c), + tm.get_cell_atlas_coords(c), + tm.get_cell_alternative_tile(c) + ]) + + undo_manager.add_undo_method(self, &"restore_tiles", tm, restore) + + if !and_surrounding_cells: + return + + var edges := [] + for x in range(area.position.x, area.end.x): + edges.append(Vector2i(x, area.position.y)) + edges.append(Vector2i(x, area.end.y)) + for y in range(area.position.y + 1, area.end.y - 1): + edges.append(Vector2i(area.position.x, y)) + edges.append(Vector2i(area.end.x, y)) + + edges = BetterTerrain._widen_with_exclusion(tm, edges, area) + create_tile_restore_point(undo_manager, tm, edges, false) + + +func restore_tiles(tm: TileMapLayer, restore: Array) -> void: + for r in restore: + tm.set_cell(r[0], r[1], r[2], r[3]) + + +func create_peering_restore_point(undo_manager: EditorUndoRedoManager, ts: TileSet) -> void: + var restore := [] + + for s in ts.get_source_count(): + var source_id := ts.get_source_id(s) + var source := ts.get_source(source_id) as TileSetAtlasSource + if !source: + continue + + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + + var td := source.get_tile_data(coord, alternate) + var tile_type := BetterTerrain.get_tile_terrain_type(td) + if tile_type == BetterTerrain.TileCategory.NON_TERRAIN: + continue + + var peering_dict := {} + for c in BetterTerrain.tile_peering_keys(td): + peering_dict[c] = BetterTerrain.tile_peering_types(td, c) + var symmetry = BetterTerrain.get_tile_symmetry_type(td) + restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry]) + + undo_manager.add_undo_method(self, &"restore_peering", ts, restore) + + +func create_peering_restore_point_specific(undo_manager: EditorUndoRedoManager, ts: TileSet, protect: int) -> void: + var restore := [] + + for s in ts.get_source_count(): + var source_id := ts.get_source_id(s) + var source := ts.get_source(source_id) as TileSetAtlasSource + if !source: + continue + + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + + var td := source.get_tile_data(coord, alternate) + var tile_type := BetterTerrain.get_tile_terrain_type(td) + if tile_type == BetterTerrain.TileCategory.NON_TERRAIN: + continue + + var to_restore : bool = tile_type == protect + + var terrain := BetterTerrain.get_terrain(ts, tile_type) + var cells = BetterTerrain.data.get_terrain_peering_cells(ts, terrain.type) + for c in cells: + if protect in BetterTerrain.tile_peering_types(td, c): + to_restore = true + break + + if !to_restore: + continue + + var peering_dict := {} + for c in cells: + peering_dict[c] = BetterTerrain.tile_peering_types(td, c) + var symmetry = BetterTerrain.get_tile_symmetry_type(td) + restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry]) + + undo_manager.add_undo_method(self, &"restore_peering", ts, restore) + + +func create_peering_restore_point_tile(undo_manager: EditorUndoRedoManager, ts: TileSet, source_id: int, coord: Vector2i, alternate: int) -> void: + var source := ts.get_source(source_id) as TileSetAtlasSource + var td := source.get_tile_data(coord, alternate) + var tile_type := BetterTerrain.get_tile_terrain_type(td) + + var restore := [] + var peering_dict := {} + for c in BetterTerrain.tile_peering_keys(td): + peering_dict[c] = BetterTerrain.tile_peering_types(td, c) + var symmetry = BetterTerrain.get_tile_symmetry_type(td) + restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry]) + + undo_manager.add_undo_method(self, &"restore_peering", ts, restore) + + +func restore_peering(ts: TileSet, restore: Array) -> void: + for r in restore: + var source := ts.get_source(r[0]) as TileSetAtlasSource + var td := source.get_tile_data(r[1], r[2]) + BetterTerrain.set_tile_terrain_type(ts, td, r[3]) + var peering_types = r[4] + for peering in peering_types: + var types := BetterTerrain.tile_peering_types(td, peering) + for t in types: + BetterTerrain.remove_tile_peering_type(ts, td, peering, t) + for t in peering_types[peering]: + BetterTerrain.add_tile_peering_type(ts, td, peering, t) + var symmetry = r[5] + BetterTerrain.set_tile_symmetry_type(ts, td, symmetry) + + +func create_terrain_type_restore_point(undo_manager: EditorUndoRedoManager, ts: TileSet) -> void: + var count = BetterTerrain.terrain_count(ts) + var restore = [] + for i in count: + restore.push_back(BetterTerrain.get_terrain(ts, i)) + + undo_manager.add_undo_method(self, &"restore_terrain", ts, restore) + + +func restore_terrain(ts: TileSet, restore: Array) -> void: + for i in restore.size(): + var r = restore[i] + BetterTerrain.set_terrain(ts, i, r.name, r.color, r.type, r.categories, r.icon) + + +func add_do_method(undo_manager: EditorUndoRedoManager, object:Object, method:StringName, args:Array): + if action_index > _current_action_index: + _current_action_index = action_index + _current_action_count = action_count + if action_count > _current_action_count: + _current_action_count = action_count + undo_manager.add_do_method(self, "_do_method", object, method, args, action_count) + + +func _do_method(object:Object, method:StringName, args:Array, this_action_count:int): + if this_action_count >= _current_action_count: + object.callv(method, args) + + +func finish_action(): + _current_action_count = 0 diff --git a/addons/better-terrain/editor/TerrainUndo.gd.uid b/addons/better-terrain/editor/TerrainUndo.gd.uid new file mode 100644 index 0000000..996e0b4 --- /dev/null +++ b/addons/better-terrain/editor/TerrainUndo.gd.uid @@ -0,0 +1 @@ +uid://dacg14iy8yybw diff --git a/addons/better-terrain/editor/TileView.gd b/addons/better-terrain/editor/TileView.gd new file mode 100644 index 0000000..4947d83 --- /dev/null +++ b/addons/better-terrain/editor/TileView.gd @@ -0,0 +1,899 @@ +@tool +extends Control + +signal paste_occurred +signal change_zoom_level(value) +signal terrain_updated(index) + +@onready var checkerboard := get_theme_icon("Checkerboard", "EditorIcons") + +@onready var paint_symmetry_icons := [ + null, + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg"), +] + +# Draw checkerboard and tiles with specific materials in +# individual canvas items via rendering server +var _canvas_item_map = {} +var _canvas_item_background : RID + +var tileset: TileSet +var disabled_sources: Array[int] = []: set = set_disabled_sources + +var paint := BetterTerrain.TileCategory.NON_TERRAIN +var paint_symmetry := BetterTerrain.SymmetryType.NONE +var highlighted_tile_part := { valid = false } +var zoom_level := 1.0 + +var tiles_size : Vector2 +var tile_size : Vector2i +var tile_part_size : Vector2 +var alternate_size : Vector2 +var alternate_lookup := [] +var initial_click : Vector2i +var prev_position : Vector2i +var current_position : Vector2i + +var selection_start : Vector2i +var selection_end : Vector2i +var selection_rect : Rect2i +var selected_tile_states : Array[Dictionary] = [] +var copied_tile_states : Array[Dictionary] = [] +var staged_paste_tile_states : Array[Dictionary] = [] + +var pick_icon_terrain : int = -1 +var pick_icon_terrain_cancel := false + +var undo_manager : EditorUndoRedoManager +var terrain_undo + +# Modes for painting +enum PaintMode { + NO_PAINT, + PAINT_TYPE, + PAINT_PEERING, + PAINT_SYMMETRY, + SELECT, + PASTE +} + +var paint_mode := PaintMode.NO_PAINT + +# Actual interactions for painting +enum PaintAction { + NO_ACTION, + DRAW_TYPE, + ERASE_TYPE, + DRAW_PEERING, + ERASE_PEERING, + DRAW_SYMMETRY, + ERASE_SYMMETRY, + SELECT, + PASTE +} + +var paint_action := PaintAction.NO_ACTION + +const ALTERNATE_TILE_MARGIN := 18 + +func _enter_tree() -> void: + _canvas_item_background = RenderingServer.canvas_item_create() + RenderingServer.canvas_item_set_parent(_canvas_item_background, get_canvas_item()) + RenderingServer.canvas_item_set_draw_behind_parent(_canvas_item_background, true) + + +func _exit_tree() -> void: + RenderingServer.free_rid(_canvas_item_background) + for p in _canvas_item_map: + RenderingServer.free_rid(_canvas_item_map[p]) + _canvas_item_map.clear() + + +func refresh_tileset(ts: TileSet) -> void: + tileset = ts + + tiles_size = Vector2.ZERO + alternate_size = Vector2.ZERO + alternate_lookup = [] + disabled_sources = disabled_sources.filter( + func(id): + return ts.has_source(id) + ) + + if !tileset: + return + + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source or !source.texture: + continue + + tiles_size.x = max(tiles_size.x, source.texture.get_width()) + tiles_size.y += source.texture.get_height() + + tile_size = source.texture_region_size + tile_part_size = Vector2(tile_size) / 3.0 + + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var alt_count := source.get_alternative_tiles_count(coord) + if alt_count <= 1: + continue + + var rect := source.get_tile_texture_region(coord, 0) + alternate_lookup.append([rect.size, source_id, coord]) + alternate_size.x = max(alternate_size.x, rect.size.x * (alt_count - 1)) + alternate_size.y += rect.size.y + + _on_zoom_value_changed(zoom_level) + + +func is_tile_in_source(source: TileSetAtlasSource, coord: Vector2i) -> bool: + var origin := source.get_tile_at_coords(coord) + if origin == Vector2i(-1, -1): + return false + + # Animation frames are not needed + var size := source.get_tile_size_in_atlas(origin) + return coord.x < origin.x + size.x and coord.y < origin.y + size.y + + +func _build_tile_part_from_position(result: Dictionary, position: Vector2i, rect: Rect2) -> void: + result.rect = rect + var type := BetterTerrain.get_tile_terrain_type(result.data) + if type == BetterTerrain.TileCategory.NON_TERRAIN: + return + result.terrain_type = type + + var normalize_position := (Vector2(position) - rect.position) / rect.size + + var terrain := BetterTerrain.get_terrain(tileset, type) + if !terrain.valid: + return + for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type): + var side_polygon = BetterTerrain.data.peering_polygon(tileset, terrain.type, p) + if Geometry2D.is_point_in_polygon(normalize_position, side_polygon): + result.peering = p + result.polygon = side_polygon + break + + +func tile_part_from_position(position: Vector2i) -> Dictionary: + if !tileset: + return { valid = false } + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + if Rect2(alt_offset, zoom_level * alternate_size).has_point(position): + for a in alternate_lookup: + if a[1] in disabled_sources: + continue + var next_offset_y = alt_offset.y + zoom_level * a[0].y + if position.y > next_offset_y: + alt_offset.y = next_offset_y + continue + + var source := tileset.get_source(a[1]) as TileSetAtlasSource + if !source: + break + + var count := source.get_alternative_tiles_count(a[2]) + var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1 + + if index < count: + var alt_id := source.get_alternative_tile_id(a[2], index) + var target_rect := Rect2( + alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x, + zoom_level * a[0] + ) + + var result := { + valid = true, + source_id = a[1], + coord = a[2], + alternate = alt_id, + data = source.get_tile_data(a[2], alt_id) + } + _build_tile_part_from_position(result, position, target_rect) + return result + + else: + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source || !source.texture: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var rect := source.get_tile_texture_region(coord, 0) + var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size) + if !target_rect.has_point(position): + continue + + var result := { + valid = true, + source_id = source_id, + coord = coord, + alternate = 0, + data = source.get_tile_data(coord, 0) + } + _build_tile_part_from_position(result, position, target_rect) + return result + + offset.y += zoom_level * source.texture.get_height() + + return { valid = false } + + +func tile_rect_from_position(position: Vector2i) -> Rect2: + if !tileset: + return Rect2(-1,-1,0,0) + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + if Rect2(alt_offset, zoom_level * alternate_size).has_point(position): + for a in alternate_lookup: + if a[1] in disabled_sources: + continue + var next_offset_y = alt_offset.y + zoom_level * a[0].y + if position.y > next_offset_y: + alt_offset.y = next_offset_y + continue + + var source := tileset.get_source(a[1]) as TileSetAtlasSource + if !source: + break + + var count := source.get_alternative_tiles_count(a[2]) + var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1 + + if index < count: + var target_rect := Rect2( + alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x, + zoom_level * a[0] + ) + return target_rect + + else: + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var rect := source.get_tile_texture_region(coord, 0) + var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size) + if target_rect.has_point(position): + return target_rect + + offset.y += zoom_level * source.texture.get_height() + + return Rect2(-1,-1,0,0) + + +func tile_parts_from_rect(rect:Rect2) -> Array[Dictionary]: + if !tileset: + return [] + + var tiles:Array[Dictionary] = [] + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var tile_rect := source.get_tile_texture_region(coord, 0) + var target_rect := Rect2(offset + zoom_level * tile_rect.position, zoom_level * tile_rect.size) + if target_rect.intersects(rect): + var result := { + valid = true, + source_id = source_id, + coord = coord, + alternate = 0, + data = source.get_tile_data(coord, 0) + } + var pos = target_rect.position + target_rect.size/2 + _build_tile_part_from_position(result, pos, target_rect) + tiles.push_back(result) + var alt_count := source.get_alternative_tiles_count(coord) + for a in alt_count: + var alt_id := 0 + if a == 0: + continue + + target_rect = Rect2(alt_offset + zoom_level * (a - 1) * tile_rect.size.x * Vector2.RIGHT, zoom_level * tile_rect.size) + alt_id = source.get_alternative_tile_id(coord, a) + if target_rect.intersects(rect): + var td := source.get_tile_data(coord, alt_id) + var result := { + valid = true, + source_id = source_id, + coord = coord, + alternate = alt_id, + data = td + } + var pos = target_rect.position + target_rect.size/2 + _build_tile_part_from_position(result, pos, target_rect) + tiles.push_back(result) + if alt_count > 1: + alt_offset.y += zoom_level * tile_rect.size.y + + offset.y += zoom_level * source.texture.get_height() + + return tiles + + +func _get_canvas_item(td: TileData) -> RID: + if !td.material: + return self.get_canvas_item() + if _canvas_item_map.has(td.material): + return _canvas_item_map[td.material] + + var rid = RenderingServer.canvas_item_create() + RenderingServer.canvas_item_set_material(rid, td.material.get_rid()) + RenderingServer.canvas_item_set_parent(rid, get_canvas_item()) + RenderingServer.canvas_item_set_draw_behind_parent(rid, true) + RenderingServer.canvas_item_set_default_texture_filter(rid, RenderingServer.CANVAS_ITEM_TEXTURE_FILTER_NEAREST) + _canvas_item_map[td.material] = rid + return rid + + +func _draw_tile_data(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_sides: bool = true) -> void: + var flipped_rect := rect + if td.flip_h: + flipped_rect.size.x = -rect.size.x + if td.flip_v: + flipped_rect.size.y = -rect.size.y + + RenderingServer.canvas_item_add_texture_rect_region( + _get_canvas_item(td), + flipped_rect, + texture.get_rid(), + src_rect, + td.modulate, + td.transpose + ) + + var type := BetterTerrain.get_tile_terrain_type(td) + if type == BetterTerrain.TileCategory.NON_TERRAIN: + draw_rect(rect, Color(0.1, 0.1, 0.1, 0.5), true) + return + + var terrain := BetterTerrain.get_terrain(tileset, type) + if !terrain.valid: + return + + var transform := Transform2D(0.0, rect.size, 0.0, rect.position) + var center_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, -1) + draw_colored_polygon(center_polygon, Color(terrain.color, 0.6)) + if terrain.type == BetterTerrain.TerrainType.DECORATION: + center_polygon.append(center_polygon[0]) + draw_polyline(center_polygon, Color.BLACK) + + if paint < BetterTerrain.TileCategory.EMPTY or paint >= BetterTerrain.terrain_count(tileset): + return + + if not draw_sides: + return + + var paint_terrain := BetterTerrain.get_terrain(tileset, paint) + for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type): + if paint in BetterTerrain.tile_peering_types(td, p): + var side_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, p) + draw_colored_polygon(side_polygon, Color(paint_terrain.color, 0.6)) + if paint_terrain.type == BetterTerrain.TerrainType.DECORATION: + side_polygon.append(side_polygon[0]) + draw_polyline(side_polygon, Color.BLACK) + + +func _draw_tile_symmetry(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_icon: bool = true) -> void: + var flipped_rect := rect + if td.flip_h: + flipped_rect.size.x = -rect.size.x + if td.flip_v: + flipped_rect.size.y = -rect.size.y + + RenderingServer.canvas_item_add_texture_rect_region( + _get_canvas_item(td), + flipped_rect, + texture.get_rid(), + src_rect, + td.modulate, + td.transpose + ) + + if not draw_icon: + return + + var symmetry_type = BetterTerrain.get_tile_symmetry_type(td) + if symmetry_type == 0: + return + var symmetry_icon = paint_symmetry_icons[symmetry_type] + + RenderingServer.canvas_item_add_texture_rect_region( + _get_canvas_item(td), + rect, + symmetry_icon.get_rid(), + Rect2(Vector2.ZERO, symmetry_icon.get_size()), + Color(1,1,1,0.5) + ) + + +func _draw() -> void: + if !tileset: + return + + # Clear material-based render targets + RenderingServer.canvas_item_clear(_canvas_item_background) + for p in _canvas_item_map: + RenderingServer.canvas_item_clear(_canvas_item_map[p]) + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + + RenderingServer.canvas_item_add_texture_rect( + _canvas_item_background, + Rect2(alt_offset, zoom_level * alternate_size), + checkerboard.get_rid(), + true + ) + + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source or !source.texture: + continue + + RenderingServer.canvas_item_add_texture_rect( + _canvas_item_background, + Rect2(offset, zoom_level * source.texture.get_size()), + checkerboard.get_rid(), + true + ) + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var rect := source.get_tile_texture_region(coord, 0) + var alt_count := source.get_alternative_tiles_count(coord) + var target_rect : Rect2 + for a in alt_count: + var alt_id := 0 + if a == 0: + target_rect = Rect2(offset + zoom_level * rect.position, zoom_level * rect.size) + else: + target_rect = Rect2(alt_offset + zoom_level * (a - 1) * rect.size.x * Vector2.RIGHT, zoom_level * rect.size) + alt_id = source.get_alternative_tile_id(coord, a) + + var td := source.get_tile_data(coord, alt_id) + var drawing_current = BetterTerrain.get_tile_terrain_type(td) == paint + if paint_mode == PaintMode.PAINT_SYMMETRY: + _draw_tile_symmetry(source.texture, target_rect, rect, td, drawing_current) + else: + _draw_tile_data(source.texture, target_rect, rect, td) + + if drawing_current: + draw_rect(target_rect.grow(-1), Color(0,0,0, 0.75), false, 1) + draw_rect(target_rect, Color(1,1,1, 0.75), false, 1) + + if paint_mode == PaintMode.SELECT: + if selected_tile_states.any(func(v): + return v.part.data == td + ): + draw_rect(target_rect.grow(-1), Color.DEEP_SKY_BLUE, false, 2) + + if alt_count > 1: + alt_offset.y += zoom_level * rect.size.y + + # Blank out unused or uninteresting tiles + var size := source.get_atlas_grid_size() + for y in size.y: + for x in size.x: + var pos := Vector2i(x, y) + if !is_tile_in_source(source, pos): + var atlas_pos := source.margins + pos * (source.separation + source.texture_region_size) + draw_rect(Rect2(offset + zoom_level * atlas_pos, zoom_level * source.texture_region_size), Color(0.0, 0.0, 0.0, 0.8), true) + + offset.y += zoom_level * source.texture.get_height() + + # Blank out unused alternate tile sections + alt_offset = Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + for a in alternate_lookup: + if a[1] in disabled_sources: + continue + var source := tileset.get_source(a[1]) as TileSetAtlasSource + if source: + var count := source.get_alternative_tiles_count(a[2]) - 1 + var occupied_width = count * zoom_level * a[0].x + var area := Rect2( + alt_offset.x + occupied_width, + alt_offset.y, + zoom_level * alternate_size.x - occupied_width, + zoom_level * a[0].y + ) + draw_rect(area, Color(0.0, 0.0, 0.0, 0.8), true) + alt_offset.y += zoom_level * a[0].y + + if highlighted_tile_part.valid: + if paint_mode == PaintMode.PAINT_PEERING and highlighted_tile_part.has("polygon"): + var transform := Transform2D(0.0, highlighted_tile_part.rect.size - 2 * Vector2.ONE, 0.0, highlighted_tile_part.rect.position + Vector2.ONE) + draw_colored_polygon(transform * highlighted_tile_part.polygon, Color(Color.WHITE, 0.2)) + if paint_mode != PaintMode.NO_PAINT: + var inner_rect := Rect2(highlighted_tile_part.rect.position + Vector2.ONE, highlighted_tile_part.rect.size - 2 * Vector2.ONE) + draw_rect(inner_rect, Color.WHITE, false) + if paint_mode == PaintMode.PAINT_SYMMETRY: + if paint_symmetry > 0: + var symmetry_icon = paint_symmetry_icons[paint_symmetry] + draw_texture_rect(symmetry_icon, highlighted_tile_part.rect, false, Color(0.5,0.75,1,0.5)) + + if paint_mode == PaintMode.SELECT: + draw_rect(selection_rect, Color.WHITE, false) + + if paint_mode == PaintMode.PASTE: + if staged_paste_tile_states.size() > 0: + var base_rect = staged_paste_tile_states[0].base_rect + var paint_terrain := BetterTerrain.get_terrain(tileset, paint) + var paint_terrain_type = paint_terrain.type + if paint_terrain_type == BetterTerrain.TerrainType.CATEGORY: + paint_terrain_type = 0 + for state in staged_paste_tile_states: + var staged_rect:Rect2 = state.base_rect + staged_rect.position -= base_rect.position + base_rect.size / 2 + + staged_rect.position *= zoom_level + staged_rect.size *= zoom_level + + staged_rect.position += Vector2(current_position) + + var real_rect = tile_rect_from_position(staged_rect.get_center()) + if real_rect.position.x >= 0: + draw_rect(real_rect, Color(0,0,0, 0.3), true) + var transform := Transform2D(0.0, real_rect.size, 0.0, real_rect.position) + var tile_sides = BetterTerrain.data.get_terrain_peering_cells(tileset, paint_terrain_type) + for p in tile_sides: + if state.paint in BetterTerrain.tile_peering_types(state.part.data, p): + var side_polygon = BetterTerrain.data.peering_polygon(tileset, paint_terrain_type, p) + var color = Color(paint_terrain.color, 0.6) + draw_colored_polygon(transform * side_polygon, color) + + draw_rect(staged_rect, Color.DEEP_PINK, false) + + + +func delete_selection(): + undo_manager.create_action("Delete tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset) + for t in selected_tile_states: + for side in range(16): + var old_peering = BetterTerrain.tile_peering_types(t.part.data, side) + if old_peering.has(paint): + undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, t.part.data, side, paint) + undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, t.part.data, side, paint) + + undo_manager.add_do_method(self, &"queue_redraw") + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + + +func toggle_selection(): + undo_manager.create_action("Toggle tile terrain", UndoRedo.MERGE_DISABLE, tileset, true) + for t in selected_tile_states: + var type := BetterTerrain.get_tile_terrain_type(t.part.data) + var goal := paint if paint != type else BetterTerrain.TileCategory.NON_TERRAIN + + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, t.part.data, goal]) + if goal == BetterTerrain.TileCategory.NON_TERRAIN: + terrain_undo.create_peering_restore_point_tile( + undo_manager, + tileset, + t.part.source_id, + t.part.coord, + t.part.alternate + ) + else: + undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, t.part.data, type) + + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + + +func copy_selection(): + copied_tile_states = selected_tile_states + + +func paste_selection(): + staged_paste_tile_states = copied_tile_states + selected_tile_states = [] + paint_mode = PaintMode.PASTE + paint_action = PaintAction.PASTE + paste_occurred.emit() + queue_redraw() + + +func set_disabled_sources(list): + disabled_sources = list + queue_redraw() + + +func emit_terrain_updated(index): + terrain_updated.emit(index) + + +func _gui_input(event) -> void: + if event is InputEventKey and event.is_pressed(): + if event.keycode == KEY_DELETE and not event.echo: + accept_event() + delete_selection() + if event.keycode == KEY_ENTER and not event.echo: + accept_event() + toggle_selection() + if event.keycode == KEY_ESCAPE and not event.echo: + accept_event() + if paint_action == PaintAction.PASTE: + staged_paste_tile_states = [] + paint_mode = PaintMode.SELECT + paint_action = PaintAction.NO_ACTION + selection_start = Vector2i(-1,-1) + if event.keycode == KEY_C and (event.ctrl_pressed or event.meta_pressed) and not event.echo: + accept_event() + copy_selection() + if event.keycode == KEY_X and (event.ctrl_pressed or event.meta_pressed) and not event.echo: + accept_event() + copy_selection() + delete_selection() + if event.keycode == KEY_V and (event.ctrl_pressed or event.meta_pressed) and not event.echo: + accept_event() + paste_selection() + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_WHEEL_UP and (event.ctrl_pressed or event.meta_pressed): + accept_event() + change_zoom_level.emit(zoom_level * 1.1) + if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and (event.ctrl_pressed or event.meta_pressed): + accept_event() + change_zoom_level.emit(zoom_level / 1.1) + + var released : bool = event is InputEventMouseButton and (not event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT)) + if released: + paint_action = PaintAction.NO_ACTION + + if event is InputEventMouseMotion: + prev_position = current_position + current_position = event.position + var tile := tile_part_from_position(event.position) + if tile.valid != highlighted_tile_part.valid or\ + (tile.valid and tile.data != highlighted_tile_part.data) or\ + (tile.valid and tile.get("peering") != highlighted_tile_part.get("peering")) or\ + event.button_mask & MOUSE_BUTTON_LEFT and paint_action == PaintAction.SELECT: + queue_redraw() + highlighted_tile_part = tile + + var clicked : bool = event is InputEventMouseButton and (event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT)) + if clicked: + initial_click = current_position + selection_start = Vector2i(-1,-1) + terrain_undo.action_index += 1 + terrain_undo.action_count = 0 + if released: + terrain_undo.finish_action() + selection_rect = Rect2i(0,0,0,0) + queue_redraw() + + if paint_action == PaintAction.PASTE: + if event is InputEventMouseMotion: + queue_redraw() + + if clicked: + if event.button_index == MOUSE_BUTTON_LEFT and staged_paste_tile_states.size() > 0: + undo_manager.create_action("Paste tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset) + var base_rect = staged_paste_tile_states[0].base_rect + for p in staged_paste_tile_states: + var staged_rect:Rect2 = p.base_rect + staged_rect.position -= base_rect.position + base_rect.size / 2 + + staged_rect.position *= zoom_level + staged_rect.size *= zoom_level + + staged_rect.position += Vector2(current_position) + + var old_tile_part = tile_part_from_position(staged_rect.get_center()) + var new_tile_state = p + if (not old_tile_part.valid) or (not new_tile_state.part.valid): + continue + + for side in range(16): + var old_peering = BetterTerrain.tile_peering_types(old_tile_part.data, side) + var new_sides = new_tile_state.sides + if new_sides.has(side) and not old_peering.has(paint): + undo_manager.add_do_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint) + undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint) + elif old_peering.has(paint) and not new_sides.has(side): + undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint) + undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint) + + var old_symmetry = BetterTerrain.get_tile_symmetry_type(old_tile_part.data) + var new_symmetry = new_tile_state.symmetry + if new_symmetry != old_symmetry: + undo_manager.add_do_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, new_symmetry) + undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, old_symmetry) + + undo_manager.add_do_method(self, &"queue_redraw") + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + + staged_paste_tile_states = [] + paint_mode = PaintMode.SELECT + paint_action = PaintAction.SELECT + return + + if clicked and pick_icon_terrain >= 0: + highlighted_tile_part = tile_part_from_position(current_position) + if !highlighted_tile_part.valid: + return + + var t = BetterTerrain.get_terrain(tileset, paint) + var prev_icon = t.icon.duplicate() + var icon = { + source_id = highlighted_tile_part.source_id, + coord = highlighted_tile_part.coord + } + undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, icon) + undo_manager.add_do_method(self, &"emit_terrain_updated", paint) + undo_manager.add_undo_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, prev_icon) + undo_manager.add_undo_method(self, &"emit_terrain_updated", paint) + undo_manager.commit_action() + pick_icon_terrain = -1 + return + + if pick_icon_terrain_cancel: + pick_icon_terrain = -1 + pick_icon_terrain_cancel = false + + if paint != BetterTerrain.TileCategory.NON_TERRAIN and clicked: + paint_action = PaintAction.NO_ACTION + if highlighted_tile_part.valid: + match [paint_mode, event.button_index]: + [PaintMode.PAINT_TYPE, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_TYPE + [PaintMode.PAINT_TYPE, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_TYPE + [PaintMode.PAINT_PEERING, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_PEERING + [PaintMode.PAINT_PEERING, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_PEERING + [PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_SYMMETRY + [PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_SYMMETRY + [PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT + else: + match [paint_mode, event.button_index]: + [PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT + + if (clicked or event is InputEventMouseMotion) and paint_action != PaintAction.NO_ACTION: + + if paint_action == PaintAction.SELECT: + if clicked: + selection_start = Vector2i(-1,-1) + queue_redraw() + if selection_start.x < 0: + selection_start = current_position + selection_end = current_position + + selection_rect = Rect2i(selection_start, selection_end - selection_start).abs() + var selected_tile_parts = tile_parts_from_rect(selection_rect) + selected_tile_states = [] + for t in selected_tile_parts: + var state := { + part = t, + base_rect = Rect2(t.rect.position / zoom_level, t.rect.size / zoom_level), + paint = paint, + sides = BetterTerrain.tile_peering_for_type(t.data, paint), + symmetry = BetterTerrain.get_tile_symmetry_type(t.data) + } + selected_tile_states.push_back(state) + else: + if !highlighted_tile_part.valid: + return + #slightly crude and non-optimal but way simpler than the "correct" solution + var current_position_vec2 = Vector2(current_position) + var prev_position_vec2 = Vector2(prev_position) + var mouse_dist = current_position_vec2.distance_to(prev_position_vec2) + var step_size = (tile_part_size.x * zoom_level) + var steps = ceil(mouse_dist / step_size) + 1 + for i in range(steps): + var t = float(i) / steps + var check_position = prev_position_vec2.lerp(current_position_vec2, t) + highlighted_tile_part = tile_part_from_position(check_position) + + if !highlighted_tile_part.valid: + continue + + if paint_action == PaintAction.DRAW_TYPE or paint_action == PaintAction.ERASE_TYPE: + var type := BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data) + var goal := paint if paint_action == PaintAction.DRAW_TYPE else BetterTerrain.TileCategory.NON_TERRAIN + if type != goal: + undo_manager.create_action("Set tile terrain type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, highlighted_tile_part.data, goal]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + if goal == BetterTerrain.TileCategory.NON_TERRAIN: + terrain_undo.create_peering_restore_point_tile( + undo_manager, + tileset, + highlighted_tile_part.source_id, + highlighted_tile_part.coord, + highlighted_tile_part.alternate + ) + else: + undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, highlighted_tile_part.data, type) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.DRAW_PEERING: + if highlighted_tile_part.has("peering"): + if !(paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering)): + undo_manager.create_action("Set tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"add_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.ERASE_PEERING: + if highlighted_tile_part.has("peering"): + if paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering): + undo_manager.create_action("Remove tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"remove_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.DRAW_SYMMETRY: + if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data): + undo_manager.create_action("Set tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, paint_symmetry]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.ERASE_SYMMETRY: + if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data): + undo_manager.create_action("Remove tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, BetterTerrain.SymmetryType.NONE]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + + +func _on_zoom_value_changed(value) -> void: + zoom_level = value + custom_minimum_size.x = zoom_level * tiles_size.x + if alternate_size.x > 0: + custom_minimum_size.x += ALTERNATE_TILE_MARGIN + zoom_level * alternate_size.x + custom_minimum_size.y = zoom_level * max(tiles_size.y, alternate_size.y) + queue_redraw() + + +func clear_highlighted_tile() -> void: + highlighted_tile_part = { valid = false } + queue_redraw() diff --git a/addons/better-terrain/editor/TileView.gd.uid b/addons/better-terrain/editor/TileView.gd.uid new file mode 100644 index 0000000..c811a5c --- /dev/null +++ b/addons/better-terrain/editor/TileView.gd.uid @@ -0,0 +1 @@ +uid://dtxjjy2ylbhqo diff --git a/addons/better-terrain/icon.svg b/addons/better-terrain/icon.svg new file mode 100644 index 0000000..377fba9 --- /dev/null +++ b/addons/better-terrain/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/better-terrain/icon.svg.import b/addons/better-terrain/icon.svg.import new file mode 100644 index 0000000..41c5793 --- /dev/null +++ b/addons/better-terrain/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c66nal373iwgd" +path="res://.godot/imported/icon.svg-7d4870855c0daec5051feb4adbea0091.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icon.svg" +dest_files=["res://.godot/imported/icon.svg-7d4870855c0daec5051feb4adbea0091.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/Decoration.svg b/addons/better-terrain/icons/Decoration.svg new file mode 100644 index 0000000..8ec40b5 --- /dev/null +++ b/addons/better-terrain/icons/Decoration.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/better-terrain/icons/Decoration.svg.import b/addons/better-terrain/icons/Decoration.svg.import new file mode 100644 index 0000000..e7b445b --- /dev/null +++ b/addons/better-terrain/icons/Decoration.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kmypxsqhynyv" +path="res://.godot/imported/Decoration.svg-03773e83cc849c7744ecf3d36eee0072.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/Decoration.svg" +dest_files=["res://.godot/imported/Decoration.svg-03773e83cc849c7744ecf3d36eee0072.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/EditSymmetry.svg b/addons/better-terrain/icons/EditSymmetry.svg new file mode 100644 index 0000000..3565800 --- /dev/null +++ b/addons/better-terrain/icons/EditSymmetry.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/addons/better-terrain/icons/EditSymmetry.svg.import b/addons/better-terrain/icons/EditSymmetry.svg.import new file mode 100644 index 0000000..90e85b0 --- /dev/null +++ b/addons/better-terrain/icons/EditSymmetry.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co6gwwmog0pjy" +path="res://.godot/imported/EditSymmetry.svg-794172208a8d86bb609531b82199f095.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/EditSymmetry.svg" +dest_files=["res://.godot/imported/EditSymmetry.svg-794172208a8d86bb609531b82199f095.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/EditTerrain.svg b/addons/better-terrain/icons/EditTerrain.svg new file mode 100644 index 0000000..e175385 --- /dev/null +++ b/addons/better-terrain/icons/EditTerrain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/better-terrain/icons/EditTerrain.svg.import b/addons/better-terrain/icons/EditTerrain.svg.import new file mode 100644 index 0000000..9304811 --- /dev/null +++ b/addons/better-terrain/icons/EditTerrain.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bo2cjv08jkvf8" +path="res://.godot/imported/EditTerrain.svg-f7ee950d68a391de33e4e8ddd76bf2ac.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/EditTerrain.svg" +dest_files=["res://.godot/imported/EditTerrain.svg-f7ee950d68a391de33e4e8ddd76bf2ac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/EditType.svg b/addons/better-terrain/icons/EditType.svg new file mode 100644 index 0000000..51e7d41 --- /dev/null +++ b/addons/better-terrain/icons/EditType.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/better-terrain/icons/EditType.svg.import b/addons/better-terrain/icons/EditType.svg.import new file mode 100644 index 0000000..cfdcb90 --- /dev/null +++ b/addons/better-terrain/icons/EditType.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6lxq2y7mpb18" +path="res://.godot/imported/EditType.svg-e7b3005c6a8f21d5102295c55b564ad1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/EditType.svg" +dest_files=["res://.godot/imported/EditType.svg-e7b3005c6a8f21d5102295c55b564ad1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/MatchTiles.svg b/addons/better-terrain/icons/MatchTiles.svg new file mode 100644 index 0000000..efc5713 --- /dev/null +++ b/addons/better-terrain/icons/MatchTiles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/better-terrain/icons/MatchTiles.svg.import b/addons/better-terrain/icons/MatchTiles.svg.import new file mode 100644 index 0000000..7d1b1fb --- /dev/null +++ b/addons/better-terrain/icons/MatchTiles.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1h1p7pcwdnjk" +path="res://.godot/imported/MatchTiles.svg-38111e21a893bd8f161311f0d1968a40.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/MatchTiles.svg" +dest_files=["res://.godot/imported/MatchTiles.svg-38111e21a893bd8f161311f0d1968a40.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/MatchVertices.svg b/addons/better-terrain/icons/MatchVertices.svg new file mode 100644 index 0000000..339ee2c --- /dev/null +++ b/addons/better-terrain/icons/MatchVertices.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/better-terrain/icons/MatchVertices.svg.import b/addons/better-terrain/icons/MatchVertices.svg.import new file mode 100644 index 0000000..9eaa4d3 --- /dev/null +++ b/addons/better-terrain/icons/MatchVertices.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfemy1g6okwlv" +path="res://.godot/imported/MatchVertices.svg-288fe47ee1089920379407d6abf1a06c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/MatchVertices.svg" +dest_files=["res://.godot/imported/MatchVertices.svg-288fe47ee1089920379407d6abf1a06c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/NonModifying.svg b/addons/better-terrain/icons/NonModifying.svg new file mode 100644 index 0000000..a1a10dd --- /dev/null +++ b/addons/better-terrain/icons/NonModifying.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/better-terrain/icons/NonModifying.svg.import b/addons/better-terrain/icons/NonModifying.svg.import new file mode 100644 index 0000000..b711ebb --- /dev/null +++ b/addons/better-terrain/icons/NonModifying.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1yr6yruwl63u" +path="res://.godot/imported/NonModifying.svg-4d16d471be4a8f1d3ba0c013ff629ee1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/NonModifying.svg" +dest_files=["res://.godot/imported/NonModifying.svg-4d16d471be4a8f1d3ba0c013ff629ee1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/Replace.svg b/addons/better-terrain/icons/Replace.svg new file mode 100644 index 0000000..bcb940b --- /dev/null +++ b/addons/better-terrain/icons/Replace.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/addons/better-terrain/icons/Replace.svg.import b/addons/better-terrain/icons/Replace.svg.import new file mode 100644 index 0000000..d0fba54 --- /dev/null +++ b/addons/better-terrain/icons/Replace.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y3xy6qdckht6" +path="res://.godot/imported/Replace.svg-7654df79fd42fc27133e4d3f81a4d56b.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/Replace.svg" +dest_files=["res://.godot/imported/Replace.svg-7654df79fd42fc27133e4d3f81a4d56b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/ShuffleRandom.svg b/addons/better-terrain/icons/ShuffleRandom.svg new file mode 100644 index 0000000..a66ba86 --- /dev/null +++ b/addons/better-terrain/icons/ShuffleRandom.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/addons/better-terrain/icons/ShuffleRandom.svg.import b/addons/better-terrain/icons/ShuffleRandom.svg.import new file mode 100644 index 0000000..15e74ff --- /dev/null +++ b/addons/better-terrain/icons/ShuffleRandom.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cs4mdmluiydj6" +path="res://.godot/imported/ShuffleRandom.svg-15ee49f7a06c55a1e95e1ed056732dc5.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/ShuffleRandom.svg" +dest_files=["res://.godot/imported/ShuffleRandom.svg-15ee49f7a06c55a1e95e1ed056732dc5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryAll.svg b/addons/better-terrain/icons/SymmetryAll.svg new file mode 100644 index 0000000..4aeca2d --- /dev/null +++ b/addons/better-terrain/icons/SymmetryAll.svg @@ -0,0 +1,4 @@ + + + + diff --git a/addons/better-terrain/icons/SymmetryAll.svg.import b/addons/better-terrain/icons/SymmetryAll.svg.import new file mode 100644 index 0000000..e14d772 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryAll.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cyjra4g05dwh" +path="res://.godot/imported/SymmetryAll.svg-cd6a02766f60c09344aa97e0325457c1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryAll.svg" +dest_files=["res://.godot/imported/SymmetryAll.svg-cd6a02766f60c09344aa97e0325457c1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryFlip.svg b/addons/better-terrain/icons/SymmetryFlip.svg new file mode 100644 index 0000000..a180318 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryFlip.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/SymmetryFlip.svg.import b/addons/better-terrain/icons/SymmetryFlip.svg.import new file mode 100644 index 0000000..360f213 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryFlip.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dqmc1jp56or8m" +path="res://.godot/imported/SymmetryFlip.svg-ea11c1010d0643843f115093c045dc42.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryFlip.svg" +dest_files=["res://.godot/imported/SymmetryFlip.svg-ea11c1010d0643843f115093c045dc42.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryMirror.svg b/addons/better-terrain/icons/SymmetryMirror.svg new file mode 100644 index 0000000..463e09a --- /dev/null +++ b/addons/better-terrain/icons/SymmetryMirror.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/SymmetryMirror.svg.import b/addons/better-terrain/icons/SymmetryMirror.svg.import new file mode 100644 index 0000000..3cd39a4 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryMirror.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5hm3bfj3dvej" +path="res://.godot/imported/SymmetryMirror.svg-0bf9d259572cc33d41c783e35586310a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryMirror.svg" +dest_files=["res://.godot/imported/SymmetryMirror.svg-0bf9d259572cc33d41c783e35586310a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryReflect.svg b/addons/better-terrain/icons/SymmetryReflect.svg new file mode 100644 index 0000000..c618809 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryReflect.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/addons/better-terrain/icons/SymmetryReflect.svg.import b/addons/better-terrain/icons/SymmetryReflect.svg.import new file mode 100644 index 0000000..2894ffd --- /dev/null +++ b/addons/better-terrain/icons/SymmetryReflect.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxoewno1cefua" +path="res://.godot/imported/SymmetryReflect.svg-39f88a51808c88d6cb37005ed1ddd254.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryReflect.svg" +dest_files=["res://.godot/imported/SymmetryReflect.svg-39f88a51808c88d6cb37005ed1ddd254.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryRotate180.svg b/addons/better-terrain/icons/SymmetryRotate180.svg new file mode 100644 index 0000000..44b65fd --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotate180.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/addons/better-terrain/icons/SymmetryRotate180.svg.import b/addons/better-terrain/icons/SymmetryRotate180.svg.import new file mode 100644 index 0000000..cb2c534 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotate180.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8mcycyl3e66r" +path="res://.godot/imported/SymmetryRotate180.svg-805113e1c31c7195ed5fec5febf455b9.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotate180.svg" +dest_files=["res://.godot/imported/SymmetryRotate180.svg-805113e1c31c7195ed5fec5febf455b9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryRotateAll.svg b/addons/better-terrain/icons/SymmetryRotateAll.svg new file mode 100644 index 0000000..1fb8d0e --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotateAll.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/better-terrain/icons/SymmetryRotateAll.svg.import b/addons/better-terrain/icons/SymmetryRotateAll.svg.import new file mode 100644 index 0000000..2a9f302 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotateAll.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7fx4mk18lmls" +path="res://.godot/imported/SymmetryRotateAll.svg-959ef9f7a9c5b12d37b3a1c9ddcf2432.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotateAll.svg" +dest_files=["res://.godot/imported/SymmetryRotateAll.svg-959ef9f7a9c5b12d37b3a1c9ddcf2432.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryRotateClockwise.svg b/addons/better-terrain/icons/SymmetryRotateClockwise.svg new file mode 100644 index 0000000..1f4823a --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotateClockwise.svg @@ -0,0 +1,4 @@ + + + + diff --git a/addons/better-terrain/icons/SymmetryRotateClockwise.svg.import b/addons/better-terrain/icons/SymmetryRotateClockwise.svg.import new file mode 100644 index 0000000..98e1500 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotateClockwise.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://baxhjy28r1iqj" +path="res://.godot/imported/SymmetryRotateClockwise.svg-9d1254877c31fcd2b5fd3dd58555e624.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotateClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateClockwise.svg-9d1254877c31fcd2b5fd3dd58555e624.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg b/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg new file mode 100644 index 0000000..6ffb93a --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg @@ -0,0 +1,4 @@ + + + + diff --git a/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg.import b/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg.import new file mode 100644 index 0000000..88bf645 --- /dev/null +++ b/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csbwdkr6bc2db" +path="res://.godot/imported/SymmetryRotateCounterClockwise.svg-ba4f86a741d97c0ebfc0ae19d3460f6f.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateCounterClockwise.svg-ba4f86a741d97c0ebfc0ae19d3460f6f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/Warning.svg b/addons/better-terrain/icons/Warning.svg new file mode 100644 index 0000000..199bf7f --- /dev/null +++ b/addons/better-terrain/icons/Warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/better-terrain/icons/Warning.svg.import b/addons/better-terrain/icons/Warning.svg.import new file mode 100644 index 0000000..c307df1 --- /dev/null +++ b/addons/better-terrain/icons/Warning.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0es228gfcykd" +path="res://.godot/imported/Warning.svg-7bb0ec60ff2da2c7ebdba79b0dcdd006.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/Warning.svg" +dest_files=["res://.godot/imported/Warning.svg-7bb0ec60ff2da2c7ebdba79b0dcdd006.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg new file mode 100644 index 0000000..559ca91 --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg.import new file mode 100644 index 0000000..84ee2d9 --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://iid5buh1t5j5" +path="res://.godot/imported/SymmetryAll.svg-c2902d14b54ee9a54b7986a2ea5e47a7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg" +dest_files=["res://.godot/imported/SymmetryAll.svg-c2902d14b54ee9a54b7986a2ea5e47a7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg new file mode 100644 index 0000000..0b60a3f --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg.import new file mode 100644 index 0000000..fbb0821 --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brro1lqnf3r5y" +path="res://.godot/imported/SymmetryFlip.svg-0de1b384a4706cad746bcf7b3b7f0c2d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg" +dest_files=["res://.godot/imported/SymmetryFlip.svg-0de1b384a4706cad746bcf7b3b7f0c2d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg new file mode 100644 index 0000000..e0a268f --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg.import new file mode 100644 index 0000000..98e053e --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpf5p8xxn52cb" +path="res://.godot/imported/SymmetryMirror.svg-2ba85612b4c15f1a7eab344dc47f9a9a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg" +dest_files=["res://.godot/imported/SymmetryMirror.svg-2ba85612b4c15f1a7eab344dc47f9a9a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg new file mode 100644 index 0000000..5acc95a --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg.import new file mode 100644 index 0000000..17d191d --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d251v4pxpwsre" +path="res://.godot/imported/SymmetryReflect.svg-de65ca99c884ea9239bb60e11b7c0ca4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg" +dest_files=["res://.godot/imported/SymmetryReflect.svg-de65ca99c884ea9239bb60e11b7c0ca4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg new file mode 100644 index 0000000..677a62b --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg.import new file mode 100644 index 0000000..c55efb4 --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1bmbyb3ig0mx" +path="res://.godot/imported/SymmetryRotate180.svg-ff244f85658bd621d56af3cf4f7c7ebe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg" +dest_files=["res://.godot/imported/SymmetryRotate180.svg-ff244f85658bd621d56af3cf4f7c7ebe.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg new file mode 100644 index 0000000..fc81aae --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg.import new file mode 100644 index 0000000..8502261 --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcky1dfn4umac" +path="res://.godot/imported/SymmetryRotateAll.svg-795a9b37a8f5df7e7376c9f762121b21.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg" +dest_files=["res://.godot/imported/SymmetryRotateAll.svg-795a9b37a8f5df7e7376c9f762121b21.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg new file mode 100644 index 0000000..400e11a --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg.import new file mode 100644 index 0000000..bad87b2 --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://def0fcqsn6s6x" +path="res://.godot/imported/SymmetryRotateClockwise.svg-e133d151dd3970411596d18bb133aece.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateClockwise.svg-e133d151dd3970411596d18bb133aece.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg new file mode 100644 index 0000000..39b5242 --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg.import b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg.import new file mode 100644 index 0000000..258722f --- /dev/null +++ b/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ngej4qhkypb2" +path="res://.godot/imported/SymmetryRotateCounterClockwise.svg-b603f534dc5383de58f7e26cdf86fe8b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateCounterClockwise.svg-b603f534dc5383de58f7e26cdf86fe8b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/better-terrain/plugin.cfg b/addons/better-terrain/plugin.cfg new file mode 100644 index 0000000..1bfb36b --- /dev/null +++ b/addons/better-terrain/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="BetterTerrain" +description="This is a drop-in replacement for Godot 4's tilemap terrain system, offering more versatile and straightforward autotiling. It can be used with any existing TileMap or TileSet, either through the editor plugin, or directly via code." +author="Portponky" +version="" +script="TerrainPlugin.gd" diff --git a/addons/com.heroiclabs.nakama/Nakama.gd.uid b/addons/com.heroiclabs.nakama/Nakama.gd.uid new file mode 100644 index 0000000..1380549 --- /dev/null +++ b/addons/com.heroiclabs.nakama/Nakama.gd.uid @@ -0,0 +1 @@ +uid://br8q384r6ov6t diff --git a/addons/com.heroiclabs.nakama/api/NakamaAPI.gd b/addons/com.heroiclabs.nakama/api/NakamaAPI.gd index fc680bd..d61e061 100644 --- a/addons/com.heroiclabs.nakama/api/NakamaAPI.gd +++ b/addons/com.heroiclabs.nakama/api/NakamaAPI.gd @@ -1597,7 +1597,7 @@ class ApiMatch extends NakamaAsyncResult: "tick_rate": {"name": "_tick_rate", "type": TYPE_INT, "required": false}, } - # True if it's an server-managed authoritative match, false otherwise. + ## True if it's an server-managed authoritative match, false otherwise. var _authoritative var authoritative : bool: get: @@ -1609,25 +1609,25 @@ class ApiMatch extends NakamaAsyncResult: get: return "" if not _handler_name is String else String(_handler_name) - # Match label, if any. + ## Match label, if any. var _label var label : String: get: return "" if not _label is String else String(_label) - # The ID of the match, can be used to join. + ## The ID of the match, can be used to join. var _match_id var match_id : String: get: return "" if not _match_id is String else String(_match_id) - # Current number of users in the match. + ## Current number of users in the match. var _size var size : int: get: return 0 if not _size is int else int(_size) - # + ## var _tick_rate var tick_rate : int: get: @@ -5572,6 +5572,8 @@ class ApiClient extends RefCounted: var content : PackedByteArray var result = await _http_adapter.send_async(method, uri, headers, content) + + if result is NakamaException: return NakamaAsyncResult.new(result) return NakamaAsyncResult.new() diff --git a/addons/com.heroiclabs.nakama/api/NakamaAPI.gd.uid b/addons/com.heroiclabs.nakama/api/NakamaAPI.gd.uid new file mode 100644 index 0000000..0435fa1 --- /dev/null +++ b/addons/com.heroiclabs.nakama/api/NakamaAPI.gd.uid @@ -0,0 +1 @@ +uid://doqrcryl538r5 diff --git a/addons/com.heroiclabs.nakama/api/NakamaRTAPI.gd.uid b/addons/com.heroiclabs.nakama/api/NakamaRTAPI.gd.uid new file mode 100644 index 0000000..4dc1d3c --- /dev/null +++ b/addons/com.heroiclabs.nakama/api/NakamaRTAPI.gd.uid @@ -0,0 +1 @@ +uid://bdnpwrd0a1h7q diff --git a/addons/com.heroiclabs.nakama/api/NakamaRTMessage.gd.uid b/addons/com.heroiclabs.nakama/api/NakamaRTMessage.gd.uid new file mode 100644 index 0000000..2603e48 --- /dev/null +++ b/addons/com.heroiclabs.nakama/api/NakamaRTMessage.gd.uid @@ -0,0 +1 @@ +uid://c56menmy5n01g diff --git a/addons/com.heroiclabs.nakama/api/NakamaSession.gd.uid b/addons/com.heroiclabs.nakama/api/NakamaSession.gd.uid new file mode 100644 index 0000000..8dfc084 --- /dev/null +++ b/addons/com.heroiclabs.nakama/api/NakamaSession.gd.uid @@ -0,0 +1 @@ +uid://b7muajxnp7xye diff --git a/addons/com.heroiclabs.nakama/api/NakamaStorageObjectId.gd.uid b/addons/com.heroiclabs.nakama/api/NakamaStorageObjectId.gd.uid new file mode 100644 index 0000000..b99543b --- /dev/null +++ b/addons/com.heroiclabs.nakama/api/NakamaStorageObjectId.gd.uid @@ -0,0 +1 @@ +uid://n86umn4l3ovu diff --git a/addons/com.heroiclabs.nakama/api/NakamaWriteStorageObject.gd.uid b/addons/com.heroiclabs.nakama/api/NakamaWriteStorageObject.gd.uid new file mode 100644 index 0000000..38fbf53 --- /dev/null +++ b/addons/com.heroiclabs.nakama/api/NakamaWriteStorageObject.gd.uid @@ -0,0 +1 @@ +uid://b6aqfivcw7eax diff --git a/addons/com.heroiclabs.nakama/client/NakamaClient.gd.uid b/addons/com.heroiclabs.nakama/client/NakamaClient.gd.uid new file mode 100644 index 0000000..b661354 --- /dev/null +++ b/addons/com.heroiclabs.nakama/client/NakamaClient.gd.uid @@ -0,0 +1 @@ +uid://du33hbgwifdia diff --git a/addons/com.heroiclabs.nakama/client/NakamaHTTPAdapter.gd.uid b/addons/com.heroiclabs.nakama/client/NakamaHTTPAdapter.gd.uid new file mode 100644 index 0000000..0268349 --- /dev/null +++ b/addons/com.heroiclabs.nakama/client/NakamaHTTPAdapter.gd.uid @@ -0,0 +1 @@ +uid://brtq6j4luq0vx diff --git a/addons/com.heroiclabs.nakama/socket/NakamaSocket.gd.uid b/addons/com.heroiclabs.nakama/socket/NakamaSocket.gd.uid new file mode 100644 index 0000000..91f08a1 --- /dev/null +++ b/addons/com.heroiclabs.nakama/socket/NakamaSocket.gd.uid @@ -0,0 +1 @@ +uid://dsba5vyfkyckk diff --git a/addons/com.heroiclabs.nakama/socket/NakamaSocketAdapter.gd.uid b/addons/com.heroiclabs.nakama/socket/NakamaSocketAdapter.gd.uid new file mode 100644 index 0000000..e4c4dce --- /dev/null +++ b/addons/com.heroiclabs.nakama/socket/NakamaSocketAdapter.gd.uid @@ -0,0 +1 @@ +uid://d2p4culwq30mq diff --git a/addons/com.heroiclabs.nakama/utils/NakamaAsyncResult.gd.uid b/addons/com.heroiclabs.nakama/utils/NakamaAsyncResult.gd.uid new file mode 100644 index 0000000..0ca8c59 --- /dev/null +++ b/addons/com.heroiclabs.nakama/utils/NakamaAsyncResult.gd.uid @@ -0,0 +1 @@ +uid://dmriqkfr75tb6 diff --git a/addons/com.heroiclabs.nakama/utils/NakamaException.gd.uid b/addons/com.heroiclabs.nakama/utils/NakamaException.gd.uid new file mode 100644 index 0000000..6f4ad33 --- /dev/null +++ b/addons/com.heroiclabs.nakama/utils/NakamaException.gd.uid @@ -0,0 +1 @@ +uid://b4dcnrrkavrn7 diff --git a/addons/com.heroiclabs.nakama/utils/NakamaLogger.gd b/addons/com.heroiclabs.nakama/utils/NakamaLogger.gd index 4f01830..e317f34 100644 --- a/addons/com.heroiclabs.nakama/utils/NakamaLogger.gd +++ b/addons/com.heroiclabs.nakama/utils/NakamaLogger.gd @@ -3,14 +3,23 @@ class_name NakamaLogger enum LOG_LEVEL {NONE, ERROR, WARNING, INFO, VERBOSE, DEBUG} -var _level = LOG_LEVEL.ERROR +var disabled : bool + +var _level = LOG_LEVEL.NONE var _module = "Nakama" -func _init(p_module : String = "Nakama", p_level : int = LOG_LEVEL.ERROR): +func _init(p_module : String = "Nakama", + p_level : int = LOG_LEVEL.NONE, + p_disabled : bool = true): _level = p_level _module = p_module + disabled = p_disabled func _log(level : int, msg): + + if disabled: + return + if level <= _level: if level == LOG_LEVEL.ERROR: printerr("=== %s : ERROR === %s" % [_module, str(msg)]) diff --git a/addons/com.heroiclabs.nakama/utils/NakamaLogger.gd.uid b/addons/com.heroiclabs.nakama/utils/NakamaLogger.gd.uid new file mode 100644 index 0000000..20c1119 --- /dev/null +++ b/addons/com.heroiclabs.nakama/utils/NakamaLogger.gd.uid @@ -0,0 +1 @@ +uid://b7ce8l5k8tskq diff --git a/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd index 66c15bb..56785b0 100644 --- a/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd +++ b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd @@ -13,8 +13,8 @@ enum MetaMessageType { ASSIGN_PEER_ID, } -# Read-only variables. var _nakama_socket: NakamaSocket +## Read-only variables. var nakama_socket: NakamaSocket: get: return _nakama_socket set(_v): pass @@ -31,7 +31,7 @@ var multiplayer_peer: NakamaMultiplayerPeer: get: return _multiplayer_peer set(_v): pass -# Configuration that can be set by the developer. +## Configuration that can be set by the developer. var meta_op_code: int = 9001 var rpc_op_code: int = 9002 diff --git a/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd.uid b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd.uid new file mode 100644 index 0000000..8d4630a --- /dev/null +++ b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd.uid @@ -0,0 +1 @@ +uid://dj6wcrcniarvj diff --git a/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd index 6bfb8d9..878470a 100644 --- a/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd +++ b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd @@ -18,14 +18,14 @@ class Packet extends RefCounted: var _incoming_packets := [] -signal packet_generated (peer_id, buffer) +signal packet_generated (peer_id: int, buffer: PackedByteArray) func _get_packet_script() -> PackedByteArray: if _incoming_packets.size() == 0: return PackedByteArray() return _incoming_packets.pop_front().data -func _get_packet_mode() -> int: +func _get_packet_mode() -> TransferMode: return TRANSFER_MODE_RELIABLE func _get_packet_channel() -> int: diff --git a/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd.uid b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd.uid new file mode 100644 index 0000000..f114048 --- /dev/null +++ b/addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd.uid @@ -0,0 +1 @@ +uid://burgdqcrpcyr6 diff --git a/addons/com.heroiclabs.nakama/utils/NakamaSerializer.gd.uid b/addons/com.heroiclabs.nakama/utils/NakamaSerializer.gd.uid new file mode 100644 index 0000000..f3df316 --- /dev/null +++ b/addons/com.heroiclabs.nakama/utils/NakamaSerializer.gd.uid @@ -0,0 +1 @@ +uid://ce0xhgoq5w4yg diff --git a/codegen/README.md b/codegen/README.md index ee85ea3..042c19c 100644 --- a/codegen/README.md +++ b/codegen/README.md @@ -18,4 +18,3 @@ The generated code is designed to be supported Godot Engine `3.1+`. ### Limitations The code generator has __only__ been checked against the Swagger specification generated for Nakama server. YMMV. - diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..9cc3f13 --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,4 @@ +[gd_resource type="AudioBusLayout" format=3 uid="uid://bkyf062nq3qly"] + +[resource] +bus/0/volume_db = -0.13049698 diff --git a/game_manager.tscn b/game_manager.tscn deleted file mode 100644 index b6818e3..0000000 --- a/game_manager.tscn +++ /dev/null @@ -1,13 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://ci550n7xhfye1"] - -[ext_resource type="Script" path="res://GameManager.gd" id="1_c0vjc"] -[ext_resource type="PackedScene" uid="uid://ch4g8yusgdhm2" path="res://node_2d.tscn" id="2_sjivl"] -[ext_resource type="PackedScene" uid="uid://p2ptqa1u1n1k" path="res://test scene.tscn" id="2_ysfsu"] - -[node name="GameManager" type="Node2D"] -script = ExtResource("1_c0vjc") -multiplayerScene = ExtResource("2_sjivl") - -[node name="UI" parent="." instance=ExtResource("2_ysfsu")] - -[node name="MultiplayerScene" type="Node2D" parent="."] diff --git a/icon.svg.import b/icon.svg.import index ab2b3e5..8a2812e 100644 --- a/icon.svg.import +++ b/icon.svg.import @@ -2,7 +2,7 @@ importer="texture" type="CompressedTexture2D" -uid="uid://5b23m8jtcy47" +uid="uid://b5644j1scdp4u" path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" metadata={ "vram_texture": false diff --git a/node_2d.tscn b/node_2d.tscn deleted file mode 100644 index 4aaccba..0000000 --- a/node_2d.tscn +++ /dev/null @@ -1,831 +0,0 @@ -[gd_scene load_steps=6 format=3 uid="uid://ch4g8yusgdhm2"] - -[ext_resource type="Texture2D" uid="uid://bkpla8vcdb8o" path="res://Texture/TX Tileset Ground.png" id="1_iivnr"] -[ext_resource type="Script" path="res://SceneManager.gd" id="1_u3ux4"] -[ext_resource type="PackedScene" uid="uid://cexkbcrii6gsh" path="res://player.tscn" id="3_cbm6k"] - -[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_ahsbe"] -texture = ExtResource("1_iivnr") -texture_region_size = Vector2i(32, 32) -0:0/0 = 0 -0:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -1:0/0 = 0 -1:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -2:0/0 = 0 -2:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -4:0/0 = 0 -4:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:0/0 = 0 -5:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -9:0/0 = 0 -9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -10:0/0 = 0 -10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -18:0/0 = 0 -19:0/0 = 0 -20:0/0 = 0 -21:0/0 = 0 -24:0/0 = 0 -25:0/0 = 0 -26:0/0 = 0 -27:0/0 = 0 -0:1/0 = 0 -0:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -1:1/0 = 0 -2:1/0 = 0 -2:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -4:1/0 = 0 -4:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -5:1/0 = 0 -5:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -18:1/0 = 0 -19:1/0 = 0 -20:1/0 = 0 -21:1/0 = 0 -24:1/0 = 0 -25:1/0 = 0 -26:1/0 = 0 -27:1/0 = 0 -0:2/0 = 0 -0:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -1:2/0 = 0 -1:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -2:2/0 = 0 -2:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -24:2/0 = 0 -25:2/0 = 0 -26:2/0 = 0 -27:2/0 = 0 -4:3/0 = 0 -4:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -8:3/0 = 0 -8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -9:3/0 = 0 -9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -10:3/0 = 0 -10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:3/0 = 0 -11:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -14:3/0 = 0 -14:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:3/0 = 0 -15:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -24:3/0 = 0 -25:3/0 = 0 -26:3/0 = 0 -27:3/0 = 0 -0:4/0 = 0 -0:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -1:4/0 = 0 -1:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -2:4/0 = 0 -2:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -0:5/0 = 0 -0:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -2:5/0 = 0 -2:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -4:5/0 = 0 -4:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -8:6/0 = 0 -8:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -12:6/0 = 0 -13:6/0 = 0 -14:6/0 = 0 -15:6/0 = 0 -15:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, -0.0890903, -0.97999, 16, 16, -16, 16, -16, -16) -16:6/0 = 0 -17:6/0 = 0 -18:6/0 = 0 -19:6/0 = 0 -20:6/0 = 0 -21:6/0 = 0 -22:6/0 = 0 -23:6/0 = 0 -24:6/0 = 0 -25:6/0 = 0 -26:6/0 = 0 -27:6/0 = 0 -28:6/0 = 0 -29:6/0 = 0 -30:6/0 = 0 -31:6/0 = 0 -8:7/0 = 0 -8:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -9:7/0 = 0 -9:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -12:7/0 = 0 -13:7/0 = 0 -14:7/0 = 0 -15:7/0 = 0 -15:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:7/0 = 0 -17:7/0 = 0 -18:7/0 = 0 -19:7/0 = 0 -20:7/0 = 0 -21:7/0 = 0 -22:7/0 = 0 -23:7/0 = 0 -24:7/0 = 0 -25:7/0 = 0 -26:7/0 = 0 -27:7/0 = 0 -28:7/0 = 0 -29:7/0 = 0 -30:7/0 = 0 -31:7/0 = 0 -0:8/0 = 0 -0:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -2:8/0 = 0 -2:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -4:8/0 = 0 -4:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:8/0 = 0 -17:8/0 = 0 -20:8/0 = 0 -21:8/0 = 0 -22:8/0 = 0 -23:8/0 = 0 -24:8/0 = 0 -25:8/0 = 0 -26:8/0 = 0 -27:8/0 = 0 -30:8/0 = 0 -31:8/0 = 0 -0:9/0 = 0 -0:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -4:9/0 = 0 -4:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:9/0 = 0 -5:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:9/0 = 0 -17:9/0 = 0 -20:9/0 = 0 -21:9/0 = 0 -22:9/0 = 0 -23:9/0 = 0 -24:9/0 = 0 -25:9/0 = 0 -26:9/0 = 0 -27:9/0 = 0 -30:9/0 = 0 -31:9/0 = 0 -0:10/0 = 0 -0:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -4:10/0 = 0 -4:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:10/0 = 0 -8:10/0 = 0 -13:10/0 = 0 -13:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:10/0 = 0 -15:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:10/0 = 0 -17:10/0 = 0 -18:10/0 = 0 -19:10/0 = 0 -20:10/0 = 0 -21:10/0 = 0 -22:10/0 = 0 -23:10/0 = 0 -24:10/0 = 0 -25:10/0 = 0 -26:10/0 = 0 -27:10/0 = 0 -28:10/0 = 0 -29:10/0 = 0 -30:10/0 = 0 -31:10/0 = 0 -4:11/0 = 0 -4:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:11/0 = 0 -8:11/0 = 0 -13:11/0 = 0 -13:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:11/0 = 0 -15:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, -0.97999, -14.5216, -0.97999) -16:11/0 = 0 -17:11/0 = 0 -18:11/0 = 0 -19:11/0 = 0 -20:11/0 = 0 -21:11/0 = 0 -22:11/0 = 0 -23:11/0 = 0 -24:11/0 = 0 -25:11/0 = 0 -26:11/0 = 0 -27:11/0 = 0 -28:11/0 = 0 -29:11/0 = 0 -30:11/0 = 0 -31:11/0 = 0 -0:12/0 = 0 -0:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -4:12/0 = 0 -4:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:12/0 = 0 -8:12/0 = 0 -13:12/0 = 0 -13:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:12/0 = 0 -17:12/0 = 0 -20:12/0 = 0 -21:12/0 = 0 -22:12/0 = 0 -23:12/0 = 0 -24:12/0 = 0 -25:12/0 = 0 -26:12/0 = 0 -27:12/0 = 0 -28:12/0 = 0 -29:12/0 = 0 -30:12/0 = 0 -31:12/0 = 0 -4:13/0 = 0 -4:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:13/0 = 0 -8:13/0 = 0 -9:13/0 = 0 -9:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -12:13/0 = 0 -12:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -13:13/0 = 0 -13:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -14:13/0 = 0 -14:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:13/0 = 0 -15:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:13/0 = 0 -17:13/0 = 0 -20:13/0 = 0 -21:13/0 = 0 -22:13/0 = 0 -23:13/0 = 0 -24:13/0 = 0 -25:13/0 = 0 -26:13/0 = 0 -27:13/0 = 0 -28:13/0 = 0 -29:13/0 = 0 -30:13/0 = 0 -31:13/0 = 0 -8:14/0 = 0 -10:14/0 = 0 -10:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -13:14/0 = 0 -14:14/0 = 0 -15:14/0 = 0 -15:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:14/0 = 0 -17:14/0 = 0 -18:14/0 = 0 -19:14/0 = 0 -20:14/0 = 0 -21:14/0 = 0 -22:14/0 = 0 -23:14/0 = 0 -24:14/0 = 0 -25:14/0 = 0 -26:14/0 = 0 -27:14/0 = 0 -28:14/0 = 0 -29:14/0 = 0 -30:14/0 = 0 -31:14/0 = 0 -8:15/0 = 0 -8:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -9:15/0 = 0 -9:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -10:15/0 = 0 -10:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:15/0 = 0 -11:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -13:15/0 = 0 -13:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -14:15/0 = 0 -14:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:15/0 = 0 -15:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -16:15/0 = 0 -17:15/0 = 0 -18:15/0 = 0 -19:15/0 = 0 -20:15/0 = 0 -21:15/0 = 0 -22:15/0 = 0 -23:15/0 = 0 -24:15/0 = 0 -25:15/0 = 0 -26:15/0 = 0 -27:15/0 = 0 -28:15/0 = 0 -29:15/0 = 0 -30:15/0 = 0 -31:15/0 = 0 -0:16/0 = 0 -1:16/0 = 0 -4:16/0 = 0 -5:16/0 = 0 -8:16/0 = 0 -9:16/0 = 0 -12:16/0 = 0 -13:16/0 = 0 -16:16/0 = 0 -17:16/0 = 0 -18:16/0 = 0 -19:16/0 = 0 -20:16/0 = 0 -21:16/0 = 0 -22:16/0 = 0 -23:16/0 = 0 -24:16/0 = 0 -25:16/0 = 0 -26:16/0 = 0 -27:16/0 = 0 -30:16/0 = 0 -31:16/0 = 0 -0:17/0 = 0 -1:17/0 = 0 -4:17/0 = 0 -5:17/0 = 0 -8:17/0 = 0 -9:17/0 = 0 -12:17/0 = 0 -13:17/0 = 0 -16:17/0 = 0 -17:17/0 = 0 -18:17/0 = 0 -19:17/0 = 0 -20:17/0 = 0 -21:17/0 = 0 -22:17/0 = 0 -23:17/0 = 0 -24:17/0 = 0 -25:17/0 = 0 -26:17/0 = 0 -27:17/0 = 0 -30:17/0 = 0 -31:17/0 = 0 -0:18/0 = 0 -1:18/0 = 0 -8:18/0 = 0 -9:18/0 = 0 -10:18/0 = 0 -11:18/0 = 0 -12:18/0 = 0 -13:18/0 = 0 -14:18/0 = 0 -15:18/0 = 0 -16:18/0 = 0 -17:18/0 = 0 -18:18/0 = 0 -19:18/0 = 0 -20:18/0 = 0 -21:18/0 = 0 -22:18/0 = 0 -23:18/0 = 0 -24:18/0 = 0 -25:18/0 = 0 -26:18/0 = 0 -27:18/0 = 0 -28:18/0 = 0 -29:18/0 = 0 -30:18/0 = 0 -31:18/0 = 0 -0:19/0 = 0 -1:19/0 = 0 -8:19/0 = 0 -9:19/0 = 0 -10:19/0 = 0 -11:19/0 = 0 -12:19/0 = 0 -13:19/0 = 0 -14:19/0 = 0 -15:19/0 = 0 -16:19/0 = 0 -17:19/0 = 0 -18:19/0 = 0 -19:19/0 = 0 -20:19/0 = 0 -21:19/0 = 0 -22:19/0 = 0 -23:19/0 = 0 -24:19/0 = 0 -25:19/0 = 0 -26:19/0 = 0 -27:19/0 = 0 -28:19/0 = 0 -29:19/0 = 0 -30:19/0 = 0 -31:19/0 = 0 -0:20/0 = 0 -1:20/0 = 0 -8:20/0 = 0 -9:20/0 = 0 -10:20/0 = 0 -11:20/0 = 0 -12:20/0 = 0 -13:20/0 = 0 -14:20/0 = 0 -15:20/0 = 0 -16:20/0 = 0 -17:20/0 = 0 -22:20/0 = 0 -23:20/0 = 0 -26:20/0 = 0 -27:20/0 = 0 -30:20/0 = 0 -31:20/0 = 0 -0:21/0 = 0 -1:21/0 = 0 -8:21/0 = 0 -9:21/0 = 0 -10:21/0 = 0 -11:21/0 = 0 -12:21/0 = 0 -13:21/0 = 0 -14:21/0 = 0 -15:21/0 = 0 -16:21/0 = 0 -17:21/0 = 0 -22:21/0 = 0 -23:21/0 = 0 -26:21/0 = 0 -27:21/0 = 0 -30:21/0 = 0 -31:21/0 = 0 -8:22/0 = 0 -9:22/0 = 0 -10:22/0 = 0 -11:22/0 = 0 -12:22/0 = 0 -13:22/0 = 0 -14:22/0 = 0 -15:22/0 = 0 -16:22/0 = 0 -17:22/0 = 0 -20:22/0 = 0 -21:22/0 = 0 -22:22/0 = 0 -23:22/0 = 0 -26:22/0 = 0 -27:22/0 = 0 -30:22/0 = 0 -31:22/0 = 0 -8:23/0 = 0 -9:23/0 = 0 -10:23/0 = 0 -11:23/0 = 0 -12:23/0 = 0 -13:23/0 = 0 -14:23/0 = 0 -15:23/0 = 0 -16:23/0 = 0 -17:23/0 = 0 -20:23/0 = 0 -21:23/0 = 0 -22:23/0 = 0 -23:23/0 = 0 -26:23/0 = 0 -27:23/0 = 0 -30:23/0 = 0 -0:24/0 = 0 -1:24/0 = 0 -2:24/0 = 0 -3:24/0 = 0 -4:24/0 = 0 -5:24/0 = 0 -8:24/0 = 0 -9:24/0 = 0 -10:24/0 = 0 -11:24/0 = 0 -12:24/0 = 0 -13:24/0 = 0 -14:24/0 = 0 -15:24/0 = 0 -16:24/0 = 0 -17:24/0 = 0 -20:24/0 = 0 -21:24/0 = 0 -26:24/0 = 0 -27:24/0 = 0 -0:25/0 = 0 -1:25/0 = 0 -2:25/0 = 0 -3:25/0 = 0 -4:25/0 = 0 -5:25/0 = 0 -8:25/0 = 0 -9:25/0 = 0 -10:25/0 = 0 -11:25/0 = 0 -12:25/0 = 0 -13:25/0 = 0 -14:25/0 = 0 -15:25/0 = 0 -16:25/0 = 0 -17:25/0 = 0 -20:25/0 = 0 -21:25/0 = 0 -26:25/0 = 0 -27:25/0 = 0 -8:26/0 = 0 -9:26/0 = 0 -10:26/0 = 0 -11:26/0 = 0 -12:26/0 = 0 -13:26/0 = 0 -14:26/0 = 0 -15:26/0 = 0 -16:26/0 = 0 -17:26/0 = 0 -18:26/0 = 0 -19:26/0 = 0 -20:26/0 = 0 -21:26/0 = 0 -22:26/0 = 0 -23:26/0 = 0 -24:26/0 = 0 -25:26/0 = 0 -26:26/0 = 0 -27:26/0 = 0 -28:26/0 = 0 -29:26/0 = 0 -30:26/0 = 0 -31:26/0 = 0 -8:27/0 = 0 -9:27/0 = 0 -10:27/0 = 0 -11:27/0 = 0 -12:27/0 = 0 -13:27/0 = 0 -14:27/0 = 0 -15:27/0 = 0 -16:27/0 = 0 -17:27/0 = 0 -18:27/0 = 0 -19:27/0 = 0 -20:27/0 = 0 -21:27/0 = 0 -22:27/0 = 0 -23:27/0 = 0 -24:27/0 = 0 -25:27/0 = 0 -26:27/0 = 0 -27:27/0 = 0 -28:27/0 = 0 -29:27/0 = 0 -30:27/0 = 0 -31:27/0 = 0 -0:28/0 = 0 -1:28/0 = 0 -2:28/0 = 0 -3:28/0 = 0 -4:28/0 = 0 -5:28/0 = 0 -8:28/0 = 0 -9:28/0 = 0 -12:28/0 = 0 -13:28/0 = 0 -14:28/0 = 0 -15:28/0 = 0 -16:28/0 = 0 -17:28/0 = 0 -20:28/0 = 0 -21:28/0 = 0 -26:28/0 = 0 -27:28/0 = 0 -28:28/0 = 0 -29:28/0 = 0 -30:28/0 = 0 -31:28/0 = 0 -0:29/0 = 0 -1:29/0 = 0 -2:29/0 = 0 -3:29/0 = 0 -4:29/0 = 0 -5:29/0 = 0 -8:29/0 = 0 -9:29/0 = 0 -12:29/0 = 0 -13:29/0 = 0 -14:29/0 = 0 -15:29/0 = 0 -16:29/0 = 0 -17:29/0 = 0 -20:29/0 = 0 -21:29/0 = 0 -26:29/0 = 0 -27:29/0 = 0 -28:29/0 = 0 -29:29/0 = 0 -30:29/0 = 0 -31:29/0 = 0 -8:30/0 = 0 -9:30/0 = 0 -10:30/0 = 0 -11:30/0 = 0 -12:30/0 = 0 -13:30/0 = 0 -14:30/0 = 0 -15:30/0 = 0 -16:30/0 = 0 -17:30/0 = 0 -18:30/0 = 0 -19:30/0 = 0 -20:30/0 = 0 -21:30/0 = 0 -22:30/0 = 0 -23:30/0 = 0 -26:30/0 = 0 -27:30/0 = 0 -28:30/0 = 0 -29:30/0 = 0 -30:30/0 = 0 -31:30/0 = 0 -8:31/0 = 0 -9:31/0 = 0 -10:31/0 = 0 -11:31/0 = 0 -12:31/0 = 0 -13:31/0 = 0 -14:31/0 = 0 -15:31/0 = 0 -16:31/0 = 0 -17:31/0 = 0 -18:31/0 = 0 -19:31/0 = 0 -20:31/0 = 0 -21:31/0 = 0 -22:31/0 = 0 -23:31/0 = 0 -26:31/0 = 0 -27:31/0 = 0 -28:31/0 = 0 -29:31/0 = 0 -30:31/0 = 0 -31:31/0 = 0 -0:6/0 = 0 -0:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -1:6/0 = 0 -1:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -2:6/0 = 0 -2:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -7:0/0 = 0 -7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -12:0/0 = 0 -12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -13:0/0 = 0 -13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -7:1/0 = 0 -7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -12:1/0 = 0 -12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -13:1/0 = 0 -13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(16, -16, 16, 16, -16, 16, -16, -16) -6:3/0 = 0 -6:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -7:3/0 = 0 -7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -12:3/0 = 0 -12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -13:3/0 = 0 -13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -8:4/0 = 0 -8:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -10:4/0 = 0 -10:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:4/0 = 0 -12:4/0 = 0 -13:4/0 = 0 -13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:4/0 = 0 -15:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:5/0 = 0 -6:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -7:5/0 = 0 -7:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -8:5/0 = 0 -8:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -9:5/0 = 0 -9:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -10:5/0 = 0 -10:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:5/0 = 0 -12:5/0 = 0 -13:5/0 = 0 -13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -14:5/0 = 0 -14:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:5/0 = 0 -15:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -4:6/0 = 0 -4:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:6/0 = 0 -6:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -7:6/0 = 0 -10:6/0 = 0 -10:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:6/0 = 0 -4:7/0 = 0 -4:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:7/0 = 0 -5:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:7/0 = 0 -6:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -7:7/0 = 0 -7:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -10:7/0 = 0 -10:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:7/0 = 0 -6:8/0 = 0 -6:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -8:8/0 = 0 -8:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -9:8/0 = 0 -10:8/0 = 0 -11:8/0 = 0 -12:8/0 = 0 -13:8/0 = 0 -15:8/0 = 0 -15:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:9/0 = 0 -6:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -7:9/0 = 0 -7:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -8:9/0 = 0 -8:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -9:9/0 = 0 -10:9/0 = 0 -11:9/0 = 0 -12:9/0 = 0 -13:9/0 = 0 -13:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -14:9/0 = 0 -14:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -15:9/0 = 0 -15:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:10/0 = 0 -7:10/0 = 0 -11:10/0 = 0 -11:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:11/0 = 0 -7:11/0 = 0 -10:11/0 = 0 -10:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:11/0 = 0 -11:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:12/0 = 0 -7:12/0 = 0 -10:12/0 = 0 -10:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:13/0 = 0 -7:13/0 = 0 -10:13/0 = 0 -10:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -11:13/0 = 0 -11:13/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -0:14/0 = 0 -0:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -1:14/0 = 0 -1:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -2:14/0 = 0 -2:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -4:14/0 = 0 -4:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:14/0 = 0 -7:14/0 = 0 -4:15/0 = 0 -4:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -5:15/0 = 0 -5:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -6:15/0 = 0 -6:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -7:15/0 = 0 -7:15/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -1:12/0 = 0 -1:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) -2:12/0 = 0 -2:12/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) - -[sub_resource type="TileSet" id="TileSet_vfn0p"] -tile_size = Vector2i(32, 32) -physics_layer_0/collision_layer = 1 -sources/0 = SubResource("TileSetAtlasSource_ahsbe") - -[node name="Node2D" type="Node2D"] -script = ExtResource("1_u3ux4") -playerScene = ExtResource("3_cbm6k") - -[node name="TileMap" type="TileMap" parent="."] -position = Vector2(98, 1) -tile_set = SubResource("TileSet_vfn0p") -format = 2 -layer_0/tile_data = PackedInt32Array(983044, 393216, 3, 983045, 458752, 3, 983046, 524288, 3, 983047, 589824, 3, 983048, 655360, 3, 983049, 720896, 3, 983050, 786432, 3, 983051, 65536, 0, 983052, 65536, 0, 983053, 65536, 0, 983054, 65536, 0, 983055, 65536, 0, 983056, 65536, 0, 983057, 655360, 3, 983058, 720896, 3, 983059, 786432, 3, 983060, 851968, 3, 983061, 917504, 3, 983062, 983040, 3, 1048593, 655360, 4, 1114129, 655360, 5, 1179665, 655360, 6, 1245201, 655360, 4, 1310737, 655360, 4, 1376273, 655360, 4, 1048594, 720896, 4, 1114130, 720896, 5, 1179666, 720896, 6, 1245202, 720896, 7, 1310738, 720896, 8, 1376274, 720896, 9, 1048595, 786432, 4, 1114131, 786432, 5, 1179667, 786432, 6, 1245203, 786432, 7, 1310739, 786432, 8, 1376275, 786432, 9, 1048596, 851968, 4, 1114132, 524288, 12, 1179668, 524288, 12, 1245204, 524288, 12, 1310740, 524288, 12, 1376276, 851968, 9, 1048584, 655360, 4, 1114120, 655360, 5, 1179656, 655360, 6, 1245192, 655360, 4, 1310728, 655360, 4, 1376264, 262144, 13, 1048585, 720896, 4, 1114121, 720896, 5, 1179657, 720896, 6, 1245193, 720896, 7, 1310729, 720896, 8, 1376265, 720896, 9, 1048586, 786432, 4, 1114122, 786432, 5, 1179658, 786432, 6, 1245194, 786432, 7, 1310730, 786432, 8, 1376266, 786432, 9, 1048587, 851968, 4, 1114123, 524288, 12, 1179659, 524288, 12, 1245195, 524288, 12, 1310731, 851968, 8, 1376267, 851968, 9) - -[node name="SpawnPoint" type="Node2D" parent="." groups=["SpawnPoint"]] -position = Vector2(279, 412) - -[node name="SpawnPoint2" type="Node2D" parent="." groups=["SpawnPoint"]] -position = Vector2(738, 412) diff --git a/player.tscn b/player.tscn deleted file mode 100644 index c6823f9..0000000 --- a/player.tscn +++ /dev/null @@ -1,20 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://cexkbcrii6gsh"] - -[ext_resource type="Script" path="res://Player.gd" id="1_24elr"] -[ext_resource type="Texture2D" uid="uid://5b23m8jtcy47" path="res://icon.svg" id="2_ygr70"] - -[sub_resource type="RectangleShape2D" id="RectangleShape2D_cm78x"] -size = Vector2(48, 49) - -[node name="Player" type="CharacterBody2D"] -position = Vector2(280, 385) -script = ExtResource("1_24elr") - -[node name="Sprite2D" type="Sprite2D" parent="."] -position = Vector2(-1, 0.499998) -scale = Vector2(0.375, 0.367187) -texture = ExtResource("2_ygr70") - -[node name="CollisionShape2D" type="CollisionShape2D" parent="."] -position = Vector2(-1, 0.5) -shape = SubResource("RectangleShape2D_cm78x") diff --git a/project.godot b/project.godot index 9e642e8..3a8c7e2 100644 --- a/project.godot +++ b/project.godot @@ -11,13 +11,30 @@ config_version=5 [application] config/name="Nakama Integration Tutorial" -config/features=PackedStringArray("4.3", "Forward Plus") +config/features=PackedStringArray("4.5", "Forward Plus") config/icon="res://icon.svg" [autoload] Nakama="*res://addons/com.heroiclabs.nakama/Nakama.gd" +NakamaManager="*res://Nakama/NakamaManager.tscn" +BetterTerrain="*res://addons/better-terrain/BetterTerrain.gd" +NotificationContainer="*res://NotificationContainer/NotificationContainer.tscn" + +[display] + +window/stretch/mode="viewport" +window/stretch/aspect="ignore" [dotnet] project/assembly_name="Nakama Integration Tutorial" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/better-terrain/plugin.cfg") + +[layer_names] + +2d_physics/layer_1="World" +2d_physics/layer_2="EndFlag" diff --git a/test scene.tscn b/test scene.tscn deleted file mode 100644 index 7c71974..0000000 --- a/test scene.tscn +++ /dev/null @@ -1,805 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://p2ptqa1u1n1k"] - -[ext_resource type="Script" path="res://Client.gd" id="1_mtcsr"] - -[node name="Control" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_mtcsr") - -[node name="Panel" type="Panel" parent="."] -visible = false -layout_mode = 0 -offset_left = 802.0 -offset_top = 102.0 -offset_right = 1079.0 -offset_bottom = 197.0 - -[node name="UserAccountLabel" type="Label" parent="Panel"] -layout_mode = 0 -offset_left = 41.0 -offset_top = 39.0 -offset_right = 146.0 -offset_bottom = 62.0 -text = "User Account" - -[node name="UserAccountText" type="Label" parent="Panel"] -layout_mode = 0 -offset_left = 159.0 -offset_top = 39.0 -offset_right = 264.0 -offset_bottom = 62.0 -text = "user account" - -[node name="DisplayName" type="Label" parent="Panel"] -layout_mode = 0 -offset_left = 41.0 -offset_top = 63.0 -offset_right = 148.0 -offset_bottom = 86.0 -text = "Display Name" - -[node name="DisplayNameText" type="Label" parent="Panel"] -layout_mode = 0 -offset_left = 159.0 -offset_top = 63.0 -offset_right = 264.0 -offset_bottom = 86.0 -text = "user account" - -[node name="Panel2" type="Panel" parent="."] -layout_mode = 0 -offset_left = 13.0 -offset_top = 13.0 -offset_right = 400.0 -offset_bottom = 182.0 - -[node name="Label" type="Label" parent="Panel2"] -layout_mode = 0 -offset_left = 34.0 -offset_top = 20.0 -offset_right = 108.0 -offset_bottom = 43.0 -text = "Email" - -[node name="Label2" type="Label" parent="Panel2"] -layout_mode = 0 -offset_left = 34.0 -offset_top = 69.0 -offset_right = 108.0 -offset_bottom = 92.0 -text = "Password" - -[node name="EmailInput" type="LineEdit" parent="Panel2"] -layout_mode = 0 -offset_left = 122.0 -offset_top = 23.0 -offset_right = 347.0 -offset_bottom = 54.0 -text = "test@gmail.com" - -[node name="PasswordInput" type="LineEdit" parent="Panel2"] -layout_mode = 0 -offset_left = 120.0 -offset_top = 63.0 -offset_right = 347.0 -offset_bottom = 94.0 -text = "password" - -[node name="LoginButton" type="Button" parent="Panel2"] -layout_mode = 0 -offset_left = 222.0 -offset_top = 114.0 -offset_right = 352.0 -offset_bottom = 157.0 -text = "Submit" - -[node name="Data Pannel" type="Panel" parent="."] -visible = false -layout_mode = 0 -offset_left = 9.0 -offset_top = 508.0 -offset_right = 241.0 -offset_bottom = 637.0 - -[node name="StoreData" type="Button" parent="Data Pannel"] -layout_mode = 0 -offset_left = 36.0 -offset_top = 11.0 -offset_right = 199.0 -offset_bottom = 42.0 -text = "Store Data" - -[node name="GetData" type="Button" parent="Data Pannel"] -layout_mode = 0 -offset_left = 36.0 -offset_top = 49.0 -offset_right = 137.0 -offset_bottom = 71.0 -text = "Get Data from Store" - -[node name="ListData" type="Button" parent="Data Pannel"] -layout_mode = 0 -offset_left = 36.0 -offset_top = 90.0 -offset_right = 200.0 -offset_bottom = 121.0 -text = "List Data from Store" - -[node name="Panel3" type="Panel" parent="."] -visible = false -layout_mode = 0 -offset_left = 15.0 -offset_top = 46.0 -offset_right = 268.0 -offset_bottom = 269.0 - -[node name="JoinCreateMatch" type="Button" parent="Panel3"] -layout_mode = 0 -offset_left = 120.0 -offset_top = 69.0 -offset_right = 230.0 -offset_bottom = 104.0 -text = "Join/Create" - -[node name="MatchName" type="LineEdit" parent="Panel3"] -layout_mode = 0 -offset_left = 99.0 -offset_top = 22.0 -offset_right = 243.0 -offset_bottom = 53.0 - -[node name="Label" type="Label" parent="Panel3"] -layout_mode = 0 -offset_left = 9.0 -offset_top = 11.0 -offset_right = 91.0 -offset_bottom = 60.0 -text = "Name Of -Match" - -[node name="Ping" type="Button" parent="Panel3"] -layout_mode = 0 -offset_left = 9.0 -offset_top = 75.0 -offset_right = 102.0 -offset_bottom = 106.0 -text = "Ping" - -[node name="Matchmaking" type="Button" parent="Panel3"] -layout_mode = 0 -offset_left = 51.0 -offset_top = 131.0 -offset_right = 207.0 -offset_bottom = 166.0 -text = "Start Matchmaking" - -[node name="Button" type="Button" parent="Panel3"] -layout_mode = 0 -offset_left = 53.0 -offset_top = 175.0 -offset_right = 201.0 -offset_bottom = 215.0 -text = "Start Game" - -[node name="Panel4" type="Panel" parent="."] -layout_mode = 0 -offset_left = 670.0 -offset_top = -2.0 -offset_right = 894.0 -offset_bottom = 162.0 - -[node name="BlockFriends" type="Button" parent="Panel4"] -layout_mode = 0 -offset_left = 1.0 -offset_top = 126.0 -offset_right = 102.0 -offset_bottom = 157.0 -text = "Block Friend" - -[node name="GetFriends" type="Button" parent="Panel4"] -layout_mode = 0 -offset_left = 3.0 -offset_top = 84.0 -offset_right = 104.0 -offset_bottom = 115.0 -text = "Get Friends" - -[node name="AddFriendText" type="LineEdit" parent="Panel4"] -layout_mode = 0 -offset_left = 20.0 -offset_top = 32.0 -offset_right = 201.0 -offset_bottom = 63.0 -placeholder_text = "Name of friend" - -[node name="AddFriend" type="Button" parent="Panel4"] -layout_mode = 0 -offset_left = 106.0 -offset_top = 84.0 -offset_right = 207.0 -offset_bottom = 115.0 -text = "Add friend" - -[node name="RemoveFriend" type="Button" parent="Panel4/AddFriend"] -layout_mode = 0 -offset_left = -4.0 -offset_top = 41.0 -offset_right = 105.0 -offset_bottom = 72.0 -text = "Delete friend" - -[node name="Panel4" type="Panel" parent="Panel4"] -layout_mode = 0 -offset_left = 246.0 -offset_top = 9.0 -offset_right = 470.0 -offset_bottom = 253.0 - -[node name="VBoxContainer" type="VBoxContainer" parent="Panel4/Panel4"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = 26.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="RichTextLabel" type="RichTextLabel" parent="Panel4/Panel4"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 30.0 -grow_horizontal = 2 -bbcode_enabled = true -text = "[center][b]Friends" - -[node name="Panel5" type="Panel" parent="."] -visible = false -layout_mode = 0 -offset_left = 10.0 -offset_top = 274.0 -offset_right = 234.0 -offset_bottom = 438.0 - -[node name="CloseGroup" type="Button" parent="Panel5"] -layout_mode = 0 -offset_left = 1.0 -offset_top = 126.0 -offset_right = 102.0 -offset_bottom = 157.0 -text = "Close Group" - -[node name="GetGroupMemebers" type="Button" parent="Panel5"] -layout_mode = 0 -offset_left = 3.0 -offset_top = 84.0 -offset_right = 104.0 -offset_bottom = 115.0 -text = "Get Group" - -[node name="GroupName" type="LineEdit" parent="Panel5"] -layout_mode = 0 -offset_left = 20.0 -offset_top = 32.0 -offset_right = 201.0 -offset_bottom = 63.0 -placeholder_text = "Name of group" - -[node name="CreateGroup" type="Button" parent="Panel5"] -layout_mode = 0 -offset_left = 106.0 -offset_top = 84.0 -offset_right = 207.0 -offset_bottom = 115.0 -text = "Create Group" - -[node name="DeleteGroup" type="Button" parent="Panel5/CreateGroup"] -layout_mode = 0 -offset_left = -4.0 -offset_top = 41.0 -offset_right = 105.0 -offset_bottom = 72.0 -text = "Delete Group" - -[node name="Panel4" type="Panel" parent="Panel5"] -layout_mode = 0 -offset_left = 246.0 -offset_top = 9.0 -offset_right = 470.0 -offset_bottom = 253.0 - -[node name="GroupVBox" type="VBoxContainer" parent="Panel5/Panel4"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = 26.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="RichTextLabel" type="RichTextLabel" parent="Panel5/Panel4"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 30.0 -grow_horizontal = 2 -bbcode_enabled = true -text = "[center][b]Group Memebers" - -[node name="Panel6" type="Panel" parent="."] -visible = false -layout_mode = 0 -offset_left = 546.0 -offset_top = 250.0 -offset_right = 945.0 -offset_bottom = 658.0 - -[node name="Label" type="Label" parent="Panel6"] -layout_mode = 0 -offset_left = 14.0 -offset_top = 26.0 -offset_right = 61.0 -offset_bottom = 49.0 -text = "Name" - -[node name="Label2" type="Label" parent="Panel6"] -layout_mode = 0 -offset_left = 16.0 -offset_top = 66.0 -offset_right = 105.0 -offset_bottom = 89.0 -text = "Description" - -[node name="GroupName" type="LineEdit" parent="Panel6"] -layout_mode = 0 -offset_left = 113.0 -offset_top = 26.0 -offset_right = 298.0 -offset_bottom = 57.0 - -[node name="GroupDesc" type="LineEdit" parent="Panel6"] -layout_mode = 0 -offset_left = 114.0 -offset_top = 63.0 -offset_right = 298.0 -offset_bottom = 94.0 - -[node name="GroupQuery" type="LineEdit" parent="Panel6"] -layout_mode = 0 -offset_left = 157.0 -offset_top = 212.0 -offset_right = 298.0 -offset_bottom = 243.0 -placeholder_text = "Group Query" - -[node name="CreateGroup" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 153.0 -offset_top = 107.0 -offset_right = 265.0 -offset_bottom = 138.0 -text = "Create Group" - -[node name="AddUserToGroup" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 28.0 -offset_top = 107.0 -offset_right = 140.0 -offset_bottom = 138.0 -text = "Join Group" - -[node name="AddUserToGroup2" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 30.0 -offset_top = 147.0 -offset_right = 142.0 -offset_bottom = 201.0 -text = "Add User -To Group" - -[node name="List Groups" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 30.0 -offset_top = 208.0 -offset_right = 142.0 -offset_bottom = 262.0 -text = "List Groups" - -[node name="CheckButton" type="CheckButton" parent="Panel6"] -layout_mode = 0 -offset_left = 173.0 -offset_top = 164.0 -offset_right = 263.0 -offset_bottom = 195.0 -text = "Open" - -[node name="Panel" type="Panel" parent="Panel6"] -layout_mode = 0 -offset_left = 412.0 -offset_top = 4.0 -offset_right = 656.0 -offset_bottom = 273.0 - -[node name="VBoxContainer" type="VBoxContainer" parent="Panel6/Panel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="UserToManage" type="LineEdit" parent="Panel6"] -layout_mode = 0 -offset_left = 33.0 -offset_top = 277.0 -offset_right = 276.0 -offset_bottom = 308.0 -placeholder_text = "User To Manage" - -[node name="PromoteUser" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 25.0 -offset_top = 319.0 -offset_right = 129.0 -offset_bottom = 355.0 -text = "Promote" - -[node name="KickUser" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 144.0 -offset_top = 320.0 -offset_right = 248.0 -offset_bottom = 356.0 -text = "Kick" - -[node name="DemoteUser" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 23.0 -offset_top = 364.0 -offset_right = 127.0 -offset_bottom = 400.0 -text = "Demote" - -[node name="BanUser" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 147.0 -offset_top = 364.0 -offset_right = 251.0 -offset_bottom = 400.0 -text = "Ban" - -[node name="LeaveGroup" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 285.0 -offset_top = 106.0 -offset_right = 391.0 -offset_bottom = 142.0 -text = "Leave Group" - -[node name="DeleteGroup" type="Button" parent="Panel6"] -layout_mode = 0 -offset_left = 278.0 -offset_top = 158.0 -offset_right = 384.0 -offset_bottom = 194.0 -text = "Leave Group" - -[node name="Panel7" type="Panel" parent="."] -visible = false -layout_mode = 0 -offset_left = 20.0 -offset_top = 355.0 -offset_right = 229.0 -offset_bottom = 613.0 - -[node name="JoinGroupChatRoom" type="Button" parent="Panel7"] -layout_mode = 0 -offset_left = 59.0 -offset_top = 109.0 -offset_right = 189.0 -offset_bottom = 153.0 -text = "Join Group Chat" - -[node name="JoinChatRoom" type="Button" parent="Panel7"] -layout_mode = 0 -offset_left = 63.0 -offset_top = 57.0 -offset_right = 173.0 -offset_bottom = 101.0 -text = "Join Chat" - -[node name="ChatName" type="LineEdit" parent="Panel7"] -layout_mode = 0 -offset_left = 19.0 -offset_top = 13.0 -offset_right = 199.0 -offset_bottom = 44.0 -placeholder_text = "Chat Name" - -[node name="Chat" type="Panel" parent="Panel7"] -layout_mode = 0 -offset_left = 222.0 -offset_top = 3.0 -offset_right = 536.0 -offset_bottom = 255.0 - -[node name="ChatText" type="LineEdit" parent="Panel7/Chat"] -layout_mode = 1 -anchors_preset = 12 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = -31.0 -offset_right = -82.0 -grow_horizontal = 2 -grow_vertical = 0 -placeholder_text = "Chat Text Here" - -[node name="SubmitChat" type="Button" parent="Panel7/Chat"] -layout_mode = 1 -anchors_preset = 3 -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -75.0 -offset_top = -33.0 -grow_horizontal = 0 -grow_vertical = 0 -text = ">" - -[node name="TabContainer" type="TabContainer" parent="Panel7/Chat"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = -31.0 -offset_bottom = -33.0 -grow_horizontal = 2 -grow_vertical = 2 -current_tab = 0 - -[node name="username" type="TextEdit" parent="Panel7/Chat/TabContainer"] -layout_mode = 2 -editable = false -wrap_mode = 1 -metadata/_tab_index = 0 - -[node name="JoinDirectChat" type="Button" parent="Panel7"] -layout_mode = 0 -offset_left = 57.0 -offset_top = 161.0 -offset_right = 187.0 -offset_bottom = 205.0 -text = "Join Direct Chat" - -[node name="Panel8" type="Panel" parent="."] -visible = false -layout_mode = 0 -offset_left = 25.0 -offset_top = 302.0 -offset_right = 218.0 -offset_bottom = 428.0 - -[node name="CreateParty" type="Button" parent="Panel8"] -layout_mode = 0 -offset_left = 16.0 -offset_top = 14.0 -offset_right = 125.0 -offset_bottom = 51.0 -text = "Create Party" - -[node name="Panel" type="Panel" parent="Panel8"] -layout_mode = 0 -offset_left = 202.0 -offset_right = 422.0 -offset_bottom = 150.0 - -[node name="VBoxContainer" type="VBoxContainer" parent="Panel8/Panel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 9.0 -offset_top = 7.0 -offset_right = -13.0 -offset_bottom = -16.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="Panel2" type="Panel" parent="Panel8"] -visible = false -layout_mode = 0 -offset_left = -6.0 -offset_top = 139.0 -offset_right = 178.0 -offset_bottom = 257.0 - -[node name="Label" type="Label" parent="Panel8/Panel2"] -layout_mode = 0 -offset_left = 51.0 -offset_top = 24.0 -offset_right = 141.0 -offset_bottom = 47.0 -text = "Join Party?" - -[node name="JoinPartyNo" type="Button" parent="Panel8/Panel2"] -layout_mode = 0 -offset_left = 19.0 -offset_top = 72.0 -offset_right = 78.0 -offset_bottom = 103.0 -text = "No" - -[node name="JoinPartyYes" type="Button" parent="Panel8/Panel2"] -layout_mode = 0 -offset_left = 111.0 -offset_top = 73.0 -offset_right = 170.0 -offset_bottom = 104.0 -text = "Yes" - -[node name="TradeSystem" type="Panel" parent="."] -layout_mode = 0 -offset_left = 22.0 -offset_top = 202.0 -offset_right = 810.0 -offset_bottom = 632.0 - -[node name="AddItemToInventory" type="Button" parent="TradeSystem"] -layout_mode = 0 -offset_left = 29.0 -offset_top = 27.0 -offset_right = 176.0 -offset_bottom = 85.0 -text = "Ping RPC" - -[node name="GetInventory" type="Button" parent="TradeSystem"] -layout_mode = 0 -offset_left = 29.0 -offset_top = 99.0 -offset_right = 176.0 -offset_bottom = 157.0 -text = "Get Inventory" - -[node name="SendTradeOffer" type="Button" parent="TradeSystem"] -layout_mode = 0 -offset_left = 29.0 -offset_top = 163.0 -offset_right = 176.0 -offset_bottom = 221.0 -text = "Send Trade Offer" - -[node name="GetTradeOffers" type="Button" parent="TradeSystem"] -layout_mode = 0 -offset_left = 29.0 -offset_top = 228.0 -offset_right = 176.0 -offset_bottom = 286.0 -text = "Get Trade Offers" - -[node name="AcceptTradeOffer2" type="Button" parent="TradeSystem"] -layout_mode = 0 -offset_left = 30.0 -offset_top = 352.0 -offset_right = 183.0 -offset_bottom = 410.0 -text = "Accept Trade Offer" - -[node name="CancelTradeOffer" type="Button" parent="TradeSystem"] -layout_mode = 0 -offset_left = 29.0 -offset_top = 291.0 -offset_right = 176.0 -offset_bottom = 349.0 -text = "Cancel Trade Offer" - -[node name="Panel" type="Panel" parent="TradeSystem"] -layout_mode = 0 -offset_left = 482.0 -offset_top = 20.0 -offset_right = 774.0 -offset_bottom = 356.0 - -[node name="Panel" type="Panel" parent="TradeSystem/Panel"] -layout_mode = 0 -offset_right = 292.0 -offset_bottom = 155.0 - -[node name="Panel2" type="Panel" parent="TradeSystem/Panel"] -layout_mode = 0 -offset_top = 178.0 -offset_right = 292.0 -offset_bottom = 333.0 - -[node name="FriendNameLabel" type="Label" parent="TradeSystem/Panel"] -layout_mode = 0 -offset_top = 155.0 -offset_right = 94.0 -offset_bottom = 178.0 -text = "Other User: " - -[node name="Label2" type="Label" parent="TradeSystem/Panel"] -layout_mode = 0 -offset_left = 4.0 -offset_top = -22.0 -offset_right = 98.0 -offset_bottom = 1.0 -text = "My Inventory" - -[node name="VBoxContainer" type="VBoxContainer" parent="TradeSystem/Panel"] -layout_mode = 0 -offset_right = 293.0 -offset_bottom = 153.0 - -[node name="VBoxContainer2" type="VBoxContainer" parent="TradeSystem/Panel"] -layout_mode = 0 -offset_top = 183.0 -offset_right = 293.0 -offset_bottom = 336.0 - -[node name="TradeOffers" type="Panel" parent="TradeSystem"] -layout_mode = 0 -offset_left = 196.0 -offset_top = 21.0 -offset_right = 473.0 -offset_bottom = 349.0 - -[node name="Label" type="Label" parent="TradeSystem/TradeOffers"] -layout_mode = 0 -offset_top = -21.0 -offset_right = 40.0 -offset_bottom = 2.0 -text = "Trade Offers" - -[node name="VBoxContainer" type="VBoxContainer" parent="TradeSystem/TradeOffers"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[connection signal="button_down" from="Panel2/LoginButton" to="." method="_on_login_button_button_down"] -[connection signal="button_down" from="Data Pannel/StoreData" to="." method="_on_store_data_button_down"] -[connection signal="button_down" from="Data Pannel/GetData" to="." method="_on_get_data_button_down"] -[connection signal="button_down" from="Data Pannel/ListData" to="." method="_on_list_data_button_down"] -[connection signal="button_down" from="Panel3/JoinCreateMatch" to="." method="_on_join_create_match_button_down"] -[connection signal="button_down" from="Panel3/Ping" to="." method="_on_ping_button_down"] -[connection signal="button_down" from="Panel3/Matchmaking" to="." method="_on_matchmaking_button_down"] -[connection signal="button_down" from="Panel3/Button" to="." method="_on_button_button_down"] -[connection signal="button_down" from="Panel4/BlockFriends" to="." method="_on_block_friends_button_down"] -[connection signal="button_down" from="Panel4/GetFriends" to="." method="_on_get_friends_button_down"] -[connection signal="button_down" from="Panel4/AddFriend" to="." method="_on_add_friend_button_down"] -[connection signal="button_down" from="Panel4/AddFriend/RemoveFriend" to="." method="_on_remove_friend_button_down"] -[connection signal="button_down" from="Panel5/GetGroupMemebers" to="." method="_on_get_group_memebers_button_down"] -[connection signal="button_down" from="Panel5/CreateGroup" to="." method="_on_create_group_button_down"] -[connection signal="button_down" from="Panel6/CreateGroup" to="." method="_on_create_group_button_down"] -[connection signal="button_down" from="Panel6/AddUserToGroup" to="." method="_on_add_user_to_group_button_down"] -[connection signal="button_down" from="Panel6/AddUserToGroup2" to="." method="_on_add_user_to_group_2_button_down"] -[connection signal="button_down" from="Panel6/List Groups" to="." method="_on_list_groups_button_down"] -[connection signal="toggled" from="Panel6/CheckButton" to="." method="_on_check_button_toggled"] -[connection signal="button_down" from="Panel6/PromoteUser" to="." method="_on_promote_user_button_down"] -[connection signal="button_down" from="Panel6/KickUser" to="." method="_on_kick_user_button_down"] -[connection signal="button_down" from="Panel6/DemoteUser" to="." method="_on_demote_user_button_down"] -[connection signal="button_down" from="Panel6/BanUser" to="." method="_on_ban_user_button_down"] -[connection signal="button_down" from="Panel6/LeaveGroup" to="." method="_on_leave_group_button_down"] -[connection signal="button_down" from="Panel6/DeleteGroup" to="." method="_on_delete_group_button_down"] -[connection signal="button_down" from="Panel7/JoinGroupChatRoom" to="." method="_on_join_group_chat_room_button_down"] -[connection signal="button_down" from="Panel7/JoinChatRoom" to="." method="_on_join_chat_room_button_down"] -[connection signal="button_down" from="Panel7/Chat/SubmitChat" to="." method="_on_submit_chat_button_down"] -[connection signal="button_down" from="Panel7/JoinDirectChat" to="." method="_on_join_direct_chat_button_down"] -[connection signal="button_down" from="Panel8/CreateParty" to="." method="_on_create_party_button_down"] -[connection signal="button_down" from="Panel8/Panel2/JoinPartyNo" to="." method="_on_join_party_no_button_down"] -[connection signal="button_down" from="Panel8/Panel2/JoinPartyYes" to="." method="_on_join_party_yes_button_down"] -[connection signal="button_down" from="TradeSystem/AddItemToInventory" to="." method="_on_ping_rpc_button_down"] -[connection signal="button_down" from="TradeSystem/GetInventory" to="." method="_on_get_inventory_button_down"] -[connection signal="button_down" from="TradeSystem/SendTradeOffer" to="." method="_on_send_trade_offer_button_down"] -[connection signal="button_down" from="TradeSystem/GetTradeOffers" to="." method="_on_get_trade_offers_button_down"] -[connection signal="button_down" from="TradeSystem/AcceptTradeOffer2" to="." method="_on_accept_trade_offer_button_down"] -[connection signal="button_down" from="TradeSystem/CancelTradeOffer" to="." method="_on_cancel_trade_offer_button_down"] diff --git a/test scene.tscn7629610203.tmp b/test scene.tscn7629610203.tmp new file mode 100644 index 0000000..3bfe0a2 --- /dev/null +++ b/test scene.tscn7629610203.tmp @@ -0,0 +1,879 @@ +[gd_scene load_steps=13 format=3 uid="uid://p2ptqa1u1n1k"] + +[ext_resource type="Script" uid="uid://bkso6trqnr35v" path="res://Client.gd" id="1_mtcsr"] +[ext_resource type="PackedScene" uid="uid://c25altqf87k2j" path="res://Lobby/FriendHBoxContainer/FriendHBoxContainer.tscn" id="2_cekro"] +[ext_resource type="PackedScene" uid="uid://bg6xgsfypi4gr" path="res://NotificationContainer/NotificationContainer.tscn" id="2_lanui"] +[ext_resource type="PackedScene" uid="uid://cvedwg86rsyp2" path="res://Lobby/UserInformationDisplay/UserInformationDisplay.tscn" id="2_qmkq0"] +[ext_resource type="PackedScene" uid="uid://dd2ekb3yor3hh" path="res://Authentication/LoginPanel/LoginPanel.tscn" id="4_4xlfs"] +[ext_resource type="PackedScene" uid="uid://xq36w5vrpfwb" path="res://Authentication/RegisterPanel/RegisterPanel.tscn" id="5_wys0s"] +[ext_resource type="PackedScene" uid="uid://32gcwea4fq4t" path="res://Popup/PopupBox.tscn" id="7_tv77q"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6qptp"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.140447, 0.140447, 0.140447, 1) + +[sub_resource type="Theme" id="Theme_k1pkr"] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_6qptp") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tmgb8"] +content_margin_left = 7.0 +content_margin_top = 7.0 +content_margin_right = 7.0 +content_margin_bottom = 7.0 +bg_color = Color(0.315076, 0.398015, 0.42524, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u303v"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 15.0 +bg_color = Color(0.139728, 0.149761, 0.185489, 1) +corner_radius_top_left = 15 +corner_radius_top_right = 15 +corner_radius_bottom_right = 15 +corner_radius_bottom_left = 15 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tv77q"] +content_margin_left = 15.0 +content_margin_top = 15.0 +content_margin_right = 15.0 +content_margin_bottom = 5.0 +bg_color = Color(0.0728426, 0.0728427, 0.0728426, 1) + +[node name="Game" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_k1pkr") +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +script = ExtResource("1_mtcsr") +friends_packed_scene = ExtResource("2_cekro") + +[node name="HBox" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="LobbyContainer" type="TabContainer" parent="HBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +current_tab = 5 + +[node name="Friend List" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +size_flags_vertical = 0 +metadata/_tab_index = 0 + +[node name="HBoxContainer" type="HSplitContainer" parent="HBox/LobbyContainer/Friend List"] +layout_mode = 2 + +[node name="VBox" type="VBoxContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="AddFriendText" type="LineEdit" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Name of username" +alignment = 1 + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox"] +layout_mode = 2 +size_flags_vertical = 0 +columns = 2 + +[node name="AddFriend" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Add friend" + +[node name="BlockFriends" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 0 +text = "Block Friend" + +[node name="RemoveFriend" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer"] +layout_mode = 2 +text = "Delete friend" + +[node name="GetFriends" type="Button" parent="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer"] +layout_mode = 2 +text = "Update Friends List" + +[node name="FriendsBox" type="PanelContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer"] +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_tmgb8") + +[node name="VBox" type="VBoxContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox"] +layout_mode = 2 + +[node name="RichTextLabel" type="RichTextLabel" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox"] +layout_mode = 2 +bbcode_enabled = true +text = "[center][b]Friends" +fit_content = true + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_u303v") + +[node name="FriendsContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Friend List/HBoxContainer/FriendsBox/VBox/Panel"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Match Finder" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +custom_minimum_size = Vector2(350, 0) +layout_mode = 2 +size_flags_horizontal = 0 +metadata/_tab_index = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Match Finder"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +layout_mode = 2 +text = "Name Of Match" + +[node name="MatchName" type="LineEdit" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/Match Finder/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +columns = 2 + +[node name="JoinCreateMatch" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Join/Create" + +[node name="Ping" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Ping" + +[node name="Matchmaking" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Start Matchmaking" + +[node name="Button" type="Button" parent="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Start Game" + +[node name="TradeSystem" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +text = "Inventory" + +[node name="GridContainer" type="GridContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 +columns = 3 + +[node name="AddItemToInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Ping RPC" + +[node name="GetInventory" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Inventory" + +[node name="SendTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Send Trade Offer" + +[node name="GetTradeOffers" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Get Trade Offers" + +[node name="AcceptTradeOffer2" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Accept Trade Offer" + +[node name="CancelTradeOffer" type="Button" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Cancel Trade Offer" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel"] +layout_mode = 2 + +[node name="Panel" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="Panel2" type="Panel" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 + +[node name="FriendNameLabel" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "Other User: " + +[node name="Label2" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "My Inventory" + +[node name="TradeVbox1" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeVBox2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TradeOffers" type="PanelContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer2" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 +text = "Trade Offers" + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/TradeSystem/VBoxContainer/TradeOffers/VBoxContainer2"] +layout_mode = 2 + +[node name="Data Panel" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 0 +metadata/_tab_index = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Data Panel"] +layout_mode = 2 + +[node name="StoreData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Store Data" + +[node name="GetData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "Get Data from Store" + +[node name="ListData" type="Button" parent="HBox/LobbyContainer/Data Panel/VBoxContainer"] +layout_mode = 2 +text = "List Data from Store" + +[node name="Group Admin Panel" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +metadata/_tab_index = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel"] +layout_mode = 2 + +[node name="GroupName2" type="LineEdit" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Name of group" + +[node name="GetGroupInformation" type="Button" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +layout_mode = 2 +text = "Select Group" + +[node name="DeleteSelectedGroup" type="Button" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +layout_mode = 2 +text = "Delete Group" + +[node name="SelectedGroup" type="PanelContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup"] +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="SelectedGroupName" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group name:" + +[node name="SelectedGroupDescription" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group description:" + +[node name="SelectedGroupID" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Group ID:" + +[node name="SelectedGroupIsOpen" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +unique_name_in_owner = true +layout_mode = 2 +text = "Is open:" + +[node name="GroupMembers" type="Label" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Group Members:" + +[node name="PanelContainer" type="PanelContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_tv77q") + +[node name="GroupMembersList" type="VBoxContainer" parent="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/SelectedGroup/ScrollContainer/_/PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Group Manager" type="PanelContainer" parent="HBox/LobbyContainer"] +layout_mode = 2 +metadata/_tab_index = 5 + +[node name="TabContainer" type="TabContainer" parent="HBox/LobbyContainer/Group Manager"] +layout_mode = 2 +current_tab = 2 + +[node name="Create Group" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group"] +layout_mode = 2 +theme_override_constants/separation = 25 + +[node name="CreateGroupGrid" type="GridContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +theme_override_constants/h_separation = 15 +theme_override_constants/v_separation = 15 +columns = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Name" + +[node name="GroupName" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupDescLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +layout_mode = 2 +text = "Description" + +[node name="GroupDesc" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroupGrid"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CreateGroup2" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Create Group" + +[node name="Group Query" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 +metadata/_tab_index = 1 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query"] +layout_mode = 2 + +[node name="_" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 15 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 + +[node name="GroupNameLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +layout_mode = 2 +text = "Group Name: " + +[node name="GroupQuery" type="LineEdit" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Group Query" + +[node name="HBoxContainer2" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="List Groups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "List Groups" + +[node name="GroupListingLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +text = "Group listing limit:" +horizontal_alignment = 1 + +[node name="GroupListingSlider" type="HSlider" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +min_value = 1.0 +max_value = 25.0 +value = 10.0 + +[node name="GroupListingSelectedLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +text = "10" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel"] +custom_minimum_size = Vector2(0, 75) +layout_mode = 2 + +[node name="GroupsVBox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/Panel/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Manage Groups" type="MarginContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 5 +metadata/_tab_index = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups"] +layout_mode = 2 +theme_override_constants/separation = 15 + +[node name="UpdateAvailableGroups" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 +text = "Update groups" + +[node name="SelectAGroup" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/SelectAGroup"] +layout_mode = 2 +text = "Selected Group: " + +[node name="GroupsAvailableToUser" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/SelectAGroup"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HSeparator3" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 +text = "Group Information" + +[node name="CloseOpenGroup" type="CheckButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Close Group" + +[node name="HSeparator4" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 + +[node name="SelectAMember" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/SelectAMember"] +layout_mode = 2 +text = "Manage Member:" + +[node name="GroupUsers" type="OptionButton" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/SelectAMember"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GroupMemberStatus" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="JoinedMemberVbox" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="PromoteDemote" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 + +[node name="PromoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Promote" + +[node name="DemoteUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/JoinedMemberVbox/PromoteDemote"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Demote" + +[node name="KickUser" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/JoinedMemberVbox"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Kick" + +[node name="AcceptJoinRequest" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Accept Join Request" + +[node name="HSeparator2" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 + +[node name="PendingToJoinSection" type="VBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 15 + +[node name="SectionTitle" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Pending to Join:" + +[node name="_" type="PanelContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="PendingToJoinGroupLabel" type="Label" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/PendingToJoinSection/_"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="AcceptAllPendingRequests" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 +text = "Accept All Pending Requests" + +[node name="HSeparator3" type="HSeparator" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/PendingToJoinSection"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer"] +layout_mode = 2 + +[node name="LeaveGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Leave Group" + +[node name="DeleteGroup" type="Button" parent="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Delete Group" + +[node name="Chat" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 6 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Chat"] +layout_mode = 2 + +[node name="ChatName" type="LineEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +placeholder_text = "Chat Name" + +[node name="HBoxContainer" type="HBoxContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 + +[node name="CreateChatRoom" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Create Chat room" + +[node name="JoinGroupChatRoom" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Join Group Chat" + +[node name="JoinDirectChat" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 +text = "Join Direct Chat" + +[node name="Chat" type="PanelContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat"] +layout_mode = 2 + +[node name="UsernameContainer" type="TabContainer" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="username" type="TextEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer/UsernameContainer"] +layout_mode = 2 +editable = false +wrap_mode = 1 +metadata/_tab_index = 0 + +[node name="ChatTextLineEdit" type="LineEdit" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Chat Text Here" + +[node name="SubmitChat" type="Button" parent="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer"] +layout_mode = 2 +text = ">" + +[node name="CreateParty" type="PanelContainer" parent="HBox/LobbyContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 7 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty"] +layout_mode = 2 + +[node name="CreateParty" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +layout_mode = 2 +text = "Create Party" + +[node name="Panel" type="PanelContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/Panel"] +layout_mode = 2 + +[node name="ChannelMessagePanel" type="PanelContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel"] +layout_mode = 2 + +[node name="ChannelMessageLabel" type="Label" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Join Party?" + +[node name="JoinPartyNo" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +layout_mode = 2 +text = "No" + +[node name="JoinPartyYes" type="Button" parent="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer"] +layout_mode = 2 +text = "Yes" + +[node name="UserInformationDisplay" parent="HBox" instance=ExtResource("2_qmkq0")] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 105) +layout_mode = 2 +size_flags_vertical = 1 + +[node name="Authentication" type="CenterContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="Tabs" type="TabContainer" parent="Authentication"] +layout_mode = 2 +current_tab = 0 + +[node name="Login" parent="Authentication/Tabs" instance=ExtResource("4_4xlfs")] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="Register" parent="Authentication/Tabs" instance=ExtResource("5_wys0s")] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="NotificationContainer" parent="." instance=ExtResource("2_lanui")] +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +size_flags_vertical = 8 + +[node name="Popup" parent="." instance=ExtResource("7_tv77q")] +position = Vector2i(0, 36) +visible = false + +[node name="CreateChatRoom" type="Window" parent="."] +title = "Create Chat Room" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="CreateChatRoom"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="CreateChatRoom/PanelContainer"] +layout_mode = 2 + +[node name="UsernameField" type="HBoxContainer" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 + +[node name="Label" type="Label" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +text = "Username" + +[node name="LineEdit" type="LineEdit" parent="CreateChatRoom/PanelContainer/_/UsernameField"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="CreateChatRoom/PanelContainer/_"] +layout_mode = 2 +text = "Create" + +[node name="JoinGroupChat" type="Window" parent="."] +auto_translate_mode = 1 +title = "Join Group Chat" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="JoinGroupChat"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="JoinGroupChat/PanelContainer"] +layout_mode = 2 + +[node name="Group" type="HBoxContainer" parent="JoinGroupChat/PanelContainer/_"] +layout_mode = 2 + +[node name="Select a Group" type="Label" parent="JoinGroupChat/PanelContainer/_/Group"] +layout_mode = 2 +text = "Select a Group" + +[node name="OptionButton" type="OptionButton" parent="JoinGroupChat/PanelContainer/_/Group"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="JoinGroupChat/PanelContainer/_"] +layout_mode = 2 +text = "Join" + +[node name="DirectChat" type="Window" parent="."] +auto_translate_mode = 1 +title = "Join Group Chat" +position = Vector2i(0, 36) +size = Vector2i(500, 100) +visible = false +min_size = Vector2i(250, 0) + +[node name="PanelContainer" type="PanelContainer" parent="DirectChat"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="_" type="VBoxContainer" parent="DirectChat/PanelContainer"] +layout_mode = 2 + +[node name="User" type="HBoxContainer" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 + +[node name="SelectAFriend" type="Label" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +text = "Select a Friend" + +[node name="OptionButton" type="OptionButton" parent="DirectChat/PanelContainer/_/User"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="DirectChat/PanelContainer/_"] +layout_mode = 2 +text = "Join" + +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer/AddFriend" to="." method="_on_add_friend_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer/BlockFriends" to="." method="_on_block_friends_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer/RemoveFriend" to="." method="_on_remove_friend_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Friend List/HBoxContainer/VBox/GridContainer/GetFriends" to="." method="_on_get_friends_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/JoinCreateMatch" to="." method="_on_join_create_match_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Ping" to="." method="_on_ping_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Matchmaking" to="." method="_on_matchmaking_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Match Finder/VBoxContainer/GridContainer/Button" to="." method="_on_button_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AddItemToInventory" to="." method="_on_ping_rpc_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetInventory" to="." method="_on_get_inventory_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/SendTradeOffer" to="." method="_on_send_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/GetTradeOffers" to="." method="_on_get_trade_offers_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/AcceptTradeOffer2" to="." method="_on_accept_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/TradeSystem/VBoxContainer/GridContainer/CancelTradeOffer" to="." method="_on_cancel_trade_offer_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/StoreData" to="." method="_on_store_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/GetData" to="." method="_on_get_data_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Data Panel/VBoxContainer/ListData" to="." method="_on_list_data_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/GetGroupInformation" to="." method="_on_get_group_information_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Admin Panel/VBoxContainer/DeleteSelectedGroup" to="." method="_on_delete_group_pressed"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Create Group/VBoxContainer/CreateGroup2" to="." method="_on_create_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/List Groups" to="." method="_on_list_groups_button_down"] +[connection signal="value_changed" from="HBox/LobbyContainer/Group Manager/TabContainer/Group Query/ScrollContainer/_/HBoxContainer2/VBoxContainer/GroupListingSlider" to="." method="_on_group_listing_slider_value_changed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/UpdateAvailableGroups" to="." method="_on_update_available_groups_pressed"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/SelectAGroup/GroupsAvailableToUser" to="." method="_on_groups_available_to_user_item_selected"] +[connection signal="toggled" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/CloseOpenGroup" to="." method="_on_close_group_pressed"] +[connection signal="item_selected" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/SelectAMember/GroupUsers" to="." method="_on_group_users_item_selected"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/JoinedMemberVbox/PromoteDemote/PromoteUser" to="." method="_on_promote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/JoinedMemberVbox/PromoteDemote/DemoteUser" to="." method="_on_demote_user_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/JoinedMemberVbox/KickUser" to="." method="_on_kick_user_button_down"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/AcceptJoinRequest" to="." method="_on_accept_join_request_pressed"] +[connection signal="pressed" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/PendingToJoinSection/AcceptAllPendingRequests" to="." method="_on_accept_all_pending_requests_pressed"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/HBoxContainer/LeaveGroup" to="." method="_on_leave_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Group Manager/TabContainer/Manage Groups/VBoxContainer/HBoxContainer/DeleteGroup" to="." method="_on_delete_group_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer/CreateChatRoom" to="." method="_on_join_chat_room_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/HBoxContainer/JoinGroupChatRoom" to="." method="_on_join_group_chat_room_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/JoinDirectChat" to="." method="_on_join_direct_chat_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/Chat/VBoxContainer/Chat/VBoxContainer/SubmitChat" to="." method="_on_submit_chat_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/CreateParty" to="." method="_on_create_party_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer/JoinPartyNo" to="." method="_on_join_party_no_button_down"] +[connection signal="button_down" from="HBox/LobbyContainer/CreateParty/VBoxContainer/ChannelMessagePanel/VBoxContainer/JoinPartyYes" to="." method="_on_join_party_yes_button_down"] +[connection signal="login" from="Authentication/Tabs/Login" to="." method="_on_login_pressed"] +[connection signal="register_account" from="Authentication/Tabs/Register" to="." method="_on_register_account_pressed"]