feat: Implement PortalDoor functionality and integrate PortalModeManager for the Tekton Doors game mode.
This commit is contained in:
+30
-1
@@ -12,6 +12,8 @@ var touch_controls
|
|||||||
var camera_context_manager
|
var camera_context_manager
|
||||||
var stop_n_go_manager
|
var stop_n_go_manager
|
||||||
var stop_n_go_winner_id: int = -1 # Track who finished first in Stop n Go mode
|
var stop_n_go_winner_id: int = -1 # Track who finished first in Stop n Go mode
|
||||||
|
var portal_mode_winner_id: int = -1
|
||||||
|
var is_match_ended: bool = false
|
||||||
var obstacle_manager
|
var obstacle_manager
|
||||||
var portal_mode_manager
|
var portal_mode_manager
|
||||||
|
|
||||||
@@ -1758,6 +1760,27 @@ func sync_game_end_stop_n_go(winner_id: int):
|
|||||||
# Trigger match end
|
# Trigger match end
|
||||||
_on_match_ended()
|
_on_match_ended()
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func sync_game_end_portal_mode(winner_id: int):
|
||||||
|
print("[TEKTON DOORS] Game ended! Winner: ", winner_id)
|
||||||
|
portal_mode_winner_id = 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("MATCH COMPLETE", winner_name + " Wins with 8 Missions!", MessageType.GOAL)
|
||||||
|
|
||||||
|
# Stop logic
|
||||||
|
if portal_mode_manager:
|
||||||
|
if portal_mode_manager.swap_timer: portal_mode_manager.swap_timer.stop()
|
||||||
|
if portal_mode_manager.tile_refresh_timer: portal_mode_manager.tile_refresh_timer.stop()
|
||||||
|
|
||||||
|
# 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."""
|
||||||
print("[Main] Match ended! Showing game over screen...")
|
print("[Main] Match ended! Showing game over screen...")
|
||||||
@@ -1846,13 +1869,19 @@ func _show_game_over_panel():
|
|||||||
"score": goals_cycle_manager.get_player_score(p.name.to_int()) if goals_cycle_manager else 0
|
"score": goals_cycle_manager.get_player_score(p.name.to_int()) if goals_cycle_manager else 0
|
||||||
})
|
})
|
||||||
|
|
||||||
# Custom Sort for Stop n Go: Winner always first
|
# Custom Sort for Stop n Go and Tekton Doors: Winner always first
|
||||||
if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1:
|
if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1:
|
||||||
player_scores.sort_custom(func(a, b):
|
player_scores.sort_custom(func(a, b):
|
||||||
if a.peer_id == stop_n_go_winner_id: return true
|
if a.peer_id == stop_n_go_winner_id: return true
|
||||||
if b.peer_id == stop_n_go_winner_id: return false
|
if b.peer_id == stop_n_go_winner_id: return false
|
||||||
return a.score > b.score
|
return a.score > b.score
|
||||||
)
|
)
|
||||||
|
elif LobbyManager.game_mode == "Tekton Doors" and portal_mode_winner_id != -1:
|
||||||
|
player_scores.sort_custom(func(a, b):
|
||||||
|
if a.peer_id == portal_mode_winner_id: return true
|
||||||
|
if b.peer_id == portal_mode_winner_id: return false
|
||||||
|
return a.score > b.score
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
player_scores.sort_custom(func(a, b): return a.score > b.score)
|
player_scores.sort_custom(func(a, b): return a.score > b.score)
|
||||||
|
|
||||||
|
|||||||
@@ -1345,6 +1345,13 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
|
|||||||
if sng_manager.check_win_condition(name.to_int(), current_position):
|
if sng_manager.check_win_condition(name.to_int(), current_position):
|
||||||
sng_main.rpc("sync_game_end_stop_n_go", name.to_int())
|
sng_main.rpc("sync_game_end_stop_n_go", name.to_int())
|
||||||
|
|
||||||
|
# Tekton Doors Win Check
|
||||||
|
elif LobbyManager.game_mode == "Tekton Doors":
|
||||||
|
var main_node = get_tree().root.get_node_or_null("Main")
|
||||||
|
if main_node and main_node.portal_mode_manager:
|
||||||
|
if main_node.portal_mode_manager.check_win_condition(name.to_int(), current_position):
|
||||||
|
main_node.rpc("sync_game_end_portal_mode", 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)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ emission = Color(0.0, 0.4, 1.0, 1)
|
|||||||
emission_energy_multiplier = 2.0
|
emission_energy_multiplier = 2.0
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_trigger"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_trigger"]
|
||||||
size = Vector3(1.4, 2.1, 0.6)
|
size = Vector3(1.4, 2.1, 0.5)
|
||||||
|
|
||||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_portal"]
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_portal"]
|
||||||
properties/0/path = NodePath(":target_room_id")
|
properties/0/path = NodePath(":target_room_id")
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ var doors = [] # List of PortalDoor nodes
|
|||||||
var swap_timer: Timer
|
var swap_timer: Timer
|
||||||
var tile_refresh_timer: Timer
|
var tile_refresh_timer: Timer
|
||||||
var finish_spawned: bool = false
|
var finish_spawned: bool = false
|
||||||
var missions_required: int = 3
|
var missions_required: int = 8
|
||||||
var arena_setup_done: bool = false
|
var arena_setup_done: bool = false
|
||||||
var player_portal_cooldowns: Dictionary = {}
|
var player_portal_cooldowns: Dictionary = {}
|
||||||
|
|
||||||
|
var hud_layer: CanvasLayer
|
||||||
|
var mission_label: Label
|
||||||
|
var _has_notified_mission_complete: bool = false
|
||||||
|
|
||||||
func initialize(p_main: Node, p_gridmap: Node):
|
func initialize(p_main: Node, p_gridmap: Node):
|
||||||
main = p_main
|
main = p_main
|
||||||
gridmap = p_gridmap
|
gridmap = p_gridmap
|
||||||
@@ -52,6 +56,9 @@ func initialize(p_main: Node, p_gridmap: Node):
|
|||||||
var gcm = main.get_node_or_null("GoalsCycleManager")
|
var gcm = main.get_node_or_null("GoalsCycleManager")
|
||||||
if gcm:
|
if gcm:
|
||||||
gcm.global_timer_updated.connect(_on_global_timer_updated)
|
gcm.global_timer_updated.connect(_on_global_timer_updated)
|
||||||
|
gcm.goal_count_updated.connect(_on_goal_count_updated)
|
||||||
|
|
||||||
|
_setup_hud()
|
||||||
|
|
||||||
func _on_global_timer_updated(time_remaining: float):
|
func _on_global_timer_updated(time_remaining: float):
|
||||||
if not multiplayer.is_server(): return
|
if not multiplayer.is_server(): return
|
||||||
@@ -79,6 +86,14 @@ func start_game_mode():
|
|||||||
# Initial Tile Spawn
|
# Initial Tile Spawn
|
||||||
_refresh_tiles()
|
_refresh_tiles()
|
||||||
|
|
||||||
|
# Show HUD
|
||||||
|
_activate_hud()
|
||||||
|
|
||||||
|
func _activate_hud():
|
||||||
|
if hud_layer:
|
||||||
|
hud_layer.visible = true
|
||||||
|
_update_hud_visuals()
|
||||||
|
|
||||||
func setup_arena_locally():
|
func setup_arena_locally():
|
||||||
"""Sets up GridMap size and walls. Called on host and clients."""
|
"""Sets up GridMap size and walls. Called on host and clients."""
|
||||||
if arena_setup_done:
|
if arena_setup_done:
|
||||||
@@ -119,6 +134,69 @@ func get_spawn_points() -> Array[Vector2i]:
|
|||||||
Vector2i(10, 10) # Room 3
|
Vector2i(10, 10) # Room 3
|
||||||
]
|
]
|
||||||
|
|
||||||
|
func _setup_hud():
|
||||||
|
hud_layer = CanvasLayer.new()
|
||||||
|
hud_layer.layer = 5
|
||||||
|
hud_layer.visible = false
|
||||||
|
add_child(hud_layer)
|
||||||
|
|
||||||
|
var bottom_container = CenterContainer.new()
|
||||||
|
bottom_container.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
|
||||||
|
bottom_container.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||||
|
bottom_container.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||||
|
bottom_container.offset_bottom = -50
|
||||||
|
hud_layer.add_child(bottom_container)
|
||||||
|
|
||||||
|
mission_label = Label.new()
|
||||||
|
mission_label.text = "GOALS (0/8)"
|
||||||
|
mission_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
|
||||||
|
var custom_font = load("res://assets/fonts/Nougat-ExtraBlack.ttf")
|
||||||
|
if custom_font: mission_label.add_theme_font_override("font", custom_font)
|
||||||
|
|
||||||
|
mission_label.add_theme_font_size_override("font_size", 28)
|
||||||
|
mission_label.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||||
|
mission_label.add_theme_constant_override("outline_size", 8)
|
||||||
|
bottom_container.add_child(mission_label)
|
||||||
|
|
||||||
|
# Initial update
|
||||||
|
_update_hud_visuals()
|
||||||
|
|
||||||
|
func _update_hud_visuals():
|
||||||
|
if not mission_label: return
|
||||||
|
|
||||||
|
var my_id = multiplayer.get_unique_id()
|
||||||
|
var gcm = main.get_node_or_null("GoalsCycleManager")
|
||||||
|
var completed_count = gcm.player_goal_counts.get(my_id, 0) if gcm else 0
|
||||||
|
|
||||||
|
mission_label.text = "GOALS (%d/%d)" % [completed_count, missions_required]
|
||||||
|
|
||||||
|
if completed_count >= missions_required:
|
||||||
|
mission_label.text = "ALL GOALS COMPLETE!\nFIND THE FINISH ROOM!"
|
||||||
|
mission_label.add_theme_color_override("font_color", Color.GOLD)
|
||||||
|
|
||||||
|
if not _has_notified_mission_complete:
|
||||||
|
_has_notified_mission_complete = true
|
||||||
|
var player_node = main.get_node_or_null(str(my_id))
|
||||||
|
if player_node:
|
||||||
|
NotificationManager.send_message(player_node, "ALL GOALS COMPLETE!", NotificationManager.MessageType.GOAL)
|
||||||
|
else:
|
||||||
|
mission_label.add_theme_color_override("font_color", Color.WHITE)
|
||||||
|
_has_notified_mission_complete = false
|
||||||
|
|
||||||
|
func is_mission_complete(peer_id: int) -> bool:
|
||||||
|
var gcm = main.get_node_or_null("GoalsCycleManager")
|
||||||
|
if not gcm: return false
|
||||||
|
return gcm.player_goal_counts.get(peer_id, 0) >= missions_required
|
||||||
|
|
||||||
|
func check_win_condition(player_id: int, pos: Vector2i) -> bool:
|
||||||
|
# 1. Check if on finish tile
|
||||||
|
var tile = gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
|
||||||
|
if tile != 3: return false
|
||||||
|
|
||||||
|
# 2. Check missions
|
||||||
|
return is_mission_complete(player_id)
|
||||||
|
|
||||||
func _setup_room_partitions():
|
func _setup_room_partitions():
|
||||||
for i in range(GRID_SIZE):
|
for i in range(GRID_SIZE):
|
||||||
# Vertical wall (middle columns)
|
# Vertical wall (middle columns)
|
||||||
@@ -308,28 +386,47 @@ func _on_global_goal_count_updated(_peer_id: int, _count: int):
|
|||||||
# Mission requirement removed in favor of time-based finish reveal
|
# Mission requirement removed in favor of time-based finish reveal
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
func _on_goal_count_updated(peer_id: int, _count: int):
|
||||||
|
if peer_id == multiplayer.get_unique_id():
|
||||||
|
_update_hud_visuals()
|
||||||
|
|
||||||
func _spawn_finish_room():
|
func _spawn_finish_room():
|
||||||
print("[PortalModeManager] Time is running out! Revealing Finish Room...")
|
print("[PortalModeManager] Time is running out! Revealing Finish Room...")
|
||||||
finish_spawned = true
|
finish_spawned = true
|
||||||
|
|
||||||
# Choose a random center room spawn point to place the finish tile
|
# Choose a random room quadrant index (0 to 3)
|
||||||
var room_centers = get_spawn_points()
|
var room_idx = randi() % 4
|
||||||
var center = room_centers[randi() % room_centers.size()]
|
|
||||||
|
|
||||||
# Place finish tile (ID 3) on Floor 1 (Y=1)
|
# Determine center for the selected room quadrant (7x7 rooms)
|
||||||
# We use current_position as spawn point centers are guaranteed to be walkable
|
var x_center = 3 if (room_idx == 0 or room_idx == 2) else 10
|
||||||
# IMPORTANT: Change the floor tile (Floor 0) to item ID 3 (Goal/Finish)
|
var z_center = 3 if (room_idx == 0 or room_idx == 1) else 10
|
||||||
main.rpc("sync_grid_item", center.x, 0, center.y, 3)
|
|
||||||
|
|
||||||
# Clear any item on Floor 1 above the finish tile
|
# Determine 3x3 bounds around the center
|
||||||
main.rpc("sync_grid_item", center.x, 1, center.y, -1)
|
var x_start = x_center - 1
|
||||||
|
var x_end = x_center + 2 # exclusive for range()
|
||||||
|
var z_start = z_center - 1
|
||||||
|
var z_end = z_center + 2 # exclusive for range()
|
||||||
|
|
||||||
|
print("[PortalModeManager] Converting 3x3 area in Room %d (X:%d-%d, Z:%d-%d) to Finish Tiles" % [room_idx, x_start, x_end-1, z_start, z_end-1])
|
||||||
|
|
||||||
|
# Iterate through the 3x3 area
|
||||||
|
for x in range(x_start, x_end):
|
||||||
|
for z in range(z_start, z_end):
|
||||||
|
# Only convert walkable floor tiles (Item ID 0) on Floor 0
|
||||||
|
var floor_0_item = gridmap.get_cell_item(Vector3i(x, 0, z))
|
||||||
|
if floor_0_item == 0:
|
||||||
|
# Change Floor 0 tile to Finish Tile (ID 3)
|
||||||
|
main.rpc("sync_grid_item", x, 0, z, 3)
|
||||||
|
|
||||||
|
# Clear any item on Floor 1 above this tile
|
||||||
|
main.rpc("sync_grid_item", x, 1, z, -1)
|
||||||
|
|
||||||
# Visual update for server
|
# Visual update for server
|
||||||
if gridmap.has_method("update_grid_data"):
|
if gridmap.has_method("update_grid_data"):
|
||||||
gridmap.update_grid_data()
|
gridmap.update_grid_data()
|
||||||
|
|
||||||
main.rpc("display_message", "[ALARM] THE FINISH ROOM HAS APPEARED!")
|
main.rpc("display_message", "[ALARM] THE FINISH ROOM HAS APPEARED!")
|
||||||
main.rpc("broadcast_message", "SYSTEM", "The Finish Tile is in Room %d!" % _get_room_index(center), 4) # 4 = MessageType.WARNING
|
main.rpc("broadcast_message", "SYSTEM", "A 3x3 Finish Zone has appeared in Room %d!" % room_idx, 4) # 4 = MessageType.WARNING
|
||||||
|
|
||||||
func _get_room_index(pos: Vector2i) -> int:
|
func _get_room_index(pos: Vector2i) -> int:
|
||||||
if pos.x < 7 and pos.y < 7: return 0
|
if pos.x < 7 and pos.y < 7: return 0
|
||||||
@@ -393,7 +490,7 @@ func handle_portal_interaction(player, door):
|
|||||||
|
|
||||||
var current_time = Time.get_ticks_msec()
|
var current_time = Time.get_ticks_msec()
|
||||||
if player_portal_cooldowns.has(player.name):
|
if player_portal_cooldowns.has(player.name):
|
||||||
if current_time - player_portal_cooldowns[player.name] < 3000:
|
if current_time - player_portal_cooldowns[player.name] < 1000:
|
||||||
return
|
return
|
||||||
player_portal_cooldowns[player.name] = current_time
|
player_portal_cooldowns[player.name] = current_time
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func _on_body_entered(body: Node3D):
|
|||||||
if body.is_in_group("Players") or body.get("is_bot"):
|
if body.is_in_group("Players") or body.get("is_bot"):
|
||||||
var current_time = Time.get_ticks_msec()
|
var current_time = Time.get_ticks_msec()
|
||||||
if body.has_meta("last_portal_time"):
|
if body.has_meta("last_portal_time"):
|
||||||
if current_time - body.get_meta("last_portal_time") < 3000:
|
if current_time - body.get_meta("last_portal_time") < 1000:
|
||||||
return
|
return
|
||||||
|
|
||||||
body.set_meta("last_portal_time", current_time)
|
body.set_meta("last_portal_time", current_time)
|
||||||
|
|||||||
Reference in New Issue
Block a user