QCCFLAGS_WATERMARK ?= $(shell git describe --tags --dirty='~')
VER = $(subst *,\*,$(QCCFLAGS_WATERMARK))
NDEBUG ?= 1
-XONOTIC ?= 1
+XONOTIC ?= 0
ENABLE_EFFECTINFO ?= 0
ENABLE_DEBUGDRAW ?= 0
ENABLE_DEBUGTRACE ?= 0
-std=gmqcc \
-Ooverlap-locals \
-O3 \
- $(QCCFLAGS_WERROR) \
-Wall \
$(QCCFLAGS_WTFS) \
-flno -futf8 -fno-bail-on-werror \
#include <lib/warpzone/server.qc>
#include <lib/warpzone/util_server.qc>
-#include <ecs/_mod.inc>
#endif
+#include <ecs/_mod.inc>
#ifdef BUILD_MOD
#include <mod/client/progs.inc>
// generated file; do not modify
#include <ecs/main.qc>
+#ifdef CSQC
+ #include <ecs/cl_main.qc>
+#endif
+#ifdef SVQC
+ #include <ecs/sv_main.qc>
+#endif
#include <ecs/components/_mod.inc>
#include <ecs/events/_mod.inc>
--- /dev/null
+#if !XONOTIC
+ entity me;
+
+ /** 6 bits provides up to 64 remembered states */
+ const int SNAP_BITS = 13;
+ .int snap;
+ .float times[1 << SNAP_BITS];
+
+ void CSQC_Init()
+ {
+ entity it = me = spawn();
+ it.mins = '-16 -16 -24';
+ it.maxs = '+16 +16 +45';
+ it.com_phys = true;
+ it.com_phys_nogravityonground = true;
+ it.com_phys_stepheight = 31;
+ it.com_phys_jumpvel = 260;
+ it.com_phys_friction = 6;
+ //it.com_phys_gravity = '0 0 800';
+ it.com_phys_noclip = true;
+
+ it.com_phys_pos = '0 0 200';
+ }
+
+ void CSQC_Ent_Update(entity this, bool isnew)
+ {
+ int id = ReadByte();
+ FOREACH(LinkedEntities, it.m_id == id, {
+ if (isnew) this.classname = it.netname;
+ it.m_read(this, NULL, isnew);
+ break;
+ });
+ }
+
+ void rec()
+ {
+ me.ARRAY_INDEX(float, times, me.snap) = time;
+ // me.times[me.snap] = time;
+ me.snap = (me.snap + 1) & BITS(SNAP_BITS);
+ }
+
+ NET_HANDLE(ENT_OBJECT, bool isnew)
+ {
+ if (isnew)
+ {
+ this.com_phys = true;
+ precache_model("models/player/erebus.iqm");
+ _setmodel(this, "models/player/erebus.iqm");
+ this.drawmask = MASK_NORMAL;
+ }
+ this.com_phys_pos_prev = this.com_phys_pos;
+ this.com_phys_ang_prev = this.com_phys_ang;
+ serialize(ENT_OBJECT, 0, this);
+ this.com_phys_pos = this.origin;
+ this.com_phys_ang = this.angles;
+ return true;
+ }
+
+ void CSQC_UpdateView(entity this, float w, float h)
+ {
+ entity it = me;
+ it.com_in_move = input_movevalues;
+ it.com_in_angles = input_angles;
+ it.com_in_jump = input_buttons & BIT(1);
+ makevectors(it.com_in_angles);
+ vector dir = normalize(it.com_in_move);
+ vector upvec = '0 0 1';
+ vector vel = (v_forward * dir.x + v_right * dir.y + upvec * dir.z);
+ if (!it.com_phys_noclip) vel = vec_reflect(vel, upvec, 0);
+ vel = normalize(vel);
+ vel *= 360 * frametime * 8;
+ it.com_phys_vel += vel;
+
+ systems_update();
+
+ setproperty(VF_ORIGIN, it.origin + '0 0 35');
+ addentities(MASK_NORMAL); // .drawmask
+ renderscene();
+ clearscene();
+
+ Net_Flush();
+ IL_ENDFRAME();
+ }
+#endif
+
\ No newline at end of file
.bool com_phys_water;
.bool com_phys_friction_air;
.bool move_qcphysics;
+
+.bool com_phys_nogravityonground;
+.float com_phys_stepheight;
+.float com_phys_jumpvel;
+.float com_phys_bounce;
+.bool com_phys_noclip;
#pragma once
EVENT(phys_land, (entity this));
+
+EVENT(phys_stepfall, (entity this));
+EVENT(phys_stepland, (entity this));
#include "lib.qh"
+REGISTER_NET_LINKED(ENT_OBJECT);
+
+#define serialize_ENT_OBJECT(stream, this) \
+ MACRO_BEGIN \
+ serialize_vector(stream, this.origin); \
+ serialize_vector(stream, this.angles); \
+ MACRO_END
+
void systems_update();
--- /dev/null
+#if !XONOTIC
+// interpolating with the engine:
+// it.movetype = MOVETYPE_STEP; // engine only interpolates this movetype
+// it.flags |= FL_FLY; // don't apply forces
+// it.solid = SOLID_NOT; // don't make stepping sounds
+
+entity testent;
+
+bool net_object_send(entity this, entity to, int sendflags)
+{
+ WriteHeader(MSG_ENTITY, ENT_OBJECT);
+ serialize(ENT_OBJECT, MSG_ENTITY, this);
+ return true;
+}
+
+spawnfunc(worldspawn)
+{
+ static_init();
+ static_init_late();
+ // static_init_precache();
+
+ entity it = testent = spawn();
+ precache_model("models/player/erebus.iqm");
+ _setmodel(it, "models/player/erebus.iqm");
+
+ //Net_LinkEntity(it, true, 0, net_object_send);
+
+ __spawnfunc_spawn_all();
+}
+
+void PlayerPreThink(entity this)
+{
+ // the truth: time
+ // what the client knows: time - this.ping
+ // for fairness, don't compensate shots beyond 400ms
+ // what the client sees: _ - this.cl_interp;
+}
+
+void StartFrame()
+{
+ float f = 1 / autocvar_xon_sys_phys_dt;
+ float n = 5;
+ float x = ((floor(time * f) / f) % n) / n;
+ vector norg = '0 1 0' * x * 500;
+ if (norg != testent.origin)
+ {
+ if (!norg) testent.effects |= EF_TELEPORT_BIT;
+ testent.SendFlags |= 1;
+ setorigin(testent, norg);
+ testent.angles = '0 -360 0' * x;
+ // testent.origin = norg;
+ }
+}
+#endif
#include "physics.qh"
+#if XONOTIC
void sys_phys_fix(entity this, float dt)
{
this.team = myteam + 1; // is this correct?
void sys_phys_spectator_control(entity this) {}
void sys_phys_fixspeed(entity this, float maxspeed_mod) {}
+#endif
void sys_in_update(entity this, float dt)
{
+#if XONOTIC
this.com_in_jump = PHYS_INPUT_BUTTON_JUMP(this);
this.com_in_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
+#endif
}
#include "physics.qh"
#include "input.qh"
+#if XONOTIC
.int disableclientprediction;
void sys_phys_simulate(entity this, float dt);
{
sys_phys_simulate_simple(this, frametime);
}
+
+#else
+const int PHYSICS_TRACE_PLANE_MAX = 5;
+
+// NOTE: currently unsuitable for players
+void sys_phys_update(entity this, float dt)
+{
+ // x: { 60: 0.5, 45: ~0.7, 30: ~0.9 }, from: 'vec2(0, 1) * vec2(cos(90 - x), sin(90 - x))'
+ // read as 'within x degrees'
+ float maxgrounddot = 0.5;
+ float maxstepdot = 0.7;
+ vector upvec = '0 0 1';
+ float groundsnap = 1;
+ bool jump = this.com_in_jump;
+ bool jumpstep = true;
+
+ vector mn = this.mins;
+ vector mx = this.maxs;
+
+ vector acc = this.com_phys_acc;
+ vector vel = this.com_phys_vel;
+ vector pos = this.com_phys_pos_prev = this.com_phys_pos;
+ bool onground = this.com_phys_ground;
+
+ bool nogravityonground = this.com_phys_nogravityonground;
+ float stepheight = this.com_phys_stepheight;
+ float stepdownheight = -stepheight;
+ float jumpvel = this.com_phys_jumpvel;
+ float bounce = this.com_phys_bounce;
+ float friction = this.com_phys_friction;
+ float gravity = this.com_phys_gravity.z;
+ bool noclip = this.com_phys_noclip;
+ if (noclip)
+ {
+ jump = false;
+ nogravityonground = false;
+ }
+
+ vector g = upvec * -gravity;
+
+ // apply accelaration in two steps: https://www.niksula.hut.fi/~hkankaan/Homepages/gravity.html
+ // alternatives: rk4, verlet, euler
+ vel += (acc + g) * dt / 2;
+ {
+ if (onground || noclip)
+ {
+ if (nogravityonground)
+ {
+ g = '0 0 0';
+ if (vel * upvec < 0) vel = vec_reflect(vel, upvec, 0); // kill downward velocity
+ }
+ if (jump)
+ {
+ vel += upvec * jumpvel;
+ }
+ else // the first landing frame is free
+ {
+ // friction
+ vector slide = noclip ? vel : vec_reflect(vel, upvec, 0);
+ vector push = vel - slide;
+ // TODO: slick
+ slide *= 1 - friction * dt;
+ vel = slide + push;
+ }
+ }
+ vector step = vel * dt;
+ bool pass = false;
+ bool foundground = false; // assume until proven otherwise
+ if (nogravityonground) foundground = true; // override
+ bool steplimit = 1;
+ if (noclip)
+ {
+ pass = true;
+ }
+ else
+ {
+ for (int i = 0; i < PHYSICS_TRACE_PLANE_MAX; ++i)
+ {
+ vector p0 = pos;
+ vector p1 = p0 + step;
+ tracebox(p0, mn, mx, p1, MOVE_NORMAL, this);
+ float frac = trace_fraction;
+ vector norm = trace_plane_normal;
+ if (frac == 1)
+ {
+ // all clear
+ if (steplimit > 0 && onground && vel * upvec <= 0)
+ {
+ // can we step down?
+ tracebox(p1, mn, mx, p1 + upvec * stepdownheight, MOVE_NORMAL, this);
+ if (trace_fraction == 1)
+ {
+ // no stairs here
+ }
+ else if (trace_plane_normal * upvec >= maxstepdot)
+ {
+ // step down
+ step += upvec * (stepdownheight * trace_fraction);
+ }
+ }
+ pass = true;
+ break;
+ }
+ // hit something
+ if (norm * upvec >= maxgrounddot) foundground = true;
+ if (steplimit > 0 && (jumpstep || onground)) // try: vel * upvec >= 0
+ {
+ // can we step up?
+ vector slide = vec_reflect(step, upvec, 0); // remove fall component
+ vector p1 = p0 + slide; // step is here
+ tracebox(p1 + upvec * stepheight, mn, mx, p1, MOVE_NORMAL, this);
+ if (trace_fraction < 1 && trace_plane_normal * upvec >= maxstepdot)
+ {
+ // there is a step in front of us, get above it
+ // TODO: not if it's slippery (slick)
+ vector stepup = upvec * (1 - trace_fraction) * stepheight;
+ tracebox(p0, mn, mx, p0 + stepup, MOVE_NORMAL, this);
+ if (trace_fraction == 1)
+ {
+ // go over
+ tracebox(p0 + stepup, mn, mx, p1 + stepup, MOVE_NORMAL, this);
+ if (trace_fraction == 1)
+ {
+ // all clear
+ steplimit -= 1;
+ pos += stepup;
+ if (vel * upvec < 0) vel = vec_reflect(vel, upvec, 0); // kill downward velocity
+ step = p1 - p0;
+ pass = true;
+ break;
+ }
+ }
+ }
+ }
+ // no stairs here
+ pos += frac * step;
+ vel = vec_reflect(vel, norm, bounce);
+ step = (1 - frac) * vel * dt;
+ continue;
+ }
+ }
+ if (nogravityonground)
+ {
+ vector p1 = pos + step;
+ tracebox(p1, mn, mx, p1 - groundsnap * upvec, MOVE_NORMAL, this);
+ foundground = trace_plane_normal * upvec >= maxgrounddot;
+ }
+ if (pass)
+ {
+ pos += step;
+ if (!foundground)
+ {
+ if (onground) emit(phys_stepfall, this);
+ }
+ else
+ {
+ if (!onground) emit(phys_stepland, this);
+ }
+ onground = foundground;
+ }
+ }
+ vel += (acc + g) * dt / 2;
+
+ this.com_phys_acc = acc;
+ this.com_phys_vel = vel;
+ this.com_phys_pos = pos;
+ this.com_phys_ground = onground;
+}
+#endif
#include "physics.qh"
+#if XONOTIC
void sys_phys_fix(entity this, float dt)
{
WarpZone_PlayerPhysics_FixVAngle(this);
entity listener = new_pure(sys_phys);
subscribe(listener, phys_land, sys_phys_land);
}
+#endif
#include <lib/warpzone/server.qc>
#include <lib/warpzone/util_server.qc>
-#include <ecs/_mod.inc>
#endif
+#include <ecs/_mod.inc>
#ifdef BUILD_MOD
#include <mod/server/progs.inc>