feat: implement the new "Stop N Go" game mode, including phase management, dynamic safe zones, player missions, and dedicated UI with visual effects.

This commit is contained in:
Yogi Wiguna
2026-03-24 15:16:30 +08:00
parent da9ba9361d
commit 4946f1daa6
30 changed files with 759 additions and 48 deletions
+2 -4
View File
@@ -181,10 +181,8 @@ func clear_highlights():
# Check Layer 2 (Overlay Highlight)
var l2_pos = Vector3i(cell.x, 2, cell.y)
var l2_item = enhanced_gridmap.get_cell_item(l2_pos)
# Only clear if it looks like a highlight (e.g. ID 1)
# or generally just clear layer 2 if we assume we own it for highlights?
# Safest is to check against hover_id or typical highlight IDs.
if l2_item != -1:
# Only clear if it is a highlight and NOT a Safe Zone (ID 2)
if l2_item != -1 and l2_item != 2:
enhanced_gridmap.set_cell_item(l2_pos, -1)
highlighted_cells.clear()
+22 -21
View File
@@ -99,18 +99,18 @@ func _process(delta):
if multiplayer.is_server():
if current_phase == Phase.GO:
var int_timer = int(ceil(phase_timer))
if int_timer == 3 and spawned_safe_zones == 0 and phase_timer <= 3.0:
_spawn_dynamic_safe_zone()
elif int_timer == 2 and spawned_safe_zones == 1 and phase_timer <= 2.0:
_spawn_dynamic_safe_zone()
elif int_timer == 1 and spawned_safe_zones == 2 and phase_timer <= 1.0:
_spawn_dynamic_safe_zone()
# Spawn all 3 safe zones at once, 3 seconds before STOP
if phase_timer <= 3.0 and spawned_safe_zones == 0:
print("[StopNGo] GO phase ending soon. Spawning 3 safe zones...")
for i in range(3):
_spawn_dynamic_safe_zone()
if phase_timer <= 0:
if current_phase == Phase.GO:
print("[StopNGo] GO Timer reached 0. Starting STOP Phase.")
_start_phase(Phase.STOP)
else:
print("[StopNGo] STOP Timer reached 0. Starting GO Phase.")
_start_phase(Phase.GO)
# Update HUD locally
@@ -499,8 +499,9 @@ func _is_in_safe_zone(pos: Vector2i) -> bool:
gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
if not gridmap: return false
var floor_tile = gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
return floor_tile == TILE_SAFE
# Check Layer 2 (Overlay layer) for Safe Zone tile
var overlay_tile = gridmap.get_cell_item(Vector3i(pos.x, 2, pos.y))
return overlay_tile == TILE_SAFE
func _spawn_dynamic_safe_zone():
if not multiplayer.is_server(): return
@@ -523,15 +524,17 @@ func _spawn_dynamic_safe_zone():
var rect = possible_rects.pick_random()
active_safe_zone_rects.append(rect)
spawned_safe_zones += 1
print("[StopNGo] Spawning Safe Zone %d at %s" % [spawned_safe_zones, rect])
# Paint floor to TILE_SAFE
# Paint Floor 2 (Overlay layer) to TILE_SAFE
# This allows seeing items below on Floor 1
for rx in range(rect.size.x):
for rz in range(rect.size.y):
var px = rect.position.x + rx
var pz = rect.position.y + rz
gridmap.set_cell_item(Vector3i(px, 0, pz), TILE_SAFE)
gridmap.set_cell_item(Vector3i(px, 2, pz), TILE_SAFE)
if can_rpc() and main:
main.rpc("sync_grid_item", px, 0, pz, TILE_SAFE)
main.rpc("sync_grid_item", px, 2, pz, TILE_SAFE)
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
@@ -546,16 +549,12 @@ func _is_valid_safe_zone_area(gridmap: Node, start_x: int, start_z: int, width:
for x in range(start_x, start_x + width):
for z in range(start_z, start_z + height):
var floor_0 = gridmap.get_cell_item(Vector3i(x, 0, z))
var floor_1 = gridmap.get_cell_item(Vector3i(x, 1, z))
# Floor must be purely TILE_WALKABLE (0)
# We no longer check Floor 1 (items) so safe zones can spawn ON TOP of tiles
if floor_0 != TILE_WALKABLE:
return false
# Floor 1 must be empty (-1) - no items or obstacles
if floor_1 != -1:
return false
return true
func _clear_dynamic_safe_zones():
@@ -564,17 +563,19 @@ func _clear_dynamic_safe_zones():
if not gridmap: return
var main = get_node_or_null("/root/Main")
print("[StopNGo] Clearing %d active safe zones." % active_safe_zone_rects.size())
for rect in active_safe_zone_rects:
for rx in range(rect.size.x):
for rz in range(rect.size.y):
var px = rect.position.x + rx
var pz = rect.position.y + rz
# Only clear if it is actually still a safe zone
if gridmap.get_cell_item(Vector3i(px, 0, pz)) == TILE_SAFE:
gridmap.set_cell_item(Vector3i(px, 0, pz), TILE_WALKABLE)
# Only clear layer 2
if gridmap.get_cell_item(Vector3i(px, 2, pz)) == TILE_SAFE:
gridmap.set_cell_item(Vector3i(px, 2, pz), -1)
if can_rpc() and main:
main.rpc("sync_grid_item", px, 0, pz, TILE_WALKABLE)
main.rpc("sync_grid_item", px, 2, pz, -1)
active_safe_zone_rects.clear()
spawned_safe_zones = 0
+1 -1
View File
@@ -497,7 +497,7 @@ func _apply_settings():
"""Apply current settings to UI elements."""
# Apply joystick visibility
if virtual_joystick:
virtual_joystick.visible = joystick_enabled
virtual_joystick.visible = joystick_enabled and _is_touch_device()
# Apply touch buttons visibility - FORCED ON per request to "just show them"
# Apply touch buttons visibility