92 lines
7.6 KiB
Plaintext
92 lines
7.6 KiB
Plaintext
#version 450
|
|
#extension GL_GOOGLE_include_directive : require
|
|
layout(local_size_x = 64) in;
|
|
#include "fx_common.glsl"
|
|
|
|
// Consumes the cast requests of this frame (written by the host for a trigger pull, or by proj_step for the sub-shots of triggers):
|
|
// a request names a shot node; every spec of the node becomes a projectile. A negative node is a visual-only explosion (exploding props).
|
|
shared int reqCount;
|
|
|
|
void main() {
|
|
int list = pc.frame & 1;
|
|
if (gl_LocalInvocationID.x == 0u) reqCount = min(list == 0 ? hhead.x : hhead.y, 256);
|
|
barrier();
|
|
for (int r = int(gl_LocalInvocationID.x); r < reqCount; r += 64) {
|
|
vec4 r0 = hcast[(list * 256 + r) * 2], r1 = hcast[(list * 256 + r) * 2 + 1];
|
|
int node = int(r1.x);
|
|
uint seed = uint(r1.z) * 2654435761u + uint(r) * 40503u + uint(pc.frame);
|
|
if (node < 0) { boom_fx(-node - 1, r0.xy, seed, -1.0); continue; }
|
|
vec4 n0 = hnodes[node * 4], n1 = hnodes[node * 4 + 1], n2 = hnodes[node * 4 + 2], n3 = hnodes[node * 4 + 3];
|
|
int first = int(n0.x), count = int(n0.y);
|
|
float base = atan(r0.w, r0.z);
|
|
for (int i = 0; i < count; ++i) {
|
|
ivec4 s0 = hspecs[(first + i) * 4];
|
|
int type = s0.x;
|
|
int tb = type * TYPE_STRIDE;
|
|
vec4 t0 = ftf(tb), t1 = ftf(tb + 1);
|
|
ivec4 t3 = fti(tb + 3);
|
|
uvec4 hs = uvec4(hash3(seed, uint(i), 1u), hash3(seed, uint(i), 2u), hash3(seed, uint(i), 3u), hash3(seed, uint(i), 4u));
|
|
const bool done = ((int(r1.y) >> 30) & 1) != 0; // the host has run the script_shot scripts on this shot: its velocity is final
|
|
const bool exact = (int(r1.y) & 4) != 0 && (fti(tb + 1).w < 0 || done); // a particle entity the host created (its velocity was drawn by SetStartVelocityComponent on the CPU): exactly that velocity
|
|
float ang = base + (rnd01(hs.x) - 0.5) * n1.x + (exact ? 0.0 : (rnd01(hs.y) - 0.5) * 2.0 * t1.z);
|
|
if (count > 1 && n1.y != 0.0) ang += -n1.y + 2.0 * n1.y * float(i) / float(n1.y < 3.1241393 ? count - 1 : count); // pattern_degrees (00c0b440): the aim of the first root projectile is turned by -pattern, then a step of 2 pattern / (N - 1, or N when the pattern is 179 degrees or more) per projectile
|
|
float speed = (t0.x + (t0.y - t0.x) * rnd01(hs.z)) * n1.z;
|
|
if ((int(r1.y) & 4) != 0) speed = length(r0.zw); // a script's shot: its own velocity
|
|
if ((fti(tb + 16).x & 256) != 0) speed = 5400.0 / 60.0; // projectile_type LIGHTNING (00c0d290 case 1): the aim direction x 5400 px/s, whatever the speed range
|
|
const int shooterSlot = int(r1.w) >> 1;
|
|
if (shooterSlot > 0 && !done && count == 1 && s0.y == 0 && fti(tb + 1).w >= 0 && (ents[shooterSlot - 1].ex8.x & 256) != 0 && ents[shooterSlot - 1].st.x != 3) {
|
|
// Message_Shot (00c0d290): the shooter's script_shot scripts see the projectile first; the host makes the runtime copy, lets them change it and shoots it again (event kind -3: two entries)
|
|
uint xi = uint(atomicAdd(xhead.x, 2));
|
|
if (xi + 1u < 512u) { xe[xi] = vec4(r0.xy, -3.0, float(1 + type)); xe[xi + 1u] = vec4(ang, speed, base, float(shooterSlot + ((int(r1.y) & 3) << 20))); }
|
|
continue;
|
|
}
|
|
if ((fti(tb + 16).x & 512) != 0) { // projectile_type MATERIAL_PARTICLE (00c0d290 case 3): on_death_emit_particle_count real particles of on_death_emit_particle_type with the launch
|
|
// velocity and the VelocityComponent gravity, and no projectile (it is removed at once)
|
|
const int dp = fti(tb + 17).w, dmat = (dp & 0xFFFF) - 1, dn = min(dp >> 16, 64);
|
|
for (int k = 0; k < dn && dmat >= 0; ++k) emit_material_particle(r0.xy, vec2(cos(ang), sin(ang)) * speed, max(t0.w, 60.0), t1.x, mats[3u * uint(dmat)].x, dmat);
|
|
continue;
|
|
}
|
|
const uint serial = atomicAdd(fcnt[0], 1u); // the cast serial identifies the projectile beyond its ring slot
|
|
uint slot = serial % uint(FP_MAX);
|
|
for (uint k = 0u; k < 64u && fprojs[slot].d.w != 0; ++k) slot = (serial + k + 1u) % uint(FP_MAX); // (a live record is not overwritten: the next free one of 64, else the ring's)
|
|
if (fprojs[slot].d.w != 0) slot = serial % uint(FP_MAX);
|
|
FProj p;
|
|
p.a = vec4(r0.xy, cos(ang) * speed, sin(ang) * speed);
|
|
{ // lifetime: -1 never runs out (the timer of 00c06fc0 skips it), lifetime_randomness: + random(-r, r) (ProjectileComponent +50)
|
|
const int lr = fti(tb + 17).z;
|
|
const float lrand = lr > 0 ? float(int(hash3(seed, uint(i), 91u) % uint(2 * lr + 1)) - lr) : 0.0;
|
|
p.b = vec4(t0.z + n2.x, t0.w < 0.0 ? 1e9 : max(1.0, t0.w + n1.w + lrand), t1.x + n2.y, t1.y);
|
|
}
|
|
p.c = vec4(ftf(tb + 2).x + n2.w, ftf(tb + 2).y + n3.x, 0.0, n2.z);
|
|
p.e = vec4(rnd01(hs.w) * 6.2832, (fti(tb + 1).w & 32) != 0 ? (rnd01(hs.z) - 0.5) * 0.5 : speed, float(int(r1.w) >> 1), float((int(r1.y) >> 3) & 0xFFFFFF)); // e.y: the spin of a thrown body, else the speed at the start (mInitialSpeed, damage_scaled_by_speed) // e.z: the slot + 1 of the shooter (0 = unknown), e.w: its herd + 1
|
|
const int shotBits = int(n3.w); // bit 0 friendly fire, bits 1-12 trail material + 1, bits 13- its amount
|
|
p.f = vec4(n0.z, n0.w, n3.z, float(shotBits >> 1)); // the wand's electricity / ice adds, knockback add, trail
|
|
p.c.w += float(ft[tb + 7].y); // bounces_left of the projectile file
|
|
{ // the perks scale the shot once, here: the projectile damage and (unless not_scaled_by_gamefx) the explosion damage
|
|
const float dm = dealt_of(int(p.e.z));
|
|
p.b.x *= dm;
|
|
if ((fti(tb + 1).w & 32768) == 0) p.c.y *= dm;
|
|
}
|
|
p.d = ivec4(type, first + i, ((int(r1.y) & 3) << 8) | ((shotBits & 1) << 10) | (min(int(n3.y) & 8191, 4095) << 12) | (((int(n3.y) >> 13) & 15) << 24) | (n2.z > 0.0 ? (1 << 28) : 0), int(serial + 1u)); // bits 24-27: lightning_count, bit 28: the wand added bounces (00c0b440 then sets bounce_at_any_angle) // team in bits 8-9, the critical chance (percent) from bit 12 // team in bits 8-9 (1 the player's wand, 2 enemies), fired triggers in the low bits
|
|
fprojs[slot] = p;
|
|
if ((fti(tb + 11).y & 1) != 0) { // a projectile with components the GPU does not simulate: the host makes its runtime twin (event kind -4, two entries)
|
|
uint xi = uint(atomicAdd(xhead.x, 2));
|
|
if (xi + 1u < 512u) { xe[xi] = vec4(r0.xy, -4.0, float(slot)); xe[xi + 1u] = vec4(float(serial + 1u), float(type), float(shooterSlot), float(int(r1.y) & 3)); }
|
|
}
|
|
if ((int(r1.w) & 1) != 0) { // muzzle flash of the first projectiles of a cast
|
|
ivec4 t6 = fti(tb + 6);
|
|
float fr = uf(ft[tb + 6].w);
|
|
if (fr > 0.0 && i == 0) add_anim(r0.xy, -1, min(fr, 40.0) * 0.5, 0.55, 5, uint(t6.z), 0.0);
|
|
int mb = int(ft[tb + 7].x);
|
|
if (mb >= 0 && i == 0) { // the muzzle flash sprite, along the shot
|
|
ivec4 m0 = fti(BOOM_BASE + mb * BOOM_STRIDE), m1 = fti(BOOM_BASE + mb * BOOM_STRIDE + 1);
|
|
add_anim(r0.xy, mb, 0.0, 0.0, max(m0.w, 1) * max(m1.x, 1) + 2, uint(m1.y), base);
|
|
}
|
|
// (the cast flash is the muzzle_flash_file sprite and the light only: no sparks of a colour of ours - the wall pieces of WALL_SQUARE made gold dust)
|
|
}
|
|
}
|
|
}
|
|
barrier();
|
|
if (gl_LocalInvocationID.x == 0u) { if (list == 0) hhead.x = 0; else hhead.y = 0; }
|
|
}
|