/ source / blood / src / seq.cpp
seq.cpp
  1  //-------------------------------------------------------------------------
  2  /*
  3  Copyright (C) 2010-2019 EDuke32 developers and contributors
  4  Copyright (C) 2019 Nuke.YKT
  5  
  6  This file is part of NBlood.
  7  
  8  NBlood is free software; you can redistribute it and/or
  9  modify it under the terms of the GNU General Public License version 2
 10  as published by the Free Software Foundation.
 11  
 12  This program is distributed in the hope that it will be useful,
 13  but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 15  
 16  See the GNU General Public License for more details.
 17  
 18  You should have received a copy of the GNU General Public License
 19  along with this program; if not, write to the Free Software
 20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 21  */
 22  //-------------------------------------------------------------------------
 23  #include <string.h>
 24  #include "build.h"
 25  #include "common_game.h"
 26  
 27  #include "blood.h"
 28  #include "db.h"
 29  #include "eventq.h"
 30  #include "globals.h"
 31  #include "levels.h"
 32  #include "loadsave.h"
 33  #include "sfx.h"
 34  #include "sound.h"
 35  #include "seq.h"
 36  #include "gameutil.h"
 37  #include "actor.h"
 38  #include "tile.h"
 39  #include "view.h"
 40  
 41  #define kMaxClients 256
 42  #define kMaxSequences (1024*4)
 43  #define kMaxSequencesVanilla 1024
 44  #define kSurfSoundBase  800
 45  
 46  static ACTIVE activeList[kMaxSequences];
 47  static int activeCount = 0;
 48  static char bActiveListWarnVanilla = 0;
 49  static int nClients = 0;
 50  static void(*clientCallback[kMaxClients])(int, int);
 51  
 52  int seqRegisterClient(void(*pClient)(int, int))
 53  {
 54      dassert(nClients < kMaxClients);
 55      clientCallback[nClients] = pClient;
 56      return nClients++;
 57  }
 58  
 59  void Seq::Preload(void)
 60  {
 61      if (memcmp(signature, "SEQ\x1a", 4) != 0)
 62          ThrowError("Invalid sequence");
 63      if ((version & 0xff00) != 0x300)
 64          ThrowError("Obsolete sequence version");
 65      for (int i = 0; i < nFrames; i++)
 66          tilePreloadTile(seqGetTile(&frames[i]));
 67  }
 68  
 69  void Seq::Precache(void)
 70  {
 71      if (memcmp(signature, "SEQ\x1a", 4) != 0)
 72          ThrowError("Invalid sequence");
 73      if ((version & 0xff00) != 0x300)
 74          ThrowError("Obsolete sequence version");
 75      for (int i = 0; i < nFrames; i++)
 76          tilePrecacheTile(seqGetTile(&frames[i]));
 77  }
 78  
 79  void seqPrecacheId(int id)
 80  {
 81      DICTNODE *hSeq = gSysRes.Lookup(id, "SEQ");
 82      if (!hSeq)
 83          return;
 84      Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
 85      pSeq->Precache();
 86      gSysRes.Unlock(hSeq);
 87  }
 88  
 89  SEQINST siWall[kMaxXWalls];
 90  SEQINST siCeiling[kMaxXSectors];
 91  SEQINST siFloor[kMaxXSectors];
 92  SEQINST siSprite[kMaxXSprites];
 93  SEQINST siMasked[kMaxXWalls];
 94  
 95  void UpdateSprite(int nXSprite, SEQFRAME *pFrame)
 96  {
 97      dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
 98      int nSprite = xsprite[nXSprite].reference;
 99      dassert(nSprite >= 0 && nSprite < kMaxSprites);
100      spritetype *pSprite = &sprite[nSprite];
101      dassert(pSprite->extra == nXSprite);
102      
103      int nScale = xsprite[nXSprite].scale; // SEQ size scaling
104  
105      if (pSprite->flags & kPhysGravity)
106      {
107          if (tilesiz[pSprite->picnum].y != tilesiz[seqGetTile(pFrame)].y || picanm[pSprite->picnum].yofs != picanm[seqGetTile(pFrame)].yofs
108              || (pFrame->yrepeat && pFrame->yrepeat != pSprite->yrepeat))
109              pSprite->flags |= kPhysFalling;
110      }
111  
112      pSprite->picnum = seqGetTile(pFrame);
113  
114      if (pFrame->pal)
115          pSprite->pal = seqGetPal(pFrame);
116  
117      pSprite->shade = pFrame->shade;
118  
119      if (pFrame->xrepeat)
120      {
121          if (nScale) pSprite->xrepeat = ClipRange(mulscale8(pFrame->xrepeat, nScale), 0, 255);
122          else pSprite->xrepeat = pFrame->xrepeat;
123      }
124  
125      if (pFrame->yrepeat)
126      {
127          if (nScale) pSprite->yrepeat = ClipRange(mulscale8(pFrame->yrepeat, nScale), 0, 255);
128          else pSprite->yrepeat = pFrame->yrepeat;
129      }
130  
131      if (pFrame->transparent)
132          pSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
133      else
134          pSprite->cstat &= ~CSTAT_SPRITE_TRANSLUCENT;
135  
136      if (pFrame->transparent2)
137          pSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT_INVERT;
138      else
139          pSprite->cstat &= ~CSTAT_SPRITE_TRANSLUCENT_INVERT;
140  
141      if (pFrame->blockable)
142          pSprite->cstat |= CSTAT_SPRITE_BLOCK;
143      else
144          pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
145  
146      if (pFrame->hittable)
147          pSprite->cstat |= CSTAT_SPRITE_BLOCK_HITSCAN;
148      else
149          pSprite->cstat &= ~CSTAT_SPRITE_BLOCK_HITSCAN;
150  
151      if (pFrame->invisible)
152          pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
153      else
154          pSprite->cstat &= (unsigned short)~CSTAT_SPRITE_INVISIBLE;
155  
156      if (pFrame->pushable)
157          pSprite->cstat |= CSTAT_SPRITE_RESERVED3;
158      else
159          pSprite->cstat &= ~CSTAT_SPRITE_RESERVED3;
160  
161      if (pFrame->smoke)
162          pSprite->flags |= kHitagSmoke;
163      else
164          pSprite->flags &= ~kHitagSmoke;
165  
166      if (pFrame->autoaim)
167          pSprite->flags |= kHitagAutoAim;
168      else
169          pSprite->flags &= ~kHitagAutoAim;
170  
171      if (pFrame->xflip)
172          pSprite->flags |= kHitagFlipX;
173      else
174          pSprite->flags &= ~kHitagFlipX;
175  
176      if (pFrame->yflip)
177          pSprite->flags |= kHitagFlipY;
178      else
179          pSprite->flags &= ~kHitagFlipY;
180  
181  }
182  
183  void UpdateWall(int nXWall, SEQFRAME *pFrame)
184  {
185      dassert(nXWall > 0 && nXWall < kMaxXWalls);
186      int nWall = xwall[nXWall].reference;
187      dassert(nWall >= 0 && nWall < kMaxWalls);
188      walltype *pWall = &wall[nWall];
189      dassert(pWall->extra == nXWall);
190  
191      pWall->picnum = seqGetTile(pFrame);
192  
193      if (pFrame->pal)
194          pWall->pal = seqGetPal(pFrame);
195  
196      if (pFrame->transparent)
197          pWall->cstat |= CSTAT_WALL_TRANSLUCENT;
198      else
199          pWall->cstat &= ~CSTAT_WALL_TRANSLUCENT;
200  
201      if (pFrame->transparent2)
202          pWall->cstat |= CSTAT_WALL_TRANS_FLIP;
203      else
204          pWall->cstat &= ~CSTAT_WALL_TRANS_FLIP;
205  
206      if (pFrame->blockable)
207          pWall->cstat |= CSTAT_WALL_BLOCK;
208      else
209          pWall->cstat &= ~CSTAT_WALL_BLOCK;
210  
211      if (pFrame->hittable)
212          pWall->cstat |= CSTAT_WALL_BLOCK_HITSCAN;
213      else
214          pWall->cstat &= ~CSTAT_WALL_BLOCK_HITSCAN;
215  }
216  
217  void UpdateMasked(int nXWall, SEQFRAME *pFrame)
218  {
219      dassert(nXWall > 0 && nXWall < kMaxXWalls);
220      int nWall = xwall[nXWall].reference;
221      dassert(nWall >= 0 && nWall < kMaxWalls);
222      walltype *pWall = &wall[nWall];
223      dassert(pWall->extra == nXWall);
224      dassert(pWall->nextwall >= 0);
225      walltype *pWallNext = &wall[pWall->nextwall];
226  
227      pWall->overpicnum = pWallNext->overpicnum = seqGetTile(pFrame);
228  
229      if (pFrame->pal)
230          pWall->pal = pWallNext->pal = seqGetPal(pFrame);
231  
232      if (pFrame->transparent)
233      {
234          pWall->cstat |= CSTAT_WALL_TRANSLUCENT;
235          pWallNext->cstat |= CSTAT_WALL_TRANSLUCENT;
236      }
237      else
238      {
239          pWall->cstat &= ~CSTAT_WALL_TRANSLUCENT;
240          pWallNext->cstat &= ~CSTAT_WALL_TRANSLUCENT;
241      }
242  
243      if (pFrame->transparent2)
244      {
245          pWall->cstat |= CSTAT_WALL_TRANS_FLIP;
246          pWallNext->cstat |= CSTAT_WALL_TRANS_FLIP;
247      }
248      else
249      {
250          pWall->cstat &= ~CSTAT_WALL_TRANS_FLIP;
251          pWallNext->cstat &= ~CSTAT_WALL_TRANS_FLIP;
252      }
253  
254      if (pFrame->blockable)
255      {
256          pWall->cstat |= CSTAT_WALL_BLOCK;
257          pWallNext->cstat |= CSTAT_WALL_BLOCK;
258      }
259      else
260      {
261          pWall->cstat &= ~CSTAT_WALL_BLOCK;
262          pWallNext->cstat &= ~CSTAT_WALL_BLOCK;
263      }
264  
265      if (pFrame->hittable)
266      {
267          pWall->cstat |= CSTAT_WALL_BLOCK_HITSCAN;
268          pWallNext->cstat |= CSTAT_WALL_BLOCK_HITSCAN;
269      }
270      else
271      {
272          pWall->cstat &= ~CSTAT_WALL_BLOCK_HITSCAN;
273          pWallNext->cstat &= ~CSTAT_WALL_BLOCK_HITSCAN;
274      }
275  }
276  
277  void UpdateFloor(int nXSector, SEQFRAME *pFrame)
278  {
279      dassert(nXSector > 0 && nXSector < kMaxXSectors);
280      int nSector = xsector[nXSector].reference;
281      dassert(nSector >= 0 && nSector < kMaxSectors);
282      sectortype *pSector = &sector[nSector];
283      dassert(pSector->extra == nXSector);
284  
285      pSector->floorpicnum = seqGetTile(pFrame);
286  
287      pSector->floorshade = pFrame->shade;
288  
289      if (pFrame->pal)
290          pSector->floorpal = seqGetPal(pFrame);
291  }
292  
293  void UpdateCeiling(int nXSector, SEQFRAME *pFrame)
294  {
295      dassert(nXSector > 0 && nXSector < kMaxXSectors);
296      int nSector = xsector[nXSector].reference;
297      dassert(nSector >= 0 && nSector < kMaxSectors);
298      sectortype *pSector = &sector[nSector];
299      dassert(pSector->extra == nXSector);
300  
301      pSector->ceilingpicnum = seqGetTile(pFrame);
302  
303      pSector->ceilingshade = pFrame->shade;
304  
305      if (pFrame->pal)
306          pSector->ceilingpal = seqGetPal(pFrame);
307  }
308  
309  void SEQINST::Update(ACTIVE *pActive)
310  {
311      dassert(frameIndex < pSequence->nFrames);
312      SEQFRAME* pFrame = &pSequence->frames[frameIndex];
313      
314      switch (pActive->type)
315      {
316      case 0:
317          UpdateWall(pActive->xindex, pFrame);
318          break;
319      case 1:
320          UpdateCeiling(pActive->xindex , pFrame);
321          break;
322      case 2:
323          UpdateFloor(pActive->xindex, pFrame);
324          break;
325      case 3: 
326      {
327          UpdateSprite(pActive->xindex, pFrame);
328          spritetype* pSpr = &sprite[xsprite[pActive->xindex].reference];
329          int nSnd, nSurf;
330  
331  #ifdef NOONE_EXTENSIONS
332          if (pFrame->playSound)
333          {
334              nSnd = pSequence->nSoundID;
335              
336              // add random sound range feature
337              if (!VanillaMode() && pFrame->soundRange > 0)
338                  nSnd += Random((pFrame->soundRange == 1) ? 2 : pFrame->soundRange);
339              
340              sfxPlay3DSound(pSpr, nSnd, -1, 0);
341          }
342          
343          // add surfaceSound trigger feature
344          if (!VanillaMode() && pFrame->surfaceSound && zvel[pSpr->index] == 0 && approxDist(xvel[pSpr->index], yvel[pSpr->index]))
345          {
346              if (pSpr->sectnum >= 0 && gUpperLink[pSpr->sectnum] < 0 && (nSurf = tileGetSurfType(pSpr->sectnum, 0x4000)) > 0)
347              {
348                  DICTNODE* hRes;
349                  
350                  nSnd = kSurfSoundBase + ((nSurf << 1) + Random(2));
351                  if ((hRes = gSoundRes.Lookup(nSnd, "SFX")) != NULL)
352                  {
353                      SFX* pSFX = (SFX*)gSoundRes.Load(hRes);
354                      sfxPlay3DSoundCP(pSpr, nSnd, -1, 0, 0, (pSFX->relVol != 80) ? pSFX->relVol : 25);
355                  }
356              }
357          }
358  #else
359          if (pFrame->playSound)
360              sfxPlay3DSound(pSpr, pSequence->nSoundID, -1, 0);
361  #endif
362          break;
363      }
364      case 4:
365          UpdateMasked(pActive->xindex, pFrame);
366          break;
367      }
368      if (pFrame->trigger && nCallbackID != -1)
369          clientCallback[nCallbackID](pActive->type, pActive->xindex);
370  }
371  
372  SEQINST * GetInstance(int nType, int nXIndex)
373  {
374      switch (nType)
375      {
376      case 0:
377          if (nXIndex > 0 && nXIndex < kMaxXWalls) return &siWall[nXIndex];
378          break;
379      case 1:
380          if (nXIndex > 0 && nXIndex < kMaxXSectors) return &siCeiling[nXIndex];
381          break;
382      case 2:
383          if (nXIndex > 0 && nXIndex < kMaxXSectors) return &siFloor[nXIndex];
384          break;
385      case 3:
386          if (nXIndex > 0 && nXIndex < kMaxXSprites) return &siSprite[nXIndex];
387          break;
388      case 4:
389          if (nXIndex > 0 && nXIndex < kMaxWalls) return &siMasked[nXIndex];
390          break;
391      }
392      return NULL;
393  }
394  
395  void UnlockInstance(SEQINST *pInst)
396  {
397      dassert(pInst != NULL);
398      dassert(pInst->hSeq != NULL);
399      dassert(pInst->pSequence != NULL);
400      gSysRes.Unlock(pInst->hSeq);
401      pInst->hSeq = NULL;
402      pInst->pSequence = NULL;
403      pInst->isPlaying = 0;
404  }
405  
406  void seqSpawn(int nSeq, int nType, int nXIndex, int nCallbackID)
407  {
408      SEQINST *pInst = GetInstance(nType, nXIndex);
409      if (!pInst) return;
410      
411      DICTNODE *hSeq = gSysRes.Lookup(nSeq, "SEQ");
412      if (!hSeq)
413          ThrowError("Missing sequence #%d", nSeq);
414  
415      int i = activeCount;
416      if (pInst->isPlaying)
417      {
418          if (hSeq == pInst->hSeq)
419              return;
420          UnlockInstance(pInst);
421          for (i = 0; i < activeCount; i++)
422          {
423              if (activeList[i].type == nType && activeList[i].xindex == nXIndex)
424                  break;
425          }
426          dassert(i < activeCount);
427      }
428      Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
429      if (memcmp(pSeq->signature, "SEQ\x1a", 4) != 0)
430          ThrowError("Invalid sequence %d", nSeq);
431      if ((pSeq->version & 0xff00) != 0x300)
432          ThrowError("Sequence %d is obsolete version", nSeq);
433      if ((pSeq->version & 0xff) == 0x00)
434      {
435          for (int i = 0; i < pSeq->nFrames; i++)
436          {
437              pSeq->frames[i].tile2 = 0;
438  #ifdef NOONE_EXTENSIONS
439              pSeq->frames[i].pal2 = 0;
440  #endif
441          }
442      }
443      pInst->isPlaying = 1;
444      pInst->hSeq = hSeq;
445      pInst->pSequence = pSeq;
446      pInst->nSeq = nSeq;
447      pInst->nCallbackID = nCallbackID;
448      pInst->timeCount = pSeq->ticksPerFrame;
449      pInst->frameIndex = 0;
450      if (i == activeCount)
451      {
452          dassert(activeCount < kMaxSequences);
453          if (!bActiveListWarnVanilla && (activeCount >= kMaxSequencesVanilla) && VanillaMode())
454          {
455              OSD_Printf("Warning: Active SEQ list over vanilla limit (%d/%d)\n", activeCount, kMaxSequencesVanilla);
456              bActiveListWarnVanilla = 1;
457          }
458          activeList[activeCount].type = nType;
459          activeList[activeCount].xindex = nXIndex;
460          activeCount++;
461      }
462      pInst->Update(&activeList[i]);
463  }
464  
465  void seqKill(int nType, int nXIndex)
466  {
467      SEQINST *pInst = GetInstance(nType, nXIndex);
468      if (!pInst || !pInst->isPlaying)
469          return;
470      int i;
471      for (i = 0; i < activeCount; i++)
472      {
473          if (activeList[i].type == nType && activeList[i].xindex == nXIndex)
474              break;
475      }
476      dassert(i < activeCount);
477      activeCount--;
478      activeList[i] = activeList[activeCount];
479      pInst->isPlaying = 0;
480      UnlockInstance(pInst);
481  }
482  
483  void seqKillAll(void)
484  {
485      for (int i = 0; i < kMaxXWalls; i++)
486      {
487          if (siWall[i].isPlaying)
488              UnlockInstance(&siWall[i]);
489          if (siMasked[i].isPlaying)
490              UnlockInstance(&siMasked[i]);
491      }
492      for (int i = 0; i < kMaxXSectors; i++)
493      {
494          if (siCeiling[i].isPlaying)
495              UnlockInstance(&siCeiling[i]);
496          if (siFloor[i].isPlaying)
497              UnlockInstance(&siFloor[i]);
498      }
499      for (int i = 0; i < kMaxXSprites; i++)
500      {
501          if (siSprite[i].isPlaying)
502              UnlockInstance(&siSprite[i]);
503      }
504      activeCount = 0;
505      bActiveListWarnVanilla = 0;
506  }
507  
508  int seqGetStatus(int nType, int nXIndex)
509  {
510      SEQINST *pInst = GetInstance(nType, nXIndex);
511      if (pInst && pInst->isPlaying)
512          return pInst->frameIndex;
513      return -1;
514  }
515  
516  int seqGetID(int nType, int nXIndex)
517  {
518      SEQINST *pInst = GetInstance(nType, nXIndex);
519      if (pInst)
520          return pInst->nSeq;
521      return -1;
522  }
523  
524  void seqProcess(int nTicks)
525  {
526      for (int i = 0; i < activeCount; i++)
527      {
528          SEQINST *pInst = GetInstance(activeList[i].type, activeList[i].xindex);
529          Seq *pSeq = pInst->pSequence;
530          dassert(pInst->frameIndex < pSeq->nFrames);
531          pInst->timeCount -= nTicks;
532          while (pInst->timeCount < 0)
533          {
534              pInst->timeCount += pSeq->ticksPerFrame;
535              pInst->frameIndex++;
536              if (pInst->frameIndex == pSeq->nFrames)
537              {
538                  if (pSeq->flags & 1)
539                      pInst->frameIndex = 0;
540                  else
541                  {
542                      UnlockInstance(pInst);
543                      if (pSeq->flags & 2)
544                      {
545                          switch (activeList[i].type)
546                          {
547                          case 3:
548                          {
549                              int nXSprite = activeList[i].xindex;
550                              int nSprite = xsprite[nXSprite].reference;
551                              dassert(nSprite >= 0 && nSprite < kMaxSprites);
552                              evKill(nSprite, 3);
553                              if ((sprite[nSprite].flags & kHitagRespawn) && sprite[nSprite].inittype >= kDudeBase && sprite[nSprite].inittype < kDudeMax)
554                                  evPost(nSprite, 3, gGameOptions.nMonsterRespawnTime, kCallbackRespawn);
555                              else if (VanillaMode() || sprite[nSprite].statnum < kMaxStatus)
556                                  DeleteSprite(nSprite);
557                              break;
558                          }
559                          case 4:
560                          {
561                              int nXWall = activeList[i].xindex;
562                              int nWall = xwall[nXWall].reference;
563                              dassert(nWall >= 0 && nWall < kMaxWalls);
564                              wall[nWall].cstat &= ~(8 + 16 + 32);
565                              if (wall[nWall].nextwall != -1)
566                                  wall[wall[nWall].nextwall].cstat &= ~(8 + 16 + 32);
567                              break;
568                          }
569                          }
570                      }
571                      activeList[i--] = activeList[--activeCount];
572                      break;
573                  }
574              }
575              pInst->Update(&activeList[i]);
576          }
577      }
578  }
579  
580  class SeqLoadSave : public LoadSave {
581      virtual void Load(void);
582      virtual void Save(void);
583  };
584  
585  void SeqLoadSave::Load(void)
586  {
587      Read(&siWall, sizeof(siWall));
588      Read(&siMasked, sizeof(siMasked));
589      Read(&siCeiling, sizeof(siCeiling));
590      Read(&siFloor, sizeof(siFloor));
591      Read(&siSprite, sizeof(siSprite));
592      Read(&activeList, sizeof(activeList));
593      Read(&activeCount, sizeof(activeCount));
594      for (int i = 0; i < kMaxXWalls; i++)
595      {
596          siWall[i].hSeq = NULL;
597          siMasked[i].hSeq = NULL;
598          siWall[i].pSequence = NULL;
599          siMasked[i].pSequence = NULL;
600      }
601      for (int i = 0; i < kMaxXSectors; i++)
602      {
603          siCeiling[i].hSeq = NULL;
604          siFloor[i].hSeq = NULL;
605          siCeiling[i].pSequence = NULL;
606          siFloor[i].pSequence = NULL;
607      }
608      for (int i = 0; i < kMaxXSprites; i++)
609      {
610          siSprite[i].hSeq = NULL;
611          siSprite[i].pSequence = NULL;
612      }
613      for (int i = 0; i < activeCount; i++)
614      {
615          SEQINST *pInst = GetInstance(activeList[i].type, activeList[i].xindex);
616          if (pInst->isPlaying)
617          {
618              int nSeq = pInst->nSeq;
619              DICTNODE *hSeq = gSysRes.Lookup(nSeq, "SEQ");
620              if (!hSeq) {
621                  ThrowError("Missing sequence #%d", nSeq);
622                  continue;
623              }
624              Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
625              if (memcmp(pSeq->signature, "SEQ\x1a", 4) != 0)
626                  ThrowError("Invalid sequence %d", nSeq);
627              if ((pSeq->version & 0xff00) != 0x300)
628                  ThrowError("Sequence %d is obsolete version", nSeq);
629  
630              // Edited SEQ and loading old savegame...
631              if (pInst->frameIndex >= pSeq->nFrames)
632                  pInst->frameIndex = pSeq->nFrames - 1;
633                 
634              pInst->hSeq = hSeq;
635              pInst->pSequence = pSeq;
636          }
637      }
638  }
639  
640  void SeqLoadSave::Save(void)
641  {
642      Write(&siWall, sizeof(siWall));
643      Write(&siMasked, sizeof(siMasked));
644      Write(&siCeiling, sizeof(siCeiling));
645      Write(&siFloor, sizeof(siFloor));
646      Write(&siSprite, sizeof(siSprite));
647      Write(&activeList, sizeof(activeList));
648      Write(&activeCount, sizeof(activeCount));
649  }
650  
651  static SeqLoadSave *myLoadSave;
652  
653  void SeqLoadSaveConstruct(void)
654  {
655      myLoadSave = new SeqLoadSave();
656  }