feat: Implement powerup inventory UI and manager, introducing ghost and area freeze special tiles.

This commit is contained in:
Yogi Wiguna
2026-02-03 17:04:35 +08:00
parent 02d13d9ff5
commit 757051aca8
12 changed files with 297 additions and 69 deletions
@@ -6,7 +6,7 @@
[ext_resource type="ArrayMesh" uid="uid://brevl3ab0tdqe" path="res://assets/models/tiles/tile_wall.tres" id="4_8v5xv"]
[ext_resource type="ArrayMesh" uid="uid://b5ta7tcw0iscd" path="res://assets/models/tiles/tile_coin.tres" id="4_76xkl"]
[ext_resource type="ArrayMesh" uid="uid://d4himvyb81in8" path="res://assets/models/meshes/non-walkable.res" id="4_sx8rm"]
[ext_resource type="ArrayMesh" uid="uid://dr80txgr61irt" path="res://assets/models/tiles/tile_area_freeze.tres" id="5_sx8rm"]
[ext_resource type="ArrayMesh" uid="uid://dqo83gvaay6sn" path="res://assets/models/tiles/tile_area_freeze.tres" id="5_sx8rm"]
[ext_resource type="ArrayMesh" uid="uid://bqvqj3fhf5x51" path="res://assets/models/tiles/tile_ghost.tres" id="6_r32il"]
[ext_resource type="ArrayMesh" uid="uid://cv4bedhida00g" path="res://assets/models/tiles/tile_star.tres" id="7_p5epg"]
[ext_resource type="ArrayMesh" uid="uid://gpnl4cjrivor" path="res://assets/models/tiles/tile_speed.tres" id="7_sx8rm"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2vhatfmufn3d"
path="res://.godot/imported/ghost.png-50b35ecd19732e2631cec7740daf0cec.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/touch_control/ghost.png"
dest_files=["res://.godot/imported/ghost.png-50b35ecd19732e2631cec7740daf0cec.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
+13 -13
View File
@@ -1,41 +1,41 @@
[gd_resource type="ArrayMesh" format=4 uid="uid://dr80txgr61irt"]
[gd_resource type="ArrayMesh" format=4 uid="uid://dqo83gvaay6sn"]
[ext_resource type="Texture2D" uid="uid://cp7jtg77hc078" path="res://assets/textures/power_tile/freeze_area_tile.png" id="1_3pijd"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2kxvc"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ev6sk"]
resource_name = "boost"
cull_mode = 2
albedo_color = Color(0.905882, 0.905882, 0.905882, 0.45098)
albedo_color = Color(0.91, 0.91, 0.91, 0.45098)
albedo_texture = ExtResource("1_3pijd")
[sub_resource type="ArrayMesh" id="ArrayMesh_ggb2g"]
[sub_resource type="ArrayMesh" id="ArrayMesh_w3aij"]
_surfaces = [{
"aabb": AABB(-0.282176, -0.000324821, -0.282176, 0.564351, 0.050792, 0.564351),
"format": 34896613377,
"index_count": 36,
"index_data": PackedByteArray("AAABAAIAAAADAAEABAABAAMABQADAAAABQAEAAMAAgAFAAAABAAGAAEAAgABAAYABgAEAAUAAgAHAAUABgAFAAcAAgAGAAcA"),
"index_data": PackedByteArray("BwAEAAUABwAGAAQABQACAAMABQAEAAIAAAAEAAYAAAACAAQABQABAAcABQADAAEAAgABAAMAAgAAAAEAAQAGAAcAAQAAAAYA"),
"name": "boost",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 8,
"vertex_data": PackedByteArray("AAAAAAAAAAD//wAA//8AAP//AAAAAAAAAAAAAP//AAAAAP////8AAAAA/v8AAAAA////////AAD///7/AAAAAA==")
"vertex_data": PackedByteArray("AAD/////AAAAAP7/AAAAAP///////wAA///+/wAAAAD//wAA//8AAP//AAAAAAAAAAAAAP//AAAAAAAAAAAAAA==")
}]
blend_shape_mode = 0
[resource]
resource_name = "tile_tile_coin_002"
resource_name = "tile_star"
_surfaces = [{
"aabb": AABB(-0.282176, -0.000324821, -0.282176, 0.564351, 0.050792, 0.564351),
"attribute_data": PackedByteArray("zuS25MX6sfrF+rbkzuSx+szkQOSr9qfkzOSn5Kv2QOSw+yzlSvvl70r7LOWw++XvSvu0+rD7+u9K+/rvsPu0+pHeot5tIVwhkd5cIW0hot47+6/61PrQ6NT6r/o7+9Do"),
"attribute_data": PackedByteArray("sPss5W0hot47+9DosPv6720hXCE7+6/6sPvl75Heot6r9qfksPu0+pHeXCHM5KfkSvvl78X6sfqr9kDkSvu0+sX6tuTM5EDkSvss5c7ksfrU+tDoSvv6787ktuTU+q/6"),
"format": 34896613399,
"index_count": 36,
"index_data": PackedByteArray("AAABAAIAAAADAAEABAAFAAYABAAHAAUACAAJAAoACAALAAkADAANAA4ADAAPAA0AEAARABIAEAATABEAFAAVABYAFAAXABUA"),
"material": SubResource("StandardMaterial3D_2kxvc"),
"name": "tile_diamond_holo",
"index_data": PackedByteArray("FgANABAAFgATAA0AEQAIAAsAEQAOAAgAAAAMABIAAAAGAAwADwADABUADwAJAAMABwAEAAoABwABAAQABQAUABcABQACABQA"),
"material": SubResource("StandardMaterial3D_ev6sk"),
"name": "tile_area_freeze",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 24,
"vertex_data": PackedByteArray("AAAAAAAA/z///wAA////P///AAAAAP8/AAAAAP///z///wAAAAD////////////////+/wAA/////wAA/////wAA//////+///8AAP///78AAAAA////v/////////+///8AAAAAAAAAAP7/AAAAAAAAAAAAAAAA///+/wAAAAD/////////vwAA/v8AAP+////+/wAA/78AAP//////vwAA/v8AAKoqAAAAAP//qioAAAAAAACqKgAA/////6oqAAD/fwAA/38AAP9/AAD/f/+//3//v/9//7//f/+//3///////////////////////7//v/+//7//v/+//7//v////3////9/////f////39U1VTVVNVU1VTVVNVU1VTV")
"vertex_data": PackedByteArray("AAD//////78AAP//////vwAA/////6oqAAD+/wAAAAAAAP7/AAD/vwAA/v8AAKoq/////////7//////////v//////////////+/wAAAAD///7/AAD/v////v8AAP////8AAP///7///wAA////P///AAD///////8AAAAAAAD//wAAAAD/P///AAAAAP//AAAAAP///78AAAAA////PwAAAAD//6oqAAAAAAAAAAAAAAAAAAD/PwAAAAAAAKoq/////////39U1VTV/7//v////39U1VTV/////////3//v/9//7//v////3//v/9//////wAA/3//v/9//7//vwAA/3//v/9//////wAA/39U1VTV/7//vwAA/39U1VTV")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_ggb2g")
shadow_mesh = SubResource("ArrayMesh_w3aij")
+3
View File
@@ -1,9 +1,12 @@
[gd_resource type="ArrayMesh" format=4 uid="uid://bqvqj3fhf5x51"]
[ext_resource type="Texture2D" uid="uid://dv0datyodktqu" path="res://assets/textures/power_tile/ghost_tile.png" id="1_avhe4"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ev6sk"]
resource_name = "boost"
cull_mode = 2
albedo_color = Color(0.91, 0.91, 0.91, 0.45098)
albedo_texture = ExtResource("1_avhe4")
[sub_resource type="ArrayMesh" id="ArrayMesh_w3aij"]
_surfaces = [{
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dv0datyodktqu"
path.s3tc="res://.godot/imported/ghost_tile.png-472e4da46a96c33de653b9d0ab2de179.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/textures/power_tile/ghost_tile.png"
dest_files=["res://.godot/imported/ghost_tile.png-472e4da46a96c33de653b9d0ab2de179.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
+2 -1
View File
@@ -29,6 +29,7 @@
[ext_resource type="Texture2D" uid="uid://cupfmb5m15kmf" path="res://assets/graphics/touch_control/wall.png" id="27_yq6so"]
[ext_resource type="Texture2D" uid="uid://3up2su2e0lfa" path="res://assets/graphics/touch_control/freeze_area.png" id="28_fv21b"]
[ext_resource type="Texture2D" uid="uid://ckhdyxnho6sjp" path="res://assets/graphics/touch_control/spawn_tile.png" id="28_j8jky"]
[ext_resource type="Texture2D" uid="uid://b2vhatfmufn3d" path="res://assets/graphics/touch_control/ghost.png" id="33_5q0nq"]
[ext_resource type="Script" uid="uid://86ikh0wuqk7v" path="res://scripts/ui/powerup_inventory_ui.gd" id="powerup_ui_script"]
[ext_resource type="Script" uid="uid://b54tfa0n6kogi" path="res://scripts/managers/touch_controls.gd" id="touch_manager"]
[ext_resource type="Script" uid="uid://djiml4sh61dc1" path="res://scripts/ui/virtual_joystick.gd" id="virtual_joystick"]
@@ -9881,7 +9882,7 @@ expand_icon = true
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
icon = ExtResource("27_dgi5k")
icon = ExtResource("33_5q0nq")
flat = true
icon_alignment = 1
expand_icon = true
+42 -2
View File
@@ -613,11 +613,20 @@ func _apply_tint_recursive(node: Node, color: Color):
# If color is WHITE (reset), clear the overlay
if color == Color.WHITE:
node.material_overlay = null
node.transparency = 0.0 # Reset
else:
# If color is Blue (frozen), make it semi-transparent overlay
mat.albedo_color = color
mat.albedo_color.a = 0.5 # Semi-transparent
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
# Special Ghost Effect (Invisible Mode)
if color.a < 1.0:
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
node.transparency = 0.5 # Make base mesh transparent too if possible, depending on material
else:
# Frozen
mat.albedo_color.a = 0.5 # Semi-transparent overlay
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
node.material_overlay = mat
for child in node.get_children():
@@ -658,6 +667,37 @@ func apply_stagger(duration: float = 1.5):
else:
_apply_tint_recursive(self, Color.WHITE) # Remove tint
@rpc("any_peer", "call_local")
func apply_slow_effect(duration: float = 3.0):
# "area with blue like wall... Player who cross on that area will got slowed and freeze effect"
# Visual: Blue Tint
_apply_tint_recursive(self, Color(0.6, 0.8, 1.0)) # Icy Blue
# Logic: Slow Movement speed
if movement_manager:
# Use 0.2 multipliers to match "slowed" request (20% speed)
movement_manager.set_speed_multiplier(0.2)
print("Player %s is slowed for %.1f seconds" % [name, duration])
# Restore after duration
# Note: If they stand in the zone, this will be re-applied constantly, resetting the visual
await get_tree().create_timer(duration).timeout
# Only restore if not re-applied recently (simple check: if still tinted?)
# A better way is managing a "slow_timer" but for now let's just reset if timer expires.
# The persistent zone logic reapplies every frame, so we want this timer to be short
# OR we rely on the zone logic.
# The RPC call says "apply_slow_effect(0.5)", so it expires quickly.
if movement_manager:
movement_manager.set_speed_multiplier(1.0)
if immunity_timer > 0:
_apply_tint_recursive(self, Color(0.5, 1.0, 0.5))
else:
_apply_tint_recursive(self, Color.WHITE)
func playerboard_is_empty() -> bool:
for item in playerboard:
if item != -1:
+21 -4
View File
@@ -70,7 +70,11 @@ func simple_move_to(grid_position: Vector2i) -> bool:
return false
var cell_item = enhanced_gridmap.get_cell_item(Vector3i(grid_position.x, 0, grid_position.y))
if cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items:
# Allow passing through Walls (Item 4) if Invisible
var is_wall_passable = player.get("is_invisible") and cell_item == 4
if (cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items) and not is_wall_passable:
return false
if player.is_position_occupied(grid_position):
@@ -234,7 +238,14 @@ func highlight_movement_range():
# Check basic walkability
var cell_item = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z))
if cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items or player.is_position_occupied(test_pos):
# Allow passing through Walls (Item 4) if Invisible
var is_wall_passable = player.get("is_invisible") and cell_item == 4
if (cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items) and not is_wall_passable:
continue
if player.is_position_occupied(test_pos):
continue
# Check if there's a valid path to this cell
@@ -284,8 +295,14 @@ func can_reach_cell(target_pos: Vector2i, blocked_cells: Array) -> bool:
# Skip if already visited, blocked, or not valid
if visited.has(next_pos) or next_pos in blocked_cells:
continue
if not enhanced_gridmap.is_position_valid(next_pos) or not enhanced_gridmap.is_cell_walkable(next_pos, 0):
# Custom Walkable Check incorporating invisibility
var cell_item = enhanced_gridmap.get_cell_item(Vector3i(next_pos.x, 0, next_pos.y))
var is_wall_passable = player.get("is_invisible") and cell_item == 4
if (not enhanced_gridmap.is_position_valid(next_pos) or \
(cell_item in enhanced_gridmap.non_walkable_items and not is_wall_passable) or \
cell_item == -1):
continue
if player.is_position_occupied(next_pos) and next_pos != target_pos:
+133 -47
View File
@@ -52,8 +52,10 @@ const INVISIBLE_DURATION = 6.0
# Active effect tracking
var blocked_tiles: Array[Dictionary] = [] # {position: Vector3i, original_item: int, timer: float}
var freeze_zones: Array[Dictionary] = [] # {position: Vector2i, timer: float}
var active_freeze_zones: Array = [] # Array of {center, radius, timer}
var invisible_timer: float = 0.0
# INVENTORY SYSTEM
# Stores count of each power-up type. Max 1 per type as per user request?
# "player can store 1 of each different power up"
@@ -189,47 +191,107 @@ func _execute_faster_speed():
NotificationManager.send_message(player, "Speed Boost! (5s)", NotificationManager.MessageType.POWERUP)
func _execute_area_freeze():
# "Area Freeze... slow their speed movement for 3s"
# Reuse freeze logic but simpler duration
var center = player.current_position
# 3x3 around player
var radius = 1
print("Player %s executing Area Freeze" % player.name)
# "area with blue like wall but with far away from the player who use it"
# "Make it like 4 floor first (offset 4) and the continue to bigger when the level... is close to max"
# Get enemies in radius
# 1. Calculate Forward Direction based on Rotation
# Rotation 0 = South (+Z), PI = North (-Z)
var rot = player.rotation.y
var forward_x = round(sin(rot))
var forward_z = round(cos(rot))
var forward_vec = Vector2i(forward_x, forward_z)
# If rotation is diagonal or imprecise, normalize to cardinal
if abs(forward_x) > abs(forward_z):
forward_vec = Vector2i(sign(forward_x), 0)
else:
forward_vec = Vector2i(0, sign(forward_z))
# 2. Offset Center (4 tiles away)
var offset_dist = 4
var center = player.current_position + (forward_vec * offset_dist)
# 3. Determine Radius based on Level
# Level 1-4: Radius 1 (3x3 area)
# Level 5-8: Radius 2 (5x5 area)
var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1)
var radius = 1
if current_lvl >= 5:
radius = 2 # Bigger area at high levels
print("Player %s executing Area Freeze at %s (Offset %s, Lvl %d, Rad %d)" % [player.name, center, forward_vec, current_lvl, radius])
# Register Zone for persistence
active_freeze_zones.append({
"center": center,
"radius": radius,
"timer": FREEZE_SLOW_DURATION # Same duration as the visual
})
# Initial Check (Instant Feedback)
var all_players = player.get_tree().get_nodes_in_group("Players")
for p in all_players:
if p == player: continue
var dist = Vector2(p.current_position.x - center.x, p.current_position.y - center.y).length()
if dist <= 1.5: # Adjacent or on top
# Check distance (Chebyshev distance for square area)
var dx = abs(p.current_position.x - center.x)
var dy = abs(p.current_position.y - center.y)
# If inside square radius
if dx <= radius and dy <= radius:
p.rpc("apply_slow_effect", FREEZE_SLOW_DURATION)
NotificationManager.send_message(p, "Caught in Freeze Zone!", NotificationManager.MessageType.WARNING)
# Visual Feedback (Icy Floor for 3s?)
# Visual Feedback (Turn Floor Blue - Item 12 on Layer 0)
if player.is_multiplayer_authority():
for x in range(-1, 2):
for y in range(-1, 2):
# Sync Icy Floor (Layer 0)
for x in range(-radius, radius + 1):
for y in range(-radius, radius + 1):
var pos = center + Vector2i(x, y)
var main = player.get_tree().get_root().get_node_or_null("Main")
if main: main.rpc("sync_grid_item", pos.x, 2, pos.y, 15) # Icy decal
if enhanced_gridmap.is_position_valid(pos):
var main = player.get_tree().get_root().get_node_or_null("Main")
# Use Item 12 (Blue Freeze Tile) on Layer 0 (Floor)
if main: main.rpc("sync_grid_item", pos.x, 0, pos.y, 12)
# Cleanup visual timer (managed locally by author)
get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func():
for x in range(-1, 2):
for y in range(-1, 2):
for x in range(-radius, radius + 1):
for y in range(-radius, radius + 1):
var pos = center + Vector2i(x, y)
var main = player.get_tree().get_root().get_node_or_null("Main")
if main: main.rpc("sync_grid_item", pos.x, 2, pos.y, -1)
if enhanced_gridmap.is_position_valid(pos):
var main = player.get_tree().get_root().get_node_or_null("Main")
# Restore to Item 0 (Standard Floor)
if main: main.rpc("sync_grid_item", pos.x, 0, pos.y, 0)
)
func _execute_block_floor(target: Node3D):
# Existing logic for blocking, reused
# "Wall Block" usually means block WHERE YOU ARE or FRONT?
# Original code blocked target's floor.
# If target is self, blocks self's floor? Maybe defensive.
# "Have the same cooldown as Faster" -> Just logic reuse.
# User Request: "choose one between horizontal or vertical all the way to the colus or rows"
# We interpret "Choose one" as random 50/50 since there's no UI for sub-selection.
# Check for Immunity (Invisible Mode)
if target.get("is_invisible"):
NotificationManager.send_message(target, "blocked!", NotificationManager.MessageType.POWERUP)
# We should probably notify the attacker too?
return
var center = target.current_position
var neighbors = enhanced_gridmap.get_neighbors(center, 1)
neighbors.append({"position": center})
var is_horizontal = rng.randf() < 0.5
var neighbors = []
if is_horizontal:
# Block entire Row (Fixed Z, iterate all X)
# Assuming 'center.y' corresponds to Grid Z-row
var row_z = center.y
for x in range(enhanced_gridmap.columns):
neighbors.append({"position": Vector2i(x, row_z)})
print("Player %s activated Wall Block: HORIZONTAL ROW (Z=%d)" % [player.name, row_z])
else:
# Block entire Column (Fixed X, iterate all Z)
var col_x = center.x
for z in range(enhanced_gridmap.rows):
neighbors.append({"position": Vector2i(col_x, z)})
print("Player %s activated Wall Block: VERTICAL COLUMN (X=%d)" % [player.name, col_x])
for n in neighbors:
var pos = n.position
@@ -240,8 +302,9 @@ func _execute_block_floor(target: Node3D):
if main:
main.rpc("sync_grid_item", block_pos.x, block_pos.y, block_pos.z, 4)
var original_item = 0 # Assume floor
# If we have logic to save original, fine, but for now just 0
# We don't save original item here properly in this loop if we overwrite something important,
# but for Floor 0 it's usually just ground (0) or obstacles.
# If we overwrite another Wall, it's fine.
blocked_tiles.append({
"position": block_pos,
"original_item": 0,
@@ -250,9 +313,13 @@ func _execute_block_floor(target: Node3D):
NotificationManager.send_message(target, "Wall Block Created!", NotificationManager.MessageType.POWERUP)
func _execute_invisible_mode(target: Node3D):
# Existing logic kept as ID 14 placeholder
target.is_invisible = true
invisible_timer = INVISIBLE_DURATION
# Visual Feedback: Ghost Mode (Low Alpha)
if target.has_method("sync_modulate"):
target.rpc("sync_modulate", Color(1.0, 1.0, 1.0, 0.4)) # 40% Opacity
NotificationManager.send_message(target, "Invisible Mode!", NotificationManager.MessageType.POWERUP)
@@ -273,12 +340,12 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true):
if rng.randf() > 0.5: continue
var item_id: int
# 70% Chance for PowerUp (11-14)
# 70% Chance for Normal Tile (7-10)
if rng.randf() < 0.7:
item_id = rng.randi_range(11, 14)
else:
# 30% Chance for Normal Tile (7-10)
item_id = rng.randi_range(7, 10)
else:
# 30% Chance for PowerUp (11-14)
item_id = rng.randi_range(11, 14)
var cell = Vector3i(pos.x, 1, pos.y)
@@ -290,23 +357,35 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true):
func _update_freeze_zones(delta: float):
# Only the authority of this manager (the caster) handles the timers and cleanup
if not player.is_multiplayer_authority():
return
if not active_freeze_zones.is_empty():
var zones_to_remove = []
var zones_to_remove = []
for i in range(freeze_zones.size()):
freeze_zones[i].timer -= delta
if freeze_zones[i].timer <= 0:
zones_to_remove.append(i)
for i in range(active_freeze_zones.size()):
var zone = active_freeze_zones[i]
zone.timer -= delta
# Cleanup expired zones
zones_to_remove.reverse()
for idx in zones_to_remove:
var zone = freeze_zones[idx]
var main = player.get_tree().get_root().get_node_or_null("Main")
if main:
main.rpc("sync_grid_item", zone.position.x, 2, zone.position.y, -1)
freeze_zones.remove_at(idx)
# Check for players inside this zone (Trap Logic)
var all_players = player.get_tree().get_nodes_in_group("Players")
for p in all_players:
# Invisible Immunity (Passive)
if p.get("is_invisible"): continue
var dx = abs(p.current_position.x - zone.center.x)
var dy = abs(p.current_position.y - zone.center.y)
# If inside zone
if dx <= zone.radius and dy <= zone.radius:
# Apply slow effect repeatedly
# We use a short duration so it expires quickly if they leave
p.rpc("apply_slow_effect", 0.5)
if zone.timer <= 0:
zones_to_remove.append(i)
# Cleanup expired zones
zones_to_remove.reverse()
for idx in zones_to_remove:
active_freeze_zones.remove_at(idx)
func _check_for_icy_floor():
# Every player checks if they are standing on an icy floor (item 15 on layer 2)
@@ -314,6 +393,10 @@ func _check_for_icy_floor():
if not player.is_multiplayer_authority():
return
# Invisible Immunity (Passive)
if player.is_invisible:
return
if not enhanced_gridmap:
return
@@ -339,7 +422,6 @@ func _process(delta):
if powerup_cooldowns[effect] <= 0:
powerup_cooldowns[effect] = 0
emit_signal("cooldown_updated", effect, 0, 0)
print("Cooldown finished for %s" % SpecialEffect.keys()[effect])
# Update Active Buffs (Speed)
if active_buffs.has(SpecialEffect.FASTER_SPEED):
@@ -392,7 +474,11 @@ func _update_invisible_timer(delta: float):
invisible_timer = 0
if is_instance_valid(player):
player.is_invisible = false
NotificationManager.send_message(player, NotificationManager.MESSAGES.INVISIBILITY_ENDED, NotificationManager.MessageType.NORMAL)
# Reset Visuals
if player.has_method("sync_modulate"):
player.rpc("sync_modulate", Color.WHITE)
NotificationManager.send_message(player, "Invisibility Ended", NotificationManager.MessageType.NORMAL)
# =============================================================================
+1 -1
View File
@@ -160,7 +160,7 @@ func _on_cooldown_updated(effect: int, time_left: float, max_time: float):
var cd_lbl = btn.get_node_or_null("CooldownLabel")
if cd_lbl:
if time_left > 0:
cd_lbl.text = "%.1f" % time_left
cd_lbl.text = "%d" % int(time_left)
btn.disabled = true
btn.modulate = Color(0.7, 0.7, 0.7, 0.8)
else: