extends Node3D var multiplayer_peer = ENetMultiplayerPeer.new() const PORT = 9999 const ADDRESS = "127.0.0.1" var connected_peer_ids = [] var local_player_character : CharacterBody3D var player_scene = preload("res://scenes/player.tscn") var current_turn_index = 0 @export var players = [] var game_started = false var max_players = 4 var bots = [] @export var turn_based_mode: bool = true #var bot_move_timer: float = 0.0 #const BOT_MOVE_INTERVAL: float = 2.0 #var moving_bots = {} enum ActionState { NONE, MOVING, GRABBING, PUTTING, RANDOMIZING, ARRANGING } var current_action_state = ActionState.NONE @onready var action_menu = $ActionMenu @onready var move_button = $ActionMenu/ActionButtonContainer/MoveButton @onready var grab_button = $ActionMenu/ActionButtonContainer/GrabButton @onready var put_button = $ActionMenu/ActionButtonContainer/PutButton @onready var randomize_button = $ActionMenu/ActionButtonContainer/RandomizeButton @onready var arrange_button = $ActionMenu/ActionButtonContainer/ArrangeButton @onready var playerboard_ui = $PlayerboardUI const item_tex = [ preload("res://assets/textures/player_board_and_blue_print/tile_null.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_heart.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_diamond.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_star.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_coin.tres") ] func _ready(): multiplayer_peer.peer_connected.connect(_on_peer_connected) multiplayer_peer.peer_disconnected.connect(_on_peer_disconnected) setup_action_buttons() setup_playerboard_ui() func _process(delta): if multiplayer.is_server() and game_started: if turn_based_mode: rpc("sync_turn_index", current_turn_index) else: pass #bot_move_timer += delta #if bot_move_timer >= BOT_MOVE_INTERVAL: #bot_move_timer = 0.0 #var current_bots = bots.duplicate() #for bot_id in current_bots: #if not moving_bots.get(bot_id, false): #move_bot(bot_id) func setup_action_buttons(): move_button.pressed.connect(func(): set_action_state(ActionState.MOVING)) grab_button.pressed.connect(func(): set_action_state(ActionState.GRABBING)) put_button.pressed.connect(func(): if local_player_character: local_player_character.handle_put_action() set_action_state(ActionState.PUTTING) ) randomize_button.pressed.connect(func(): set_action_state(ActionState.RANDOMIZING)) arrange_button.pressed.connect(func(): if local_player_character and local_player_character.action_points >= 2: set_action_state(ActionState.ARRANGING) ) func setup_playerboard_ui(): for child in playerboard_ui.get_children(): child.queue_free() playerboard_ui.columns = 5 for i in range(25): var slot = TextureRect.new() var highlight_rect = TextureRect.new() var hr_tex = load("res://assets/models/pboard/HighlightRect.tres") var select_rect = TextureRect.new() var sr_tex = load("res://assets/models/pboard/SelectRect.tres") var adjacent_rect = TextureRect.new() var ar_tex = load("res://assets/models/pboard/AdjacentRect.tres") slot.custom_minimum_size = Vector2(36, 36) slot.gui_input.connect(func(event): _on_playerboard_slot_clicked(event, i)) slot.texture = item_tex[0] playerboard_ui.add_child(slot, true) highlight_rect.texture = hr_tex highlight_rect.size = Vector2(36, 36) select_rect.texture = sr_tex select_rect.size = Vector2(36, 36) adjacent_rect.texture = ar_tex adjacent_rect.size = Vector2(36, 36) slot.add_child(highlight_rect) slot.add_child(select_rect) slot.add_child(adjacent_rect) slot.get_child(0).hide() slot.get_child(1).hide() slot.get_child(2).hide() func set_action_state(new_state): if not local_player_character or not local_player_character.is_multiplayer_authority() or current_action_state == new_state or local_player_character.action_points <= 0: return current_action_state = new_state local_player_character.clear_highlights() local_player_character.clear_playerboard_highlights() match new_state: ActionState.MOVING: local_player_character.highlight_movement_range() ActionState.GRABBING: local_player_character.highlight_adjacent_cells() if local_player_character.has_item_at_current_position(): local_player_character.highlighted_cells.append(local_player_character.current_position) local_player_character.enhanced_gridmap.set_cell_item( Vector3i(local_player_character.current_position.x, 0, local_player_character.current_position.y), local_player_character.enhanced_gridmap.hover_item ) ActionState.PUTTING: local_player_character.highlight_occupied_playerboard_slots() ActionState.RANDOMIZING: local_player_character.highlight_random_valid_cells() ActionState.ARRANGING: show_arrangement_ui() local_player_character.highlight_occupied_playerboard_slots() func update_button_states(): if not local_player_character or local_player_character.is_in_group("Bots"): move_button.visible = false grab_button.visible = false put_button.visible = false randomize_button.visible = false arrange_button.visible = false return move_button.visible = true grab_button.visible = true put_button.visible = true randomize_button.visible = true arrange_button.visible = true move_button.disabled = local_player_character.is_player_moving or local_player_character.has_performed_action grab_button.disabled = not local_player_character.has_item_at_current_position() put_button.disabled = not (local_player_character.has_items_in_playerboard() and not local_player_character.has_item_at_current_position()) randomize_button.disabled = local_player_character.has_performed_action arrange_button.disabled = not local_player_character.has_items_in_playerboard() func _on_playerboard_slot_clicked(event, slot_index): if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: if not local_player_character: return match current_action_state: ActionState.ARRANGING: local_player_character.arrange_playerboard_item(slot_index) ActionState.GRABBING: local_player_character.handle_playerboard_slot_selected(slot_index) ActionState.PUTTING: local_player_character.handle_put_slot_selected(slot_index) func update_playerboard_ui(): if not local_player_character: return for i in range(25): var slot = playerboard_ui.get_child(i) var item = local_player_character.playerboard[i] slot.texture = item_tex[0] match item: 7: slot.texture = item_tex[1] 8: slot.texture = item_tex[2] 9: slot.texture = item_tex[3] 10: slot.texture = item_tex[4] func update_playerboard_highlights(highlighted_slots: Array): for i in range(playerboard_ui.get_child_count()): var slot = playerboard_ui.get_child(i) if slot.get_child_count() > 1: slot.get_child(1).visible = highlighted_slots.has(i) func show_arrangement_ui(): if playerboard_ui: playerboard_ui.visible = true update_playerboard_ui() func _on_host_pressed(): $NetworkInfo/NetworkSideDisplay.text = "Server" $Menu.visible = false multiplayer_peer.create_server(PORT) multiplayer.multiplayer_peer = multiplayer_peer $NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id()) add_player_character(1) players.append(1) for i in range(2, max_players + 1): add_bot(i) start_game() func _on_join_pressed(): $NetworkInfo/NetworkSideDisplay.text = "Client" $Menu.visible = false multiplayer_peer.create_client(ADDRESS, PORT) multiplayer.multiplayer_peer = multiplayer_peer $NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id()) func _on_peer_connected(new_peer_id): if multiplayer.is_server(): await get_tree().create_timer(1).timeout rpc("add_newly_connected_player_character", new_peer_id) rpc_id(new_peer_id, "add_previously_connected_player_characters", connected_peer_ids) rpc_id(new_peer_id, "sync_game_state", players, bots, game_started, turn_based_mode) add_player_character(new_peer_id) replace_bot_with_player(new_peer_id) func _on_peer_disconnected(peer_id): if multiplayer.is_server(): connected_peer_ids.erase(peer_id) players.erase(peer_id) add_bot(get_next_available_bot_id()) func add_player_character(peer_id): connected_peer_ids.append(peer_id) var player_character = player_scene.instantiate() player_character.set_multiplayer_authority(peer_id) if multiplayer.is_server() and bots.size() > 0: var bot_to_replace = get_node_or_null(str(bots[0])) if bot_to_replace: player_character.current_position = bot_to_replace.current_position add_child(player_character) player_character.add_to_group("Players", true) if peer_id == multiplayer.get_unique_id(): local_player_character = player_character update_button_states() update_playerboard_ui() func add_bot(bot_id): if multiplayer.is_server(): rpc("create_bot", bot_id) #moving_bots[bot_id] = false @rpc("call_local") func create_bot(bot_id): var bot_character = player_scene.instantiate() bot_character.set_multiplayer_authority(1) bot_character.name = str(bot_id) add_child(bot_character) bot_character.add_to_group("Players", true) bot_character.add_to_group("Bots", true) # The BehaviorTree will auto-activate for bots var behavior_tree = bot_character.get_node_or_null("BotBehavior") if behavior_tree: behavior_tree.enabled = true behavior_tree.actor = bot_character if multiplayer.is_server(): bots.append(bot_id) players.append(bot_id) func replace_bot_with_player(player_id): if multiplayer.is_server() and bots.size() > 0: var bot_id = bots.pop_front() players.erase(bot_id) players.append(player_id) #moving_bots.erase(bot_id) rpc("remove_bot", bot_id) rpc("sync_players", players) @rpc("call_local") func remove_bot(bot_id): var bot_node = get_node_or_null(str(bot_id)) if bot_node: bot_node.queue_free() func get_next_available_bot_id() -> int: for i in range(2, max_players + 1): if not i in players: return i return -1 @rpc func add_newly_connected_player_character(new_peer_id): add_player_character(new_peer_id) @rpc func add_previously_connected_player_characters(peer_ids): for peer_id in peer_ids: add_player_character(peer_id) @rpc("call_local") func sync_game_state(current_players, current_bots, is_game_started, is_turn_based): players = current_players bots = current_bots game_started = is_game_started turn_based_mode = is_turn_based for bot_id in bots: if not has_node(str(bot_id)): create_bot(bot_id) func start_game(): if multiplayer.is_server(): game_started = true connected_peer_ids.sort() rpc("sync_game_start", connected_peer_ids, players, bots, turn_based_mode) if turn_based_mode: current_turn_index = -1 next_turn() @rpc("call_local") func sync_game_start(peer_ids, current_players, current_bots, is_turn_based): connected_peer_ids = peer_ids players = current_players bots = current_bots turn_based_mode = is_turn_based game_started = true @rpc("reliable") func sync_turn_index(index): current_turn_index = index @rpc("reliable") func sync_players(new_players): players = new_players func next_turn(): if multiplayer.is_server() and turn_based_mode: current_turn_index = (current_turn_index + 1) % players.size() rpc("set_current_turn", players[current_turn_index]) #if players[current_turn_index] in bots: #move_bot(players[current_turn_index]) func request_next_turn(): if multiplayer.is_server(): end_current_turn() else: rpc_id(1, "server_end_current_turn") @rpc("any_peer") func server_end_current_turn(): if multiplayer.is_server(): end_current_turn() @rpc("any_peer", "call_local") func set_current_turn(player_id): if not turn_based_mode: return for player in get_tree().get_nodes_in_group("Players"): if player.name == str(player_id): player.is_my_turn = true player.action_points = 2 player.has_moved_this_turn = false player.has_performed_action = false player.start_turn() else: player.is_my_turn = false func end_current_turn(): if multiplayer.is_server(): next_turn() rpc("sync_turn_index", current_turn_index) #func move_bot(bot_id): #if not multiplayer.is_server() or moving_bots.get(bot_id, false): #return # #var bot = get_node_or_null(str(bot_id)) #if not is_instance_valid(bot) or bot.is_player_moving: #moving_bots.erase(bot_id) #return # #if bot.action_points <= 0: #if turn_based_mode: #moving_bots[bot_id] = false #end_current_turn() #else: #bot.action_points = 2 #bot.has_moved_this_turn = false #bot.has_performed_action = false #moving_bots[bot_id] = false #await get_tree().create_timer(0.5).timeout #move_bot(bot_id) #return # #moving_bots[bot_id] = true #await get_tree().create_timer(0.5).timeout # #if not is_instance_valid(bot): #moving_bots.erase(bot_id) #return # #var best_move = evaluate_bot_move(bot) #execute_bot_move(bot, bot_id, best_move) #func execute_bot_move(bot, bot_id, move): #match move.action: #"arrange": #if bot.action_points >= 2: #bot.arrange_playerboard_item(bot.find_best_arrangement_slot()) #bot.action_points -= 2 #moving_bots[bot_id] = false #if bot.action_points > 0 or not turn_based_mode: #move_bot(bot_id) #elif turn_based_mode: #end_current_turn() # #"grab", "put": #var success = bot.grab_item(move.position) if move.action == "grab" else bot.put_item(move.position) #if success: #bot.action_points -= 1 #moving_bots[bot_id] = false #if bot.action_points > 0 or not turn_based_mode: #move_bot(bot_id) #elif turn_based_mode: #end_current_turn() # #"move": #if is_instance_valid(bot) and bot.action_points >= 1: #var path = bot.enhanced_gridmap.find_path(Vector2(bot.current_position), Vector2(move.position)) #if path.size() > 1: #path.pop_front() #var trimmed_path = path.slice(0, bot.movement_range) #bot.rotate_towards_target(move.position) #bot.move_bot_along_path(trimmed_path, bot_id) #bot.action_points -= 1 #else: #moving_bots[bot_id] = false #if bot.action_points > 0 or not turn_based_mode: #move_bot(bot_id) #elif turn_based_mode: #end_current_turn() #else: #moving_bots[bot_id] = false #if bot.action_points > 0 or not turn_based_mode: #move_bot(bot_id) #elif turn_based_mode: #end_current_turn() # #_: #moving_bots[bot_id] = false #if turn_based_mode: #end_current_turn() #else: #move_bot(bot_id) #func evaluate_bot_move(bot: Node) -> Dictionary: #if not is_instance_valid(bot) or bot.action_points <= 0: #return { "action": "none", "position": Vector2i.ZERO, "value": -1 } # #var moves = [] # #if bot.action_points >= 2 and bot.check_playerboard_arrangement(bot): #moves.append({ "action": "arrange", "position": bot.current_position, "value": 20 }) # #if bot.action_points >= 1: #var put_position = bot.find_best_put_position(bot) #if put_position != Vector2i(-1, -1): #moves.append({ "action": "put", "position": put_position, "value": 18 }) # #if bot.action_points >= 1 and not bot.playerboard_is_full(): #var grab_position = bot.find_best_grab_position() #if grab_position != Vector2i(-1, -1): #moves.append({ "action": "grab", "position": grab_position, "value": 15 }) # #if bot.action_points >= 1 and not bot.has_moved_this_turn: #var random_pos = bot.find_random_valid_position_in_range() #if random_pos != bot.current_position: #moves.append({ "action": "move", "position": random_pos, "value": 5 }) # #moves.sort_custom(func(a, b): return a.value > b.value) #return moves[0] if moves.size() > 0 else { "action": "none", "position": bot.current_position, "value": 0 } #func bot_movement_completed(bot_id): #if not multiplayer.is_server(): #return # #moving_bots[bot_id] = false #var bot = get_node_or_null(str(bot_id)) # #if not is_instance_valid(bot): #moving_bots.erase(bot_id) #return # #if bot.action_points > 0 or not turn_based_mode: #await get_tree().create_timer(0.5).timeout #if is_instance_valid(bot): #move_bot(bot_id) #else: #end_current_turn()