#version 450 #extension GL_GOOGLE_include_directive : require layout(local_size_x = 64) in; #include "fx_common.glsl" // The player's right arm and the item in its hand, drawn into the entity layer every frame. The arm (player_arm.xml) hangs at the body's // right_arm_root hotspot (the host follows the stand / walk frames, so it breathes with the body), turns toward the aim point (the cursor, or // ahead of the player) and is mirrored when aiming left; the item is held at the arm's "hand" hotspot, turned the same way about its grip. // The arm header is in the first words of the hand texture region (hand1.x - its offset is absolute): [0] arm on, [1] arm pixels (absolute), // [2..3] arm size, [4..5] arm root relative to the body (floats), [6..7] the arm sprite's pivot (offset_x, offset_y), [8..9] its hand hotspot; // [48] cape on, [49..50] the corner of its 32 x 32 image (words 64..1087, rasterized by the host) relative to the body: drawn behind the body (empty pixels only). const uint HAND_BASE = (1u << 20) - 32768u; uint hw(uint i) { return fxtex[HAND_BASE + i]; } // draws a w x h image whose pixel `pivot` sits at `org`, turned by (c, s), mirrored vertically when `mirror` void draw_turned(vec2 org, vec2 pivot, float c, float s, bool mirror, int w, int h, uint off) { float r = length(vec2(float(w), float(h))) + 2.0; int n = int(2.0 * r) + 2; ivec2 lo = ivec2(floor(org - vec2(r))); for (int i = int(gl_LocalInvocationID.x); i < n * n; i += 64) { ivec2 dst = lo + ivec2(i % n, i / n); vec2 d = vec2(dst) + 0.5 - org; vec2 l = vec2(c * d.x + s * d.y, -s * d.x + c * d.y); // the image frame, relative to the pivot if (mirror) l.y = -l.y; ivec2 sp = ivec2(floor(pivot + l)); if (sp.x < 0 || sp.y < 0 || sp.x >= w || sp.y >= h) continue; ivec2 q = dst - 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[off + uint(sp.y * w + sp.x)]; const uint ta = t >> 24; if (ta == 0u) continue; const uint di = uint(q.y * pc.gw + q.x); const uint under = entlayer[di]; if (ta < 255u && (under >> 24) != 0u) { // a translucent texel over the body: mixed with it (source over), not cut through it uint mixd = max(ta, under >> 24) << 24; for (uint sh = 0u; sh < 24u; sh += 8u) mixd |= (((t >> sh) & 0xFFu) * ta + ((under >> sh) & 0xFFu) * (255u - ta)) / 255u << sh; entlayer[di] = mixd; } else entlayer[di] = t; } } // The items NPCs hold (ItemPickUpperComponent is_in_npc): at word 32640 of the hand region the count, then 12 words per entry: [0] the slot + 1, [1] the image // (absolute), [2..3] its size, [4..5] the grip, [6..7] the hand relative to the body, [8..9] the aim point (floats); drawn like the player's item, turned toward the aim void draw_npc_items() { const uint n = min(hw(32640u), 10u); for (uint k = 0u; k < n; ++k) { const uint b = 32641u + k * 12u; const int sl = int(hw(b)) - 1; if (sl <= 0 || sl >= int(ents.length()) || (ents[sl].st.w & 8) != 0) continue; vec2 cur = ents[sl].a.xy, prv = prevpos[sl].xy; vec2 body = distance(prv, cur) < 10.0 ? mix(prv, cur, ctrl.view.z) : cur; vec2 hand = body + vec2(uintBitsToFloat(hw(b + 6u)), uintBitsToFloat(hw(b + 7u))); vec2 aim = vec2(uintBitsToFloat(hw(b + 8u)), uintBitsToFloat(hw(b + 9u))); float ang = length(aim - hand) > 0.5 ? atan(aim.y - hand.y, aim.x - hand.x) : 0.0; float c = cos(ang), s = sin(ang); draw_turned(hand, vec2(uintBitsToFloat(hw(b + 4u)), uintBitsToFloat(hw(b + 5u))), c, s, c < 0.0, int(hw(b + 2u)), int(hw(b + 3u)), hw(b + 1u)); } memoryBarrierBuffer(); barrier(); } void main() { draw_npc_items(); if (ctrl.inp.w < 1 || ents[0].st.x != 3 || (ents[0].st.w & 8) != 0 || (ents[0].ex8.x & 8192) != 0) return; vec2 cur = ents[0].a.xy, prv = prevpos[0].xy; vec2 body = distance(prv, cur) < 10.0 ? mix(prv, cur, ctrl.view.z) : cur; if (hw(48u) == 1u) { // the cape, behind the body ivec2 corner = ivec2(floor(body + vec2(uintBitsToFloat(hw(49u)), uintBitsToFloat(hw(50u))) + 0.5)); for (int i = int(gl_LocalInvocationID.x); i < 1024; i += 64) { uint t = hw(64u + uint(i)); if ((t >> 24) == 0u) continue; ivec2 q = corner + ivec2(i % 32, i / 32) - ivec2(pc.view_x, pc.view_y); if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) continue; if ((entlayer[q.y * pc.gw + q.x] >> 24) == 0u) entlayer[q.y * pc.gw + q.x] = t; } memoryBarrierBuffer(); barrier(); } if (ctrl.hand0.z < 0.5) return; const bool arm = hw(0u) == 1u; vec2 root = arm ? body + vec2(uintBitsToFloat(hw(4u)), uintBitsToFloat(hw(5u))) : body + vec2(0.0, -5.0); vec2 aim = ctrl.hand0.xy; float ang = length(aim - root) > 0.5 ? atan(aim.y - root.y, aim.x - root.x) : 0.0; float c = cos(ang), s = sin(ang); bool mirror = c < 0.0; vec2 hand = root; if (arm) { // the hand: the arm's hand hotspot relative to its pivot, turned and mirrored with the arm vec2 piv = vec2(uintBitsToFloat(hw(6u)), uintBitsToFloat(hw(7u))); vec2 hl = vec2(uintBitsToFloat(hw(8u)), uintBitsToFloat(hw(9u))) + 0.5 - piv; if (mirror) hl.y = -hl.y; hand = root + vec2(c * hl.x - s * hl.y, s * hl.x + c * hl.y); } { // the item's recoil (00b51670): hand2.z px back along the aim, turned muzzle-up by hand0.w radians (the arm keeps the aim) const float ia = ang + (mirror ? ctrl.hand0.w : -ctrl.hand0.w); const float ic = cos(ia), is = sin(ia); draw_turned(hand - vec2(c, s) * ctrl.hand2.z, ctrl.hand2.xy, ic, is, mirror, ctrl.hand1.y, ctrl.hand1.z, uint(ctrl.hand1.x)); } memoryBarrierBuffer(); barrier(); if (arm) draw_turned(root, vec2(uintBitsToFloat(hw(6u)), uintBitsToFloat(hw(7u))), c, s, mirror, int(hw(2u)), int(hw(3u)), hw(1u)); // the hand over the grip }