1 /*
2 ** gamemap.cpp
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2011 Braden Obrzut
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 ** notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 ** notice, this list of conditions and the following disclaimer in the
16 ** documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 ** derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 **
33 */
34
35 #include <climits>
36 #include "id_ca.h"
37 #include "farchive.h"
38 #include "gamemap.h"
39 #include "tarray.h"
40 #include "w_wad.h"
41 #include "wl_def.h"
42 #include "lnspec.h"
43 #include "actor.h"
44 #include "thingdef/thingdef.h"
45 #include "wl_agent.h"
46 #include "wl_game.h"
47 #include "r_sprites.h"
48 #include "resourcefiles/resourcefile.h"
49 #include "wl_loadsave.h"
50 #include "doomerrors.h"
51 #include "m_random.h"
52 #include "g_mapinfo.h"
53
GameMap(const FString & map)54 GameMap::GameMap(const FString &map) : map(map), valid(false), isUWMF(false),
55 file(NULL), zoneTraversed(NULL), zoneLinks(NULL)
56 {
57 lumps[0] = NULL;
58
59 // Find the map
60 markerLump = Wads.CheckNumForName(map);
61
62 // PK3 format maps
63 FString mapWad;
64 mapWad.Format("maps/%s.wad", map.GetChars());
65
66 int wadLump = Wads.CheckNumForFullName(mapWad);
67 if(wadLump > markerLump)
68 {
69 isWad = true;
70 markerLump = wadLump;
71 }
72 else
73 isWad = false;
74
75 if(markerLump == -1)
76 {
77 FString error;
78 error.Format("Could not find map %s!", map.GetChars());
79 throw CRecoverableError(error);
80 }
81
82 // Hmm... What follows is some massive copy and paste, but I can't really
83 // think of a cleaner way to do this.
84 // Anyways, if we have a wad we need to open a resource file for it.
85 // Otherwise we open the relevent lumps.
86 if(isWad)
87 {
88 file = FResourceFile::OpenResourceFile(mapWad.GetChars(), Wads.ReopenLumpNum(markerLump), true);
89 if(!file || file->LumpCount() < 2) // Maps must be 2 lumps in size
90 {
91 FString error;
92 error.Format("Map %s is in an unknown format.", map.GetChars());
93 throw CRecoverableError(error);
94 }
95
96 // First lump is assumed marker
97 FResourceLump *lump = file->GetLump(1);
98 if(stricmp(lump->Name, "PLANES") == 0)
99 {
100 numLumps = 1;
101 isUWMF = false;
102 valid = true;
103 lumps[0] = lump->NewReader();
104 }
105 else
106 {
107 if(stricmp(lump->Name, "TEXTMAP") != 0)
108 {
109 FString error;
110 error.Format("Invalid map format for %s!", map.GetChars());
111 throw CRecoverableError(error);
112 }
113
114 isUWMF = true;
115 lumps[0] = lump->NewReader();
116
117 for(unsigned int i = 2;i < file->LumpCount();++i)
118 {
119 lump = file->GetLump(i);
120 if(stricmp(lump->Name, "ENDMAP") == 0)
121 {
122 valid = true;
123 break;
124 }
125 numLumps++;
126 }
127 if(!valid)
128 {
129 FString error;
130 error.Format("ENDMAP not found for map %s!", map.GetChars());
131 throw CRecoverableError(error);
132 }
133 }
134 }
135 else
136 {
137 if(strcmp(Wads.GetLumpFullName(markerLump+1), "PLANES") == 0)
138 {
139 numLumps = 1;
140 isUWMF = false;
141 valid = true;
142 lumps[0] = Wads.ReopenLumpNum(markerLump+1);
143 }
144 else
145 {
146 // Expect UWMF formatted map.
147 if(strcmp(Wads.GetLumpFullName(markerLump+1), "TEXTMAP") != 0)
148 {
149 FString error;
150 error.Format("Invalid map format for %s!", map.GetChars());
151 throw CRecoverableError(error);
152 }
153
154 isUWMF = true;
155 lumps[0] = Wads.ReopenLumpNum(markerLump+1);
156
157 for(int i = 2;i < Wads.GetNumLumps();i++)
158 {
159 if(strcmp(Wads.GetLumpFullName(markerLump+i), "ENDMAP") == 0)
160 {
161 valid = true;
162 break;
163 }
164 numLumps++;
165 }
166 if(!valid)
167 {
168 FString error;
169 error.Format("ENDMAP not found for map %s!", map.GetChars());
170 throw CRecoverableError(error);
171 }
172 }
173 }
174 }
175
~GameMap()176 GameMap::~GameMap()
177 {
178 if(isWad)
179 delete file;
180 delete lumps[0];
181
182 for(unsigned int i = 0;i < planes.Size();++i)
183 delete[] planes[i].map;
184 UnloadLinks();
185 }
186
ActivateTrigger(Trigger & trig,Trigger::Side direction,AActor * activator)187 bool GameMap::ActivateTrigger(Trigger &trig, Trigger::Side direction, AActor *activator)
188 {
189 if(!trig.repeatable && !trig.active)
190 return false;
191
192 MapSpot spot = GetSpot(trig.x, trig.y, trig.z);
193
194 Specials::LineSpecialFunction func = Specials::LookupFunction(Specials::LineSpecials(trig.action));
195 bool ret = func(spot, trig.arg, direction, activator) != 0;
196 if(ret)
197 {
198 if(trig.active && trig.isSecret)
199 ++gamestate.secretcount;
200 trig.active = false;
201 }
202 return ret;
203 }
204
ClearVisibility()205 void GameMap::ClearVisibility()
206 {
207 for(unsigned int i = 0;i < header.width*header.height;++i)
208 {
209 for(unsigned int p = 0;p < planes.Size();++p)
210 planes[p].map[i].visible = false;
211 }
212 if(players[0].camera)
213 GetSpot(players[0].camera->tilex, players[0].camera->tiley, 0)->visible = true;
214 }
215
CheckMapExists(const FString & map)216 bool GameMap::CheckMapExists(const FString &map)
217 {
218 try
219 {
220 GameMap gm(map);
221 return true;
222 }
223 catch(CRecoverableError &)
224 {
225 return false;
226 }
227 }
228
CheckLink(const Zone * zone1,const Zone * zone2,bool recurse)229 bool GameMap::CheckLink(const Zone *zone1, const Zone *zone2, bool recurse)
230 {
231 if(zone1 == NULL || zone2 == NULL)
232 return false;
233
234 // We only have the top half of the table.
235 if(zone2->index < zone1->index)
236 {
237 const Zone *tmp = zone1;
238 zone1 = zone2;
239 zone2 = tmp;
240 }
241
242 // If we're doing a recursive check and the straight check passes use that
243 bool straightCheck = zoneLinks[zone1->index][zone2->index - zone1->index] > 0;
244 if(!recurse || straightCheck)
245 return straightCheck;
246
247 memset(zoneTraversed, 0, sizeof(bool)*zonePalette.Size());
248 return TraverseLink(zone1, zone2);
249 }
TraverseLink(const Zone * src,const Zone * dest)250 bool GameMap::TraverseLink(const Zone* src, const Zone* dest)
251 {
252 // Mark this node as checked
253 zoneTraversed[src->index] = true;
254
255 // Check upper zones (right side of table)
256 unsigned int ofs = src->index;
257 unsigned int i = zonePalette.Size() - src->index;
258 while(--i > 0)
259 {
260 if(!zoneTraversed[i + ofs] && zoneLinks[ofs][i] > 0)
261 {
262 if(i + ofs == dest->index || TraverseLink(&zonePalette[i + ofs], dest))
263 return true;
264 }
265 }
266
267 // Check lower zones (top side of table)
268 while(i < src->index)
269 {
270 if(!zoneTraversed[i] && zoneLinks[i][ofs] > 0)
271 {
272 if(i == dest->index || TraverseLink(&zonePalette[i], dest))
273 return true;
274 }
275 --ofs;
276 ++i;
277 }
278 return false;
279 }
280
281 // Get a list of textures to precache
GetHitlist(BYTE * hitlist) const282 void GameMap::GetHitlist(BYTE* hitlist) const
283 {
284 R_GetSpriteHitlist(hitlist);
285
286 for(unsigned int i = planes.Size();i-- > 0;)
287 {
288 Plane &plane = planes[i];
289 for(unsigned int j = GetHeader().width*GetHeader().height;j-- > 0;)
290 {
291 Plane::Map &spot = plane.map[j];
292
293 if(spot.tile)
294 {
295 hitlist[spot.tile->texture[Tile::East].GetIndex()] =
296 hitlist[spot.tile->texture[Tile::North].GetIndex()] =
297 hitlist[spot.tile->texture[Tile::West].GetIndex()] =
298 hitlist[spot.tile->texture[Tile::South].GetIndex()] |= 1;
299 }
300
301 if(spot.sector)
302 {
303 hitlist[spot.sector->texture[Sector::Floor].GetIndex()] =
304 hitlist[spot.sector->texture[Sector::Ceiling].GetIndex()] |= 2;
305 }
306 }
307 }
308 }
309
310 // Looks up the MapSpot by tag number. If spot is NULL then the first spot
311 // in the chain is returned.
312 // Technically at the moment further spots could be found by just using nexttag
313 // but the implementation details could change.
GetSpotByTag(unsigned int tag,MapSpot spot) const314 MapSpot GameMap::GetSpotByTag(unsigned int tag, MapSpot spot) const
315 {
316 if(!spot)
317 {
318 const MapSpot *starttag = tagMap.CheckKey(tag);
319 if(!starttag)
320 return NULL;
321 spot = *starttag;
322 }
323 else
324 spot = spot->nexttag;
325
326 return spot;
327 }
328
GetTile(unsigned int index) const329 const GameMap::Tile *GameMap::GetTile(unsigned int index) const
330 {
331 if(index > tilePalette.Size())
332 return NULL;
333 return &tilePalette[index];
334 }
335
GetTileIndex(const GameMap::Tile * tile) const336 unsigned int GameMap::GetTileIndex(const GameMap::Tile *tile) const
337 {
338 if(!tile)
339 return INT_MAX;
340
341 return static_cast<unsigned int>(tile - &tilePalette[0]);
342 }
343
GetSector(unsigned int index) const344 const GameMap::Sector *GameMap::GetSector(unsigned int index) const
345 {
346 if(index > sectorPalette.Size())
347 return NULL;
348 return §orPalette[index];
349 }
350
GetSectorIndex(const GameMap::Sector * sector) const351 unsigned int GameMap::GetSectorIndex(const GameMap::Sector *sector) const
352 {
353 if(!sector)
354 return INT_MAX;
355
356 return static_cast<unsigned int>(sector - §orPalette[0]);
357 }
358
LinkZones(const Zone * zone1,const Zone * zone2,bool open)359 void GameMap::LinkZones(const Zone *zone1, const Zone *zone2, bool open)
360 {
361 if(zone1 == zone2 || zone1 == NULL || zone2 == NULL)
362 return;
363
364 unsigned short &value = zone1->index < zone2->index ?
365 zoneLinks[zone1->index][zone2->index - zone1->index] :
366 zoneLinks[zone2->index][zone1->index - zone2->index];
367 if(!open)
368 {
369 if(value > 0)
370 --value;
371 }
372 else
373 ++value;
374 }
375
LoadMap(bool loadingSave)376 void GameMap::LoadMap(bool loadingSave)
377 {
378 if(!valid)
379 throw CRecoverableError("Tried to load invalid map!");
380
381 if(isUWMF)
382 ReadUWMFData();
383 else
384 ReadPlanesData();
385
386 if(!loadingSave)
387 ScanTiles();
388 }
389
NewPlane()390 GameMap::Plane &GameMap::NewPlane()
391 {
392 planes.Reserve(1);
393 Plane &newPlane = planes[planes.Size()-1];
394 newPlane.gm = this;
395 newPlane.map = new Plane::Map[header.width*header.height];
396 for(unsigned int i = 0;i < header.width*header.height;++i)
397 newPlane.map[i].plane = &newPlane;
398 return newPlane;
399 }
400
NewTrigger(unsigned int x,unsigned int y,unsigned int z)401 GameMap::Trigger &GameMap::NewTrigger(unsigned int x, unsigned int y, unsigned int z)
402 {
403 if(z >= planes.Size())
404 throw CRecoverableError("Trigger assigned to non-existant plane!");
405
406 MapSpot spot = GetSpot(x, y, z);
407 Trigger newTrig;
408 newTrig.x = x;
409 newTrig.y = y;
410 newTrig.z = z;
411 spot->triggers.Push(newTrig);
412 return spot->triggers[spot->triggers.Size()-1];
413 }
414
PropagateMark()415 void GameMap::PropagateMark()
416 {
417 for(unsigned int p = 0;p < NumPlanes();++p)
418 {
419 MapPlane &plane = planes[p];
420
421 for(unsigned int i = 0;i < GetHeader().width*GetHeader().height;++i)
422 GC::Mark(plane.map[i].thinker);
423 }
424 }
425
426 // Look at data and determine if we need to set up any flags.
ScanTiles()427 void GameMap::ScanTiles()
428 {
429 for(unsigned int p = 0;p < planes.Size();++p)
430 {
431 MapSpot spot = planes[p].map;
432 MapSpot endSpot = spot + header.width*header.height;
433 while(spot < endSpot)
434 {
435 if(spot->tile)
436 {
437 if(spot->tile->mapped > gamestate.difficulty->MapFilter)
438 spot->amFlags |= AM_Visible;
439 if(spot->tile->dontOverlay)
440 spot->amFlags |= AM_DontOverlay;
441 }
442
443 ++spot;
444 }
445 }
446 }
447
448 // Adds the spot to the tag list. The linked chain is stored in the tile itself.
SetSpotTag(MapSpot spot,unsigned int tag)449 void GameMap::SetSpotTag(MapSpot spot, unsigned int tag)
450 {
451 spot->tag = tag;
452
453 MapSpot *chainPtr = tagMap.CheckKey(tag);
454 if(chainPtr)
455 {
456 MapSpot chain = *chainPtr;
457 while(chain->nexttag)
458 chain = chain->nexttag;
459 chain->nexttag = spot;
460 }
461 else
462 tagMap.Insert(tag, spot);
463 }
464
SetupLinks()465 void GameMap::SetupLinks()
466 {
467 // Allocate as one large block for locality.
468 const unsigned int zdSize = sizeof(bool)*zonePalette.Size()
469 + sizeof(unsigned short)*((zonePalette.Size()*(zonePalette.Size()+1))>>1);
470 byte* zoneData = new byte[zdSize + sizeof(unsigned short*)*zonePalette.Size()];
471 memset(zoneData, 0, zdSize);
472 zoneTraversed = reinterpret_cast<bool*>(zoneData);
473
474 // Set up the table
475 unsigned short* ptr = reinterpret_cast<unsigned short*>(zoneData + sizeof(bool)*zonePalette.Size());
476 zoneLinks = reinterpret_cast<unsigned short**>(zoneData+zdSize);
477 for(unsigned int i = 0;i < zonePalette.Size();++i)
478 {
479 zoneLinks[i] = ptr;
480 ptr += zonePalette.Size()-i;
481 zoneLinks[i][0] = 1;
482 }
483 }
484
485 extern FRandom pr_spawnmobj;
SpawnThings() const486 void GameMap::SpawnThings() const
487 {
488 #if 0
489 // Debug code - Show the number of things spawned at map start.
490 printf("Spawning %d things\n", things.Size());
491 #endif
492 for(unsigned int i = 0;i < things.Size();++i)
493 {
494 Thing &thing = things[i];
495 if(!thing.skill[gamestate.difficulty->SpawnFilter])
496 continue;
497
498 if(thing.type == 1)
499 SpawnPlayer(thing.x>>FRACBITS, thing.y>>FRACBITS, thing.angle);
500 else
501 {
502 static const ClassDef *unknownClass = ClassDef::FindClass("Unknown");
503 // Spawn object
504 const ClassDef *cls = ClassDef::FindClass(thing.type);
505 if(cls == NULL)
506 {
507 cls = unknownClass;
508 printf("Unknown thing %d @ (%d, %d)\n", thing.type, thing.x>>FRACBITS, thing.y>>FRACBITS);
509 }
510
511 AActor *actor = AActor::Spawn(cls, thing.x, thing.y, 0, SPAWN_AllowReplacement|(thing.patrol ? SPAWN_Patrol : 0));
512 // This forumla helps us to avoid errors in roundoffs.
513 actor->angle = (thing.angle/45)*ANGLE_45 + (thing.angle%45)*ANGLE_1;
514 actor->dir = nodir;
515 if(thing.ambush)
516 actor->flags |= FL_AMBUSH;
517 if(thing.patrol)
518 actor->dir = dirtype(actor->angle/ANGLE_45);
519
520 // Check for valid frames
521 if(!actor->state || !R_CheckSpriteValid(actor->sprite))
522 {
523 actor->Destroy();
524 actor = AActor::Spawn(unknownClass, thing.x, thing.y, 0, SPAWN_AllowReplacement);
525
526 printf("%s at (%d, %d) has no frames\n", cls->GetName().GetChars(), thing.x>>FRACBITS, thing.y>>FRACBITS);
527 }
528 }
529 }
530 }
531
UnloadLinks()532 void GameMap::UnloadLinks()
533 {
534 // Make sure there's stuff to unload.
535 if(!zoneLinks)
536 return;
537
538 // zoneTraversed holds the base address for our single allocation.
539 delete[] zoneTraversed;
540 zoneTraversed = NULL;
541 zoneLinks = NULL;
542 }
543
544 ////////////////////////////////////////////////////////////////////////////////
545
GetX() const546 unsigned int GameMap::Plane::Map::GetX() const
547 {
548 return static_cast<unsigned int>(this - plane->map)%plane->gm->GetHeader().width;
549 }
550
GetY() const551 unsigned int GameMap::Plane::Map::GetY() const
552 {
553 return static_cast<unsigned int>(this - plane->map)/plane->gm->GetHeader().width;
554 }
555
GetAdjacent(MapTile::Side dir,bool opposite) const556 MapSpot GameMap::Plane::Map::GetAdjacent(MapTile::Side dir, bool opposite) const
557 {
558 if(opposite) // Rotate the dir 180 degrees.
559 dir = MapTile::Side((dir+2)%4);
560
561 unsigned int x = GetX();
562 unsigned int y = GetY();
563 switch(dir)
564 {
565 case MapTile::South:
566 ++y;
567 break;
568 case MapTile::North:
569 --y;
570 break;
571 case MapTile::West:
572 --x;
573 break;
574 case MapTile::East:
575 ++x;
576 break;
577 }
578 if(y >= plane->gm->GetHeader().height || x >= plane->gm->GetHeader().width)
579 return NULL;
580 return &plane->map[y*plane->gm->GetHeader().width+x];
581 }
582
SetTile(const MapTile * tile)583 void GameMap::Plane::Map::SetTile(const MapTile *tile)
584 {
585 this->tile = tile;
586 for(unsigned int i = 0;i < 4;++i)
587 {
588 if(tile)
589 {
590 sideSolid[i] = tile->sideSolid[i];
591 texture[i] = tile->texture[i];
592 }
593 else
594 {
595 sideSolid[i] = false;
596 texture[i].SetInvalid();
597 }
598 }
599 }
600
operator <<(FArchive & arc,GameMap * & gm)601 FArchive &operator<< (FArchive &arc, GameMap *&gm)
602 {
603 arc << gm->header.name
604 << gm->header.width
605 << gm->header.height
606 << gm->header.tileSize;
607
608 // zoneLinks
609 if(GameSave::SaveVersion >= 1383348286)
610 {
611 unsigned int zone = gm->zonePalette.Size();
612 while(--zone > 0) // We don't care about == 0 since it's always 1
613 {
614 unsigned int i = gm->zonePalette.Size() - zone;
615 while(--i > 0)
616 arc << gm->zoneLinks[zone][i];
617 }
618 }
619 else
620 {
621 // Old zoneLinks
622 // It would probably be too much work to try to convert this, so we'll
623 // just read past it and let the game be a little inconsistent. Most
624 // people won't notice and in most cases the level will fix itself after
625 // some time elapses.
626 uint32_t packing = 0;
627 unsigned short shift = 0;
628 unsigned int x = 0;
629 unsigned int y = 1;
630 unsigned int max = 1;
631
632 arc << packing;
633
634 do
635 {
636 //gm->zoneLinks[x][y] = (packing>>(shift++))&1;
637 ++shift;
638
639 if(++x >= max)
640 {
641 x = 0;
642 ++y;
643 ++max;
644 }
645
646 if(shift == sizeof(packing)*8)
647 {
648 arc << packing;
649 shift = 0;
650 }
651 }
652 while(y < gm->zonePalette.Size());
653 }
654
655 // Serialize any map information that may change
656 for(unsigned int p = 0;p < gm->NumPlanes();++p)
657 {
658 MapPlane &plane = gm->planes[p];
659
660 arc << plane.depth;
661 assert(plane.depth == 64);
662 if(!arc.IsStoring())
663 plane.gm = gm;
664
665 for(unsigned int i = 0;i < gm->GetHeader().width*gm->GetHeader().height;++i)
666 {
667 BYTE pushdir = plane.map[i].pushDirection;
668 arc << pushdir;
669 plane.map[i].pushDirection = static_cast<MapTile::Side>(pushdir);
670
671 arc << plane.map[i].texture[0] << plane.map[i].texture[1] << plane.map[i].texture[2] << plane.map[i].texture[3]
672 << plane.map[i].visible;
673 if(GameSave::SaveVersion >= 1393719642)
674 arc << plane.map[i].amFlags;
675 arc << plane.map[i].thinker
676 << plane.map[i].slideAmount[0] << plane.map[i].slideAmount[1] << plane.map[i].slideAmount[2] << plane.map[i].slideAmount[3]
677 << plane.map[i].sideSolid[0] << plane.map[i].sideSolid[1] << plane.map[i].sideSolid[2] << plane.map[i].sideSolid[3]
678 << plane.map[i].triggers
679 << plane.map[i].pushAmount
680 << plane.map[i].tile
681 << plane.map[i].sector
682 << plane.map[i].zone
683 << plane.map[i].pushReceptor;
684
685 if(GameSave::SaveProdVersion >= 0x001002FF && GameSave::SaveVersion >= 1375246092)
686 arc << plane.map[i].slideStyle;
687
688 if(!arc.IsStoring())
689 plane.map[i].plane = &plane;
690 }
691 }
692
693 // Current elevator positions.
694 if(GameSave::SaveVersion > 1438232816)
695 {
696 if(arc.IsStoring())
697 {
698 unsigned int count = gm->elevatorPosition.CountUsed();
699 arc << count;
700
701 TMap<unsigned int, MapSpot>::Iterator iter(gm->elevatorPosition);
702 TMap<unsigned int, MapSpot>::Pair *pair;
703 while(iter.NextPair(pair))
704 {
705 DWORD key = pair->Key;
706 arc << key << pair->Value;
707 }
708 }
709 else
710 {
711 unsigned int count;
712 arc << count;
713
714 gm->elevatorPosition.Clear();
715 while(count-- > 0)
716 {
717 DWORD key;
718 MapSpot value;
719 arc << key << value;
720
721 gm->elevatorPosition[key] = value;
722 }
723 }
724 }
725
726 return arc;
727 }
728
729 ////////////////////////////////////////////////////////////////////////////////
730
operator <<(FArchive & arc,MapSpot & spot)731 FArchive &operator<< (FArchive &arc, MapSpot &spot)
732 {
733 if(arc.IsStoring())
734 {
735 unsigned int x = INT_MAX;
736 unsigned int y = INT_MAX;
737 if(spot)
738 {
739 x = spot->GetX();
740 y = spot->GetY();
741 }
742
743 arc << x << y;
744 }
745 else
746 {
747 unsigned int x, y;
748 arc << x << y;
749
750 if(x == INT_MAX || y == INT_MAX)
751 spot = NULL;
752 else
753 spot = map->GetSpot(x, y, 0);
754 }
755
756 return arc;
757 }
758
operator <<(FArchive & arc,const MapSector * & sector)759 FArchive &operator<< (FArchive &arc, const MapSector *§or)
760 {
761 if(arc.IsStoring())
762 {
763 unsigned int index = map->GetSectorIndex(sector);
764 arc << index;
765 }
766 else
767 {
768 unsigned int index;
769 arc << index;
770
771 sector = map->GetSector(index);
772 }
773 return arc;
774 }
775
operator <<(FArchive & arc,const MapTile * & tile)776 FArchive &operator<< (FArchive &arc, const MapTile *&tile)
777 {
778 if(arc.IsStoring())
779 {
780 unsigned int index = map->GetTileIndex(tile);
781 arc << index;
782 }
783 else
784 {
785 unsigned int index;
786 arc << index;
787
788 tile = map->GetTile(index);
789 }
790 return arc;
791 }
792
operator <<(FArchive & arc,const MapZone * & zone)793 FArchive &operator<< (FArchive &arc, const MapZone *&zone)
794 {
795 if(arc.IsStoring())
796 {
797 unsigned int index;
798 if(zone)
799 index = zone->index;
800 else
801 index = INT_MAX;
802
803 arc << index;
804 }
805 else
806 {
807 unsigned int index;
808 arc << index;
809
810 if(index != INT_MAX)
811 zone = &map->GetZone(index);
812 else
813 zone = NULL;
814 }
815
816 return arc;
817 }
818
operator <<(FArchive & arc,MapTrigger & trigger)819 FArchive &operator<< (FArchive &arc, MapTrigger &trigger)
820 {
821 arc << trigger.x
822 << trigger.y
823 << trigger.z
824 << trigger.active
825 << trigger.action
826 << trigger.activate[0] << trigger.activate[1] << trigger.activate[2] << trigger.activate[3]
827 << trigger.arg[0] << trigger.arg[1] << trigger.arg[2] << trigger.arg[3] << trigger.arg[4]
828 << trigger.playerUse
829 << trigger.playerCross
830 << trigger.monsterUse
831 << trigger.isSecret
832 << trigger.repeatable;
833
834 return arc;
835 }
836