1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: p_snapshot.cpp 2785 2012-02-18 23:22:07Z dr_sean $
5 //
6 // Copyright (C) 2000-2006 by Sergey Makovkin (CSDoom .62).
7 // Copyright (C) 2006-2014 by The Odamex Team.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // DESCRIPTION:
20 // Stores a limited view of actor, player, and sector objects at a particular
21 // point in time. Used with Unlagged, client-side prediction, and positional
22 // interpolation
23 //
24 //-----------------------------------------------------------------------------
25
26
27 #include <math.h>
28 #include "actor.h"
29 #include "d_player.h"
30 #include "p_local.h"
31 #include "p_spec.h"
32 #include "m_vectors.h"
33
34 #include "p_snapshot.h"
35
36 static const int MAX_EXTRAPOLATION = 4;
37
38 static const fixed_t POS_LERP_THRESHOLD = 2 * FRACUNIT;
39 static const fixed_t SECTOR_LERP_THRESHOLD = 2 * FRACUNIT;
40
41 extern bool predicting;
42
43 // ============================================================================
44 //
45 // Snapshot implementation
46 //
47 // ============================================================================
48
Snapshot(int time)49 Snapshot::Snapshot(int time) :
50 mTime(time), mValid(time > 0),
51 mAuthoritative(false), mContinuous(true),
52 mInterpolated(false), mExtrapolated(false)
53 {
54 }
55
operator ==(const Snapshot & other) const56 bool Snapshot::operator==(const Snapshot &other) const
57 {
58 return mTime == other.mTime &&
59 mValid == other.mValid &&
60 mAuthoritative == other.mAuthoritative &&
61 mContinuous == other.mContinuous &&
62 mInterpolated == other.mInterpolated &&
63 mExtrapolated == other.mExtrapolated;
64 }
65
66 // ============================================================================
67 //
68 // ActorSnapshot implementation
69 //
70 // ============================================================================
71
ActorSnapshot(int time)72 ActorSnapshot::ActorSnapshot(int time) :
73 Snapshot(time), mFields(0),
74 mX(0), mY(0), mZ(0),
75 mMomX(0), mMomY(0), mMomZ(0), mAngle(0), mPitch(0), mOnGround(true),
76 mCeilingZ(0), mFloorZ(0), mReactionTime(0), mWaterLevel(0),
77 mFlags(0), mFlags2(0), mFrame(0)
78 {
79 }
80
ActorSnapshot(int time,const AActor * mo)81 ActorSnapshot::ActorSnapshot(int time, const AActor *mo) :
82 Snapshot(time), mFields(0xFFFFFFFF),
83 mX(mo->x), mY(mo->y), mZ(mo->z),
84 mMomX(mo->momx), mMomY(mo->momy), mMomZ(mo->momz),
85 mAngle(mo->angle), mPitch(mo->pitch), mOnGround(mo->onground),
86 mCeilingZ(mo->ceilingz), mFloorZ(mo->floorz),
87 mReactionTime(mo->reactiontime), mWaterLevel(mo->waterlevel),
88 mFlags(mo->flags), mFlags2(mo->flags2), mFrame(mo->frame)
89 {
90 }
91
operator ==(const ActorSnapshot & other) const92 bool ActorSnapshot::operator==(const ActorSnapshot &other) const
93 {
94 return Snapshot::operator==(other) &&
95 mFields == other.mFields &&
96 mX == other.mX &&
97 mY == other.mY &&
98 mZ == other.mZ &&
99 mMomX == other.mMomX &&
100 mMomY == other.mMomY &&
101 mMomZ == other.mMomZ &&
102 mAngle == other.mAngle &&
103 mPitch == other.mPitch &&
104 mOnGround == other.mOnGround &&
105 mCeilingZ == other.mCeilingZ &&
106 mFloorZ == other.mFloorZ &&
107 mReactionTime == other.mReactionTime &&
108 mWaterLevel == other.mWaterLevel &&
109 mFlags == other.mFlags &&
110 mFlags2 == other.mFlags2 &&
111 mFrame == other.mFrame;
112 }
113
merge(const ActorSnapshot & other)114 void ActorSnapshot::merge(const ActorSnapshot& other)
115 {
116 if (other.mFields & ACT_POSITIONX)
117 setX(other.mX);
118 if (other.mFields & ACT_POSITIONY)
119 setY(other.mY);
120 if (other.mFields & ACT_POSITIONZ)
121 setZ(other.mZ);
122 if (other.mFields & ACT_MOMENTUMX)
123 setMomX(other.mMomX);
124 if (other.mFields & ACT_MOMENTUMY)
125 setMomY(other.mMomY);
126 if (other.mFields & ACT_MOMENTUMZ)
127 setMomZ(other.mMomZ);
128 if (other.mFields & ACT_ANGLE)
129 setAngle(other.mAngle);
130 if (other.mFields & ACT_PITCH)
131 setPitch(other.mPitch);
132 if (other.mFields & ACT_ONGROUND)
133 setOnGround(other.mOnGround);
134 if (other.mFields & ACT_CEILINGZ)
135 setCeilingZ(other.mCeilingZ);
136 if (other.mFields & ACT_FLOORZ)
137 setFloorZ(other.mFloorZ);
138 if (other.mFields & ACT_REACTIONTIME)
139 setReactionTime(other.mReactionTime);
140 if (other.mFields & ACT_WATERLEVEL)
141 setWaterLevel(other.mWaterLevel);
142 if (other.mFields & ACT_FLAGS)
143 setFlags(other.mFlags);
144 if (other.mFields & ACT_FLAGS2)
145 setFlags2(other.mFlags2);
146 if (other.mFields & ACT_FRAME)
147 setFrame(other.mFrame);
148
149 if (other.isContinuous())
150 setContinuous(true);
151 if (other.isAuthoritative())
152 setAuthoritative(true);
153 }
154
toActor(AActor * mo) const155 void ActorSnapshot::toActor(AActor *mo) const
156 {
157 if (!mo)
158 return;
159
160 if (mFields & (ACT_POSITIONX | ACT_POSITIONY | ACT_POSITIONZ))
161 {
162 fixed_t destx = mX, desty = mY, destz = mZ;
163 if (isInterpolated() || isExtrapolated())
164 {
165 // need to check for collisions before moving
166 P_TestActorMovement(mo, mX, mY, mZ, destx, desty, destz);
167
168 #ifdef _SNAPSHOT_DEBUG_
169 if (mX != destx || mY != desty || mZ != destz)
170 Printf(PRINT_HIGH, "Snapshot %i: ActorSnapshot::toActor() clipping movement.\n", getTime());
171 #endif // _SNAPSHOT_DEBUG_
172 }
173
174 // [SL] 2011-11-06 - Avoid setting the actor's floorz value if it hasn't moved.
175 // This ensures the floorz value is correct for actors that have spawned too
176 // close to a ledge but have not yet moved.
177 if (mo->x != destx || mo->y != desty || mo->z != destz)
178 {
179 P_CheckPosition(mo, destx, desty);
180
181 mo->UnlinkFromWorld();
182 mo->x = destx;
183 mo->y = desty;
184 mo->z = destz;
185
186 mo->ceilingz = tmceilingz;
187 mo->floorz = tmfloorz;
188 mo->dropoffz = tmdropoffz;
189 mo->floorsector = tmfloorsector;
190
191 mo->LinkToWorld();
192 }
193 }
194
195 if (mFields & (ACT_MOMENTUMX | ACT_MOMENTUMY | ACT_MOMENTUMZ))
196 {
197 mo->momx = mMomX;
198 mo->momy = mMomY;
199 mo->momz = mMomZ;
200 }
201
202 // Only set a player's angle if he is alive. Otherwise it will
203 // interfere with the deathcam
204 if (mFields & ACT_ANGLE && (!mo->player || mo->player->playerstate != PST_DEAD))
205 mo->angle = mAngle;
206
207 if (mFields & ACT_PITCH)
208 mo->pitch = mPitch;
209 if (mFields & ACT_ONGROUND)
210 mo->onground = mOnGround;
211 if (mFields & ACT_CEILINGZ)
212 mo->ceilingz = mCeilingZ;
213 if (mFields & ACT_FLOORZ)
214 mo->floorz = mFloorZ;
215 if (mFields & ACT_REACTIONTIME)
216 mo->reactiontime = mReactionTime;
217 if (mFields & ACT_WATERLEVEL)
218 mo->waterlevel = mWaterLevel;
219 if (mFields & ACT_FLAGS)
220 mo->flags = mFlags;
221 if (mFields & ACT_FLAGS2)
222 mo->flags2 = mFlags2;
223 if (mFields & ACT_FRAME)
224 mo->frame = mFrame;
225 }
226
227 // ============================================================================
228 //
229 // PlayerSnapshot implementation
230 //
231 // ============================================================================
232
PlayerSnapshot(int time)233 PlayerSnapshot::PlayerSnapshot(int time) :
234 Snapshot(time), mFields(0),
235 mActorSnap(time),
236 mViewHeight(0), mDeltaViewHeight(0), mJumpTime(0)
237 {
238 }
239
PlayerSnapshot(int time,player_t * player)240 PlayerSnapshot::PlayerSnapshot(int time, player_t *player) :
241 Snapshot(time), mFields(0xFFFFFFFF),
242 mActorSnap(time, player->mo),
243 mViewHeight(player->viewheight), mDeltaViewHeight(player->deltaviewheight),
244 mJumpTime(player->jumpTics)
245 {
246 }
247
operator ==(const PlayerSnapshot & other) const248 bool PlayerSnapshot::operator==(const PlayerSnapshot &other) const
249 {
250 return Snapshot::operator==(other) &&
251 mFields == other.mFields &&
252 mActorSnap == other.mActorSnap &&
253 mViewHeight == other.mViewHeight &&
254 mDeltaViewHeight == other.mDeltaViewHeight &&
255 mJumpTime == other.mJumpTime;
256 }
257
merge(const PlayerSnapshot & other)258 void PlayerSnapshot::merge(const PlayerSnapshot& other)
259 {
260 mActorSnap.merge(other.mActorSnap);
261
262 if (other.mFields & PLY_VIEWHEIGHT)
263 setViewHeight(other.mViewHeight);
264 if (other.mFields & PLY_DELTAVIEWHEIGHT)
265 setDeltaViewHeight(other.mDeltaViewHeight);
266 if (other.mFields & PLY_JUMPTIME)
267 setJumpTime(other.mJumpTime);
268 }
269
toPlayer(player_t * player) const270 void PlayerSnapshot::toPlayer(player_t *player) const
271 {
272 if (!player || !player->mo)
273 return;
274
275 mActorSnap.toActor(player->mo);
276
277 if (mFields & PLY_VIEWHEIGHT)
278 player->viewheight = mViewHeight;
279 if (mFields & PLY_DELTAVIEWHEIGHT)
280 player->deltaviewheight = mDeltaViewHeight;
281 if (mFields & PLY_JUMPTIME)
282 player->jumpTics = mJumpTime;
283 }
284
285
286 // ============================================================================
287 //
288 // PlayerSnapshotManager implementation
289 //
290 // ============================================================================
291
PlayerSnapshotManager()292 PlayerSnapshotManager::PlayerSnapshotManager() :
293 mMostRecent(0)
294 {
295 clearSnapshots();
296 }
297
298 //
299 // PlayerSnapshotManager::clearSnapshots()
300 //
301 // Marks all of the snapshots in the container invalid, effectively
302 // clearing the container.
303 //
clearSnapshots()304 void PlayerSnapshotManager::clearSnapshots()
305 {
306 // Set the time for all snapshots to an invalid value
307 for (int i = 0; i < NUM_SNAPSHOTS; i++)
308 mSnaps[i].setTime(-1);
309
310 mMostRecent = 0;
311 }
312
313 //
314 // PlayerSnapshotManager::mValidSnapshot()
315 //
316 // Returns true if a snapshot at the given time is present in the container
317 //
mValidSnapshot(int time) const318 bool PlayerSnapshotManager::mValidSnapshot(int time) const
319 {
320
321 return ((time <= mMostRecent) && (mMostRecent - time <= NUM_SNAPSHOTS) &&
322 (time > 0) && (mSnaps[time % NUM_SNAPSHOTS].isValid()) &&
323 (mSnaps[time % NUM_SNAPSHOTS].getTime() == time));
324 }
325
326 //
327 // PlayerSnapshotManager::mInterpolateSnapshots()
328 //
329 // Returns a snapshot based on the snapshot at the time "to" with a position
330 // linearly interpolated between snapshots "from" and "to".
331 //
332 // Note: The caller of this function has the responsibility for verifying
333 // that the container has valid snapshots at the times "from" and "to".
334 //
mInterpolateSnapshots(int from,int to,int time) const335 PlayerSnapshot PlayerSnapshotManager::mInterpolateSnapshots(int from, int to, int time) const
336 {
337 // Assumes that range checking from and to has been performed by the caller
338 const PlayerSnapshot *snapfrom = &mSnaps[from % NUM_SNAPSHOTS];
339 const PlayerSnapshot *snapto = &mSnaps[to % NUM_SNAPSHOTS];
340
341 if (to == from || !snapto->isContinuous())
342 return *snapto;
343
344 float amount = float(time - from) / float(to - from);
345
346 return P_LerpPlayerPosition(*snapfrom, *snapto, amount);
347 }
348
349 //
350 // PlayerSnapshotManager::mExtrapolateSnapshots()
351 //
352 // Returns a snapshot based on the snapshot at the time "from" with an
353 // estimated position calculated using the player's velocity.
354 //
355 // Note: The caller of this function has the responsibility for verifying
356 // that the container has valid snapshots at the time "from".
357 //
mExtrapolateSnapshot(int from,int time) const358 PlayerSnapshot PlayerSnapshotManager::mExtrapolateSnapshot(int from, int time) const
359 {
360 // Assumes that range checking from has been performed by the caller
361 const PlayerSnapshot *snapfrom = &mSnaps[from % NUM_SNAPSHOTS];
362
363 float amount = time - from;
364
365 return P_ExtrapolatePlayerPosition(*snapfrom, amount);
366 }
367
368 //
369 // PlayerSnapshotManager::addSnapshot()
370 //
371 // Inserts a new snapshot into the container, provided it is valid and not
372 // too old
373 //
addSnapshot(const PlayerSnapshot & snap)374 void PlayerSnapshotManager::addSnapshot(const PlayerSnapshot &snap)
375 {
376 int time = snap.getTime();
377
378 if (!snap.isValid())
379 {
380 #ifdef _SNAPSHOT_DEBUG_
381 Printf(PRINT_HIGH, "Snapshot %i: Not adding invalid player snapshot\n", time);
382 #endif // _SNAPSHOT_DEBUG_
383 return;
384 }
385
386 if (mMostRecent > snap.getTime() + NUM_SNAPSHOTS)
387 {
388 #ifdef _SNAPSHOT_DEBUG_
389 Printf(PRINT_HIGH, "Snapshot %i: Not adding expired player snapshot\n", time);
390 #endif // _SNAPSHOT_DEBUG_
391 return;
392 }
393
394 PlayerSnapshot& dest = mSnaps[time % NUM_SNAPSHOTS];
395 if (dest.getTime() == time)
396 {
397 // A valid snapshot for this time already exists. Merge it with this one.
398 dest.merge(snap);
399 }
400 else
401 {
402 dest = snap;
403 }
404
405 if (time > mMostRecent)
406 mMostRecent = time;
407 }
408
mFindValidSnapshot(int starttime,int endtime) const409 int PlayerSnapshotManager::mFindValidSnapshot(int starttime, int endtime) const
410 {
411 if (starttime < mMostRecent - NUM_SNAPSHOTS ||
412 endtime < mMostRecent - NUM_SNAPSHOTS ||
413 starttime > mMostRecent || endtime > mMostRecent)
414 return -1;
415
416 if (endtime >= starttime)
417 {
418 for (int t = starttime; t <= endtime; t++)
419 if (mValidSnapshot(t))
420 return t;
421 }
422 else
423 {
424 for (int t = starttime; t >= endtime; t--)
425 if (mValidSnapshot(t))
426 return t;
427 }
428
429 // Did not find any valid snapshots
430 return -1;
431 }
432
433 //
434 // PlayerSnapshotManager::getSnapshot()
435 //
436 // Returns a snapshot from the container at a specified time.
437 // If there is not a snapshot matching the time, one is generated using
438 // interpolation or extrapolation.
439 //
getSnapshot(int time) const440 PlayerSnapshot PlayerSnapshotManager::getSnapshot(int time) const
441 {
442 if (time <= 0 || mMostRecent <= 0)
443 return PlayerSnapshot();
444
445 // Return the requested snapshot if availible
446 if (mValidSnapshot(time))
447 return mSnaps[time % NUM_SNAPSHOTS];
448
449 // Should we extrapolate?
450 if (time > mMostRecent)
451 {
452 int amount = time - mMostRecent;
453 if (amount > MAX_EXTRAPOLATION) // cap extrapolation
454 {
455 #ifdef _SNAPSHOT_DEBUG_
456 Printf(PRINT_HIGH, "Extrap %i: PlayerSnapshotManager::getSnapshot() capping extrapolation past %i\n",
457 time, mMostRecent);
458 #endif // _SNAPSHOT_DEBUG_
459
460 amount = MAX_EXTRAPOLATION;
461 }
462
463 #ifdef _SNAPSHOT_DEBUG_
464 Printf(PRINT_HIGH, "Extrap %i: PlayerSnapshotManager::getSnapshot() extrapolating past %i\n",
465 time, mMostRecent);
466 #endif // _SNAPSHOT_DEBUG_
467
468 return mExtrapolateSnapshot(mMostRecent, amount + mMostRecent);
469 }
470
471 // find the snapshot that precedes the desired time
472 int pretime = mFindValidSnapshot(time, mMostRecent - NUM_SNAPSHOTS);
473
474 // find the snapshot that follows the desired time
475 int posttime = mFindValidSnapshot(time, mMostRecent);
476
477 // Can we interpolate?
478 if (pretime > 0 && posttime > 0 && time < posttime && time > pretime)
479 {
480 #ifdef _SNAPSHOT_DEBUG_
481 Printf(PRINT_HIGH, "Lerp %i: PlayerSnapshotManager::getSnapshot() interpolating between %i and %i.\n",
482 time, pretime, posttime);
483 #endif // _SNAPSHOT_DEBUG_
484
485 return mInterpolateSnapshots(pretime, posttime, time);
486 }
487
488 // No snapshots before the desired time? Return the closest one to it
489 if (pretime <= 0 && posttime > 0)
490 return mSnaps[posttime % NUM_SNAPSHOTS];
491
492 // Could not find a valid snapshot so return a blank (invalid) one
493 return PlayerSnapshot();
494 }
495
496
497 // ============================================================================
498 //
499 // Actor and Player Helper functions
500 //
501 // ============================================================================
502
P_PositionDifference(const v3fixed_t & a,const v3fixed_t & b)503 static fixed_t P_PositionDifference(const v3fixed_t &a, const v3fixed_t &b)
504 {
505 v3fixed_t diff;
506 M_SubVec3Fixed(&diff, &b, &a);
507
508 return M_LengthVec3Fixed(&diff);
509 }
510
511 //
512 // P_ExtrapolateActorPosition
513 //
514 //
P_ExtrapolateActorPosition(const ActorSnapshot & from,float amount)515 ActorSnapshot P_ExtrapolateActorPosition(const ActorSnapshot &from, float amount)
516 {
517 fixed_t amount_fixed = FLOAT2FIXED(amount);
518
519 if (amount_fixed <= 0)
520 return from;
521
522 v3fixed_t velocity, pos_new;
523 M_SetVec3Fixed(&pos_new, from.getX(), from.getY(), from.getZ());
524 M_SetVec3Fixed(&velocity, from.getMomX(), from.getMomY(), from.getMomZ());
525 M_ScaleVec3Fixed(&velocity, &velocity, amount_fixed);
526 M_AddVec3Fixed(&pos_new, &pos_new, &velocity);
527
528 ActorSnapshot newsnapshot(from);
529 newsnapshot.setAuthoritative(false);
530 newsnapshot.setExtrapolated(true);
531 newsnapshot.setX(pos_new.x);
532 newsnapshot.setY(pos_new.y);
533 newsnapshot.setZ(pos_new.z);
534
535 return newsnapshot;
536 }
537
538
P_ExtrapolatePlayerPosition(const PlayerSnapshot & from,float amount)539 PlayerSnapshot P_ExtrapolatePlayerPosition(const PlayerSnapshot &from, float amount)
540 {
541 PlayerSnapshot newsnap(from);
542 newsnap.mActorSnap = P_ExtrapolateActorPosition(from.mActorSnap, amount);
543 newsnap.setExtrapolated(newsnap.mActorSnap.isExtrapolated());
544
545 return newsnap;
546 }
547
548
549 //
550 // P_LerpActorPosition
551 //
552 // Returns a snapshot containing a position that is a percentage of the distance
553 // between the positions contained in from and to. Besides the position, the
554 // returned snapshot will be identical to the snapshot to.
555 //
556 // Note that amount will most often be between 0.0 and 1.0, however it can be
557 // larger than 1.0 as an alternative method of extrapolating a position.
558 //
P_LerpActorPosition(const ActorSnapshot & from,const ActorSnapshot & to,float amount)559 ActorSnapshot P_LerpActorPosition(const ActorSnapshot &from, const ActorSnapshot &to, float amount)
560 {
561 fixed_t amount_fixed = FLOAT2FIXED(amount);
562
563 if (amount_fixed <= 0)
564 return from;
565
566 // Calculate the distance between the positions
567 v3fixed_t pos_from, pos_to;
568 M_SetVec3Fixed(&pos_from, from.getX(), from.getY(), from.getZ());
569 M_SetVec3Fixed(&pos_to, to.getX(), to.getY(), to.getZ());
570 fixed_t pos_delta = P_PositionDifference(pos_from, pos_to);
571
572 #ifdef _SNAPSHOT_DEBUG_
573 if (pos_delta)
574 Printf(PRINT_HIGH, "Lerp: MF2_ONMOBJ = %s\n", from.getFlags2() & MF2_ONMOBJ ? "yes" : "no");
575 #endif // _SNAPSHOT_DEBUG_
576
577 if (pos_delta <= POS_LERP_THRESHOLD || !to.isContinuous())
578 {
579 // snap directly to the new position
580 #ifdef _SNAPSHOT_DEBUG_
581 if (pos_delta)
582 Printf(PRINT_HIGH, "Lerp: %d Snapping to position (delta %d)\n",
583 gametic, pos_delta >> FRACBITS);
584 #endif // _SNAPSHOT_DEBUG_
585 return to;
586 }
587
588 // Calculate the interpolated position between pos_from and pos_to
589 v3fixed_t pos_new;
590 M_SubVec3Fixed(&pos_new, &pos_to, &pos_from);
591 M_ScaleVec3Fixed(&pos_new, &pos_new, amount_fixed);
592 M_AddVec3Fixed(&pos_new, &pos_new, &pos_from);
593
594 #ifdef _SNAPSHOT_DEBUG_
595 Printf(PRINT_HIGH, "Lerp: %d, Lerping to position (delta %d)\n",
596 gametic, pos_delta >> FRACBITS);
597 #endif // _SNAPSHOT_DEBUG_
598
599 // lerp the angle
600 int anglediff = int(to.getAngle()) - int(from.getAngle());
601 angle_t angle = from.getAngle() + FixedMul(anglediff, amount_fixed);
602
603 #ifdef _SNAPSHOT_DEBUG_
604 if (anglediff)
605 Printf(PRINT_HIGH, "Lerp: %d, Lerping to angle (delta %d)\n",
606 gametic, anglediff >> ANGLETOFINESHIFT);
607 #endif // _SNAPSHOT_DEBUG_
608
609 ActorSnapshot newsnapshot(to);
610 newsnapshot.setAuthoritative(false);
611 newsnapshot.setInterpolated(true);
612 newsnapshot.setX(pos_new.x);
613 newsnapshot.setY(pos_new.y);
614 newsnapshot.setZ(pos_new.z);
615 newsnapshot.setAngle(angle);
616
617 return newsnapshot;
618 }
619
620
P_LerpPlayerPosition(const PlayerSnapshot & from,const PlayerSnapshot & to,float amount)621 PlayerSnapshot P_LerpPlayerPosition(const PlayerSnapshot &from, const PlayerSnapshot &to, float amount)
622 {
623 PlayerSnapshot newsnap(to);
624 newsnap.mActorSnap = P_LerpActorPosition(from.mActorSnap, to.mActorSnap, amount);
625 newsnap.setInterpolated(newsnap.mActorSnap.isInterpolated());
626
627 return newsnap;
628 }
629
630
631 //
632 // P_SetPlayerSnapshotNoPosition
633 //
634 // Handles the special case where we want the player's position to not change
635 // but would like to apply all of the other player characteristics from the
636 // snapshot to the player.
637 //
P_SetPlayerSnapshotNoPosition(player_t * player,const PlayerSnapshot & snap)638 void P_SetPlayerSnapshotNoPosition(player_t *player, const PlayerSnapshot &snap)
639 {
640 if (!player || !player->mo)
641 return;
642
643 fixed_t x = player->mo->x;
644 fixed_t y = player->mo->y;
645 fixed_t z = player->mo->z;
646 fixed_t ceilingz = player->mo->ceilingz;
647 fixed_t floorz = player->mo->floorz;
648 fixed_t momx = player->mo->momx;
649 fixed_t momy = player->mo->momy;
650 fixed_t momz = player->mo->momz;
651
652 snap.toPlayer(player);
653
654 player->mo->UnlinkFromWorld();
655 player->mo->x = x;
656 player->mo->y = y;
657 player->mo->z = z;
658 player->mo->ceilingz = ceilingz;
659 player->mo->floorz = floorz;
660 player->mo->momx = momx;
661 player->mo->momy = momy;
662 player->mo->momz = momz;
663 player->mo->LinkToWorld();
664 }
665
666
667 // ============================================================================
668 //
669 // SectorSnapshot implementation
670 //
671 // ============================================================================
672
673
SectorSnapshot(int time)674 SectorSnapshot::SectorSnapshot(int time) :
675 Snapshot(time), mCeilingMoverType(SEC_INVALID), mFloorMoverType(SEC_INVALID),
676 mSector(NULL), mCeilingType(0), mFloorType(0), mCeilingTag(0), mFloorTag(0),
677 mCeilingLine(NULL), mFloorLine(NULL), mCeilingHeight(0), mFloorHeight(0),
678 mCeilingSpeed(0), mFloorSpeed(0), mCeilingDestination(0), mFloorDestination(0),
679 mCeilingDirection(0), mFloorDirection(0), mCeilingOldDirection(0), mFloorOldDirection(0),
680 mCeilingTexture(0), mFloorTexture(0),
681 mNewCeilingSpecial(0), mNewFloorSpecial(0), mCeilingLow(0), mCeilingHigh(0),
682 mFloorLow(0), mFloorHigh(0), mCeilingCrush(false), mFloorCrush(false), mSilent(false),
683 mCeilingWait(0), mFloorWait(0), mCeilingCounter(0), mFloorCounter(0), mResetCounter(0),
684 mCeilingStatus(0), mFloorStatus(0), mOldFloorStatus(0),
685 mCrusherSpeed1(0), mCrusherSpeed2(0), mStepTime(0), mPerStepTime(0), mPauseTime(0),
686 mOrgHeight(0), mDelay(0), mFloorLip(0), mFloorOffset(0), mCeilingChange(0), mFloorChange(0)
687 {
688 }
689
SectorSnapshot(int time,sector_t * sector)690 SectorSnapshot::SectorSnapshot(int time, sector_t *sector) :
691 Snapshot(time), mCeilingMoverType(SEC_INVALID), mFloorMoverType(SEC_INVALID),
692 mSector(sector)
693 {
694 if (!sector)
695 {
696 clear();
697 return;
698 }
699
700 mCeilingHeight = P_CeilingHeight(sector);
701 mFloorHeight = P_FloorHeight(sector);
702
703 if (sector->floordata)
704 {
705 if (sector->floordata->IsA(RUNTIME_CLASS(DElevator)))
706 {
707 DElevator *elevator = static_cast<DElevator *>(sector->floordata);
708 mCeilingMoverType = SEC_ELEVATOR;
709 mFloorMoverType = SEC_ELEVATOR;
710 mCeilingType = elevator->m_Type;
711 mFloorType = elevator->m_Type;
712 mCeilingStatus = elevator->m_Status;
713 mFloorStatus = elevator->m_Status;
714 mCeilingDirection = elevator->m_Direction;
715 mFloorDirection = elevator->m_Direction;
716 mCeilingDestination = elevator->m_CeilingDestHeight;
717 mFloorDestination = elevator->m_FloorDestHeight;
718 mCeilingSpeed = elevator->m_Speed;
719 mFloorSpeed = elevator->m_Speed;
720 }
721 else if (sector->floordata->IsA(RUNTIME_CLASS(DPillar)))
722 {
723 DPillar *pillar = static_cast<DPillar *>(sector->floordata);
724 mCeilingMoverType = SEC_PILLAR;
725 mFloorMoverType = SEC_PILLAR;
726 mCeilingType = pillar->m_Type;
727 mFloorType = pillar->m_Type;
728 mCeilingStatus = pillar->m_Status;
729 mFloorStatus = pillar->m_Status;
730 mCeilingSpeed = pillar->m_CeilingSpeed;
731 mFloorSpeed = pillar->m_FloorSpeed;
732 mCeilingDestination = pillar->m_CeilingTarget;
733 mFloorDestination = pillar->m_FloorTarget;
734 mCeilingCrush = pillar->m_Crush;
735 mFloorCrush = pillar->m_Crush;
736 }
737 else if (sector->floordata->IsA(RUNTIME_CLASS(DFloor)))
738 {
739 DFloor *floor = static_cast<DFloor *>(sector->floordata);
740 mFloorMoverType = SEC_FLOOR;
741 mFloorType = floor->m_Type;
742 mFloorStatus = floor->m_Status;
743 mFloorCrush = floor->m_Crush;
744 mFloorDirection = floor->m_Direction;
745 mNewFloorSpecial = floor->m_NewSpecial;
746 mFloorTexture = floor->m_Texture;
747 mFloorDestination = floor->m_FloorDestHeight;
748 mFloorSpeed = floor->m_Speed;
749 mStepTime = floor->m_StepTime;
750 mPerStepTime = floor->m_PerStepTime;
751 mResetCounter = floor->m_ResetCount;
752 mPauseTime = floor->m_PauseTime;
753 mDelay = floor->m_Delay;
754 mOrgHeight = floor->m_OrgHeight;
755 mFloorLine = floor->m_Line;
756 mFloorOffset = floor->m_Height;
757 mFloorChange = floor->m_Change;
758 }
759 else if (sector->floordata->IsA(RUNTIME_CLASS(DPlat)))
760 {
761 DPlat *plat = static_cast<DPlat *>(sector->floordata);
762 mFloorMoverType = SEC_PLAT;
763 mFloorType = plat->m_Type;
764 mFloorTag = plat->m_Tag;
765 mFloorCrush = plat->m_Crush;
766 mFloorSpeed = plat->m_Speed;
767 mFloorLow = plat->m_Low;
768 mFloorHigh = plat->m_High;
769 mFloorWait = plat->m_Wait;
770 mFloorCounter = plat->m_Count;
771 mFloorStatus = plat->m_Status;
772 mOldFloorStatus = plat->m_OldStatus;
773 mFloorOffset = plat->m_Height;
774 mFloorLip = plat->m_Lip;
775 }
776 }
777
778 if (sector->ceilingdata)
779 {
780 if (sector->ceilingdata->IsA(RUNTIME_CLASS(DCeiling)))
781 {
782 DCeiling *ceiling = static_cast<DCeiling *>(sector->ceilingdata);
783 mCeilingMoverType = SEC_CEILING;
784 mCeilingType = ceiling->m_Type;
785 mCeilingStatus = ceiling->m_Status;
786 mCeilingTag = ceiling->m_Tag;
787 mCeilingCrush = ceiling->m_Crush;
788 mSilent = (ceiling->m_Silent != 0);
789 mCeilingLow = ceiling->m_BottomHeight;
790 mCeilingHigh = ceiling->m_TopHeight;
791 mCeilingSpeed = ceiling->m_Speed;
792 mCeilingDirection = ceiling->m_Direction;
793 mCeilingTexture = ceiling->m_Texture;
794 mNewCeilingSpecial = ceiling->m_NewSpecial;
795 mCrusherSpeed1 = ceiling->m_Speed1;
796 mCrusherSpeed2 = ceiling->m_Speed2;
797 mCeilingOldDirection = ceiling->m_OldDirection;
798 }
799 else if (sector->ceilingdata->IsA(RUNTIME_CLASS(DDoor)))
800 {
801 DDoor *door = static_cast<DDoor *>(sector->ceilingdata);
802 mCeilingMoverType = SEC_DOOR;
803 mCeilingType = door->m_Type;
804 mCeilingHigh = door->m_TopHeight;
805 mCeilingSpeed = door->m_Speed;
806 mCeilingWait = door->m_TopWait;
807 mCeilingCounter = door->m_TopCountdown;
808 mCeilingStatus = door->m_Status;
809 mCeilingLine = door->m_Line;
810 }
811 }
812 }
813
clear()814 void SectorSnapshot::clear()
815 {
816 setTime(-1);
817 mCeilingMoverType = SEC_INVALID;
818 mFloorMoverType = SEC_INVALID;
819 }
820
toSector(sector_t * sector) const821 void SectorSnapshot::toSector(sector_t *sector) const
822 {
823 if (!sector)
824 return;
825
826 P_SetCeilingHeight(sector, mCeilingHeight);
827 P_SetFloorHeight(sector, mFloorHeight);
828 P_ChangeSector(sector, false);
829
830 if (mCeilingMoverType == SEC_PILLAR && mCeilingStatus != DPillar::destroy)
831 {
832 if (sector->ceilingdata && !sector->ceilingdata->IsA(RUNTIME_CLASS(DPillar)))
833 {
834 sector->ceilingdata->Destroy();
835 sector->ceilingdata = NULL;
836
837 }
838 if (sector->floordata && !sector->ceilingdata->IsA(RUNTIME_CLASS(DPillar)))
839 {
840 sector->floordata->Destroy();
841 sector->floordata = NULL;
842 }
843
844 if (!sector->ceilingdata)
845 {
846 sector->ceilingdata = new DPillar();
847 sector->floordata = sector->ceilingdata;
848 }
849
850 DPillar *pillar = static_cast<DPillar *>(sector->ceilingdata);
851 pillar->m_Type = static_cast<DPillar::EPillar>(mCeilingType);
852 pillar->m_Status = static_cast<DPillar::EPillarState>(mCeilingStatus);
853 pillar->m_CeilingSpeed = mCeilingSpeed;
854 pillar->m_FloorSpeed = mFloorSpeed;
855 pillar->m_CeilingTarget = mCeilingDestination;
856 pillar->m_FloorTarget = mFloorDestination;
857 pillar->m_Crush = mCeilingCrush;
858 }
859
860 if (mCeilingMoverType == SEC_ELEVATOR && mCeilingStatus != DElevator::destroy)
861 {
862 if (sector->ceilingdata && !sector->ceilingdata->IsA(RUNTIME_CLASS(DElevator)))
863 {
864 sector->ceilingdata->Destroy();
865 sector->ceilingdata = NULL;
866 }
867 if (sector->floordata && !sector->floordata->IsA(RUNTIME_CLASS(DElevator)))
868 {
869 sector->floordata->Destroy();
870 sector->floordata = NULL;
871 }
872
873 if (!sector->ceilingdata)
874 {
875 sector->ceilingdata = new DElevator(sector);
876 sector->floordata = sector->ceilingdata;
877 }
878
879 DElevator *elevator = static_cast<DElevator *>(sector->ceilingdata);
880 elevator->m_Type = static_cast<DElevator::EElevator>(mCeilingType);
881 elevator->m_Status = static_cast<DElevator::EElevatorState>(mCeilingStatus);
882 elevator->m_Direction = mCeilingDirection;
883 elevator->m_CeilingDestHeight = mCeilingDestination;
884 elevator->m_FloorDestHeight = mFloorDestination;
885 elevator->m_Speed = mCeilingSpeed;
886 }
887
888 if (mCeilingMoverType == SEC_CEILING && mCeilingStatus != DCeiling::destroy)
889 {
890 if (sector->ceilingdata && !sector->ceilingdata->IsA(RUNTIME_CLASS(DCeiling)))
891 {
892 sector->ceilingdata->Destroy();
893 sector->ceilingdata = NULL;
894 }
895
896 if (!sector->ceilingdata)
897 sector->ceilingdata = new DCeiling(sector);
898
899 DCeiling *ceiling = static_cast<DCeiling *>(sector->ceilingdata);
900 ceiling->m_Type = static_cast<DCeiling::ECeiling>(mCeilingType);
901 ceiling->m_Status = static_cast<DCeiling::ECeilingState>(mCeilingStatus);
902 ceiling->m_Tag = mCeilingTag;
903 ceiling->m_BottomHeight = mCeilingLow;
904 ceiling->m_TopHeight = mCeilingHigh;
905 ceiling->m_Speed = mCeilingSpeed;
906 ceiling->m_Speed1 = mCrusherSpeed1;
907 ceiling->m_Speed2 = mCrusherSpeed2;
908 ceiling->m_Crush = mCeilingCrush;
909 ceiling->m_Silent = mSilent;
910 ceiling->m_Direction = mCeilingDirection;
911 ceiling->m_OldDirection = mCeilingOldDirection;
912 ceiling->m_Texture = mCeilingTexture;
913 ceiling->m_NewSpecial = mNewCeilingSpecial;
914 }
915
916 if (mCeilingMoverType == SEC_DOOR && mCeilingStatus != DDoor::destroy)
917 {
918 if (sector->ceilingdata && !sector->ceilingdata->IsA(RUNTIME_CLASS(DDoor)))
919 {
920 sector->ceilingdata->Destroy();
921 sector->ceilingdata = NULL;
922 }
923
924 if (!sector->ceilingdata)
925 {
926 sector->ceilingdata =
927 new DDoor(sector, mCeilingLine,
928 static_cast<DDoor::EVlDoor>(mCeilingType),
929 mCeilingSpeed, mCeilingWait);
930 }
931
932 DDoor *door = static_cast<DDoor *>(sector->ceilingdata);
933 door->m_Type = static_cast<DDoor::EVlDoor>(mCeilingType);
934 door->m_Status = static_cast<DDoor::EDoorState>(mCeilingStatus);
935 door->m_TopHeight = mCeilingHigh;
936 door->m_Speed = mCeilingSpeed;
937 door->m_TopWait = mCeilingWait;
938 door->m_TopCountdown = mCeilingCounter;
939 door->m_Line = mCeilingLine;
940 }
941
942 if (mFloorMoverType == SEC_FLOOR && mFloorStatus != DFloor::destroy)
943 {
944 if (sector->floordata && !sector->floordata->IsA(RUNTIME_CLASS(DFloor)))
945 {
946 sector->floordata->Destroy();
947 sector->floordata = NULL;
948 }
949
950 if (!sector->floordata)
951 {
952 sector->floordata =
953 new DFloor(sector, static_cast<DFloor::EFloor>(mFloorType),
954 mFloorLine, mFloorSpeed, mFloorOffset,
955 mFloorCrush, mFloorChange);
956 }
957
958 DFloor *floor = static_cast<DFloor *>(sector->floordata);
959 floor->m_Type = static_cast<DFloor::EFloor>(mFloorType);
960 floor->m_Status = static_cast<DFloor::EFloorState>(mFloorStatus);
961 floor->m_Crush = mFloorCrush;
962 floor->m_Direction = mFloorDirection;
963 floor->m_NewSpecial = mNewFloorSpecial;
964 floor->m_FloorDestHeight = mFloorDestination;
965 floor->m_Speed = mFloorSpeed;
966 floor->m_ResetCount = mResetCounter;
967 floor->m_OrgHeight = mOrgHeight;
968 floor->m_Delay = mDelay;
969 floor->m_PauseTime = mPauseTime;
970 floor->m_StepTime = mStepTime;
971 floor->m_PerStepTime = mPerStepTime;
972 floor->m_Line = mFloorLine;
973 floor->m_Height = mFloorOffset;
974 floor->m_Change = mFloorChange;
975 }
976
977 if (mFloorMoverType == SEC_PLAT && mFloorStatus != DPlat::destroy)
978 {
979 if (sector->floordata && !sector->floordata->IsA(RUNTIME_CLASS(DPlat)))
980 {
981 sector->floordata->Destroy();
982 sector->floordata = NULL;
983 }
984
985 if (!sector->floordata)
986 {
987 sector->floordata =
988 new DPlat(sector, static_cast<DPlat::EPlatType>(mFloorType),
989 mFloorOffset, mFloorSpeed, mFloorWait, mFloorLip);
990 }
991
992 DPlat *plat = static_cast<DPlat *>(sector->floordata);
993 plat->m_Type = static_cast<DPlat::EPlatType>(mFloorType);
994 plat->m_Tag = mFloorTag;
995 plat->m_Status = static_cast<DPlat::EPlatState>(mFloorStatus);
996 plat->m_OldStatus = static_cast<DPlat::EPlatState>(mOldFloorStatus);
997 plat->m_Crush = mFloorCrush;
998 plat->m_Low = mFloorLow;
999 plat->m_High = mFloorHigh;
1000 plat->m_Speed = mFloorSpeed;
1001 plat->m_Wait = mFloorWait;
1002 plat->m_Count = mFloorCounter;
1003 plat->m_Height = mFloorOffset;
1004 plat->m_Lip = mFloorLip;
1005 }
1006 }
1007
1008 // ============================================================================
1009 //
1010 // SectorrSnapshotManager implementation
1011 //
1012 // ============================================================================
1013
SectorSnapshotManager()1014 SectorSnapshotManager::SectorSnapshotManager() :
1015 mMostRecent(0)
1016 {
1017 clearSnapshots();
1018 }
1019
1020 //
1021 // SectorSnapshotManager::clearSnapshots()
1022 //
1023 // Marks all of the snapshots in the container invalid, effectively
1024 // clearing the container.
1025 //
clearSnapshots()1026 void SectorSnapshotManager::clearSnapshots()
1027 {
1028 // Set the time for all snapshots to an invalid value
1029 for (int i = 0; i < NUM_SNAPSHOTS; i++)
1030 mSnaps[i].clear();
1031
1032 mMostRecent = 0;
1033 }
1034
1035 //
1036 // SectorSnapshotManager::mValidSnapshot()
1037 //
1038 // Returns true if a snapshot at the given time is present in the container
1039 //
mValidSnapshot(int time) const1040 bool SectorSnapshotManager::mValidSnapshot(int time) const
1041 {
1042 return ((time <= mMostRecent) && (mMostRecent - time <= NUM_SNAPSHOTS) &&
1043 (time > 0) && (mSnaps[time % NUM_SNAPSHOTS].isValid()) &&
1044 (mSnaps[time % NUM_SNAPSHOTS].getTime() == time) &&
1045 (mSnaps[time % NUM_SNAPSHOTS].getCeilingMoverType() != SEC_INVALID ||
1046 mSnaps[time % NUM_SNAPSHOTS].getFloorMoverType() != SEC_INVALID));
1047 }
1048
1049 //
1050 // SectorSnapshotManager::empty()
1051 //
1052 // Returns true if the container does not contain any valid snapshots
1053 //
empty()1054 bool SectorSnapshotManager::empty()
1055 {
1056 return (!mValidSnapshot(mMostRecent));
1057 }
1058
1059 //
1060 // SectorSnapshotManager::addSnapshot()
1061 //
1062 // Inserts a new snapshot into the container, provided it is valid and not
1063 // too old
1064 //
addSnapshot(const SectorSnapshot & newsnap)1065 void SectorSnapshotManager::addSnapshot(const SectorSnapshot &newsnap)
1066 {
1067 int time = newsnap.getTime();
1068
1069 if (!newsnap.isValid())
1070 {
1071 #ifdef _SNAPSHOT_DEBUG_
1072 Printf(PRINT_HIGH, "Snapshot %i: Not adding invalid sector snapshot\n", time);
1073 #endif // _SNAPSHOT_DEBUG_
1074 return;
1075 }
1076
1077 if (mMostRecent > newsnap.getTime() + NUM_SNAPSHOTS)
1078 {
1079 #ifdef _SNAPSHOT_DEBUG_
1080 Printf(PRINT_HIGH, "Snapshot %i: Not adding expired sector snapshot\n", time);
1081 #endif // _SNAPSHOT_DEBUG_
1082 return;
1083 }
1084
1085 mSnaps[time % NUM_SNAPSHOTS] = newsnap;
1086
1087 if (time > mMostRecent)
1088 mMostRecent = time;
1089 }
1090
1091
1092 //
1093 // SectorSnapshotManager::getSnapshot()
1094 //
1095 // Returns a snapshot from the container at a specified time.
1096 // If there is not a snapshot matching the time, one is generated by
1097 // running the moving sector's thinker function.
1098 //
getSnapshot(int time) const1099 SectorSnapshot SectorSnapshotManager::getSnapshot(int time) const
1100 {
1101 if (time <= 0 || mMostRecent <= 0)
1102 return SectorSnapshot();
1103
1104 // Return the requested snapshot if availible
1105 if (mValidSnapshot(time))
1106 return mSnaps[time % NUM_SNAPSHOTS];
1107
1108 // Find the snapshot in the container that preceeds the desired time
1109 int prevsnaptime = time;
1110 while (--prevsnaptime > mMostRecent - NUM_SNAPSHOTS)
1111 {
1112 if (mValidSnapshot(prevsnaptime))
1113 {
1114 const SectorSnapshot *snap = &mSnaps[prevsnaptime % NUM_SNAPSHOTS];
1115
1116 // turn off any sector movement sounds from RunThink()
1117 bool oldpredicting = predicting;
1118 predicting = true;
1119
1120 // create a temporary sector for the snapshot and run the
1121 // sector movement til we get to the desired time
1122 sector_t tempsector;
1123 P_CopySector(&tempsector, snap->getSector());
1124
1125 // set values for the Z parameter of the sector's planes so that
1126 // P_SetCeilingHeight/P_SetFloorHeight will work properly
1127 tempsector.floorplane.c = tempsector.floorplane.invc = FRACUNIT;
1128 tempsector.ceilingplane.c = tempsector.ceilingplane.invc = -FRACUNIT;
1129
1130 snap->toSector(&tempsector);
1131
1132 for (int i = 0; i < time - prevsnaptime; i++)
1133 {
1134 if (tempsector.ceilingdata)
1135 tempsector.ceilingdata->RunThink();
1136 if (tempsector.floordata &&
1137 tempsector.floordata != tempsector.ceilingdata)
1138 tempsector.floordata->RunThink();
1139 }
1140
1141 SectorSnapshot newsnap(time, &tempsector);
1142
1143 // clean up allocated memory
1144 if (tempsector.ceilingdata)
1145 tempsector.ceilingdata->Destroy();
1146 if (tempsector.floordata)
1147 tempsector.floordata->Destroy();
1148
1149 // restore sector movement sounds
1150 predicting = oldpredicting;
1151
1152 return newsnap;
1153 }
1154 }
1155
1156 // Could not find a valid snapshot so return a blank (invalid) one
1157 return SectorSnapshot();
1158 }
1159
1160
P_CeilingSnapshotDone(SectorSnapshot * snap)1161 bool P_CeilingSnapshotDone(SectorSnapshot *snap)
1162 {
1163 if (!snap || !snap->isValid() || snap->getCeilingMoverType() == SEC_INVALID)
1164 return true;
1165
1166 if ((snap->getCeilingMoverType() == SEC_CEILING &&
1167 snap->getCeilingStatus() == DCeiling::destroy) ||
1168 (snap->getCeilingMoverType() == SEC_DOOR &&
1169 snap->getCeilingStatus() == DDoor::destroy) ||
1170 (snap->getCeilingMoverType() == SEC_PILLAR &&
1171 snap->getCeilingStatus() == DPillar::destroy) ||
1172 (snap->getCeilingMoverType() == SEC_ELEVATOR &&
1173 snap->getCeilingStatus() == DElevator::destroy))
1174 return true;
1175
1176 return false;
1177 }
1178
P_FloorSnapshotDone(SectorSnapshot * snap)1179 bool P_FloorSnapshotDone(SectorSnapshot *snap)
1180 {
1181 if (!snap || !snap->isValid() || snap->getFloorMoverType() == SEC_INVALID)
1182 return true;
1183
1184 if ((snap->getFloorMoverType() == SEC_FLOOR &&
1185 snap->getFloorStatus() == DFloor::destroy) ||
1186 (snap->getFloorMoverType() == SEC_PLAT &&
1187 snap->getFloorStatus() == DPlat::destroy) ||
1188 (snap->getFloorMoverType() == SEC_PILLAR &&
1189 snap->getFloorStatus() == DPillar::destroy) ||
1190 (snap->getFloorMoverType() == SEC_ELEVATOR &&
1191 snap->getFloorStatus() == DElevator::destroy))
1192 return true;
1193
1194 return false;
1195 }
1196
1197 VERSION_CONTROL (p_snapshot_cpp, "$Id: p_snapshot.cpp 2785 2012-02-18 23:22:07Z dr_sean $")
1198
1199
1200