+float autocvar_cl_viewmodel_scale;
+
+bool autocvar_cl_bobmodel;
+float autocvar_cl_bobmodel_speed;
+float autocvar_cl_bobmodel_side;
+float autocvar_cl_bobmodel_up;
+
+float autocvar_cl_followmodel;
+float autocvar_cl_followmodel_speed = 0.3;
+float autocvar_cl_followmodel_limit = 1000;
+float autocvar_cl_followmodel_highpass1 = 0.05;
+float autocvar_cl_followmodel_highpass = 0.05;
+float autocvar_cl_followmodel_lowpass = 0.03;
+
+float autocvar_cl_leanmodel;
+float autocvar_cl_leanmodel_speed = 0.3;
+float autocvar_cl_leanmodel_limit = 1000;
+float autocvar_cl_leanmodel_highpass1 = 0.2;
+float autocvar_cl_leanmodel_highpass = 0.2;
+float autocvar_cl_leanmodel_lowpass = 0.05;
+
+#define avg_factor(avg_time) (1 - exp(-frametime / max(0.001, avg_time)))
+#define lowpass(value, frac, ref_store, ret) MACRO_BEGIN \
+{ \
+ ret = ref_store = ref_store * (1 - frac) + (value) * frac; \
+} MACRO_END
+
+#define lowpass_limited(value, frac, limit, ref_store, ret) MACRO_BEGIN \
+{ \
+ float __ignore; lowpass(value, frac, ref_store, __ignore); \
+ ret = ref_store = bound((value) - (limit), ref_store, (value) + (limit)); \
+} MACRO_END
+
+#define highpass(value, frac, ref_store, ret) MACRO_BEGIN \
+{ \
+ float __f = 0; lowpass(value, frac, ref_store, __f); \
+ ret = (value) - __f; \
+} MACRO_END
+
+#define highpass_limited(value, frac, limit, ref_store, ret) MACRO_BEGIN \
+{ \
+ float __f = 0; lowpass_limited(value, frac, limit, ref_store, __f); \
+ ret = (value) - __f; \
+} MACRO_END
+
+#define lowpass2(value, frac, ref_store, ref_out) MACRO_BEGIN \
+{ \
+ lowpass(value.x, frac, ref_store.x, ref_out.x); \
+ lowpass(value.y, frac, ref_store.y, ref_out.y); \
+} MACRO_END
+
+#define highpass2(value, frac, ref_store, ref_out) MACRO_BEGIN \
+{ \
+ highpass(value.x, frac, ref_store.x, ref_out.x); \
+ highpass(value.y, frac, ref_store.y, ref_out.y); \
+} MACRO_END
+
+#define highpass2_limited(value, frac, limit, ref_store, ref_out) MACRO_BEGIN \
+{ \
+ highpass_limited(value.x, frac, limit, ref_store.x, ref_out.x); \
+ highpass_limited(value.y, frac, limit, ref_store.y, ref_out.y); \
+} MACRO_END
+
+#define lowpass3(value, frac, ref_store, ref_out) MACRO_BEGIN \
+{ \
+ lowpass(value.x, frac, ref_store.x, ref_out.x); \
+ lowpass(value.y, frac, ref_store.y, ref_out.y); \
+ lowpass(value.z, frac, ref_store.z, ref_out.z); \
+} MACRO_END
+
+#define highpass3(value, frac, ref_store, ref_out) MACRO_BEGIN \
+{ \
+ highpass(value.x, frac, ref_store.x, ref_out.x); \
+ highpass(value.y, frac, ref_store.y, ref_out.y); \
+ highpass(value.z, frac, ref_store.z, ref_out.z); \
+} MACRO_END
+
+#define highpass3_limited(value, frac, limit, ref_store, ref_out) MACRO_BEGIN \
+{ \
+ highpass_limited(value.x, frac, limit, ref_store.x, ref_out.x); \
+ highpass_limited(value.y, frac, limit, ref_store.y, ref_out.y); \
+ highpass_limited(value.z, frac, limit, ref_store.z, ref_out.z); \
+} MACRO_END
+
+void viewmodel_animate(entity this)
+{
+ static float prevtime;
+ float frametime = (time - prevtime);
+ prevtime = time;
+
+ if (autocvar_chase_active) return;
+ if (STAT(HEALTH) <= 0) return;
+
+ entity view = CSQCModel_server2csqc(player_localentnum - 1);
+
+ bool clonground = !(view.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
+ static bool oldonground;
+ static float hitgroundtime;
+ static float lastongroundtime;
+ if (clonground)
+ {
+ float f = time; // cl.movecmd[0].time
+ if (!oldonground)
+ hitgroundtime = f;
+ lastongroundtime = f;
+ }
+ oldonground = clonground;
+
+
+ bool teleported = view.csqcmodel_teleported;
+
+ float frac;
+ if(autocvar_cl_followmodel)
+ {
+ vector gunorg = '0 0 0';
+ static vector gunorg_prev = '0 0 0';
+ static vector gunorg_highpass = '0 0 0';
+ static vector gunorg_adjustment_highpass;
+ static vector gunorg_adjustment_lowpass;
+
+ // if we teleported, clear the frametime... the lowpass will recover the previous value then
+ if (teleported)
+ {
+ // try to fix the first highpass; result is NOT
+ // perfect! TODO find a better fix
+ gunorg_prev = view_origin;
+ }
+
+ // for the gun origin, only keep the high frequency (non-DC) parts, which is "somewhat like velocity"
+ gunorg_highpass += gunorg_prev;
+ frac = avg_factor(autocvar_cl_followmodel_highpass1);
+ highpass3_limited(view_origin, frac, autocvar_cl_followmodel_limit, gunorg_highpass, gunorg);
+ gunorg_prev = view_origin;
+ gunorg_highpass -= gunorg_prev;
+
+ // calculate the RAW adjustment vectors
+ gunorg *= -autocvar_cl_followmodel_speed;
+
+ // perform highpass/lowpass on the adjustment vectors (turning velocity into acceleration!)
+ // trick: we must do the lowpass LAST, so the lowpass vector IS the final vector!
+ frac = avg_factor(autocvar_cl_followmodel_highpass);
+ highpass3(gunorg, frac, gunorg_adjustment_highpass, gunorg);
+ frac = avg_factor(autocvar_cl_followmodel_lowpass);
+ lowpass3(gunorg, frac, gunorg_adjustment_lowpass, gunorg);
+
+ vector v;
+ vector forward, right = '0 0 0', up = '0 0 0';
+ MAKEVECTORS(makevectors, view_angles, forward, right, up);
+ v.x = gunorg * forward;
+ v.y = gunorg * right * -1;
+ v.z = gunorg * up;
+ this.origin += v;
+ }
+
+ if(autocvar_cl_leanmodel)
+ {
+ vector gunangles = '0 0 0';
+ static vector gunangles_prev = '0 0 0';
+ static vector gunangles_highpass = '0 0 0';
+ static vector gunangles_adjustment_highpass;
+ static vector gunangles_adjustment_lowpass;
+
+ if (teleported)
+ gunangles_prev = view_angles;
+
+ // in the highpass, we _store_ the DIFFERENCE to the actual view angles...
+ gunangles_highpass += gunangles_prev;
+ PITCH(gunangles_highpass) += 360 * floor((PITCH(view_angles) - PITCH(gunangles_highpass)) / 360 + 0.5);
+ YAW(gunangles_highpass) += 360 * floor((YAW(view_angles) - YAW(gunangles_highpass)) / 360 + 0.5);
+ ROLL(gunangles_highpass) += 360 * floor((ROLL(view_angles) - ROLL(gunangles_highpass)) / 360 + 0.5);
+ frac = avg_factor(autocvar_cl_leanmodel_highpass1);
+ highpass2_limited(view_angles, frac, autocvar_cl_leanmodel_limit, gunangles_highpass, gunangles);
+ gunangles_prev = view_angles;
+ gunangles_highpass -= gunangles_prev;
+
+ PITCH(gunangles) *= -autocvar_cl_leanmodel_speed;
+ YAW(gunangles) *= -autocvar_cl_leanmodel_speed;
+
+ // we assume here: PITCH = 0, YAW = 1, ROLL = 2
+ frac = avg_factor(autocvar_cl_leanmodel_highpass);
+ highpass2(gunangles, frac, gunangles_adjustment_highpass, gunangles);
+ frac = avg_factor(autocvar_cl_leanmodel_lowpass);
+ lowpass2(gunangles, frac, gunangles_adjustment_lowpass, gunangles);
+
+ gunangles.x = -gunangles.x; // pitch was inverted, now that actually matters
+ this.angles += gunangles;
+ }
+
+ float xyspeed = bound(0, vlen(vec2(view.velocity)), 400);
+
+ // vertical view bobbing code
+ // TODO: cl_bob
+
+ // horizontal view bobbing code
+ // TODO: cl_bob2
+
+ // fall bobbing code
+ // causes the view to swing down and back up when touching the ground
+ // TODO: cl_bobfall
+
+ // gun model bobbing code
+ if (autocvar_cl_bobmodel)
+ {
+ // calculate for swinging gun model
+ // the gun bobs when running on the ground, but doesn't bob when you're in the air.
+ // Sajt: I tried to smooth out the transitions between bob and no bob, which works
+ // for the most part, but for some reason when you go through a message trigger or
+ // pick up an item or anything like that it will momentarily jolt the gun.
+ float bspeed;
+ float t = 1;
+ float s = time * autocvar_cl_bobmodel_speed;
+ if (clonground)
+ {
+ if (time - hitgroundtime < 0.2)
+ {
+ // just hit the ground, speed the bob back up over the next 0.2 seconds
+ t = time - hitgroundtime;
+ t = bound(0, t, 0.2);
+ t *= 5;
+ }
+ }
+ else
+ {
+ // recently left the ground, slow the bob down over the next 0.2 seconds
+ t = time - lastongroundtime;
+ t = 0.2 - bound(0, t, 0.2);
+ t *= 5;
+ }
+ bspeed = xyspeed * 0.01;
+ vector gunorg = '0 0 0';
+ gunorg.y += bspeed * autocvar_cl_bobmodel_side * autocvar_cl_viewmodel_scale * sin(s) * t;
+ gunorg.z += bspeed * autocvar_cl_bobmodel_up * autocvar_cl_viewmodel_scale * cos(s * 2) * t;
+
+ this.origin += gunorg;
+ }
+}
+
+.vector viewmodel_origin, viewmodel_angles;
+.float weapon_nextthink;
+.float weapon_eta_last;
+.float weapon_switchdelay;
+