1 /*
2 ** gamemap_planes.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 
37 #include "doomerrors.h"
38 #include "id_ca.h"
39 #include "g_mapinfo.h"
40 #include "gamemap.h"
41 #include "gamemap_common.h"
42 #include "lnspec.h"
43 #include "scanner.h"
44 #include "w_wad.h"
45 #include "wl_game.h"
46 #include "wl_shade.h"
47 
48 static const char* const FeatureFlagNames[] = {
49 	"lightlevels",
50 	NULL
51 };
52 
53 class Xlat : public TextMapParser
54 {
55 public:
56 	enum
57 	{
58 		TF_PATHING = 1,
59 		TF_HOLOWALL = 2,
60 		TF_AMBUSH = 4,
61 
62 		TF_ISELEVATOR = 0x40000000,
63 		TF_ISTRIGGER = 0x80000000
64 	};
65 
66 	enum EFeatureFlags
67 	{
68 		FF_LIGHTLEVELS = 1
69 	};
70 
71 	struct ThingXlat
72 	{
73 	public:
SortCompareXlat::ThingXlat74 		static int SortCompare(const void *t1, const void *t2)
75 		{
76 			unsigned short old1 = static_cast<const ThingXlat *>(t1)->oldnum;
77 			unsigned short old2 = static_cast<const ThingXlat *>(t2)->oldnum;
78 			if(old1 < old2)
79 				return -1;
80 			else if(old1 > old2)
81 				return 1;
82 			return 0;
83 		}
84 
85 		unsigned short	oldnum;
86 		unsigned short	newnum;
87 		unsigned char	angles;
88 		uint32_t		flags;
89 		unsigned char	minskill;
90 
91 		MapTrigger		templateTrigger;
92 	};
93 
94 	struct ModZone
95 	{
96 	public:
97 		enum Type
98 		{
99 			AMBUSH,
100 			CHANGETRIGGER
101 		};
102 
103 		Type			type;
104 		bool			fillZone;
105 
106 		unsigned int	oldTrigger;
107 		MapTrigger		triggerTemplate;
108 	};
109 
Xlat()110 	Xlat() : lump(0)
111 	{
112 	}
113 
ClearTables()114 	void ClearTables()
115 	{
116 		// Clear out old data (to be called before initial load)
117 		for(unsigned int i = 0;i < 256;++i)
118 		{
119 			flatTable[i][0].SetInvalid();
120 			flatTable[i][1].SetInvalid();
121 		}
122 		tileTriggers.Clear();
123 		thingTable.Clear();
124 	}
125 
LoadXlat(const FString & baseLumpName,const GameInfo::FStringStack * baseStack,bool included=false)126 	void LoadXlat(const FString &baseLumpName, const GameInfo::FStringStack *baseStack, bool included=false)
127 	{
128 		int lump = Wads.CheckNumForFullName(baseLumpName, true);
129 		if(lump == -1)
130 		{
131 			FString error;
132 			error.Format("Could not open map translator '%s'.", baseLumpName.GetChars());
133 			throw CRecoverableError(error);
134 		}
135 
136 		if(!included)
137 		{
138 			if(this->lump == lump)
139 				return;
140 			this->lump = lump;
141 			FeatureFlags = static_cast<EFeatureFlags>(0);
142 
143 			ClearTables();
144 		}
145 
146 		FMemLump data = Wads.ReadLump(lump);
147 		Scanner sc((const char*)data.GetMem(), data.GetSize());
148 		sc.SetScriptIdentifier(Wads.GetLumpFullName(lump));
149 
150 		while(sc.TokensLeft())
151 		{
152 			sc.MustGetToken(TK_Identifier);
153 
154 			if(sc->str.CompareNoCase("tiles") == 0)
155 				LoadTilesTable(sc);
156 			else if(sc->str.CompareNoCase("things") == 0)
157 				LoadThingTable(sc);
158 			else if(sc->str.CompareNoCase("flats") == 0)
159 				LoadFlatsTable(sc);
160 			else if(sc->str.CompareNoCase("include") == 0)
161 			{
162 				sc.MustGetToken(TK_StringConst);
163 
164 				// "$base" is used to include the previous translator in the stack
165 				FString lumpName = sc->str;
166 				const GameInfo::FStringStack *baseStackNext = baseStack;
167 				if(lumpName.CompareNoCase("$base") == 0)
168 				{
169 					if(baseStack == NULL)
170 						sc.ScriptMessage(Scanner::ERROR, "$base is empty.");
171 					lumpName = baseStack->str;
172 					baseStackNext = baseStack->Next();
173 				}
174 
175 				LoadXlat(lumpName, baseStackNext, true);
176 			}
177 			else if(sc->str.CompareNoCase("enable") == 0 || sc->str.CompareNoCase("disable") == 0)
178 			{
179 				bool enable = sc->str.CompareNoCase("enable") == 0;
180 				sc.MustGetToken(TK_Identifier);
181 				unsigned int i = 0;
182 				do
183 				{
184 					if(sc->str.CompareNoCase(FeatureFlagNames[i]) == 0)
185 					{
186 						if(enable)
187 							FeatureFlags = static_cast<EFeatureFlags>(FeatureFlags|(1<<i));
188 						else
189 							FeatureFlags = static_cast<EFeatureFlags>(FeatureFlags|(~(1<<i)));
190 						break;
191 					}
192 				}
193 				while(FeatureFlagNames[++i]);
194 				sc.MustGetToken(';');
195 			}
196 			else
197 				sc.ScriptMessage(Scanner::ERROR, "Unknown xlat property '%s'.", sc->str.GetChars());
198 		}
199 	}
200 
GetFeatureFlags() const201 	EFeatureFlags GetFeatureFlags() const { return FeatureFlags; }
202 
GetTilePalette(TArray<MapTile> & tilePalette)203 	WORD GetTilePalette(TArray<MapTile> &tilePalette)
204 	{
205 		WORD max = 0;
206 		WORD min = 0xFFFF;
207 
208 		TMap<WORD, MapTile>::Iterator iter(this->tilePalette);
209 		TMap<WORD, MapTile>::Pair *pair;
210 		while(iter.NextPair(pair))
211 		{
212 			if(pair->Key > max)
213 				max = pair->Key;
214 			if(pair->Key < min)
215 				min = pair->Key;
216 		}
217 		if(min > max)
218 			throw CRecoverableError("No tiles found for translation!");
219 
220 		tilePalette.Resize(max-min+1);
221 
222 		iter.Reset();
223 		while(iter.NextPair(pair))
224 		{
225 			tilePalette[pair->Key - min] = pair->Value;
226 		}
227 		return min;
228 	}
GetZonePalette(TArray<MapZone> & zonePalette)229 	void GetZonePalette(TArray<MapZone> &zonePalette)
230 	{
231 		TMap<WORD, MapZone>::Iterator iter(this->zonePalette);
232 		TMap<WORD, MapZone>::Pair *pair;
233 		while(iter.NextPair(pair))
234 		{
235 			pair->Value.index = zonePalette.Size();
236 			zonePalette.Push(pair->Value);
237 		}
238 	}
GetModZone(unsigned short tile,ModZone & modZone)239 	bool GetModZone(unsigned short tile, ModZone &modZone)
240 	{
241 		ModZone *item = modZones.CheckKey(tile);
242 		if(item)
243 		{
244 			modZone = *item;
245 			return true;
246 		}
247 		return false;
248 	}
IsValidTile(unsigned short tile)249 	bool IsValidTile(unsigned short tile)
250 	{
251 		MapTile *item = tilePalette.CheckKey(tile);
252 		if(item)
253 			return true;
254 		return false;
255 	}
256 
TranslateFlat(unsigned int index,bool ceiling)257 	FTextureID TranslateFlat(unsigned int index, bool ceiling)
258 	{
259 		if(flatTable[index][ceiling].isValid())
260 			return flatTable[index][ceiling];
261 		return levelInfo->DefaultTexture[ceiling];
262 	}
263 
TranslateTileTrigger(unsigned short tile,MapTrigger & trigger)264 	bool TranslateTileTrigger(unsigned short tile, MapTrigger &trigger)
265 	{
266 		MapTrigger *item = tileTriggers.CheckKey(tile);
267 		if(item)
268 		{
269 			trigger = *item;
270 			return true;
271 		}
272 		return false;
273 	}
274 
TranslateThing(MapThing & thing,MapTrigger & trigger,uint32_t & flags,unsigned short oldnum) const275 	bool TranslateThing(MapThing &thing, MapTrigger &trigger, uint32_t &flags, unsigned short oldnum) const
276 	{
277 		unsigned int index = SearchForThing(oldnum, thingTable.Size());
278 		if(index == UINT_MAX)
279 			return false;
280 		const ThingXlat &type = thingTable[index];
281 
282 		flags = type.flags;
283 		if(type.flags & Xlat::TF_ISELEVATOR)
284 			return true;
285 
286 		if(type.flags & Xlat::TF_ISTRIGGER)
287 		{
288 			trigger = type.templateTrigger;
289 		}
290 		else
291 		{
292 			thing.type = type.newnum;
293 
294 			// The player has a weird rotation pattern. It's 450-angle.
295 			bool playerRotation = false;
296 			if(thing.type == 1)
297 				playerRotation = true;
298 
299 			if(type.angles)
300 			{
301 				thing.angle = (oldnum - type.oldnum)*(360/type.angles);
302 				if(playerRotation)
303 					thing.angle = (360 + 360/type.angles)-thing.angle;
304 			}
305 			else
306 				thing.angle = 0;
307 
308 			thing.patrol = flags&Xlat::TF_PATHING;
309 			thing.skill[0] = thing.skill[1] = type.minskill <= 1;
310 			thing.skill[2] = type.minskill <= 2;
311 			thing.skill[3] = type.minskill <= 3;
312 		}
313 		return true;
314 	}
315 
TranslateZone(unsigned short tile)316 	int TranslateZone(unsigned short tile)
317 	{
318 		MapZone *zone = zonePalette.CheckKey(tile);
319 		if(zone)
320 			return zone->index;
321 		return -1;
322 	}
323 
324 protected:
325 	// Find the index in the thing table corresponding to an old thing number
326 	// taking into account the number of angle variants
SearchForThing(unsigned short oldnum,unsigned int tableSize) const327 	unsigned int SearchForThing(unsigned short oldnum, unsigned int tableSize) const
328 	{
329 		unsigned int type = tableSize/2;
330 		unsigned int max = tableSize-1;
331 		unsigned int min = 0;
332 		do
333 		{
334 			ThingXlat &check = thingTable[type];
335 			if(check.oldnum == oldnum ||
336 				(check.angles && unsigned(oldnum - check.oldnum) < check.angles))
337 			{
338 				return type;
339 			}
340 
341 			if(check.oldnum > oldnum)
342 				max = type-1;
343 			else if(check.oldnum < oldnum)
344 				min = type+1;
345 
346 			type = (max+min)/2;
347 		}
348 		while(max >= min && max < tableSize);
349 		return UINT_MAX;
350 	}
351 
LoadFlatsTable(Scanner & sc)352 	void LoadFlatsTable(Scanner &sc)
353 	{
354 		sc.MustGetToken('{');
355 		while(!sc.CheckToken('}'))
356 		{
357 			sc.MustGetToken(TK_Identifier);
358 			bool ceiling = sc->str.CompareNoCase("ceiling") == 0;
359 			if(!ceiling && sc->str.CompareNoCase("floor") != 0)
360 				sc.ScriptMessage(Scanner::ERROR, "Unknown flat section '%s'.", sc->str.GetChars());
361 
362 			sc.MustGetToken('{');
363 			unsigned int index = 0;
364 			do
365 			{
366 				sc.MustGetToken(TK_StringConst);
367 				FTextureID texID = TexMan.GetTexture(sc->str, FTexture::TEX_Flat);
368 				if(sc.CheckToken('='))
369 				{
370 					sc.MustGetToken(TK_IntConst);
371 					index = sc->number;
372 					if(index > 255)
373 						index = 255;
374 				}
375 				flatTable[index++][ceiling] = texID;
376 
377 				if(index == 256)
378 					break;
379 			}
380 			while(sc.CheckToken(','));
381 			sc.MustGetToken('}');
382 		}
383 	}
384 
LoadTilesTable(Scanner & sc)385 	void LoadTilesTable(Scanner &sc)
386 	{
387 		sc.MustGetToken('{');
388 		while(!sc.CheckToken('}'))
389 		{
390 			sc.MustGetToken(TK_Identifier);
391 			if(sc->str.CompareNoCase("trigger") == 0)
392 			{
393 				sc.MustGetToken(TK_IntConst);
394 				if(sc->number > 0xFFFF)
395 					sc.ScriptMessage(Scanner::ERROR, "Trigger number out of range.");
396 
397 				MapTrigger &trigger = tileTriggers[sc->number];
398 				sc.MustGetToken('{');
399 				TextMapParser::ParseTrigger(sc, trigger);
400 			}
401 			else if(sc->str.CompareNoCase("tile") == 0)
402 			{
403 				sc.MustGetToken(TK_IntConst);
404 				if(sc->number > 0xFFFF)
405 					sc.ScriptMessage(Scanner::ERROR, "Tile number out of range.");
406 
407 				MapTile &tile = tilePalette[sc->number];
408 				sc.MustGetToken('{');
409 				TextMapParser::ParseTile(sc, tile);
410 			}
411 			else if(sc->str.CompareNoCase("modzone") == 0)
412 			{
413 				sc.MustGetToken(TK_IntConst);
414 				if(sc->number > 0xFFFF)
415 					sc.ScriptMessage(Scanner::ERROR, "Modzone number out of range.");
416 
417 				unsigned int zoneIndex = sc->number;
418 				ModZone &zone = modZones[zoneIndex];
419 				sc.MustGetToken(TK_Identifier);
420 				if(sc->str.CompareNoCase("fillzone") == 0)
421 				{
422 					zone.fillZone = true;
423 					sc.MustGetToken(TK_Identifier);
424 				}
425 				else
426 				{
427 					zonePalette[zoneIndex] = MapZone();
428 					zone.fillZone = false;
429 				}
430 
431 				if(sc->str.CompareNoCase("ambush") == 0)
432 				{
433 					zone.type = ModZone::AMBUSH;
434 					sc.MustGetToken(';');
435 				}
436 				else if(sc->str.CompareNoCase("changetrigger") == 0)
437 				{
438 					sc.MustGetToken(TK_IntConst);
439 					zone.type = ModZone::CHANGETRIGGER;
440 					zone.oldTrigger = sc->number;
441 
442 					sc.MustGetToken('{');
443 					TextMapParser::ParseTrigger(sc, zone.triggerTemplate);
444 				}
445 				else
446 					sc.ScriptMessage(Scanner::ERROR, "Unknown modzone type.");
447 			}
448 			else if(sc->str.CompareNoCase("zone") == 0)
449 			{
450 				sc.MustGetToken(TK_IntConst);
451 
452 				MapZone &zone = zonePalette[sc->number];
453 				sc.MustGetToken('{');
454 				TextMapParser::ParseZone(sc, zone);
455 			}
456 		}
457 	}
458 
LoadThingTable(Scanner & sc)459 	void LoadThingTable(Scanner &sc)
460 	{
461 		// Get the size of existing table so we know what we need to search
462 		// for replacements.
463 		unsigned int oldTableSize = thingTable.Size();
464 		unsigned int replace;
465 
466 		sc.MustGetToken('{');
467 		while(!sc.CheckToken('}'))
468 		{
469 			ThingXlat thing;
470 
471 			// Property
472 			if(sc.CheckToken(TK_Identifier))
473 			{
474 				if(sc->str.CompareNoCase("trigger") == 0)
475 				{
476 					sc.MustGetToken(TK_IntConst);
477 					thing.flags = Xlat::TF_ISTRIGGER;
478 					thing.angles = 0;
479 					thing.oldnum = sc->number;
480 
481 					sc.MustGetToken('{');
482 					TextMapParser::ParseTrigger(sc, thing.templateTrigger);
483 				}
484 				else if(sc->str.CompareNoCase("elevator") == 0)
485 				{
486 					sc.MustGetToken(TK_IntConst);
487 					thing.flags = Xlat::TF_ISELEVATOR;
488 					thing.angles = 0;
489 					thing.oldnum = sc->number;
490 					sc.MustGetToken(';');
491 				}
492 				else
493 					sc.ScriptMessage(Scanner::ERROR, "Unknown thing table block '%s'.", sc->str.GetChars());
494 			}
495 			else
496 			{
497 				// Handle thing translation
498 				sc.MustGetToken('{');
499 				sc.MustGetToken(TK_IntConst);
500 				thing.oldnum = sc->number;
501 				sc.MustGetToken(',');
502 				sc.MustGetToken(TK_IntConst);
503 				thing.newnum = sc->number;
504 				sc.MustGetToken(',');
505 				sc.MustGetToken(TK_IntConst);
506 				thing.angles = sc->number;
507 				sc.MustGetToken(',');
508 				if(sc.CheckToken(TK_IntConst))
509 					thing.flags = sc->number;
510 				else
511 				{
512 					thing.flags = 0;
513 					do
514 					{
515 						sc.MustGetToken(TK_Identifier);
516 						if(sc->str.CompareNoCase("PATHING") == 0)
517 							thing.flags |= TF_PATHING;
518 						else if(sc->str.CompareNoCase("HOLOWALL") == 0)
519 							thing.flags |= TF_HOLOWALL;
520 						else if(sc->str.CompareNoCase("AMBUSH") == 0)
521 							thing.flags |= TF_AMBUSH;
522 						else
523 							sc.ScriptMessage(Scanner::ERROR, "Unknown flag '%s'.", sc->str.GetChars());
524 					}
525 					while(sc.CheckToken('|'));
526 				}
527 				sc.MustGetToken(',');
528 				sc.MustGetToken(TK_IntConst);
529 				thing.minskill = sc->number;
530 				sc.MustGetToken('}');
531 			}
532 
533 			if(oldTableSize &&
534 				(replace = SearchForThing(thing.oldnum, oldTableSize)) != UINT_MAX)
535 			{
536 				ThingXlat &replaced = thingTable[replace];
537 				if(replaced.oldnum != thing.oldnum) // Quick check for potential unwanted behavior.
538 					sc.ScriptMessage(Scanner::ERROR, "Thing %d partially replaces %d.\n", thing.oldnum, replaced.oldnum);
539 				replaced = thing;
540 			}
541 			else
542 				thingTable.Push(thing);
543 		}
544 
545 		qsort(&thingTable[0], thingTable.Size(), sizeof(thingTable[0]), ThingXlat::SortCompare);
546 
547 		// Sanity check for mod authors: Check for duplicate/overlapping oldnums
548 		unsigned int i = thingTable.Size();
549 		if(i >= 2)
550 		{
551 			const ThingXlat *current = &thingTable[--i];
552 			unsigned short oldmin = current->oldnum;
553 			unsigned short oldmax = oldmin + (current->angles ? current->angles - 1 : 0);
554 			do
555 			{
556 				--current;
557 				if(current->oldnum <= oldmax && current->oldnum + current->angles - 1 >= oldmin)
558 					sc.ScriptMessage(Scanner::ERROR, "Thing table contains ambiguous overlap for old num %d (%d).\n", oldmin, i);
559 				oldmin = current->oldnum;
560 				oldmax = oldmin + (current->angles ? current->angles - 1 : 0);
561 			}
562 			while(--i);
563 		}
564 	}
565 
566 private:
567 	int lump;
568 
569 	TArray<ThingXlat> thingTable;
570 	TMap<WORD, MapTile> tilePalette;
571 	TMap<WORD, MapTrigger> tileTriggers;
572 	TMap<WORD, ModZone> modZones;
573 	TMap<WORD, MapZone> zonePalette;
574 	FTextureID flatTable[256][2]; // Floor/ceiling textures
575 	EFeatureFlags FeatureFlags;
576 };
577 
578 static int FindAdjacentDoor(MapSpot spot, MapTrigger *&trigger);
579 
580 /* Reads old format maps... well technically WDC format maps.
581  * char[6] - Magic "WDC3.1"
582  * int32 - Number of maps (Should be 1 in our case)
583  * int16 - Number of planes
584  * int16 - (Max) name length
585  * --- The following would be repeated per map ---
586  * char[max] - Name
587  * int16 - Width
588  * int16 - Hieght
589  * ... raw plane data ...
590  */
ReadPlanesData()591 void GameMap::ReadPlanesData()
592 {
593 	static Xlat xlat;
594 	static const unsigned short UNIT = 64;
595 	enum OldPlanes { Plane_Tiles, Plane_Object, Plane_Flats, NUM_USABLE_PLANES };
596 
597 	if(levelInfo->Translator.IsEmpty())
598 		xlat.LoadXlat(gameinfo.Translator.str, gameinfo.Translator.Next());
599 	else
600 		xlat.LoadXlat(levelInfo->Translator, &gameinfo.Translator);
601 
602 	Xlat::EFeatureFlags FeatureFlags = xlat.GetFeatureFlags();
603 
604 	// Old format maps always have a tile size of 64
605 	header.tileSize = UNIT;
606 
607 	FileReader *lump = lumps[0];
608 
609 	// Read plane count
610 	lump->Seek(10, SEEK_SET);
611 	WORD numPlanes, nameLength;
612 	lump->Read(&numPlanes, 2);
613 	lump->Read(&nameLength, 2);
614 	numPlanes = LittleShort(numPlanes);
615 	nameLength = LittleShort(nameLength);
616 
617 	char* name = new char[nameLength+1];
618 	lump->Read(name, nameLength);
619 	name[nameLength] = 0;
620 	header.name = name;
621 	delete[] name;
622 
623 	WORD dimensions[2];
624 	lump->Read(dimensions, 4);
625 	dimensions[0] = LittleShort(dimensions[0]);
626 	dimensions[1] = LittleShort(dimensions[1]);
627 	DWORD size = dimensions[0]*dimensions[1];
628 	header.width = dimensions[0];
629 	header.height = dimensions[1];
630 
631 	Plane &mapPlane = NewPlane();
632 	mapPlane.depth = UNIT;
633 
634 	// We need to store the spots marked for ambush since it's stored in the
635 	// tiles plane instead of the objects plane.
636 	TArray<WORD> ambushSpots;
637 	TArray<MapTrigger> triggers;
638 	TMap<WORD, TArray<MapSpot> > elevatorSpots;
639 
640 	// Read and store the info plane so we can reference it
641 	WORD* infoplane = new WORD[size];
642 	if(numPlanes > 3)
643 	{
644 		lump->Seek(size*2*3, SEEK_CUR);
645 		lump->Read(infoplane, size*2);
646 		lump->Seek(18+nameLength, SEEK_SET);
647 	}
648 	else
649 		memset(infoplane, 0, size*2);
650 
651 	for(int plane = 0;plane < numPlanes && plane < NUM_USABLE_PLANES;++plane)
652 	{
653 		if(plane == 3) // Info plane is already read
654 			continue;
655 
656 		WORD* oldplane = new WORD[size];
657 		lump->Read(oldplane, size*2);
658 
659 		switch(plane)
660 		{
661 			default:
662 				break;
663 
664 			case Plane_Tiles:
665 			{
666 				WORD tileStart = xlat.GetTilePalette(tilePalette);
667 				xlat.GetZonePalette(zonePalette);
668 
669 				TArray<WORD> fillSpots;
670 				TMap<WORD, Xlat::ModZone> changeTriggerSpots;
671 
672 
673 				for(unsigned int i = 0;i < size;++i)
674 				{
675 					oldplane[i] = LittleShort(oldplane[i]);
676 
677 					if(xlat.IsValidTile(oldplane[i]))
678 						mapPlane.map[i].SetTile(&tilePalette[oldplane[i]-tileStart]);
679 					else
680 						mapPlane.map[i].SetTile(NULL);
681 
682 					Xlat::ModZone zone;
683 					if(xlat.GetModZone(oldplane[i], zone))
684 					{
685 						if(zone.fillZone)
686 							fillSpots.Push(i);
687 
688 						switch(zone.type)
689 						{
690 							case Xlat::ModZone::AMBUSH:
691 								ambushSpots.Push(i);
692 								break;
693 							case Xlat::ModZone::CHANGETRIGGER:
694 								changeTriggerSpots[i] = zone;
695 								break;
696 						}
697 					}
698 
699 					MapTrigger templateTrigger;
700 					if(xlat.TranslateTileTrigger(oldplane[i], templateTrigger))
701 					{
702 						templateTrigger.x = i%header.width;
703 						templateTrigger.y = i/header.width;
704 						templateTrigger.z = 0;
705 
706 						triggers.Push(templateTrigger);
707 					}
708 
709 					int zoneIndex;
710 					if((zoneIndex = xlat.TranslateZone(oldplane[i])) != -1)
711 						mapPlane.map[i].zone = &zonePalette[zoneIndex];
712 					else
713 						mapPlane.map[i].zone = NULL;
714 				}
715 
716 				// Get a sound zone for modzones that aren't valid sound zones.
717 				for(unsigned int i = 0;i < fillSpots.Size();++i)
718 				{
719 					const int candidates[4] = {
720 						fillSpots[i] + 1,
721 						fillSpots[i] - (int)header.width,
722 						fillSpots[i] - 1,
723 						fillSpots[i] + (int)header.width
724 					};
725 					for(unsigned int j = 0;j < 4;++j)
726 					{
727 						// Ensure that all candidates are valid locations.
728 						// In addition for moving left/right check to see that
729 						// the new location is indeed in the same row.
730 						if((candidates[j] < 0 || (unsigned)candidates[j] > size) ||
731 							((j == Tile::East || j == Tile::West) &&
732 							(candidates[j]/header.width != fillSpots[i]/header.width)))
733 							continue;
734 
735 						// First adjacent zone wins
736 						if(mapPlane.map[candidates[j]].zone != NULL)
737 						{
738 							mapPlane.map[fillSpots[i]].zone = mapPlane.map[candidates[j]].zone;
739 							break;
740 						}
741 					}
742 				}
743 
744 				TMap<WORD, Xlat::ModZone>::Iterator iter(changeTriggerSpots);
745 				TMap<WORD, Xlat::ModZone>::Pair *pair;
746 				while(iter.NextPair(pair))
747 				{
748 					// Look for and switch exit triggers.
749 					// NOTE: We're modifying the adjacent tile so the directions here are reversed.
750 					//       That is to say the tile to the east modifies the west wall.
751 					const int candidates[4] = {
752 						pair->Key - 1,
753 						pair->Key + (int)header.width,
754 						pair->Key + 1,
755 						pair->Key - (int)header.width
756 					};
757 					for(unsigned int j = 0;j < 4;++j)
758 					{
759 						// Same as before, only this time we check if our
760 						// replacement trigger activates at the line between
761 						// this tile and the cadidate.
762 						if((candidates[j] < 0 || (unsigned)candidates[j] > size) ||
763 							((j == Trigger::East || j == Trigger::West) &&
764 							(candidates[j]/header.width != pair->Key/header.width)) ||
765 							!pair->Value.triggerTemplate.activate[j])
766 							continue;
767 
768 						// Look for any triggers matching the candidate
769 						for(int k = triggers.Size()-1;k >= 0;--k)
770 						{
771 							if(triggers[k].action == pair->Value.oldTrigger &&
772 								triggers[k].x == (unsigned)candidates[j]%header.width &&
773 								triggers[k].y == (unsigned)candidates[j]/header.width)
774 							{
775 								// Disable
776 								triggers[k].activate[j] = false;
777 
778 								Trigger &triggerTemplate = pair->Value.triggerTemplate;
779 								triggerTemplate.x = triggers[k].x;
780 								triggerTemplate.y = triggers[k].y;
781 								triggerTemplate.z = triggers[k].z;
782 
783 								// Only enable the activation in the direction we're changing.
784 								triggerTemplate.activate[0] = triggerTemplate.activate[1] = triggerTemplate.activate[2] = triggerTemplate.activate[3] = false;
785 								triggerTemplate.activate[j] = true;
786 
787 								triggers.Push(triggerTemplate);
788 							}
789 						}
790 					}
791 				}
792 
793 				if(FeatureFlags & Xlat::FF_LIGHTLEVELS)
794 				{
795 					// Visibility is roughly exponential
796 					static const fixed visTable[16] = {
797 						0x8888, 0xDDDD, 2<<FRACBITS,
798 						3<<FRACBITS, 8<<FRACBITS, 15<<FRACBITS,
799 						29<<FRACBITS, 56<<FRACBITS, 108<<FRACBITS,
800 						// After this point we basically max out the depth fog any way
801 						200<<FRACBITS, 200<<FRACBITS, 200<<FRACBITS,
802 						200<<FRACBITS, 200<<FRACBITS, 200<<FRACBITS,
803 						200<<FRACBITS
804 					};
805 
806 					gLevelVisibility = visTable[clamp(oldplane[3] - 0xFC, 0, 15)]*LIGHTVISIBILITY_FACTOR;
807 					gLevelLight = clamp(oldplane[2] - 0xD8, 0, 7)*8 + 130; // Seems to be approx accurate for every even number lighting (0, 2, 4, 6)
808 				}
809 				else
810 				{
811 					gLevelVisibility = levelInfo->DefaultVisibility;
812 					gLevelLight = levelInfo->DefaultLighting;
813 				}
814 				gLevelMaxLightVis = levelInfo->DefaultMaxLightVis;
815 				break;
816 			}
817 
818 			case Plane_Object:
819 			{
820 				unsigned int ambushSpot = 0;
821 				ambushSpots.Push(0xFFFF); // Prevent uninitialized value errors.
822 
823 				unsigned int i = 0;
824 				// Using ROTT feature flags invalidates the first four tiles
825 				if(xlat.GetFeatureFlags() & Xlat::FF_LIGHTLEVELS)
826 					i = 4;
827 				for(;i < size;++i)
828 				{
829 					oldplane[i] = LittleShort(oldplane[i]);
830 
831 					if(oldplane[i] == 0)
832 					{
833 						// In case of malformed maps we need to always check this.
834 						if(ambushSpots[ambushSpot] == i)
835 							++ambushSpot;
836 						continue;
837 					}
838 
839 					Thing thing;
840 					Trigger trigger;
841 					uint32_t flags = 0;
842 
843 					if(!xlat.TranslateThing(thing, trigger, flags, oldplane[i]))
844 						printf("Unknown old type %d @ (%d,%d)\n", oldplane[i], i%header.width, i/header.width);
845 					else
846 					{
847 						if(flags & Xlat::TF_ISTRIGGER)
848 						{
849 							trigger.x = i%header.width;
850 							trigger.y = i/header.width;
851 							trigger.z = 0;
852 
853 							triggers.Push(trigger);
854 						}
855 						else if(flags & Xlat::TF_ISELEVATOR)
856 						{
857 							elevatorSpots[oldplane[i]].Push(&mapPlane.map[i]);
858 						}
859 						else
860 						{
861 							if(flags & Xlat::TF_HOLOWALL)
862 							{
863 								MapSpot spot = &mapPlane.map[i];
864 								if(spot->tile)
865 								{
866 									spot->sideSolid[0] = spot->sideSolid[1] = spot->sideSolid[2] = spot->sideSolid[3] = false;
867 									if(flags & Xlat::TF_PATHING)
868 									{
869 										// If we created a holowall and we path into another wall it should also become non-solid.
870 										spot = spot->GetAdjacent(MapTile::Side(thing.angle/90));
871 										if(spot->tile)
872 											spot->sideSolid[0] = spot->sideSolid[1] = spot->sideSolid[2] = spot->sideSolid[3] = false;
873 									}
874 								}
875 							}
876 
877 							thing.x = ((i%header.width)<<FRACBITS)+(FRACUNIT/2);
878 							thing.y = ((i/header.width)<<FRACBITS)+(FRACUNIT/2);
879 							thing.z = 0;
880 							thing.ambush = (flags & Xlat::TF_AMBUSH) || ambushSpots[ambushSpot] == i;
881 							things.Push(thing);
882 						}
883 					}
884 
885 					if(ambushSpots[ambushSpot] == i)
886 						++ambushSpot;
887 				}
888 				break;
889 			}
890 
891 			case Plane_Flats:
892 			{
893 				// Look for all unique floor/ceiling texture combinations.
894 				WORD type = 0;
895 				TMap<WORD, WORD> flatMap;
896 				for(unsigned int i = 0;i < size;++i)
897 				{
898 					oldplane[i] = LittleShort(oldplane[i]);
899 
900 					if(!flatMap.CheckKey(oldplane[i]))
901 						flatMap[oldplane[i]] = type++;
902 				}
903 
904 				// Build the palette.
905 				sectorPalette.Resize(type);
906 				TMap<WORD, WORD>::ConstIterator iter(flatMap);
907 				TMap<WORD, WORD>::ConstPair *pair;
908 				while(iter.NextPair(pair))
909 				{
910 					Sector &sect = sectorPalette[pair->Value];
911 					sect.texture[Sector::Floor] = xlat.TranslateFlat(pair->Key&0xFF, Sector::Floor);
912 					sect.texture[Sector::Ceiling] = xlat.TranslateFlat(pair->Key>>8, Sector::Ceiling);
913 				}
914 
915 				// Now link the sector data to map points!
916 				for(unsigned int i = 0;i < size;++i)
917 					mapPlane.map[i].sector = &sectorPalette[flatMap[oldplane[i]]];
918 				break;
919 			}
920 		}
921 		delete[] oldplane;
922 	}
923 
924 	SetupLinks();
925 
926 	// Install triggers
927 	for(unsigned int i = 0;i < triggers.Size();++i)
928 	{
929 		Trigger &templateTrigger = triggers[i];
930 
931 		// Check the info plane and if set move the activation point to a switch ot touch plate
932 		const WORD info = infoplane[templateTrigger.y*header.width + templateTrigger.x];
933 		if(info)
934 		{
935 			MapSpot target = GetSpot(templateTrigger.x, templateTrigger.y, 0);
936 			unsigned int tag = (templateTrigger.x<<8)|templateTrigger.y;
937 			SetSpotTag(target, tag);
938 
939 			// Activated by touch plate or switch
940 			templateTrigger.arg[0] = tag;
941 			templateTrigger.x = (info>>8)&0xFF;
942 			templateTrigger.y = info&0xFF;
943 
944 			MapSpot spot = GetSpot(templateTrigger.x, templateTrigger.y, 0);
945 			if(spot->tile) // Switch
946 			{
947 				templateTrigger.playerCross = false;
948 				templateTrigger.playerUse = true;
949 			}
950 			else // Touch plate
951 			{
952 				templateTrigger.playerCross = true;
953 				templateTrigger.playerUse = false;
954 			}
955 			templateTrigger.activate[0] = templateTrigger.activate[1] = templateTrigger.activate[2] = templateTrigger.activate[3] = true;
956 		}
957 
958 		Trigger &trig = NewTrigger(templateTrigger.x, templateTrigger.y, templateTrigger.z);
959 		trig = templateTrigger;
960 
961 		if(trig.isSecret)
962 			++gamestate.secrettotal;
963 	}
964 	delete[] infoplane;
965 
966 	// Install elevators
967 	TMap<WORD, TArray<MapSpot> >::ConstIterator iter(elevatorSpots);
968 	TMap<WORD, TArray<MapSpot> >::ConstPair *pair;
969 	while(iter.NextPair(pair))
970 	{
971 		const TArray<MapSpot> &locations = pair->Value;
972 
973 		// Elevators in the same sound zone move faster
974 		bool samezone = true;
975 		for(unsigned int i = locations.Size();i-- > 1;)
976 		{
977 			if(locations[i]->zone != locations[0]->zone)
978 			{
979 				samezone = false;
980 				break;
981 			}
982 		}
983 
984 		{
985 			unsigned int elevTag = 0;
986 			unsigned int swtchTag = 0;
987 			int *lastNext = NULL;
988 			for(unsigned int i = 0;i < locations.Size();++i)
989 			{
990 				MapSpot spot = locations[i];
991 
992 				// Search for door in adjacent tile
993 				int doorside;
994 				MapTrigger *trigger = NULL;
995 				if((doorside = FindAdjacentDoor(spot, trigger)) != -1)
996 				{
997 					MapSpot door = spot->GetAdjacent(static_cast<MapTile::Side>(doorside));
998 					MapSpot swtch = spot->GetAdjacent(static_cast<MapTile::Side>(doorside), true);
999 
1000 					trigger->action = Specials::Door_Elevator;
1001 					trigger->arg[0] = (swtch->GetX()<<8)|swtch->GetY();
1002 					SetSpotTag(swtch, trigger->arg[0]);
1003 					if(i == 0)
1004 					{
1005 						elevTag = trigger->arg[0];
1006 						elevatorPosition[elevTag] = swtch;
1007 					}
1008 
1009 					Trigger &elevTrigger = NewTrigger(swtch->GetX(), swtch->GetY(), 0);
1010 					elevTrigger.action = Specials::Elevator_SwitchFloor;
1011 					elevTrigger.playerUse = true;
1012 					elevTrigger.repeatable = true;
1013 					elevTrigger.arg[0] = elevTag;
1014 					elevTrigger.arg[1] = (door->GetX()<<8)|door->GetY();
1015 					elevTrigger.arg[2] = samezone ? 140 : 280;
1016 					if(i == 0)
1017 						lastNext = &elevTrigger.arg[3];
1018 					else
1019 						elevTrigger.arg[3] = swtchTag;
1020 					SetSpotTag(door, elevTrigger.arg[1]);
1021 
1022 					swtchTag = trigger->arg[0];
1023 				}
1024 			}
1025 			*lastNext = swtchTag;
1026 		}
1027 	}
1028 }
1029 
FindAdjacentDoor(MapSpot spot,MapTrigger * & trigger)1030 static int FindAdjacentDoor(MapSpot spot, MapTrigger *&trigger)
1031 {
1032 	const TArray<MapTrigger> *triggers[4] = {
1033 		&spot->GetAdjacent(MapTile::East)->triggers,
1034 		&spot->GetAdjacent(MapTile::North)->triggers,
1035 		&spot->GetAdjacent(MapTile::West)->triggers,
1036 		&spot->GetAdjacent(MapTile::South)->triggers
1037 	};
1038 
1039 	for(unsigned int i = 0;i < 4;++i)
1040 	{
1041 		for(unsigned int t = triggers[i]->Size();t-- > 0;)
1042 		{
1043 			if(triggers[i]->operator[](t).action == Specials::Door_Open)
1044 			{
1045 				trigger = &triggers[i]->operator[](t);
1046 				return i;
1047 			}
1048 		}
1049 	}
1050 	return -1;
1051 }
1052