9a8a946931
arcology updated ship models in particle fx is broken on kestrel
224 lines
7.9 KiB
GDScript
224 lines
7.9 KiB
GDScript
extends Node3D
|
|
## Kestrel EC-01 — analog 0..1 clip driver.
|
|
## Nodes: hatch_crew, hatch_ladder, hatch_dock, LandingGear, SeatCamera.
|
|
##
|
|
## Particle look is PARTICLE_FX.md. RCS is a short crossed ribbon. Abort is two tapered ribbons plus sparks.
|
|
## rcs_direction fires only the bells that push that way. Off fires nothing.
|
|
## abort_burn holds all four abort motors. The flame ramps in over 0.22 s and out over 0.28 s.
|
|
## Ship axes: nose +Y, starboard +X, dorsal +Z.
|
|
|
|
const Fx = preload("res://naf_fx.gd")
|
|
|
|
@export_range(0.0, 1.0) var gear_deploy := 1.0 : set = set_gear_deploy
|
|
@export_range(0.0, 1.0) var hatch_crew_open := 0.0 : set = set_hatch_crew_open
|
|
@export_range(0.0, 1.0) var hatch_ladder_deploy := 0.0 : set = set_hatch_ladder_deploy
|
|
@export_range(0.0, 1.0) var hatch_dock_open := 0.0 : set = set_hatch_dock_open
|
|
@export_range(0.0, 1.0) var instruments_demo := 0.4 : set = set_instruments_demo
|
|
@export_enum("Off", "Nose", "Tail", "Starboard", "Port", "Dorsal", "Ventral") var rcs_direction := 0
|
|
@export var abort_burn := false
|
|
## Cabin strips and fill. On drops them to a low watch. It does not swap to battle red.
|
|
@export var cabin_dim := false : set = set_cabin_dim
|
|
|
|
var _rcs: Array[Node3D] = []
|
|
var _aborts: Array[Dictionary] = []
|
|
var _abort_hold := 0.0
|
|
var _time := 0.0
|
|
var _cabin_ready := false
|
|
var _cabin_lights: Array[Light3D] = []
|
|
var _cabin_rest: Array[Dictionary] = []
|
|
var _cabin_mats: Array[Dictionary] = []
|
|
|
|
const _RCS_THRUST := [
|
|
Vector3.ZERO,
|
|
Vector3(0, 1, 0),
|
|
Vector3(0, -1, 0),
|
|
Vector3(1, 0, 0),
|
|
Vector3(-1, 0, 0),
|
|
Vector3(0, 0, 1),
|
|
Vector3(0, 0, -1),
|
|
]
|
|
|
|
func _ready() -> void:
|
|
_seek("gear_deploy", gear_deploy)
|
|
_arm_fx()
|
|
_collect_cabin()
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
_time += delta
|
|
var thrust: Vector3 = _RCS_THRUST[clampi(rcs_direction, 0, _RCS_THRUST.size() - 1)]
|
|
var ship := global_transform.basis
|
|
for ribbon in _rcs:
|
|
var want := 0.0
|
|
if thrust != Vector3.ZERO:
|
|
var exhaust: Vector3 = ship.inverse() * ribbon.global_transform.basis.y
|
|
if exhaust.dot(-thrust) > 0.7:
|
|
want = 1.0
|
|
var burn := float(ribbon.get_meta("burn"))
|
|
burn = Fx.smooth(burn, want, delta, 0.08 if want > burn else 0.14)
|
|
ribbon.set_meta("burn", burn)
|
|
Fx.set_spear(ribbon, burn, 1.0, _time)
|
|
if abort_burn:
|
|
_abort_hold = minf(1.0, _abort_hold + delta / 0.22)
|
|
elif _abort_hold > 0.0:
|
|
_abort_hold = maxf(0.0, _abort_hold - delta / 0.28)
|
|
var on := _abort_hold > 0.03
|
|
var flick := 0.86 + 0.14 * sin(_time * 41.0)
|
|
var sheath_scale := 0.9 + 0.1 * sin(_time * 19.0)
|
|
for a in _aborts:
|
|
var core := a.core as Node3D
|
|
var sheath := a.sheath as Node3D
|
|
Fx.set_opacity(core, _abort_hold * 0.95 * flick if on else 0.0)
|
|
Fx.set_opacity(sheath, _abort_hold * 0.82 * flick if on else 0.0)
|
|
sheath.scale = Vector3(1, sheath_scale, 1)
|
|
var sparks := a.sparks as GPUParticles3D
|
|
var sheath_sparks := a.sheath_sparks as GPUParticles3D
|
|
sparks.emitting = on
|
|
sheath_sparks.emitting = on
|
|
var proc := sparks.process_material as ParticleProcessMaterial
|
|
var proc2 := sheath_sparks.process_material as ParticleProcessMaterial
|
|
if proc:
|
|
proc.color = Color(1, 1, 1, _abort_hold * 0.55)
|
|
if proc2:
|
|
proc2.color = Color(1, 1, 1, _abort_hold * 0.55)
|
|
var flash := a.flash as MeshInstance3D
|
|
flash.visible = on
|
|
var fmat := flash.material_override as StandardMaterial3D
|
|
if fmat:
|
|
var tint := fmat.albedo_color
|
|
tint.a = _abort_hold * 0.45
|
|
fmat.albedo_color = tint
|
|
flash.scale = Vector3.ONE * (0.06 + _abort_hold * 0.04)
|
|
var light := a.light as SpotLight3D
|
|
light.light_energy = _abort_hold * 8.0 if on else 0.0
|
|
|
|
func set_cabin_dim(v: bool) -> void:
|
|
cabin_dim = v
|
|
if _cabin_ready:
|
|
_apply_cabin()
|
|
|
|
func _collect_cabin() -> void:
|
|
_cabin_lights.clear()
|
|
_cabin_rest.clear()
|
|
_cabin_mats.clear()
|
|
for n in find_children("*", "Light3D", true, false):
|
|
var nm := String(n.name)
|
|
if nm.begins_with("Nav"):
|
|
continue
|
|
if not (nm.begins_with("PointLight_") or nm == "CabinFill" or nm == "PanelLight" or nm.begins_with("CabinLamp")):
|
|
var p := n.get_parent()
|
|
var cabin := false
|
|
while p:
|
|
if String(p.name) == "CabinLights":
|
|
cabin = true
|
|
break
|
|
p = p.get_parent()
|
|
if not cabin:
|
|
continue
|
|
_cabin_lights.append(n)
|
|
_cabin_rest.append({"energy": n.light_energy, "color": n.light_color})
|
|
for n in find_children("*", "MeshInstance3D", true, false):
|
|
var nm := String(n.name)
|
|
if not nm.begins_with("LightStrip"):
|
|
continue
|
|
var mesh := n as MeshInstance3D
|
|
var mat := mesh.get_active_material(0)
|
|
if mat == null or not (mat is StandardMaterial3D):
|
|
continue
|
|
var copy := (mat as StandardMaterial3D).duplicate() as StandardMaterial3D
|
|
mesh.set_surface_override_material(0, copy)
|
|
_cabin_mats.append({"mat": copy, "energy": copy.emission_energy_multiplier, "color": copy.emission, "on": copy.emission_enabled})
|
|
_cabin_ready = true
|
|
_apply_cabin()
|
|
|
|
func _apply_cabin() -> void:
|
|
for i in _cabin_lights.size():
|
|
var lamp := _cabin_lights[i]
|
|
var rest: Dictionary = _cabin_rest[i]
|
|
if cabin_dim:
|
|
lamp.light_color = rest.color
|
|
lamp.light_energy = float(rest.energy) * 0.16
|
|
else:
|
|
lamp.light_color = rest.color
|
|
lamp.light_energy = float(rest.energy)
|
|
for row in _cabin_mats:
|
|
var mat := row.mat as StandardMaterial3D
|
|
if cabin_dim:
|
|
mat.emission_enabled = true
|
|
mat.emission = row.color
|
|
mat.emission_energy_multiplier = float(row.energy) * 0.18
|
|
else:
|
|
mat.emission_enabled = bool(row.on)
|
|
mat.emission = row.color
|
|
mat.emission_energy_multiplier = float(row.energy)
|
|
|
|
func set_gear_deploy(v: float) -> void:
|
|
gear_deploy = v
|
|
_seek("gear_deploy", v)
|
|
|
|
func set_hatch_crew_open(v: float) -> void:
|
|
hatch_crew_open = v
|
|
_seek("hatch_crew_open", v)
|
|
|
|
func set_hatch_ladder_deploy(v: float) -> void:
|
|
hatch_ladder_deploy = v
|
|
_seek("hatch_ladder_deploy", v)
|
|
|
|
func set_hatch_dock_open(v: float) -> void:
|
|
hatch_dock_open = v
|
|
_seek("hatch_dock_open", v)
|
|
|
|
func set_instruments_demo(v: float) -> void:
|
|
instruments_demo = v
|
|
_seek("instruments_demo", v)
|
|
|
|
func _seek(clip: StringName, t: float) -> void:
|
|
var ap := get_node_or_null("AnimationPlayer") as AnimationPlayer
|
|
if ap == null:
|
|
ap = find_child("AnimationPlayer", true, false) as AnimationPlayer
|
|
if ap == null or not ap.has_animation(clip):
|
|
return
|
|
var len := ap.get_animation(clip).length
|
|
ap.play(clip)
|
|
ap.seek(clampf(t, 0.0, 1.0) * len, true)
|
|
ap.pause()
|
|
|
|
func _arm_fx() -> void:
|
|
for n in find_children("*", "Node3D", true, false):
|
|
var nm := String(n.name)
|
|
if nm.begins_with("Vfx_RCS_"):
|
|
_rcs.append(Fx.spear(n, 1.2, 0.09, Color(0.863, 0.937, 1.0, 0.95), Color(0.41, 0.67, 1.0, 0.35), Color(0.25, 0.45, 0.8, 0.0), 0.82, 2, 1.0, "Spear"))
|
|
elif nm.begins_with("Vfx_Abort_"):
|
|
_aborts.append(_make_abort(n))
|
|
|
|
func _make_abort(socket: Node3D) -> Dictionary:
|
|
# Core is the white-hot throat. Sheath is the orange plume that opens toward the tip.
|
|
var core := Fx.taper_ribbon(socket, 2.1, 0.06, 0.20, Color(1.0, 0.97, 0.92, 1.0), Color(1.0, 0.45, 0.12, 0.72), Color(0.40, 0.08, 0.02, 0.0), 0.95, 3, "AbortCore")
|
|
var sheath := Fx.taper_ribbon(socket, 5.2, 0.10, 0.78, Color(1.0, 0.55, 0.18, 1.0), Color(1.0, 0.28, 0.06, 0.55), Color(0.35, 0.06, 0.01, 0.0), 0.82, 3, "AbortSheath")
|
|
var sparks := Fx.sparks(socket, "AbortCoreSparks", 10, 0.15, 18.0, 28.0, 1.6, 0.05, 6.0)
|
|
var sheath_sparks := Fx.sparks(socket, "AbortSheathSparks", 18, 0.26, 14.0, 22.0, 4.5, 0.09, 8.0)
|
|
for p in [sparks, sheath_sparks]:
|
|
var proc := p.process_material as ParticleProcessMaterial
|
|
if proc:
|
|
proc.color_ramp = _abort_ramp()
|
|
return {
|
|
"core": core,
|
|
"sheath": sheath,
|
|
"sparks": sparks,
|
|
"sheath_sparks": sheath_sparks,
|
|
"flash": Fx.abort_flash(socket),
|
|
"light": Fx.spot(socket, 0.08, Color(1.0, 0.416, 0.157), 0.5, 6.5, 1.8),
|
|
}
|
|
|
|
func _abort_ramp() -> GradientTexture1D:
|
|
var g := Gradient.new()
|
|
g.offsets = PackedFloat32Array([0.0, 0.15, 0.45, 1.0])
|
|
g.colors = PackedColorArray([
|
|
Color(1.0, 0.97, 0.92, 1.0),
|
|
Color(1.0, 0.46, 0.12, 0.9),
|
|
Color(0.55, 0.16, 0.05, 0.45),
|
|
Color(0.25, 0.05, 0.01, 0.0),
|
|
])
|
|
var tex := GradientTexture1D.new()
|
|
tex.gradient = g
|
|
return tex
|