feat: Implement a new portal door game mode with arena partitioning, dynamic connections, and multiplayer synchronization.

This commit is contained in:
Yogi Wiguna
2026-03-02 14:48:59 +08:00
parent 1425dff93e
commit 78634d6421
4 changed files with 55 additions and 27 deletions
+8
View File
@@ -58,6 +58,14 @@ func _ready():
if multiplayer.is_server():
randomize_game_grid()
@rpc("any_peer", "call_local", "reliable")
func sync_portal_configs(configs: Array):
if portal_mode_manager:
# Temporarily store the configs and trigger spawn
# Note: We use a custom property in manager to pass this
portal_mode_manager.set_meta("door_configs", configs)
portal_mode_manager._spawn_portal_doors()
# Force gridmap cell size to match player logic (1, 0.05, 1) - >0.001 to avoid errors
var em = $EnhancedGridMap
if em:
+1 -1
View File
@@ -21,7 +21,7 @@ emission = Color(0.0, 0.4, 1.0, 1)
emission_energy_multiplier = 2.0
[sub_resource type="BoxShape3D" id="BoxShape3D_trigger"]
size = Vector3(1.4, 2.1, 0.5)
size = Vector3(1.4, 2.1, 0.8)
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_portal"]
properties/0/path = NodePath(":target_room_id")
+44 -25
View File
@@ -210,36 +210,53 @@ func _setup_room_partitions():
var _pending_sync_data = null
func _spawn_portal_doors():
# Check if doors already exist to avoid duplicates
if not doors.is_empty():
print("[PortalModeManager] Doors already exist, skipping spawn. Count: ", doors.size())
return
# 1. Use synced configs if they exist (passed via main.rpc("sync_portal_configs"))
var door_configs = get_meta("door_configs") if has_meta("door_configs") else []
# 2. If no synced configs (e.g. Server start), generate base + extras
if door_configs.is_empty():
if not multiplayer.is_server():
print("[PortalModeManager] Client waiting for portal configs sync...")
return
door_configs = [
# BASE DOORS (2 per room)
{"room": 0, "pos": Vector2i(6, 2), "rot": PI / 2, "offset": Vector2i(-1, 0)}, # East
{"room": 0, "pos": Vector2i(2, 6), "rot": 0, "offset": Vector2i(0, -1)}, # South
{"room": 1, "pos": Vector2i(7, 2), "rot": PI / 2, "offset": Vector2i(1, 0)}, # West
{"room": 1, "pos": Vector2i(11, 6), "rot": 0, "offset": Vector2i(0, -1)}, # South
{"room": 2, "pos": Vector2i(2, 7), "rot": 0, "offset": Vector2i(0, 1)}, # North
{"room": 2, "pos": Vector2i(6, 11), "rot": PI / 2, "offset": Vector2i(-1, 0)}, # East
{"room": 3, "pos": Vector2i(11, 7), "rot": 0, "offset": Vector2i(0, 1)}, # North
{"room": 3, "pos": Vector2i(7, 11), "rot": PI / 2, "offset": Vector2i(1, 0)} # West
]
# Server adds extras
var extra_options = [
{"room": 0, "pos": Vector2i(6, 5), "rot": PI / 2, "offset": Vector2i(-1, 0)}, # East (Gap from 6,2)
{"room": 1, "pos": Vector2i(7, 5), "rot": PI / 2, "offset": Vector2i(1, 0)}, # West (Gap from 7,2)
{"room": 2, "pos": Vector2i(6, 8), "rot": PI / 2, "offset": Vector2i(-1, 0)}, # East (Gap from 6,11)
{"room": 3, "pos": Vector2i(7, 8), "rot": PI / 2, "offset": Vector2i(1, 0)} # West (Gap from 7,11)
]
extra_options.shuffle()
door_configs.append(extra_options[0])
door_configs.append(extra_options[1])
# Broadcast to clients
main.rpc("sync_portal_configs", door_configs)
# 3. Spawn the doors
if not doors.is_empty(): return # Guard against double spawn
print("[PortalModeManager] Spawning %d doors. Peer: %d" % [door_configs.size(), multiplayer.get_unique_id()])
print("[PortalModeManager] Spawning portal doors. Peer ID: ", multiplayer.get_unique_id())
var portal_scene = load("res://scenes/portal_door.tscn")
var stands_container = main.get_node_or_null("Stands")
if not stands_container:
print("[PortalModeManager] Warning: 'Stands' container not found, creating one...")
stands_container = Node3D.new()
stands_container.name = "Stands"
main.add_child(stands_container)
var door_configs = [
# Room 0
{"room": 0, "pos": Vector2i(6, 2), "rot": PI / 2, "offset": Vector2i(-1, 0)}, # East
{"room": 0, "pos": Vector2i(2, 6), "rot": 0, "offset": Vector2i(0, -1)}, # South
# Room 1
{"room": 1, "pos": Vector2i(7, 2), "rot": PI / 2, "offset": Vector2i(1, 0)}, # West
{"room": 1, "pos": Vector2i(11, 6), "rot": 0, "offset": Vector2i(0, -1)}, # South
# Room 2
{"room": 2, "pos": Vector2i(2, 7), "rot": 0, "offset": Vector2i(0, 1)}, # North
{"room": 2, "pos": Vector2i(6, 11), "rot": PI / 2, "offset": Vector2i(-1, 0)}, # East
# Room 3
{"room": 3, "pos": Vector2i(11, 7), "rot": 0, "offset": Vector2i(0, 1)}, # North
{"room": 3, "pos": Vector2i(7, 11), "rot": PI / 2, "offset": Vector2i(1, 0)} # West
]
for i in range(door_configs.size()):
var cfg = door_configs[i]
if not portal_scene:
@@ -278,7 +295,8 @@ const PORTAL_COLORS = [
Color(0, 1, 1), # Cyan
Color(1, 0, 1), # Magenta
Color(1, 1, 0), # Yellow
Color(0, 1, 0) # Green
Color(0, 1, 0), # Green
Color(1, 0.5, 0) # Orange
]
func _randomize_connections():
@@ -490,7 +508,8 @@ func handle_portal_interaction(player, door):
var current_time = Time.get_ticks_msec()
if player_portal_cooldowns.has(player.name):
if current_time - player_portal_cooldowns[player.name] < 1000:
# Reduce cooldown to 200ms (more responsive than 1s, but enough to avoid jitter)
if current_time - player_portal_cooldowns[player.name] < 200:
return
player_portal_cooldowns[player.name] = current_time
+2 -1
View File
@@ -35,7 +35,8 @@ func _on_body_entered(body: Node3D):
if body.is_in_group("Players") or body.get("is_bot"):
var current_time = Time.get_ticks_msec()
if body.has_meta("last_portal_time"):
if current_time - body.get_meta("last_portal_time") < 1000:
# Reduce cooldown to 200ms to match manager logic and allow fast re-entry
if current_time - body.get_meta("last_portal_time") < 200:
return
body.set_meta("last_portal_time", current_time)