// generated file; do not modify
#include <common/physics/movetypes/follow.qc>
#include <common/physics/movetypes/movetypes.qc>
+#include <common/physics/movetypes/push.qc>
#include <common/physics/movetypes/step.qc>
#include <common/physics/movetypes/toss.qc>
#include <common/physics/movetypes/walk.qc>
// generated file; do not modify
#include <common/physics/movetypes/follow.qh>
#include <common/physics/movetypes/movetypes.qh>
+#include <common/physics/movetypes/push.qh>
#include <common/physics/movetypes/step.qh>
#include <common/physics/movetypes/toss.qh>
#include <common/physics/movetypes/walk.qh>
#include "walk.qc"
#include "step.qc"
#include "follow.qc"
+#include "push.qc"
#include "movetypes.qc"
void set_movetype(entity this, int mt)
{
this.move_movetype = mt;
- if (mt == MOVETYPE_PHYSICS || mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH) {
+ if (mt == MOVETYPE_PHYSICS) {
this.move_qcphysics = false;
}
if(!IL_CONTAINS(g_moveables, this))
IL_PUSH(g_moveables, this); // add it to the moveable entities list (even if it doesn't move!) logic: if an object never sets its movetype, we assume it never does anything notable
- this.movetype = (this.move_qcphysics) ? MOVETYPE_NONE : mt;
+ this.movetype = (this.move_qcphysics) ? MOVETYPE_QCENTITY : mt;
}
#elif defined(CSQC)
void set_movetype(entity this, int mt)
}
#endif
+bool _Movetype_NudgeOutOfSolid_PivotIsKnownGood(entity this, vector pivot) // SV_NudgeOutOfSolid_PivotIsKnownGood
+{
+ vector stuckorigin = this.origin;
+ vector goodmins = pivot, goodmaxs = pivot;
+ for(int bump = 0; bump < 6; bump++)
+ {
+ int coord = 2 - (bump >> 1);
+ int dir = (bump & 1);
+
+ for(int subbump = 0; ; ++subbump)
+ {
+ vector testorigin = stuckorigin;
+ if(dir)
+ {
+ // pushing maxs
+ switch(coord)
+ {
+ case 0: testorigin.x += this.maxs_x - goodmaxs.x; break;
+ case 1: testorigin.y += this.maxs_y - goodmaxs.y; break;
+ case 2: testorigin.z += this.maxs_z - goodmaxs.z; break;
+ }
+ }
+ else
+ {
+ // pushing mins
+ switch(coord)
+ {
+ case 0: testorigin.x += this.mins_x - goodmins.x; break;
+ case 1: testorigin.y += this.mins_y - goodmins.y; break;
+ case 2: testorigin.z += this.mins_z - goodmins.z; break;
+ }
+ }
+
+ tracebox(stuckorigin, goodmins, goodmaxs, testorigin, MOVE_NOMONSTERS, this);
+ if(trace_startsolid && trace_ent.solid == SOLID_BSP) // NOTE: this checks for bmodelstartsolid in the engine
+ {
+ // BAD BAD, can't fix that
+ return false;
+ }
+
+ if(trace_fraction >= 1)
+ break; // it WORKS!
+
+ if(subbump >= 10)
+ {
+ // BAD BAD, can't fix that
+ return false;
+ }
+
+ // we hit something... let's move out of it
+ vector move = trace_endpos - testorigin;
+ float nudge = (trace_plane_normal * move) + 0.03125; // FIXME cvar this constant
+ stuckorigin = stuckorigin + nudge * trace_plane_normal;
+ }
+
+ if(dir)
+ {
+ // pushing maxs
+ switch(coord)
+ {
+ case 0: goodmaxs.x = this.maxs_x; break;
+ case 1: goodmaxs.y = this.maxs_y; break;
+ case 2: goodmaxs.z = this.maxs_z; break;
+ }
+ }
+ else
+ {
+ // pushing mins
+ switch(coord)
+ {
+ case 0: goodmins.x = this.mins_x; break;
+ case 1: goodmins.y = this.mins_y; break;
+ case 2: goodmins.z = this.mins_z; break;
+ }
+ }
+ }
+
+ // WE WIN
+ this.origin = stuckorigin;
+
+ return true;
+}
+
void _Movetype_WallFriction(entity this, vector stepnormal) // SV_WallFriction
{
/*float d, i;
if(dt <= 0)
return 0;
- int blocked = 0;
+ int blockedflag = 0;
int i, j, numplanes = 0;
float time_left = dt, grav = 0;
vector push;
{
// we got teleported by a touch function
// let's abort the move
- blocked |= 8;
+ blockedflag |= 8;
break;
}
if(trace_plane_normal.z > 0.7)
{
// floor
- blocked |= 1;
+ blockedflag |= 1;
if(!trace_ent)
{
if(!_Movetype_PushEntity(this, steppush, true, false))
{
- blocked |= 8;
+ blockedflag |= 8;
break;
}
if(!_Movetype_PushEntity(this, push, true, false))
{
- blocked |= 8;
+ blockedflag |= 8;
break;
}
float trace2_fraction = trace_fraction;
steppush = vec3(0, 0, org.z - this.origin_z);
if(!_Movetype_PushEntity(this, steppush, true, false))
{
- blocked |= 8;
+ blockedflag |= 8;
break;
}
else
{
// step - return it to caller
- blocked |= 2;
+ blockedflag |= 2;
// save the trace for player extrafriction
if(applystepnormal)
move_stepnormal = trace_plane_normal;
{
// this shouldn't really happen
this.velocity = '0 0 0';
- blocked = 3;
+ blockedflag = 3;
break;
}
if(numplanes != 2)
{
this.velocity = '0 0 0';
- blocked = 7;
+ blockedflag = 7;
break;
}
vector dir = cross(planes[0], planes[1]);
}
// LordHavoc: this came from QW and allows you to get out of water more easily
- if(GAMEPLAYFIX_EASIERWATERJUMP(this) && (this.flags & FL_WATERJUMP) && !(blocked & 8))
+ if(GAMEPLAYFIX_EASIERWATERJUMP(this) && (this.flags & FL_WATERJUMP) && !(blockedflag & 8))
this.velocity = primal_velocity;
- if(PHYS_WALLCLIP(this) && this.pm_time && !(this.flags & FL_WATERJUMP) && !(blocked & 8))
+ if(PHYS_WALLCLIP(this) && this.pm_time && !(this.flags & FL_WATERJUMP) && !(blockedflag & 8))
this.velocity = primal_velocity;
if(applygravity)
}
}
- return blocked;
+ return blockedflag;
}
void _Movetype_CheckVelocity(entity this) // SV_CheckVelocity
return (this.origin == last_origin); // false if teleported by touch
}
-
-.float ltime;
-.void() blocked;
-
void _Movetype_Physics_Frame(entity this, float movedt)
{
this.move_didgravity = -1;
{
case MOVETYPE_PUSH:
case MOVETYPE_FAKEPUSH:
- LOG_DEBUG("Physics: Lacking QuakeC support for Push movetype, FIX ME by using engine physics!");
+ _Movetype_Physics_Push(this, movedt);
break;
case MOVETYPE_NONE:
break;
// set by _Movetype_FlyMove
vector move_stepnormal;
+bool _Movetype_NudgeOutOfSolid_PivotIsKnownGood(entity this, vector pivot);
void _Movetype_WallFriction(entity this, vector stepnormal);
int _Movetype_FlyMove(entity this, float dt, bool applygravity, bool applystepnormal, float stepheight);
void _Movetype_CheckVelocity(entity this);
#endif
const int MOVETYPE_QCPLAYER = 150; // QC-driven player physics, no think functions!
+const int MOVETYPE_QCENTITY = 151; // QC-driven entity physics, some think functions!
const int FL_ONSLICK = BIT(20);
--- /dev/null
+#include "push.qh"
+void _Movetype_PushMove(entity this, float dt) // SV_PushMove
+{
+ if(this.velocity == '0 0 0' && this.avelocity == '0 0 0')
+ {
+ this.ltime += dt;
+ return;
+ }
+
+ switch(this.solid)
+ {
+ // LadyHavoc: valid pusher types
+ case SOLID_BSP:
+ case SOLID_BBOX:
+ case SOLID_SLIDEBOX:
+ case SOLID_CORPSE: // LadyHavoc: this would be weird...
+ break;
+ // LadyHavoc: no collisions
+ case SOLID_NOT:
+ case SOLID_TRIGGER:
+ {
+ this.origin = this.origin + dt * this.velocity;
+ this.angles = this.angles + dt * this.avelocity;
+ this.angles_x -= 360.0 * floor(this.angles_x * (1.0 / 360.0));
+ this.angles_y -= 360.0 * floor(this.angles_y * (1.0 / 360.0));
+ this.angles_z -= 360.0 * floor(this.angles_z * (1.0 / 360.0));
+ this.ltime += dt;
+ _Movetype_LinkEdict(this, false);
+ return;
+ }
+ default:
+ {
+ LOG_INFOF("_Movetype_Physics_Push: entity #%d, unrecognized solid type %d", etof(this), this.solid);
+ return;
+ }
+ }
+ if(!this.modelindex)
+ {
+ LOG_INFOF("_Movetype_Physics_Push: entity #%d has an invalid modelindex %d", etof(this), this.modelindex);
+ return;
+ }
+
+ bool rotated = ((vlen2(this.angles) + vlen2(this.avelocity)) > 0);
+
+ vector move1 = this.velocity * dt;
+ vector moveangle = this.avelocity * dt;
+
+ vector a = -moveangle;
+ vector forward, left, up;
+ MAKE_VECTORS(a, forward, left, up);
+ left *= -1; // actually make it left!
+
+ vector pushorig = this.origin;
+ vector pushang = this.angles;
+ float pushltime = this.ltime;
+
+ // move the pusher to its final position
+
+ this.origin = this.origin + dt * this.velocity;
+ this.angles = this.angles + dt * this.avelocity;
+ this.ltime += dt;
+ _Movetype_LinkEdict(this, false); // pulls absmin/absmax from the engine
+
+ if(this.move_movetype == MOVETYPE_FAKEPUSH) // Tenebrae's MOVETYPE_PUSH variant that doesn't push...
+ {
+ this.angles_x -= 360.0 * floor(this.angles_x * (1.0 / 360.0));
+ this.angles_y -= 360.0 * floor(this.angles_y * (1.0 / 360.0));
+ this.angles_z -= 360.0 * floor(this.angles_z * (1.0 / 360.0));
+ return;
+ }
+
+ IL_CLEAR(g_pushmove_moved); // make sure it's not somehow uncleared
+
+ for(entity check = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); check; check = check.chain)
+ {
+ switch(check.move_movetype)
+ {
+ case MOVETYPE_NONE:
+ case MOVETYPE_PUSH:
+ case MOVETYPE_FOLLOW:
+ case MOVETYPE_NOCLIP:
+ case MOVETYPE_FLY_WORLDONLY:
+ continue;
+ default:
+ break;
+ }
+
+ if(check.owner == this || this.owner == check)
+ continue;
+
+ // if the entity is standing on the pusher, it will definitely be moved
+ // if the entity is not standing on the pusher, but is in the pusher's
+ // final position, move it
+ if (!IS_ONGROUND(check) || check.groundentity != this)
+ {
+ tracebox(check.origin, check.mins, check.maxs, check.origin, MOVE_NOMONSTERS, check);
+ if(!trace_startsolid)
+ continue;
+ }
+ vector pivot = check.mins + 0.5 * (check.maxs - check.mins);
+ vector move;
+
+ if(rotated)
+ {
+ vector org = check.origin - this.origin;
+ org = org + pivot;
+
+ vector org2;
+ org2.x = (org * forward);
+ org2.y = (org * left);
+ org2.z = (org * up);
+ move = org2 - org;
+ move = move + move1;
+ }
+ else
+ move = move1;
+
+ check.moved_from = check.origin;
+ check.moved_fromangles = check.angles;
+ IL_PUSH(g_pushmove_moved, check);
+
+ // physics objects need better collisions than this code can do
+ if(check.move_movetype == MOVETYPE_PHYSICS)
+ {
+ check.origin = check.origin + move;
+ _Movetype_LinkEdict(check, true);
+ continue;
+ }
+
+ // try moving the contacted entity
+ int savesolid = this.solid;
+ this.solid = SOLID_NOT;
+ if(!_Movetype_PushEntity(check, move, true, true))
+ {
+ // entity "check" got teleported
+ check.angles_y += trace_fraction * moveangle.y;
+ this.solid = savesolid;
+ continue; // pushed enough
+ }
+ // FIXME: turn players specially
+ check.angles_y += trace_fraction * moveangle.y;
+ this.solid = savesolid;
+
+ // this trace.fraction < 1 check causes items to fall off of pushers
+ // if they pass under or through a wall
+ // the groundentity check causes items to fall off of ledges
+ if(check.move_movetype != MOVETYPE_WALK && (trace_fraction < 1 || check.groundentity != this))
+ UNSET_ONGROUND(check);
+
+ // if it is still inside the pusher, block
+ tracebox(check.origin, check.mins, check.maxs, check.origin, MOVE_NOMONSTERS, check);
+ if(trace_startsolid)
+ {
+ if(_Movetype_NudgeOutOfSolid_PivotIsKnownGood(check, pivot))
+ {
+ // hack to invoke all necessary movement triggers
+ _Movetype_PushEntity(check, '0 0 0', true, true);
+ // we could fix it or entity "check" was telported
+ continue;
+ }
+
+ // still inside pusher, so it's really blocked
+
+ // fail the move
+ if(check.mins_x == check.maxs_x)
+ continue;
+ if(check.solid == SOLID_NOT || check.solid == SOLID_TRIGGER)
+ {
+ // corpse
+ check.mins_x = check.mins_y = 0;
+ check.maxs = check.mins;
+ continue;
+ }
+
+ this.origin = pushorig;
+ this.angles = pushang;
+ this.ltime = pushltime;
+ _Movetype_LinkEdict(this, false);
+
+ // move back any entities we already moved
+ IL_EACH(g_pushmove_moved, true,
+ {
+ check.origin = check.moved_from;
+ check.angles = check.moved_fromangles;
+ _Movetype_LinkEdict(check, false);
+ });
+
+ // if the pusher has a "blocked" function, call it, otherwise just stay in place until the obstacle is gone
+ if(getblocked(this))
+ getblocked(this)(this, check);
+ break;
+ }
+ }
+ this.angles_x -= 360.0 * floor(this.angles_x * (1.0 / 360.0));
+ this.angles_y -= 360.0 * floor(this.angles_y * (1.0 / 360.0));
+ this.angles_z -= 360.0 * floor(this.angles_z * (1.0 / 360.0));
+ IL_CLEAR(g_pushmove_moved); // clean up
+}
+
+void _Movetype_Physics_Push(entity this, float dt) // SV_Physics_Pusher
+{
+ float oldltime = this.ltime;
+ float movetime = dt;
+ if(this.nextthink < this.ltime + dt)
+ {
+ movetime = this.nextthink - this.ltime;
+ if(movetime < 0)
+ movetime = 0;
+ }
+
+ if(movetime)
+ {
+ // advances this.ltime if not blocked
+ _Movetype_PushMove(this, movetime);
+ }
+
+ if(this.nextthink > oldltime && this.nextthink <= this.ltime)
+ {
+ this.nextthink = 0;
+ getthink(this)(this);
+ }
+}
--- /dev/null
+#pragma once
+
+void _Movetype_Physics_Push(entity this, float dt);
+
+.vector moved_from, moved_fromangles;
+
+IntrusiveList g_pushmove_moved;
+STATIC_INIT(g_pushmove_moved) { g_pushmove_moved = IL_NEW(); }
+
+#ifdef CSQC
+.float ltime;
+#endif
#ifdef GAMEQC
SELFWRAP(blocked, void, (), (entity this, entity blocker), (this, other))
#define setblocked(e, f) SELFWRAP_SET(blocked, e, f)
-#define blocked stopusingthis
+#define getblocked(e) SELFWRAP_GET(blocked, e)
#endif
SELFWRAP(predraw, void, (), (entity this), (this))
IL_EACH(g_moveables, true,
{
- if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH || it.move_movetype == MOVETYPE_PHYSICS)
+ if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_PHYSICS)
continue;
//set_movetype(it, it.move_movetype);
// inline the set_movetype function, since this is called a lot
- it.movetype = (it.move_qcphysics) ? MOVETYPE_NONE : it.move_movetype;
+ it.movetype = (it.move_qcphysics) ? MOVETYPE_QCENTITY : it.move_movetype;
- if(it.move_movetype == MOVETYPE_NONE)
- continue;
-
- if(it.move_qcphysics)
+ if(it.move_qcphysics && it.move_movetype != MOVETYPE_NONE)
Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
if(it.movetype >= MOVETYPE_USER_FIRST && it.movetype <= MOVETYPE_USER_LAST) // these cases have no think handling
{
+ if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH)
+ continue; // these movetypes have no regular think function
// handle thinking here
if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
RunThink(it);
IL_EACH(g_moveables, it.move_qcphysics,
{
- if(IS_CLIENT(it) || is_pure(it) || it.classname == "" || it.move_movetype == MOVETYPE_NONE)
+ if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_NONE)
continue;
Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
});