feat: Implement powerup inventory UI and manager, introducing ghost and area freeze special tiles.
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user