Files
tekton/scripts/controllers/candy_cannon_controller.gd
T
adtpdn 5466f0bf36
Build and Release Patch PCK / build-and-deploy-patch (push) Failing after 2m22s
Automated Testing / test (push) Failing after 57s
fix: empty out all gridmap layers under candy pump and use precise duration timer for projectile destruction
2026-06-29 21:13:44 +08:00

138 lines
5.1 KiB
GDScript

extends Node3D
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:
_apply_outline_recursive(self)
func _apply_outline_recursive(node: Node) -> void:
if node is MeshInstance3D and node.mesh:
# Some procedural meshes might not have get_surface_count or it might be 1.
# If the mesh has material_override, apply outline to that
if node.material_override and node.material_override is StandardMaterial3D:
var mat = node.material_override
if not mat.next_pass:
var outline_mat = ShaderMaterial.new()
outline_mat.shader = load("res://assets/shaders/outline3d.gdshader")
mat.next_pass = outline_mat
else:
for i in range(node.mesh.get_surface_count()):
var mat = node.get_active_material(i)
if mat:
if not mat.next_pass:
var unique_mat = mat.duplicate()
var outline_mat = ShaderMaterial.new()
outline_mat.shader = load("res://assets/shaders/outline3d.gdshader")
unique_mat.next_pass = outline_mat
node.set_surface_override_material(i, unique_mat)
if node is GPUParticles3D and node.draw_pass_1 and node.draw_pass_1.material:
var mat = node.draw_pass_1.material
if not mat.next_pass:
var outline_mat = ShaderMaterial.new()
outline_mat.shader = load("res://assets/shaders/outline3d.gdshader")
mat.next_pass = outline_mat
for child in node.get_children():
_apply_outline_recursive(child)
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
func spawn_projectile(target_world_pos: Vector3, duration: float) -> void:
var projectile = MeshInstance3D.new()
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
projectile.top_level = true
add_child(projectile)
# Start projectile slightly above the cannon center
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)
_apply_outline_recursive(projectile)
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()
# Important: Make sure both halves of the y-tween together equal `duration`
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)
# 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()
# We need to wait for the X/Z tween to finish, but since it's parallel we can just use a separate timer or tween
# to kill the projectile exactly when duration is reached, ensuring it doesn't get killed early by X/Z finishing 1 frame earlier than Y
get_tree().create_timer(duration).timeout.connect(func():
if is_instance_valid(spin_tween): spin_tween.kill()
if is_instance_valid(projectile): projectile.queue_free()
)
func can_rpc() -> bool:
if not multiplayer.has_multiplayer_peer(): return false
return multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED