void func_wait_return_solid( gentity_t *self ) { //once a frame, see if it's clear. self->clipmask = CONTENTS_BODY;//|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP; if ( !(self->spawnflags&16) || G_TestEntityPosition( self ) == NULL ) { gi.SetBrushModel( self, self->model ); VectorCopy( self->currentOrigin, self->pos1 ); InitMover( self ); /* VectorCopy( self->s.origin, self->s.pos.trBase ); VectorCopy( self->s.origin, self->currentOrigin ); */ //if we moved, we want the *current* origin, not our start origin! VectorCopy( self->currentOrigin, self->s.pos.trBase ); gi.linkentity( self ); self->svFlags &= ~SVF_NOCLIENT; self->s.eFlags &= ~EF_NODRAW; self->e_UseFunc = useF_func_usable_use; self->clipmask = 0; if ( self->target2 && self->target2[0] ) { G_UseTargets2( self, self->activator, self->target2 ); } if ( self->s.eFlags & EF_ANIM_ONCE ) {//Start our anim self->s.frame = 0; } //NOTE: be sure to reset the brushmodel before doing this or else CONTENTS_OPAQUE may not be on when you call this if ( !(self->spawnflags&1) ) {//START_OFF doesn't effect area portals gi.AdjustAreaPortalState( self, qfalse ); } } else { self->clipmask = 0; self->e_ThinkFunc = thinkF_func_wait_return_solid; self->nextthink = level.time + FRAMETIME; } }
/* ============ G_MoverPush Objects need to be moved back on a failed push, otherwise riders would continue to slide. If qfalse is returned, *obstacle will be the blocking entity ============ */ qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) { int i, e; gentity_t *check; vec3_t mins, maxs; pushed_t *p; int entityList[MAX_GENTITIES]; int listedEntities; vec3_t totalMins, totalMaxs; *obstacle = NULL; // mins/maxs are the bounds at the destination // totalMins / totalMaxs are the bounds for the entire move if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2] || amove[0] || amove[1] || amove[2] ) { float radius; radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs ); for ( i = 0 ; i < 3 ; i++ ) { mins[i] = pusher->r.currentOrigin[i] + move[i] - radius; maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius; totalMins[i] = mins[i] - move[i]; totalMaxs[i] = maxs[i] - move[i]; } } else { for (i=0 ; i<3 ; i++) { mins[i] = pusher->r.absmin[i] + move[i]; maxs[i] = pusher->r.absmax[i] + move[i]; } VectorCopy( pusher->r.absmin, totalMins ); VectorCopy( pusher->r.absmax, totalMaxs ); for (i=0 ; i<3 ; i++) { if ( move[i] > 0 ) { totalMaxs[i] += move[i]; } else { totalMins[i] += move[i]; } } } // unlink the pusher so we don't get it in the entityList trap_UnlinkEntity( pusher ); listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES ); // move the pusher to it's final position VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin ); VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles ); trap_LinkEntity( pusher ); // see if any solid entities are inside the final position for ( e = 0 ; e < listedEntities ; e++ ) { check = &g_entities[ entityList[ e ] ]; #ifdef MISSIONPACK if ( check->s.eType == ET_MISSILE ) { // if it is a prox mine if ( !strcmp(check->classname, "prox mine") ) { // if this prox mine is attached to this mover try to move it with the pusher if ( check->enemy == pusher ) { if (!G_TryPushingProxMine( check, pusher, move, amove )) { //explode check->s.loopSound = 0; G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 ); G_ExplodeMissile(check); if (check->activator) { G_FreeEntity(check->activator); check->activator = NULL; } //G_Printf("prox mine explodes\n"); } } else { //check if the prox mine is crushed by the mover if (!G_CheckProxMinePosition( check )) { //explode check->s.loopSound = 0; G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 ); G_ExplodeMissile(check); if (check->activator) { G_FreeEntity(check->activator); check->activator = NULL; } //G_Printf("prox mine explodes\n"); } } continue; } } #endif // only push items and players if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) { continue; } // if the entity is standing on the pusher, it will definitely be moved if ( check->s.groundEntityNum != pusher->s.number ) { // see if the ent needs to be tested if ( check->r.absmin[0] >= maxs[0] || check->r.absmin[1] >= maxs[1] || check->r.absmin[2] >= maxs[2] || check->r.absmax[0] <= mins[0] || check->r.absmax[1] <= mins[1] || check->r.absmax[2] <= mins[2] ) { continue; } // see if the ent's bbox is inside the pusher's final position // this does allow a fast moving object to pass through a thin entity... if (!G_TestEntityPosition (check)) { continue; } } // the entity needs to be pushed if ( G_TryPushingEntity( check, pusher, move, amove ) ) { continue; } // the move was blocked an entity // bobbing entities are instant-kill and never get blocked if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) { G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH ); continue; } // save off the obstacle so we can call the block function (crush, etc) *obstacle = check; // move back any entities we already moved // go backwards, so if the same entity was pushed // twice, it goes back to the original position for ( p=pushed_p-1 ; p>=pushed ; p-- ) { VectorCopy (p->origin, p->ent->s.pos.trBase); VectorCopy (p->angles, p->ent->s.apos.trBase); if ( p->ent->client ) { p->ent->client->ps.delta_angles[YAW] = p->deltayaw; VectorCopy (p->origin, p->ent->client->ps.origin); } trap_LinkEntity (p->ent); } return qfalse; } return qtrue; }
/* ================== G_TryPushingEntity Returns qfalse if the move is blocked ================== */ qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) { vec3_t matrix[3], transpose[3]; vec3_t org, org2, move2; gentity_t *block; // EF_MOVER_STOP will just stop when contacting another entity // instead of pushing it, but entities can still ride on top of it if ( ( pusher->s.eFlags & EF_MOVER_STOP ) && check->s.groundEntityNum != pusher->s.number ) { return qfalse; } // save off the old position if (pushed_p > &pushed[MAX_GENTITIES]) { G_Error( "pushed_p > &pushed[MAX_GENTITIES]" ); } pushed_p->ent = check; VectorCopy (check->s.pos.trBase, pushed_p->origin); VectorCopy (check->s.apos.trBase, pushed_p->angles); if ( check->client ) { pushed_p->deltayaw = check->client->ps.delta_angles[YAW]; VectorCopy (check->client->ps.origin, pushed_p->origin); } pushed_p++; // try moving the contacted entity // figure movement due to the pusher's amove G_CreateRotationMatrix( amove, transpose ); G_TransposeMatrix( transpose, matrix ); if ( check->client ) { VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org); } else { VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org); } VectorCopy( org, org2 ); G_RotatePoint( org2, matrix ); VectorSubtract (org2, org, move2); // add movement VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase); VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase); if ( check->client ) { VectorAdd (check->client->ps.origin, move, check->client->ps.origin); VectorAdd (check->client->ps.origin, move2, check->client->ps.origin); // make sure the client's view rotates when on a rotating mover check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]); } // may have pushed them off an edge if ( check->s.groundEntityNum != pusher->s.number ) { check->s.groundEntityNum = -1; } block = G_TestEntityPosition( check ); if (!block) { // pushed ok if ( check->client ) { VectorCopy( check->client->ps.origin, check->r.currentOrigin ); } else { VectorCopy( check->s.pos.trBase, check->r.currentOrigin ); } trap_LinkEntity (check); return qtrue; } // if it is ok to leave in the old position, do it // this is only relevent for riding entities, not pushed // Sliding trapdoors can cause this. VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase); if ( check->client ) { VectorCopy( (pushed_p-1)->origin, check->client->ps.origin); } VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase ); block = G_TestEntityPosition (check); if ( !block ) { check->s.groundEntityNum = -1; pushed_p--; return qtrue; } // blocked return qfalse; }
/* ================ G_SetEntState sets the entstate of an entity. ================ */ void G_SetEntState( gentity_t *ent, entState_t state ) { if ( ent->entstate == state ) { G_DPrintf( "entity %i already in desired state [%i]\n", ent->s.number, state ); return; } switch ( state ) { case STATE_DEFAULT: if ( ent->entstate == STATE_UNDERCONSTRUCTION ) { ent->clipmask = ent->realClipmask; ent->r.contents = ent->realContents; if ( !ent->realNonSolidBModel ) { ent->s.eFlags &= ~EF_NONSOLID_BMODEL; } } ent->entstate = STATE_DEFAULT; ent->s.powerups = STATE_DEFAULT; if ( ent->s.eType == ET_WOLF_OBJECTIVE ) { char cs[MAX_STRING_CHARS]; trap_GetConfigstring( ent->count, cs, sizeof( cs ) ); ent->count2 &= ~256; Info_SetValueForKey( cs, "t", va( "%i", ent->count2 ) ); trap_SetConfigstring( ent->count, cs ); } if ( ent->s.eType != ET_COMMANDMAP_MARKER ) { trap_LinkEntity( ent ); } // deal with any entities in the solid { int listedEntities, e; int entityList[MAX_GENTITIES]; gentity_t *check, *block; listedEntities = trap_EntitiesInBox( ent->r.absmin, ent->r.absmax, entityList, MAX_GENTITIES ); for ( e = 0; e < listedEntities; e++ ) { check = &g_entities[entityList[e]]; // ignore everything but items, players and missiles (grenades too) if ( check->s.eType != ET_MISSILE && check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) { continue; } if ( ( block = G_TestEntityPosition( check ) ) == NULL ) { continue; } if ( block != ent ) { // the entity is blocked by another entity - that block this should take care of this itself continue; } if ( check->client || check->s.eType == ET_CORPSE ) { // gibs anything player like G_Damage( check, ent, ent, NULL, NULL, 9999, DAMAGE_NO_PROTECTION, MOD_CRUSH_CONSTRUCTIONDEATH_NOATTACKER ); } else if ( check->s.eType == ET_ITEM && check->item->giType == IT_TEAM ) { // see if it's a critical entity, one that we can't just simply kill (basically flags) Team_DroppedFlagThink( check ); } else { // remove the landmine from both teamlists if ( check->s.eType == ET_MISSILE && check->methodOfDeath == MOD_LANDMINE ) { mapEntityData_t *mEnt; if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], check - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], check - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } } // just get rid of it G_TempEntity( check->s.origin, EV_ITEM_POP ); G_FreeEntity( check ); } } } // if this is an mg42, then we should try and calculate mg42 spots again BotCalculateMg42Spots(); break; case STATE_UNDERCONSTRUCTION: ent->entstate = STATE_UNDERCONSTRUCTION; ent->s.powerups = STATE_UNDERCONSTRUCTION; ent->realClipmask = ent->clipmask; if ( ent->s.eType != ET_CONSTRUCTIBLE ) { // don't make nonsolid as we want to make them partially solid for staged construction ent->clipmask = 0; } ent->realContents = ent->r.contents; if ( ent->s.eType != ET_CONSTRUCTIBLE ) { ent->r.contents = 0; } if ( ent->s.eFlags & EF_NONSOLID_BMODEL ) { ent->realNonSolidBModel = qtrue; } else if ( ent->s.eType != ET_CONSTRUCTIBLE ) { ent->s.eFlags |= EF_NONSOLID_BMODEL; } if ( !Q_stricmp( ent->classname, "misc_mg42" ) ) { // stop using the mg42 mg42_stopusing( ent ); } if ( ent->s.eType == ET_COMMANDMAP_MARKER ) { mapEntityData_t *mEnt; if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], ent - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], ent - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } } trap_LinkEntity( ent ); break; case STATE_INVISIBLE: if ( ent->entstate == STATE_UNDERCONSTRUCTION ) { ent->clipmask = ent->realClipmask; ent->r.contents = ent->realContents; if ( !ent->realNonSolidBModel ) { ent->s.eFlags &= ~EF_NONSOLID_BMODEL; } } ent->entstate = STATE_INVISIBLE; ent->s.powerups = STATE_INVISIBLE; if ( !Q_stricmp( ent->classname, "misc_mg42" ) ) { mg42_stopusing( ent ); } else if ( ent->s.eType == ET_WOLF_OBJECTIVE ) { char cs[MAX_STRING_CHARS]; trap_GetConfigstring( ent->count, cs, sizeof( cs ) ); ent->count2 |= 256; Info_SetValueForKey( cs, "t", va( "%i", ent->count2 ) ); trap_SetConfigstring( ent->count, cs ); } if ( ent->s.eType == ET_COMMANDMAP_MARKER ) { mapEntityData_t *mEnt; if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], ent - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], ent - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } } trap_UnlinkEntity( ent ); break; } }
// Objects need to be moved back on a failed push, otherwise riders would continue to slide. // If qfalse is returned, *obstacle will be the blocking entity qboolean G_MoverPush( gentity_t *pusher, vector3 *move, vector3 *amove, gentity_t **obstacle ) { int i, e; gentity_t *check; vector3 mins, maxs; pushed_t *p; int entityList[MAX_GENTITIES]; int listedEntities; vector3 totalMins, totalMaxs; *obstacle = NULL; // mins/maxs are the bounds at the destination // totalMins / totalMaxs are the bounds for the entire move if ( pusher->r.currentAngles.x || pusher->r.currentAngles.y || pusher->r.currentAngles.z || amove->x || amove->y || amove->z ) { float radius; radius = RadiusFromBounds( &pusher->r.mins, &pusher->r.maxs ); for ( i = 0 ; i < 3 ; i++ ) { mins.data[i] = pusher->r.currentOrigin.data[i] + move->data[i] - radius; maxs.data[i] = pusher->r.currentOrigin.data[i] + move->data[i] + radius; totalMins.data[i] = mins.data[i] - move->data[i]; totalMaxs.data[i] = maxs.data[i] - move->data[i]; } } else { for (i=0 ; i<3 ; i++) { mins.data[i] = pusher->r.absmin.data[i] + move->data[i]; maxs.data[i] = pusher->r.absmax.data[i] + move->data[i]; } VectorCopy( &pusher->r.absmin, &totalMins ); VectorCopy( &pusher->r.absmax, &totalMaxs ); for (i=0 ; i<3 ; i++) { if ( move->data[i] > 0 ) totalMaxs.data[i] += move->data[i]; else totalMins.data[i] += move->data[i]; } } // unlink the pusher so we don't get it in the entityList trap->SV_UnlinkEntity( (sharedEntity_t *)pusher ); listedEntities = trap->SV_AreaEntities( &totalMins, &totalMaxs, entityList, MAX_GENTITIES ); // move the pusher to its final position VectorAdd( &pusher->r.currentOrigin, move, &pusher->r.currentOrigin ); VectorAdd( &pusher->r.currentAngles, amove, &pusher->r.currentAngles ); trap->SV_LinkEntity( (sharedEntity_t *)pusher ); // see if any solid entities are inside the final position for ( e = 0 ; e < listedEntities ; e++ ) { check = &g_entities[ entityList[ e ] ]; // only push items and players if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) { continue; } // if the entity is standing on the pusher, it will definitely be moved if ( check->s.groundEntityNum != pusher->s.number ) { // see if the ent needs to be tested if ( check->r.absmin.x >= maxs.x || check->r.absmin.y >= maxs.y || check->r.absmin.z >= maxs.z || check->r.absmax.x <= mins.x || check->r.absmax.y <= mins.y || check->r.absmax.z <= mins.z ) { continue; } // see if the ent's bbox is inside the pusher's final position // this does allow a fast moving object to pass through a thin entity... if (!G_TestEntityPosition (check)) { continue; } } // the entity needs to be pushed if ( G_TryPushingEntity( check, pusher, move, amove ) ) { continue; } // the move was blocked an entity // bobbing entities are instant-kill and never get blocked if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) { //Raz: don't care about the affector for pushers G_Damage( check, pusher, pusher, NULL, NULL, NULL, 99999, 0, MOD_CRUSH ); continue; } // save off the obstacle so we can call the block function (crush, etc) *obstacle = check; // move back any entities we already moved // go backwards, so if the same entity was pushed // twice, it goes back to the original position for ( p=pushed_p-1 ; p>=pushed ; p-- ) { VectorCopy (&p->origin, &p->ent->s.pos.trBase); VectorCopy (&p->angles, &p->ent->s.apos.trBase); if ( p->ent->client ) { p->ent->client->ps.delta_angles.yaw = (int)p->deltayaw; VectorCopy (&p->origin, &p->ent->client->ps.origin); } trap->SV_LinkEntity ((sharedEntity_t *)p->ent); } return qfalse; } return qtrue; }
void WolfReviveBbox( gentity_t *self ) { int touch[MAX_GENTITIES]; int num,i, touchnum = 0; gentity_t *hit = NULL; vec3_t mins, maxs; gentity_t *capsulehit = NULL; VectorAdd( self->r.currentOrigin, playerMins, mins ); VectorAdd( self->r.currentOrigin, playerMaxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // Arnout, we really should be using capsules, do a quick, more refined test using mover collision if ( num ) { capsulehit = G_TestEntityPosition( self ); } for ( i = 0 ; i < num ; i++ ) { hit = &g_entities[touch[i]]; if ( hit->client ) { // ATVI Wolfenstein Misc #467 // don't look at yourself when counting the hits if ( hit->client->ps.persistant[PERS_HWEAPON_USE] && hit != self ) { touchnum++; // Move corpse directly to the person who revived them if ( self->props_frame_state >= 0 ) { trap_UnlinkEntity( self ); VectorCopy( g_entities[self->props_frame_state].client->ps.origin, self->client->ps.origin ); VectorCopy( self->client->ps.origin, self->r.currentOrigin ); trap_LinkEntity( self ); // Reset value so we don't continue to warp them self->props_frame_state = -1; } } else if ( hit->health > 0 ) { if ( hit->s.number != self->s.number ) { WolfRevivePushEnt( hit, self ); touchnum++; } } } else if ( hit->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_PLAYERCLIP ) ) { // Arnout: if hit is a mover, use capsulehit (this will only work if we touch one mover at a time - situations where you hit two are // really rare anyway though. The real fix is to move everything to capsule collision detection though if ( hit->s.eType == ET_MOVER ) { if ( capsulehit && capsulehit != hit ) { continue; // we collided with a mover, but we're not stuck in this one } else { continue; // we didn't collide with any movers } } WolfRevivePushEnt( hit, self ); touchnum++; } } if ( g_dbgRevive.integer ) { G_Printf( "WolfReviveBbox: touchnum: %d\n", touchnum ); } if ( touchnum == 0 ) { if ( g_dbgRevive.integer ) { G_Printf( "WolfReviveBbox: Player is solid now!\n" ); } self->r.contents = CONTENTS_BODY; } }
/* * @brief Objects need to be moved back on a failed push, * otherwise riders would continue to slide. */ static _Bool G_Push(g_edict_t *pusher, vec3_t move, vec3_t amove) { int32_t i, e; g_edict_t *check, *block; vec3_t mins, maxs; g_pushed_t *p; vec3_t org, org2, move2, forward, right, up; // clamp the move to 1/8 units, so the position will // be accurate for client side prediction for (i = 0; i < 3; i++) { vec_t temp; temp = move[i] * 8.0; if (temp > 0.0) temp += 0.5; else temp -= 0.5; move[i] = 0.125 * (int16_t) temp; } // find the bounding box for (i = 0; i < 3; i++) { mins[i] = pusher->abs_mins[i] + move[i]; maxs[i] = pusher->abs_maxs[i] + move[i]; } // we need this for pushing things later VectorSubtract(vec3_origin, amove, org); AngleVectors(org, forward, right, up); // save the pusher's original position g_pushed_p->ent = pusher; VectorCopy(pusher->s.origin, g_pushed_p->origin); VectorCopy(pusher->s.angles, g_pushed_p->angles); if (pusher->client) g_pushed_p->delta_yaw = pusher->client->ps.pm_state.delta_angles[YAW]; g_pushed_p++; // move the pusher to it's final position VectorAdd(pusher->s.origin, move, pusher->s.origin); VectorAdd(pusher->s.angles, amove, pusher->s.angles); gi.LinkEdict(pusher); // see if any solid entities are inside the final position check = g_game.edicts + 1; for (e = 1; e < ge.num_edicts; e++, check++) { if (!check->in_use) continue; if (check->locals.move_type == MOVE_TYPE_PUSH || check->locals.move_type == MOVE_TYPE_STOP || check->locals.move_type == MOVE_TYPE_NONE || check->locals.move_type == MOVE_TYPE_NO_CLIP) continue; if (!check->area.prev) continue; // not linked in anywhere // if the entity is standing on the pusher, it will definitely be moved if (check->locals.ground_entity != pusher) { // do not push entities which are beside us if (check->locals.item) continue; // see if the ent needs to be tested if (check->abs_mins[0] >= maxs[0] || check->abs_mins[1] >= maxs[1] || check->abs_mins[2] >= maxs[2] || check->abs_maxs[0] <= mins[0] || check->abs_maxs[1] <= mins[1] || check->abs_maxs[2] <= mins[2]) continue; // see if the ent's bbox is inside the pusher's final position if (!G_TestEntityPosition(check)) continue; } if ((pusher->locals.move_type == MOVE_TYPE_PUSH) || (check->locals.ground_entity == pusher)) { // move this entity g_pushed_p->ent = check; VectorCopy(check->s.origin, g_pushed_p->origin); VectorCopy(check->s.angles, g_pushed_p->angles); g_pushed_p++; // try moving the contacted entity VectorAdd(check->s.origin, move, check->s.origin); if (check->client) { // rotate the client check->client->ps.pm_state.delta_angles[YAW] += PackAngle(amove[YAW]); } // figure movement due to the pusher's move VectorSubtract(check->s.origin, pusher->s.origin, org); org2[0] = DotProduct(org, forward); org2[1] = -DotProduct(org, right); org2[2] = DotProduct(org, up); VectorSubtract(org2, org, move2); VectorAdd(check->s.origin, move2, check->s.origin); // may have pushed them off an edge if (check->locals.ground_entity != pusher) check->locals.ground_entity = NULL; block = G_TestEntityPosition(check); if (!block) { // pushed okay gi.LinkEdict(check); continue; } // if it is okay to leave in the old position, do it // this is only relevant for riding entities, not pushed // FIXME: this doesn't account for rotation VectorSubtract(check->s.origin, move, check->s.origin); block = G_TestEntityPosition(check); if (!block) { g_pushed_p--; continue; } } // save off the obstacle so we can call the block function obstacle = check; // move back any entities we already moved // go backwards, so if the same entity was pushed // twice, it goes back to the original position for (p = g_pushed_p - 1; p >= g_pushed; p--) { VectorCopy(p->origin, p->ent->s.origin); VectorCopy(p->angles, p->ent->s.angles); if (p->ent->client) { p->ent->client->ps.pm_state.delta_angles[YAW] = p->delta_yaw; } gi.LinkEdict(p->ent); } return false; } // FIXME: is there a better way to handle this? // see if anything we moved has touched a trigger for (p = g_pushed_p - 1; p >= g_pushed; p--) { if (p->ent->in_use && p->ent->client && p->ent->locals.health > 0) G_TouchTriggers(p->ent); } return true; }