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 &sectorPalette[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 - &sectorPalette[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 *&sector)
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