feat: update some visual and fixing the bug
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
[ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"]
|
[ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"]
|
||||||
[ext_resource type="BoxMesh" uid="uid://fy4bhoeii40c" path="res://addons/enhanced_gridmap/meshlibrary/tile_safe_zone.tres" id="10_uwjsj"]
|
[ext_resource type="BoxMesh" uid="uid://fy4bhoeii40c" path="res://addons/enhanced_gridmap/meshlibrary/tile_safe_zone.tres" id="10_uwjsj"]
|
||||||
[ext_resource type="BoxMesh" uid="uid://b5cc3prem52r6" path="res://addons/enhanced_gridmap/meshlibrary/tile_freeze.tres" id="11_pgnbl"]
|
[ext_resource type="BoxMesh" uid="uid://b5cc3prem52r6" path="res://addons/enhanced_gridmap/meshlibrary/tile_freeze.tres" id="11_pgnbl"]
|
||||||
[ext_resource type="BoxMesh" uid="uid://dcjdwbffgtutt" path="res://addons/enhanced_gridmap/meshlibrary/tile_non_walkable.tres" id="11_uwjsj"]
|
[ext_resource type="BoxMesh" path="res://addons/enhanced_gridmap/meshlibrary/tile_non_walkable.tres" id="11_uwjsj"]
|
||||||
|
|
||||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_5d0gc"]
|
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_5d0gc"]
|
||||||
load_path = "res://.godot/imported/tile_heart.png-deeef50755ca225f028608dfd16900e6.s3tc.ctex"
|
load_path = "res://.godot/imported/tile_heart.png-deeef50755ca225f028608dfd16900e6.s3tc.ctex"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ou2ex"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ou2ex"]
|
||||||
transparency = 1
|
transparency = 1
|
||||||
albedo_color = Color(0.38039216, 1, 0.33333334, 1)
|
albedo_color = Color(0.38039216, 1, 0.33333334, 0.65)
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
material = SubResource("StandardMaterial3D_ou2ex")
|
material = SubResource("StandardMaterial3D_ou2ex")
|
||||||
|
|||||||
+4
-4
@@ -720,6 +720,10 @@ func _auto_start_from_lobby():
|
|||||||
|
|
||||||
func _start_game():
|
func _start_game():
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
|
# NOW assign spawn positions for EVERYONE (Host, Client, Bots)
|
||||||
|
# We do this BEFORE the stabilization delay so players are moved away from (0,0) immediately.
|
||||||
|
_assign_random_spawn_positions()
|
||||||
|
|
||||||
# Wait for Nakama websocket to actually be open, up to 5 seconds
|
# Wait for Nakama websocket to actually be open, up to 5 seconds
|
||||||
# SKIP THIS FOR LAN MODE
|
# SKIP THIS FOR LAN MODE
|
||||||
if not LobbyManager.is_lan_mode:
|
if not LobbyManager.is_lan_mode:
|
||||||
@@ -735,10 +739,6 @@ func _start_game():
|
|||||||
# before the countdown starts.
|
# before the countdown starts.
|
||||||
await get_tree().create_timer(1.5).timeout
|
await get_tree().create_timer(1.5).timeout
|
||||||
|
|
||||||
# NOW assign spawn positions for EVERYONE (Host, Client, Bots)
|
|
||||||
# This safely sends RPCs over the completed socket connection
|
|
||||||
_assign_random_spawn_positions()
|
|
||||||
|
|
||||||
# PRE-GAME COUNTDOWN (3s)
|
# PRE-GAME COUNTDOWN (3s)
|
||||||
# Spawn static obstacles before countdown starts (Stop n Go only)
|
# Spawn static obstacles before countdown starts (Stop n Go only)
|
||||||
if obstacle_manager and LobbyManager.game_mode == "Stop n Go":
|
if obstacle_manager and LobbyManager.game_mode == "Stop n Go":
|
||||||
|
|||||||
@@ -1096,6 +1096,9 @@ func _process(delta):
|
|||||||
if LobbyManager.get_randomize_spawn() and spawn_point_selected and not visible:
|
if LobbyManager.get_randomize_spawn() and spawn_point_selected and not visible:
|
||||||
visible = true
|
visible = true
|
||||||
|
|
||||||
|
if not multiplayer.has_multiplayer_peer():
|
||||||
|
return
|
||||||
|
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
# Visual debugging - show display name with connection status
|
# Visual debugging - show display name with connection status
|
||||||
$Name.text = display_name if not display_name.is_empty() else str(name)
|
$Name.text = display_name if not display_name.is_empty() else str(name)
|
||||||
@@ -1184,6 +1187,9 @@ var last_sent_position: Vector3
|
|||||||
|
|
||||||
func _physics_process(delta):
|
func _physics_process(delta):
|
||||||
# Sync position periodically (Heartbeat / Smoothing)
|
# Sync position periodically (Heartbeat / Smoothing)
|
||||||
|
if not multiplayer.has_multiplayer_peer():
|
||||||
|
return
|
||||||
|
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
# OPTIMIZATION: Only send smoothing updates if we ARE NOT currently mid-tween
|
# OPTIMIZATION: Only send smoothing updates if we ARE NOT currently mid-tween
|
||||||
# The start/end of paths are already synced via start_movement_along_path.
|
# The start/end of paths are already synced via start_movement_along_path.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func _process(delta):
|
|||||||
emit_signal("global_timer_updated", global_match_timer)
|
emit_signal("global_timer_updated", global_match_timer)
|
||||||
|
|
||||||
# Server broadcasts global timer sync every second
|
# Server broadcasts global timer sync every second
|
||||||
if multiplayer.is_server() and int(global_match_timer) != int(global_match_timer + delta):
|
if multiplayer.has_multiplayer_peer() and multiplayer.is_server() and int(global_match_timer) != int(global_match_timer + delta):
|
||||||
rpc("sync_global_timer", global_match_timer)
|
rpc("sync_global_timer", global_match_timer)
|
||||||
|
|
||||||
# Update cycle timer if cycle is active
|
# Update cycle timer if cycle is active
|
||||||
@@ -87,7 +87,7 @@ func _process(delta):
|
|||||||
emit_signal("timer_updated", current_cycle_timer)
|
emit_signal("timer_updated", current_cycle_timer)
|
||||||
|
|
||||||
# Server broadcasts timer sync every second
|
# Server broadcasts timer sync every second
|
||||||
if multiplayer.is_server() and int(current_cycle_timer) != int(current_cycle_timer + delta):
|
if multiplayer.has_multiplayer_peer() and multiplayer.is_server() and int(current_cycle_timer) != int(current_cycle_timer + delta):
|
||||||
rpc("sync_timer", current_cycle_timer)
|
rpc("sync_timer", current_cycle_timer)
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func initialize(p_player: Node3D, p_movement_manager: Node, p_race_manager: Node
|
|||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
# Early return conditions
|
# Early return conditions
|
||||||
if not is_instance_valid(player) or not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots") or player.is_frozen or player.is_stop_frozen:
|
if not is_instance_valid(player) or not multiplayer.has_multiplayer_peer() or not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots") or player.is_frozen or player.is_stop_frozen:
|
||||||
return
|
return
|
||||||
|
|
||||||
if TurnManager.turn_based_mode:
|
if TurnManager.turn_based_mode:
|
||||||
@@ -59,8 +59,9 @@ func _process(delta):
|
|||||||
|
|
||||||
func handle_unhandled_input(event):
|
func handle_unhandled_input(event):
|
||||||
# Early return if not authorized human player
|
# Early return if not authorized human player
|
||||||
if not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots"):
|
if not multiplayer.has_multiplayer_peer() or not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots"):
|
||||||
player.set_process_unhandled_input(false)
|
if multiplayer.has_multiplayer_peer():
|
||||||
|
player.set_process_unhandled_input(false)
|
||||||
return
|
return
|
||||||
|
|
||||||
var main = player.get_node("/root/Main")
|
var main = player.get_node("/root/Main")
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ func add_powerup_from_item(item_id: int):
|
|||||||
for e in inventory:
|
for e in inventory:
|
||||||
inventory[e] = false
|
inventory[e] = false
|
||||||
powerup_levels[e] = 1 # Reset levels of discarded powerups
|
powerup_levels[e] = 1 # Reset levels of discarded powerups
|
||||||
|
_exit_targeting_mode()
|
||||||
|
|
||||||
# Instant Level 8 on pickup (User Request)
|
# Instant Level 8 on pickup (User Request)
|
||||||
inventory[effect] = true
|
inventory[effect] = true
|
||||||
@@ -183,6 +184,8 @@ func sync_inventory_add(effect: int, level: int):
|
|||||||
for e in inventory:
|
for e in inventory:
|
||||||
inventory[e] = false
|
inventory[e] = false
|
||||||
powerup_levels[e] = 1 # Reset levels of discarded powerups
|
powerup_levels[e] = 1 # Reset levels of discarded powerups
|
||||||
|
|
||||||
|
_exit_targeting_mode()
|
||||||
|
|
||||||
inventory[effect] = true
|
inventory[effect] = true
|
||||||
powerup_levels[effect] = level
|
powerup_levels[effect] = level
|
||||||
|
|||||||
@@ -49,17 +49,8 @@ func calculate_spawn_points(count: int, gridmap: Node) -> Array[Vector2i]:
|
|||||||
|
|
||||||
# Determine Position Type for Bias
|
# Determine Position Type for Bias
|
||||||
# 0:TL, 1:TR, 2:BL, 3:BR, 4:Center
|
# 0:TL, 1:TR, 2:BL, 3:BR, 4:Center
|
||||||
var pos_type = -1
|
# We pass zone_idx to _pick_spot_in_zone to snap corners
|
||||||
match zone_idx:
|
var pos = _pick_spot_in_zone(zone, gridmap, zone_idx)
|
||||||
0: pos_type = 0 # TL
|
|
||||||
2: pos_type = 1 # TR
|
|
||||||
6: pos_type = 2 # BL
|
|
||||||
8: pos_type = 3 # BR
|
|
||||||
4: pos_type = 4 # Center
|
|
||||||
|
|
||||||
# Use PURE RANDOM spot in zone (User Request #2)
|
|
||||||
# instead of biased corner/center logic
|
|
||||||
var pos = _pick_spot_in_zone(zone, gridmap)
|
|
||||||
if pos != Vector2i(-1, -1):
|
if pos != Vector2i(-1, -1):
|
||||||
spawn_points.append(pos)
|
spawn_points.append(pos)
|
||||||
|
|
||||||
@@ -126,11 +117,29 @@ func _is_valid_3x3(center: Vector2i, gridmap: Node) -> bool:
|
|||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
func _pick_spot_in_zone(zone: Rect2i, gridmap: Node) -> Vector2i:
|
func _pick_spot_in_zone(zone: Rect2i, gridmap: Node, zone_idx: int = -1) -> Vector2i:
|
||||||
# Find a valid 3x3 spot in the zone
|
"""
|
||||||
# The returned position is the CENTER of the 3x3 area
|
Find a valid 3x3 spot in the zone.
|
||||||
|
The returned position is the CENTER of the 3x3 area.
|
||||||
|
If zone_idx is a corner (0, 2, 6, 8), we snap to the absolute map corner.
|
||||||
|
"""
|
||||||
|
# CORNER SNAPPING: If this is a corner zone, force it to the extreme corner
|
||||||
|
# to ensure the 3x3 Stand fills the corner completely (no 1-tile gaps).
|
||||||
|
if zone_idx == 0: # Top-Left
|
||||||
|
var center = Vector2i(1, 1)
|
||||||
|
if _is_valid_3x3(center, gridmap): return center
|
||||||
|
elif zone_idx == 2: # Top-Right
|
||||||
|
var center = Vector2i(gridmap.columns - 2, 1)
|
||||||
|
if _is_valid_3x3(center, gridmap): return center
|
||||||
|
elif zone_idx == 6: # Bottom-Left
|
||||||
|
var center = Vector2i(1, gridmap.rows - 2)
|
||||||
|
if _is_valid_3x3(center, gridmap): return center
|
||||||
|
elif zone_idx == 8: # Bottom-Right
|
||||||
|
var center = Vector2i(gridmap.columns - 2, gridmap.rows - 2)
|
||||||
|
if _is_valid_3x3(center, gridmap): return center
|
||||||
|
|
||||||
|
# Fallback/Random logic for non-corner zones or if preferred corner was invalid
|
||||||
var attempts = 0
|
var attempts = 0
|
||||||
|
|
||||||
while attempts < 30:
|
while attempts < 30:
|
||||||
attempts += 1
|
attempts += 1
|
||||||
# Ensure center is at least 1 tile away from edges of the map to fit 3x3
|
# Ensure center is at least 1 tile away from edges of the map to fit 3x3
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ enum Phase {GO, STOP}
|
|||||||
# Dynamic Safe Zone
|
# Dynamic Safe Zone
|
||||||
var active_safe_zone_rects: Array[Rect2i] = []
|
var active_safe_zone_rects: Array[Rect2i] = []
|
||||||
var spawned_safe_zones: int = 0
|
var spawned_safe_zones: int = 0
|
||||||
|
var _safe_zone_animating: bool = false
|
||||||
|
var _outline_nodes: Array[Node3D] = [] # Track perimeter outline containers for cleanup
|
||||||
|
|
||||||
# Power-Up Tile Spawning
|
# Power-Up Tile Spawning
|
||||||
const POWERUP_TILES = [11, 14] # Speed, Ghost (Freeze and Wall excluded in this mode)
|
const POWERUP_TILES = [11, 14] # Speed, Ghost (Freeze and Wall excluded in this mode)
|
||||||
@@ -97,6 +99,12 @@ func _process(delta):
|
|||||||
print("[StopNGo] GO phase ending soon. Spawning 3 safe zones...")
|
print("[StopNGo] GO phase ending soon. Spawning 3 safe zones...")
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
_spawn_dynamic_safe_zone()
|
_spawn_dynamic_safe_zone()
|
||||||
|
|
||||||
|
# Trigger global VFX and outline drawing on all clients
|
||||||
|
if can_rpc():
|
||||||
|
rpc("sync_all_safe_zones_vfx")
|
||||||
|
else:
|
||||||
|
sync_all_safe_zones_vfx()
|
||||||
|
|
||||||
if phase_timer <= 0:
|
if phase_timer <= 0:
|
||||||
if current_phase == Phase.GO:
|
if current_phase == Phase.GO:
|
||||||
@@ -607,19 +615,200 @@ func _spawn_dynamic_safe_zone():
|
|||||||
gridmap.set_cell_item(Vector3i(px, 2, pz), TILE_SAFE)
|
gridmap.set_cell_item(Vector3i(px, 2, pz), TILE_SAFE)
|
||||||
if can_rpc() and main:
|
if can_rpc() and main:
|
||||||
main.rpc("sync_grid_item", px, 2, pz, TILE_SAFE)
|
main.rpc("sync_grid_item", px, 2, pz, TILE_SAFE)
|
||||||
|
|
||||||
# Trigger safe zone appear visual effects on all clients
|
|
||||||
if can_rpc():
|
|
||||||
rpc("sync_safe_zone_vfx")
|
|
||||||
|
|
||||||
@rpc("authority", "call_local", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func sync_safe_zone_vfx():
|
func sync_all_safe_zones_vfx():
|
||||||
var main = get_node_or_null("/root/Main")
|
var main = get_node_or_null("/root/Main")
|
||||||
if main and main.get("vfx_manager"):
|
if main and main.get("vfx_manager"):
|
||||||
if main.vfx_manager.has_method("play_safe_zone_appear"):
|
if main.vfx_manager.has_method("play_safe_zone_appear"):
|
||||||
main.vfx_manager.play_safe_zone_appear()
|
main.vfx_manager.play_safe_zone_appear()
|
||||||
elif main.vfx_manager.get("animation_player"):
|
elif main.vfx_manager.get("animation_player"):
|
||||||
main.vfx_manager.animation_player.play("safe-zone-appear")
|
main.vfx_manager.animation_player.play("safe-zone-appear")
|
||||||
|
|
||||||
|
# Build and animate the CONTINUOUS outer outline for ALL zones combined
|
||||||
|
var outline = _create_merged_safe_zone_outlines()
|
||||||
|
if outline:
|
||||||
|
_outline_nodes.append(outline)
|
||||||
|
_animate_outline_appear(outline)
|
||||||
|
|
||||||
|
# Animate the safe zone panels appearing (alpha 0 → 0.65)
|
||||||
|
_animate_safe_zone_appear()
|
||||||
|
|
||||||
|
func _animate_safe_zone_appear():
|
||||||
|
"""Two-phase appear: sharp bright flash then settle to semi-transparent.
|
||||||
|
Guarded so only ONE animation runs even when all 3 zones spawn at once."""
|
||||||
|
if _safe_zone_animating:
|
||||||
|
return
|
||||||
|
_safe_zone_animating = true
|
||||||
|
|
||||||
|
var gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
||||||
|
if not gridmap: gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
||||||
|
if not gridmap or not gridmap.mesh_library:
|
||||||
|
_safe_zone_animating = false
|
||||||
|
return
|
||||||
|
|
||||||
|
var original_mesh = gridmap.mesh_library.get_item_mesh(TILE_SAFE)
|
||||||
|
if not is_instance_valid(original_mesh):
|
||||||
|
_safe_zone_animating = false
|
||||||
|
return
|
||||||
|
|
||||||
|
var mat = original_mesh.material
|
||||||
|
if not is_instance_valid(mat):
|
||||||
|
_safe_zone_animating = false
|
||||||
|
return
|
||||||
|
|
||||||
|
# Duplicate mesh+material so we animate without touching the shared .tres on disk.
|
||||||
|
var anim_mat: StandardMaterial3D = mat.duplicate()
|
||||||
|
anim_mat.albedo_color = Color(mat.albedo_color.r, mat.albedo_color.g, mat.albedo_color.b, 0.0)
|
||||||
|
|
||||||
|
var anim_mesh = original_mesh.duplicate()
|
||||||
|
anim_mesh.material = anim_mat
|
||||||
|
gridmap.mesh_library.set_item_mesh(TILE_SAFE, anim_mesh)
|
||||||
|
|
||||||
|
const TARGET_ALPHA := 0.65
|
||||||
|
var tween = create_tween()
|
||||||
|
|
||||||
|
# Phase 1 — Sharp bright flash: alpha 0 → 1.0 in 0.2s (EXPO ease-out = instant pop)
|
||||||
|
tween.tween_method(
|
||||||
|
func(a: float):
|
||||||
|
if is_instance_valid(anim_mat): anim_mat.albedo_color.a = a,
|
||||||
|
0.0, 1.0, 0.2
|
||||||
|
).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO)
|
||||||
|
|
||||||
|
# Phase 2 — Settle: alpha 1.0 → 0.65 in 0.35s (CUBIC ease-in-out = soft land)
|
||||||
|
tween.tween_method(
|
||||||
|
func(a: float):
|
||||||
|
if is_instance_valid(anim_mat): anim_mat.albedo_color.a = a,
|
||||||
|
1.0, TARGET_ALPHA, 0.35
|
||||||
|
).set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC)
|
||||||
|
|
||||||
|
# Done — anim_mesh stays in library at alpha=0.65 (no restore needed)
|
||||||
|
tween.tween_callback(func():
|
||||||
|
_safe_zone_animating = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func _create_merged_safe_zone_outlines() -> Node3D:
|
||||||
|
"""Create thin edge BoxMesh strips ONLY on the outer perimeter of the merged safe zones.
|
||||||
|
Inner edges between adjacent safe zones are skipped."""
|
||||||
|
var gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
||||||
|
if not gridmap: gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
||||||
|
if not gridmap: return null
|
||||||
|
|
||||||
|
var cs: Vector3 = gridmap.cell_size
|
||||||
|
|
||||||
|
# Sit slightly above the safe panel (layer 2, panel top ~= 2*cs.y + 0.05)
|
||||||
|
var y := 2.0 * cs.y + 0.09
|
||||||
|
const T := 0.09 # outline thickness (world units)
|
||||||
|
const H := 0.045 # outline height
|
||||||
|
|
||||||
|
# One shared material (transparent, emissive lime-green) — starts invisible for animation
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
mat.albedo_color = Color(0.15, 1.0, 0.3, 0.0) # start at alpha=0
|
||||||
|
mat.emission_enabled = true
|
||||||
|
mat.emission = Color(0.1, 0.9, 0.25)
|
||||||
|
mat.emission_energy_multiplier = 1.8
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
|
||||||
|
var container := Node3D.new()
|
||||||
|
container.add_to_group("SafeZoneOutlines")
|
||||||
|
gridmap.add_child(container)
|
||||||
|
|
||||||
|
var corner_points = {}
|
||||||
|
|
||||||
|
# Scan layer 2 for boundaries
|
||||||
|
for x in range(gridmap.columns):
|
||||||
|
for z in range(gridmap.rows):
|
||||||
|
if gridmap.get_cell_item(Vector3i(x, 2, z)) != TILE_SAFE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
var cell_cx = x * cs.x + cs.x * 0.5
|
||||||
|
var cell_cz = z * cs.z + cs.z * 0.5
|
||||||
|
|
||||||
|
# Check North (z - 1)
|
||||||
|
if gridmap.get_cell_item(Vector3i(x, 2, z - 1)) != TILE_SAFE:
|
||||||
|
var mi := MeshInstance3D.new()
|
||||||
|
var box := BoxMesh.new()
|
||||||
|
box.size = Vector3(cs.x, H, T)
|
||||||
|
box.material = mat
|
||||||
|
mi.mesh = box
|
||||||
|
mi.position = Vector3(cell_cx, y, z * cs.z)
|
||||||
|
container.add_child(mi)
|
||||||
|
corner_points[Vector2i(x, z)] = true
|
||||||
|
corner_points[Vector2i(x + 1, z)] = true
|
||||||
|
|
||||||
|
# Check South (z + 1)
|
||||||
|
if gridmap.get_cell_item(Vector3i(x, 2, z + 1)) != TILE_SAFE:
|
||||||
|
var mi := MeshInstance3D.new()
|
||||||
|
var box := BoxMesh.new()
|
||||||
|
box.size = Vector3(cs.x, H, T)
|
||||||
|
box.material = mat
|
||||||
|
mi.mesh = box
|
||||||
|
mi.position = Vector3(cell_cx, y, z * cs.z + cs.z)
|
||||||
|
container.add_child(mi)
|
||||||
|
corner_points[Vector2i(x, z + 1)] = true
|
||||||
|
corner_points[Vector2i(x + 1, z + 1)] = true
|
||||||
|
|
||||||
|
# Check West (x - 1)
|
||||||
|
if gridmap.get_cell_item(Vector3i(x - 1, 2, z)) != TILE_SAFE:
|
||||||
|
var mi := MeshInstance3D.new()
|
||||||
|
var box := BoxMesh.new()
|
||||||
|
box.size = Vector3(T, H, cs.z)
|
||||||
|
box.material = mat
|
||||||
|
mi.mesh = box
|
||||||
|
mi.position = Vector3(x * cs.x, y, cell_cz)
|
||||||
|
container.add_child(mi)
|
||||||
|
corner_points[Vector2i(x, z)] = true
|
||||||
|
corner_points[Vector2i(x, z + 1)] = true
|
||||||
|
|
||||||
|
# Check East (x + 1)
|
||||||
|
if gridmap.get_cell_item(Vector3i(x + 1, 2, z)) != TILE_SAFE:
|
||||||
|
var mi := MeshInstance3D.new()
|
||||||
|
var box := BoxMesh.new()
|
||||||
|
box.size = Vector3(T, H, cs.z)
|
||||||
|
box.material = mat
|
||||||
|
mi.mesh = box
|
||||||
|
mi.position = Vector3(x * cs.x + cs.x, y, cell_cz)
|
||||||
|
container.add_child(mi)
|
||||||
|
corner_points[Vector2i(x + 1, z)] = true
|
||||||
|
corner_points[Vector2i(x + 1, z + 1)] = true
|
||||||
|
|
||||||
|
# Add a small cylinder at every exposed corner to connect lines and round the tips
|
||||||
|
for cp in corner_points:
|
||||||
|
var mi := MeshInstance3D.new()
|
||||||
|
var cyl := CylinderMesh.new()
|
||||||
|
cyl.top_radius = T * 0.5
|
||||||
|
cyl.bottom_radius = T * 0.5
|
||||||
|
cyl.height = H * 0.98 # Slightly shorter to prevent top-face Z-fighting with lines
|
||||||
|
cyl.radial_segments = 16
|
||||||
|
cyl.material = mat
|
||||||
|
mi.mesh = cyl
|
||||||
|
mi.position = Vector3(cp.x * cs.x, y, cp.y * cs.z)
|
||||||
|
container.add_child(mi)
|
||||||
|
|
||||||
|
return container
|
||||||
|
|
||||||
|
func _animate_outline_appear(container: Node3D):
|
||||||
|
"""Tween the shared outline material from transparent to full opacity,
|
||||||
|
mirroring the safe zone panel appear animation."""
|
||||||
|
if not is_instance_valid(container) or container.get_child_count() == 0: return
|
||||||
|
var mi := container.get_child(0) as MeshInstance3D
|
||||||
|
if not mi or not mi.mesh: return
|
||||||
|
var mat := mi.mesh.material as StandardMaterial3D
|
||||||
|
if not mat: return
|
||||||
|
|
||||||
|
var tween := create_tween()
|
||||||
|
# Flash in: alpha 0→1 in 0.2s
|
||||||
|
tween.tween_method(
|
||||||
|
func(a: float): if is_instance_valid(mat): mat.albedo_color.a = a,
|
||||||
|
0.0, 1.0, 0.2
|
||||||
|
).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO)
|
||||||
|
# Settle: alpha 1→0.9 in 0.35s
|
||||||
|
tween.tween_method(
|
||||||
|
func(a: float): if is_instance_valid(mat): mat.albedo_color.a = a,
|
||||||
|
1.0, 0.9, 0.35
|
||||||
|
).set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC)
|
||||||
|
|
||||||
func _is_valid_safe_zone_area(gridmap: Node, start_x: int, start_z: int, width: int, height: int) -> bool:
|
func _is_valid_safe_zone_area(gridmap: Node, start_x: int, start_z: int, width: int, height: int) -> bool:
|
||||||
# Avoid bounds or start/finish cols
|
# Avoid bounds or start/finish cols
|
||||||
@@ -642,6 +831,64 @@ func _is_valid_safe_zone_area(gridmap: Node, start_x: int, start_z: int, width:
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_safe_zone_disappear_vfx():
|
||||||
|
_animate_safe_zone_disappear()
|
||||||
|
|
||||||
|
func _animate_safe_zone_disappear():
|
||||||
|
"""Two-phase disappear: quick flicker-brighten then fade to invisible.
|
||||||
|
Plays before cells are cleared so the panel smoothly vanishes."""
|
||||||
|
var gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
||||||
|
if not gridmap: gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
||||||
|
if not gridmap or not gridmap.mesh_library: return
|
||||||
|
|
||||||
|
var cur_mesh = gridmap.mesh_library.get_item_mesh(TILE_SAFE)
|
||||||
|
if not is_instance_valid(cur_mesh): return
|
||||||
|
var cur_mat = cur_mesh.material
|
||||||
|
if not is_instance_valid(cur_mat): return
|
||||||
|
|
||||||
|
var fade_mat: StandardMaterial3D = cur_mat.duplicate()
|
||||||
|
var fade_mesh = cur_mesh.duplicate()
|
||||||
|
fade_mesh.material = fade_mat
|
||||||
|
gridmap.mesh_library.set_item_mesh(TILE_SAFE, fade_mesh)
|
||||||
|
|
||||||
|
var start_alpha: float = cur_mat.albedo_color.a
|
||||||
|
var tween = create_tween()
|
||||||
|
|
||||||
|
# Phase 1 — Flicker brighten: alpha → 0.95 in 0.15s (warn the player)
|
||||||
|
tween.tween_method(
|
||||||
|
func(a: float):
|
||||||
|
if is_instance_valid(fade_mat): fade_mat.albedo_color.a = a,
|
||||||
|
start_alpha, 0.95, 0.15
|
||||||
|
).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_SINE)
|
||||||
|
|
||||||
|
# Phase 2 — Fade out: alpha 0.95 → 0 in 0.4s (EXPO ease-in = fast vanish)
|
||||||
|
tween.tween_method(
|
||||||
|
func(a: float):
|
||||||
|
if is_instance_valid(fade_mat): fade_mat.albedo_color.a = a,
|
||||||
|
0.95, 0.0, 0.4
|
||||||
|
).set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO)
|
||||||
|
|
||||||
|
# Fade out all outline nodes in sync
|
||||||
|
for outline in _outline_nodes:
|
||||||
|
if not is_instance_valid(outline) or outline.get_child_count() == 0: continue
|
||||||
|
var mi := outline.get_child(0) as MeshInstance3D
|
||||||
|
if not mi or not mi.mesh: continue
|
||||||
|
var omat := mi.mesh.material as StandardMaterial3D
|
||||||
|
if not omat: continue
|
||||||
|
var fade_start := omat.albedo_color.a
|
||||||
|
var otween := create_tween()
|
||||||
|
# Flicker brighten
|
||||||
|
otween.tween_method(
|
||||||
|
func(a: float): if is_instance_valid(omat): omat.albedo_color.a = a,
|
||||||
|
fade_start, 1.0, 0.15
|
||||||
|
).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_SINE)
|
||||||
|
# Fade to zero
|
||||||
|
otween.tween_method(
|
||||||
|
func(a: float): if is_instance_valid(omat): omat.albedo_color.a = a,
|
||||||
|
1.0, 0.0, 0.4
|
||||||
|
).set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO)
|
||||||
|
|
||||||
func _clear_dynamic_safe_zones():
|
func _clear_dynamic_safe_zones():
|
||||||
var gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
var gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
||||||
if not gridmap: gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
if not gridmap: gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
||||||
@@ -650,6 +897,13 @@ func _clear_dynamic_safe_zones():
|
|||||||
|
|
||||||
print("[StopNGo] Clearing %d active safe zones." % active_safe_zone_rects.size())
|
print("[StopNGo] Clearing %d active safe zones." % active_safe_zone_rects.size())
|
||||||
|
|
||||||
|
# Play disappear animation on all peers, then wait for it to finish (0.55s) before clearing.
|
||||||
|
if can_rpc():
|
||||||
|
rpc("sync_safe_zone_disappear_vfx")
|
||||||
|
else:
|
||||||
|
sync_safe_zone_disappear_vfx()
|
||||||
|
await get_tree().create_timer(0.55).timeout
|
||||||
|
|
||||||
for rect in active_safe_zone_rects:
|
for rect in active_safe_zone_rects:
|
||||||
for rx in range(rect.size.x):
|
for rx in range(rect.size.x):
|
||||||
for rz in range(rect.size.y):
|
for rz in range(rect.size.y):
|
||||||
@@ -664,6 +918,12 @@ func _clear_dynamic_safe_zones():
|
|||||||
|
|
||||||
active_safe_zone_rects.clear()
|
active_safe_zone_rects.clear()
|
||||||
spawned_safe_zones = 0
|
spawned_safe_zones = 0
|
||||||
|
|
||||||
|
# Free all outline containers now that the animation has finished
|
||||||
|
for outline in _outline_nodes:
|
||||||
|
if is_instance_valid(outline):
|
||||||
|
outline.queue_free()
|
||||||
|
_outline_nodes.clear()
|
||||||
|
|
||||||
func _scatter_player_tiles(player_node: Node):
|
func _scatter_player_tiles(player_node: Node):
|
||||||
"""Server: Take all tiles from player's playerboard and scatter them onto nearby grid cells."""
|
"""Server: Take all tiles from player's playerboard and scatter them onto nearby grid cells."""
|
||||||
|
|||||||
Reference in New Issue
Block a user