init
This commit is contained in:
commit
87bff164f1
416 files changed
+279512
No files matched your search
@@ -0,0 +1,722 @@
|
||||
#version 450
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
layout(local_size_x = 64) in;
|
||||
#include "fx_common.glsl"
|
||||
layout(std430, set = 0, binding = 8) readonly buffer Tables { uint tbl[]; };
|
||||
#include "biome_mods.glsl"
|
||||
|
||||
// Flight of the wand projectiles, one invocation per projectile: gravity, air friction, collisions with the world (die or bounce)
|
||||
// and with creatures, triggers (timer, hit, death) that queue the next shot of the wand, explosions (damage, holes, material, push of rigid
|
||||
// bodies, visuals), particle emitters, light and the sprite.
|
||||
const int TF_COLLISION_DIE = 1, TF_EXPLODE = 2, TF_HOLE = 4, TF_ROTATE = 8, TF_ADDITIVE = 16, TF_PHYSICS = 32, TF_DIE_IN_LIQUID = 64, TF_FRIENDLY = 128, TF_NO_PLAYER = 256, TF_HIT_EFFECTS = 512;
|
||||
|
||||
// The entity a projectile touches: the player or a creature / prop. The shooter's herd is spared (ex8.y of an entity holds its herd + 1) unless the projectile has friendly fire.
|
||||
// What the shooter deals: the DAMAGE_MULTIPLIER game effect etc. (ex7.y of creatures and the player, set by the host)
|
||||
// The critical hit of a hit: the chance (percent) of the shot, +100 with the CRITICAL_HIT_BOOST of the type while the hit entity has the condition, + what the shooter's game effects add (ex7.z);
|
||||
// the damage is multiplied by 5 (DAMAGE_CRITICAL_HIT_MULTIPLIER); a chance above 100 % multiplies the multiplier by the extra, the victim's critical_damage_resistance scales it
|
||||
float crit_mult(int chance, int flags, int shooter, int victim, uint seed) {
|
||||
float c = float(chance);
|
||||
const int cond = (flags >> 10) & 7;
|
||||
if (cond != 0 && victim >= 0) { // the HitEffectComponent conditions are on the HIT entity
|
||||
const Ent s = ents[victim];
|
||||
const int z = s.ex8.z;
|
||||
const bool fresh = ((z >> 8) & 0xFFFFFF) + 900 > (pc.frame & 0xFFFFFF);
|
||||
bool ok = false;
|
||||
if (cond == 1) ok = s.ex4.w > 0.0;
|
||||
else if (cond == 2) ok = fresh && (z & 4) != 0;
|
||||
else if (cond == 3) ok = fresh && (z & 2) != 0;
|
||||
else if (cond == 4) ok = fresh && (z & 1) != 0;
|
||||
if (ok) c += 100.0;
|
||||
}
|
||||
if (shooter > 0) { const Ent s = ents[shooter - 1]; if (s.st.x == 1 || s.st.x == 2 || s.st.x == 3) c += s.ex7.z; }
|
||||
if (c <= 0.0) return 1.0;
|
||||
// 00ab9a90: rand % 100 + 1 <= chance; m = the packet's multiplier (5), x chance / 100 above 100 %; the victim's critical_damage_resistance r gives (1 - r) (m - 1) + 1, no crit at r >= 1
|
||||
const float r = victim >= 0 ? ents[victim].ex19.x : 0.0;
|
||||
if (r >= 1.0 || float(hash3(seed, 17u, 99u) % 100u) >= c) return 1.0;
|
||||
const float m = c > 100.0 ? 5.0 * c / 100.0 : 5.0;
|
||||
return (1.0 - r) * (m - 1.0) + 1.0;
|
||||
}
|
||||
|
||||
// A critical hit (00ab9a90: the multiplier after the resistance above 1): the host loads data/entities/particles/critical_hit.xml at the point of the message and marks the damage number (event kind -9)
|
||||
void note_crit(vec2 at, int victim) {
|
||||
uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(at, -9.0, float(victim));
|
||||
if (victim >= 0) atomicOr(ents[victim].ex3.z, 0x10000); // a critical hit this tick: the victim's hit flash is red (00ab5b00: shader 6 + crit)
|
||||
}
|
||||
|
||||
// DAMAGE_BOOST of a hit effect: the type flags hold the stain it needs on the victim (bits 24-26, 1 + the stain bit) and the factor (bits 27-30)
|
||||
float boost_of(int flags, int victim) {
|
||||
const int need = (flags >> 24) & 7, mult = (flags >> 27) & 15;
|
||||
if (need == 0 || mult == 0) return 1.0;
|
||||
const int z = ents[victim].ex8.z;
|
||||
return (((z >> (need - 1)) & 1) != 0 && ((z >> 8) & 0xFFFFFF) + 900 > (pc.frame & 0xFFFFFF)) ? float(mult) : 1.0;
|
||||
}
|
||||
|
||||
vec2 gAhead; // the next point of the projectile's path (a body's pixel there is a hit on its entity)
|
||||
// 00c08d10: the shooter (mWhoShot) is hit only when collide_with_shooter_frames is not -1 and has run out (bits 16-23 of the flags: 255 = -1, never); friendly_fire
|
||||
// (00c091f0) only lets the shooter's herd be hit. A shot with the friendly_fire modifier (PIERCING_SHOT...) gets friendly_fire 1 and 6 frames (00c0b440): shot_flags.
|
||||
bool shooter_safe(int flags, int age) { const int f = (flags >> 16) & 255; return f == 255 || age < f; }
|
||||
int shot_flags(int typeFlags, int dz) { return ((dz >> 10) & 1) != 0 ? (typeFlags & ~(255 << 16)) | TF_FRIENDLY | (6 << 16) : typeFlags; }
|
||||
bool victim_at(vec2 pos, int shooter, int herd, int flags, int age, out int hit) {
|
||||
hit = -1;
|
||||
for (int k = 0; k < ctrl.inp.w; ++k) {
|
||||
const int kind = ents[k].st.x;
|
||||
if (kind == 0 && (ents[k].st.w & 64) != 0 && ents[k].ex19.w > 0.5) { // a worm's segment (WormComponent hitbox_radius around every part): the hit goes to the worm
|
||||
const int root = int(ents[k].ex19.w) - 1;
|
||||
if (root <= 0 || root >= ctrl.inp.w || !(ents[root].st.x == 1 || ents[root].st.x == 2) || (ents[root].st.w & 8) != 0) continue;
|
||||
if (root == shooter - 1 && shooter_safe(flags, age)) continue;
|
||||
if ((flags & TF_FRIENDLY) == 0 && !(shooter > 0 && (ents[shooter - 1].ex8.x & 4) != 0) && herd != 0 && ents[root].ex8.y == herd) continue;
|
||||
if (distance(pos, ents[k].a.xy) <= ents[k].ex19.z + 1.0) { hit = root; return true; }
|
||||
continue;
|
||||
}
|
||||
if (k == 0 ? (kind != 3 || (flags & TF_NO_PLAYER) != 0) : !(kind == 1 || kind == 2 || (kind == 4 && ents[k].hp.x < 1.0e8))) continue;
|
||||
if ((ents[k].st.w & (8 | 4096)) != 0) continue; // (4096: its sprites, damage model and hitbox are switched off - Olematon away from an Evil Eye)
|
||||
if (k == shooter - 1 && shooter_safe(flags, age)) continue;
|
||||
if ((flags & TF_FRIENDLY) == 0 && !(shooter > 0 && (ents[shooter - 1].ex8.x & 4) != 0) && herd != 0 && ents[k].ex8.y == herd && kind != 4) continue; // (a berserk shooter has no herd: its projectiles hurt everybody)
|
||||
if (kind == 4 && ents[k].ex7.w > 0.5) { if (body_slot(gAhead) == k || body_slot(pos) == k) { hit = k; return true; } continue; } // no HitboxComponent: its pixels (00c073b0)
|
||||
const float r = k == 0 ? 1.0 : 2.0;
|
||||
const vec2 d = pos - ents[k].a.xy;
|
||||
if (d.x >= ents[k].box.x - r && d.x <= ents[k].box.y + r && d.y >= ents[k].box.z - r && d.y <= ents[k].box.w + r) { hit = k; return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool creature_at(vec2 pos, float r, out int hit) {
|
||||
hit = -1;
|
||||
for (int k = 1; k < ctrl.inp.w; ++k) {
|
||||
int kind = ents[k].st.x;
|
||||
if (!(kind == 1 || kind == 2 || (kind == 4 && ents[k].hp.x < 1.0e8)) || (ents[k].st.w & 8) != 0) continue;
|
||||
vec2 d = pos - ents[k].a.xy;
|
||||
if (d.x >= ents[k].box.x - r && d.x <= ents[k].box.y + r && d.y >= ents[k].box.z - r && d.y <= ents[k].box.w + r) { hit = k; return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void queue_shot(int node, vec2 pos, vec2 vel, uint seed, int team, int shooter, int herd) {
|
||||
float len = length(vel);
|
||||
vec2 dir = len < 1e-6 ? vec2(0.0, 1.0) : vel / len;
|
||||
int next = (pc.frame + 1) & 1;
|
||||
int slot = next == 0 ? atomicAdd(hhead.x, 1) : atomicAdd(hhead.y, 1);
|
||||
if (slot >= 256) return;
|
||||
hcast[(next * 256 + slot) * 2] = vec4(pos, dir); // at the trigger's position itself (00c0b440 puts the new projectile there)
|
||||
hcast[(next * 256 + slot) * 2 + 1] = vec4(float(node), float(team + (herd << 3)), float(seed & 0xFFFFFFu), float(shooter * 2));
|
||||
}
|
||||
|
||||
// The triggers of a projectile (recorded by the natives 00b283e0 / 00b28510 / 00b28600 into three lists of the ProjectileComponent: timers +0x610, hit-world +0x61c, death +0x628;
|
||||
// kinds here 1 timer, 2 hit world, 3 death). mode 1: the update's countdown (00c06c60): a timer whose delay has run out fires and goes; 2: a hit on the world (00c073b0 -> 00c06250):
|
||||
// the hit-world and the waiting timers fire and go; 3: an impact on a creature (00c091f0): the hit-world and the waiting timers fire and stay (a projectile that pierces casts
|
||||
// them again at its next impact); 4: the lifetime runs out (00c06fc0): the waiting timers fire; 5: the projectile is removed (00c0e3e0): the death triggers fire
|
||||
void fire_triggers(int spec, inout int fired, int mode, int age, vec2 pos, vec2 vel, uint seed, int team, int shooter, int herd) {
|
||||
ivec4 s0 = hspecs[spec * 4];
|
||||
for (int t = 0; t < min(s0.y, 3); ++t) {
|
||||
ivec4 tr = hspecs[spec * 4 + 1 + t]; // kind, delay, node
|
||||
if ((fired & (1 << t)) != 0 || tr.z < 0) continue;
|
||||
// (mode 1: the counter starts at the delay and fires when the decrement reaches 0: a delay of 0 never does)
|
||||
bool go = mode == 1 ? (tr.x == 1 && tr.y > 0 && age == tr.y) : (mode == 2 || mode == 3) ? (tr.x == 1 || tr.x == 2) : mode == 4 ? tr.x == 1 : tr.x == 3;
|
||||
if (!go) continue;
|
||||
vec2 v = vel;
|
||||
if (mode == 5 && dot(vel, vel) * 3600.0 < 100.0) { // 00c0e3e0: nearly at rest (|v| < 10 px/s): a random direction (and length 20..80 px/s) for each death trigger
|
||||
const float ang = rnd01(hash3(seed, uint(t), 131u)) * 6.2831855;
|
||||
v = vec2(cos(ang), sin(ang)) * (rnd01(hash3(seed, uint(t), 132u)) * 60.0 + 20.0) / 60.0;
|
||||
}
|
||||
if (mode != 3) fired |= 1 << t;
|
||||
queue_shot(mode == 2 ? tr.w : tr.z, pos, v, seed + uint(t), team, shooter, herd); // (.w: the payload with its child_speed_multiplier, 00c06250)
|
||||
}
|
||||
}
|
||||
|
||||
void explode(vec2 pos, float radius, float damage, int type, int team, int shooter, int herd, int critChance, uint critSeed) {
|
||||
int tb = type * TYPE_STRIDE;
|
||||
vec4 t2 = ftf(tb + 2);
|
||||
ivec4 t1 = fti(tb + 1);
|
||||
int cellMat = int(ft[tb + 2].w);
|
||||
float cellProb = uf(ft[tb + 3].x);
|
||||
if (radius <= 0.0) return;
|
||||
for (int k = 1; k < ctrl.inp.w; ++k) // corpse parts in range bleed
|
||||
if (ents[k].st.x == 4 && ents[k].ex7.y > 0.0 && distance(ents[k].a.xy, pos) <= radius) body_damage(k, pos, 1.0);
|
||||
// 00657960 (docs/EXPLOSION_KNOCKBACK_AND_VINES.md): every entity whose position is within explosion_radius, except the config's dont_damage_this (the shooter only when the projectile has
|
||||
// explosion_dont_damage_shooter), takes the config's damage in full (only with damage_mortals; the perks scaled it at the Shot) and gets a knockback: scalar = radius * knockback_force *
|
||||
// 10 * (0.5 t + 0.5), t = 1 - distance / radius clamped; its direction is the radial physics force (lerp of physics_explosion_power.min / .max by t: zero = no kick); it is compared with
|
||||
// minimum_knockback_force before the division by the CharacterDataComponent mass, the vertical part is always upward, and a killing hit gives none
|
||||
const vec4 t8 = ftf(tb + 8);
|
||||
const bool mortals = t8.w > 0.5;
|
||||
const vec2 pw = unpackHalf2x16(ft[tb + 7].w); // physics_explosion_power.min / .max
|
||||
if (mortals && damage > 0.0) {
|
||||
for (int k = 0; k < ctrl.inp.w; ++k) {
|
||||
int kind = ents[k].st.x;
|
||||
if (k == 0 ? (ents[0].st.w & 8) != 0 : (!(kind == 1 || kind == 2 || (kind == 4 && ents[k].hp.x < 1.0e8)) || (ents[k].st.w & (8 | 4096)) != 0)) continue;
|
||||
if ((t1.w & 16384) != 0 && k == shooter - 1) continue;
|
||||
const vec2 c = ents[k].a.xy;
|
||||
const float dist = distance(c, pos);
|
||||
if (dist > radius) continue;
|
||||
const float ck = crit_mult(critChance, t1.w, shooter, k, critSeed + uint(k) * 7919u);
|
||||
if (ck > 1.0) note_crit(c, k);
|
||||
const float dk = damage * ents[k].ex9.y * ck; // the victim's explosion multiplier (and protections); the damage message carries the projectile's ConfigDamageCritical (00657960 copies chance / multiplier), TakeDamage rolls it per victim
|
||||
note_block(k, damage, dk);
|
||||
atomicAdd(ents[k].x.x, int(dk * 1000.0));
|
||||
note_attacker(k, shooter);
|
||||
if (type >= 0) ents[k].ex19.yz = vec2(float(type + 1), float(pc.frame & 0xFFFFF));
|
||||
atomicOr(ents[k].ex3.z, 2);
|
||||
const vec2 away = normalize(c - pos + vec2(0.0, -0.01));
|
||||
{ // 00657960: the packet's impulse = the direction x ((power.max - power.min) x (1 - d / R) + power.min) x physics_multiplier_ragdoll_force x 3600 (the blood of the hit
|
||||
// and the ragdoll take it); RAGDOLL_FX of the explosion packet: the constructor's NORMAL
|
||||
const float a = ((pw.y - pw.x) * clamp(1.0 - dist / radius, 0.0, 1.0) + pw.x) * (float((fti(tb + 11).y >> 8) & 4095) / 256.0) * 3600.0;
|
||||
if (k != 0) { // (not the player: its aux.x is the maximum hp - a blast there made the respawn hp 765)
|
||||
atomicExchange(ents[k].aux.x, int(away.x * a * 1000.0)); atomicExchange(ents[k].aux.y, int(away.y * a * 1000.0));
|
||||
atomicAnd(ents[k].ex3.z, ~0xF000); atomicOr(ents[k].ex3.z, 1 << 12);
|
||||
}
|
||||
}
|
||||
const float tt = clamp(1.0 - dist / radius, 0.0, 1.0);
|
||||
const float physF = mix(pw.x, pw.y, tt); // (the multiplier +0x108 defaults to 1)
|
||||
const float sc = radius * t8.z * 10.0 * (0.5 * tt + 0.5);
|
||||
if ((kind == 1 || kind == 3) && t8.z != 0.0 && physF != 0.0 && dist > 0.0 && ents[k].hp.x - dk > 0.0 && abs(sc) >= ents[k].ex11.x && (ents[k].ex8.x & 96) == 0 && ents[k].ex8.y > 0) { // (96: KNOCKBACK or KNOCKBACK_IMMUNITY on it; ex8.y > 0: it has a GenomeDataComponent)
|
||||
const float mass = intBitsToFloat(ents[k].rg2.w);
|
||||
vec2 imp = (c - pos) / dist * sc;
|
||||
if (mass != 0.0) imp /= mass;
|
||||
imp.y = -abs(imp.y);
|
||||
ents[k].ex11.yz = imp / 60.0; // the pending kick (pixels per second -> cells per frame); the entity pass adds it, inverts it when confused and starts the knockback state
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((t1.w & TF_HOLE) != 0) {
|
||||
if (radius <= 64.0) add_edit(vec4(pos, radius, t2.z), vec4(2.0, ftf(tb + 7).z, cellMat >= 0 ? float(cellMat) : 0.0, cellMat >= 0 ? cellProb * 100.0 : 0.0)); // angular rays (explode_rays.comp), which also create the cells of create_cell_material
|
||||
else add_edit(vec4(pos, radius * 0.5, t2.z), vec4(0.0));
|
||||
}
|
||||
if (((fti(tb + 9).w >> 12) & 255) != 0) { uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(pos, -2.0, float(1 + type)); } // config_explosion.load_this_entity: the host spawns the entities (event kind -2)
|
||||
if (cellMat >= 0 && cellProb > 0.0 && ((t1.w & TF_HOLE) == 0 || radius > 64.0)) add_edit(vec4(pos, radius * 0.6, 0.0), vec4(1.0, float(cellMat), cellProb, 0.0));
|
||||
{ // the force on the rigid bodies (00657960): the event is a pair, (x, y, radius in pixels, -1) then (force min, force max)
|
||||
const uint ph = ft[tb + 7].w;
|
||||
const vec2 fr = unpackHalf2x16(ph);
|
||||
uint xp = uint(atomicAdd(xhead.x, 2));
|
||||
if (xp + 1u < 512u) { xe[xp] = vec4(pos, radius, -1.0); xe[xp + 1u] = vec4(fr, float(1 + type), 0.0); } // (z: the type, for the explosion's sound)
|
||||
}
|
||||
}
|
||||
|
||||
// The sprite of a projectile, rotated along the flight when it is not round.
|
||||
// scale: the entity's transform scale (a script's EntitySetTransform on the twin: summon_portal_vibrate.lua pulses the portal 0.1 .. 0.42); the host keeps it as -scale in e.x of
|
||||
// a projectile that is no thrown body (e.x is the tumble angle of those only)
|
||||
void draw_sprite(vec2 pos, vec2 vel, int age, ivec4 t1, ivec4 t5, ivec4 t6, float spin, float scale) {
|
||||
ivec2 sz = ivec2(t5.y, t5.z);
|
||||
scale = max(scale, 0.01);
|
||||
if (sz.x > 0 && sz.y > 0) {
|
||||
int frame = (age / max(t6.x, 1)) % max(t5.w, 1);
|
||||
float ang = (t1.w & TF_PHYSICS) != 0 ? spin : ((t1.w & TF_ROTATE) != 0 ? atan(vel.y, vel.x) : 0.0); // thrown bodies tumble, long sprites follow the flight
|
||||
float c = cos(ang), s = sin(ang);
|
||||
float r = 0.5 * length(vec2(sz)) * scale + 1.0;
|
||||
ivec2 lo = ivec2(floor(pos - vec2(r)));
|
||||
int n = int(2.0 * r) + 2;
|
||||
for (int j = 0; j < n * n; ++j) {
|
||||
ivec2 w = lo + ivec2(j % n, j / n);
|
||||
vec2 d = vec2(w) + 0.5 - pos;
|
||||
ivec2 sp = ivec2(floor(vec2(sz) * 0.5 + vec2(c * d.x + s * d.y, -s * d.x + c * d.y) / scale));
|
||||
if (sp.x < 0 || sp.y < 0 || sp.x >= sz.x || sp.y >= sz.y) continue;
|
||||
ivec2 q = w - ivec2(pc.view_x, pc.view_y);
|
||||
if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) continue;
|
||||
uint t = fxtex[t5.x + frame * sz.x * sz.y + sp.y * sz.x + sp.x];
|
||||
if ((t >> 24) != 0u) entlayer[q.y * pc.gw + q.x] = (t1.w & TF_ADDITIVE) != 0 ? (0x01000000u | (t & 0xFFFFFFu)) : t; // alpha 1 marks additive light
|
||||
}
|
||||
} else if ((t1.w & 8192) == 0) { // (an entity with only emitters has no body to draw)
|
||||
ivec2 w = ivec2(floor(pos)) - ivec2(pc.view_x, pc.view_y);
|
||||
for (int y = -1; y <= 1; ++y)
|
||||
for (int x = -1; x <= 1; ++x) {
|
||||
ivec2 q = w + ivec2(x, y);
|
||||
if (q.x >= 0 && q.y >= 0 && q.x < pc.gw && q.y < pc.gh) entlayer[q.y * pc.gw + q.x] = 0xFF000000u | uint(t6.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- VERLET projectiles (the tentacles: projectile_type VERLET, a VerletPhysicsComponent chain in a child entity, a VerletWeaponComponent on the root) ----
|
||||
// 00c0d290 (the shot, case 2): every point of the chain starts at the origin with prev = origin - aim x speed (so it moves speed px a frame), the projectile
|
||||
// itself does not move; each frame the chain integrates (00c37d30: cur + (cur - prev) x velocity_dampening, liquid_damping in a liquid; a solid cell
|
||||
// stops the point halfway or lifts it one cell) and relaxes twice (00c38310, stiffness x 0.5), its first point held at the origin (follow_entity_transform;
|
||||
// the weapon system 00c3fdc0 puts the chain on the shooter's hand every frame); the projectile's position is the last point. 00c3fdc0 hurts with every point:
|
||||
// min(damage_coeff x |v|^2, damage_max), nothing under damage_min_step, the boxes of the creatures within damage_radius (not the shooter; the player at most once in
|
||||
// 20 frames, mPlayerCooldownEnd), times (lifetime left) / fade_duration_frames in the last frames. 17 records a slot: the 16 points (position, previous), then
|
||||
// (the anchor: its offset from the shooter or its position, 1 when it follows the shooter, the player's cooldown end).
|
||||
// TODO(to-do #38): the stains of the cells (stain_cells_probability).
|
||||
layout(std430, set = 0, binding = 63) buffer VerletPts { vec4 vpts[]; };
|
||||
const int VERLET_BASE = 13696, VERLET_STRIDE = 20;
|
||||
|
||||
void verlet_draw(uint id, int vi, int n, float fadeK, uint seed) {
|
||||
const int vb = VERLET_BASE + vi * VERLET_STRIDE;
|
||||
const int nparts = fti(vb + 1).w;
|
||||
if (nparts <= 0) return;
|
||||
const uint base = id * 17u;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const ivec4 s = fti(vb + 4 + min(i, nparts - 1));
|
||||
const vec4 sf = ftf(vb + 4 + min(i, nparts - 1));
|
||||
if (s.x < 0) continue;
|
||||
const int w = s.y & 0xFFFF, h = s.y >> 16;
|
||||
if (w <= 0 || h <= 0) continue;
|
||||
const vec2 a = vpts[base + uint(i)].xy;
|
||||
vec2 d = i + 1 < n ? vpts[base + uint(i) + 1u].xy - a : (i > 0 ? a - vpts[base + uint(i) - 1u].xy : vec2(1.0, 0.0));
|
||||
float len = length(d);
|
||||
const vec2 dir = len > 0.01 ? d / len : vec2(1.0, 0.0), nrm = vec2(-dir.y, dir.x);
|
||||
if (i + 1 >= n || len < float(w)) len = max(len, float(w)); // (the last point's sprite at its own length; scale_sprite_x stretches the others along their link)
|
||||
const vec2 lo = min(min(a + nrm * (-sf.w), a + nrm * (float(h) - sf.w)), min(a + dir * len + nrm * (-sf.w), a + dir * len + nrm * (float(h) - sf.w)));
|
||||
const vec2 hi = max(max(a + nrm * (-sf.w), a + nrm * (float(h) - sf.w)), max(a + dir * len + nrm * (-sf.w), a + dir * len + nrm * (float(h) - sf.w)));
|
||||
const ivec2 c0 = ivec2(floor(lo)) - 1, c1 = ivec2(ceil(hi)) + 1;
|
||||
if (c1.x - c0.x > 64 || c1.y - c0.y > 64) continue;
|
||||
for (int y = c0.y; y <= c1.y; ++y)
|
||||
for (int x = c0.x; x <= c1.x; ++x) {
|
||||
const vec2 rel = vec2(x, y) + 0.5 - a;
|
||||
const float u = dot(rel, dir), v = dot(rel, nrm) + sf.w;
|
||||
if (u < 0.0 || u >= len || v < 0.0 || v >= float(h)) continue;
|
||||
const int sx = min(int(u / len * float(w)), w - 1), sy = int(v);
|
||||
const uint t = fxtex[s.x + sy * w + sx];
|
||||
if ((t >> 24) == 0u) continue;
|
||||
if (fadeK < 1.0 && rnd01(hash3(uint(x * 73 + y * 151), seed, 11u)) > fadeK) continue; // (the fade: pixels drop out)
|
||||
const ivec2 q = ivec2(x, y) - ivec2(pc.view_x, pc.view_y);
|
||||
if (q.x >= 0 && q.y >= 0 && q.x < pc.gw && q.y < pc.gh) entlayer[q.y * pc.gw + q.x] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool verlet_blocking(vec2 p) { return solid_at(ivec2(int(floor(p.x)), int(ceil(p.y)))); }
|
||||
|
||||
void verlet_step(uint id, FProj p, int tb, int vi, int age, int fired, int team, int spec, int shooter, int herd, uint seed) {
|
||||
const int vb = VERLET_BASE + vi * VERLET_STRIDE;
|
||||
const ivec4 v0i = fti(vb), v1i = fti(vb + 1);
|
||||
const vec4 v0 = ftf(vb), v1 = ftf(vb + 1), v2 = ftf(vb + 2);
|
||||
const int n = clamp(v0i.x, 2, 16);
|
||||
const float rest = v0.y, stiff = v0.z, damp = v0.w;
|
||||
const uint base = id * 17u;
|
||||
vec2 pts[16], prv[16];
|
||||
const bool shooterOk = shooter > 0 && shooter - 1 < ctrl.inp.w && (ents[shooter - 1].st.w & 8) == 0;
|
||||
if (age <= 1) { // the shot (00c0d290 case 2)
|
||||
const vec2 origin = p.a.xy, v = p.a.zw * 60.0; // (the aim x the speed: speed px a frame)
|
||||
for (int k = 0; k < n; ++k) { pts[k] = origin; prv[k] = origin - v; }
|
||||
const bool follow = shooterOk && distance(origin, ents[shooter - 1].a.xy) < 24.0; // (cast from the hand: it follows the shooter; one a trigger made somewhere stays there)
|
||||
vpts[base + 16u] = vec4(follow ? origin - ents[shooter - 1].a.xy : origin, follow ? 1.0 : 0.0, -1.0);
|
||||
} else for (int k = 0; k < n; ++k) { const vec4 q = vpts[base + uint(k)]; pts[k] = q.xy; prv[k] = q.zw; }
|
||||
vec4 aux = vpts[base + 16u];
|
||||
const vec2 anchor = aux.z > 0.5 ? (shooterOk ? ents[shooter - 1].a.xy + aux.xy : pts[0]) : aux.xy;
|
||||
if (age > 1) {
|
||||
for (int k = 1; k < n; ++k) { // 00c37d30
|
||||
vec2 delta = (pts[k] - prv[k]) * damp, np = pts[k] + delta;
|
||||
if (v1i.x != 0 && verlet_blocking(np)) {
|
||||
const vec2 mid = (pts[k] + np) * 0.5;
|
||||
if (!verlet_blocking(mid)) np = mid;
|
||||
else if (!verlet_blocking(mid - vec2(0.0, 1.0))) np = pts[k] - vec2(0.0, 1.0);
|
||||
else np = pts[k];
|
||||
} else if (liquid_nonsand(ivec2(floor(np)))) np = pts[k] + (pts[k] - prv[k]) * v1.y;
|
||||
prv[k] = pts[k]; pts[k] = np;
|
||||
}
|
||||
for (int it = 0; it < 2; ++it) // 00c38310
|
||||
for (int k = 0; k + 1 < n; ++k) {
|
||||
const vec2 d = pts[k + 1] - pts[k];
|
||||
const float l = length(d);
|
||||
if (l < 1e-5) continue;
|
||||
const float diff = (l - rest) / max(l, 0.01) * stiff * 0.5;
|
||||
pts[k] += d * diff * 0.5; pts[k + 1] -= d * diff * 0.5;
|
||||
}
|
||||
}
|
||||
pts[0] = anchor; prv[0] = anchor;
|
||||
const int lifeLeft = int(p.b.y) - age, fade = max(v1i.z, 1);
|
||||
const float fadeK = lifeLeft < fade ? max(float(lifeLeft), 0.0) / float(fade) : 1.0;
|
||||
// 00c3fdc0: the damage of every point
|
||||
for (int k = 0; k < n; ++k) {
|
||||
const vec2 v = pts[k] - prv[k];
|
||||
float dmg = min(v2.w * dot(v, v), v2.z);
|
||||
if (dmg < v2.y) continue;
|
||||
dmg *= fadeK;
|
||||
if (dmg <= 0.0) continue;
|
||||
const float r = v2.x;
|
||||
for (int e = 0; e < ctrl.inp.w; ++e) {
|
||||
const int kind = ents[e].st.x;
|
||||
if (!(kind == 1 || kind == 2 || kind == 3) || (ents[e].st.w & (8 | 4096)) != 0 || e == shooter - 1) continue;
|
||||
const vec2 rel = pts[k] - ents[e].a.xy;
|
||||
if (rel.x < ents[e].box.x - r || rel.x > ents[e].box.y + r || rel.y < ents[e].box.z - r || rel.y > ents[e].box.w + r) continue;
|
||||
if (kind == 3) { if (float(pc.frame) < aux.w) continue; aux.w = float(pc.frame + 20); } // mPlayerCooldownEnd
|
||||
if (kind != 3 && herd != 0 && ents[e].ex8.y == herd) continue; // (the shooter's herd is spared)
|
||||
atomicAdd(ents[e].x.x, int(dmg * 1000.0));
|
||||
note_attacker(e, shooter);
|
||||
ents[e].ex19.yz = vec2(float(p.d.x + 1), float(pc.frame & 0xFFFFF));
|
||||
atomicOr(ents[e].ex3.z, 1);
|
||||
if (e != 0) { const vec2 imp = v * 60.0 * ftf(vb + 3).x; atomicExchange(ents[e].aux.x, int(imp.x * 1000.0)); atomicExchange(ents[e].aux.y, int(imp.y * 1000.0)); } // the damage packet's impulse: impulse_coeff x the point's velocity (the blood and the ragdoll of the hit)
|
||||
}
|
||||
}
|
||||
// 00c3fdc0: a point going back toward the shooter (its velocity toward the anchor) faster than 10 px a frame takes what is at it along: every entity whose position is within
|
||||
// damage_radius of the point (not the shooter; creatures, the player, items) gets the point's velocity (VelocityComponent mVelocity, a knockback for a character)
|
||||
for (int k = 1; k < n; ++k) {
|
||||
const vec2 v = pts[k] - prv[k];
|
||||
if (dot(anchor - pts[k], v) <= 0.0 || dot(v, v) <= 100.0) continue; // (|v|^2 > 100: faster than 10 px a frame)
|
||||
const float r = v2.x;
|
||||
for (int e = 0; e < ctrl.inp.w; ++e) {
|
||||
const int kind = ents[e].st.x;
|
||||
if (!(kind == 1 || kind == 2 || kind == 3 || kind == 4) || (ents[e].st.w & (8 | 32 | 128 | 4096)) != 0 || e == shooter - 1) continue; // (not dead, not a rigid body, not a hovering wand, not hidden)
|
||||
const vec2 d = ents[e].a.xy - pts[k];
|
||||
if (abs(d.x) > r || abs(d.y) > r) continue;
|
||||
if (kind == 1 || kind == 3) ents[e].ex11.yz = v - ents[e].a.zw; // (00ab9910: a character's knockback; the entity pass adds the kick and holds it in the knockback state)
|
||||
else ents[e].a.zw = v;
|
||||
}
|
||||
}
|
||||
// 00c3fdc0: the physics bodies within physics_force_radius of a point (a Box2D query of the box) get their velocity moved toward the point's: the host applies
|
||||
// v += (the point's displacement - v) x physics_impulse_coeff / 6 (event -30000 - the body's entity slot, the displacement as two halves)
|
||||
if (ftf(vb + 3).z != 0.0) for (int k = 1; k < n; ++k) {
|
||||
const float fr = ftf(vb + 3).y;
|
||||
int bs = -1;
|
||||
for (int q = 0; q < 5 && bs < 0; ++q) {
|
||||
const vec2 o = q == 0 ? vec2(0.0) : (q == 1 ? vec2(fr, 0.0) : (q == 2 ? vec2(-fr, 0.0) : (q == 3 ? vec2(0.0, fr) : vec2(0.0, -fr))));
|
||||
if (body_hit(pts[k] + o)) bs = body_slot(pts[k] + o);
|
||||
}
|
||||
if (bs < 0) continue;
|
||||
uint xi = uint(atomicAdd(xhead.x, 1));
|
||||
if (xi < 512u) xe[xi] = vec4(ftf(vb + 3).z, 0.0, -30000.0 - float(bs), uintBitsToFloat(packHalf2x16(pts[k] - prv[k]))); // (x: physics_impulse_coeff)
|
||||
}
|
||||
vpts[base + 16u] = aux;
|
||||
const vec2 tip = pts[n - 1], tipV = pts[n - 1] - prv[n - 1];
|
||||
bool alive = true, expired = false;
|
||||
if (spec >= 0) fire_triggers(spec, fired, 1, age, tip, tipV, seed, team, shooter, herd);
|
||||
{ // the ProjectileSystem's own hit (00c073b0 / 00c091f0) at the root, which 00c3fdc0 puts on the last point: the projectile's damage, and its hit triggers on every hit
|
||||
// (00c091f0 casts the +0x61c and +0x610 lists and removes nothing: a piercing tentacle with a trigger casts its payload at each collision)
|
||||
const int tflags = fti(tb + 11).y, tw = fti(tb + 1).w;
|
||||
int hk;
|
||||
gAhead = tip;
|
||||
if (age > 1 && (tflags & 8) == 0 && ((tflags & 4) == 0 || age % max(fti(tb + 11).z & 0xFFFF, 1) == 0)
|
||||
&& victim_at(tip, shooter, herd, shot_flags(tw, p.d.z), age, hk)) {
|
||||
const float hitDmg = hit_damage(hk, p.b.x, tb, p.f) * boost_of(tw, hk) * fadeK;
|
||||
if (hitDmg > 0.0) atomicAdd(ents[hk].x.x, int(hitDmg * 1000.0));
|
||||
note_attacker(hk, shooter);
|
||||
ents[hk].ex19.yz = vec2(float(p.d.x + 1), float(pc.frame & 0xFFFFF));
|
||||
atomicOr(ents[hk].ex3.z, 1);
|
||||
if (spec >= 0) fire_triggers(spec, fired, 3, age, tip, tipV, seed, team, shooter, herd);
|
||||
if ((tflags & 4) == 0) alive = false;
|
||||
}
|
||||
}
|
||||
if (age >= int(p.b.y)) { alive = false; expired = true; }
|
||||
if (!alive && spec >= 0) { if (expired) fire_triggers(spec, fired, 4, age, tip, tipV, seed, team, shooter, herd); fire_triggers(spec, fired, 5, age, tip, tipV, seed, team, shooter, herd); }
|
||||
for (int k = 0; k < n; ++k) vpts[base + uint(k)] = vec4(pts[k], prv[k]);
|
||||
if (alive) verlet_draw(id, vi, n, fadeK, seed);
|
||||
p.a = vec4(tip, 0.0, 0.0);
|
||||
p.c.z = float(age);
|
||||
p.d.z = fired | (team << 8) | (p.d.z & ~0x3FF);
|
||||
p.d.w = alive ? p.d.w : 0;
|
||||
fprojs[id] = p;
|
||||
}
|
||||
|
||||
void main() {
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
if (id >= uint(FP_MAX)) return;
|
||||
FProj p = fprojs[id];
|
||||
if (p.d.w == 0) return;
|
||||
int type = p.d.x, tb = type * TYPE_STRIDE;
|
||||
ivec4 t1 = fti(tb + 1), t3 = fti(tb + 3), t5 = fti(tb + 5), t6 = fti(tb + 6);
|
||||
vec4 t4 = ftf(tb + 4);
|
||||
const int vflag = fti(tb + 16).x;
|
||||
if ((vflag & 4096) != 0 && pc.pass_ == 1) { // a VERLET chain between ticks: drawn as it is
|
||||
const int vi = ((vflag >> 13) & 31) - 1;
|
||||
const int fade = max(fti(VERLET_BASE + vi * VERLET_STRIDE + 1).z, 1), lifeLeft = int(p.b.y) - int(p.c.z);
|
||||
verlet_draw(id, vi, clamp(fti(VERLET_BASE + vi * VERLET_STRIDE).x, 2, 16), lifeLeft < fade ? max(float(lifeLeft), 0.0) / float(fade) : 1.0, hash3(id, uint(pc.frame), 5u));
|
||||
return;
|
||||
}
|
||||
if (pc.pass_ == 1) { // between ticks: only draw, between the last two positions
|
||||
draw_sprite(p.a.xy - p.a.zw * (1.0 - ctrl.view.z), p.a.zw, int(p.c.z), t1, t5, t6, p.e.x + p.e.y * ctrl.view.z, (t1.w & TF_PHYSICS) == 0 && p.e.x < 0.0 ? -p.e.x : 1.0);
|
||||
return;
|
||||
}
|
||||
uint seed = hash3(id, uint(pc.frame), 5u);
|
||||
vec2 start = p.a.xy, pos = p.a.xy, vel = p.a.zw;
|
||||
{ // waiting at the edge of what is loaded (far.glsl): its next position outside the window, inside the far map, in a block not resident (a biome-map tile
|
||||
// without its area yet, or a block not generated yet): it stays where it is, its age frozen, and goes on from there once the block is resident
|
||||
const ivec2 nw = ivec2(floor(pos + vel));
|
||||
const ivec2 lq = nw - ivec2(pc.view_x, pc.view_y);
|
||||
if ((lq.x < 0 || lq.y < 0 || lq.x >= pc.gw || lq.y >= pc.gh) && far_in_map(nw) && far_slot(nw) == 0) return;
|
||||
}
|
||||
int age = int(p.c.z) + 1, fired = p.d.z & 255, team = (p.d.z >> 8) & 3, bounces = int(p.c.w), spec = p.d.y, shooter = int(p.e.z), herd = int(p.e.w);
|
||||
if ((vflag & 4096) != 0) { verlet_step(id, p, tb, ((vflag >> 13) & 31) - 1, age, fired, team, spec, shooter, herd, seed); return; } // VERLET: the chain (verlet_step)
|
||||
if ((fti(tb + 11).x & 1) != 0) { // HomingComponent (00b323f0): the nearest entity with the target tag within detect_distance (or the shooter with target_who_shot) pulls the velocity
|
||||
const vec4 h = ftf(tb + 10); // detect_distance, homing_velocity_multiplier, homing_targeting_coeff, max_turn_rate
|
||||
const int hf = fti(tb + 11).x, shooterSlot = int(p.e.z);
|
||||
int target = -1;
|
||||
float bestD = h.x * h.x;
|
||||
// 00b323f0: predefined_target first (alive, not invisible), then the shooter with target_who_shot, then the nearest tagged entity; invisible ones (INVISIBILITY) never
|
||||
const bool shooterAlive = shooterSlot > 0 && (ents[shooterSlot - 1].st.w & 8) == 0 && (ents[shooterSlot - 1].ex8.x & 8192) == 0;
|
||||
const int pre = fti(tb + 11).w;
|
||||
vec2 preAim = vec2(0.0); bool preOk = false;
|
||||
if (pre > 0 && pre - 1 < ctrl.inp.w && (ents[pre - 1].st.w & 8) == 0 && (ents[pre - 1].ex8.x & 8192) == 0) target = pre - 1;
|
||||
else if (pre < 0 && -pre - 1 < FP_MAX && fprojs[-pre - 1].d.w != 0) { preAim = fprojs[-pre - 1].a.xy; preOk = true; } // a projectile (homing_projectile.lua aims at other shots)
|
||||
if (target >= 0 || preOk) {}
|
||||
else if ((hf & 4) != 0 && shooterAlive) target = shooterSlot - 1;
|
||||
else for (int k = 0; k < ctrl.inp.w; ++k) {
|
||||
const int kind = ents[k].st.x;
|
||||
if (!(kind == 1 || kind == 2 || kind == 3) || (ents[k].st.w & 8) != 0 || (ents[k].ex8.x & 8192) != 0 || k == shooterSlot - 1 || (ents[k].ex8.x & ((hf >> 4) & 0x1FFF)) == 0) continue;
|
||||
const vec2 dd = pos - ents[k].a.xy;
|
||||
const float dsq = dot(dd, dd);
|
||||
if (dsq < bestD) { bestD = dsq; target = k; }
|
||||
}
|
||||
if (target >= 0 || preOk) {
|
||||
const vec2 tp = preOk ? preAim : ents[target].a.xy + vec2(ents[target].box.x + ents[target].box.y, ents[target].box.z + ents[target].box.w) * 0.5; // the centre of its hitbox
|
||||
const vec2 delta = tp - pos;
|
||||
const float dist = length(delta);
|
||||
if (dist < h.x) {
|
||||
if ((hf & 2) == 0) { // the velocity keeps homing_velocity_multiplier of itself and gains homing_targeting_coeff x the offset x dt x (1 - distance / detect_distance), in pixels per second
|
||||
const float k = max(1.0 - dist / h.x, 0.0);
|
||||
vel = vel * h.y + h.z * delta * (1.0 / 60.0) * k / 60.0;
|
||||
} else { // just_rotate_towards_target: the velocity turns toward the target by at most max_turn_rate
|
||||
const float sp = length(vel);
|
||||
const float av = sp > 0.0 ? atan(vel.y, vel.x) : 0.0, at = dist > 0.0 ? atan(delta.y, delta.x) : 0.0;
|
||||
const float TAU = 6.2831855;
|
||||
float A = av - at; A = A - TAU * floor(A / TAU);
|
||||
const float turn = min(A, h.w);
|
||||
const float ang = (TAU - A <= A) ? turn : -turn;
|
||||
vel = vec2(cos(ang) * vel.x - sin(ang) * vel.y, cos(ang) * vel.y + sin(ang) * vel.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// VelocityComponent (00c370c0): gravity, then v -= v dt air_friction (a negative friction speeds it up), then the terminal velocity (apply_terminal_velocity, pixels per second)
|
||||
const vec4 t15 = ftf(tb + 15), t16f = ftf(tb + 16), t17 = ftf(tb + 17);
|
||||
const int f16 = fti(tb + 16).x;
|
||||
vel.y += p.b.z * bm_gravity(pos); vel.x += t17.x; // (gravity_y x the biome's entity_gravity_y_multiplier, 00c370c0)
|
||||
vel -= vel * p.b.w;
|
||||
if ((f16 & 64) != 0 && (t1.w & TF_PHYSICS) == 0 && dot(vel, vel) * 3600.0 > t16f.z * t16f.z) vel = normalize(vel) * (t16f.z / 60.0);
|
||||
const bool inert = ((p.d.z >> 11) & 1) != 0; // on_collision_remove_projectile: the entity stuck where it hit, without its ProjectileComponent
|
||||
if (inert) vel = vec2(0.0);
|
||||
const bool removed = ((p.d.z >> 29) & 1) != 0; // the host removed it (EntityKill of its twin): the component goes, nothing of the update runs
|
||||
const bool wasStuck = (p.d.z & 0x800) != 0;
|
||||
if (spec >= 0 && !inert && !removed) fire_triggers(spec, fired, 1, age, pos, vel, seed, team, shooter, herd); // the countdown runs before the flight of the update (00c06c60)
|
||||
int steps = max(1, int(ceil(max(abs(vel.x), abs(vel.y)))));
|
||||
float spin = p.e.x, omega = p.e.y;
|
||||
bool alive = !removed, hitWorld = false, stuck = false, hitEntity = false;
|
||||
bool touchedWorld = false; vec2 trigVel = vec2(0.0); // a world hit this frame (bounce or stop): the hit-world triggers fire with the velocity reflected about the surface (00c06250)
|
||||
// a thrown physics body (the bomb) rests on its shape, not on its centre: the lowest point of its image counts as well (half the smaller side below the position)
|
||||
const vec2 foot = (t1.w & TF_PHYSICS) != 0 && t5.y > 0 ? vec2(0.0, 0.5 * float(min(t5.y, t5.z))) : vec2(0.0);
|
||||
const int tflags = fti(tb + 11).y; // 2: collide_with_world 0 (goes through terrain), 4: penetrate_entities (hits every damage_every_x_frames frames, does not die), 8: collide_with_entities 0
|
||||
const bool noWorld = (tflags & 2) != 0 || inert;
|
||||
// penetrate_world (00c073b0): no collision with the world; when the path of the frame crosses it, the step is penetrate_world_velocity_coeff of the velocity
|
||||
const bool penWorld = (f16 & 1) != 0 && !noWorld;
|
||||
vec2 stepVel = vel;
|
||||
if (penWorld) {
|
||||
for (int i = 1; i <= steps; ++i) if (solid_at(ivec2(floor(pos + vel * (float(i) / float(steps)))))) { stepVel = vel * t15.x; break; }
|
||||
}
|
||||
const float gpEnergy = (f16 & 1) == 0 ? t15.y : 0.0; // ground_penetration_coeff x VelocityComponent mass
|
||||
for (int i = 0; i < steps && alive; ++i) {
|
||||
vec2 np = pos + stepVel / float(steps);
|
||||
const bool bodyAt = body_hit(np) && (shooter <= 0 || body_slot(np) != shooter - 1); // (not the body of its shooter: a physics creature shoots from inside itself)
|
||||
if (!noWorld && !penWorld && bodyAt && !solid_at(ivec2(floor(np)))) body_damage(body_slot(np), pos, p.b.x); // (the hit point: the last free position)
|
||||
bool blocked = !noWorld && !penWorld && (solid_at(ivec2(floor(np))) || solid_at(ivec2(floor(np + foot))) || bodyAt);
|
||||
if (blocked && gpEnergy > 0.0 && solid_at(ivec2(floor(np))) && !bodyAt) {
|
||||
// ground_penetration (00c08a30, per cell of the ray): the kinetic energy coeff x mass x 0.5 x speed^2 is spent on the cell's hp; a cell that loses all of it is removed and the
|
||||
// projectile flies on, slower; a cell harder than ground_penetration_max_durability_to_destroy or a speed of 10 px/s or less stops it
|
||||
const ivec2 c = ivec2(floor(np));
|
||||
const ivec2 q = c - ivec2(pc.view_x, pc.view_y);
|
||||
const float v2 = dot(vel, vel) * 3600.0;
|
||||
if (q.x >= 0 && q.y >= 0 && q.x < pc.gw && q.y < pc.gh && v2 > 100.0) {
|
||||
const int ci = q.y * pc.gw + q.x;
|
||||
const uint m = cells[ci] & 0xFFFFu;
|
||||
const float dur = uintBitsToFloat(mats[3u * m + 2u].y);
|
||||
const int maxd = fti(tb + 15).z;
|
||||
if (!(maxd > 0 && dur > float(maxd))) {
|
||||
const float energy = gpEnergy * 0.5 * v2;
|
||||
const float hp = float((mats[3u * m + 2u].z >> 20) * 50u);
|
||||
vel *= 1.0 - min(hp, energy) / energy;
|
||||
stepVel = vel;
|
||||
if (hp - min(hp, round(energy)) < 1.0) { cells[ci] = 0u; ext[ci] = vec2(0.0); blocked = false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blocked) {
|
||||
if (t1.w < 0 && solid_at(ivec2(floor(np)))) { vel = vec2(0.0); break; } // a particle entity (SimplePhysicsComponent, 00c17fc0): a hit on the terrain keeps the position before it and zeroes the velocity (DieIfSpeedBelow then removes it)
|
||||
if ((t1.w & TF_PHYSICS) != 0) { // a thrown body: bounces with loss, rolls and comes to rest; only the fuse makes it explode
|
||||
const int brk = (fti(tb + 11).z >> 16) & 0xFFFF; // a breakable one (the egg): an impact faster than its break speed kills it, it explodes (projectiles.cpp break_speed)
|
||||
if (brk > 0 && length(vel) * 60.0 > float(brk)) { alive = false; hitWorld = true; break; }
|
||||
bool bx = solid_at(ivec2(floor(vec2(np.x, pos.y)))) || solid_at(ivec2(floor(vec2(np.x, pos.y) + foot))), by = solid_at(ivec2(floor(vec2(pos.x, np.y)))) || solid_at(ivec2(floor(vec2(pos.x, np.y) + foot)));
|
||||
if (bx || !by) { vel.x = -vel.x * 0.4; vel.y *= 0.85; omega = -omega * 0.5 + vel.y * 0.15; }
|
||||
if (by || !bx) { vel.y = -vel.y * 0.4; vel.x *= 0.85; omega = omega * 0.6 + vel.x * 0.2; }
|
||||
if (length(vel) < 0.25) vel = vec2(0.0);
|
||||
break;
|
||||
}
|
||||
if (body_hit(np) && t16f.w > 0.0 && body_slot(np) >= 0) { // 00c073b0: the body hit gets ApplyLinearImpulse(the frame's displacement x physics_impulse_coeff x VelocityComponent mass / 10) at the point (event -10000 - the entity slot)
|
||||
uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(np, -10000.0 - float(body_slot(np)), uintBitsToFloat(packHalf2x16(vel * t16f.w * ftf(tb + 9).y / 10.0)));
|
||||
}
|
||||
// the hit (00c073b0): bounces_left counts down at every hit; with some left, the velocity is reflected about the surface normal (16 rays of 5 px around the last free position,
|
||||
// 0063fd80) and scaled by bounce_energy, unless the reflection points back into the wall (r.v <= 0.75, bounce_at_any_angle allows it); bounce_always then turns it around with a
|
||||
// random turn of +-0.1 rad. Without a bounce the projectile stops there (velocity 0) and the death of 00c08b90 follows: on_collision_die kills it, on_collision_remove_projectile
|
||||
// leaves it stuck, otherwise it stays where it stopped.
|
||||
const int bl = bounces;
|
||||
bounces = max(bl - 1, -1);
|
||||
bool bounced = false;
|
||||
vec2 nsum = vec2(0.0); int nh = 0; // the surface normal: 16 rays of 5 px around the last free position (0063fd80)
|
||||
for (int k = 0; k < 16; ++k) {
|
||||
const float a = float(k) * (6.2831855 / 16.0);
|
||||
const vec2 d = vec2(-sin(a), cos(a)) * 5.0;
|
||||
for (int s = 1; s <= 5; ++s) if (solid_at(ivec2(floor(pos + d * (float(s) / 5.0)))) || body_hit(pos + d * (float(s) / 5.0))) { nsum += d; ++nh; break; }
|
||||
}
|
||||
touchedWorld = true;
|
||||
trigVel = (nh > 0 && dot(nsum, nsum) > 0.0) ? vel - normalize(nsum) * 2.0 * dot(normalize(nsum), vel) : -vel;
|
||||
if (bl > 0) {
|
||||
const vec2 v = vel * 60.0;
|
||||
if (nh > 0 && dot(nsum, nsum) > 0.0) {
|
||||
const vec2 n = normalize(nsum);
|
||||
const vec2 r = v - n * 2.0 * dot(n, v);
|
||||
if (dot(r, v) > 0.75 || (f16 & 4) != 0 || ((p.d.z >> 28) & 1) != 0) { vel = r * t15.w / 60.0; bounced = true; }
|
||||
}
|
||||
if (!bounced && (f16 & 2) != 0) {
|
||||
const float a = rnd01(hash3(seed, uint(i), 97u)) * 0.2 - 0.1;
|
||||
const vec2 rv = -vel * t15.w;
|
||||
vel = vec2(cos(a) * rv.x - sin(a) * rv.y, sin(a) * rv.x + cos(a) * rv.y);
|
||||
bounced = true;
|
||||
}
|
||||
}
|
||||
if (bounced && (f16 & 2048) != 0) { uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(pos, -8.0, float(id)); } // bounce_fx_file: the host loads it at the bounce (00c073b0; event -8, the projectile's slot)
|
||||
if (bounced) { pos += vel; break; } // (the position of the frame: the last free one + the new velocity x dt)
|
||||
vel = vec2(0.0);
|
||||
if ((t1.w & TF_COLLISION_DIE) != 0) { alive = false; hitWorld = true; }
|
||||
else if ((f16 & 8) != 0) stuck = true;
|
||||
break;
|
||||
}
|
||||
pos = np;
|
||||
if (!inert && !stuck && (t1.w & TF_PHYSICS) == 0 && (tflags & 8) == 0 && ((tflags & 4) == 0 || age % max(fti(tb + 11).z & 0xFFFF, 1) == 0)) {
|
||||
int hk; // the herds decide who is hurt: everything except the herd of the shooter (unless friendly fire); never the player with dont_hit_player
|
||||
gAhead = pos + stepVel / float(steps);
|
||||
if (victim_at(pos, shooter, herd, shot_flags(t1.w, p.d.z), age, hk)) { // (the shot's friendly_fire modifier)
|
||||
float speedScale = 1.0; // damage_scaled_by_speed: speed / mInitialSpeed (or damage_scale_max_speed when > 0)
|
||||
if ((f16 & 128) != 0) { const float s0 = t17.y > 0.0 ? t17.y / 60.0 : p.e.y; speedScale = s0 > 0.0 ? length(vel) / s0 : 1.0; }
|
||||
const float cm = crit_mult((p.d.z >> 12) & 4095, t1.w, shooter, hk, seed);
|
||||
const float hitDmg = hit_damage(hk, p.b.x * speedScale, tb, p.f) * boost_of(t1.w, hk) * cm;
|
||||
if (cm > 1.0) note_crit(pos, hk);
|
||||
note_block(hk, p.b.x, hitDmg);
|
||||
atomicAdd(ents[hk].x.x, int(hitDmg * 1000.0));
|
||||
if (ents[hk].st.x == 4) body_damage(hk, pos, hitDmg); // a prop with a liquid inventory leaks
|
||||
if (ents[hk].st.x == 4 && ents[hk].ex7.w > 0.5 && t16f.w > 0.0) { uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(gAhead, -10000.0 - float(hk), uintBitsToFloat(packHalf2x16(vel * t16f.w * ftf(tb + 9).y / 10.0))); } // and the impulse on its body
|
||||
{ // 00c091f0: knockback scalar = ProjectileComponent.knockback_force x speed (pixels per second) x the projectile's VelocityComponent mass; the force vector = velocity x ragdoll_force_multiplier;
|
||||
// then the victim's gate (00ab9a90): abs(scalar) against minimum_knockback_force before the division by its mass, upward y, never on a killing hit
|
||||
const vec4 t9 = ftf(tb + 9);
|
||||
const int vk = ents[hk].st.x;
|
||||
const vec2 fv = vel * 60.0 * t9.z;
|
||||
const float kfh = t9.x + p.f.z; // ProjectileComponent knockback_force + the shot's knockback_force modifier
|
||||
const float sc = kfh * length(vel * 60.0) * t9.y;
|
||||
if (kfh != 0.0 && (vk == 1 || vk == 3) && length(fv) > 0.0 && ents[hk].hp.x - hitDmg > 0.0 && abs(sc) >= ents[hk].ex11.x && (ents[hk].ex8.x & 96) == 0 && ents[hk].ex8.y > 0) {
|
||||
const float mass = intBitsToFloat(ents[hk].rg2.w);
|
||||
vec2 imp = normalize(fv) * sc;
|
||||
if (mass != 0.0) imp /= mass;
|
||||
imp.y = -abs(imp.y);
|
||||
ents[hk].ex11.yz = imp / 60.0;
|
||||
}
|
||||
}
|
||||
note_attacker(hk, shooter);
|
||||
ents[hk].ex19.yz = vec2(float(type + 1), float(pc.frame & 0xFFFFF)); // the projectile of the hit (script_damage_received's projectile_thats_responsible)
|
||||
atomicOr(ents[hk].ex3.z, 1); // hit by a projectile
|
||||
if ((t1.w & TF_HIT_EFFECTS) != 0) { uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(float(hk), float(shooter), -1.0, float(1 + type)); } // the hit effects of the projectile: the host applies them (victim slot, shooter slot + 1, type + 1)
|
||||
if (hk != 0) { // the packet of 00c091f0: the impulse = velocity (px/s) x ragdoll_force_multiplier (the blood of the hit and the ragdoll take it), its RAGDOLL_FX
|
||||
const vec2 imp = vel * 60.0 * ftf(tb + 9).z;
|
||||
atomicExchange(ents[hk].aux.x, int(imp.x * 1000.0)); atomicExchange(ents[hk].aux.y, int(imp.y * 1000.0));
|
||||
atomicAnd(ents[hk].ex3.z, ~0xF000); atomicOr(ents[hk].ex3.z, ((fti(tb + 11).y >> 4) & 15) << 12);
|
||||
}
|
||||
if (spec >= 0) fire_triggers(spec, fired, 3, age, pos, vel, seed, team, shooter, herd); // the impact (00c091f0)
|
||||
if ((tflags & 4) == 0) { alive = false; hitEntity = true; } // (a penetrating projectile flies on)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alive && t1.w < 0 && length(vel) < float(fti(tb + 9).w >> 20 & 4095) / 256.0) alive = false; // DieIfSpeedBelowComponent of a particle entity
|
||||
// 00c370c0: the count N of liquid cells (not powder) in the 3x3 around the new integer position (float coordinates truncate toward zero), taken only when that position changed.
|
||||
// Every candidate counts, then (displace_liquid on, 75 % of the draws) its cell becomes a particle that flies off against the motion (00717670: rotated, 0.1 x); the velocity keeps
|
||||
// 1 - liquid_drag * dt * N of itself (VelocityComponent liquid_drag, unclamped), and N above liquid_death_threshold kills the projectile (liquid_death_threshold, default 0: off).
|
||||
if (alive && any(notEqual(ivec2(pos), ivec2(start)))) {
|
||||
int n = 0;
|
||||
const ivec2 q = ivec2(pos);
|
||||
for (int dy = -1; dy <= 1; ++dy) for (int dx = -1; dx <= 1; ++dx) if (liquid_nonsand(q + ivec2(dx, dy))) ++n;
|
||||
if (n > 0 && fti(tb + 8).y != 0) {
|
||||
for (int k = 0; k < 9; ++k) {
|
||||
const ivec2 c = q + ivec2(k % 3 - 1, k / 3 - 1);
|
||||
if (liquid_at_pos(vec2(c) + 0.5) == 0u || (hash3(seed, uint(k), 81u) % 101u) >= 75u) continue;
|
||||
const float ang = 0.6 * float(hash3(seed, uint(k), 82u) & 0x7FFFu) / 32767.0 - 0.3;
|
||||
displace_cell(c, -(mat2(cos(ang), sin(ang), -sin(ang), cos(ang)) * (0.1 * vel)));
|
||||
}
|
||||
}
|
||||
vel *= 1.0 - ftf(tb + 8).x * (1.0 / 60.0) * float(n);
|
||||
// (liquid_death_threshold of the VelocityComponent defaults to 0: no death by the count; die_on_liquid_collision is the collision with a liquid cell)
|
||||
if ((t1.w & TF_DIE_IN_LIQUID) != 0 && liquid_nonsand(ivec2(pos))) { alive = false; hitWorld = true; }
|
||||
}
|
||||
if (alive && !inert && (f16 & 16) != 0 && (t1.w & TF_PHYSICS) == 0 && dot(vel, vel) * 3600.0 <= t16f.y * t16f.y) { // die_on_low_velocity: the death of a hit (00c08b90)
|
||||
if ((t1.w & TF_COLLISION_DIE) != 0) alive = false; else if ((f16 & 8) != 0) stuck = true;
|
||||
}
|
||||
bool expired = false;
|
||||
if (alive && !inert && !stuck && age >= int(p.b.y)) { alive = false; expired = true; } // lifetime out (the inert entity has no ProjectileComponent, so no lifetime)
|
||||
if (!alive && fti(tb + 17).w != 0) { // on_death_emit_particle: on_death_emit_particle_count real particles of the material
|
||||
const int dp = fti(tb + 17).w, dmat = (dp & 0xFFFF) - 1, dn = min(dp >> 16, 64);
|
||||
for (int k = 0; k < dn; ++k) {
|
||||
const vec2 ev = (vec2(rnd01(hash3(seed, uint(k), 93u)), rnd01(hash3(seed, uint(k), 94u))) - 0.5) * 2.0 / 60.0 * 30.0;
|
||||
emit_material_particle(pos, ev, 30.0, PART_GRAV, mats[3u * uint(dmat)].x, dmat);
|
||||
}
|
||||
}
|
||||
if (!alive && (hitWorld || hitEntity) && (f16 & 1024) != 0) { uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(pos, -6.0, float(type)); } // on_collision_spawn_entity: the host loads spawn_entity there (00c08b90)
|
||||
// triggers (see fire_triggers): a hit on the world fires the hit-world and waiting timers in the reflected direction (also when the projectile bounces and lives on); the end of the
|
||||
// lifetime fires the waiting timers; the removal of the projectile, whatever ended it, fires the death triggers
|
||||
if (touchedWorld && spec >= 0) fire_triggers(spec, fired, 2, age, pos, trigVel, seed, team, shooter, herd);
|
||||
if (stuck && !wasStuck && spec >= 0) fire_triggers(spec, fired, 5, age, pos, vel, seed, team, shooter, herd); // on_collision_remove_projectile: the ProjectileComponent goes, its removal (00c0e3e0, slot 15 of the system) casts the death triggers
|
||||
if (!alive) {
|
||||
if (spec >= 0) {
|
||||
if (expired) fire_triggers(spec, fired, 4, age, pos, vel, seed, team, shooter, herd);
|
||||
fire_triggers(spec, fired, 5, age, pos, vel, seed, team, shooter, herd);
|
||||
}
|
||||
if ((t1.w & TF_EXPLODE) != 0) {
|
||||
explode(pos, p.c.x, p.c.y, type, team, shooter, herd, (p.d.z >> 12) & 4095, seed);
|
||||
if (((p.d.z >> 24) & 15) != 0) { uint xi = uint(atomicAdd(xhead.x, 1)); if (xi < 512u) xe[xi] = vec4(pos, -7.0, float((p.d.z >> 24) & 15)); } // lightning_count: that many electricity.xml at the blast (event kind -7)
|
||||
if (t3.y >= 0) boom_fx(t3.y, pos, seed, p.c.x);
|
||||
} // (a projectile that does not explode leaves nothing of its own when it dies: no sparks - wall pieces absorbed by Spells To Power vanish into their red poofs only)
|
||||
}
|
||||
if (alive && p.f.w > 0.5) { // the shot's trail_material: real particles of the material along the path (00c0b440: an emitter with count = amount, velocity +- sqrt(amount) x 0.5)
|
||||
const int tm = int(p.f.w), tmat = (tm & 4095) - 1, amount = tm >> 12;
|
||||
const float tv = sqrt(float(amount)) * 0.5 / 60.0;
|
||||
for (int k = 0; k < min(amount, 8); ++k) {
|
||||
const vec2 ep = mix(start, pos, rnd01(hash3(seed, uint(k), 71u)));
|
||||
const vec2 ev = (vec2(rnd01(hash3(seed, uint(k), 72u)), rnd01(hash3(seed, uint(k), 73u))) - 0.5) * 2.0 * tv;
|
||||
emit_material_particle(ep, ev, 20.0, PART_GRAV, mats[3u * uint(tmat)].x, tmat);
|
||||
}
|
||||
}
|
||||
if (alive) {
|
||||
// particle emitters (cosmetic)
|
||||
for (int e = 0; e < t3.w; ++e) {
|
||||
int eb = EMIT_BASE + (t3.z + e) * EMIT_STRIDE;
|
||||
ivec4 e0 = fti(eb), e2 = fti(eb + 2);
|
||||
vec4 e0f = ftf(eb), e1 = ftf(eb + 1), e3 = ftf(eb + 3);
|
||||
const int iv2 = max(e2.y & 0xFF, 2); // the mean emission interval in half frames (the min .. max interval of 00bc75e0 is random; 1..2 frames emits 2 times in 3 frames, not every frame)
|
||||
const float away = float(e2.y >> 16) / 60.0; // velocity_always_away_from_center, cells per frame
|
||||
const uint attr = uint(e2.y >> 8) & 0xFFu; // attractor_force
|
||||
{ const int ph = age + int(id) + e * 3; if ((ph * 2) / iv2 == (ph * 2 - 2) / iv2) continue; }
|
||||
if (age < (e2.z >> 16)) continue; // delay_frames (summon_portal.xml's burst comes at frame 118)
|
||||
const int c0 = e2.z & 0xFFFF;
|
||||
int n = c0 + int(rnd01(hash3(seed, uint(e), 51u)) * float(e2.w - c0 + 1));
|
||||
for (int k = 0; k < n && k < 64; ++k) { // (the giga black hole's emitters make 40 - 60 a frame)
|
||||
float tt = (e0.y & 1) != 0 ? rnd01(hash3(seed, uint(e * 64 + k), 52u)) : 1.0;
|
||||
vec2 ep = mix(start, pos, tt);
|
||||
ep += (vec2(rnd01(hash3(seed, uint(e * 64 + k), 59u)), rnd01(hash3(seed, uint(e * 64 + k), 60u))) - 0.5) * 2.0 * e3.zw; // x/y_pos_offset ranges
|
||||
{ // area_circle_radius (00bc75e0): a point of the disc, radius sqrt(u) between min and max (quarter pixels in bits 8-19 / 20-31 of the flags)
|
||||
const float rmax = float((uint(e0.y) >> 8) & 0xFFFu) * 0.25, rmin = float((uint(e0.y) >> 20) & 0xFFFu) * 0.25;
|
||||
if (rmax > 0.0) { const float rr = sqrt(rnd01(hash3(seed, uint(e * 64 + k), 61u))) * (rmax - rmin) + rmin, aa = rnd01(hash3(seed, uint(e * 64 + k), 62u)) * 6.2831853; ep += vec2(cos(aa), sin(aa)) * rr; }
|
||||
}
|
||||
vec2 ev = vec2(mix(e1.x, e1.y, rnd01(hash3(seed, uint(e * 64 + k), 53u))), mix(e1.z, e1.w, rnd01(hash3(seed, uint(e * 64 + k), 54u))));
|
||||
ev += (vec2(rnd01(hash3(seed, uint(k), 55u)), rnd01(hash3(seed, uint(k), 56u))) - 0.5) * 2.0 * e3.x;
|
||||
if (away != 0.0) { const vec2 dd = ep - pos; const float dl = length(dd); if (dl > 1e-4) ev += dd / dl * away; } // (00bc75e0: along the way out from the emitter)
|
||||
float life = max(2.0, mix(e0f.z, e0f.w, rnd01(hash3(seed, uint(e * 64 + k), 57u))));
|
||||
if ((e0.y & 4) != 0) { // create_real_particles (00bc75e0 route +0x60): the material is created in the grid at the position (velocity and lifetime are ignored) through the cell
|
||||
// factory: into an empty cell, or over a gas or liquid one unless emit_only_if_there_is_space (+0x67, flag 64)
|
||||
const ivec2 q = ivec2(floor(ep)) - ivec2(pc.view_x, pc.view_y);
|
||||
const int mat = int(ft[eb + 3].y);
|
||||
if (mat > 0 && q.x >= 0 && q.y >= 0 && q.x < pc.gw && q.y < pc.gh) {
|
||||
const int ci = q.y * pc.gw + q.x;
|
||||
const uint cur = cells[ci], cm = cell_mat(cur);
|
||||
const bool free = cm == 0u || ((e0.y & 64) == 0 && (mflags(cm) & (F_GAS | F_LIQCELL)) != 0u && (mflags(cm) & F_STATIC) == 0u);
|
||||
if (free && atomicCompSwap(cells[ci], cur, uint(mat) | (((hash3(seed, uint(e * 8 + k), 58u) >> 16) & 0xFFu) << 16)) == cur) { ext[ci] = vec2(0.0); birth[ci] = birth_frame(); }
|
||||
}
|
||||
}
|
||||
else if ((e0.y & 16) == 0 || cell_free_at(ep)) { // (bit 3: collide_with_grid 0, bit 4: cosmetic_force_create 0, bit 5: a material that does not glow - blended with its alpha)
|
||||
const bool blended = (e0.y & 32) != 0;
|
||||
const int ps = emit_particle_g(ep, ev, life, uf(ft[eb + 2].x), uint(e0.x) & 0xFFFFFFu, (e0.y & 2) != 0, !blended, (e0.y & 8) == 0);
|
||||
if (ps >= 0 && blended) fparts[ps].c.z = 255u - ((uint(e0.x) >> 24) & 0xFFu);
|
||||
if (ps >= 0 && (attr != 0u || (e0.y & 128) != 0)) fparts[ps].c.z |= (id & 0x7FFu) << 8 | attr << 19 | ((e0.y & 128) != 0 ? 0x8000000u : 0u); // the attractor (this projectile, its force) and draw_as_long, for fx_particles.comp
|
||||
}
|
||||
}
|
||||
}
|
||||
if (t4.w > 0.0) add_light(pos, min(t4.w, 36.0) * 0.55, 0.32, t4.xyz);
|
||||
draw_sprite(pos, vel, age, t1, t5, t6, spin, (t1.w & TF_PHYSICS) == 0 && spin < 0.0 ? -spin : 1.0);
|
||||
}
|
||||
if ((t1.w & TF_PHYSICS) != 0) { if (length(vel) < 0.01) omega *= 0.8; spin += omega; omega *= 0.99; }
|
||||
p.e = vec4(spin, omega, p.e.z, p.e.w); // (the shooter slot + 1 and its herd stay with the projectile)
|
||||
if (alive && !inert) vel *= bm_drag(pos); // 00c06c60: after the move the velocity is multiplied by the biome's projectile_drag_coeff (MOIST 0.965)
|
||||
p.a = vec4(pos, vel);
|
||||
p.c.z = float(age); p.c.w = float(bounces);
|
||||
if (!alive && (fti(tb + 11).y & 1) != 0) { uint xi = uint(atomicAdd(xhead.x, 2)); if (xi + 1u < 512u) { xe[xi] = vec4(pos, -5.0, float(id)); xe[xi + 1u] = vec4(float(p.d.w), 0.0, 0.0, 0.0); } } // the twin's projectile died (event kind -5)
|
||||
p.d.z = fired | (team << 8) | (p.d.z & ~0x3FF) | (p.d.z & 0x400) | (stuck ? 0x800 : 0); // bit 11: stuck without its ProjectileComponent (on_collision_remove_projectile); bits 12-23 crit, 24-27 lightning_count, 28 bounce at any angle stay
|
||||
p.d.w = alive ? p.d.w : 0;
|
||||
fprojs[id] = p;
|
||||
}
|
||||
Reference in new issue
Block a user