Attempt to using Nakama as replacement of Low-Level ENet
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
extends Node
|
||||
|
||||
# GameStateManager - Central game state management
|
||||
|
||||
signal game_started()
|
||||
signal game_state_changed()
|
||||
|
||||
@export var enable_bots: bool = true
|
||||
@export var max_players: int = 4
|
||||
|
||||
var players: Array = []
|
||||
var bots: Array = []
|
||||
var game_started_flag: bool = false
|
||||
var local_player_character: CharacterBody3D
|
||||
|
||||
func is_game_started() -> bool:
|
||||
return game_started_flag
|
||||
|
||||
func start_game():
|
||||
game_started_flag = true
|
||||
emit_signal("game_started")
|
||||
|
||||
func add_player(peer_id: int):
|
||||
if not peer_id in players:
|
||||
players.append(peer_id)
|
||||
emit_signal("game_state_changed")
|
||||
|
||||
func remove_player(peer_id: int):
|
||||
players.erase(peer_id)
|
||||
emit_signal("game_state_changed")
|
||||
|
||||
func add_bot(bot_id: int):
|
||||
if not bot_id in bots:
|
||||
bots.append(bot_id)
|
||||
players.append(bot_id)
|
||||
emit_signal("game_state_changed")
|
||||
|
||||
func remove_bot(bot_id: int):
|
||||
bots.erase(bot_id)
|
||||
players.erase(bot_id)
|
||||
emit_signal("game_state_changed")
|
||||
|
||||
func reset():
|
||||
players.clear()
|
||||
bots.clear()
|
||||
game_started_flag = false
|
||||
local_player_character = null
|
||||
@@ -0,0 +1,40 @@
|
||||
extends Node
|
||||
|
||||
# GoalManager - Manages goal generation and synchronization
|
||||
|
||||
var preset_goals: Array = []
|
||||
|
||||
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 generate_preset_goals(count: int) -> Array:
|
||||
preset_goals.clear()
|
||||
for i in range(count):
|
||||
var goals = initialize_random_goals(9, 7, 10, 1.0)
|
||||
var int_goals: Array[int] = []
|
||||
for g in goals:
|
||||
int_goals.append(g)
|
||||
preset_goals.append(int_goals)
|
||||
return preset_goals
|
||||
|
||||
func get_goals_for_player(player_index: int) -> Array:
|
||||
if player_index >= 0 and player_index < preset_goals.size():
|
||||
return preset_goals[player_index].duplicate()
|
||||
return []
|
||||
@@ -0,0 +1,56 @@
|
||||
extends Node
|
||||
|
||||
# ObstacleManager - Handles obstacle placement and management
|
||||
|
||||
enum ObstacleOrientation {
|
||||
NORTH = 0,
|
||||
EAST = 1,
|
||||
SOUTH = 2,
|
||||
WEST = 3
|
||||
}
|
||||
|
||||
var current_obstacle_orientation = ObstacleOrientation.NORTH
|
||||
var current_obstacle_item = 12
|
||||
|
||||
var gridmap_ref # Reference to EnhancedGridMap
|
||||
|
||||
func initialize(gridmap):
|
||||
gridmap_ref = gridmap
|
||||
|
||||
func place_obstacle(grid_position: Vector2i, local_player) -> bool:
|
||||
if not local_player or local_player.action_points < 1:
|
||||
return false
|
||||
|
||||
var floor_index = 3 # Always place on floor 3
|
||||
|
||||
var success = gridmap_ref.place_obstacle(
|
||||
Vector3i(grid_position.x, floor_index, grid_position.y),
|
||||
current_obstacle_item,
|
||||
current_obstacle_orientation
|
||||
)
|
||||
|
||||
if success:
|
||||
local_player.action_points -= 1
|
||||
return true
|
||||
return false
|
||||
|
||||
func cycle_obstacle_orientation() -> String:
|
||||
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]
|
||||
|
||||
func cycle_obstacle_type() -> String:
|
||||
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)
|
||||
@@ -0,0 +1,38 @@
|
||||
extends Node
|
||||
|
||||
# PlayerManager - Handles player and bot lifecycle management
|
||||
|
||||
signal player_added(peer_id, player_node)
|
||||
signal player_removed(peer_id)
|
||||
signal bot_created(bot_id, bot_node)
|
||||
signal bot_removed(bot_id)
|
||||
|
||||
var player_scene = preload("res://scenes/player.tscn")
|
||||
var connected_peer_ids = []
|
||||
|
||||
func add_player_character(peer_id: int, position: Vector2i = Vector2i.ZERO) -> Node:
|
||||
var player_character = player_scene.instantiate()
|
||||
player_character.set_multiplayer_authority(peer_id)
|
||||
player_character.name = str(peer_id)
|
||||
|
||||
connected_peer_ids.append(peer_id)
|
||||
|
||||
return player_character
|
||||
|
||||
func create_bot(bot_id: int) -> Node:
|
||||
var bot_character = player_scene.instantiate()
|
||||
bot_character.set_multiplayer_authority(1) # Server controls bots
|
||||
bot_character.name = str(bot_id)
|
||||
bot_character.is_bot = true
|
||||
|
||||
return bot_character
|
||||
|
||||
func remove_player(peer_id: int):
|
||||
connected_peer_ids.erase(peer_id)
|
||||
emit_signal("player_removed", peer_id)
|
||||
|
||||
func get_next_available_bot_id(max_players: int, current_players: Array) -> int:
|
||||
for i in range(2, max_players + 1):
|
||||
if not i in current_players:
|
||||
return i
|
||||
return -1
|
||||
@@ -0,0 +1,23 @@
|
||||
extends Node
|
||||
|
||||
# TurnManager - Manages turn-based gameplay flow
|
||||
|
||||
signal turn_changed(player_id)
|
||||
signal turn_ended()
|
||||
|
||||
var current_turn_index: int = 0
|
||||
var turn_based_mode: bool = true
|
||||
|
||||
func next_turn(players: Array):
|
||||
if turn_based_mode and players.size() > 0:
|
||||
current_turn_index = (current_turn_index + 1) % players.size()
|
||||
var next_player_id = players[current_turn_index]
|
||||
emit_signal("turn_changed", next_player_id)
|
||||
return next_player_id
|
||||
return -1
|
||||
|
||||
func end_current_turn():
|
||||
emit_signal("turn_ended")
|
||||
|
||||
func reset_turn():
|
||||
current_turn_index = -1
|
||||
@@ -0,0 +1,133 @@
|
||||
extends Node
|
||||
|
||||
# UIManager - Handles all UI setup and updates
|
||||
|
||||
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")
|
||||
]
|
||||
|
||||
# Node references - will be set by Main
|
||||
var action_menu
|
||||
var move_button
|
||||
var grab_button
|
||||
var put_button
|
||||
var randomize_button
|
||||
var arrange_button
|
||||
var playerboard_ui
|
||||
|
||||
var local_player_character
|
||||
|
||||
enum ActionState {
|
||||
NONE,
|
||||
MOVING,
|
||||
GRABBING,
|
||||
PUTTING,
|
||||
RANDOMIZING,
|
||||
ARRANGING,
|
||||
PLACING_OBSTACLE
|
||||
}
|
||||
|
||||
var current_action_state = ActionState.NONE
|
||||
|
||||
func initialize(main_node):
|
||||
# Get node references from main scene
|
||||
action_menu = main_node.get_node("ActionMenu")
|
||||
move_button = main_node.get_node("ActionMenu/ActionButtonContainer/MoveButton")
|
||||
grab_button = main_node.get_node("ActionMenu/ActionButtonContainer/GrabButton")
|
||||
put_button = main_node.get_node("ActionMenu/ActionButtonContainer/PutButton")
|
||||
randomize_button = main_node.get_node("ActionMenu/ActionButtonContainer/RandomizeButton")
|
||||
arrange_button = main_node.get_node("ActionMenu/ActionButtonContainer/ArrangeButton")
|
||||
playerboard_ui = main_node.get_node("PlayerboardUI")
|
||||
|
||||
func setup_action_buttons(action_state_callback):
|
||||
move_button.pressed.connect(func(): action_state_callback.call(ActionState.MOVING))
|
||||
grab_button.pressed.connect(func(): action_state_callback.call(ActionState.GRABBING))
|
||||
put_button.pressed.connect(func():
|
||||
if local_player_character:
|
||||
local_player_character.auto_put_item()
|
||||
)
|
||||
randomize_button.pressed.connect(func(): action_state_callback.call(ActionState.RANDOMIZING))
|
||||
arrange_button.pressed.connect(func():
|
||||
if local_player_character and local_player_character.action_points >= 2:
|
||||
action_state_callback.call(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.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 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_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 = false
|
||||
grab_button.disabled = false
|
||||
put_button.disabled = false
|
||||
arrange_button.disabled = false
|
||||
|
||||
func set_local_player(player):
|
||||
local_player_character = player
|
||||
@@ -0,0 +1,111 @@
|
||||
extends Node
|
||||
|
||||
# Standard Nakama Configuration
|
||||
const NAKAMA_SERVER_KEY = "defaultkey"
|
||||
const NAKAMA_HOST = "77.237.232.232"
|
||||
const NAKAMA_PORT = 7350
|
||||
const NAKAMA_SCHEME = "http"
|
||||
|
||||
# Core Nakama Variables
|
||||
var client: NakamaClient
|
||||
var session: NakamaSession
|
||||
var socket: NakamaSocket
|
||||
var bridge: NakamaMultiplayerBridge
|
||||
|
||||
# Signals
|
||||
signal connected_to_nakama
|
||||
signal connection_failed(error_message)
|
||||
signal match_joined(match_id)
|
||||
signal match_join_error(error_message)
|
||||
|
||||
# Current Match Info
|
||||
var current_match_id: String = ""
|
||||
|
||||
func _ready():
|
||||
# Initialize the Nakama Client
|
||||
client = Nakama.create_client(NAKAMA_SERVER_KEY, NAKAMA_HOST, NAKAMA_PORT, NAKAMA_SCHEME)
|
||||
|
||||
# Ensure we process network events
|
||||
set_process(true)
|
||||
|
||||
func _process(_delta):
|
||||
# If using the standard socket adapter, it needs polling in some versions
|
||||
if socket:
|
||||
pass
|
||||
|
||||
func connect_to_nakama_async(email: String = "", password: String = "") -> void:
|
||||
# 1. Authenticate
|
||||
if email == "":
|
||||
var device_id = OS.get_unique_id()
|
||||
# For testing, append a random number to device ID to simulate unique users on one machine
|
||||
# device_id = device_id + str(randi())
|
||||
session = await client.authenticate_device_async(device_id)
|
||||
else:
|
||||
session = await client.authenticate_email_async(email, password)
|
||||
|
||||
if session.is_exception():
|
||||
printerr("Auth Error: ", session.get_exception().message)
|
||||
emit_signal("connection_failed", session.get_exception().message)
|
||||
return
|
||||
|
||||
# 2. Connect Socket
|
||||
socket = Nakama.create_socket_from(client)
|
||||
var socket_result = await socket.connect_async(session)
|
||||
|
||||
if socket_result.is_exception():
|
||||
printerr("Socket Error: ", socket_result.get_exception().message)
|
||||
emit_signal("connection_failed", socket_result.get_exception().message)
|
||||
return
|
||||
|
||||
# 3. Initialize Multiplayer Bridge
|
||||
# This links Nakama's socket to Godot's High-Level Multiplayer API
|
||||
bridge = NakamaMultiplayerBridge.new(socket)
|
||||
|
||||
# Connect bridge signals
|
||||
bridge.match_joined.connect(_on_bridge_match_joined)
|
||||
bridge.match_join_error.connect(_on_bridge_match_join_error)
|
||||
|
||||
# CRITICAL: Set Godot's multiplayer peer to the Nakama bridge
|
||||
# This allows @rpc functions to work over Nakama
|
||||
multiplayer.set_multiplayer_peer(bridge.multiplayer_peer)
|
||||
|
||||
print("Connected to Nakama and Bridge Initialized.")
|
||||
emit_signal("connected_to_nakama")
|
||||
|
||||
# --- Match Management ---
|
||||
|
||||
func host_game():
|
||||
if not bridge:
|
||||
printerr("Cannot host: Bridge not initialized")
|
||||
return
|
||||
print("Hosting match via Nakama Bridge...")
|
||||
var result = await bridge.create_match()
|
||||
if result.is_exception():
|
||||
emit_signal("match_join_error", result.get_exception().message)
|
||||
|
||||
func join_game(match_id: String):
|
||||
if not bridge:
|
||||
printerr("Cannot join: Bridge not initialized")
|
||||
return
|
||||
print("Joining match: ", match_id)
|
||||
var result = await bridge.join_match(match_id)
|
||||
if result.is_exception():
|
||||
emit_signal("match_join_error", result.get_exception().message)
|
||||
|
||||
# --- Callbacks ---
|
||||
|
||||
func _on_bridge_match_joined() -> void:
|
||||
current_match_id = bridge.match_id
|
||||
print("Successfully joined match: ", current_match_id)
|
||||
emit_signal("match_joined", current_match_id)
|
||||
|
||||
func _on_bridge_match_join_error(error) -> void:
|
||||
printerr("Bridge failed to join match: ", error.message)
|
||||
emit_signal("match_join_error", error.message)
|
||||
|
||||
func is_connected_to_nakama() -> bool:
|
||||
return socket != null and socket.is_connected_to_host()
|
||||
|
||||
func _exit_tree():
|
||||
if socket:
|
||||
socket.close()
|
||||
@@ -0,0 +1 @@
|
||||
uid://doec8teq6vbhr
|
||||
@@ -0,0 +1,16 @@
|
||||
extends Node
|
||||
|
||||
# This script is deprecated in favor of NakamaManager.
|
||||
# The logic has been moved to allow the NakamaMultiplayerBridge to handle
|
||||
# the networking transparently.
|
||||
|
||||
func _ready():
|
||||
pass
|
||||
|
||||
func host_game():
|
||||
# Redirect to NakamaManager
|
||||
NakamaManager.host_game()
|
||||
|
||||
func join_game(match_id):
|
||||
# Redirect to NakamaManager
|
||||
NakamaManager.join_game(match_id)
|
||||
@@ -0,0 +1 @@
|
||||
uid://l4qd7n8l2hch
|
||||
Reference in New Issue
Block a user