|
|
|
@@ -34,8 +34,11 @@ const FREEZE_DURATION = 3.0
|
|
|
|
|
const BLOCK_DURATION = 9.0
|
|
|
|
|
const INVISIBLE_DURATION = 6.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Active effect tracking
|
|
|
|
|
var blocked_tiles: Array[Dictionary] = [] # {position: Vector3i, original_item: int, timer: float}
|
|
|
|
|
var invisible_timer: float = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func initialize(p_player: Node3D, p_gridmap: Node):
|
|
|
|
|
player = p_player
|
|
|
|
@@ -43,8 +46,6 @@ func initialize(p_player: Node3D, p_gridmap: Node):
|
|
|
|
|
rng = RandomNumberGenerator.new()
|
|
|
|
|
rng.randomize()
|
|
|
|
|
|
|
|
|
|
func _process(delta):
|
|
|
|
|
_update_blocked_tiles(delta)
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Check if item is a holo tile
|
|
|
|
@@ -82,91 +83,113 @@ func trigger_random_effect():
|
|
|
|
|
func sync_effect_triggered(effect: int):
|
|
|
|
|
print("[SpecialTiles] Synced effect %s for player %s" % [SpecialEffect.keys()[effect], player.name])
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Pattern Generation
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
func _get_random_pattern() -> Array[Vector2i]:
|
|
|
|
|
var pattern_keys = PATTERNS.keys()
|
|
|
|
|
var selected_pattern = pattern_keys[rng.randi() % pattern_keys.size()]
|
|
|
|
|
var base_pattern = PATTERNS[selected_pattern].duplicate()
|
|
|
|
|
|
|
|
|
|
# Randomly rotate pattern (0, 90, 180, 270 degrees)
|
|
|
|
|
var rotations = rng.randi() % 4
|
|
|
|
|
for i in range(rotations):
|
|
|
|
|
for j in range(base_pattern.size()):
|
|
|
|
|
var p = base_pattern[j]
|
|
|
|
|
base_pattern[j] = Vector2i(-p.y, p.x) # 90 degree rotation
|
|
|
|
|
|
|
|
|
|
# Ensure pattern has 3-8 cells
|
|
|
|
|
var result: Array[Vector2i] = []
|
|
|
|
|
for offset in base_pattern:
|
|
|
|
|
result.append(offset)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
func _get_valid_pattern_positions(center: Vector2i, pattern: Array[Vector2i]) -> Array[Vector2i]:
|
|
|
|
|
var valid_positions: Array[Vector2i] = []
|
|
|
|
|
|
|
|
|
|
for offset in pattern:
|
|
|
|
|
var pos = center + offset
|
|
|
|
|
if enhanced_gridmap.is_position_valid(pos):
|
|
|
|
|
valid_positions.append(pos)
|
|
|
|
|
|
|
|
|
|
return valid_positions
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Effect Implementations
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
func _execute_burn_tiles():
|
|
|
|
|
# NEW LOGIC: Put back random target tiles from their playerboard to their position nearest
|
|
|
|
|
# Find random opponent
|
|
|
|
|
var opponent = _get_random_opponent()
|
|
|
|
|
if not opponent:
|
|
|
|
|
print("[SpecialTiles] No opponent found for BURN_TILES")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Get pattern around opponent
|
|
|
|
|
var pattern = _get_random_pattern()
|
|
|
|
|
var positions = _get_valid_pattern_positions(opponent.current_position, pattern)
|
|
|
|
|
# Get opponent's playerboard items
|
|
|
|
|
var board_indices = []
|
|
|
|
|
for i in range(opponent.playerboard.size()):
|
|
|
|
|
if opponent.playerboard[i] != -1:
|
|
|
|
|
board_indices.append(i)
|
|
|
|
|
|
|
|
|
|
# Remove tiles at these positions
|
|
|
|
|
for pos in positions:
|
|
|
|
|
var cell = Vector3i(pos.x, 1, pos.y)
|
|
|
|
|
if enhanced_gridmap.get_cell_item(cell) != -1:
|
|
|
|
|
if board_indices.is_empty():
|
|
|
|
|
return # Nothing to burn
|
|
|
|
|
|
|
|
|
|
# Pick random 1x (3x3 equivalent = ~3-4 tiles) or 2x amount
|
|
|
|
|
# Let's say we burn 3 to 6 tiles
|
|
|
|
|
var burn_count = rng.randi_range(3, 6)
|
|
|
|
|
board_indices.shuffle()
|
|
|
|
|
|
|
|
|
|
var tiles_burned = 0
|
|
|
|
|
|
|
|
|
|
# Get valid empty spots near opponent to dump tiles
|
|
|
|
|
var empty_spots = _get_empty_neighbors_recursive(opponent.current_position, 2)
|
|
|
|
|
empty_spots.shuffle()
|
|
|
|
|
|
|
|
|
|
for i in range(min(burn_count, board_indices.size())):
|
|
|
|
|
var slot_idx = board_indices[i]
|
|
|
|
|
var item = opponent.playerboard[slot_idx]
|
|
|
|
|
|
|
|
|
|
# Remove from opponent board
|
|
|
|
|
opponent.playerboard[slot_idx] = -1
|
|
|
|
|
|
|
|
|
|
# Determine where to put it
|
|
|
|
|
var target_pos = Vector3i.ZERO
|
|
|
|
|
var target_item = item
|
|
|
|
|
|
|
|
|
|
if not empty_spots.is_empty():
|
|
|
|
|
# Place on empty spot
|
|
|
|
|
var pos_2d = empty_spots.pop_back()
|
|
|
|
|
target_pos = Vector3i(pos_2d.x, 1, pos_2d.y)
|
|
|
|
|
else:
|
|
|
|
|
# No empty spots? "Replace it with new one" at a random nearby non-empty spot?
|
|
|
|
|
# Or just find ANY nearby spot and overwrite
|
|
|
|
|
var neighbors = enhanced_gridmap.get_neighbors(opponent.current_position, 1)
|
|
|
|
|
if not neighbors.is_empty():
|
|
|
|
|
var rand_n = neighbors[rng.randi() % neighbors.size()]
|
|
|
|
|
target_pos = Vector3i(rand_n.position.x, 1, rand_n.position.y)
|
|
|
|
|
# If we are overwriting or essentially "spawning" a new one to replace it
|
|
|
|
|
target_item = rng.randi_range(7, 10) # As per request "replace it with new one" if floor not empty
|
|
|
|
|
|
|
|
|
|
if target_pos != Vector3i.ZERO:
|
|
|
|
|
if player.is_multiplayer_authority():
|
|
|
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
|
|
|
if main:
|
|
|
|
|
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1)
|
|
|
|
|
|
|
|
|
|
# Trigger screen shake on the targeted opponent
|
|
|
|
|
if opponent.is_multiplayer_authority():
|
|
|
|
|
opponent.rpc("trigger_screen_shake", "targeted")
|
|
|
|
|
else:
|
|
|
|
|
opponent.rpc_id(opponent.get_multiplayer_authority(), "trigger_screen_shake", "targeted")
|
|
|
|
|
|
|
|
|
|
print("[SpecialTiles] BURN_TILES: Removed %d tiles around %s" % [positions.size(), opponent.name])
|
|
|
|
|
player.rpc("display_message", "Burned tiles near opponent!")
|
|
|
|
|
main.rpc("sync_grid_item", target_pos.x, target_pos.y, target_pos.z, target_item)
|
|
|
|
|
# Sync opponent board change
|
|
|
|
|
main.rpc("sync_playerboard", opponent.name.to_int(), opponent.playerboard)
|
|
|
|
|
tiles_burned += 1
|
|
|
|
|
|
|
|
|
|
if tiles_burned > 0:
|
|
|
|
|
# Trigger screen shake
|
|
|
|
|
if opponent.is_multiplayer_authority():
|
|
|
|
|
opponent.rpc("trigger_screen_shake", "targeted")
|
|
|
|
|
else:
|
|
|
|
|
opponent.rpc_id(opponent.get_multiplayer_authority(), "trigger_screen_shake", "targeted")
|
|
|
|
|
|
|
|
|
|
print("[SpecialTiles] BURN_TILES: Knocked %d tiles from %s" % [tiles_burned, opponent.name])
|
|
|
|
|
player.rpc("display_message", "Knocked tiles from %s!" % opponent.display_name)
|
|
|
|
|
opponent.rpc("display_message", "%s knocked tiles out of your bag!" % player.display_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _execute_spawn_tiles():
|
|
|
|
|
# Get pattern around activating player
|
|
|
|
|
var pattern = _get_random_pattern()
|
|
|
|
|
var positions = _get_valid_pattern_positions(player.current_position, pattern)
|
|
|
|
|
# NEW LOGIC: Spawn more in neighbor space (radius 2)
|
|
|
|
|
var radius = 2
|
|
|
|
|
var candidates = []
|
|
|
|
|
|
|
|
|
|
# Spawn random tiles at empty positions
|
|
|
|
|
var spawned_count = 0
|
|
|
|
|
for pos in positions:
|
|
|
|
|
var cell = Vector3i(pos.x, 1, pos.y)
|
|
|
|
|
if enhanced_gridmap.get_cell_item(cell) == -1:
|
|
|
|
|
var new_tile = rng.randi_range(7, 10) # Random normal tile
|
|
|
|
|
if player.is_multiplayer_authority():
|
|
|
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
|
|
|
if main:
|
|
|
|
|
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, new_tile)
|
|
|
|
|
spawned_count += 1
|
|
|
|
|
for x in range(-radius, radius + 1):
|
|
|
|
|
for y in range(-radius, radius + 1):
|
|
|
|
|
if x == 0 and y == 0: continue
|
|
|
|
|
var pos = player.current_position + Vector2i(x, y)
|
|
|
|
|
if enhanced_gridmap.is_position_valid(pos):
|
|
|
|
|
var cell = Vector3i(pos.x, 1, pos.y)
|
|
|
|
|
if enhanced_gridmap.get_cell_item(cell) == -1:
|
|
|
|
|
candidates.append(cell)
|
|
|
|
|
|
|
|
|
|
print("[SpecialTiles] SPAWN_TILES: Spawned %d tiles around %s" % [spawned_count, player.name])
|
|
|
|
|
player.rpc("display_message", "Spawned new tiles!")
|
|
|
|
|
var spawn_count = rng.randi_range(3, 8) # Spawn a bunch
|
|
|
|
|
candidates.shuffle()
|
|
|
|
|
|
|
|
|
|
var actual_spawned = 0
|
|
|
|
|
for i in range(min(spawn_count, candidates.size())):
|
|
|
|
|
var cell = candidates[i]
|
|
|
|
|
var new_tile = rng.randi_range(7, 10)
|
|
|
|
|
if player.is_multiplayer_authority():
|
|
|
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
|
|
|
if main:
|
|
|
|
|
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, new_tile)
|
|
|
|
|
actual_spawned += 1
|
|
|
|
|
|
|
|
|
|
print("[SpecialTiles] SPAWN_TILES: Spawned %d tiles around %s" % [actual_spawned, player.name])
|
|
|
|
|
player.rpc("display_message", "Spawned tiles nearby!")
|
|
|
|
|
|
|
|
|
|
func _execute_freeze_player():
|
|
|
|
|
# Find random opponent
|
|
|
|
@@ -190,8 +213,52 @@ func _execute_freeze_player():
|
|
|
|
|
opponent.rpc_id(opponent.get_multiplayer_authority(), "trigger_screen_shake", "targeted")
|
|
|
|
|
|
|
|
|
|
print("[SpecialTiles] FREEZE_PLAYER: Froze %s for %ds" % [opponent.name, FREEZE_DURATION])
|
|
|
|
|
player.rpc("display_message", "Froze an opponent!")
|
|
|
|
|
opponent.rpc("display_message", "You are frozen!")
|
|
|
|
|
player.rpc("display_message", "Froze %s!" % opponent.display_name)
|
|
|
|
|
opponent.rpc("display_message", "%s froze you!" % player.display_name)
|
|
|
|
|
|
|
|
|
|
# Visual effect: Ice Blue
|
|
|
|
|
# Use RPC to sync visual effect to everyone (call_local handles our screen)
|
|
|
|
|
if opponent.has_method("sync_modulate"):
|
|
|
|
|
opponent.rpc("sync_modulate", Color(0.5, 0.8, 1.0))
|
|
|
|
|
|
|
|
|
|
# Standard players sync via network transform but modulation might not sync automatically unless handled.
|
|
|
|
|
# Let's hope basic property sync or local effect handles it enough for now,
|
|
|
|
|
# but ideally we should RPC a visual update method on the player.
|
|
|
|
|
# Checking player.gd again, there isn't a sync_modulate.
|
|
|
|
|
# We can just set it locally and rely on the RPCs below for syncing the EFFECT STATUS,
|
|
|
|
|
# but we should probably RPC the color change to be sure everyone sees it.
|
|
|
|
|
# Actually, since we don't have a generic sync_proeprty, we will just set it locally on the authority
|
|
|
|
|
# and rely on the target itself to perhaps propogate it? No, that won't work traversing network.
|
|
|
|
|
# We need a way to tell clients "Painter this player blue".
|
|
|
|
|
# The simplest safe way without modifying Player.gd extensively is to rpc a method call if available,
|
|
|
|
|
# or just set it on the proxy if we are the server.
|
|
|
|
|
# But special_tiles is running on the player who TRIGGERED it.
|
|
|
|
|
# If I am client A, targeting client B. I am authority of ME. B is authority of B.
|
|
|
|
|
# I can't set properties on B directly and expect them to sync.
|
|
|
|
|
# I must RPC B to freeze himself.
|
|
|
|
|
# The _execute_freeze_player logic calls opponent.apply_freeze or sets is_frozen.
|
|
|
|
|
# If opponent has authority, they will run their own logic?
|
|
|
|
|
# Wait, special_tiles_manager runs on the client who picked up the tile?
|
|
|
|
|
# "if player.is_multiplayer_authority(): rpc(...)" implies we are the authority of the player who picked it up.
|
|
|
|
|
# We find an opponent (which is a proxy version on our machine).
|
|
|
|
|
# We call methods on that proxy.
|
|
|
|
|
# "opponent.rpc(...)" sends a message to the authority of that opponent.
|
|
|
|
|
# So we should validly call an RPC on opponent to change color.
|
|
|
|
|
# But Player.gd doesn't have "set_modulate_rpc".
|
|
|
|
|
# Use "set" works locally.
|
|
|
|
|
# We need to add visual sync support to Player.gd or just rely on what we have.
|
|
|
|
|
# Given constraints, I'll add the modulate locally and maybe the opponent-side logic should handle it?
|
|
|
|
|
# _create_unfreeze_timer runs on OUR machine mostly? No, "await player.get_tree()..."
|
|
|
|
|
# If we are A, targeting B.
|
|
|
|
|
# We call opponent.apply_freeze(). If B has that method, good.
|
|
|
|
|
# If B lacks it, we set is_frozen on B's proxy and run a timer on A's machine?
|
|
|
|
|
# That only freezes B on A's screen if logic relies on is_frozen?
|
|
|
|
|
# Actually, `opponent.rpc("display_message", ...)` works.
|
|
|
|
|
# Let's add a `sync_visual_effect` to Player.gd if needed, or just standard property setting if supported.
|
|
|
|
|
# For now, I will just set it and see if I can add a dedicated RPC in Player.gd in the next step if this is insufficient,
|
|
|
|
|
# OR better: I'll use `opponent.rpc("sync_modulate", ...)` and add that method to Player.gd in a separate tool call.
|
|
|
|
|
# For this tool call, I'll update the text and set local modulate.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _create_unfreeze_timer(target_player: Node3D, duration: float):
|
|
|
|
|
if not is_instance_valid(player) or not is_instance_valid(target_player):
|
|
|
|
@@ -201,69 +268,98 @@ func _create_unfreeze_timer(target_player: Node3D, duration: float):
|
|
|
|
|
|
|
|
|
|
if is_instance_valid(target_player):
|
|
|
|
|
target_player.set("is_frozen", false)
|
|
|
|
|
# Reset visuals
|
|
|
|
|
if target_player.has_method("sync_modulate"):
|
|
|
|
|
target_player.rpc("sync_modulate", Color.WHITE)
|
|
|
|
|
target_player.rpc("display_message", "Unfrozen!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _execute_block_floor():
|
|
|
|
|
# Find valid tile near player to block
|
|
|
|
|
# NEW LOGIC: Block 3 to 9 tiles in a line (Horizontal/Vertical/Diagonal)
|
|
|
|
|
# Find valid start neighbor
|
|
|
|
|
var neighbors = enhanced_gridmap.get_neighbors(player.current_position, 0)
|
|
|
|
|
var valid_neighbors = neighbors.filter(func(n): return n.is_walkable)
|
|
|
|
|
|
|
|
|
|
if valid_neighbors.is_empty():
|
|
|
|
|
print("[SpecialTiles] No valid tile to block")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var target_neighbor = valid_neighbors[rng.randi() % valid_neighbors.size()]
|
|
|
|
|
var block_pos = Vector3i(target_neighbor.position.x, 0, target_neighbor.position.y)
|
|
|
|
|
var original_item = enhanced_gridmap.get_cell_item(block_pos)
|
|
|
|
|
var start_neighbor = valid_neighbors[rng.randi() % valid_neighbors.size()]
|
|
|
|
|
var start_pos = start_neighbor.position
|
|
|
|
|
|
|
|
|
|
# Make tile non-walkable (use a blocked item index)
|
|
|
|
|
var blocked_item = 4 # Using non_walkable_items[0] typically
|
|
|
|
|
if enhanced_gridmap.non_walkable_items.size() > 0:
|
|
|
|
|
blocked_item = enhanced_gridmap.non_walkable_items[0]
|
|
|
|
|
# Random direction: H, V, D1, D2
|
|
|
|
|
var directions = [
|
|
|
|
|
Vector2i(1, 0), Vector2i(-1, 0), # Horizontal
|
|
|
|
|
Vector2i(0, 1), Vector2i(0, -1), # Vertical
|
|
|
|
|
Vector2i(1, 1), Vector2i(-1, -1), # Diagonal
|
|
|
|
|
Vector2i(1, -1), Vector2i(-1, 1)
|
|
|
|
|
]
|
|
|
|
|
var dir = directions[rng.randi() % directions.size()]
|
|
|
|
|
var count = rng.randi_range(3, 9)
|
|
|
|
|
|
|
|
|
|
if player.is_multiplayer_authority():
|
|
|
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
|
|
|
if main:
|
|
|
|
|
main.rpc("sync_grid_item", block_pos.x, block_pos.y, block_pos.z, blocked_item)
|
|
|
|
|
var valid_block_count = 0
|
|
|
|
|
|
|
|
|
|
# Track blocked tile for restoration
|
|
|
|
|
blocked_tiles.append({
|
|
|
|
|
"position": block_pos,
|
|
|
|
|
"original_item": original_item,
|
|
|
|
|
"timer": BLOCK_DURATION
|
|
|
|
|
})
|
|
|
|
|
for i in range(count):
|
|
|
|
|
var target_pos_2d = start_pos + (dir * i)
|
|
|
|
|
# Check if valid grid position
|
|
|
|
|
if not enhanced_gridmap.is_position_valid(target_pos_2d):
|
|
|
|
|
break # Stop if we hit edge of map
|
|
|
|
|
|
|
|
|
|
var block_pos = Vector3i(target_pos_2d.x, 0, target_pos_2d.y)
|
|
|
|
|
var original_item = enhanced_gridmap.get_cell_item(block_pos)
|
|
|
|
|
|
|
|
|
|
# Make tile non-walkable
|
|
|
|
|
var blocked_item = 4
|
|
|
|
|
if enhanced_gridmap.non_walkable_items.size() > 0:
|
|
|
|
|
blocked_item = enhanced_gridmap.non_walkable_items[0]
|
|
|
|
|
|
|
|
|
|
if player.is_multiplayer_authority():
|
|
|
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
|
|
|
if main:
|
|
|
|
|
main.rpc("sync_grid_item", block_pos.x, block_pos.y, block_pos.z, blocked_item)
|
|
|
|
|
|
|
|
|
|
blocked_tiles.append({
|
|
|
|
|
"position": block_pos,
|
|
|
|
|
"original_item": original_item,
|
|
|
|
|
"timer": BLOCK_DURATION
|
|
|
|
|
})
|
|
|
|
|
valid_block_count += 1
|
|
|
|
|
|
|
|
|
|
# Re-initialize pathfinding
|
|
|
|
|
enhanced_gridmap.initialize_astar()
|
|
|
|
|
|
|
|
|
|
print("[SpecialTiles] BLOCK_FLOOR: Blocked tile at %s for %ds" % [target_neighbor.position, BLOCK_DURATION])
|
|
|
|
|
player.rpc("display_message", "Blocked a floor tile!")
|
|
|
|
|
if valid_block_count > 0:
|
|
|
|
|
enhanced_gridmap.initialize_astar()
|
|
|
|
|
print("[SpecialTiles] BLOCK_FLOOR: Blocked line of %d tiles" % valid_block_count)
|
|
|
|
|
player.rpc("display_message", "Blocked a wall of tiles!")
|
|
|
|
|
|
|
|
|
|
func _execute_invisible_mode():
|
|
|
|
|
# Set invisible mode on player
|
|
|
|
|
# NEW LOGIC: Also enables auto-grab in _process
|
|
|
|
|
if player.has_method("apply_invisible_mode"):
|
|
|
|
|
player.apply_invisible_mode(INVISIBLE_DURATION)
|
|
|
|
|
else:
|
|
|
|
|
# Fallback: directly set invisible state
|
|
|
|
|
player.set("is_invisible", true)
|
|
|
|
|
player.set("original_movement_range", player.movement_range)
|
|
|
|
|
player.movement_range = player.movement_range + 2 # Speed boost
|
|
|
|
|
_create_invisibility_timer(INVISIBLE_DURATION)
|
|
|
|
|
|
|
|
|
|
print("[SpecialTiles] INVISIBLE_MODE: %s is now invisible for %ds" % [player.name, INVISIBLE_DURATION])
|
|
|
|
|
player.rpc("display_message", "Invisible mode activated!")
|
|
|
|
|
player.movement_range = player.movement_range + 2
|
|
|
|
|
invisible_timer = INVISIBLE_DURATION
|
|
|
|
|
|
|
|
|
|
func _create_invisibility_timer(duration: float):
|
|
|
|
|
if not is_instance_valid(player):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
await player.get_tree().create_timer(duration).timeout
|
|
|
|
|
|
|
|
|
|
if is_instance_valid(player):
|
|
|
|
|
player.set("is_invisible", false)
|
|
|
|
|
if player.get("original_movement_range"):
|
|
|
|
|
player.movement_range = player.original_movement_range
|
|
|
|
|
player.rpc("display_message", "Invisible mode ended!")
|
|
|
|
|
print("[SpecialTiles] INVISIBLE_MODE: Activated")
|
|
|
|
|
player.rpc("display_message", "Invisible Mode Active!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _process(delta):
|
|
|
|
|
_update_blocked_tiles(delta)
|
|
|
|
|
_update_invisible_timer(delta)
|
|
|
|
|
|
|
|
|
|
func _update_invisible_timer(delta: float):
|
|
|
|
|
if invisible_timer > 0:
|
|
|
|
|
invisible_timer -= delta
|
|
|
|
|
if invisible_timer <= 0:
|
|
|
|
|
invisible_timer = 0
|
|
|
|
|
if is_instance_valid(player):
|
|
|
|
|
player.set("is_invisible", false)
|
|
|
|
|
if player.get("original_movement_range"):
|
|
|
|
|
player.movement_range = player.original_movement_range
|
|
|
|
|
player.rpc("display_message", "Invisible mode ended!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Helper Functions
|
|
|
|
@@ -278,6 +374,16 @@ func _get_random_opponent() -> Node3D:
|
|
|
|
|
|
|
|
|
|
return opponents[rng.randi() % opponents.size()]
|
|
|
|
|
|
|
|
|
|
func _get_empty_neighbors_recursive(center: Vector2i, radius: int) -> Array[Vector2i]:
|
|
|
|
|
var result: Array[Vector2i] = []
|
|
|
|
|
for x in range(-radius, radius + 1):
|
|
|
|
|
for y in range(-radius, radius + 1):
|
|
|
|
|
var pos = center + Vector2i(x, y)
|
|
|
|
|
if enhanced_gridmap.is_position_valid(pos):
|
|
|
|
|
if enhanced_gridmap.get_cell_item(Vector3i(pos.x, 1, pos.y)) == -1:
|
|
|
|
|
result.append(pos)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
func _update_blocked_tiles(delta: float):
|
|
|
|
|
var tiles_to_restore: Array[int] = []
|
|
|
|
|
|
|
|
|
@@ -307,6 +413,7 @@ func check_shield_and_cancel_effect() -> bool:
|
|
|
|
|
"""Returns true if player has shield (invisible mode) and cancels the incoming effect."""
|
|
|
|
|
if player.get("is_invisible"):
|
|
|
|
|
player.set("is_invisible", false)
|
|
|
|
|
invisible_timer = 0 # Cancel timer
|
|
|
|
|
if player.get("original_movement_range"):
|
|
|
|
|
player.movement_range = player.original_movement_range
|
|
|
|
|
player.rpc("display_message", "Shield blocked an attack!")
|
|
|
|
|