From 53ca13d99c04cd9994b3ef53450cb290e471300e Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 1 Jun 2020 23:34:29 +1000 Subject: [PATCH] Rough implementation of MOVETYPE_PUSH in QuakeC --- qcsrc/common/physics/movetypes/_mod.inc | 1 + qcsrc/common/physics/movetypes/_mod.qh | 1 + qcsrc/common/physics/movetypes/all.inc | 1 + qcsrc/common/physics/movetypes/movetypes.qc | 90 +++++++- qcsrc/common/physics/movetypes/movetypes.qh | 2 + qcsrc/common/physics/movetypes/push.qc | 227 ++++++++++++++++++++ qcsrc/common/physics/movetypes/push.qh | 12 ++ qcsrc/lib/self.qh | 2 +- qcsrc/server/g_world.qc | 6 +- 9 files changed, 336 insertions(+), 6 deletions(-) create mode 100644 qcsrc/common/physics/movetypes/push.qc create mode 100644 qcsrc/common/physics/movetypes/push.qh diff --git a/qcsrc/common/physics/movetypes/_mod.inc b/qcsrc/common/physics/movetypes/_mod.inc index 5cb1d0bc4..4effcbd04 100644 --- a/qcsrc/common/physics/movetypes/_mod.inc +++ b/qcsrc/common/physics/movetypes/_mod.inc @@ -1,6 +1,7 @@ // generated file; do not modify #include #include +#include #include #include #include diff --git a/qcsrc/common/physics/movetypes/_mod.qh b/qcsrc/common/physics/movetypes/_mod.qh index 1b1241a0a..32ae3813c 100644 --- a/qcsrc/common/physics/movetypes/_mod.qh +++ b/qcsrc/common/physics/movetypes/_mod.qh @@ -1,6 +1,7 @@ // generated file; do not modify #include #include +#include #include #include #include diff --git a/qcsrc/common/physics/movetypes/all.inc b/qcsrc/common/physics/movetypes/all.inc index 70157d186..b34bd3f88 100644 --- a/qcsrc/common/physics/movetypes/all.inc +++ b/qcsrc/common/physics/movetypes/all.inc @@ -2,5 +2,6 @@ #include "walk.qc" #include "step.qc" #include "follow.qc" +#include "push.qc" #include "movetypes.qc" diff --git a/qcsrc/common/physics/movetypes/movetypes.qc b/qcsrc/common/physics/movetypes/movetypes.qc index 7894d14fd..d8320cc27 100644 --- a/qcsrc/common/physics/movetypes/movetypes.qc +++ b/qcsrc/common/physics/movetypes/movetypes.qc @@ -4,12 +4,12 @@ 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 /*|| mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH*/) { 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) @@ -18,6 +18,90 @@ 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) // 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.03125f; // 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; @@ -589,7 +673,7 @@ void _Movetype_Physics_Frame(entity this, float movedt) { 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; diff --git a/qcsrc/common/physics/movetypes/movetypes.qh b/qcsrc/common/physics/movetypes/movetypes.qh index 52610c58b..13867d1fb 100644 --- a/qcsrc/common/physics/movetypes/movetypes.qh +++ b/qcsrc/common/physics/movetypes/movetypes.qh @@ -94,6 +94,7 @@ const int UNSTICK_STUCK = 2; // 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); @@ -142,6 +143,7 @@ const int MOVETYPE_ANGLECLIP = 2; #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); diff --git a/qcsrc/common/physics/movetypes/push.qc b/qcsrc/common/physics/movetypes/push.qc new file mode 100644 index 000000000..288524896 --- /dev/null +++ b/qcsrc/common/physics/movetypes/push.qc @@ -0,0 +1,227 @@ +#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 + + FOREACH_ENTITY_RADIUS(0.5 * (this.absmin + this.absmax), 0.5 * vlen(this.absmax - this.absmin), true, + { + switch(it.move_movetype) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_FOLLOW: + case MOVETYPE_NOCLIP: + case MOVETYPE_FLY_WORLDONLY: + continue; + default: + break; + } + + if(it.owner == this || this.owner == it) + 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(it) || it.groundentity != this) + { + tracebox(it.origin, it.mins, it.maxs, it.origin, MOVE_NORMAL, it); + if(!trace_startsolid) + continue; + } + vector pivot = it.mins + 0.5 * it.maxs; + vector move; + + if(rotated) + { + vector org = it.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; + + it.moved_from = it.origin; + it.moved_fromangles = it.angles; + IL_PUSH(g_pushmove_moved, it); + + // physics objects need better collisions than this code can do + if(it.move_movetype == MOVETYPE_PHYSICS) + { + it.origin = it.origin + move; + _Movetype_LinkEdict(it, true); + continue; + } + + // try moving the contacted entity + int savesolid = it.solid; + it.solid = SOLID_NOT; + if(!_Movetype_PushEntity(it, move, true, false)) + { + // entity "check" got teleported + it.angles_y += trace_fraction * moveangle.y; + it.solid = savesolid; + continue; // pushed enough + } + // FIXME: turn players specially + it.angles_y += trace_fraction * moveangle.y; + it.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(it.move_movetype != MOVETYPE_WALK && (trace_fraction < 1 || it.groundentity != this)) + UNSET_ONGROUND(it); + + // if it is still inside the pusher, block + tracebox(it.origin, it.mins, it.maxs, it.origin, MOVE_NORMAL, it); + if(trace_startsolid) + { + if(_Movetype_NudgeOutOfSolid_PivotIsKnownGood(it, pivot)) + { + // hack to invoke all necessary movement triggers + vector move2 = '0 0 0'; + if(!_Movetype_PushEntity(it, move2, true, false)) + { + // entity "check" got teleported + continue; + } + // we could fix it + continue; + } + + // still inside pusher, so it's really blocked + + // fail the move + if(it.mins_x == it.maxs_x) + continue; + if(it.solid == SOLID_NOT || it.solid == SOLID_TRIGGER) + { + // corpse + it.mins_x = it.mins_y = 0; + it.maxs = it.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, + { + it.origin = it.moved_from; + it.angles = it.moved_fromangles; + _Movetype_LinkEdict(it, 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, it); + 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, dt); + } + + if(this.nextthink > oldltime && this.nextthink <= this.ltime) + { + this.nextthink = 0; + getthink(this)(this); + } +} diff --git a/qcsrc/common/physics/movetypes/push.qh b/qcsrc/common/physics/movetypes/push.qh new file mode 100644 index 000000000..f33e76191 --- /dev/null +++ b/qcsrc/common/physics/movetypes/push.qh @@ -0,0 +1,12 @@ +#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 diff --git a/qcsrc/lib/self.qh b/qcsrc/lib/self.qh index 4299c19cd..d5ddf202d 100644 --- a/qcsrc/lib/self.qh +++ b/qcsrc/lib/self.qh @@ -77,7 +77,7 @@ SELFWRAP(touch, void, (), (entity this, entity toucher), (this, other)) #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)) diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 511a448b4..90e796266 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -2064,12 +2064,12 @@ void Physics_Frame() 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_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH*/ || 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; @@ -2079,6 +2079,8 @@ void Physics_Frame() 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); -- 2.39.2