f9f7d34f80
Added a new example scene and player script for EnhancedGridMap. Updated mesh library, materials, and tile assets for improved visuals and structure. Modified main scene and logic to support new gridmap configuration and auto item handling. Adjusted project settings for resolution and main scene path.
1237 lines
40 KiB
GDScript
1237 lines
40 KiB
GDScript
# -------------------------------------------------------------------------------------
|
|
# Tekton Dash - Multiplayer Board Game - 2024
|
|
# -------------------------------------------------------------------------------------
|
|
# [x] Move is working, you can move the character to another tile on gridmap
|
|
# [x] Make the Randomize tile, currently there's no logic to handle it
|
|
# it's randomly highlighted random tile on gridmap
|
|
# [ ] Arrange tile is working, you can move the tile to another slot on the playerboard
|
|
# [x] Put tile is working, you can put the tile from playerboard to the gridmap
|
|
# [x] Grab tile is working, you can grab the tile from gridmap to playerboard
|
|
# -------------------------------------------------------------------------------------
|
|
# [ ] Implement the Boosts tile, that can be used to boost player movement to next tile
|
|
# [x] Implement the Obstacle tile, that can be used to block player movement to next tile
|
|
# -------------------------------------------------------------------------------------
|
|
# [x] Added multiplayer support - with act as server and client
|
|
# [x] Added UPnP support for automatic port forwarding, for android and desktop
|
|
# [x] Added Randomized Goals for each player
|
|
# [x] Added bot support ( currently broken )
|
|
# [x] Added turn-based mode
|
|
# [x] Added Realtime mode
|
|
# [ ] Implement ActionState costs
|
|
# [x] Implement ActionState disable condition
|
|
# [ ] Implement the special item that can trigger card draw, with realtime effect
|
|
# -------------------------------------------------------------------------------------
|
|
# [ ] Reskin the Game UI with offline prototype assets
|
|
# [ ] Grab the GUI for Main Menu, character selector, from Promotional Video
|
|
# [ ] Reskin the 3D models with offline prototype assets
|
|
# [ ] Implement the statemachine animation for the character
|
|
# -------------------------------------------------------------------------------------
|
|
# [ ] Implement specialty character with unique ability
|
|
# [ ] Implement the sabotage meter
|
|
# -------------------------------------------------------------------------------------
|
|
|
|
|
|
extends Node3D
|
|
|
|
var multiplayer_peer = ENetMultiplayerPeer.new()
|
|
const PORT = 9999
|
|
const ADDRESS = "127.0.0.1"
|
|
|
|
@export var enable_bots: bool = true # Add this line
|
|
|
|
var connected_peer_ids = []
|
|
var _connection_check_timer: float = 0.0
|
|
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 = []
|
|
var preset_goals = []
|
|
|
|
@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,
|
|
PLACING_OBSTACLE
|
|
}
|
|
|
|
# Obstacle
|
|
# Add these properties to track current obstacle settings
|
|
var current_obstacle_orientation = ObstacleOrientation.NORTH
|
|
var current_obstacle_item = 12 # Starting with first obstacle item (12)
|
|
|
|
enum ObstacleDirection {
|
|
VERTICAL,
|
|
HORIZONTAL
|
|
}
|
|
|
|
enum ObstacleOrientation {
|
|
NORTH = 0, # Blocks movement to the north (top)
|
|
EAST = 1, # Blocks movement to the east (right)
|
|
SOUTH = 2, # Blocks movement to the south (bottom)
|
|
WEST = 3 # Blocks movement to the west (left)
|
|
}
|
|
|
|
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()
|
|
# Obstacles
|
|
setup_obstacle_ui()
|
|
|
|
func _process(delta):
|
|
if multiplayer.is_server() and game_started:
|
|
if turn_based_mode:
|
|
rpc("sync_turn_index", current_turn_index)
|
|
# Sync all players' goals to the new peer
|
|
update_all_players_goals()
|
|
|
|
# Also periodically verify client connections
|
|
_connection_check_timer += delta
|
|
if _connection_check_timer >= 5.0:
|
|
_connection_check_timer = 0.0
|
|
verify_all_connections()
|
|
|
|
func verify_all_connections():
|
|
if multiplayer.is_server():
|
|
for peer_id in players:
|
|
if peer_id != 1: # Skip server
|
|
# Ping each client
|
|
rpc_id(peer_id, "connection_verify", players)
|
|
|
|
@rpc
|
|
func connection_verify(expected_players: Array):
|
|
# Client checks if it has all expected players
|
|
for peer_id in expected_players:
|
|
if peer_id != multiplayer.get_unique_id() and not has_node(str(peer_id)):
|
|
# Missing a player - request it
|
|
rpc_id(1, "request_specific_player_data", peer_id)
|
|
print("Requesting missing player: ", peer_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)
|
|
#)
|
|
put_button.pressed.connect(func():
|
|
if local_player_character:
|
|
local_player_character.auto_put_item()
|
|
)
|
|
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():
|
|
return
|
|
|
|
if local_player_character.is_bot or local_player_character.is_in_group("Bots"):
|
|
current_action_state = new_state
|
|
return
|
|
|
|
if 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()
|
|
|
|
# Deactivated since, using Auto Grabber
|
|
|
|
#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()
|
|
# Make sure this is client-friendly
|
|
#if not multiplayer.is_server():
|
|
#rpc_id(1, "notify_server_of_action_state", new_state)
|
|
ActionState.RANDOMIZING:
|
|
local_player_character.highlight_random_valid_cells()
|
|
ActionState.ARRANGING:
|
|
show_arrangement_ui()
|
|
local_player_character.highlight_occupied_playerboard_slots()
|
|
ActionState.PLACING_OBSTACLE:
|
|
local_player_character.highlight_valid_obstacle_cells()
|
|
|
|
func place_obstacle(grid_position: Vector2i):
|
|
if not local_player_character or local_player_character.action_points < 1:
|
|
return false
|
|
|
|
var floor_index = 3 # Always place on floor 3
|
|
|
|
var success = $EnhancedGridMap.place_obstacle(
|
|
Vector3i(grid_position.x, floor_index, grid_position.y),
|
|
current_obstacle_item,
|
|
current_obstacle_orientation
|
|
)
|
|
|
|
if success:
|
|
local_player_character.action_points -= 1
|
|
|
|
# Clear all highlights after placing obstacle
|
|
local_player_character.clear_highlights()
|
|
|
|
# Exit obstacle placement mode and return to default state
|
|
set_action_state(ActionState.NONE)
|
|
|
|
# Sync the obstacle with other clients
|
|
if is_multiplayer_authority():
|
|
rpc("sync_place_obstacle", grid_position.x, grid_position.y, floor_index, current_obstacle_item, current_obstacle_orientation)
|
|
|
|
return true
|
|
return false
|
|
|
|
# Updated function to cycle through the four orientations
|
|
# Updated function to cycle through the four orientations
|
|
func cycle_obstacle_orientation():
|
|
var orientations = [
|
|
ObstacleOrientation.NORTH,
|
|
ObstacleOrientation.EAST,
|
|
ObstacleOrientation.SOUTH,
|
|
ObstacleOrientation.WEST
|
|
]
|
|
var current_index = orientations.find(current_obstacle_orientation)
|
|
current_index = (current_index + 1) % orientations.size()
|
|
current_obstacle_orientation = orientations[current_index]
|
|
|
|
var direction_names = ["North", "East", "South", "West"]
|
|
return "Direction: " + direction_names[current_index]
|
|
|
|
# Function to cycle through obstacle types
|
|
func cycle_obstacle_type():
|
|
var obstacle_types = [12, 13, 14, 15]
|
|
var current_index = obstacle_types.find(current_obstacle_item)
|
|
current_index = (current_index + 1) % obstacle_types.size()
|
|
current_obstacle_item = obstacle_types[current_index]
|
|
return "Type: " + str(current_index + 1)
|
|
|
|
# Update the RPC for obstacle synchronization
|
|
@rpc("any_peer", "call_local")
|
|
func sync_place_obstacle(x: int, y: int, floor_index: int, item_index: int, orientation: int):
|
|
$EnhancedGridMap.place_obstacle(Vector3i(x, floor_index, y), item_index, orientation)
|
|
|
|
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
|
|
|
|
# Only keep randomize button's disable condition
|
|
#randomize_button.disabled = local_player_character.has_performed_action
|
|
|
|
# Remove disabled conditions for other buttons:
|
|
move_button.disabled = false
|
|
grab_button.disabled = false
|
|
put_button.disabled = false
|
|
arrange_button.disabled = false
|
|
|
|
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)
|
|
|
|
# Deactivated since we're using auto grab
|
|
#ActionState.GRABBING:
|
|
#local_player_character.handle_playerboard_slot_selected(slot_index)
|
|
# Deactivated since we're using auto put
|
|
#ActionState.PUTTING:
|
|
#local_player_character.handle_put_slot_selected(slot_index)
|
|
|
|
func update_playerboard_ui():
|
|
if not local_player_character:
|
|
return
|
|
|
|
#update_goals_ui() # Update goals UI whenever playerboard updates
|
|
update_all_players_goals() # Update all players' goals UI
|
|
|
|
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())
|
|
|
|
# Generate all goals first
|
|
preset_goals.clear()
|
|
for i in range(max_players):
|
|
var goals = initialize_random_goals(9, 7, 10, 1.0)
|
|
# Convert to int array explicitly
|
|
var int_goals: Array[int] = []
|
|
for g in goals:
|
|
int_goals.append(g)
|
|
preset_goals.append(int_goals)
|
|
|
|
# Now add host with first set of goals
|
|
add_player_character(1)
|
|
|
|
# Explicitly assign host's goals and force UI update
|
|
var host_player = get_node_or_null("1")
|
|
if host_player:
|
|
host_player.goals = preset_goals[0].duplicate()
|
|
rpc("sync_player_goals", 1, preset_goals[0])
|
|
update_all_players_goals()
|
|
|
|
players.append(1)
|
|
|
|
# Sync goals to all clients after host is set up
|
|
rpc("sync_preset_goals", preset_goals)
|
|
|
|
# Only add bots if enable_bots is true and we need them
|
|
if enable_bots:
|
|
var needed_bots = max_players - 1 # -1 for the host
|
|
for i in range(2, needed_bots + 2): # +2 because we start from ID 2
|
|
add_bot(i)
|
|
|
|
start_game()
|
|
|
|
@rpc("reliable")
|
|
func sync_preset_goals(goals_list: Array):
|
|
preset_goals = goals_list
|
|
|
|
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())
|
|
# After connection is established
|
|
await get_tree().create_timer(2.0).timeout
|
|
rpc_id(1, "request_full_player_sync", multiplayer.get_unique_id())
|
|
|
|
func _on_peer_connected(new_peer_id):
|
|
if multiplayer.is_server():
|
|
# Create a more robust state sync process
|
|
await get_tree().create_timer(1.5).timeout
|
|
|
|
# First sync complete game state
|
|
var complete_state = {
|
|
"players": players,
|
|
"bots": bots,
|
|
"game_started": game_started,
|
|
"turn_based": turn_based_mode,
|
|
"preset_goals": preset_goals,
|
|
"player_states": {}
|
|
}
|
|
|
|
# Gather all existing player states
|
|
for peer_id in players:
|
|
var player = get_node_or_null(str(peer_id))
|
|
if player:
|
|
complete_state["player_states"][peer_id] = {
|
|
"position": player.current_position,
|
|
"goals": player.goals,
|
|
"playerboard": player.playerboard,
|
|
"is_bot": player.is_bot || player.is_in_group("Bots")
|
|
}
|
|
|
|
# Send complete state in one RPC
|
|
rpc_id(new_peer_id, "receive_complete_game_state", complete_state)
|
|
|
|
# Finally add the new player
|
|
await get_tree().create_timer(0.5).timeout
|
|
add_player_character(new_peer_id)
|
|
rpc("add_newly_connected_player_character", new_peer_id)
|
|
|
|
# Make sure all clients know about all players
|
|
rpc("sync_complete_player_list", players)
|
|
|
|
## Replace bot if needed
|
|
#if bots.size() > 0:
|
|
#replace_bot_with_player(new_peer_id)
|
|
#
|
|
# Final sync of all goals
|
|
await get_tree().create_timer(0.5).timeout
|
|
rpc("force_update_all_goals")
|
|
|
|
@rpc("reliable")
|
|
func sync_complete_player_list(player_list: Array):
|
|
# Ensure we have all players in our list
|
|
players = player_list.duplicate()
|
|
|
|
# Check which players we don't have nodes for
|
|
for peer_id in players:
|
|
if not has_node(str(peer_id)) and peer_id != multiplayer.get_unique_id():
|
|
# Request this specific player's data from server
|
|
rpc_id(1, "request_specific_player_data", peer_id)
|
|
|
|
@rpc("any_peer")
|
|
func request_specific_player_data(requested_peer_id: int):
|
|
if multiplayer.is_server():
|
|
var player = get_node_or_null(str(requested_peer_id))
|
|
if player:
|
|
var player_data = {
|
|
"peer_id": requested_peer_id,
|
|
"position": player.current_position,
|
|
"goals": player.goals,
|
|
"playerboard": player.playerboard,
|
|
"is_bot": player.is_bot || player.is_in_group("Bots")
|
|
}
|
|
|
|
# Send to the requesting client only
|
|
rpc_id(multiplayer.get_remote_sender_id(), "create_specific_player", player_data)
|
|
|
|
@rpc("any_peer")
|
|
func request_full_player_sync(requesting_peer_id):
|
|
if multiplayer.is_server():
|
|
print("Full sync requested by: ", requesting_peer_id)
|
|
|
|
# Send the complete list of players
|
|
rpc_id(requesting_peer_id, "sync_complete_player_list", players)
|
|
|
|
# Send each player's data
|
|
for peer_id in players:
|
|
var player = get_node_or_null(str(peer_id))
|
|
if player:
|
|
var player_data = {
|
|
"peer_id": peer_id,
|
|
"position": player.current_position,
|
|
"goals": player.goals,
|
|
"playerboard": player.playerboard,
|
|
"is_bot": player.is_bot || player.is_in_group("Bots")
|
|
}
|
|
rpc_id(requesting_peer_id, "create_specific_player", player_data)
|
|
|
|
# Allow a short delay between player creations
|
|
await get_tree().create_timer(0.1).timeout
|
|
|
|
@rpc("reliable")
|
|
func create_specific_player(data: Dictionary):
|
|
var peer_id = data["peer_id"]
|
|
|
|
# Don't create if already exists
|
|
if has_node(str(peer_id)):
|
|
return
|
|
|
|
# Create the player
|
|
var player_character = player_scene.instantiate()
|
|
player_character.set_multiplayer_authority(peer_id)
|
|
player_character.name = str(peer_id)
|
|
|
|
# Set properties before adding to tree
|
|
player_character.current_position = data["position"]
|
|
|
|
# Add to scene
|
|
add_child(player_character)
|
|
|
|
# Apply properties after adding
|
|
player_character.add_to_group("Players", true)
|
|
if data["is_bot"]:
|
|
player_character.add_to_group("Bots", true)
|
|
player_character.is_bot = true
|
|
player_character.rpc("sync_bot_status", true)
|
|
|
|
# Apply data
|
|
player_character.goals = data["goals"].duplicate()
|
|
player_character.playerboard = data["playerboard"].duplicate()
|
|
|
|
# Force position sync
|
|
player_character.global_position = Vector3(
|
|
data["position"].x * 2 + 1,
|
|
1.0,
|
|
data["position"].y * 2 + 1
|
|
)
|
|
player_character.rpc("sync_position", data["position"])
|
|
|
|
# Update UI
|
|
update_all_players_goals()
|
|
update_all_players_boards()
|
|
|
|
@rpc("reliable")
|
|
func force_update_all_goals():
|
|
# This is called but might be getting lost in the sequence
|
|
# Make sure it's called after all players are created
|
|
await get_tree().create_timer(0.2).timeout
|
|
update_all_players_goals()
|
|
update_all_players_boards()
|
|
|
|
# Set
|
|
@rpc("reliable")
|
|
func receive_complete_game_state(state):
|
|
# Apply complete game state
|
|
players = state["players"]
|
|
bots = state["bots"]
|
|
game_started = state["game_started"]
|
|
turn_based_mode = state["turn_based"]
|
|
preset_goals = state["preset_goals"]
|
|
|
|
# Process each player state in a consistent order
|
|
var sorted_peers = state["player_states"].keys()
|
|
sorted_peers.sort()
|
|
|
|
for peer_id in sorted_peers:
|
|
var player_data = state["player_states"][peer_id]
|
|
|
|
# Create player if doesn't exist
|
|
if not has_node(str(peer_id)):
|
|
var player_character = player_scene.instantiate()
|
|
player_character.set_multiplayer_authority(peer_id)
|
|
player_character.name = str(peer_id)
|
|
|
|
# Set basic properties before adding to scene tree
|
|
player_character.current_position = player_data["position"]
|
|
|
|
# Add to scene
|
|
add_child(player_character)
|
|
|
|
# Apply state after adding to tree
|
|
player_character.add_to_group("Players", true)
|
|
if player_data["is_bot"]:
|
|
player_character.add_to_group("Bots", true)
|
|
player_character.is_bot = true
|
|
player_character.rpc("sync_bot_status", true)
|
|
|
|
player_character.goals = player_data["goals"].duplicate()
|
|
player_character.playerboard = player_data["playerboard"].duplicate()
|
|
|
|
# Ensure proper grid-aligned positioning
|
|
player_character.global_position = Vector3(
|
|
player_data["position"].x * 2 + 1,
|
|
1.0,
|
|
player_data["position"].y * 2 + 1
|
|
)
|
|
|
|
# Force position sync
|
|
player_character.rpc("sync_position", player_data["position"])
|
|
|
|
# Force UI updates
|
|
update_all_players_goals()
|
|
update_all_players_boards()
|
|
|
|
@rpc("reliable")
|
|
func sync_existing_player(peer_id: int, player_data: Dictionary):
|
|
# Create player if doesn't exist
|
|
if not has_node(str(peer_id)):
|
|
var player_character = player_scene.instantiate()
|
|
player_character.set_multiplayer_authority(peer_id)
|
|
player_character.name = str(peer_id)
|
|
player_character.current_position = player_data["position"]
|
|
add_child(player_character)
|
|
player_character.add_to_group("Players", true)
|
|
|
|
# Get player node and wait a frame to ensure it's ready
|
|
await get_tree().create_timer(0.1).timeout
|
|
var player = get_node_or_null(str(peer_id))
|
|
if player:
|
|
# Apply synced state
|
|
player.current_position = player_data["position"]
|
|
player.goals = player_data["goals"].duplicate()
|
|
player.playerboard = player_data["playerboard"].duplicate()
|
|
|
|
# Ensure proper grid-aligned positioning
|
|
player.global_position = Vector3(
|
|
player_data["position"].x * 2 + 1, # cell_size.x = 2
|
|
1.0,
|
|
player_data["position"].y * 2 + 1 # cell_size.z = 2
|
|
)
|
|
|
|
# Force position sync
|
|
player.rpc("sync_position", player_data["position"])
|
|
|
|
# Update UI
|
|
update_all_players_goals()
|
|
update_all_players_boards()
|
|
|
|
func _on_peer_disconnected(peer_id):
|
|
if multiplayer.is_server():
|
|
connected_peer_ids.erase(peer_id)
|
|
players.erase(peer_id)
|
|
# Only add replacement bot if bots are enabled
|
|
if enable_bots:
|
|
add_bot(get_next_available_bot_id())
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func add_player_character(peer_id):
|
|
# First check if this player already exists
|
|
if has_node(str(peer_id)):
|
|
print("Player already exists: ", peer_id)
|
|
return
|
|
|
|
print("Adding player: ", peer_id)
|
|
|
|
connected_peer_ids.append(peer_id)
|
|
var player_character = player_scene.instantiate()
|
|
player_character.set_multiplayer_authority(peer_id)
|
|
player_character.name = str(peer_id)
|
|
|
|
# Handle bot replacement position
|
|
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
|
|
# Set initial grid-aligned position
|
|
player_character.global_position = Vector3(
|
|
bot_to_replace.current_position.x * 2 + 1,
|
|
1.0,
|
|
bot_to_replace.current_position.y * 2 + 1
|
|
)
|
|
|
|
add_child(player_character)
|
|
player_character.add_to_group("Players", true)
|
|
|
|
# Wait for the node to be properly added to the scene
|
|
await get_tree().process_frame
|
|
|
|
# Ensure the player list is updated
|
|
if not peer_id in players:
|
|
players.append(peer_id)
|
|
|
|
# Finish setup and sync position
|
|
if multiplayer.is_server():
|
|
await get_tree().create_timer(0.1).timeout
|
|
player_character.rpc("sync_position", player_character.current_position)
|
|
|
|
# Set goals based on player ID if server
|
|
if multiplayer.is_server():
|
|
var goal_index = peer_id - 1
|
|
if goal_index >= 0 and goal_index < preset_goals.size():
|
|
# Convert to int array before assigning
|
|
var goals: Array[int] = []
|
|
for g in preset_goals[goal_index]:
|
|
goals.append(g)
|
|
player_character.goals = goals
|
|
# Force sync goals to everyone, including host
|
|
rpc("sync_player_goals", peer_id, goals)
|
|
# Update UI immediately for server
|
|
update_all_players_goals()
|
|
|
|
# Local player setup
|
|
if peer_id == multiplayer.get_unique_id():
|
|
local_player_character = player_character
|
|
update_button_states()
|
|
update_playerboard_ui()
|
|
update_all_players_goals() # Force UI update for local player
|
|
|
|
# Request goals from server if we're a client
|
|
if not multiplayer.is_server():
|
|
rpc_id(1, "request_goals_from_server", peer_id)
|
|
|
|
if multiplayer.is_server():
|
|
if peer_id > 1: # Not the host
|
|
# Assign preset goals
|
|
var goal_index = peer_id - 2
|
|
if goal_index < preset_goals.size():
|
|
player_character.goals = preset_goals[goal_index]
|
|
rpc("sync_player_goals", peer_id, player_character.goals)
|
|
|
|
if multiplayer.is_server():
|
|
# If replacing a bot, inherit its goals
|
|
var bot_to_replace = get_node_or_null(str(bots[0])) if bots.size() > 0 else null
|
|
if bot_to_replace:
|
|
player_character.goals = bot_to_replace.goals.duplicate()
|
|
else:
|
|
# Only generate new goals if not inheriting from a bot
|
|
player_character.append_random_goals()
|
|
|
|
func add_bot(bot_id):
|
|
rpc("create_bot", bot_id)
|
|
|
|
|
|
@rpc("call_local")
|
|
func create_bot(bot_id):
|
|
|
|
# First check if bots are enabled
|
|
if not enable_bots:
|
|
print("Attempted to create bot while bots are disabled")
|
|
return
|
|
|
|
|
|
# Ensure we're not duplicating bots
|
|
if has_node(str(bot_id)):
|
|
push_error("Bot already exists: " + str(bot_id))
|
|
return
|
|
|
|
var bot_character = player_scene.instantiate()
|
|
if not bot_character:
|
|
push_error("Failed to instantiate bot scene")
|
|
return
|
|
|
|
bot_character.set_multiplayer_authority(1) # Server controls bots
|
|
bot_character.name = str(bot_id)
|
|
|
|
# Add to scene tree
|
|
call_deferred("add_child", bot_character)
|
|
|
|
# Add to groups after adding to scene tree
|
|
bot_character.add_to_group("Players", true)
|
|
bot_character.add_to_group("Bots", true)
|
|
|
|
if multiplayer.is_server():
|
|
bots.append(bot_id)
|
|
players.append(bot_id)
|
|
|
|
# Assign goals from preset array
|
|
var goal_index = bot_id - 1
|
|
if goal_index < preset_goals.size():
|
|
bot_character.goals = preset_goals[goal_index].duplicate()
|
|
rpc("sync_player_goals", bot_id, bot_character.goals)
|
|
|
|
# Sync bot status after a short delay to ensure node is ready
|
|
await get_tree().create_timer(0.1).timeout
|
|
bot_character.rpc("sync_bot_status", true)
|
|
# Sync bot's goals
|
|
rpc("sync_player_goals", bot_id, bot_character.goals)
|
|
|
|
# Only generate goals for new bots, not replacement bots
|
|
#if not (players.size() > max_players):
|
|
#bot_character.append_random_goals()
|
|
|
|
# Always sync the bot's goals
|
|
rpc("sync_player_goals", bot_id, bot_character.goals)
|
|
|
|
func replace_bot_with_player(player_id):
|
|
if multiplayer.is_server() and bots.size() > 0:
|
|
var bot_id = bots[0]
|
|
var bot_node = get_node_or_null(str(bot_id))
|
|
if bot_node:
|
|
# Get bot's state
|
|
var goals = bot_node.goals
|
|
var playerboard = bot_node.playerboard.duplicate()
|
|
var current_pos = bot_node.current_position
|
|
|
|
# Transfer state to new player
|
|
var player_node = get_node_or_null(str(player_id))
|
|
if player_node:
|
|
player_node.goals = goals
|
|
player_node.playerboard = playerboard.duplicate() # Make sure to duplicate
|
|
player_node.current_position = current_pos
|
|
|
|
# Sync state
|
|
rpc("sync_player_goals", player_id, goals)
|
|
rpc("sync_playerboard", player_id, playerboard)
|
|
|
|
# Remove bot but keep board structure intact
|
|
bots.pop_front()
|
|
players.erase(bot_id)
|
|
players.append(player_id)
|
|
rpc("remove_bot_keep_board", bot_id)
|
|
rpc("sync_players", players)
|
|
|
|
@rpc("call_local")
|
|
func remove_bot_keep_board(bot_id):
|
|
# This RPC is called but not implemented in your code
|
|
var bot_node = get_node_or_null(str(bot_id))
|
|
if bot_node:
|
|
# Don't immediately queue_free - this can cause timing issues
|
|
# Instead, mark for removal and remove after a short delay
|
|
bot_node.visible = false # Hide immediately
|
|
bot_node.set_process(false)
|
|
bot_node.set_physics_process(false)
|
|
|
|
# Disable all input and behavior
|
|
var behavior_tree = bot_node.get_node_or_null("BehaviorTree")
|
|
if behavior_tree:
|
|
behavior_tree.enabled = false
|
|
|
|
# Remove after a short delay
|
|
await get_tree().create_timer(0.5).timeout
|
|
if is_instance_valid(bot_node) and bot_node.get_parent() == self:
|
|
bot_node.queue_free()
|
|
|
|
@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])
|
|
|
|
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"):
|
|
var is_current_turn = player.name == str(player_id)
|
|
player.is_my_turn = is_current_turn
|
|
|
|
# Only reset state for human players
|
|
if is_current_turn and not (player.is_bot or player.is_in_group("Bots")):
|
|
player.action_points = 2
|
|
player.has_moved_this_turn = false
|
|
player.has_performed_action = false
|
|
player.start_turn()
|
|
# Clear any existing highlights from other players
|
|
player.clear_highlights()
|
|
player.clear_playerboard_highlights()
|
|
else:
|
|
player.is_my_turn = false
|
|
|
|
func end_current_turn():
|
|
if multiplayer.is_server():
|
|
next_turn()
|
|
rpc("sync_turn_index", current_turn_index)
|
|
|
|
func update_all_players_boards():
|
|
if not game_started:
|
|
return
|
|
|
|
var local_id = multiplayer.get_unique_id()
|
|
var all_players = get_tree().get_nodes_in_group("Players")
|
|
var all_player_boards = $AllPlayerBoards
|
|
|
|
# Store current active tab
|
|
var current_tab = all_player_boards.current_tab
|
|
|
|
# Board 1 should show host (server)
|
|
var host_player = null
|
|
for player in all_players:
|
|
if int(String(player.name)) == 1:
|
|
host_player = player
|
|
break
|
|
|
|
# Update host board (board 1)
|
|
var host_board = all_player_boards.get_node("1")
|
|
if host_player and host_board and host_board.has_node("PlayerboardUI"):
|
|
host_board.visible = true
|
|
var board_ui = host_board.get_node("PlayerboardUI")
|
|
for slot_idx in range(25):
|
|
update_board_slot(board_ui, slot_idx, host_player.playerboard[slot_idx])
|
|
|
|
# Sort remaining players by ID for boards 2,3,4
|
|
var other_players = all_players.filter(func(p):
|
|
var id = int(String(p.name))
|
|
return id != 1 and not p.is_in_group("Bots") # Exclude host and bots
|
|
)
|
|
other_players.sort_custom(func(a, b):
|
|
return int(String(a.name)) < int(String(b.name))
|
|
)
|
|
|
|
# Update client boards - board 2 for first client, board 3 for second client, etc.
|
|
for i in range(min(other_players.size(), 3)):
|
|
var board_idx = i + 2 # Start from board 2
|
|
var player = other_players[i]
|
|
var board = all_player_boards.get_node(str(board_idx))
|
|
|
|
if board and board.has_node("PlayerboardUI"):
|
|
board.visible = true
|
|
board.name = str(board_idx) # Ensure board name matches index
|
|
var board_ui = board.get_node("PlayerboardUI")
|
|
for slot_idx in range(25):
|
|
update_board_slot(board_ui, slot_idx, player.playerboard[slot_idx])
|
|
|
|
# Hide unused boards
|
|
for i in range(other_players.size() + 2, 5):
|
|
var unused_board = all_player_boards.get_node_or_null(str(i))
|
|
if unused_board:
|
|
unused_board.visible = false
|
|
|
|
# Restore previous active tab
|
|
all_player_boards.current_tab = current_tab
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_playerboard(player_id: int, new_playerboard: Array):
|
|
# Update local player's board if it's their board
|
|
if player_id == multiplayer.get_unique_id() and local_player_character:
|
|
update_playerboard_ui()
|
|
|
|
# Important: Always update all boards when any board changes
|
|
update_all_players_boards()
|
|
|
|
# Update specific board in AllPlayerBoards UI
|
|
var board_index = players.find(player_id)
|
|
if board_index >= 0 and board_index < max_players:
|
|
var target_board_index = board_index + 1
|
|
if target_board_index != 1: # Skip local player's board
|
|
var container = $AllPlayerBoards.get_node_or_null(str(target_board_index))
|
|
if container and container.has_node("PlayerboardUI"):
|
|
var board_ui = container.get_node("PlayerboardUI")
|
|
for slot_idx in range(25):
|
|
update_board_slot(board_ui, slot_idx, new_playerboard[slot_idx])
|
|
|
|
# Update the obstacle UI setup function
|
|
func setup_obstacle_ui():
|
|
# Create the obstacle button
|
|
var obstacle_button = Button.new()
|
|
obstacle_button.text = "Place Obstacle"
|
|
obstacle_button.pressed.connect(func(): set_action_state(ActionState.PLACING_OBSTACLE))
|
|
$ActionMenu/ActionButtonContainer.add_child(obstacle_button)
|
|
|
|
# Create the rotation/orientation cycle button
|
|
var orientation_button = Button.new()
|
|
orientation_button.text = "Direction: North"
|
|
orientation_button.pressed.connect(func():
|
|
orientation_button.text = cycle_obstacle_orientation()
|
|
)
|
|
$ActionMenu/ActionButtonContainer.add_child(orientation_button)
|
|
|
|
# Create the type cycle button
|
|
var type_button = Button.new()
|
|
type_button.text = "Type: 1"
|
|
type_button.pressed.connect(func():
|
|
type_button.text = cycle_obstacle_type()
|
|
)
|
|
$ActionMenu/ActionButtonContainer.add_child(type_button)
|
|
|
|
|
|
func update_board_slot(board_ui: Node, slot_idx: int, value: int):
|
|
var slot_node = board_ui.get_node_or_null("Slot%d" % (slot_idx + 1))
|
|
if slot_node:
|
|
# Hide all tiles first
|
|
for tile in ["TileHeart", "TileDiamond", "TileStar", "TileCoin"]:
|
|
slot_node.get_node(tile).hide()
|
|
|
|
# Show appropriate tile
|
|
match value:
|
|
7: slot_node.get_node("TileHeart").show()
|
|
8: slot_node.get_node("TileDiamond").show()
|
|
9: slot_node.get_node("TileStar").show()
|
|
10: slot_node.get_node("TileCoin").show()
|
|
|
|
func update_all_players_goals():
|
|
# Only server/host should manage goals display
|
|
if not game_started:
|
|
return
|
|
|
|
var all_players = get_tree().get_nodes_in_group("Players")
|
|
all_players.sort_custom(func(a, b):
|
|
var a_str = String(a.name).get_slice("@", 0)
|
|
var b_str = String(b.name).get_slice("@", 0)
|
|
return int(a_str) < int(b_str)
|
|
)
|
|
|
|
# If we're the host, update all goals and sync to clients
|
|
if multiplayer.is_server():
|
|
# Clear all goals first
|
|
for player_idx in range(4):
|
|
var goals_grid = $AllPlayerGoals.get_child(player_idx)
|
|
for slot_idx in range(9):
|
|
var slot = goals_grid.get_child(slot_idx)
|
|
for tile in ["TileHeart", "TileDiamond", "TileStar", "TileCoin"]:
|
|
slot.get_node(tile).hide()
|
|
|
|
# Update with current goals and gather goals data
|
|
var all_goals_data = []
|
|
for i in range(min(all_players.size(), 4)):
|
|
var player = all_players[i]
|
|
if player and player.goals.size() > 0:
|
|
_update_player_goals_ui(i, player.goals)
|
|
all_goals_data.append({"player_idx": i, "goals": player.goals.duplicate()})
|
|
else:
|
|
all_goals_data.append({"player_idx": i, "goals": []})
|
|
|
|
# Sync to clients
|
|
rpc("sync_all_goals_to_clients", all_goals_data)
|
|
|
|
@rpc("reliable")
|
|
func sync_all_goals_to_clients(all_goals_data: Array):
|
|
if not multiplayer.is_server(): # Only clients should process this
|
|
# Clear all goals first
|
|
for player_idx in range(4):
|
|
var goals_grid = $AllPlayerGoals.get_child(player_idx)
|
|
for slot_idx in range(9):
|
|
var slot = goals_grid.get_child(slot_idx)
|
|
for tile in ["TileHeart", "TileDiamond", "TileStar", "TileCoin"]:
|
|
slot.get_node(tile).hide()
|
|
|
|
# Apply received goals
|
|
for goal_data in all_goals_data:
|
|
var player_idx = goal_data["player_idx"]
|
|
var goals = goal_data["goals"]
|
|
if player_idx >= 0 and player_idx < 4 and goals.size() > 0:
|
|
_update_player_goals_ui(player_idx, goals)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_player_goals(player_id: int, goals: Array):
|
|
# Update the player's goals in their node
|
|
var player = get_node_or_null(str(player_id))
|
|
if player:
|
|
player.goals = goals.duplicate()
|
|
|
|
# Only server should update the UI directly
|
|
if multiplayer.is_server():
|
|
update_all_players_goals()
|
|
|
|
# Helper function to update specific player's goals UI
|
|
func _update_player_goals_ui(player_idx: int, goals: Array):
|
|
var goals_grid = $AllPlayerGoals.get_child(player_idx)
|
|
for slot_idx in range(9):
|
|
var slot = goals_grid.get_child(slot_idx)
|
|
var goal_value = goals[slot_idx] if slot_idx < goals.size() else -1
|
|
|
|
# Hide all tiles first
|
|
for tile in ["TileHeart", "TileDiamond", "TileStar", "TileCoin"]:
|
|
slot.get_node(tile).hide()
|
|
|
|
# Show appropriate tile
|
|
match goal_value:
|
|
7: slot.get_node("TileHeart").show()
|
|
8: slot.get_node("TileDiamond").show()
|
|
9: slot.get_node("TileStar").show()
|
|
10: slot.get_node("TileCoin").show()
|
|
|
|
@rpc("any_peer")
|
|
func request_goals_from_server(requesting_peer_id: int):
|
|
if multiplayer.is_server():
|
|
var goal_index = requesting_peer_id - 1
|
|
if goal_index >= 0 and goal_index < preset_goals.size():
|
|
rpc("sync_player_goals", requesting_peer_id, preset_goals[goal_index])
|
|
|
|
# Add this function near the top with other helper functions
|
|
func initialize_random_goals(_size:int, min_value:int, max_value:int, null_count:float) -> Array:
|
|
var goals = []
|
|
var rng = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
|
|
var null_val = 0
|
|
var max_nulls = 3
|
|
|
|
const SPECIAL_VALUES = {1: 7, 2: 8, 3: 9, 4: 10}
|
|
|
|
for i in range(_size):
|
|
if null_val < max_nulls and rng.randf() < null_count:
|
|
goals.append(-1)
|
|
null_val += 1
|
|
else:
|
|
var val = rng.randi_range(min_value, max_value)
|
|
goals.append(val if not val in SPECIAL_VALUES else SPECIAL_VALUES[val])
|
|
|
|
return goals
|
|
|
|
func randomize_item_at_position(grid_position: Vector2i):
|
|
if not multiplayer.is_server() and multiplayer.get_unique_id() == multiplayer.get_remote_sender_id():
|
|
# Request server to randomize item
|
|
rpc_id(1, "request_randomize_item", grid_position)
|
|
return
|
|
|
|
var enhanced_gridmap = $EnhancedGridMap
|
|
if enhanced_gridmap:
|
|
var cell = Vector3i(grid_position.x, 1, grid_position.y)
|
|
var current_item = enhanced_gridmap.get_cell_item(cell)
|
|
|
|
# Only randomize if there's already an item
|
|
if current_item != -1:
|
|
# Generate a random item value (7-10 based on the game's item system)
|
|
var rng = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
var new_item = rng.randi_range(7, 10)
|
|
|
|
# Make sure it's different from the current item
|
|
while new_item == current_item:
|
|
new_item = rng.randi_range(7, 10)
|
|
|
|
# Update the item and sync to all clients
|
|
sync_grid_item(cell.x, cell.y, cell.z, new_item)
|
|
rpc("sync_grid_item", cell.x, cell.y, cell.z, new_item)
|
|
|
|
# Consume action points for the player who performed the action
|
|
var player
|
|
if multiplayer.get_remote_sender_id() != 0:
|
|
player = get_node_or_null(str(multiplayer.get_remote_sender_id()))
|
|
else:
|
|
player = get_node_or_null(str(multiplayer.get_unique_id()))
|
|
|
|
if player and player.has_method("consume_action_points"):
|
|
player.has_performed_action = true
|
|
player.consume_action_points(1)
|
|
player.clear_highlights()
|
|
set_action_state(ActionState.NONE)
|
|
|
|
return true
|
|
return false
|
|
|
|
@rpc("any_peer")
|
|
func request_randomize_item(grid_position: Vector2i):
|
|
if multiplayer.is_server():
|
|
# Verify request came from a valid authority
|
|
var sender_id = multiplayer.get_remote_sender_id()
|
|
var player = get_node_or_null(str(sender_id))
|
|
|
|
if player and player.is_multiplayer_authority() and player.action_points > 0:
|
|
randomize_item_at_position(grid_position)
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func sync_grid_item(x: int, y: int, z: int, item: int):
|
|
var enhanced_gridmap = $EnhancedGridMap
|
|
if enhanced_gridmap:
|
|
var cell = Vector3i(x, y, z)
|
|
|
|
# Log the change for debugging
|
|
print("Main: Setting grid item at ", cell, " to ", item, " (called by ", multiplayer.get_remote_sender_id(), ")")
|
|
|
|
# Make sure we set the cell reliably
|
|
enhanced_gridmap.set_cell_item(cell, item)
|