Weapons no longer reload by spinning the gun 360 (that was the upside-down
flip; nothing detached). New ViewmodelAnim module drives per-style manual-of-
arms sequences on the existing model_root + named arm pivots, with a temporary
magazine/shell/rocket/cell prop that visibly leaves and returns to the gun:
- mag (M4/AK/MP7/DMR): roll gun to present the well, hand rips the mag
down and away, fresh mag up, seat, charge. Gun stays UPRIGHT.
- boltmag (AWP): mag swap + a distinct bolt-cycle rock.
- break (double barrel): hinge open, flick both shells out, drop fresh
shells in, snap shut.
- tube (rocket launcher): tip the tube in, shove a rocket home, shoulder.
- cell (plasma/nail gun): glowing energy cell swapped on the side.
Per-shot viewmodel recoil kick (ViewmodelAnim.apply_kick) scaled by each
weapon's recoil_amplitude, so an AWP slams and an MP7 chatters — replaces the
old flat model behaviour. Weapons expose reload_style; base classes call
play_reload / stop (on unequip) / apply_kick.
Knife: the old barely-visible flick is now a real diagonal slash that
alternates backhand/forehand, whips the blade through screen centre, and
fires the third-person melee arm-swing action (synced to other players).
Hair/materials: specular fully to 0 (even a 2% stepped glint swept as shine
across Taila's hair when the camera moved); rim trimmed to a whisper. Fully
matte cloth/hair now.
Verify: debug/fp_weapon_capture.gd screenshots each weapon's reload phases,
the knife slash, and the fire kick.
Co-Authored-By: Claude Fable 5 <[email protected]>
83 lines
3.2 KiB
Plaintext
83 lines
3.2 KiB
Plaintext
shader_type spatial;
|
|
|
|
// Cel/toon surface shader used across the game.
|
|
// - Banded (stepped) diffuse with a tinted shadow color instead of black
|
|
// - Soft rim light for silhouette pop
|
|
// - Optional world-space triplanar albedo (level geometry: no UVs needed,
|
|
// grid stays world-scaled) — otherwise standard UV sampling (characters)
|
|
|
|
uniform vec4 albedo_color : source_color = vec4(1.0);
|
|
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap, repeat_enable;
|
|
uniform bool has_texture = true;
|
|
uniform bool use_triplanar = false;
|
|
uniform float triplanar_tile = 2.0; // world units per texture tile
|
|
|
|
uniform float band_edge : hint_range(-1.0, 1.0) = 0.05; // NdotL where light band starts
|
|
uniform float band_softness : hint_range(0.001, 0.5) = 0.04;
|
|
uniform float mid_band_edge : hint_range(-1.0, 1.0) = 0.55; // second, brighter band
|
|
uniform vec4 shadow_color : source_color = vec4(0.62, 0.65, 0.78, 1.0); // cool shadow tint
|
|
// Matte-anime defaults: zero specular (any stepped glint reads as shine
|
|
// sweeping across hair/cloth when the camera moves), whisper of rim.
|
|
uniform float rim_strength : hint_range(0.0, 2.0) = 0.05;
|
|
uniform float rim_width : hint_range(0.0, 1.0) = 0.28;
|
|
uniform float specular_strength : hint_range(0.0, 1.0) = 0.0;
|
|
uniform float specular_shininess : hint_range(1.0, 128.0) = 64.0;
|
|
|
|
varying vec3 world_pos;
|
|
varying vec3 world_normal;
|
|
|
|
void vertex() {
|
|
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
world_normal = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
|
}
|
|
|
|
vec3 sample_triplanar(vec3 p, vec3 n) {
|
|
vec3 w = abs(n);
|
|
w = pow(w, vec3(4.0));
|
|
w /= (w.x + w.y + w.z);
|
|
vec2 uv_x = p.zy / triplanar_tile;
|
|
vec2 uv_y = p.xz / triplanar_tile;
|
|
vec2 uv_z = p.xy / triplanar_tile;
|
|
vec3 cx = texture(albedo_texture, uv_x).rgb;
|
|
vec3 cy = texture(albedo_texture, uv_y).rgb;
|
|
vec3 cz = texture(albedo_texture, uv_z).rgb;
|
|
return cx * w.x + cy * w.y + cz * w.z;
|
|
}
|
|
|
|
void fragment() {
|
|
vec3 base = albedo_color.rgb;
|
|
if (has_texture) {
|
|
if (use_triplanar) {
|
|
base *= sample_triplanar(world_pos, world_normal);
|
|
} else {
|
|
base *= texture(albedo_texture, UV).rgb;
|
|
}
|
|
}
|
|
ALBEDO = base;
|
|
ROUGHNESS = 1.0;
|
|
SPECULAR = 0.0;
|
|
|
|
// Rim: brighten grazing angles for that inked-silhouette pop.
|
|
float rim = 1.0 - clamp(dot(normalize(VIEW), NORMAL), 0.0, 1.0);
|
|
rim = smoothstep(1.0 - rim_width, 1.0, rim);
|
|
EMISSION = base * rim * rim_strength;
|
|
}
|
|
|
|
void light() {
|
|
float ndotl = dot(NORMAL, LIGHT);
|
|
// Shadowing folds into the band test so shadow edges band too.
|
|
float lit = ndotl * ATTENUATION;
|
|
float band = smoothstep(band_edge - band_softness, band_edge + band_softness, lit);
|
|
float mid = smoothstep(mid_band_edge - band_softness, mid_band_edge + band_softness, lit);
|
|
// 3 tones: shadow tint -> base band (0.82) -> full light.
|
|
float tone = mix(0.82, 1.0, mid);
|
|
vec3 shade = mix(shadow_color.rgb, vec3(tone), band);
|
|
DIFFUSE_LIGHT += ALBEDO * LIGHT_COLOR / PI * shade;
|
|
|
|
// Stepped specular dot for glossy toon highlights.
|
|
vec3 h = normalize(VIEW + LIGHT);
|
|
float spec = pow(clamp(dot(NORMAL, h), 0.0, 1.0), specular_shininess);
|
|
spec = smoothstep(0.5 - band_softness, 0.5 + band_softness, spec);
|
|
SPECULAR_LIGHT += LIGHT_COLOR * spec * specular_strength * band;
|
|
}
|