// Shambles the zombie: a knee-high zombie that lurches after its owner, arms stretched out in front, swaying like
// he's had one too many. A friend's idea (2026-10-11). Grown from my Pocket Mech (mech.lsl): two-bone IK legs with
// planted feet, but slow dragging steps, a big drunken roll, a wandering heading, and arms held forward.
// Touch: follow / stay. Owner /7: come, stay, groan, reset. Claude Opus 5.5 (Tessel).
float L1 = 0.24; // thigh
float L2 = 0.26; // shin
float H = 0.66; // body centre above the ground
float SPEED = 0.42;
float TURN = 1.1;
float STEP = 0.14;
float SWING = 0.5;
integer PRIMS;
float DT = 0.1;
float NEAR = 1.4;
list hips = [<0.0, 0.09, -0.21>, <0.0, -0.09, -0.21>];
list homes = [<0.02, 0.1, 0>, <0.02, -0.1, 0>];
list thighL; list shinL; list footL;
list planted; vector swingFrom; vector swingTo;
integer swingLeg = -1; integer lastLeg = 1;
float swingT0;
integer ARM_L; integer ARM_R; integer FIST_L; integer FIST_R; integer THR_L; integer THR_R;
vector pos; float yaw; float roll; float stride; key target = NULL_KEY;
float saluteT0; float boostT0; float baseZ;
integer link_named(string n)
{
integer i = llGetNumberOfPrims();
for (; i > 1; --i) if (llGetLinkName(i) == n) return i;
return 0;
}
float zof(vector v) { return v.z; }
float ground(vector p, float fallback)
{
list r = llCastRay(
, ,
[RC_REJECT_TYPES, RC_REJECT_AGENTS | RC_REJECT_PHYSICAL, RC_DATA_FLAGS, RC_GET_ROOT_KEY, RC_MAX_HITS, 4]);
integer n = llList2Integer(r, -1);
integer i;
for (i = 0; i < n; ++i)
if (llList2Key(r, i * 2) != llGetKey()) return zof(llList2Vector(r, i * 2 + 1));
vector off = p - llGetPos();
float t = llGround();
if (t > pos.z - 2.0 && t < pos.z + 0.4) return t;
return fallback;
}
vector home_world(integer leg, rotation rot)
{
vector h = llList2Vector(homes, leg) * rot + pos;
return ;
}
list leg_params(integer leg, vector footW, rotation rot)
{
vector hip = llList2Vector(hips, leg);
vector d = (footW - pos) / rot - hip;
float dist = llVecMag(d);
vector dir = d / dist;
if (dist > L1 + L2 - 0.005) dist = L1 + L2 - 0.005;
if (dist < 0.1) dist = 0.1;
float c = (L1 * L1 + dist * dist - L2 * L2) / (2 * L1 * dist);
if (c > 1) c = 1; if (c < -1) c = -1;
float a = llAcos(c);
vector pole = <1, 0, 0>; // knees forward, like a person
vector p = pole - dir * (pole * dir);
if (llVecMag(p) < 0.01) p = <1, 0, 0>;
p = llVecNorm(p);
vector knee = hip + dir * (L1 * llCos(a)) + p * (L1 * llSin(a));
vector end = hip + dir * dist;
vector up = <0, 0, 1>;
return [PRIM_LINK_TARGET, llList2Integer(thighL, leg), PRIM_POS_LOCAL, (hip + knee) / 2, PRIM_ROT_LOCAL, llRotBetween(up, llVecNorm(knee - hip)),
PRIM_LINK_TARGET, llList2Integer(shinL, leg), PRIM_POS_LOCAL, (knee + end) / 2, PRIM_ROT_LOCAL, llRotBetween(up, llVecNorm(end - knee)),
PRIM_LINK_TARGET, llList2Integer(footL, leg), PRIM_POS_LOCAL, end + <0.04, 0, 0.0>, PRIM_ROT_LOCAL, ZERO_ROTATION];
}
list arm(integer a, integer f, float side, float swingA, float liftA)
{
vector pivot = <0, 0.23 * side, 0.05>;
rotation r = llEuler2Rot();
return [PRIM_LINK_TARGET, a, PRIM_POS_LOCAL, pivot + <0, 0, -0.15> * r, PRIM_ROT_LOCAL, r,
PRIM_LINK_TARGET, f, PRIM_POS_LOCAL, pivot + <0.01, 0, -0.31> * r, PRIM_ROT_LOCAL, r];
}
thrusters(integer on)
{
list ps = [];
if (on) ps = [PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK | PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_INTERP_SCALE_MASK,
PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE_CONE, PSYS_SRC_ANGLE_BEGIN, PI - 0.2, PSYS_SRC_ANGLE_END, PI,
PSYS_SRC_BURST_RATE, 0.02, PSYS_SRC_BURST_PART_COUNT, 4, PSYS_SRC_BURST_SPEED_MIN, 1.0, PSYS_SRC_BURST_SPEED_MAX, 1.8,
PSYS_PART_MAX_AGE, 0.45, PSYS_PART_START_SCALE, <0.08, 0.08, 0>, PSYS_PART_END_SCALE, <0.02, 0.02, 0>,
PSYS_PART_START_COLOR, <0.6, 0.85, 1>, PSYS_PART_END_COLOR, <1, 0.5, 0.2>,
PSYS_PART_START_ALPHA, 1.0, PSYS_PART_END_ALPHA, 0.0, PSYS_PART_START_GLOW, 0.4, PSYS_PART_END_GLOW, 0.0];
if (THR_L) llLinkParticleSystem(THR_L, ps);
if (THR_R) llLinkParticleSystem(THR_R, ps);
}
pose(rotation rot, float swingA, float liftL, float liftR)
{
list p = [PRIM_POSITION, pos, PRIM_ROTATION, rot];
integer i;
for (i = 0; i < 2; ++i)
{
vector f = llList2Vector(planted, i);
if (i == swingLeg)
{
float s = (llGetTime() - swingT0) / SWING;
if (s > 1) s = 1;
float e = s * s * (3 - 2 * s);
f = swingFrom + (swingTo - swingFrom) * e + <0, 0, 0.035 * llSin(PI * s)>;
}
p += leg_params(i, f, rot);
}
// arms out in front (swing -1.5 rad points them forward), bobbing a little, slightly out of step
float t = llGetTime();
if (ARM_L && FIST_L) p += arm(ARM_L, FIST_L, 1, -1.45 + 0.08 * llSin(t * 2.1) + 0.1 * swingA, -0.08);
if (ARM_R && FIST_R) p += arm(ARM_R, FIST_R, -1, -1.5 + 0.08 * llSin(t * 2.1 + 1.3) - 0.1 * swingA, -0.08);
llSetLinkPrimitiveParamsFast(LINK_ROOT, p);
}
setup()
{
thighL = [link_named("l_thigh"), link_named("r_thigh")];
shinL = [link_named("l_shin"), link_named("r_shin")];
footL = [link_named("l_foot"), link_named("r_foot")];
ARM_L = link_named("arm_l"); ARM_R = link_named("arm_r"); FIST_L = link_named("fist_l"); FIST_R = link_named("fist_r");
THR_L = link_named("thruster_l"); THR_R = link_named("thruster_r");
integer k;
for (k = 2; k <= llGetObjectPrimCount(llGetKey()); ++k) llSetLinkPrimitiveParamsFast(k, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_NONE]);
llSetLinkPrimitiveParamsFast(LINK_ROOT, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
thrusters(FALSE);
pos = llGetPos();
vector e = llRot2Euler(llGetRot()); yaw = e.z;
pos.z = ground(pos, pos.z - H) + H;
rotation rot = llEuler2Rot(<0, 0, yaw>);
planted = [home_world(0, rot), home_world(1, rot)];
swingLeg = -1; target = NULL_KEY; saluteT0 = 0; boostT0 = 0;
pose(rot, 0, 0, 0);
}
default
{
state_entry() { PRIMS = llGetObjectPrimCount(llGetKey()); setup(); llListen(7, "", llGetOwner(), ""); llSetTimerEvent(DT); }
on_rez(integer n) { llResetScript(); }
changed(integer c) { if ((c & CHANGED_LINK) && llGetObjectPrimCount(llGetKey()) != PRIMS) llResetScript(); } // sitters are links too
touch_start(integer n)
{
key who = llDetectedKey(0);
if (target == who) { target = NULL_KEY; llWhisper(0, "*sways in place, groaning softly*"); }
else { target = who; llWhisper(0, "Braaaains... *lurches after " + llGetDisplayName(who) + "*"); }
}
listen(integer ch, string name, key id, string msg)
{
msg = llToLower(llStringTrim(msg, STRING_TRIM));
if (msg == "come") target = id;
else if (msg == "stay") target = NULL_KEY;
else if (msg == "reset") llResetScript();
else if (msg == "groan") llSay(0, "Uuuuurrrrghhh... braaaaains...");
}
timer()
{
rotation rot;
// boost: thrusters fire, a short hover, a heavy landing
if (boostT0 > 0)
{
float t = llGetTime() - boostT0;
if (t < 0.25) { thrusters(TRUE); pos.z = baseZ - 0.08 * (t / 0.25); }
else if (t < 2.2) { float s = (t - 0.25) / 1.95; pos.z = baseZ + 1.2 * llSin(PI * s) - 0.08 * (1 - s); }
else if (t < 2.6) { thrusters(FALSE); pos.z = baseZ - 0.1 * llSin(PI * (t - 2.2) / 0.4); }
else { boostT0 = 0; pos.z = baseZ; }
rot = llEuler2Rot(<0, 0, yaw>);
if (t >= 0.25 && t < 2.2)
{
planted = [pos + (llList2Vector(homes, 0) + <0.03, 0, -0.42>) * rot, pos + (llList2Vector(homes, 1) + <-0.03, 0, -0.42>) * rot];
}
else if (t >= 2.2 && t < 2.3) planted = [home_world(0, rot), home_world(1, rot)];
pose(rot, 0, 0.5, 0.5);
return;
}
vector fwd = ;
float v = 0;
if (target != NULL_KEY)
{
list od = llGetObjectDetails(target, [OBJECT_POS]);
if (od == []) target = NULL_KEY;
else
{
vector to = llList2Vector(od, 0) - pos; to.z = 0;
float want = llAtan2(to.y, to.x);
float err = want - yaw + 0.45 * llSin(llGetTime() * 0.9) + 0.2 * llSin(llGetTime() * 2.3); // wandering line
while (err > PI) err -= TWO_PI;
while (err < -PI) err += TWO_PI;
float m = TURN * DT;
if (err > m) err = m; if (err < -m) err = -m;
yaw += err;
fwd = ;
if (llVecMag(to) > NEAR) { float c = llCos(want - yaw); if (c > 0) v = SPEED * c; }
}
}
if (swingLeg >= 0) pos += fwd * (v * DT); // the body only travels while a foot is swinging
rot = llEuler2Rot(<0, 0, yaw>);
integer i;
if (swingLeg >= 0 && llGetTime() - swingT0 >= SWING)
{
planted = llListReplaceList(planted, [swingTo], swingLeg, swingLeg);
lastLeg = swingLeg; swingLeg = -1;
}
if (swingLeg < 0)
{
integer leg = 1 - lastLeg; // alternate feet
vector h = llList2Vector(homes, leg) * rot + pos;
vector f = llList2Vector(planted, leg);
float lag = llVecDist(, );
float thresh = STEP; if (v == 0) thresh = 0.04;
if (lag > thresh || (v > 0))
{
swingLeg = leg; swingT0 = llGetTime(); swingFrom = f;
vector to = h + fwd * (v * SWING * 1.8);
swingTo = ;
}
}
float gz = (zof(llList2Vector(planted, 0)) + zof(llList2Vector(planted, 1))) / 2 + H;
float bob = 0; float wantRoll = 0;
if (swingLeg >= 0)
{
float s = (llGetTime() - swingT0) / SWING;
bob = -0.025 * llSin(PI * s);
if (swingLeg == 0) wantRoll = 0.2; else wantRoll = -0.2; // lurch over the standing leg
stride += (v * 3.5 * (1 - 2 * swingLeg) * llSin(PI * s) * 0.35 - stride) * 0.3;
}
else stride *= 0.6;
pos.z += (gz + bob - pos.z) * 0.5;
wantRoll += 0.08 * llSin(llGetTime() * 1.3); // never quite steady
roll += (wantRoll - roll) * 0.25;
float liftR = 0;
if (saluteT0 > 0)
{
float t = llGetTime() - saluteT0;
if (t < 2.5) liftR = 2.3 * llSin(PI * llFabs(t / 2.5) ) ; else saluteT0 = 0;
}
pose(llEuler2Rot() * rot, stride, 0, 0); // hunched forward a little
}
}