76 lines
3.0 KiB
GDScript
76 lines
3.0 KiB
GDScript
class_name EnergyShield
|
|||
|
|
extends Node3D
|
||
|
|
|
||
|
|
const DURATION := 45.0
|
||
|
|
var fighter: Fighter
|
||
|
|
var opening := 0.0
|
||
|
|
var surface: MeshInstance3D
|
||
|
|
|
||
|
|
static func activate(p: Fighter) -> void:
|
||
|
|
if not p.game.is_host or not p.game.active or p.game.match_over or p.hp <= 0 or p.shield_charges <= 0 or p.shield_left > 0: return
|
||
|
|
p.shield_charges -= 1
|
||
|
|
p.shield_left = DURATION
|
||
|
|
p.protection = 0
|
||
|
|
|
||
|
|
static func blocks(p: Fighter, origin: Vector3) -> bool:
|
||
|
|
if p.shield_left <= 0 or p.hp <= 0 or not origin.is_finite(): return false
|
||
|
|
var offset := origin - p.eye()
|
||
|
|
return offset.length_squared() > 0.001 and Arsenal.direction(p.yaw, p.pitch).dot(offset.normalized()) > 0.01
|
||
|
|
|
||
|
|
static func pose(p: Fighter) -> Transform3D:
|
||
|
|
var facing := Basis.from_euler(Vector3(p.pitch, p.yaw, 0))
|
||
|
|
return Transform3D(facing, p.eye() + facing * Vector3(0, -0.4, -0.95))
|
||
|
|
|
||
|
|
static func clip_ray(game: Node3D, source: Fighter, start: Vector3, end: Vector3, original: Dictionary) -> Dictionary:
|
||
|
|
var best := start.distance_to(original.position) if not original.is_empty() else start.distance_to(end)
|
||
|
|
var result := original
|
||
|
|
for target: Fighter in game.players.values():
|
||
|
|
if target == source or target.hp <= 0 or target.shield_left <= 0: continue
|
||
|
|
var transform := pose(target)
|
||
|
|
var a := transform.affine_inverse() * start
|
||
|
|
var b := transform.affine_inverse() * end
|
||
|
|
# Outward face is -Z. One-way shield: incoming rays only.
|
||
|
|
if a.z >= 0 or b.z <= a.z: continue
|
||
|
|
var t := -a.z / (b.z - a.z)
|
||
|
|
if t < 0 or t > 1: continue
|
||
|
|
var point := a.lerp(b, t)
|
||
|
|
if absf(point.x) > 1.2 or absf(point.y) > 1.35: continue
|
||
|
|
var world := transform * point
|
||
|
|
if start.distance_to(world) < best:
|
||
|
|
best = start.distance_to(world)
|
||
|
|
result = {"position": world, "collider": null, "shield": target}
|
||
|
|
return result
|
||
|
|
|
||
|
|
func _ready() -> void:
|
||
|
|
surface = MeshInstance3D.new()
|
||
|
|
var panel := QuadMesh.new()
|
||
|
|
panel.size = Vector2(2.4, 2.7)
|
||
|
|
surface.mesh = panel
|
||
|
|
surface.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||
|
|
var shader := Shader.new()
|
||
|
|
shader.code = """shader_type spatial;
|
||
|
|
render_mode unshaded, cull_disabled, depth_draw_never;
|
||
|
|
void fragment() {
|
||
|
|
vec2 edge = abs(UV - vec2(0.5)) * 2.0;
|
||
|
|
float rim = smoothstep(0.94, 0.99, max(edge.x, edge.y));
|
||
|
|
vec2 cell = abs(fract(UV * vec2(18.0, 20.0)) - vec2(0.5));
|
||
|
|
float grid = smoothstep(0.465, 0.495, max(cell.x, cell.y));
|
||
|
|
float sweep = pow(max(0.0, sin(UV.y * 24.0 - TIME * 3.0)), 18.0);
|
||
|
|
ALBEDO = mix(vec3(0.10, 0.65, 1.0), vec3(0.90, 0.98, 1.0), rim);
|
||
|
|
EMISSION = ALBEDO * (0.7 + rim * 2.5);
|
||
|
|
ALPHA = 0.035 + rim * 0.78 + grid * 0.15 + sweep * 0.06;
|
||
|
|
}"""
|
||
|
|
var material := ShaderMaterial.new()
|
||
|
|
material.shader = shader
|
||
|
|
surface.material_override = material
|
||
|
|
add_child(surface)
|
||
|
|
visible = false
|
||
|
|
|
||
|
|
func _process(dt: float) -> void:
|
||
|
|
var enabled: bool = fighter.game.active and fighter.hp > 0 and fighter.shield_left > 0
|
||
|
|
opening = move_toward(opening, 1.0 if enabled else 0.0, dt * 6)
|
||
|
|
visible = opening > 0
|
||
|
|
if not visible: return
|
||
|
|
global_transform = pose(fighter)
|
||
|
|
surface.scale = Vector3(maxf(0.01, WeaponHandling.smooth(opening)), 1, 1)
|