feat: Implement a new portal door game mode with arena partitioning, dynamic connections, and multiplayer synchronization.
This commit is contained in:
@@ -58,6 +58,14 @@ func _ready():
|
|||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
randomize_game_grid()
|
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
|
# Force gridmap cell size to match player logic (1, 0.05, 1) - >0.001 to avoid errors
|
||||||
var em = $EnhancedGridMap
|
var em = $EnhancedGridMap
|
||||||
if em:
|
if em:
|
||||||
|
|||||||
@@ -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.5)
|
size = Vector3(1.4, 2.1, 0.8)
|
||||||
|
|
||||||
[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")
|
||||||
|
|||||||
@@ -210,36 +210,53 @@ func _setup_room_partitions():
|
|||||||
var _pending_sync_data = null
|
var _pending_sync_data = null
|
||||||
|
|
||||||
func _spawn_portal_doors():
|
func _spawn_portal_doors():
|
||||||
# Check if doors already exist to avoid duplicates
|
# 1. Use synced configs if they exist (passed via main.rpc("sync_portal_configs"))
|
||||||
if not doors.is_empty():
|
var door_configs = get_meta("door_configs") if has_meta("door_configs") else []
|
||||||
print("[PortalModeManager] Doors already exist, skipping spawn. Count: ", doors.size())
|
|
||||||
return
|
# 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 portal_scene = load("res://scenes/portal_door.tscn")
|
||||||
var stands_container = main.get_node_or_null("Stands")
|
var stands_container = main.get_node_or_null("Stands")
|
||||||
|
|
||||||
if not stands_container:
|
if not stands_container:
|
||||||
print("[PortalModeManager] Warning: 'Stands' container not found, creating one...")
|
|
||||||
stands_container = Node3D.new()
|
stands_container = Node3D.new()
|
||||||
stands_container.name = "Stands"
|
stands_container.name = "Stands"
|
||||||
main.add_child(stands_container)
|
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()):
|
for i in range(door_configs.size()):
|
||||||
var cfg = door_configs[i]
|
var cfg = door_configs[i]
|
||||||
if not portal_scene:
|
if not portal_scene:
|
||||||
@@ -278,7 +295,8 @@ const PORTAL_COLORS = [
|
|||||||
Color(0, 1, 1), # Cyan
|
Color(0, 1, 1), # Cyan
|
||||||
Color(1, 0, 1), # Magenta
|
Color(1, 0, 1), # Magenta
|
||||||
Color(1, 1, 0), # Yellow
|
Color(1, 1, 0), # Yellow
|
||||||
Color(0, 1, 0) # Green
|
Color(0, 1, 0), # Green
|
||||||
|
Color(1, 0.5, 0) # Orange
|
||||||
]
|
]
|
||||||
|
|
||||||
func _randomize_connections():
|
func _randomize_connections():
|
||||||
@@ -490,7 +508,8 @@ 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] < 1000:
|
# Reduce cooldown to 200ms (more responsive than 1s, but enough to avoid jitter)
|
||||||
|
if current_time - player_portal_cooldowns[player.name] < 200:
|
||||||
return
|
return
|
||||||
player_portal_cooldowns[player.name] = current_time
|
player_portal_cooldowns[player.name] = current_time
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ 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") < 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
|
return
|
||||||
|
|
||||||
body.set_meta("last_portal_time", current_time)
|
body.set_meta("last_portal_time", current_time)
|
||||||
|
|||||||
Reference in New Issue
Block a user