@@ -659,27 +659,63 @@ PUSHMOVE
659659===============================================================================
660660*/
661661
662+ cvar_t sv_gameplayfix_elevators = {"sv_gameplayfix_elevators" , "2" , CVAR_ARCHIVE }; // 0=off; 1=clients only; 2=all entities
663+
664+ static qboolean SV_GameplayFixElevators (edict_t * ent )
665+ {
666+ return sv_gameplayfix_elevators .value >= 2.f || (sv_gameplayfix_elevators .value && NUM_FOR_EDICT (ent ) <= svs .maxclients );
667+ }
668+
669+ static trace_t SV_PushEntityMove (edict_t * ent , vec3_t end )
670+ {
671+ if (ent -> v .movetype == MOVETYPE_FLYMISSILE )
672+ return SV_Move (ent -> v .origin , ent -> v .mins , ent -> v .maxs , end , MOVE_MISSILE , ent );
673+ else if (ent -> v .solid == SOLID_TRIGGER || ent -> v .solid == SOLID_NOT )
674+ // only clip against bmodels
675+ return SV_Move (ent -> v .origin , ent -> v .mins , ent -> v .maxs , end , MOVE_NOMONSTERS , ent );
676+ else
677+ return SV_Move (ent -> v .origin , ent -> v .mins , ent -> v .maxs , end , MOVE_NORMAL , ent );
678+ }
679+
662680/*
663681============
664- SV_PushEntity
682+ SV_PushEntityTo
665683
666684Does not change the entities velocity at all
667685============
668686*/
669- static trace_t SV_PushEntity (edict_t * ent , vec3_t push )
687+ static trace_t SV_PushEntityTo (edict_t * ent , vec3_t end )
670688{
671689 trace_t trace ;
672- vec3_t end ;
673690
674- VectorAdd (ent -> v . origin , push , end );
691+ trace = SV_PushEntityMove (ent , end );
675692
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 );
693+ // a move that starts inside a solid registers no impact against it, so the
694+ // entity would glide through the brush and fall out the far side. That can
695+ // happen against the pusher the entity rests on when float rounding at the
696+ // contact leaves it an epsilon deep after a carry; un-embed and redo the
697+ // move so it collides normally.
698+ if (trace .startsolid && ent -> v .groundentity && SV_GameplayFixElevators (ent ))
699+ {
700+ edict_t * ground = PROG_TO_EDICT (ent -> v .groundentity );
701+ if (ground != qcvm -> edicts && !ground -> free && ground -> v .movetype == MOVETYPE_PUSH && ground -> v .solid == SOLID_BSP &&
702+ SV_ClipMoveToEntity (ground , ent -> v .origin , ent -> v .mins , ent -> v .maxs , ent -> v .origin , CONTENTMASK_ANYSOLID ).startsolid )
703+ {
704+ int step ;
705+ for (step = 1 ; step <= 4 ; step ++ )
706+ {
707+ vec3_t org ;
708+ VectorCopy (ent -> v .origin , org );
709+ org [2 ] += step * DIST_EPSILON ;
710+ if (!SV_ClipMoveToEntity (ground , org , ent -> v .mins , ent -> v .maxs , org , CONTENTMASK_ANYSOLID ).startsolid )
711+ {
712+ VectorCopy (org , ent -> v .origin );
713+ trace = SV_PushEntityMove (ent , end );
714+ break ;
715+ }
716+ }
717+ }
718+ }
683719
684720 if (trace .ent )
685721 assert_always (!trace .ent -> free );
@@ -696,12 +732,26 @@ static trace_t SV_PushEntity (edict_t *ent, vec3_t push)
696732 return trace ;
697733}
698734
735+ /*
736+ ============
737+ SV_PushEntity
738+
739+ Does not change the entities velocity at all
740+ ============
741+ */
742+ static trace_t SV_PushEntity (edict_t * ent , vec3_t push )
743+ {
744+ vec3_t end ;
745+
746+ VectorAdd (ent -> v .origin , push , end );
747+ return SV_PushEntityTo (ent , end );
748+ }
749+
699750/*
700751============
701752SV_PushMove
702753============
703754*/
704- cvar_t sv_gameplayfix_elevators = {"sv_gameplayfix_elevators" , "2" , CVAR_ARCHIVE }; // 0=off; 1=clients only; 2=all entities
705755
706756static void SV_PushMove (edict_t * pusher , float movetime )
707757{
@@ -724,9 +774,29 @@ static void SV_PushMove (edict_t *pusher, float movetime)
724774 return ;
725775 }
726776
777+ // evaluate the new position analytically from the anchor captured when this
778+ // leg's velocity was set: position error stays at a couple of ulps regardless
779+ // of leg length and the final partial step lands on the leg destination,
780+ // instead of accumulating velocity*movetime rounding every tick. The last*
781+ // mismatch cases (QC touched origin or ltime, or a blocked move was reverted)
782+ // re-anchor at the current state, which degrades to the vanilla incremental
783+ // behavior for that stretch.
784+ if (!pusher -> push_anchor_valid || !VectorCompare (pusher -> v .velocity , pusher -> push_anchor_velocity ) ||
785+ !VectorCompare (pusher -> v .origin , pusher -> push_anchor_lastorigin ) || pusher -> v .ltime != pusher -> push_anchor_lastltime )
786+ {
787+ VectorCopy (pusher -> v .origin , pusher -> push_anchor_origin );
788+ VectorCopy (pusher -> v .velocity , pusher -> push_anchor_velocity );
789+ pusher -> push_anchor_ltime = pusher -> v .ltime ;
790+ pusher -> push_anchor_valid = true;
791+ }
792+
793+ const float newltime = pusher -> v .ltime + movetime ;
794+ vec3_t neworigin ;
795+
727796 for (i = 0 ; i < 3 ; i ++ )
728797 {
729- move [i ] = pusher -> v .velocity [i ] * movetime ;
798+ neworigin [i ] = pusher -> push_anchor_origin [i ] + pusher -> push_anchor_velocity [i ] * (newltime - pusher -> push_anchor_ltime );
799+ move [i ] = neworigin [i ] - pusher -> v .origin [i ];
730800 mins [i ] = pusher -> v .absmin [i ] + move [i ];
731801 maxs [i ] = pusher -> v .absmax [i ] + move [i ];
732802 // the grid query must span the whole sweep: riders rest on the pre-move
@@ -739,8 +809,10 @@ static void SV_PushMove (edict_t *pusher, float movetime)
739809
740810 // move the pusher to it's final position
741811
742- VectorAdd (pusher -> v .origin , move , pusher -> v .origin );
743- pusher -> v .ltime += movetime ;
812+ VectorCopy (neworigin , pusher -> v .origin );
813+ pusher -> v .ltime = newltime ;
814+ VectorCopy (neworigin , pusher -> push_anchor_lastorigin );
815+ pusher -> push_anchor_lastltime = newltime ;
744816 SV_LinkEdict (pusher , false);
745817
746818 // see if any solid entities are inside the final position
@@ -835,9 +907,17 @@ static void SV_PushMove (edict_t *pusher, float movetime)
835907 || solid_backup == SOLID_BBOX // normally boxes
836908 || solid_backup == SOLID_SLIDEBOX ) // normally monsters
837909 {
910+ // carry the entity to a target re-derived from the pusher's new
911+ // origin: the relative offset stays bounded instead of drifting
912+ // apart when both origins integrate the same move in parallel at
913+ // different float magnitudes
914+ vec3_t dest ;
915+ for (i = 0 ; i < 3 ; i ++ )
916+ dest [i ] = pusher -> v .origin [i ] + (entorig [i ] - pushorig [i ]);
917+
838918 // try moving the contacted entity
839919 pusher -> v .solid = SOLID_NOT ;
840- SV_PushEntity (check , move );
920+ SV_PushEntityTo (check , dest );
841921
842922 // if it is still inside the pusher, block
843923 if (pusher -> v .skin < 0 )
@@ -858,28 +938,65 @@ static void SV_PushMove (edict_t *pusher, float movetime)
858938 if (check -> v .mins [0 ] == check -> v .maxs [0 ])
859939 continue ;
860940
941+ // a rider that ends up marginally inside the pusher (float rounding
942+ // at the contact) is not a real squeeze: try epsilon nudges up and
943+ // along the push direction before failing the move. This must run
944+ // before the corpse path below, or items (SOLID_TRIGGER) get their
945+ // bbox zeroed over a rounding error. Genuine squeezes embed far
946+ // deeper than these nudges and still crush.
947+ if (riding && block == pusher && SV_GameplayFixElevators (check ))
948+ {
949+ vec3_t pushedorg , movedir ;
950+ int attempt ;
951+ qboolean cleared = false;
952+
953+ VectorCopy (check -> v .origin , pushedorg );
954+ VectorCopy (move , movedir );
955+ VectorNormalize (movedir );
956+
957+ for (attempt = 0 ; attempt < 6 && !cleared ; attempt ++ )
958+ {
959+ const float scale = (attempt < 3 ) ? DIST_EPSILON : 2.f * DIST_EPSILON ;
960+ vec3_t nudge = {0.f , 0.f , 0.f };
961+
962+ if (attempt % 3 == 0 )
963+ nudge [2 ] = scale ;
964+ else if (attempt % 3 == 1 )
965+ VectorScale (movedir , scale , nudge );
966+ else
967+ {
968+ VectorScale (movedir , scale , nudge );
969+ nudge [2 ] += scale ;
970+ }
971+
972+ VectorAdd (pushedorg , nudge , check -> v .origin );
973+ cleared = !SV_TestEntityPosition (check );
974+ }
975+ if (cleared )
976+ {
977+ SV_LinkEdict (check , false);
978+ continue ;
979+ }
980+ VectorCopy (pushedorg , check -> v .origin );
981+ }
982+
861983 if (check -> v .solid == SOLID_NOT || check -> v .solid == SOLID_TRIGGER )
862984 { // corpse
863985 check -> v .mins [0 ] = check -> v .mins [1 ] = 0 ;
864986 VectorCopy (check -> v .mins , check -> v .maxs );
865987 continue ;
866988 }
867989
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-
877990 VectorCopy (entorig , check -> v .origin );
878991 SV_LinkEdict (check , true);
879992
880993 VectorCopy (pushorig , pusher -> v .origin );
881994 SV_LinkEdict (pusher , false);
882995 pusher -> v .ltime -= movetime ;
996+ // the reverted ltime no longer matches push_anchor_lastltime, so the
997+ // next attempt re-anchors here and retries incrementally until the
998+ // obstacle is gone
999+ VectorCopy (pushorig , pusher -> push_anchor_lastorigin );
8831000
8841001 // if the pusher has a "blocked" function, call it
8851002 // otherwise, just stay in place until the obstacle is gone
0 commit comments