attempt to make stop n go gamemode
This commit is contained in:
@@ -40,6 +40,8 @@ extends Control
|
|||||||
@onready var enable_timer_label = $LobbyPanel/TopBar/SettingsSection/EnableTimerLabel
|
@onready var enable_timer_label = $LobbyPanel/TopBar/SettingsSection/EnableTimerLabel
|
||||||
@onready var scarcity_option = $LobbyPanel/TopBar/SettingsSection/ScarcityOption
|
@onready var scarcity_option = $LobbyPanel/TopBar/SettingsSection/ScarcityOption
|
||||||
@onready var scarcity_label = $LobbyPanel/TopBar/SettingsSection/ScarcityLabel
|
@onready var scarcity_label = $LobbyPanel/TopBar/SettingsSection/ScarcityLabel
|
||||||
|
@onready var game_mode_option = $LobbyPanel/TopBar/SettingsSection/GameModeOption
|
||||||
|
@onready var game_mode_text_label = $LobbyPanel/TopBar/SettingsSection/GameModeTextLabel
|
||||||
|
|
||||||
# UI References - Player Slots
|
# UI References - Player Slots
|
||||||
@onready var players_container = $LobbyPanel/PlayersContainer
|
@onready var players_container = $LobbyPanel/PlayersContainer
|
||||||
@@ -145,6 +147,9 @@ func _ready():
|
|||||||
if scarcity_option:
|
if scarcity_option:
|
||||||
scarcity_option.item_selected.connect(_on_scarcity_selected)
|
scarcity_option.item_selected.connect(_on_scarcity_selected)
|
||||||
|
|
||||||
|
if game_mode_option:
|
||||||
|
game_mode_option.item_selected.connect(_on_game_mode_selected)
|
||||||
|
|
||||||
# Connect LobbyManager signals
|
# Connect LobbyManager signals
|
||||||
LobbyManager.room_list_updated.connect(_on_room_list_updated)
|
LobbyManager.room_list_updated.connect(_on_room_list_updated)
|
||||||
LobbyManager.room_joined.connect(_on_room_joined)
|
LobbyManager.room_joined.connect(_on_room_joined)
|
||||||
@@ -160,6 +165,7 @@ func _ready():
|
|||||||
LobbyManager.character_changed.connect(_on_character_changed)
|
LobbyManager.character_changed.connect(_on_character_changed)
|
||||||
LobbyManager.area_changed.connect(_on_area_changed)
|
LobbyManager.area_changed.connect(_on_area_changed)
|
||||||
LobbyManager.scarcity_mode_changed.connect(_on_scarcity_mode_changed)
|
LobbyManager.scarcity_mode_changed.connect(_on_scarcity_mode_changed)
|
||||||
|
LobbyManager.game_mode_changed.connect(_on_game_mode_changed)
|
||||||
LobbyManager.player_list_changed.connect(_update_player_slots)
|
LobbyManager.player_list_changed.connect(_update_player_slots)
|
||||||
|
|
||||||
# Connect NakamaManager signals
|
# Connect NakamaManager signals
|
||||||
@@ -354,6 +360,20 @@ func _on_scarcity_mode_changed(mode: String) -> void:
|
|||||||
if scarcity_label:
|
if scarcity_label:
|
||||||
scarcity_label.text = mode
|
scarcity_label.text = mode
|
||||||
|
|
||||||
|
func _on_game_mode_selected(index: int) -> void:
|
||||||
|
if not LobbyManager.is_host: return
|
||||||
|
var mode = game_mode_option.get_item_text(index)
|
||||||
|
LobbyManager.set_game_mode(mode)
|
||||||
|
|
||||||
|
func _on_game_mode_changed(mode: String) -> void:
|
||||||
|
if game_mode_option:
|
||||||
|
for i in range(game_mode_option.item_count):
|
||||||
|
if game_mode_option.get_item_text(i) == mode:
|
||||||
|
game_mode_option.selected = i
|
||||||
|
break
|
||||||
|
if game_mode_text_label:
|
||||||
|
game_mode_text_label.text = mode
|
||||||
|
|
||||||
|
|
||||||
func _update_random_spawn_label(enabled: bool) -> void:
|
func _update_random_spawn_label(enabled: bool) -> void:
|
||||||
if random_spawn_label:
|
if random_spawn_label:
|
||||||
@@ -430,6 +450,11 @@ func _on_room_joined(room_data: Dictionary) -> void:
|
|||||||
area_right_btn.disabled = not is_host
|
area_right_btn.disabled = not is_host
|
||||||
area_name_label.text = LobbyManager.get_selected_area()
|
area_name_label.text = LobbyManager.get_selected_area()
|
||||||
|
|
||||||
|
# Game Mode Update
|
||||||
|
if game_mode_option: game_mode_option.visible = is_host
|
||||||
|
if game_mode_text_label: game_mode_text_label.visible = not is_host
|
||||||
|
_on_game_mode_changed(LobbyManager.game_mode)
|
||||||
|
|
||||||
_update_player_slots()
|
_update_player_slots()
|
||||||
connection_status.text = "Connected to room"
|
connection_status.text = "Connected to room"
|
||||||
|
|
||||||
|
|||||||
@@ -411,6 +411,29 @@ theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
|
|||||||
theme_override_font_sizes/font_size = 11
|
theme_override_font_sizes/font_size = 11
|
||||||
text = "Normal"
|
text = "Normal"
|
||||||
|
|
||||||
|
[node name="GameModeSpacer" type="Control" parent="LobbyPanel/TopBar/SettingsSection" unique_id=1261898888]
|
||||||
|
custom_minimum_size = Vector2(15, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="GameModeOption" type="OptionButton" parent="LobbyPanel/TopBar/SettingsSection" unique_id=448678888]
|
||||||
|
custom_minimum_size = Vector2(105, 28)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("5_pc087")
|
||||||
|
theme_override_font_sizes/font_size = 11
|
||||||
|
selected = 0
|
||||||
|
item_count = 2
|
||||||
|
popup/item_0/text = "Freemode"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Stop n Go"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
|
||||||
|
[node name="GameModeTextLabel" type="Label" parent="LobbyPanel/TopBar/SettingsSection" unique_id=191738888]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
|
||||||
|
theme_override_font_sizes/font_size = 11
|
||||||
|
text = "Freemode"
|
||||||
|
|
||||||
[node name="HostBanner" type="PanelContainer" parent="LobbyPanel" unique_id=1670701936]
|
[node name="HostBanner" type="PanelContainer" parent="LobbyPanel" unique_id=1670701936]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 5
|
anchors_preset = 5
|
||||||
|
|||||||
+58
-11
@@ -10,6 +10,7 @@ var goals_cycle_manager
|
|||||||
var screen_shake_manager
|
var screen_shake_manager
|
||||||
var touch_controls
|
var touch_controls
|
||||||
var camera_context_manager
|
var camera_context_manager
|
||||||
|
var stop_n_go_manager
|
||||||
|
|
||||||
# Minimal local state
|
# Minimal local state
|
||||||
var _connection_check_timer: float = 0.0
|
var _connection_check_timer: float = 0.0
|
||||||
@@ -87,6 +88,13 @@ func _init_managers():
|
|||||||
add_child(goals_cycle_manager)
|
add_child(goals_cycle_manager)
|
||||||
goals_cycle_manager.initialize(self)
|
goals_cycle_manager.initialize(self)
|
||||||
|
|
||||||
|
# Stop n Go manager for phase-based gameplay
|
||||||
|
if LobbyManager.game_mode == "Stop n Go":
|
||||||
|
stop_n_go_manager = load("res://scripts/managers/stop_n_go_manager.gd").new()
|
||||||
|
stop_n_go_manager.name = "StopNGoManager"
|
||||||
|
add_child(stop_n_go_manager)
|
||||||
|
# No direct initialize() yet, but we'll call start_game_mode later
|
||||||
|
|
||||||
# Screen shake manager for impact feedback
|
# Screen shake manager for impact feedback
|
||||||
screen_shake_manager = load("res://scripts/managers/screen_shake.gd").new()
|
screen_shake_manager = load("res://scripts/managers/screen_shake.gd").new()
|
||||||
screen_shake_manager.name = "ScreenShakeManager"
|
screen_shake_manager.name = "ScreenShakeManager"
|
||||||
@@ -516,7 +524,10 @@ func _start_game():
|
|||||||
rpc("set_current_turn", next_player)
|
rpc("set_current_turn", next_player)
|
||||||
|
|
||||||
# Start the global match timer (this also starts the first cycle)
|
# Start the global match timer (this also starts the first cycle)
|
||||||
if goals_cycle_manager:
|
if LobbyManager.game_mode == "Stop n Go":
|
||||||
|
if stop_n_go_manager:
|
||||||
|
stop_n_go_manager.start_game_mode()
|
||||||
|
elif goals_cycle_manager:
|
||||||
var match_duration = LobbyManager.get_match_duration()
|
var match_duration = LobbyManager.get_match_duration()
|
||||||
goals_cycle_manager.start_match(float(match_duration))
|
goals_cycle_manager.start_match(float(match_duration))
|
||||||
|
|
||||||
@@ -711,7 +722,6 @@ func _create_tekton(pos: Vector2i, tekton_id: int, is_static: bool = false):
|
|||||||
# If Static, swap controller
|
# If Static, swap controller
|
||||||
if is_static:
|
if is_static:
|
||||||
# tekton.is_static_turret = true # Already set above
|
# tekton.is_static_turret = true # Already set above
|
||||||
|
|
||||||
var old_controller = tekton.get_node_or_null("TektonController")
|
var old_controller = tekton.get_node_or_null("TektonController")
|
||||||
if old_controller:
|
if old_controller:
|
||||||
old_controller.queue_free()
|
old_controller.queue_free()
|
||||||
@@ -724,7 +734,6 @@ func _create_tekton(pos: Vector2i, tekton_id: int, is_static: bool = false):
|
|||||||
print("[Main] Spawned Tekton at %s (ID: %d)" % [pos, tekton_id])
|
print("[Main] Spawned Tekton at %s (ID: %d)" % [pos, tekton_id])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func _precalculate_static_positions():
|
func _precalculate_static_positions():
|
||||||
"""Calculate and reserve Static Tekton positions early."""
|
"""Calculate and reserve Static Tekton positions early."""
|
||||||
if not multiplayer.is_server(): return
|
if not multiplayer.is_server(): return
|
||||||
@@ -803,12 +812,12 @@ func _create_static_setup(pos: Vector2i, tekton_id: int, shape_idx: int):
|
|||||||
# Position Stand
|
# Position Stand
|
||||||
if enhanced_gridmap:
|
if enhanced_gridmap:
|
||||||
# Convert grid to world
|
# Convert grid to world
|
||||||
var world_pos = Vector3(pos.x + 0.5, 0, pos.y + 0.5)
|
var world_pos = Vector3(pos.x + 0.5, 0, pos.y + 0.5)
|
||||||
if "cell_size" in enhanced_gridmap:
|
if "cell_size" in enhanced_gridmap:
|
||||||
world_pos = Vector3(
|
world_pos = Vector3(
|
||||||
pos.x * enhanced_gridmap.cell_size.x + enhanced_gridmap.cell_size.x/2,
|
pos.x * enhanced_gridmap.cell_size.x + enhanced_gridmap.cell_size.x / 2,
|
||||||
0,
|
0,
|
||||||
pos.y * enhanced_gridmap.cell_size.z + enhanced_gridmap.cell_size.z/2
|
pos.y * enhanced_gridmap.cell_size.z + enhanced_gridmap.cell_size.z / 2
|
||||||
)
|
)
|
||||||
stand.global_position = world_pos
|
stand.global_position = world_pos
|
||||||
|
|
||||||
@@ -843,7 +852,6 @@ func _create_static_setup(pos: Vector2i, tekton_id: int, shape_idx: int):
|
|||||||
_create_tekton(pos, tekton_id, true)
|
_create_tekton(pos, tekton_id, true)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Player Management
|
# Player Management
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -980,6 +988,13 @@ func sync_game_start(player_list: Array, is_turn_based: bool):
|
|||||||
TurnManager.turn_based_mode = is_turn_based
|
TurnManager.turn_based_mode = is_turn_based
|
||||||
GameStateManager.start_game()
|
GameStateManager.start_game()
|
||||||
|
|
||||||
|
if LobbyManager.game_mode == "Stop n Go":
|
||||||
|
if not stop_n_go_manager:
|
||||||
|
stop_n_go_manager = load("res://scripts/managers/stop_n_go_manager.gd").new()
|
||||||
|
stop_n_go_manager.name = "StopNGoManager"
|
||||||
|
add_child(stop_n_go_manager)
|
||||||
|
stop_n_go_manager.activate_client_side()
|
||||||
|
|
||||||
# Initialize leaderboard for all peers (after a delay to ensure players loaded)
|
# Initialize leaderboard for all peers (after a delay to ensure players loaded)
|
||||||
call_deferred("_deferred_init_leaderboard")
|
call_deferred("_deferred_init_leaderboard")
|
||||||
|
|
||||||
@@ -1354,6 +1369,9 @@ func sync_grid_item(x: int, y: int, z: int, item: int):
|
|||||||
# For simplicity, we trust the grid syncs via normal mechanisms or initial state.
|
# For simplicity, we trust the grid syncs via normal mechanisms or initial state.
|
||||||
|
|
||||||
func randomize_game_grid():
|
func randomize_game_grid():
|
||||||
|
if LobbyManager.game_mode == "Stop n Go":
|
||||||
|
return # Stop n Go manages its own arena setup
|
||||||
|
|
||||||
var enhanced_gridmap = $EnhancedGridMap
|
var enhanced_gridmap = $EnhancedGridMap
|
||||||
if enhanced_gridmap:
|
if enhanced_gridmap:
|
||||||
# Randomize Floor 1 using ScarcityController
|
# Randomize Floor 1 using ScarcityController
|
||||||
@@ -1364,6 +1382,19 @@ func randomize_game_grid():
|
|||||||
# For now, let's assume server authority + sync on connect handles it, or add sync loop if critical.
|
# For now, let's assume server authority + sync on connect handles it, or add sync loop if critical.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_full_grid_data_stop_n_go(floor0: PackedInt32Array, floor1: PackedInt32Array, cols: int, rows: int):
|
||||||
|
print("[Main] Stop n Go Sync: Changing grid size to %dx%d" % [cols, rows])
|
||||||
|
var gridmap = $EnhancedGridMap
|
||||||
|
if gridmap:
|
||||||
|
gridmap.columns = cols
|
||||||
|
gridmap.rows = rows
|
||||||
|
gridmap.clear()
|
||||||
|
gridmap.set_floor_data(0, floor0)
|
||||||
|
gridmap.set_floor_data(1, floor1)
|
||||||
|
gridmap.update_grid_data()
|
||||||
|
gridmap.initialize_astar()
|
||||||
|
|
||||||
@rpc("any_peer")
|
@rpc("any_peer")
|
||||||
func request_full_grid_sync():
|
func request_full_grid_sync():
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
@@ -1382,7 +1413,7 @@ func request_full_grid_sync():
|
|||||||
func sync_full_grid_data(data: PackedInt32Array):
|
func sync_full_grid_data(data: PackedInt32Array):
|
||||||
print("[Main] sync_full_grid_data received. Items: %d" % (data.size() / 3))
|
print("[Main] sync_full_grid_data received. Items: %d" % (data.size() / 3))
|
||||||
var enhanced_gridmap = $EnhancedGridMap
|
var enhanced_gridmap = $EnhancedGridMap
|
||||||
if not enhanced_gridmap:
|
if not enhanced_gridmap:
|
||||||
print("[Main] Error: EnhancedGridMap not found!")
|
print("[Main] Error: EnhancedGridMap not found!")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1459,6 +1490,23 @@ func _on_global_timer_updated(time_remaining: float):
|
|||||||
var minutes = int(time_remaining) / 60
|
var minutes = int(time_remaining) / 60
|
||||||
var seconds = int(time_remaining) % 60
|
var seconds = int(time_remaining) % 60
|
||||||
timer_label.text = "%d:%02d" % [minutes, seconds]
|
timer_label.text = "%d:%02d" % [minutes, seconds]
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func sync_game_end_stop_n_go(winner_id: int):
|
||||||
|
print("[STOP n GO] Game ended! Winner: ", winner_id)
|
||||||
|
var winner_name = "Player " + str(winner_id)
|
||||||
|
var player_node = get_node_or_null(str(winner_id))
|
||||||
|
if player_node:
|
||||||
|
winner_name = player_node.display_name
|
||||||
|
|
||||||
|
# Broadcast win
|
||||||
|
add_message_to_bar("WINNER", winner_name + " Reached the Finish Line!", MessageType.GOAL)
|
||||||
|
|
||||||
|
# Stop logic
|
||||||
|
if stop_n_go_manager:
|
||||||
|
stop_n_go_manager.is_active = false
|
||||||
|
|
||||||
|
# Trigger match end
|
||||||
|
_on_match_ended()
|
||||||
|
|
||||||
func _on_match_ended():
|
func _on_match_ended():
|
||||||
"""Called when the global match timer ends - show game over screen."""
|
"""Called when the global match timer ends - show game over screen."""
|
||||||
@@ -1555,7 +1603,7 @@ func _show_game_over_panel():
|
|||||||
Color(0.5, 0.5, 0.5), # 5th
|
Color(0.5, 0.5, 0.5), # 5th
|
||||||
Color(0.5, 0.5, 0.5), # 6th
|
Color(0.5, 0.5, 0.5), # 6th
|
||||||
Color(0.5, 0.5, 0.5), # 7th
|
Color(0.5, 0.5, 0.5), # 7th
|
||||||
Color(0.5, 0.5, 0.5) # 8th
|
Color(0.5, 0.5, 0.5) # 8th
|
||||||
]
|
]
|
||||||
var rank_emojis = ["🥇", "🥈", "🥉", "4th", "5th", "6th", "7th", "8th"]
|
var rank_emojis = ["🥇", "🥈", "🥉", "4th", "5th", "6th", "7th", "8th"]
|
||||||
|
|
||||||
@@ -1723,7 +1771,6 @@ func _render_leaderboard_entries(sorted_player_data: Array):
|
|||||||
# If local player is outside top 3 (index > 2), show them in 4th slot
|
# If local player is outside top 3 (index > 2), show them in 4th slot
|
||||||
# But if they are exactly 4th (index 3), it's the same as showing 4th place.
|
# But if they are exactly 4th (index 3), it's the same as showing 4th place.
|
||||||
# If they are 5th (index 4) or worse, we replace 4th place with them.
|
# If they are 5th (index 4) or worse, we replace 4th place with them.
|
||||||
|
|
||||||
if my_index > 3:
|
if my_index > 3:
|
||||||
# Show local player
|
# Show local player
|
||||||
items_to_display.append({"data": sorted_player_data[my_index], "rank": my_index + 1})
|
items_to_display.append({"data": sorted_player_data[my_index], "rank": my_index + 1})
|
||||||
|
|||||||
@@ -832,6 +832,67 @@ func _find_valid_drop_position() -> Vector2i:
|
|||||||
|
|
||||||
return Vector2i(-1, -1)
|
return Vector2i(-1, -1)
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_local")
|
||||||
|
func on_stop_phase_violation():
|
||||||
|
"""Moving during STOP phase makes you lose and scatter tiles."""
|
||||||
|
if not multiplayer.is_server(): return
|
||||||
|
|
||||||
|
print("[STOP n GO] Violation by player %s! Scattering tiles." % name)
|
||||||
|
|
||||||
|
# Stun effect
|
||||||
|
apply_stagger(2.0)
|
||||||
|
|
||||||
|
# Scatter items
|
||||||
|
var items_to_scatter = []
|
||||||
|
for i in range(playerboard.size()):
|
||||||
|
if playerboard[i] != -1:
|
||||||
|
items_to_scatter.append(playerboard[i])
|
||||||
|
playerboard[i] = -1
|
||||||
|
|
||||||
|
if items_to_scatter.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
rpc("sync_playerboard", playerboard)
|
||||||
|
|
||||||
|
# Find multiple valid drop positions around the player
|
||||||
|
var drop_positions = _find_multiple_drop_positions(items_to_scatter.size())
|
||||||
|
|
||||||
|
for i in range(items_to_scatter.size()):
|
||||||
|
var item_id = items_to_scatter[i]
|
||||||
|
var pos = drop_positions[i] if i < drop_positions.size() else _find_valid_drop_position()
|
||||||
|
|
||||||
|
if pos != Vector2i(-1, -1):
|
||||||
|
var cell = Vector3i(pos.x, 1, pos.y)
|
||||||
|
rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id)
|
||||||
|
|
||||||
|
NotificationManager.send_message(self, "STOP VIOLATION! Tiles scattered!", NotificationManager.MessageType.WARNING)
|
||||||
|
|
||||||
|
func _find_multiple_drop_positions(count: int) -> Array:
|
||||||
|
var positions = []
|
||||||
|
var neighbors = enhanced_gridmap.get_neighbors(current_position, 0)
|
||||||
|
neighbors.shuffle()
|
||||||
|
|
||||||
|
# Also consider neighbors of neighbors (radius 2) for more scattering
|
||||||
|
var candidates = []
|
||||||
|
for n in neighbors:
|
||||||
|
candidates.append(n.position)
|
||||||
|
var n2 = enhanced_gridmap.get_neighbors(n.position, 0)
|
||||||
|
for nn in n2:
|
||||||
|
if not nn.position in candidates:
|
||||||
|
candidates.append(nn.position)
|
||||||
|
|
||||||
|
candidates.shuffle()
|
||||||
|
|
||||||
|
for pos in candidates:
|
||||||
|
if positions.size() >= count: break
|
||||||
|
|
||||||
|
var item_cell = Vector3i(pos.x, 1, pos.y)
|
||||||
|
if enhanced_gridmap.get_cell_item(item_cell) == -1:
|
||||||
|
if not is_position_occupied(pos):
|
||||||
|
positions.append(pos)
|
||||||
|
|
||||||
|
return positions
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Targeting Action
|
# Targeting Action
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -1200,6 +1261,15 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
|
|||||||
if is_carrying_tekton and is_instance_valid(carried_tekton):
|
if is_carrying_tekton and is_instance_valid(carried_tekton):
|
||||||
carried_tekton.current_position = current_position
|
carried_tekton.current_position = current_position
|
||||||
|
|
||||||
|
# Stop n Go Win Check
|
||||||
|
if LobbyManager.game_mode == "Stop n Go":
|
||||||
|
var sng_main = get_tree().root.get_node_or_null("Main")
|
||||||
|
if sng_main:
|
||||||
|
var sng_manager = sng_main.get_node_or_null("StopNGoManager")
|
||||||
|
if sng_manager and sng_manager.has_method("check_win_condition"):
|
||||||
|
if sng_manager.check_win_condition(name.to_int(), current_position):
|
||||||
|
sng_main.rpc("sync_game_end_stop_n_go", name.to_int())
|
||||||
|
|
||||||
# FORCE SNAP: Update target visual position to the perfect grid center
|
# FORCE SNAP: Update target visual position to the perfect grid center
|
||||||
# This ensures that when interpolation resumes (in _process), it pulls to the correct spot
|
# This ensures that when interpolation resumes (in _process), it pulls to the correct spot
|
||||||
target_visual_position = grid_to_world(current_position)
|
target_visual_position = grid_to_world(current_position)
|
||||||
|
|||||||
@@ -41,9 +41,14 @@ signal scarcity_mode_changed(mode: String)
|
|||||||
# Character and area selection
|
# Character and area selection
|
||||||
var available_characters: Array[String] = ["Copper", "Dabro", "Gatot", "Pip", "Random"]
|
var available_characters: Array[String] = ["Copper", "Dabro", "Gatot", "Pip", "Random"]
|
||||||
var available_areas: Array[String] = ["Desert", "Forest", "City", "Factory"]
|
var available_areas: Array[String] = ["Desert", "Forest", "City", "Factory"]
|
||||||
|
var available_game_modes: Array[String] = ["Freemode", "Stop n Go"]
|
||||||
var selected_area: String = "Desert" # Host-controlled
|
var selected_area: String = "Desert" # Host-controlled
|
||||||
|
var game_mode: String = "Freemode" # Host-controlled
|
||||||
var local_character_index: int = 0 # Local player's character index
|
var local_character_index: int = 0 # Local player's character index
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
signal game_mode_changed(mode: String)
|
||||||
|
|
||||||
# Ready to start game check
|
# Ready to start game check
|
||||||
var _all_ready: bool = false
|
var _all_ready: bool = false
|
||||||
|
|
||||||
@@ -345,6 +350,29 @@ func sync_area(area_name: String) -> void:
|
|||||||
selected_area = area_name
|
selected_area = area_name
|
||||||
emit_signal("area_changed", area_name)
|
emit_signal("area_changed", area_name)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Game Mode Selection (Host Only)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
func set_game_mode(mode: String) -> void:
|
||||||
|
"""Host sets the game mode. Syncs to all clients."""
|
||||||
|
if not is_host:
|
||||||
|
push_warning("Only host can change game mode")
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode not in available_game_modes:
|
||||||
|
push_error("Invalid game mode: " + mode)
|
||||||
|
return
|
||||||
|
|
||||||
|
game_mode = mode
|
||||||
|
rpc("sync_game_mode", mode)
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_game_mode(mode: String) -> void:
|
||||||
|
"""Sync game mode selection from host to clients."""
|
||||||
|
game_mode = mode
|
||||||
|
emit_signal("game_mode_changed", mode)
|
||||||
|
|
||||||
func start_game() -> void:
|
func start_game() -> void:
|
||||||
"""Host triggers game start (transitions all players to main.tscn)."""
|
"""Host triggers game start (transitions all players to main.tscn)."""
|
||||||
if not is_host:
|
if not is_host:
|
||||||
@@ -361,6 +389,8 @@ func start_game() -> void:
|
|||||||
rpc("sync_enable_cycle_timer", enable_cycle_timer)
|
rpc("sync_enable_cycle_timer", enable_cycle_timer)
|
||||||
# Sync scarcity mode
|
# Sync scarcity mode
|
||||||
rpc("sync_scarcity_mode", scarcity_mode)
|
rpc("sync_scarcity_mode", scarcity_mode)
|
||||||
|
# Sync game mode
|
||||||
|
rpc("sync_game_mode", game_mode)
|
||||||
|
|
||||||
# Notify all clients to start
|
# Notify all clients to start
|
||||||
rpc("_on_game_starting")
|
rpc("_on_game_starting")
|
||||||
@@ -484,3 +514,4 @@ func reset() -> void:
|
|||||||
selected_area = "Desert"
|
selected_area = "Desert"
|
||||||
local_character_index = 0 # Default to "Copper"
|
local_character_index = 0 # Default to "Copper"
|
||||||
enable_cycle_timer = false
|
enable_cycle_timer = false
|
||||||
|
game_mode = "Freemode"
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
|||||||
if player.get("is_frozen"):
|
if player.get("is_frozen"):
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
# Stop n Go Mode Violation Check
|
||||||
|
var main = player.get_tree().root.get_node_or_null("Main")
|
||||||
|
if main:
|
||||||
|
var manager = main.get_node_or_null("StopNGoManager")
|
||||||
|
if manager and manager.has_method("check_movement_violation"):
|
||||||
|
if manager.check_movement_violation(player.name.to_int(), player.current_position, grid_position):
|
||||||
|
return false
|
||||||
|
|
||||||
var distance: int
|
var distance: int
|
||||||
if use_diagonal_movement:
|
if use_diagonal_movement:
|
||||||
distance = max(abs(grid_position.x - player.current_position.x), abs(grid_position.y - player.current_position.y))
|
distance = max(abs(grid_position.x - player.current_position.x), abs(grid_position.y - player.current_position.y))
|
||||||
@@ -100,15 +108,17 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
|||||||
|
|
||||||
rotate_towards_target(grid_position)
|
rotate_towards_target(grid_position)
|
||||||
|
|
||||||
if player.is_multiplayer_authority() and player.has_method("sync_walk_animation"):
|
if player.is_multiplayer_authority() and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
player.rpc("sync_walk_animation")
|
if player.has_method("sync_walk_animation"):
|
||||||
|
player.rpc("sync_walk_animation")
|
||||||
|
|
||||||
var path = [Vector2(player.current_position.x, player.current_position.y), Vector2(grid_position.x, grid_position.y)]
|
var path = [Vector2(player.current_position.x, player.current_position.y), Vector2(grid_position.x, grid_position.y)]
|
||||||
path.pop_front()
|
path.pop_front()
|
||||||
|
|
||||||
current_move_direction = grid_position - player.current_position
|
current_move_direction = grid_position - player.current_position
|
||||||
|
|
||||||
player.rpc("start_movement_along_path", path, not (player.is_bot or player.is_in_group("Bots")))
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
player.rpc("start_movement_along_path", path, not (player.is_bot or player.is_in_group("Bots")))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -419,8 +429,8 @@ func _is_position_blocked_by_physics(target_pos: Vector2i) -> bool:
|
|||||||
var space_state = player.get_world_3d().direct_space_state
|
var space_state = player.get_world_3d().direct_space_state
|
||||||
var center_x = target_pos.x + 0.5
|
var center_x = target_pos.x + 0.5
|
||||||
var center_z = target_pos.y + 0.5
|
var center_z = target_pos.y + 0.5
|
||||||
var from = Vector3(center_x, 1.0, center_z)
|
var from = Vector3(center_x, 1.0, center_z)
|
||||||
var to = Vector3(center_x, 0.1, center_z)
|
var to = Vector3(center_x, 0.1, center_z)
|
||||||
|
|
||||||
var query = PhysicsRayQueryParameters3D.create(from, to)
|
var query = PhysicsRayQueryParameters3D.create(from, to)
|
||||||
query.collide_with_areas = false
|
query.collide_with_areas = false
|
||||||
|
|||||||
@@ -90,6 +90,13 @@ func grab_item(grid_position: Vector2i) -> bool:
|
|||||||
else:
|
else:
|
||||||
# Normal Tile: Add to playerboard
|
# Normal Tile: Add to playerboard
|
||||||
player.playerboard[target_slot] = item
|
player.playerboard[target_slot] = item
|
||||||
|
|
||||||
|
# Stop n Go Mission Progress
|
||||||
|
var sng_main = player.get_tree().root.get_node_or_null("Main")
|
||||||
|
if sng_main:
|
||||||
|
var sng_manager = sng_main.get_node_or_null("StopNGoManager")
|
||||||
|
if sng_manager and sng_manager.has_method("update_mission_progress"):
|
||||||
|
sng_manager.update_mission_progress(player.name.to_int(), item)
|
||||||
|
|
||||||
# Update UI immediately for responsiveness
|
# Update UI immediately for responsiveness
|
||||||
|
|
||||||
@@ -174,6 +181,11 @@ func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
|||||||
# Do not add to playerboard
|
# Do not add to playerboard
|
||||||
else:
|
else:
|
||||||
player.playerboard[target_slot] = item_id
|
player.playerboard[target_slot] = item_id
|
||||||
|
|
||||||
|
# Stop n Go Mission Progress
|
||||||
|
var sng_manager = main.get_node_or_null("StopNGoManager")
|
||||||
|
if sng_manager and sng_manager.has_method("update_mission_progress"):
|
||||||
|
sng_manager.update_mission_progress(player.name.to_int(), item_id)
|
||||||
|
|
||||||
# 3c. Broadcast the new playerboard state to all clients
|
# 3c. Broadcast the new playerboard state to all clients
|
||||||
var peer_id = player.name.to_int()
|
var peer_id = player.name.to_int()
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func add_powerup_from_item(item_id: int):
|
|||||||
emit_signal("powerup_unlocked", effect, powerup_levels[effect])
|
emit_signal("powerup_unlocked", effect, powerup_levels[effect])
|
||||||
print("Player %s leveled up %s to Lvl %d" % [player.name, SpecialEffect.keys()[effect], powerup_levels[effect]])
|
print("Player %s leveled up %s to Lvl %d" % [player.name, SpecialEffect.keys()[effect], powerup_levels[effect]])
|
||||||
|
|
||||||
if player.is_multiplayer_authority():
|
if player.is_multiplayer_authority() and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
rpc("sync_inventory_add", effect, powerup_levels[effect])
|
rpc("sync_inventory_add", effect, powerup_levels[effect])
|
||||||
|
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
@@ -254,7 +254,7 @@ func activate_effect(effect: int, target_player: Node3D = null):
|
|||||||
_execute_invisible_mode(player)
|
_execute_invisible_mode(player)
|
||||||
|
|
||||||
# Play generic cast animation or sound?
|
# Play generic cast animation or sound?
|
||||||
if player.is_multiplayer_authority():
|
if player.is_multiplayer_authority() and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
player.rpc("trigger_screen_shake", "light")
|
player.rpc("trigger_screen_shake", "light")
|
||||||
# Sync cooldown to others not strictly needed unless UI shows it?
|
# Sync cooldown to others not strictly needed unless UI shows it?
|
||||||
# Probably local UI only.
|
# Probably local UI only.
|
||||||
@@ -308,7 +308,8 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO):
|
|||||||
|
|
||||||
# If inside square radius
|
# If inside square radius
|
||||||
if dx <= radius and dy <= radius:
|
if dx <= radius and dy <= radius:
|
||||||
p.rpc("apply_slow_effect", FREEZE_SLOW_DURATION)
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
p.rpc("apply_slow_effect", FREEZE_SLOW_DURATION)
|
||||||
NotificationManager.send_message(p, "Caught in Freeze Zone!", NotificationManager.MessageType.WARNING)
|
NotificationManager.send_message(p, "Caught in Freeze Zone!", NotificationManager.MessageType.WARNING)
|
||||||
if p != player: # Don't score for freezing self (unless desired?) - Assuming enemies
|
if p != player: # Don't score for freezing self (unless desired?) - Assuming enemies
|
||||||
hit_count += 1
|
hit_count += 1
|
||||||
@@ -336,7 +337,8 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO):
|
|||||||
var current_item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
|
var current_item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
|
||||||
if current_item != 4: # 4 is Wall Block
|
if current_item != 4: # 4 is Wall Block
|
||||||
# Use Item 12 (Blue Freeze Tile) on Layer 0 (Floor)
|
# Use Item 12 (Blue Freeze Tile) on Layer 0 (Floor)
|
||||||
main.rpc("sync_grid_item", pos.x, 0, pos.y, 12)
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
main.rpc("sync_grid_item", pos.x, 0, pos.y, 12)
|
||||||
|
|
||||||
# Cleanup visual timer (managed locally by author)
|
# Cleanup visual timer (managed locally by author)
|
||||||
get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func():
|
get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func():
|
||||||
@@ -404,7 +406,7 @@ func _execute_block_floor(target_pos: Vector2i):
|
|||||||
|
|
||||||
if player.is_multiplayer_authority():
|
if player.is_multiplayer_authority():
|
||||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||||
if main:
|
if main and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
main.rpc("sync_grid_item", block_pos.x, block_pos.y, block_pos.z, 4)
|
main.rpc("sync_grid_item", block_pos.x, block_pos.y, block_pos.z, 4)
|
||||||
|
|
||||||
# Record for restoration
|
# Record for restoration
|
||||||
@@ -462,7 +464,7 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true):
|
|||||||
|
|
||||||
if player.is_multiplayer_authority():
|
if player.is_multiplayer_authority():
|
||||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||||
if main:
|
if main and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id)
|
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
extends Node
|
||||||
|
class_name StopNGoManager
|
||||||
|
|
||||||
|
# StopNGoManager - Handles phase transitions, missions, and movement penalties
|
||||||
|
|
||||||
|
signal phase_changed(new_phase: String, remaining_time: float)
|
||||||
|
signal mission_status_updated(player_id: int, completed: bool)
|
||||||
|
signal player_penalized(player_id: int)
|
||||||
|
|
||||||
|
enum Phase {GO, STOP}
|
||||||
|
|
||||||
|
const GO_DURATION: float = 12.0
|
||||||
|
const STOP_DURATION: float = 6.0
|
||||||
|
|
||||||
|
var current_phase: Phase = Phase.GO
|
||||||
|
var phase_timer: float = GO_DURATION
|
||||||
|
var is_active: bool = false
|
||||||
|
|
||||||
|
var player_missions: Dictionary = {} # player_id -> {tile_id: count, current: count}
|
||||||
|
var finish_line_x: int = 21 # Right side of the map for win condition
|
||||||
|
|
||||||
|
# Tile IDs (Assumed based on analysis)
|
||||||
|
const TILE_WALKABLE = 0
|
||||||
|
const TILE_SAFE = 2 # Green Safe Zone
|
||||||
|
const TILE_OBSTACLE = 4 # Black Obstacle
|
||||||
|
|
||||||
|
var hud_layer: CanvasLayer
|
||||||
|
var phase_label: Label
|
||||||
|
var mission_label: Label
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
set_process(false)
|
||||||
|
_setup_hud()
|
||||||
|
|
||||||
|
func _setup_hud():
|
||||||
|
hud_layer = CanvasLayer.new()
|
||||||
|
hud_layer.visible = false
|
||||||
|
add_child(hud_layer)
|
||||||
|
|
||||||
|
var vbox = VBoxContainer.new()
|
||||||
|
vbox.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||||
|
vbox.offset_right = -20
|
||||||
|
vbox.offset_top = 100
|
||||||
|
hud_layer.add_child(vbox)
|
||||||
|
|
||||||
|
# Style for HUD
|
||||||
|
var style = StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0, 0, 0, 0.4)
|
||||||
|
style.content_margin_left = 10
|
||||||
|
style.content_margin_top = 10
|
||||||
|
style.content_margin_right = 10
|
||||||
|
style.content_margin_bottom = 10
|
||||||
|
|
||||||
|
var panel = PanelContainer.new()
|
||||||
|
panel.add_theme_stylebox_override("panel", style)
|
||||||
|
vbox.add_child(panel)
|
||||||
|
|
||||||
|
var inner_vbox = VBoxContainer.new()
|
||||||
|
panel.add_child(inner_vbox)
|
||||||
|
|
||||||
|
phase_label = Label.new()
|
||||||
|
phase_label.text = "PHASE: GO"
|
||||||
|
phase_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
|
phase_label.add_theme_font_size_override("font_size", 32)
|
||||||
|
phase_label.add_theme_color_override("font_color", Color.GREEN)
|
||||||
|
inner_vbox.add_child(phase_label)
|
||||||
|
|
||||||
|
mission_label = Label.new()
|
||||||
|
mission_label.text = "MISSION: Collect 3 Hearts"
|
||||||
|
mission_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
|
inner_vbox.add_child(mission_label)
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
if not is_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
if multiplayer.is_server():
|
||||||
|
phase_timer -= delta
|
||||||
|
if phase_timer <= 0:
|
||||||
|
if current_phase == Phase.GO:
|
||||||
|
_start_phase(Phase.STOP)
|
||||||
|
else:
|
||||||
|
_start_phase(Phase.GO)
|
||||||
|
|
||||||
|
# Update HUD locally
|
||||||
|
_update_hud_visuals()
|
||||||
|
|
||||||
|
func _update_hud_visuals():
|
||||||
|
var phase_name = "GO" if current_phase == Phase.GO else "STOP"
|
||||||
|
if phase_label:
|
||||||
|
phase_label.text = "PHASE: %s (%.0fs)" % [phase_name, max(0, phase_timer)]
|
||||||
|
phase_label.add_theme_color_override("font_color", Color.GREEN if current_phase == Phase.GO else Color.RED)
|
||||||
|
|
||||||
|
var my_id = multiplayer.get_unique_id()
|
||||||
|
if mission_label and player_missions.has(my_id):
|
||||||
|
var mission = player_missions[my_id]
|
||||||
|
mission_label.text = "Tiles: %d / %d" % [mission["current"], mission["required"]]
|
||||||
|
if mission["current"] >= mission["required"]:
|
||||||
|
mission_label.text = "MISSION COMPLETE! REACH FINISH!"
|
||||||
|
mission_label.add_theme_color_override("font_color", Color.GOLD)
|
||||||
|
|
||||||
|
func activate_client_side():
|
||||||
|
is_active = true
|
||||||
|
if hud_layer:
|
||||||
|
hud_layer.visible = true
|
||||||
|
set_process(true)
|
||||||
|
|
||||||
|
func start_game_mode():
|
||||||
|
activate_client_side()
|
||||||
|
if multiplayer.is_server():
|
||||||
|
_setup_arena()
|
||||||
|
_assign_missions()
|
||||||
|
_start_phase(Phase.GO)
|
||||||
|
|
||||||
|
func _start_phase(phase: Phase):
|
||||||
|
current_phase = phase
|
||||||
|
phase_timer = GO_DURATION if phase == Phase.GO else STOP_DURATION
|
||||||
|
|
||||||
|
var phase_name = "GO" if phase == Phase.GO else "STOP"
|
||||||
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
rpc("sync_phase", phase_name, phase_timer)
|
||||||
|
emit_signal("phase_changed", phase_name, phase_timer)
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_phase(phase_name: String, duration: float):
|
||||||
|
current_phase = Phase.GO if phase_name == "GO" else Phase.STOP
|
||||||
|
phase_timer = duration
|
||||||
|
|
||||||
|
func _setup_arena():
|
||||||
|
var gridmap = get_node("/root/Main/EnhancedGridMap")
|
||||||
|
if not gridmap: return
|
||||||
|
|
||||||
|
print("[StopNGo] Setting up 22x10 Accurate Horizontal Arena...")
|
||||||
|
# Set Size for Stop n Go
|
||||||
|
gridmap.columns = 22
|
||||||
|
gridmap.rows = 10
|
||||||
|
|
||||||
|
# Clear existing items on all layers
|
||||||
|
gridmap.clear()
|
||||||
|
|
||||||
|
# Create bands based on X (Horizontal Progress)
|
||||||
|
for x in range(gridmap.columns):
|
||||||
|
var tile_id = TILE_WALKABLE
|
||||||
|
# Green Safe Zones at X=7,8 and X=14,15
|
||||||
|
if x == 7 or x == 8 or x == 14 or x == 15:
|
||||||
|
tile_id = TILE_SAFE
|
||||||
|
|
||||||
|
for z in range(gridmap.rows):
|
||||||
|
gridmap.set_cell_item(Vector3i(x, 0, z), tile_id)
|
||||||
|
|
||||||
|
# Place Specific Obstacles (Black Bars) to match user images
|
||||||
|
# Lane 1 (X=2..6) - Vertical bar at X=4
|
||||||
|
for z in range(0, 4):
|
||||||
|
gridmap.set_cell_item(Vector3i(4, 0, z), TILE_OBSTACLE)
|
||||||
|
for z in range(6, 10):
|
||||||
|
gridmap.set_cell_item(Vector3i(4, 0, z), TILE_OBSTACLE)
|
||||||
|
|
||||||
|
# Lane 2 (X=9..13) - Vertical bar at X=11
|
||||||
|
for z in range(3, 7):
|
||||||
|
gridmap.set_cell_item(Vector3i(11, 0, z), TILE_OBSTACLE)
|
||||||
|
|
||||||
|
# Lane 3 (X=16..20) - L-shape at top
|
||||||
|
for z in range(0, 3):
|
||||||
|
gridmap.set_cell_item(Vector3i(18, 0, z), TILE_OBSTACLE)
|
||||||
|
gridmap.set_cell_item(Vector3i(19, 0, 2), TILE_OBSTACLE)
|
||||||
|
gridmap.set_cell_item(Vector3i(20, 0, 2), TILE_OBSTACLE)
|
||||||
|
|
||||||
|
# Another bar at bottom of Lane 3
|
||||||
|
for z in range(6, 9):
|
||||||
|
gridmap.set_cell_item(Vector3i(18, 0, z), TILE_OBSTACLE)
|
||||||
|
|
||||||
|
gridmap.update_grid_data()
|
||||||
|
gridmap.initialize_astar()
|
||||||
|
|
||||||
|
# Spawn tiles for mission (Heart = 7)
|
||||||
|
if multiplayer.is_server():
|
||||||
|
_spawn_mission_tiles()
|
||||||
|
|
||||||
|
# Sync the WHOLE grid to all clients to ensure size and stripes are correct
|
||||||
|
var main = get_node("/root/Main")
|
||||||
|
if main and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
# Gather all floor 0 and floor 1 data
|
||||||
|
var floor0_data = gridmap.get_floor_data(0)
|
||||||
|
var floor1_data = gridmap.get_floor_data(1)
|
||||||
|
main.rpc("sync_full_grid_data_stop_n_go", floor0_data, floor1_data, gridmap.columns, gridmap.rows)
|
||||||
|
|
||||||
|
func _spawn_mission_tiles():
|
||||||
|
var gridmap = get_node("/root/Main/EnhancedGridMap")
|
||||||
|
var count = 0
|
||||||
|
while count < 40: # Spawn plenty of hearts
|
||||||
|
var x = randi() % gridmap.columns
|
||||||
|
var z = randi() % gridmap.rows
|
||||||
|
# Only spawn in walkable areas, not safe zones or start/finish
|
||||||
|
if gridmap.get_cell_item(Vector3i(x, 0, z)) == TILE_WALKABLE:
|
||||||
|
if gridmap.get_cell_item(Vector3i(x, 1, z)) == -1:
|
||||||
|
gridmap.set_cell_item(Vector3i(x, 1, z), ScarcityModel.TILE_HEART)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
func _assign_missions():
|
||||||
|
# Each player needs to collect 3 specific tiles (e.g. Heart)
|
||||||
|
var players = GameStateManager.players
|
||||||
|
for p_id in players:
|
||||||
|
player_missions[p_id] = {
|
||||||
|
"target_tile": ScarcityModel.TILE_HEART,
|
||||||
|
"required": 3,
|
||||||
|
"current": 0
|
||||||
|
}
|
||||||
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
rpc("sync_missions", player_missions)
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_missions(missions: Dictionary):
|
||||||
|
player_missions = missions
|
||||||
|
|
||||||
|
func check_movement_violation(player_id: int, from: Vector2i, to: Vector2i) -> bool:
|
||||||
|
"""Check if movement is illegal (during STOP phase and not in safe zone)."""
|
||||||
|
if current_phase == Phase.STOP:
|
||||||
|
var main = get_node("/root/Main")
|
||||||
|
var gridmap = main.get_node("EnhancedGridMap") if main else null
|
||||||
|
if gridmap:
|
||||||
|
var tile_from = gridmap.get_cell_item(Vector3i(from.x, 0, from.y))
|
||||||
|
if tile_from != TILE_SAFE:
|
||||||
|
_penalize_player(player_id)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _penalize_player(player_id: int):
|
||||||
|
if not multiplayer.is_server(): return
|
||||||
|
|
||||||
|
var main = get_node("/root/Main")
|
||||||
|
if not main: return
|
||||||
|
|
||||||
|
var player_node = main.get_node_or_null(str(player_id))
|
||||||
|
if player_node:
|
||||||
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
player_node.rpc("on_stop_phase_violation")
|
||||||
|
emit_signal("player_penalized", player_id)
|
||||||
|
|
||||||
|
func update_mission_progress(player_id: int, tile_id: int):
|
||||||
|
if not player_missions.has(player_id): return
|
||||||
|
|
||||||
|
var mission = player_missions[player_id]
|
||||||
|
if tile_id == mission["target_tile"]:
|
||||||
|
mission["current"] = min(mission["current"] + 1, mission["required"])
|
||||||
|
|
||||||
|
if mission["current"] >= mission["required"]:
|
||||||
|
emit_signal("mission_status_updated", player_id, true)
|
||||||
|
|
||||||
|
if multiplayer.is_server() and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
rpc("sync_mission_progress", player_id, mission["current"])
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func sync_mission_progress(player_id: int, current: int):
|
||||||
|
if player_missions.has(player_id):
|
||||||
|
player_missions[player_id]["current"] = current
|
||||||
|
|
||||||
|
func check_win_condition(player_id: int, position: Vector2i) -> bool:
|
||||||
|
if not player_missions.has(player_id): return false
|
||||||
|
|
||||||
|
var mission = player_missions[player_id]
|
||||||
|
if mission["current"] >= mission["required"]:
|
||||||
|
# Win when reaching X >= 21
|
||||||
|
if position.x >= finish_line_x:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://kiv6fpqym1c0
|
||||||
@@ -51,15 +51,16 @@ func connect_to_nakama_async(email: String = "", password: String = "") -> bool:
|
|||||||
# 1. Authenticate
|
# 1. Authenticate
|
||||||
if email == "":
|
if email == "":
|
||||||
var device_id = OS.get_unique_id()
|
var device_id = OS.get_unique_id()
|
||||||
# For testing, append a random number to device ID to simulate unique users on one machine
|
# Use a more stable ID for testing instead of randi() every call
|
||||||
device_id = device_id + str(randi())
|
# If you need multiple clients on one machine, consider a command line arg or config
|
||||||
session = await client.authenticate_device_async(device_id)
|
session = await client.authenticate_device_async(device_id)
|
||||||
else:
|
else:
|
||||||
session = await client.authenticate_email_async(email, password)
|
session = await client.authenticate_email_async(email, password)
|
||||||
|
|
||||||
if session.is_exception():
|
if session.is_exception():
|
||||||
printerr("Auth Error: ", session.get_exception().message)
|
var err = session.get_exception()
|
||||||
emit_signal("connection_failed", session.get_exception().message)
|
printerr("[NakamaManager] Auth Error: %s (Code: %s)" % [err.message, err.status_code])
|
||||||
|
emit_signal("connection_failed", err.message)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# 2. Connect Socket
|
# 2. Connect Socket
|
||||||
@@ -67,8 +68,9 @@ func connect_to_nakama_async(email: String = "", password: String = "") -> bool:
|
|||||||
var socket_result = await socket.connect_async(session)
|
var socket_result = await socket.connect_async(session)
|
||||||
|
|
||||||
if socket_result.is_exception():
|
if socket_result.is_exception():
|
||||||
printerr("Socket Error: ", socket_result.get_exception().message)
|
var err = socket_result.get_exception()
|
||||||
emit_signal("connection_failed", socket_result.get_exception().message)
|
printerr("[NakamaManager] Socket Error: %s (Code: %s)" % [err.message, err.status_code])
|
||||||
|
emit_signal("connection_failed", err.message)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# 3. Initialize Multiplayer Bridge
|
# 3. Initialize Multiplayer Bridge
|
||||||
|
|||||||
Reference in New Issue
Block a user