@@ -57,6 +57,13 @@ qboolean sv_analyticphysics_frame = true; // sv_analyticphysics latched per SV_P
5757
5858#define MOVE_EPSILON 0.01
5959
60+ // Upper bound on how deep contact noise can put an entity inside the surface it
61+ // rests on: the collision trace places contacts up to DIST_EPSILON off the plane,
62+ // and coordinate rounding at map scale adds at most 2^-10. A sweep started this
63+ // far above the resting position is therefore guaranteed to begin outside the
64+ // ground surface; any overlap that survives it is real interpenetration.
65+ #define PUSH_CONTACT_EPSILON (2 * DIST_EPSILON)
66+
6067static void SV_Physics_Toss (edict_t * ent );
6168
6269// For usage by SV_PushMove, allocate at max possible size,
@@ -659,27 +666,64 @@ PUSHMOVE
659666===============================================================================
660667*/
661668
669+ cvar_t sv_gameplayfix_elevators = {"sv_gameplayfix_elevators" , "2" , CVAR_ARCHIVE }; // 0=off; 1=clients only; 2=all entities
670+
671+ static qboolean SV_GameplayFixElevators (edict_t * ent )
672+ {
673+ return sv_gameplayfix_elevators .value >= 2.f || (sv_gameplayfix_elevators .value && NUM_FOR_EDICT (ent ) <= svs .maxclients );
674+ }
675+
676+ static trace_t SV_PushEntityMove (edict_t * ent , vec3_t start , vec3_t end )
677+ {
678+ if (ent -> v .movetype == MOVETYPE_FLYMISSILE )
679+ return SV_Move (start , ent -> v .mins , ent -> v .maxs , end , MOVE_MISSILE , ent );
680+ else if (ent -> v .solid == SOLID_TRIGGER || ent -> v .solid == SOLID_NOT )
681+ // only clip against bmodels
682+ return SV_Move (start , ent -> v .mins , ent -> v .maxs , end , MOVE_NOMONSTERS , ent );
683+ else
684+ return SV_Move (start , ent -> v .mins , ent -> v .maxs , end , MOVE_NORMAL , ent );
685+ }
686+
662687/*
663688============
664- SV_PushEntity
689+ SV_PushEntityTo
665690
666691Does not change the entities velocity at all
667692============
668693*/
669- static trace_t SV_PushEntity (edict_t * ent , vec3_t push )
694+ static trace_t SV_PushEntityTo (edict_t * ent , vec3_t end )
670695{
671696 trace_t trace ;
672- vec3_t end ;
673697
674- VectorAdd (ent -> v .origin , push , end );
698+ trace = SV_PushEntityMove (ent , ent -> v .origin , end );
699+
700+ // a move that starts inside a solid registers no impact against it, so the
701+ // entity would glide through the brush and fall out the far side. Against
702+ // the pusher the entity rests on such an overlap is contact noise, bounded
703+ // by PUSH_CONTACT_EPSILON: a single sweep against the pusher from that far
704+ // above is guaranteed to start outside and stops at the exact contact
705+ // coordinate. Place the entity there and redo the move so it collides
706+ // normally; if even the sweep start is inside, the overlap is real and
707+ // vanilla behavior applies.
708+ if (trace .startsolid && ent -> v .groundentity && SV_GameplayFixElevators (ent ))
709+ {
710+ edict_t * ground = PROG_TO_EDICT (ent -> v .groundentity );
711+ if (ground != qcvm -> edicts && !ground -> free && ground -> v .movetype == MOVETYPE_PUSH && ground -> v .solid == SOLID_BSP &&
712+ SV_ClipMoveToEntity (ground , ent -> v .origin , ent -> v .mins , ent -> v .maxs , ent -> v .origin , CONTENTMASK_ANYSOLID ).startsolid )
713+ {
714+ vec3_t above ;
715+ trace_t exit ;
675716
676- if (ent -> v .movetype == MOVETYPE_FLYMISSILE )
677- trace = SV_Move (ent -> v .origin , ent -> v .mins , ent -> v .maxs , end , MOVE_MISSILE , ent );
678- else if (ent -> v .solid == SOLID_TRIGGER || ent -> v .solid == SOLID_NOT )
679- // only clip against bmodels
680- trace = SV_Move (ent -> v .origin , ent -> v .mins , ent -> v .maxs , end , MOVE_NOMONSTERS , ent );
681- else
682- trace = SV_Move (ent -> v .origin , ent -> v .mins , ent -> v .maxs , end , MOVE_NORMAL , ent );
717+ VectorCopy (ent -> v .origin , above );
718+ above [2 ] += PUSH_CONTACT_EPSILON ;
719+ exit = SV_ClipMoveToEntity (ground , above , ent -> v .mins , ent -> v .maxs , ent -> v .origin , CONTENTMASK_ANYSOLID );
720+ if (!exit .startsolid && exit .fraction < 1 )
721+ {
722+ VectorCopy (exit .endpos , ent -> v .origin );
723+ trace = SV_PushEntityMove (ent , ent -> v .origin , end );
724+ }
725+ }
726+ }
683727
684728 if (trace .ent )
685729 assert_always (!trace .ent -> free );
@@ -696,12 +740,26 @@ static trace_t SV_PushEntity (edict_t *ent, vec3_t push)
696740 return trace ;
697741}
698742
743+ /*
744+ ============
745+ SV_PushEntity
746+
747+ Does not change the entities velocity at all
748+ ============
749+ */
750+ static trace_t SV_PushEntity (edict_t * ent , vec3_t push )
751+ {
752+ vec3_t end ;
753+
754+ VectorAdd (ent -> v .origin , push , end );
755+ return SV_PushEntityTo (ent , end );
756+ }
757+
699758/*
700759============
701760SV_PushMove
702761============
703762*/
704- cvar_t sv_gameplayfix_elevators = {"sv_gameplayfix_elevators" , "2" , CVAR_ARCHIVE }; // 0=off; 1=clients only; 2=all entities
705763
706764static void SV_PushMove (edict_t * pusher , float movetime )
707765{
@@ -724,9 +782,29 @@ static void SV_PushMove (edict_t *pusher, float movetime)
724782 return ;
725783 }
726784
785+ // evaluate the new position analytically from the anchor captured when this
786+ // leg's velocity was set: position error stays at a couple of ulps regardless
787+ // of leg length and the final partial step lands on the leg destination,
788+ // instead of accumulating velocity*movetime rounding every tick. The last*
789+ // mismatch cases (QC touched origin or ltime, or a blocked move was reverted)
790+ // re-anchor at the current state, which degrades to the vanilla incremental
791+ // behavior for that stretch.
792+ if (!pusher -> push_anchor_valid || !VectorCompare (pusher -> v .velocity , pusher -> push_anchor_velocity ) ||
793+ !VectorCompare (pusher -> v .origin , pusher -> push_anchor_lastorigin ) || pusher -> v .ltime != pusher -> push_anchor_lastltime )
794+ {
795+ VectorCopy (pusher -> v .origin , pusher -> push_anchor_origin );
796+ VectorCopy (pusher -> v .velocity , pusher -> push_anchor_velocity );
797+ pusher -> push_anchor_ltime = pusher -> v .ltime ;
798+ pusher -> push_anchor_valid = true;
799+ }
800+
801+ const float newltime = pusher -> v .ltime + movetime ;
802+ vec3_t neworigin ;
803+
727804 for (i = 0 ; i < 3 ; i ++ )
728805 {
729- move [i ] = pusher -> v .velocity [i ] * movetime ;
806+ neworigin [i ] = pusher -> push_anchor_origin [i ] + pusher -> push_anchor_velocity [i ] * (newltime - pusher -> push_anchor_ltime );
807+ move [i ] = neworigin [i ] - pusher -> v .origin [i ];
730808 mins [i ] = pusher -> v .absmin [i ] + move [i ];
731809 maxs [i ] = pusher -> v .absmax [i ] + move [i ];
732810 // the grid query must span the whole sweep: riders rest on the pre-move
@@ -739,8 +817,10 @@ static void SV_PushMove (edict_t *pusher, float movetime)
739817
740818 // move the pusher to it's final position
741819
742- VectorAdd (pusher -> v .origin , move , pusher -> v .origin );
743- pusher -> v .ltime += movetime ;
820+ VectorCopy (neworigin , pusher -> v .origin );
821+ pusher -> v .ltime = newltime ;
822+ VectorCopy (neworigin , pusher -> push_anchor_lastorigin );
823+ pusher -> push_anchor_lastltime = newltime ;
744824 SV_LinkEdict (pusher , false);
745825
746826 // see if any solid entities are inside the final position
@@ -835,9 +915,17 @@ static void SV_PushMove (edict_t *pusher, float movetime)
835915 || solid_backup == SOLID_BBOX // normally boxes
836916 || solid_backup == SOLID_SLIDEBOX ) // normally monsters
837917 {
918+ // carry the entity to a target re-derived from the pusher's new
919+ // origin: the relative offset stays bounded instead of drifting
920+ // apart when both origins integrate the same move in parallel at
921+ // different float magnitudes
922+ vec3_t dest ;
923+ for (i = 0 ; i < 3 ; i ++ )
924+ dest [i ] = pusher -> v .origin [i ] + (entorig [i ] - pushorig [i ]);
925+
838926 // try moving the contacted entity
839927 pusher -> v .solid = SOLID_NOT ;
840- SV_PushEntity (check , move );
928+ SV_PushEntityTo (check , dest );
841929
842930 // if it is still inside the pusher, block
843931 if (pusher -> v .skin < 0 )
@@ -858,28 +946,54 @@ static void SV_PushMove (edict_t *pusher, float movetime)
858946 if (check -> v .mins [0 ] == check -> v .maxs [0 ])
859947 continue ;
860948
949+ // with the offset-preserving carry above, every rider contact keeps
950+ // the move trace's DIST_EPSILON gap except one: the ground contact,
951+ // which gravity re-closes to exactly zero every tick. Rounding can
952+ // therefore only put a rider inside the pusher through that contact,
953+ // along +z, no deeper than PUSH_CONTACT_EPSILON. Recover the exact
954+ // contact coordinate with a single sweep from that far above, which
955+ // the bound guarantees starts outside. This must run before the
956+ // corpse path below, or items (SOLID_TRIGGER) get their bbox zeroed
957+ // over a rounding error; anything still solid after the sweep is a
958+ // genuine squeeze and crushes as before.
959+ if (riding && block == pusher && SV_GameplayFixElevators (check ))
960+ {
961+ vec3_t pushedorg , above ;
962+ trace_t settle ;
963+
964+ VectorCopy (check -> v .origin , pushedorg );
965+ VectorCopy (check -> v .origin , above );
966+ above [2 ] += PUSH_CONTACT_EPSILON ;
967+ settle = SV_PushEntityMove (check , above , pushedorg );
968+ if (!settle .startsolid )
969+ {
970+ VectorCopy (settle .endpos , check -> v .origin );
971+ if (!SV_TestEntityPosition (check ))
972+ {
973+ SV_LinkEdict (check , false);
974+ continue ;
975+ }
976+ VectorCopy (pushedorg , check -> v .origin );
977+ }
978+ }
979+
861980 if (check -> v .solid == SOLID_NOT || check -> v .solid == SOLID_TRIGGER )
862981 { // corpse
863982 check -> v .mins [0 ] = check -> v .mins [1 ] = 0 ;
864983 VectorCopy (check -> v .mins , check -> v .maxs );
865984 continue ;
866985 }
867986
868- // try moving the entity up a bit if it's blocked by the pusher while also standing on it
869- if (riding && block == pusher &&
870- (sv_gameplayfix_elevators .value >= 2.f || (sv_gameplayfix_elevators .value && NUM_FOR_EDICT (check ) <= svs .maxclients )))
871- {
872- check -> v .origin [2 ] += DIST_EPSILON ;
873- if (!SV_TestEntityPosition (check ))
874- continue ;
875- }
876-
877987 VectorCopy (entorig , check -> v .origin );
878988 SV_LinkEdict (check , true);
879989
880990 VectorCopy (pushorig , pusher -> v .origin );
881991 SV_LinkEdict (pusher , false);
882992 pusher -> v .ltime -= movetime ;
993+ // the reverted ltime no longer matches push_anchor_lastltime, so the
994+ // next attempt re-anchors here and retries incrementally until the
995+ // obstacle is gone
996+ VectorCopy (pushorig , pusher -> push_anchor_lastorigin );
883997
884998 // if the pusher has a "blocked" function, call it
885999 // otherwise, just stay in place until the obstacle is gone
0 commit comments