Gauntlet UI fixes and cleanser improvements
This commit is contained in:
@@ -3,44 +3,97 @@ class_name CandyCannonController
|
||||
|
||||
@export var is_static_turret: bool = true
|
||||
|
||||
@onready var ring1 = $Ring1 if has_node("Ring1") else null
|
||||
@onready var ring2 = $Ring2 if has_node("Ring2") else null
|
||||
@onready var ring3 = $Ring3 if has_node("Ring3") else null
|
||||
@onready var tank = $Tank if has_node("Tank") else null
|
||||
|
||||
func _ready() -> void:
|
||||
pass
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if ring1: ring1.rotate_y(delta * 1.5)
|
||||
if ring2: ring2.rotate_x(delta * -1.0)
|
||||
if ring3: ring3.rotate_z(delta * 1.2)
|
||||
|
||||
if tank and tank.mesh and tank.mesh.material:
|
||||
var mat = tank.mesh.material as StandardMaterial3D
|
||||
if mat:
|
||||
# Gentle pulse of the candy tank
|
||||
var pulse = (sin(Time.get_ticks_msec() / 300.0) + 1.0) * 0.5
|
||||
mat.emission_energy_multiplier = 1.0 + (pulse * 2.0)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func play_animation_rpc(anim_name: String) -> void:
|
||||
# Stub for future model animations
|
||||
pass
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func spawn_projectile_rpc(target_world_pos: Vector3, duration: float) -> void:
|
||||
func spawn_projectile(target_world_pos: Vector3, duration: float) -> void:
|
||||
var projectile = MeshInstance3D.new()
|
||||
var sphere = BoxMesh.new()
|
||||
sphere.size = Vector3(0.4, 0.4, 0.4)
|
||||
var sphere = SphereMesh.new()
|
||||
sphere.radius = 0.3
|
||||
sphere.height = 0.6
|
||||
projectile.mesh = sphere
|
||||
|
||||
var mat = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(1.0, 0.4, 0.8) # Candy pink for Gauntlet
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(1.0, 0.4, 0.8)
|
||||
mat.emission_energy_multiplier = 3.0
|
||||
projectile.material_override = mat
|
||||
|
||||
get_tree().get_root().add_child(projectile)
|
||||
|
||||
# Start projectile slightly above the cannon center
|
||||
projectile.global_position = global_position + Vector3(0, 2.0, 0)
|
||||
projectile.global_position = global_position + Vector3(0, 3.0, 0)
|
||||
|
||||
var tween = create_tween()
|
||||
if not tween:
|
||||
projectile.queue_free()
|
||||
return
|
||||
|
||||
# VFX trail
|
||||
var particles = GPUParticles3D.new()
|
||||
var pmat = ParticleProcessMaterial.new()
|
||||
pmat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
|
||||
pmat.emission_sphere_radius = 0.3
|
||||
pmat.gravity = Vector3(0, 0, 0)
|
||||
pmat.scale_min = 0.1
|
||||
pmat.scale_max = 0.3
|
||||
particles.process_material = pmat
|
||||
|
||||
var pmesh = SphereMesh.new()
|
||||
pmesh.radius = 0.1
|
||||
pmesh.height = 0.2
|
||||
var spatial_mat = StandardMaterial3D.new()
|
||||
spatial_mat.albedo_color = Color(1.0, 0.6, 0.9)
|
||||
spatial_mat.emission_enabled = true
|
||||
spatial_mat.emission = Color(1.0, 0.6, 0.9)
|
||||
pmesh.material = spatial_mat
|
||||
particles.draw_pass_1 = pmesh
|
||||
|
||||
particles.amount = 16
|
||||
particles.lifetime = 0.4
|
||||
projectile.add_child(particles)
|
||||
|
||||
tween.set_parallel(true)
|
||||
tween.tween_property(projectile, "global_position:x", target_world_pos.x, duration).set_trans(Tween.TRANS_LINEAR)
|
||||
tween.tween_property(projectile, "global_position:z", target_world_pos.z, duration).set_trans(Tween.TRANS_LINEAR)
|
||||
|
||||
var mid_y = max(global_position.y, target_world_pos.y) + 4.0
|
||||
var tween_y = create_tween()
|
||||
tween_y.tween_property(projectile, "global_position:y", mid_y, duration / 2.0).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
tween_y.tween_property(projectile, "global_position:y", mid_y, duration / 2.0).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
|
||||
tween_y.tween_property(projectile, "global_position:y", target_world_pos.y, duration / 2.0).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN).set_delay(duration / 2.0)
|
||||
|
||||
tween.chain().tween_callback(projectile.queue_free)
|
||||
# Add some spin to the projectile
|
||||
var spin_tween = create_tween()
|
||||
spin_tween.set_loops()
|
||||
spin_tween.tween_property(projectile, "rotation", Vector3(PI*2, PI*2, PI*2), 0.5).as_relative()
|
||||
|
||||
tween.chain().tween_callback(func():
|
||||
spin_tween.kill()
|
||||
projectile.queue_free()
|
||||
)
|
||||
|
||||
func can_rpc() -> bool:
|
||||
if not multiplayer.has_multiplayer_peer(): return false
|
||||
|
||||
@@ -413,13 +413,15 @@ func _apply_arena_setup() -> void:
|
||||
|
||||
# Center 3x3 block: NPC obstacle (Candy Pump)
|
||||
if _is_npc_zone(pos):
|
||||
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_OBSTACLE)
|
||||
# Make the floor walkable visually instead of obstacle red
|
||||
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE)
|
||||
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
|
||||
continue
|
||||
|
||||
# Boundary walls: perimeter (row 0, row 19, col 0, col 19)
|
||||
if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1:
|
||||
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_OBSTACLE)
|
||||
# Also make border walls visually walkable floors instead of red blocks
|
||||
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE)
|
||||
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
|
||||
continue
|
||||
|
||||
@@ -511,9 +513,13 @@ func _spawn_mission_tiles() -> void:
|
||||
if _is_npc_zone(pos):
|
||||
continue
|
||||
|
||||
# Check base floor — don't spawn on obstacles or void
|
||||
# Check base floor — don't spawn on void (or walls if they were still obstacles)
|
||||
var base_tile = gridmap.get_cell_item(Vector3i(x, 0, z))
|
||||
if base_tile == TILE_OBSTACLE or base_tile == -1:
|
||||
if base_tile == -1:
|
||||
continue
|
||||
|
||||
# Ensure we don't spawn powerups on the perimeter walls even though they look like floors
|
||||
if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1:
|
||||
continue
|
||||
|
||||
# Skip if something already exists on Layer 1
|
||||
@@ -851,6 +857,15 @@ func sync_growth_telegraph(cells: Array) -> void:
|
||||
# Telegraph overlay tile on Layer 2 (still passable).
|
||||
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), TILE_TELEGRAPH)
|
||||
_spawn_telegraph_highlight(pos)
|
||||
|
||||
# NEW: Throw projectile from pump for normal growth
|
||||
if pump_instance and pump_instance.has_method("spawn_projectile"):
|
||||
var target_world_pos = Vector3(
|
||||
pos.x * gridmap.cell_size.x + gridmap.cell_size.x / 2.0,
|
||||
0.5,
|
||||
pos.y * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
|
||||
)
|
||||
pump_instance.spawn_projectile(target_world_pos, telegraph_duration)
|
||||
|
||||
# Audio: warning pulse
|
||||
if SfxManager:
|
||||
@@ -958,6 +973,17 @@ func _spawn_impact_particles(targets: Array) -> void:
|
||||
material.gravity = Vector3(0, -9.8, 0)
|
||||
material.scale_min = 0.1
|
||||
material.scale_max = 0.3
|
||||
|
||||
# Define visual mesh
|
||||
var mesh = BoxMesh.new()
|
||||
mesh.size = Vector3(0.2, 0.2, 0.2)
|
||||
var spatial_mat = StandardMaterial3D.new()
|
||||
spatial_mat.albedo_color = Color(1.0, 0.6, 0.8) # Candy pink
|
||||
spatial_mat.emission_enabled = true
|
||||
spatial_mat.emission = Color(1.0, 0.6, 0.8)
|
||||
spatial_mat.emission_energy_multiplier = 2.0
|
||||
mesh.material = spatial_mat
|
||||
particles.draw_pass_1 = mesh
|
||||
|
||||
particles.process_material = material
|
||||
particles.position = world_pos
|
||||
@@ -970,8 +996,57 @@ func _spawn_impact_particles(targets: Array) -> void:
|
||||
particles.queue_free()
|
||||
|
||||
# =============================================================================
|
||||
# Sticky / Trap System
|
||||
func _spawn_cleanser_particles(pos: Vector2i) -> void:
|
||||
"""Spawn bright cleansing particles when sticky is cleared."""
|
||||
if not main_scene or not gridmap:
|
||||
return
|
||||
|
||||
var world_pos = Vector3(
|
||||
pos.x * gridmap.cell_size.x + gridmap.cell_size.x / 2.0,
|
||||
0.5,
|
||||
pos.y * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
|
||||
)
|
||||
|
||||
var particles = GPUParticles3D.new()
|
||||
particles.emitting = true
|
||||
particles.one_shot = true
|
||||
particles.amount = 12
|
||||
particles.lifetime = 0.6
|
||||
particles.explosiveness = 0.9
|
||||
|
||||
var material = ParticleProcessMaterial.new()
|
||||
material.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
|
||||
material.emission_sphere_radius = 0.3
|
||||
material.direction = Vector3(0, 1, 0)
|
||||
material.spread = 180.0
|
||||
material.initial_velocity_min = 3.0
|
||||
material.initial_velocity_max = 5.0
|
||||
material.gravity = Vector3(0, -5.0, 0)
|
||||
material.scale_min = 0.05
|
||||
material.scale_max = 0.15
|
||||
|
||||
var mesh = SphereMesh.new()
|
||||
mesh.radius = 0.2
|
||||
mesh.height = 0.4
|
||||
var spatial_mat = StandardMaterial3D.new()
|
||||
spatial_mat.albedo_color = Color(0.2, 1.0, 1.0) # Cyan/Blue for cleanser
|
||||
spatial_mat.emission_enabled = true
|
||||
spatial_mat.emission = Color(0.2, 1.0, 1.0)
|
||||
spatial_mat.emission_energy_multiplier = 3.0
|
||||
mesh.material = spatial_mat
|
||||
particles.draw_pass_1 = mesh
|
||||
|
||||
particles.process_material = material
|
||||
particles.position = world_pos
|
||||
|
||||
main_scene.add_child(particles)
|
||||
|
||||
await get_tree().create_timer(1.2).timeout
|
||||
if particles and is_instance_valid(particles):
|
||||
particles.queue_free()
|
||||
|
||||
# =============================================================================
|
||||
# Sticky / Trap System
|
||||
|
||||
func is_sticky_cell(pos: Vector2i) -> bool:
|
||||
return sticky_cells.has(pos)
|
||||
@@ -1222,6 +1297,13 @@ func _generate_bubble_candidates() -> Array:
|
||||
var pos := Vector2i(x, z)
|
||||
if cell_state(pos) != CellState.SAFE:
|
||||
continue
|
||||
|
||||
# NEW: Ensure bubbles never pick boundary tiles or NPC area as center
|
||||
if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1:
|
||||
continue
|
||||
if _is_npc_zone(pos):
|
||||
continue
|
||||
|
||||
candidates.append({"pos": pos, "score": _calculate_bubble_score(pos, player_cells)})
|
||||
return candidates
|
||||
|
||||
@@ -1430,6 +1512,15 @@ func sync_bubble_spawn(center: Vector2i, cells: Array) -> void:
|
||||
_spawn_bubble_visual(center)
|
||||
if SfxManager:
|
||||
SfxManager.rpc("play_rpc", "generate_tile") if _can_rpc() else SfxManager.play("generate_tile")
|
||||
|
||||
# NEW: VFX projectile from center pump if it exists
|
||||
if pump_instance and pump_instance.has_method("spawn_projectile"):
|
||||
var target_world_pos = Vector3(
|
||||
center.x * gridmap.cell_size.x + gridmap.cell_size.x / 2.0,
|
||||
0.5,
|
||||
center.y * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
|
||||
)
|
||||
pump_instance.spawn_projectile(target_world_pos, BUBBLE_GROW_DURATION)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_bubble_explode(center: Vector2i, cells: Array) -> void:
|
||||
@@ -1439,6 +1530,7 @@ func sync_bubble_explode(center: Vector2i, cells: Array) -> void:
|
||||
for c in cells:
|
||||
var pos = c as Vector2i
|
||||
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), TILE_STICKY)
|
||||
sticky_cells[pos] = true
|
||||
# Medium shake — bubbles hit harder than a normal growth tick.
|
||||
if main_scene and main_scene.get("screen_shake_manager"):
|
||||
main_scene.screen_shake_manager.shake(0.3, 0.6)
|
||||
@@ -1532,14 +1624,30 @@ func _trap_player(player: Node) -> void:
|
||||
|
||||
func clear_sticky_cell(pos: Vector2i) -> void:
|
||||
"""Used by Cleanser power-up to remove a sticky cell."""
|
||||
if _can_rpc():
|
||||
if multiplayer.is_server():
|
||||
rpc("sync_clear_sticky_cell", pos)
|
||||
else:
|
||||
sync_clear_sticky_cell(pos) # Predictive local clear
|
||||
rpc("rpc_use_cleanser", pos)
|
||||
else:
|
||||
sync_clear_sticky_cell(pos)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_clear_sticky_cell(pos: Vector2i) -> void:
|
||||
sticky_cells.erase(pos)
|
||||
mark_cleansed(pos) # temporary regrowth protection (v2)
|
||||
if gridmap:
|
||||
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), -1)
|
||||
|
||||
# Sync removal to clients
|
||||
if main_scene and _can_rpc():
|
||||
main_scene.rpc("sync_grid_item", pos.x, 2, pos.y, -1)
|
||||
# Play VFX and SFX
|
||||
_spawn_cleanser_particles(pos)
|
||||
if SfxManager:
|
||||
SfxManager.play("pick_up_power_tile")
|
||||
|
||||
# Sync removal to main scene's gridmap if needed
|
||||
if main_scene and main_scene.has_method("sync_grid_item"):
|
||||
main_scene.sync_grid_item(pos.x, 2, pos.y, -1)
|
||||
|
||||
func _try_use_cleanser() -> void:
|
||||
"""Local player attempts to activate Cleanser for 5-cell sticky immunity."""
|
||||
@@ -1560,10 +1668,6 @@ func _try_use_cleanser() -> void:
|
||||
return
|
||||
if local_player.get("is_frozen") or local_player.get("is_stop_frozen"):
|
||||
return
|
||||
# Already active
|
||||
if cleanser_active.has(local_pid):
|
||||
return
|
||||
|
||||
# 0.3s activation delay
|
||||
await get_tree().create_timer(CLEANSER_ACTIVATION_DELAY).timeout
|
||||
|
||||
@@ -1573,23 +1677,23 @@ func _try_use_cleanser() -> void:
|
||||
if local_player.get("is_frozen") or local_player.get("is_stop_frozen"):
|
||||
return
|
||||
|
||||
# Activate cleanser immunity
|
||||
cleanser_active[local_pid] = true
|
||||
cleanser_cells_left[local_pid] = CLEANSER_MAX_CELLS
|
||||
|
||||
# Consume cleanser from inventory
|
||||
player_cleansers[local_pid] = 0
|
||||
update_cleanser_ui(0)
|
||||
# Consume cleanser from inventory (only if client, host relies on rpc)
|
||||
if not multiplayer.is_server():
|
||||
cleanser_active[local_pid] = true
|
||||
cleanser_cells_left[local_pid] = CLEANSER_MAX_CELLS
|
||||
player_cleansers[local_pid] = max(0, player_cleansers[local_pid] - 1)
|
||||
update_cleanser_ui(player_cleansers[local_pid])
|
||||
|
||||
# Sync to server/clients
|
||||
if not multiplayer.is_server() and _can_rpc():
|
||||
rpc("rpc_activate_cleanser", local_pid)
|
||||
elif multiplayer.is_server():
|
||||
if _can_rpc():
|
||||
rpc("sync_cleanser_count", local_pid, 0)
|
||||
# Call RPC logic directly for host (it will set active/cells_left/consume)
|
||||
rpc_activate_cleanser(local_pid)
|
||||
|
||||
NotificationManager.send_message(local_player, "Cleanser Active! (5 cells)", NotificationManager.MessageType.POWERUP)
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func deactivate_cleanser(player_id: int) -> void:
|
||||
"""Deactivate cleanser immunity for a player."""
|
||||
cleanser_active.erase(player_id)
|
||||
@@ -1605,30 +1709,67 @@ func use_cleanser_cell(player_id: int) -> bool:
|
||||
return false
|
||||
cleanser_cells_left[player_id] -= 1
|
||||
if cleanser_cells_left[player_id] <= 0:
|
||||
deactivate_cleanser(player_id)
|
||||
if _can_rpc():
|
||||
rpc("deactivate_cleanser", player_id)
|
||||
else:
|
||||
deactivate_cleanser(player_id)
|
||||
return false
|
||||
return true
|
||||
|
||||
func notify_movement_stopped(player_id: int, pos: Vector2i) -> void:
|
||||
"""Cleanser also ends when the player comes to rest on a safe (non-sticky)
|
||||
cell — they're clear of the candy, so immunity is no longer needed (#072).
|
||||
Called from PlayerMovementManager when a move chain settles. Gauntlet-only;
|
||||
a no-op when the player has no active cleanser."""
|
||||
if not cleanser_active.has(player_id):
|
||||
return
|
||||
if not is_sticky_cell(pos):
|
||||
deactivate_cleanser(player_id)
|
||||
"""Called from PlayerMovementManager when a move chain settles.
|
||||
Previously deactivated cleanser here, but now immunity persists
|
||||
until charges run out to allow repeated use across safe gaps."""
|
||||
pass
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func rpc_activate_cleanser(pid: int) -> void:
|
||||
"""RPC for clients to activate cleanser on server."""
|
||||
if multiplayer.is_server():
|
||||
if not cleanser_active.has(pid):
|
||||
cleanser_active[pid] = true
|
||||
cleanser_cells_left[pid] = CLEANSER_MAX_CELLS
|
||||
player_cleansers[pid] = 0
|
||||
if _can_rpc():
|
||||
rpc("sync_cleanser_count", pid, 0)
|
||||
# Verify they actually have a cleanser charge (prevents spam/cheats)
|
||||
if player_cleansers.get(pid, 0) <= 0:
|
||||
return
|
||||
|
||||
# Always apply the state and AoE, since this is the server authority
|
||||
cleanser_active[pid] = true
|
||||
cleanser_cells_left[pid] = CLEANSER_MAX_CELLS
|
||||
player_cleansers[pid] = max(0, player_cleansers[pid] - 1)
|
||||
if _can_rpc():
|
||||
rpc("sync_cleanser_count", pid, player_cleansers[pid])
|
||||
|
||||
# NEW: Clear 3x3 area around player
|
||||
var all_players = get_tree().get_nodes_in_group("Players")
|
||||
var target_player = null
|
||||
for p in all_players:
|
||||
var target_pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
|
||||
if target_pid == pid:
|
||||
target_player = p
|
||||
break
|
||||
|
||||
if gridmap and is_instance_valid(target_player):
|
||||
var map_pos = gridmap.local_to_map(target_player.global_position)
|
||||
var center_pos = Vector2i(map_pos.x, map_pos.z)
|
||||
|
||||
# 3x3 neighborhood
|
||||
for dx in range(-1, 2):
|
||||
for dz in range(-1, 2):
|
||||
var check_pos = center_pos + Vector2i(dx, dz)
|
||||
if is_sticky_cell(check_pos):
|
||||
clear_sticky_cell(check_pos)
|
||||
|
||||
# Remove slow effect for any player in the cleansed area
|
||||
for p in all_players:
|
||||
if is_instance_valid(p) and p.has_method("remove_slow_effect"):
|
||||
if gridmap:
|
||||
var p_map_pos = gridmap.local_to_map(p.global_position)
|
||||
var p_cell_pos = Vector2i(p_map_pos.x, p_map_pos.z)
|
||||
if abs(p_cell_pos.x - center_pos.x) <= 1 and abs(p_cell_pos.y - center_pos.y) <= 1:
|
||||
if _can_rpc():
|
||||
p.rpc("remove_slow_effect")
|
||||
else:
|
||||
p.remove_slow_effect()
|
||||
|
||||
print("[Cleanser] Server cleared 3x3 area around %s for player %d" % [center_pos, pid])
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func rpc_use_cleanser(pos: Vector2i) -> void:
|
||||
@@ -1729,9 +1870,9 @@ func _setup_hud() -> void:
|
||||
hud_layer = hud_instance
|
||||
hud_layer.visible = false
|
||||
add_child(hud_layer)
|
||||
phase_label = hud_layer.get_node("TopContainer/PhaseLabel")
|
||||
cleanser_icon = hud_layer.get_node("BottomContainer/CleanserHBox/CleanserIcon")
|
||||
cleanser_label = hud_layer.get_node("BottomContainer/CleanserHBox/CleanserLabel")
|
||||
phase_label = hud_layer.get_node("BottomContainer/VBoxContainer/PhaseLabel")
|
||||
cleanser_icon = hud_layer.get_node("BottomContainer/VBoxContainer/CleanserHBox/CleanserIcon")
|
||||
cleanser_label = hud_layer.get_node("BottomContainer/VBoxContainer/CleanserHBox/CleanserLabel")
|
||||
slowmo_label = hud_layer.get_node_or_null("TopContainer/SlowMoLabel")
|
||||
_generate_cleanser_icon()
|
||||
|
||||
@@ -1770,7 +1911,7 @@ func _update_hud_phase(phase_name: String) -> void:
|
||||
func update_cleanser_ui(count: int) -> void:
|
||||
cleanser_count = count
|
||||
if cleanser_label:
|
||||
cleanser_label.text = "Cleanser: %d" % count
|
||||
cleanser_label.text = "[E] Cleanser (%d)" % count
|
||||
# Show/hide icon based on availability
|
||||
if cleanser_icon:
|
||||
cleanser_icon.visible = count > 0
|
||||
@@ -1814,15 +1955,16 @@ func _on_goal_count_updated(peer_id: int, count: int) -> void:
|
||||
player_mission_completions[peer_id] = 0
|
||||
player_mission_completions[peer_id] += 1
|
||||
|
||||
# Grant cleanser every 2 missions (max 1)
|
||||
# Grant cleanser every 2 missions
|
||||
var completions = player_mission_completions[peer_id]
|
||||
if completions % 2 == 0:
|
||||
if not player_cleansers.has(peer_id):
|
||||
player_cleansers[peer_id] = 0
|
||||
if player_cleansers[peer_id] < 1:
|
||||
player_cleansers[peer_id] = 1
|
||||
emit_signal("cleanser_granted", peer_id)
|
||||
print("[Gauntlet] Player %d granted Cleanser (mission %d)" % [peer_id, completions])
|
||||
|
||||
# Allow stacking cleanser charges instead of capping at 1
|
||||
player_cleansers[peer_id] += 1
|
||||
emit_signal("cleanser_granted", peer_id)
|
||||
print("[Gauntlet] Player %d granted Cleanser (Total: %d) (mission %d)" % [peer_id, player_cleansers[peer_id], completions])
|
||||
|
||||
# Sync cleanser count to HUD
|
||||
rpc("sync_cleanser_count", peer_id, player_cleansers.get(peer_id, 0))
|
||||
|
||||
@@ -111,11 +111,22 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
||||
is_wall_passable = false
|
||||
print("[MovementManager] Hard block at %s. Ghost pass denied." % grid_position)
|
||||
|
||||
var gm = null
|
||||
var main_gauntlet = player.get_tree().root.get_node_or_null("Main")
|
||||
if main_gauntlet and main_gauntlet.get("gauntlet_manager"):
|
||||
gm = main_gauntlet.gauntlet_manager
|
||||
|
||||
# Check Floor 0 (Basic Walkability/Void)
|
||||
if (cell_floor == -1 or cell_floor in enhanced_gridmap.non_walkable_items) and not is_wall_passable:
|
||||
print("[Move] Failed: Floor Item %d is non-walkable" % cell_floor)
|
||||
return false
|
||||
|
||||
# Gauntlet Mode explicit wall overrides (since we visually removed the wall blocks)
|
||||
if gm and gm.is_active:
|
||||
if gm._is_npc_zone(grid_position):
|
||||
print("[Move] Failed: Blocked by Gauntlet NPC center at %s" % grid_position)
|
||||
return false
|
||||
|
||||
# Check Floor 1 (Obstacles/Walls)
|
||||
if (cell_item != -1 and cell_item in [4, 16]) and not is_wall_passable:
|
||||
print("[Move] Failed: Blocked by Item %d on Floor 1" % cell_item)
|
||||
@@ -130,11 +141,6 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
||||
var push_dir = grid_position - player.current_position
|
||||
if not try_push(grid_position, push_dir):
|
||||
return false
|
||||
|
||||
var gm = null
|
||||
var main_gauntlet = player.get_tree().root.get_node_or_null("Main")
|
||||
if main_gauntlet and main_gauntlet.get("gauntlet_manager"):
|
||||
gm = main_gauntlet.gauntlet_manager
|
||||
|
||||
# Sticky no longer hard-traps — players are slowed instead and can move freely.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user