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 § = 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 = §orPalette[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