DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialPosition) { PARAM_ACTION_PROLOGUE; // Move item back to its original location DVector2 sp = self->SpawnPoint; self->UnlinkFromWorld(); self->SetXY(sp); self->LinkToWorld(true); self->SetZ(self->Sector->floorplane.ZatPoint(sp)); P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS | FFCF_NOPORTALS); // no portal checks here so that things get spawned in this sector. if (self->flags & MF_SPAWNCEILING) { self->SetZ(self->ceilingz - self->Height - self->SpawnPoint.Z); } else if (self->flags2 & MF2_SPAWNFLOAT) { double space = self->ceilingz - self->Height - self->floorz; if (space > 48) { space -= 40; self->SetZ((space * pr_restore()) / 256. + self->floorz + 40); } else { self->SetZ(self->floorz); } } else { self->SetZ(self->SpawnPoint.Z + self->floorz); } // Redo floor/ceiling check, in case of 3D floors and portals P_FindFloorCeiling(self, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT); if (self->Z() < self->floorz) { // Do not reappear under the floor, even if that's where we were for the // initial spawn. self->SetZ(self->floorz); } if ((self->flags & MF_SOLID) && (self->Top() > self->ceilingz)) { // Do the same for the ceiling. self->SetZ(self->ceilingz - self->Height); } // Do not interpolate from the position the actor was at when it was // picked up, in case that is different from where it is now. self->ClearInterpolation(); return 0; }
DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialPosition) { // Move item back to its original location fixed_t _x, _y; sector_t *sec; _x = self->SpawnPoint[0]; _y = self->SpawnPoint[1]; self->UnlinkFromWorld(); self->x = _x; self->y = _y; self->LinkToWorld(true); sec = self->Sector; self->z = self->dropoffz = self->floorz = sec->floorplane.ZatPoint(_x, _y); self->ceilingz = sec->ceilingplane.ZatPoint(_x, _y); P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS); if (self->flags & MF_SPAWNCEILING) { self->z = self->ceilingz - self->height - self->SpawnPoint[2]; } else if (self->flags2 & MF2_SPAWNFLOAT) { fixed_t space = self->ceilingz - self->height - self->floorz; if (space > 48*FRACUNIT) { space -= 40*FRACUNIT; self->z = ((space * pr_restore())>>8) + self->floorz + 40*FRACUNIT; }
void AInventory::BecomePickup () { if (Owner != NULL) { Owner->RemoveInventory (this); } if (flags & (MF_NOBLOCKMAP|MF_NOSECTOR)) { UnlinkFromWorld (); flags &= ~(MF_NOBLOCKMAP|MF_NOSECTOR); LinkToWorld (); P_FindFloorCeiling (this); } flags = (GetDefault()->flags | MF_DROPPED) & ~MF_COUNTITEM; renderflags &= ~RF_INVISIBLE; SetState (SpawnState); }
void G_FinishTravel () { TThinkerIterator<APlayerPawn> it (STAT_TRAVELLING); APlayerPawn *pawn, *pawndup, *oldpawn, *next; AInventory *inv; FPlayerStart *start; int pnum; next = it.Next (); while ( (pawn = next) != NULL) { next = it.Next (); pnum = int(pawn->player - players); pawn->ChangeStatNum (STAT_PLAYER); pawndup = pawn->player->mo; start = NULL; assert (pawn != pawndup); if (pawndup == NULL) { // Oh no! there was no start for this player! start = G_PickPlayerStart(pnum, PPS_FORCERANDOM); if (start != NULL) pawndup = P_SpawnPlayer(start, pnum, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0); if (pawndup == NULL) { pawn->flags |= MF_NOSECTOR | MF_NOBLOCKMAP; pawn->Destroy(); continue; } } if (start == NULL) { start = G_PickPlayerStart(pnum, 0); if (start == NULL) { Printf(TEXTCOLOR_RED "No player %d start to travel to!\n", pnum + 1); // Move to the coordinates this player had when they left the level. pawn->SetXYZ(pawndup->Pos()); } } oldpawn = pawndup; // The player being spawned here is a short lived dummy and // must not start any ENTER script or big problems will happen. pawndup = P_SpawnPlayer(start, pnum, SPF_TEMPPLAYER); if (pawndup != NULL) { if (!(changeflags & CHANGELEVEL_KEEPFACING)) { pawn->Angles = pawndup->Angles; } pawn->SetXYZ(pawndup->Pos()); pawn->Vel = pawndup->Vel; pawn->Sector = pawndup->Sector; pawn->floorz = pawndup->floorz; pawn->ceilingz = pawndup->ceilingz; pawn->dropoffz = pawndup->dropoffz; pawn->floorsector = pawndup->floorsector; pawn->floorpic = pawndup->floorpic; pawn->floorterrain = pawndup->floorterrain; pawn->ceilingsector = pawndup->ceilingsector; pawn->ceilingpic = pawndup->ceilingpic; pawn->Floorclip = pawndup->Floorclip; pawn->waterlevel = pawndup->waterlevel; } else { P_FindFloorCeiling(pawn); } pawn->target = NULL; pawn->lastenemy = NULL; pawn->player->mo = pawn; pawn->player->camera = pawn; pawn->player->viewheight = pawn->ViewHeight; pawn->flags2 &= ~MF2_BLASTED; DObject::StaticPointerSubstitution (oldpawn, pawn); oldpawn->Destroy(); if (pawndup != NULL) { pawndup->Destroy(); } pawn->LinkToWorld (); pawn->ClearInterpolation(); pawn->AddToHash (); pawn->SetState(pawn->SpawnState); pawn->player->SendPitchLimits(); for (inv = pawn->Inventory; inv != NULL; inv = inv->Inventory) { inv->ChangeStatNum (STAT_INVENTORY); inv->LinkToWorld (); inv->Travelled (); } if (ib_compatflags & BCOMPATF_RESETPLAYERSPEED) { pawn->Speed = pawn->GetDefault()->Speed; } if (level.FromSnapshot) { FBehavior::StaticStartTypedScripts (SCRIPT_Return, pawn, true); } } bglobal.FinishTravel (); }
void G_FinishTravel () { TThinkerIterator<APlayerPawn> it (STAT_TRAVELLING); APlayerPawn *pawn, *pawndup, *oldpawn, *next; AInventory *inv; FPlayerStart *start; int pnum; next = it.Next (); while ( (pawn = next) != NULL) { next = it.Next (); pnum = int(pawn->player - players); pawn->ChangeStatNum (STAT_PLAYER); pawndup = pawn->player->mo; assert (pawn != pawndup); start = G_PickPlayerStart(pnum, 0); if (start == NULL) { if (pawndup != nullptr) { Printf(TEXTCOLOR_RED "No player %d start to travel to!\n", pnum + 1); // Move to the coordinates this player had when they left the level. pawn->SetXYZ(pawndup->Pos()); } else { // Could not find a start for this player at all. This really should never happen but if it does, let's better abort. DThinker::DestroyThinkersInList(STAT_TRAVELLING); I_Error ("No player %d start to travel to!\n", pnum + 1); } } oldpawn = pawndup; // The player being spawned here is a short lived dummy and // must not start any ENTER script or big problems will happen. pawndup = P_SpawnPlayer(start, pnum, SPF_TEMPPLAYER); if (pawndup != NULL) { if (!(changeflags & CHANGELEVEL_KEEPFACING)) { pawn->Angles = pawndup->Angles; } pawn->SetXYZ(pawndup->Pos()); pawn->Vel = pawndup->Vel; pawn->Sector = pawndup->Sector; pawn->floorz = pawndup->floorz; pawn->ceilingz = pawndup->ceilingz; pawn->dropoffz = pawndup->dropoffz; pawn->floorsector = pawndup->floorsector; pawn->floorpic = pawndup->floorpic; pawn->floorterrain = pawndup->floorterrain; pawn->ceilingsector = pawndup->ceilingsector; pawn->ceilingpic = pawndup->ceilingpic; pawn->Floorclip = pawndup->Floorclip; pawn->waterlevel = pawndup->waterlevel; } else { P_FindFloorCeiling(pawn); } pawn->target = NULL; pawn->lastenemy = NULL; pawn->player->mo = pawn; pawn->player->camera = pawn; pawn->player->viewheight = pawn->ViewHeight; pawn->flags2 &= ~MF2_BLASTED; if (oldpawn != nullptr) { DObject::StaticPointerSubstitution (oldpawn, pawn); oldpawn->Destroy(); } if (pawndup != NULL) { pawndup->Destroy(); } pawn->LinkToWorld (nullptr); pawn->ClearInterpolation(); pawn->AddToHash (); pawn->SetState(pawn->SpawnState); pawn->player->SendPitchLimits(); for (inv = pawn->Inventory; inv != NULL; inv = inv->Inventory) { inv->ChangeStatNum (STAT_INVENTORY); inv->LinkToWorld (nullptr); inv->Travelled (); } if (ib_compatflags & BCOMPATF_RESETPLAYERSPEED) { pawn->Speed = pawn->GetDefault()->Speed; } if (level.FromSnapshot) { FBehavior::StaticStartTypedScripts (SCRIPT_Return, pawn, true); // [Nash] run REOPEN scripts upon map re-entry FBehavior::StaticStartTypedScripts(SCRIPT_Reopen, NULL, false); } } bglobal.FinishTravel (); // make sure that, after travelling has completed, no travelling thinkers are left. // Since this list is excluded from regular thinker cleaning, anything that may survive through here // will endlessly multiply and severely break the following savegames or just simply crash on broken pointers. DThinker::DestroyThinkersInList(STAT_TRAVELLING); }
// Shift stuff back in time before doing hitscan calculations // Call UNLAGGED_Restore afterwards to restore everything void UNLAGGED_Reconcile( AActor *actor ) { //Only do anything if the actor to be reconciled is a player, //it's on a server with unlagged on, and reconciliation is not being blocked if ( !actor->player || (NETWORK_GetState() != NETSTATE_SERVER) || ( dmflags3 & DF3_NOUNLAGGED ) || ( actor->player->userinfo.bUnlagged == false ) || ( reconciliationBlockers > 0 ) ) return; //Something went wrong, reconciliation was attempted when the gamestate //was already reconciled! if (reconciledGame) { // [BB] I_Error terminates the current game, so we need to reset the value of reconciledGame, // otherwise UNLAGGED_Reconcile will always trigger this error from now on. reconciledGame = false; I_Error("UNLAGGED_Reconcile called while reconciledGame is true"); } const int unlaggedGametic = UNLAGGED_Gametic( actor->player ); //Don't reconcile if the unlagged gametic is the same as the current //because unlagged data for this tic may not be completely recorded yet if (unlaggedGametic == gametic) return; reconciledGame = true; //find the index const int unlaggedIndex = unlaggedGametic % UNLAGGEDTICS; //reconcile the sectors for (int i = 0; i < numsectors; ++i) { sectors[i].floorplane.restoreD = sectors[i].floorplane.d; sectors[i].ceilingplane.restoreD = sectors[i].ceilingplane.d; sectors[i].floorplane.d = sectors[i].floorplane.unlaggedD[unlaggedIndex]; sectors[i].ceilingplane.d = sectors[i].ceilingplane.unlaggedD[unlaggedIndex]; } //reconcile the players for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i] && players[i].mo && !players[i].bSpectating) { players[i].restoreX = players[i].mo->x; players[i].restoreY = players[i].mo->y; players[i].restoreZ = players[i].mo->z; //Work around limitations of SetOrigin to prevent players //from getting stuck in ledges players[i].restoreFloorZ = players[i].mo->floorz; //Also, don't reconcile the shooter because the client is supposed //to predict him if (players+i != actor->player) { players[i].mo->SetOrigin( players[i].unlaggedX[unlaggedIndex], players[i].unlaggedY[unlaggedIndex], players[i].unlaggedZ[unlaggedIndex] ); } else //However, the client sometimes mispredicts itself if it's on a moving sector. //We need to correct for that. { //current server floorz/ceilingz before reconciliation fixed_t serverFloorZ = actor->floorz; fixed_t serverCeilingZ = actor->ceilingz; // [BB] Try to reset floorz/ceilingz to account for the fact that the sector the actor is in was possibly reconciled. actor->floorz = actor->Sector->floorplane.ZatPoint (actor->x, actor->y); actor->ceilingz = actor->Sector->ceilingplane.ZatPoint (actor->x, actor->y); P_FindFloorCeiling(actor, false); //force the shooter out of the floor/ceiling - a client has to mispredict in this case, //because not mispredicting would mean the client would think he's inside the floor/ceiling if (actor->z + actor->height > actor->ceilingz) actor->z = actor->ceilingz - actor->height; if (actor->z < actor->floorz) actor->z = actor->floorz; //floor moved up - a client might have mispredicted himself too low due to gravity //and the client thinking the floor is lower than it actually is // [BB] But only do this if the sector actually moved. Note: This adjustment seems to break on some kind of non-moving 3D floors. if ( (serverFloorZ > actor->floorz) && (( actor->Sector->floorplane.restoreD != actor->Sector->floorplane.d ) || ( actor->Sector->ceilingplane.restoreD != actor->Sector->ceilingplane.d )) ) { //shooter was standing on the floor, let's pull him down to his floor if //he wasn't falling if ( (actor->z == serverFloorZ) && (actor->momz >= 0) ) actor->z = actor->floorz; //todo: more correction for floor moving up } //todo: more correction for client misprediction } } } }