1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * aint32 with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  *
22  * Based on the original sources
23  *   Faery Tale II -- The Halls of the Dead
24  *   (c) 1993-1996 The Wyrmkeep Entertainment Co.
25  */
26 
27 #include "common/debug.h"
28 #include "graphics/surface.h"
29 
30 #include "saga2/saga2.h"
31 #include "saga2/blitters.h"
32 #include "saga2/hresmgr.h"
33 #include "saga2/objects.h"
34 #include "saga2/tile.h"
35 #include "saga2/oncall.h"
36 #include "saga2/input.h"
37 #include "saga2/cmisc.h"
38 #include "saga2/setup.h"
39 
40 #include "saga2/tagnoise.h"
41 #include "saga2/player.h"
42 #include "saga2/mapfeatr.h"
43 
44 //  Include files needed for SAGA script dispatch
45 #include "saga2/script.h"
46 #include "saga2/methods.r"                    // generated by SAGA
47 
48 namespace Saga2 {
49 
50 extern void writeLog(char *str);
51 void PlayModeSetup();
52 void initBackPanel();
53 
54 #define TATLOG  0
55 
56 /* ===================================================================== *
57    Constants
58  * ===================================================================== */
59 
60 const uint32        tileTerrainID   = MKTAG('T', 'E', 'R',  0),
61                     platformID      = MKTAG('P', 'L', 'T',  0),
62                     metaID          = MKTAG('M', 'E', 'T',  0),
63                     mapID           = MKTAG('M', 'A', 'P',  0),
64                     tagID           = MKTAG('T', 'A', 'G',  0),
65                     tagDataID       = MKTAG('T', 'G', 'D',  0),
66                     tagStateID      = MKTAG('T', 'S', 'T',  0),
67                     assocID         = MKTAG('A', 'S', 'C',  0),
68                     cycleID         = MKTAG('C', 'Y', 'C', 'L');
69 
70 //  Scrolling Speed constants
71 
72 const int           slowScrollSpeed = 6,
73                     snapScrollSpeed = maxint32,
74                     slowThreshhold  = 16,
75 //					fastThreshhold   = 100,
76                     fastThreshhold  = 16,
77                     snapThreshhold  = 400;
78 
79 const StaticTilePoint Nowhere = {(int16)minint16, (int16)minint16, (int16)minint16};
80 
81 const StaticMetaTileID NoMetaTile = {nullID, nullID};
82 const StaticActiveItemID  NoActiveItem = {activeItemIndexNullID};
83 
84 enum SurfaceType {
85 	surfaceHoriz,               //  Level surface
86 	surfaceVertV,               //  Vertical surface, parallel to V axis
87 	surfaceVertU                //  Vertical surface, parallel to U axis
88 };
89 
90 
91 void updateSpeech();
92 void setAreaSound(const TilePoint &baseCoords);
93 
94 /* ===================================================================== *
95    Bank switching interface
96  * ===================================================================== */
97 
98 TileBankPtr tileBanks[maxBanks];
99 
100 void updateHandleRefs(const TilePoint &pt);  //, StandingTileInfo *stiResult )
101 void updateFrameCount(void);
102 
103 /* ===================================================================== *
104    Prototypes
105  * ===================================================================== */
106 
107 hResContext         *tileRes;       // tile resource handle
108 
109 void drawPlatform(
110 	gPixelMap &drawMap,
111     Platform        **pList,                // platforms to draw
112     Point16         screenPos,              // screen position
113     int16           uOrg,                   // for TAG search
114     int16           vOrg);                  // for TAG search
115 
116 bool isTilePixelOpaque(int16 baseX,          // X coordinate relative to base
117                        int16 baseY,            // Y coordinate relative to base
118                        int16 mapHeight,        // pixel height of tile's bitmap
119                        uint8 *td);         // packed tile bitmap
120 
121 SurfaceType pointOnTile(TileInfo            *ti,
122                         const Point32      &tileRel,
123                         int16              h,
124                         const TilePoint    &tCoords,
125                         TilePoint          &pickCoords,
126                         TilePoint          &floorCoords);
127 
128 bool validSurface(const TilePoint &tileCoords,
129                   const TilePoint &pickCoords);
130 
131 void markMetaAsVisited(const TilePoint &pt);
132 
133 /* ===================================================================== *
134    Prototypes
135  * ===================================================================== */
136 
137 
138 extern void buildDisplayList(void);
139 extern void drawDisplayList(void);
140 //extern void evaluateActorNeeds( int32 );
141 extern void updateActorTasks(void);
142 extern void updateObjectAppearances(int32 deltaTime);
143 extern void getViewTrackPos(TilePoint &tp);
144 extern GameObject *getViewCenterObject(void);
145 extern TilePoint centerActorCoords(void);
146 void freeAllTileBanks(void);
147 
148 void cycleTiles(uint32 elapsed);
149 
150 #if DEBUG
151 void TPLine(const TilePoint &start, const TilePoint &stop);
152 #endif
153 
154 void drawFloatingWindows(gPort &, const Point16 &, const Rect16 &clip);
155 
156 /* ===================================================================== *
157    Imports
158  * ===================================================================== */
159 
160 extern int16        worldCount;     //  Used as map count as well
161 
162 extern ObjectID     viewCenterObject;       // ID of object that view tracks
163 
164 /* ===================================================================== *
165    Tile structure management
166  * ===================================================================== */
167 
168 int16                   cycleCount;
169 
170 uint16                  rippedRoofID;
171 
172 static StaticTilePoint  ripTableCoords = Nowhere;
173 
174 static RipTable         *ripTableList;
175 
176 WorldMapData            *mapList;           //  master map data array
177 
178 byte                   **stateArray;        //  Array of active item instance
179 //  state arrays
180 
181 CyclePtr                cycleList;          // list of tile cycling info
182 
183 //  Platform caching management
184 PlatformCacheEntry  *platformCache;
185 
186 /* ===================================================================== *
187    View state
188  * ===================================================================== */
189 
190 int16               defaultScrollSpeed = slowScrollSpeed;
191 
192 static StaticPoint32 tileScroll = {0, 0},             // current tile scroll pos
193                      targetScroll = {0, 0};           // where scroll going to
194 StaticPoint16 fineScroll = {0, 0};
195 
196 StaticTilePoint viewCenter = {0, 0, 0};             // coordinates of view on map
197 
198 //  These two variables define which sectors overlap the view rect.
199 
200 int16               lastMapNum;
201 
202 int32               lastUpdateTime;         // time of last display update
203 
204 /* also:
205     -- height of center character
206     -- what map we are on.
207  */
208 
209 /* ===================================================================== *
210    ActiveItemID member functions
211  * ===================================================================== */
212 
213 //-----------------------------------------------------------------------
214 //	Return the address of a tile's TileInfo structure given that tile's ID
215 
tileAddress(TileID id)216 TileInfo *TileInfo::tileAddress(TileID id) {
217 	TileInfo        *ti;
218 	TileBankPtr     tbh;
219 	int16           tileBank,
220 	                tileNum;
221 
222 	if (id == 0) return nullptr;
223 
224 	TileID2Bank(id, tileBank, tileNum);
225 	if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
226 	ti = tbh->tile(tileNum);
227 
228 	if (ti->attrs.cycleRange > 0) {
229 		TileCycleData   &tcd = cycleList[ti->attrs.cycleRange - 1];
230 
231 		TileID2Bank(tcd.cycleList[tcd.currentState],
232 		            tileBank,
233 		            tileNum);
234 
235 		if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
236 		ti = tbh->tile(tileNum);
237 	}
238 
239 	return ti;
240 }
241 
242 //-----------------------------------------------------------------------
243 //	Return the address of a tile's TileInfo structure and the address of
244 //	the tile's image data given that tile's ID
245 
tileAddress(TileID id,uint8 ** imageData)246 TileInfo *TileInfo::tileAddress(TileID id, uint8 **imageData) {
247 	TileInfo        *ti;
248 	TileBankPtr     tbh;
249 	byte            *tibh;
250 	int16           tileBank,
251 	                tileNum;
252 
253 	if (id == 0) return nullptr;
254 
255 	TileID2Bank(id, tileBank, tileNum);
256 	debugC(3, kDebugTiles, "TileID2Bank: id = %d, tileBank = %d, tileNum = %d", id, tileBank, tileNum);
257 	if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
258 	ti = tbh->tile(tileNum);
259 
260 	if (ti->attrs.cycleRange > 0) {
261 		TileCycleData   &tcd = cycleList[ti->attrs.cycleRange - 1];
262 
263 		TileID2Bank(tcd.cycleList[tcd.currentState],
264 		            tileBank,
265 		            tileNum);
266 
267 		if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
268 		ti = tbh->tile(tileNum);
269 	}
270 
271 	if (ti != nullptr) {
272 		if ((tibh = (*g_vm->_tileImageBanks)[tileBank]) != nullptr)
273 			*imageData = &tibh[ti->offset];
274 		else
275 			*imageData = nullptr;
276 	} else
277 		*imageData = nullptr;
278 
279 	return ti;
280 }
281 
282 /* ===================================================================== *
283    ActiveItem member functions
284  * ===================================================================== */
285 
286 //-----------------------------------------------------------------------
287 //	Return the map number of this active item
288 
getMapNum(void)289 int16 ActiveItem::getMapNum(void) {
290 	int16   mapNum;
291 
292 	//  Use a brute force search of all of the maps' active item lists
293 	//  to determine which map this active item is on.
294 	for (mapNum = 0; mapNum < worldCount; mapNum++) {
295 		WorldMapData    *mapData = &mapList[mapNum];
296 
297 		//  Determine if the active item in on this map's list
298 		if (_parent == mapData->activeItemList)
299 			break;
300 	}
301 
302 	return mapNum;
303 }
304 
305 //-----------------------------------------------------------------------
306 //	Return the world context for a TAG
307 
getInstanceContext(void)308 ObjectID ActiveItem::getInstanceContext(void) {
309 	int16 mn = getMapNum();
310 	assert(mn >= 0 && mn < 3);
311 	if (mn < 0 || mn > 2)
312 		return Nothing;
313 	WorldMapData &map = mapList[mn];        //  master map data array
314 	return map.worldID;
315 }
316 
317 //-----------------------------------------------------------------------
318 //	Return the Location for a TAG
319 
getInstanceLocation(void)320 Location ActiveItem::getInstanceLocation(void) {
321 	return Location(_data.instance.u  << kTileUVShift,
322 	                _data.instance.v  << kTileUVShift,
323 	                _data.instance.h  << kTileZShift,
324 	                getInstanceContext());
325 }
326 
327 
328 //-----------------------------------------------------------------------
329 //	Return the address of an active item, given its ID
330 
activeItemAddress(ActiveItemID id)331 ActiveItem *ActiveItem::activeItemAddress(ActiveItemID id) {
332 	return  id.getIndexNum() != activeItemIndexNullID
333 	        ?   mapList[id.getMapNum()].activeItemList->_items[id.getIndexNum()]
334 	        :   nullptr;
335 }
336 
337 //-----------------------------------------------------------------------
338 //	Return this active item's ID
339 
thisID(void)340 ActiveItemID ActiveItem::thisID(void) {
341 	int16   mapNum = getMapNum();
342 
343 	return  ActiveItemID(mapNum, _index);
344 }
345 
346 //-----------------------------------------------------------------------
347 //	Return this active item's ID
348 
thisID(int16 mapNum)349 ActiveItemID ActiveItem::thisID(int16 mapNum) {
350 	return ActiveItemID(mapNum, _index);
351 }
352 
353 //-----------------------------------------------------------------------
354 //	use() function for ActiveItem instance
355 
use(ObjectID enactor)356 bool ActiveItem::use(ObjectID enactor) {
357 	//  Get a pointer to the active item group
358 	ActiveItem  *groupPtr = activeItemAddress(
359 	                            ActiveItemID(
360 	                                getMapNum(),
361 	                                _data.instance.groupID));
362 
363 	return groupPtr->use(this, enactor);
364 }
365 
366 //-----------------------------------------------------------------------
367 //	trigger() function for ActiveItem instance
368 
trigger(ObjectID enactor,ObjectID objID)369 bool ActiveItem::trigger(ObjectID enactor, ObjectID objID) {
370 	//  Get a pointer to the active item group
371 	ActiveItem  *groupPtr = activeItemAddress(
372 	                            ActiveItemID(
373 	                                getMapNum(),
374 	                                _data.instance.groupID));
375 
376 	return groupPtr->trigger(this, enactor, objID);
377 }
378 
379 //-----------------------------------------------------------------------
380 //	release() function for ActiveItem instance
381 
release(ObjectID enactor,ObjectID objID)382 bool ActiveItem::release(ObjectID enactor, ObjectID objID) {
383 	//  Get a pointer to the active item group
384 	ActiveItem  *groupPtr = activeItemAddress(
385 	                            ActiveItemID(
386 	                                getMapNum(),
387 	                                _data.instance.groupID));
388 
389 	return groupPtr->release(this, enactor, objID);
390 }
391 
392 //-----------------------------------------------------------------------
393 //	acceptLockToggle() function for ActiveItem instance
394 
acceptLockToggle(ObjectID enactor,uint8 keyCode)395 bool ActiveItem::acceptLockToggle(ObjectID enactor, uint8 keyCode) {
396 	//  Get a pointer to the active item group
397 	ActiveItem  *groupPtr = activeItemAddress(
398 	                            ActiveItemID(
399 	                                getMapNum(),
400 	                                _data.instance.groupID));
401 
402 	return groupPtr->acceptLockToggle(this, enactor, keyCode);
403 }
404 
405 //-----------------------------------------------------------------------
406 //	inRange() function for ActiveItem instance
407 
inRange(const TilePoint & loc,int16 range)408 bool ActiveItem::inRange(const TilePoint &loc, int16 range) {
409 	//  Get a pointer to the active item group
410 	ActiveItem  *groupPtr = activeItemAddress(
411 	                            ActiveItemID(
412 	                                getMapNum(),
413 	                                _data.instance.groupID));
414 
415 	return      loc.u >= _data.instance.u - range
416 	            &&  loc.v >= _data.instance.v - range
417 	            &&  loc.u <  _data.instance.u + groupPtr->_data.group.uSize + range
418 	            &&  loc.v <  _data.instance.v + groupPtr->_data.group.vSize + range;
419 }
420 
421 //-----------------------------------------------------------------------
422 //	TAG noise player
423 
playTAGNoise(ActiveItem * ai,int16 tagNoiseID)424 void ActiveItem::playTAGNoise(ActiveItem *ai, int16 tagNoiseID) {
425 	playSoundAt(MKTAG('T', 'A', 'G', tagNoiseID), ai->getInstanceLocation());
426 }
427 
428 //-----------------------------------------------------------------------
429 //	use() function for ActiveItem group
430 
use(ActiveItem * ins,ObjectID enactor)431 bool ActiveItem::use(ActiveItem *ins, ObjectID enactor) {
432 	int16       mapNum = getMapNum();
433 	uint16      state = ins->getInstanceState(mapNum);
434 	scriptCallFrame scf;
435 
436 	if (ins->_data.scriptClassID != 0) {
437 		//  Set up the arguments we want to pass to the script
438 
439 		scf.invokedTAI      = ins->thisID();
440 		scf.enactor         = enactor;
441 		scf.directTAI       = scf.invokedTAI;
442 		scf.indirectObject  = Nothing;
443 
444 		//  Fill in other params with data from TAG struct
445 		scf.value           = ins->_data.instance.worldNum;
446 		scf.coords.u        = ins->_data.instance.targetU;
447 		scf.coords.v        = ins->_data.instance.targetV;
448 		scf.coords.z        = ins->_data.instance.targetZ;
449 
450 		if (runTagMethod(
451 		            scf.invokedTAI,
452 		            Method_TileActivityInstance_onUse,
453 		            scf)
454 		        ==  scriptResultFinished) {
455 			if (scf.returnVal != actionResultNotDone)
456 				return scf.returnVal == actionResultSuccess;
457 		}
458 	}
459 
460 	switch (ins->builtInBehavior()) {
461 
462 	case builtInLamp:
463 		ins->setInstanceState(mapNum, !state);
464 		break;
465 
466 	case builtInDoor:
467 		if (state < 3) {
468 			if (!ins->isLocked()) {
469 				TileActivityTask::openDoor(*ins);
470 				playTAGNoise(ins, DEFAULT_OPEN);
471 			} else {
472 				playTAGNoise(ins, DOOR_LOCKED_NO_KEY);
473 				return false;
474 			}
475 		} else {
476 			TileActivityTask::closeDoor(*ins);
477 			playTAGNoise(ins, DEFAULT_CLOSE);
478 		}
479 		break;
480 
481 	}
482 
483 	return true;
484 }
485 
486 //-----------------------------------------------------------------------
487 //	trigger() function for ActiveItem group
488 
trigger(ActiveItem * ins,ObjectID enactor,ObjectID objID)489 bool ActiveItem::trigger(ActiveItem *ins, ObjectID enactor, ObjectID objID) {
490 	assert(objID != Nothing);
491 
492 	GameObject      *obj = GameObject::objectAddress(objID);
493 	GameWorld       *world = (GameWorld *)GameObject::objectAddress(
494 	                             mapList[getMapNum()].worldID);
495 	TileRegion      instanceRegion;
496 	ActiveItemID    instanceID = ins->thisID();
497 	scriptCallFrame scf;
498 
499 	//  Trap transporters to only react to the center actor
500 	if (ins->builtInBehavior() == builtInTransporter
501 	        && (!isActor(obj) || (Actor *)obj != getCenterActor()))
502 		return true;
503 
504 	if (ins->_data.scriptClassID != 0) {
505 		//  Set up the arguments we want to pass to the script
506 
507 		scf.invokedTAI      = ins->thisID();
508 		scf.enactor         = enactor;
509 		scf.directTAI       = scf.invokedTAI;
510 		scf.indirectObject  = objID;
511 
512 		if (runTagMethod(
513 		            scf.invokedTAI,
514 		            Method_TileActivityInstance_onCanTrigger,
515 		            scf)
516 		        ==  scriptResultFinished) {
517 			if (!scf.returnVal) return true;
518 		}
519 	}
520 
521 	//  Mark the object as triggering this TAG
522 	obj->setTriggeringTAG(true);
523 
524 	instanceRegion.min.u = ins->_data.instance.u << kTileUVShift;
525 	instanceRegion.min.v = ins->_data.instance.v << kTileUVShift;
526 	instanceRegion.max.u =      instanceRegion.min.u
527 	                            + (_data.group.uSize << kTileUVShift);
528 	instanceRegion.max.v =      instanceRegion.min.v
529 	                            + (_data.group.vSize << kTileUVShift);
530 
531 	RegionalObjectIterator  iter(
532 	    world,
533 	    instanceRegion.min,
534 	    instanceRegion.max);
535 	GameObject *testObject = nullptr;
536 
537 	for (iter.first(&testObject);
538 	        testObject != nullptr;
539 	        iter.next(&testObject)) {
540 		if (testObject != obj
541 		        &&  testObject->_data.currentTAG == instanceID
542 		        &&  testObject->isTriggeringTAG())
543 			return true;
544 	}
545 
546 //	if ( proto->mass < group.triggerWeight ) return false;
547 
548 	if (ins->_data.scriptClassID != 0) {
549 		//  Set up the arguments we want to pass to the script
550 
551 		scf.invokedTAI      = ins->thisID();
552 		scf.enactor         = enactor;
553 		scf.directTAI       = scf.invokedTAI;
554 		scf.indirectObject  = objID;
555 
556 		//  Fill in other params with data from TAG struct
557 		scf.value           = ins->_data.instance.worldNum;
558 		scf.coords.u        = ins->_data.instance.targetU;
559 		scf.coords.v        = ins->_data.instance.targetV;
560 		scf.coords.z        = ins->_data.instance.targetZ;
561 
562 		if (runTagMethod(
563 		            scf.invokedTAI,
564 		            Method_TileActivityInstance_onTrigger,
565 		            scf)
566 		        ==  scriptResultFinished) {
567 			if (scf.returnVal != actionResultNotDone)
568 				return scf.returnVal == actionResultSuccess;
569 		}
570 	}
571 
572 	switch (ins->builtInBehavior()) {
573 
574 	case builtInTransporter:
575 		//playTAGNoise(BEAM_ME_UP);
576 	{
577 		Actor       *a;
578 
579 		if (isActor(obj) && (a = (Actor *)obj) == getCenterActor()) {
580 			transportCenterBand(
581 			    Location(
582 			        (ins->_data.instance.targetU << kTileUVShift)
583 			        +   kTileUVSize / 2,
584 			        (ins->_data.instance.targetV << kTileUVShift)
585 			        +   kTileUVSize / 2,
586 			        (int16)ins->_data.instance.targetZ << 3,
587 			        ins->_data.instance.worldNum + WorldBaseID));
588 		}
589 	}
590 	break;
591 
592 	}
593 
594 	return true;
595 }
596 
597 //-----------------------------------------------------------------------
598 //	release() function for ActiveItem group
599 
release(ActiveItem * ins,ObjectID enactor,ObjectID objID)600 bool ActiveItem::release(ActiveItem *ins, ObjectID enactor, ObjectID objID) {
601 	assert(objID != Nothing);
602 
603 	GameObject      *obj = GameObject::objectAddress(objID);
604 	GameWorld       *world = (GameWorld *)GameObject::objectAddress(
605 	                             mapList[getMapNum()].worldID);
606 	TileRegion      instanceRegion;
607 	ActiveItemID    instanceID = ins->thisID();
608 	scriptCallFrame scf;
609 
610 	if (obj->isTriggeringTAG()) obj->setTriggeringTAG(false);
611 
612 	instanceRegion.min.u = ins->_data.instance.u << kTileUVShift;
613 	instanceRegion.min.v = ins->_data.instance.v << kTileUVShift;
614 	instanceRegion.max.u =      instanceRegion.min.u
615 	                            + (_data.group.uSize << kTileUVShift);
616 	instanceRegion.max.v =      instanceRegion.min.v
617 	                            + (_data.group.vSize << kTileUVShift);
618 
619 	RegionalObjectIterator  iter(
620 	    world,
621 	    instanceRegion.min,
622 	    instanceRegion.max);
623 	GameObject *testObject = nullptr;
624 
625 	for (iter.first(&testObject);
626 	        testObject != nullptr;
627 	        iter.next(&testObject)) {
628 		if (testObject != obj
629 		        &&  testObject->_data.currentTAG == instanceID
630 		        &&  testObject->isTriggeringTAG())
631 			return true;
632 	}
633 
634 	if (ins->_data.scriptClassID != 0) {
635 		//  Set up the arguments we want to pass to the script
636 
637 		scf.invokedTAI      = ins->thisID();
638 		scf.enactor         = enactor;
639 		scf.directTAI       = scf.invokedTAI;
640 		scf.indirectObject  = objID;
641 
642 		//  Fill in other params with data from TAG struct
643 		scf.value           = ins->_data.instance.worldNum;
644 		scf.coords.u        = ins->_data.instance.targetU;
645 		scf.coords.v        = ins->_data.instance.targetV;
646 		scf.coords.z        = ins->_data.instance.targetZ;
647 
648 		if (runTagMethod(
649 		            scf.invokedTAI,
650 		            Method_TileActivityInstance_onRelease,
651 		            scf)
652 		        ==  scriptResultFinished) {
653 			if (scf.returnVal != actionResultNotDone)
654 				return scf.returnVal == actionResultSuccess;
655 		}
656 	}
657 
658 	return true;
659 }
660 
661 //-----------------------------------------------------------------------
662 //	acceptLockToggle() function for ActiveItem group
663 
acceptLockToggle(ActiveItem * ins,ObjectID enactor,uint8 keyCode)664 bool ActiveItem::acceptLockToggle(ActiveItem *ins, ObjectID enactor, uint8 keyCode) {
665 	scriptCallFrame scf;
666 
667 	if (ins->_data.scriptClassID != 0) {
668 		//  Set up the arguments we want to pass to the script
669 
670 		scf.invokedTAI      = ins->thisID();
671 		scf.enactor         = enactor;
672 		scf.directTAI       = scf.invokedTAI;
673 		scf.indirectObject  = Nothing;
674 
675 		//  Fill in other params with data from TAG struct
676 		scf.value           = keyCode;
677 
678 		if (runTagMethod(
679 		            scf.invokedTAI,
680 		            Method_TileActivityInstance_onAcceptLockToggle,
681 		            scf)
682 		        ==  scriptResultFinished) {
683 			if (scf.returnVal != actionResultNotDone)
684 				return scf.returnVal == actionResultSuccess;
685 		}
686 	}
687 
688 	switch (ins->builtInBehavior()) {
689 
690 	case builtInDoor:
691 		if (keyCode == ins->lockType()) {
692 			playTAGNoise(ins, UNLOCK_RIGHT_KEY);
693 			if (ins->isLocked())
694 				ins->setLocked(false);
695 			else {
696 				if (ins->getInstanceState(getMapNum()) == 0)
697 					ins->setLocked(true);
698 				else
699 					return false;
700 			}
701 		} else {
702 			playTAGNoise(ins, UNLOCK_WRONG_KEY);
703 			return false;
704 		}
705 		break;
706 	}
707 
708 	return true;
709 }
710 
711 //-----------------------------------------------------------------------
712 
getClosestPointOnTAI(ActiveItem * TAI,GameObject * obj)713 TilePoint getClosestPointOnTAI(ActiveItem *TAI, GameObject *obj) {
714 	assert(TAI->_data.itemType == activeTypeInstance);
715 
716 	TilePoint       objLoc = obj->getLocation(),
717 	                TAILoc;
718 	TileRegion      TAIReg;
719 	ActiveItem      *TAG = TAI->getGroup();
720 
721 	//  Compute in points the region of the TAI
722 	TAIReg.min.u = TAI->_data.instance.u << kTileUVShift;
723 	TAIReg.min.v = TAI->_data.instance.v << kTileUVShift;
724 	TAIReg.max.u =      TAIReg.min.u
725 	                    + (TAG->_data.group.uSize << kTileUVShift);
726 	TAIReg.max.v =      TAIReg.min.v
727 	                    + (TAG->_data.group.vSize << kTileUVShift);
728 	TAIReg.min.z = TAIReg.max.z = 0;
729 
730 	//  Find the point on the TAI closest to the object
731 	TAILoc.u = clamp(TAIReg.min.u - 1, objLoc.u, TAIReg.max.u);
732 	TAILoc.v = clamp(TAIReg.min.v - 1, objLoc.v, TAIReg.max.v);
733 	TAILoc.z = TAI->_data.instance.h + obj->proto()->height / 2;
734 
735 	return TAILoc;
736 }
737 
738 /* ===================================================================== *
739    ActiveItem instance state management functions
740  * ===================================================================== */
741 
742 //-----------------------------------------------------------------------
743 //	Initialize the active item state arrays
744 
initActiveItemStates(void)745 void initActiveItemStates(void) {
746 	int16       i;
747 
748 	stateArray = new byte *[worldCount]();
749 
750 	if (stateArray == nullptr)
751 		error("Unable to allocate the active item state array array");
752 
753 	for (i = 0; i < worldCount; i++) {
754 		stateArray[i] = (byte *)LoadResource(tileRes, tagStateID + i,
755 		                             "active item state array");
756 
757 		if (stateArray[i] == nullptr)
758 			error("Unable to load active item state array");
759 	}
760 }
761 
saveActiveItemStates(Common::OutSaveFile * outS)762 void saveActiveItemStates(Common::OutSaveFile *outS) {
763 	debugC(2, kDebugSaveload, "Saving ActiveItemStates");
764 
765 	outS->write("TAGS", 4);
766 	CHUNK_BEGIN;
767 	for (int i = 0; i < worldCount; i++) {
768 		debugC(3, kDebugSaveload, "Saving ActiveItemState %d", i);
769 
770 		if (stateArray[i] != nullptr) {
771 			WorldMapData *mapData = &mapList[i];
772 			ActiveItemList *activeItemList = mapData->activeItemList;
773 			uint8 *bufferedStateArray;
774 			int16 activeItemCount = mapData->activeCount;
775 			int32 arraySize = tileRes->size(tagStateID + i);
776 
777 			//  Save the size of the state array
778 			out->writeSint16LE(arraySize);
779 
780 			bufferedStateArray = new uint8[arraySize];
781 			memcpy(bufferedStateArray, stateArray[i], arraySize);
782 
783 			debugC(4, kDebugSaveload, "... arraySize = %d", arraySize);
784 
785 			for (int j = 0; j < activeItemCount; j++) {
786 				ActiveItem *activeItem = activeItemList->_items[j];
787 				uint8 *statePtr;
788 
789 				if (activeItem->_data.itemType != activeTypeInstance)
790 					continue;
791 
792 				//  Get a pointer to the current active item's state
793 				//  data in the archive buffer
794 				statePtr = &bufferedStateArray[activeItem->_data.instance.stateIndex];
795 
796 				//  Set the high bit of the state value based upon the
797 				//  active item's locked state
798 				if (activeItem->isLocked())
799 					*statePtr |= (1 << 7);
800 				else
801 					*statePtr &= ~(1 << 7);
802 			}
803 
804 			//  Copy the state data to the archive buffer
805 			out->write(bufferedStateArray, arraySize);
806 
807 			delete[] bufferedStateArray;
808 		} else
809 			out->writeSint16LE(0);
810 	}
811 	CHUNK_END;
812 }
813 
loadActiveItemStates(Common::InSaveFile * in)814 void loadActiveItemStates(Common::InSaveFile *in) {
815 	debugC(2, kDebugSaveload, "Loading ActiveItemStates");
816 
817 	stateArray = new byte *[worldCount]();
818 
819 	if (stateArray == nullptr)
820 		error("Unable to allocate the active item state array array");
821 
822 	for (int i = 0; i < worldCount; i++) {
823 		debugC(3, kDebugSaveload, "Loading ActiveItemState %d", i);
824 
825 		int32 arraySize;
826 
827 		arraySize = in->readSint16LE();
828 
829 		debugC(4, kDebugSaveload, "... arraySize = %d", arraySize);
830 
831 		stateArray[i] = (byte *)malloc(arraySize);
832 		in->read(stateArray[i], arraySize);
833 
834 		if (arraySize > 0) {
835 			WorldMapData *mapData = &mapList[i];
836 			ActiveItemList *activeItemList = mapData->activeItemList;
837 			int16 activeItemCount = mapData->activeCount;
838 
839 			for (int j = 0; j < activeItemCount; j++) {
840 				ActiveItem      *activeItem = activeItemList->_items[j];
841 				uint8           *statePtr;
842 
843 				if (activeItem->_data.itemType != activeTypeInstance)
844 					continue;
845 
846 				//  Get a pointer to the current active item's state
847 				//  data in the archive buffer
848 				statePtr = &stateArray[i][activeItem->_data.instance.stateIndex];
849 
850 				//  Reset the locked state of the active item based
851 				//  upon the high bit of the buffered state value
852 				activeItem->setLocked((*statePtr & (1 << 7)) != 0);
853 
854 				//  Clear the high bit of the state value
855 				*statePtr &= ~(1 << 7);
856 			}
857 		} else
858 			stateArray[i] = nullptr;
859 	}
860 }
861 
862 //-----------------------------------------------------------------------
863 //	Cleanup the active item state arrays
864 
cleanupActiveItemStates(void)865 void cleanupActiveItemStates(void) {
866 	int16       i;
867 
868 	for (i = 0; i < worldCount; i++) {
869 		if (stateArray[i] != nullptr)
870 			free(stateArray[i]);
871 	}
872 
873 	delete[] stateArray;
874 }
875 
876 /* ===================================================================== *
877    TileActivityTaskList member functions
878  * ===================================================================== */
879 
880 //-----------------------------------------------------------------------
881 //	Constructor
882 
TileActivityTaskList(void)883 TileActivityTaskList::TileActivityTaskList(void) {
884 }
885 
886 //-----------------------------------------------------------------------
887 //	Reconstruct the TileActivityTaskList from an archive buffer
888 
TileActivityTaskList(Common::SeekableReadStream * stream)889 TileActivityTaskList::TileActivityTaskList(Common::SeekableReadStream *stream) {
890 	read(stream);
891 }
892 
893 //-----------------------------------------------------------------------
894 //	Return the number of bytes needed to archive this
895 //	TileActivityTaskList
896 
read(Common::InSaveFile * in)897 void TileActivityTaskList::read(Common::InSaveFile *in) {
898 	int16 taskCount;
899 
900 	//  Retreive the task count
901 	taskCount = in->readSint16LE();
902 	debugC(3, kDebugSaveload, "... taskCount = %d", taskCount);
903 
904 	for (int i = 0; i < taskCount; i++) {
905 		ActiveItem  *tai;
906 		uint8       activityType;
907 
908 		int16 val = in->readSint16LE();
909 		tai = ActiveItem::activeItemAddress(ActiveItemID(val));
910 		debugC(4, kDebugSaveload, "...... activeItemID = %d", val);
911 
912 		activityType = in->readByte();
913 		debugC(4, kDebugSaveload, "...... activityType = %d", activityType);
914 
915 		if (tai != nullptr) {
916 			TileActivityTask *tat;
917 
918 			tat = newTask(tai);
919 			if (tat != nullptr)
920 				tat->activityType = activityType;
921 		}
922 	}
923 }
924 
write(Common::MemoryWriteStreamDynamic * out)925 void TileActivityTaskList::write(Common::MemoryWriteStreamDynamic *out) {
926 	int16 taskCount = _list.size();
927 
928 	//  Store the task count
929 	out->writeSint16LE(taskCount);
930 	debugC(3, kDebugSaveload, "... taskCount = %d", taskCount);
931 
932 	for (Common::List<TileActivityTask *>::iterator it = _list.begin(); it != _list.end(); ++it) {
933 		ActiveItem  *ai = (*it)->tai;
934 
935 		//  Store the activeItemID
936 		out->writeSint16LE(ai->thisID().val);
937 		debugC(4, kDebugSaveload, "...... activeItemID = %d", ai->thisID().val);
938 
939 		//  Store the task type
940 		out->writeByte((*it)->activityType);
941 		debugC(4, kDebugSaveload, "...... activityType = %d", (*it)->activityType);
942 	}
943 }
944 
945 //-----------------------------------------------------------------------
946 //	Cleanup
947 
cleanup(void)948 void TileActivityTaskList::cleanup(void) {
949 	for (Common::List<TileActivityTask *>::iterator it = _list.begin(); it != _list.end(); ++it)
950 		delete *it;
951 
952 	_list.clear();
953 }
954 
955 //-----------------------------------------------------------------------
956 //	Get a new tile activity task, if there is one available,
957 //	and initialize it.
958 
newTask(ActiveItem * activeInstance)959 TileActivityTask *TileActivityTaskList::newTask(ActiveItem *activeInstance) {
960 	TileActivityTask *tat = nullptr;
961 
962 	//  Check see if there's already tile activity task associated with
963 	//  this instance.
964 	for (Common::List<TileActivityTask *>::iterator it = _list.begin(); it != _list.end(); ++it)
965 		if ((*it)->tai == activeInstance) {
966 			tat = *it;
967 			break;
968 		}
969 
970 	if (tat)
971 		debugC(3, kDebugTasks, "Found old TAT");
972 
973 	if (tat == nullptr) {
974 		debugC(3, kDebugTasks, "Making new TAT");
975 
976 		tat = new TileActivityTask;
977 
978 		tat->tai = activeInstance;
979 		tat->activityType = TileActivityTask::activityTypeNone;
980 		tat->script = NoThread;
981 		tat->targetState = 0;
982 
983 		_list.push_back(tat);
984 	}
985 
986 	//  If we re-used an old task struct, then make sure script gets woken up.
987 	if (tat->script != NoThread) {
988 		debugC(3, kDebugTasks, "Waking up thread TAT");
989 
990 		wakeUpThread(tat->script);
991 		tat->script = NoThread;
992 	}
993 
994 	return tat;
995 }
996 
997 /* ===================================================================== *
998    TileActivityTask member functions
999  * ===================================================================== */
1000 
1001 //-----------------------------------------------------------------------
1002 //	When a tile activity task is finished, call this function to delete it.
1003 
remove(void)1004 void TileActivityTask::remove(void) {
1005 	debugC(3, kDebugTasks, "Removing TAT");
1006 
1007 	g_vm->_aTaskList->_list.remove(this);
1008 }
1009 
1010 //-----------------------------------------------------------------------
1011 //	This initiates a tile activity task for opening a door
1012 
openDoor(ActiveItem & activeInstance)1013 void TileActivityTask::openDoor(ActiveItem &activeInstance) {
1014 	debugC(3, kDebugTasks, "TAT Open Door");
1015 
1016 	TileActivityTask *tat;
1017 	if ((tat = g_vm->_aTaskList->newTask(&activeInstance)) != nullptr)
1018 		tat->activityType = activityTypeOpen;
1019 }
1020 
1021 //-----------------------------------------------------------------------
1022 //	This initiates a tile activity task for closing a door
1023 
closeDoor(ActiveItem & activeInstance)1024 void TileActivityTask::closeDoor(ActiveItem &activeInstance) {
1025 	debugC(3, kDebugTasks, "TAT Close Door");
1026 
1027 	TileActivityTask *tat;
1028 	if ((tat = g_vm->_aTaskList->newTask(&activeInstance)) != nullptr)
1029 		tat->activityType = activityTypeClose;
1030 }
1031 
1032 //-----------------------------------------------------------------------
1033 //	This initiates a tile activity task for script-based activity
1034 
doScript(ActiveItem & activeInstance,uint8 finalState,ThreadID scr)1035 void TileActivityTask::doScript(ActiveItem &activeInstance, uint8 finalState, ThreadID scr) {
1036 	debugC(3, kDebugTasks, "TAT Do Script");
1037 
1038 	TileActivityTask *tat;
1039 	if ((tat = g_vm->_aTaskList->newTask(&activeInstance)) != nullptr) {
1040 		if (scr)
1041 			debugC(3, kDebugTasks, "TAT Assign Script!");
1042 
1043 		tat->activityType = activityTypeScript;
1044 		tat->targetState = finalState;
1045 		tat->script = scr;
1046 	} else {
1047 		debugC(3, kDebugTasks, "Waking up thread 'cause newTask Failed");
1048 
1049 		wakeUpThread(scr);           //  If there were no threads available
1050 	}
1051 }
1052 
1053 //-----------------------------------------------------------------------
1054 //	Routine to update positions of all active terrain using TileActivityTasks
1055 
updateActiveItems(void)1056 void TileActivityTask::updateActiveItems(void) {
1057 	int count = 0, scriptCount = 0;
1058 
1059 	for (Common::List<TileActivityTask *>::iterator it = g_vm->_aTaskList->_list.begin(); it != g_vm->_aTaskList->_list.end();) {
1060 		TileActivityTask *tat = *it;
1061 		ActiveItem *activityInstance = tat->tai;
1062 		bool activityTaskDone = false;
1063 
1064 		int16       mapNum = activityInstance->getMapNum();
1065 		uint16      state = activityInstance->getInstanceState(mapNum);
1066 
1067 		// collecting stats
1068 		count++;
1069 		if (tat->script != NoThread)
1070 			scriptCount++;
1071 
1072 		switch (tat->activityType) {
1073 
1074 		case activityTypeOpen:
1075 			if (state < 3)
1076 				activityInstance->setInstanceState(mapNum, state + 1);
1077 			else
1078 				activityTaskDone = true;
1079 			break;
1080 
1081 		case activityTypeClose:
1082 			if (state > 0)
1083 				activityInstance->setInstanceState(mapNum, state - 1);
1084 			else
1085 				activityTaskDone = true;
1086 			break;
1087 
1088 		case activityTypeScript:
1089 			if (state > tat->targetState)
1090 				activityInstance->setInstanceState(mapNum, state - 1);
1091 			else if (state < tat->targetState)
1092 				activityInstance->setInstanceState(mapNum, state + 1);
1093 			else
1094 				activityTaskDone = true;
1095 			break;
1096 
1097 		default:
1098 			activityTaskDone = true;
1099 			break;
1100 		}
1101 
1102 		++it; // Go to next task before potentially removing it
1103 
1104 		if (activityTaskDone) {
1105 			//  Wake up the script...
1106 			if (tat->script != NoThread) {
1107 				debugC(3, kDebugTasks, "TAT Wake Up Thread");
1108 
1109 				wakeUpThread(tat->script);
1110 			}
1111 			tat->remove();
1112 		}
1113 	}
1114 
1115 	debugC(3, kDebugTasks, "TileTasks: %d SW:%d", count, scriptCount);
1116 }
1117 
1118 //-----------------------------------------------------------------------
1119 //	Search for tile activity task matching an item
1120 
find(ActiveItem * tai)1121 TileActivityTask *TileActivityTask::find(ActiveItem *tai) {
1122 	for (Common::List<TileActivityTask *>::iterator it = g_vm->_aTaskList->_list.begin(); it != g_vm->_aTaskList->_list.end(); ++it) {
1123 		if (tai == (*it)->tai)
1124 			return *it;
1125 	}
1126 
1127 	return nullptr;
1128 }
1129 
1130 //-----------------------------------------------------------------------
1131 //	Add script to tile activity task...
1132 
setWait(ActiveItem * tai,ThreadID script)1133 bool TileActivityTask::setWait(ActiveItem *tai, ThreadID script) {
1134 	TileActivityTask *tat = find(tai);
1135 
1136 	debugC(3, kDebugTasks, "Set Wait TAT\n");
1137 
1138 	if (tat) {
1139 		if (tat->script != NoThread) {
1140 			debugC(3, kDebugTasks, "TAT Waking Up Thread\n");
1141 
1142 			wakeUpThread(tat->script);
1143 		}
1144 		tat->script = script;
1145 
1146 		return true;
1147 	}
1148 
1149 	debugC(3, kDebugTasks, "SetWait failed\n");
1150 
1151 	return false;
1152 }
1153 
1154 //-----------------------------------------------------------------------
1155 //	Calls the handling routine for each tile activity task
1156 
moveActiveTerrain(int32 deltaTime)1157 void moveActiveTerrain(int32 deltaTime) {
1158 	TileActivityTask::updateActiveItems();
1159 }
1160 
1161 //-----------------------------------------------------------------------
1162 //	Initialize the tile activity task list
1163 
initTileTasks(void)1164 void initTileTasks(void) {
1165 }
1166 
saveTileTasks(Common::OutSaveFile * outS)1167 void saveTileTasks(Common::OutSaveFile *outS) {
1168 	debugC(2, kDebugSaveload, "Saving TileActivityTasks");
1169 
1170 	outS->write("TACT", 4);
1171 	CHUNK_BEGIN;
1172 	g_vm->_aTaskList->write(out);
1173 	CHUNK_END;
1174 }
1175 
loadTileTasks(Common::InSaveFile * in,int32 chunkSize)1176 void loadTileTasks(Common::InSaveFile *in, int32 chunkSize) {
1177 	debugC(2, kDebugSaveload, "Loading TileActivityTasks");
1178 
1179 	//  If there is no saved data, simply call the default constructor
1180 	if (chunkSize == 0)
1181 		return;
1182 
1183 	//  Reconstruct aTaskList from archived data
1184 	g_vm->_aTaskList->read(in);
1185 }
1186 
1187 
1188 //-----------------------------------------------------------------------
1189 //	Cleanup the tile activity task list
1190 
cleanupTileTasks(void)1191 void cleanupTileTasks(void) {
1192 	//  Simply call the aTaskList's cleanup
1193 	g_vm->_aTaskList->cleanup();
1194 }
1195 
1196 /* ===================================================================== *
1197    Map management functions
1198  * ===================================================================== */
1199 
1200 //-----------------------------------------------------------------------
1201 //	Initialize map data
1202 
TileBank(Common::SeekableReadStream * stream)1203 TileBank::TileBank(Common::SeekableReadStream *stream) {
1204 	_numTiles = stream->readUint32LE();
1205 	_tileArray = new TileInfo[_numTiles];
1206 
1207 	for (uint i = 0; i < _numTiles; ++i) {
1208 		_tileArray[i].offset = stream->readUint32LE();
1209 		TileAttrs *att = &_tileArray[i].attrs;
1210 		att->terrainHeight = stream->readByte();
1211 		att->height = stream->readByte();
1212 		att->terrainMask = stream->readUint16LE();
1213 		att->fgdTerrain = stream->readByte();
1214 		att->bgdTerrain = stream->readByte();
1215 		stream->read(att->reserved0, 8);
1216 		att->maskRule = stream->readByte();
1217 		att->altMask = stream->readByte();
1218 		stream->read(att->cornerHeight, 4);
1219 		att->cycleRange = stream->readByte();
1220 		att->tileFlags = stream->readByte();
1221 		att->reserved1 = stream->readUint16LE();
1222 	}
1223 }
1224 
~TileBank()1225 TileBank::~TileBank() {
1226 	delete[] _tileArray;
1227 }
1228 
MapHeader(Common::SeekableReadStream * stream)1229 MapHeader::MapHeader(Common::SeekableReadStream *stream) {
1230 	size = stream->readSint16LE();
1231 	edgeType = stream->readSint16LE();
1232 	mapData = new uint16[size * size];
1233 
1234 	for (int i = 0; i < size * size; ++i)
1235 		mapData[i] = stream->readUint16LE();
1236 }
1237 
~MapHeader()1238 MapHeader::~MapHeader() {
1239 	if (mapData)
1240 		delete[] mapData;
1241 }
1242 
MetaTile(MetaTileList * parent,int ind,Common::SeekableReadStream * stream)1243 MetaTile::MetaTile(MetaTileList *parent, int ind, Common::SeekableReadStream *stream) {
1244 	_parent = parent;
1245 	_index = ind;
1246 	_highestPixel = stream->readUint16LE();
1247 	_banksNeeded._b[0] = stream->readUint32LE();
1248 	_banksNeeded._b[1] = stream->readUint32LE();
1249 
1250 	for (int i = 0; i < maxPlatforms; ++i)
1251 		_stack[i] = stream->readUint16LE();
1252 
1253 	_properties = stream->readUint32LE();
1254 }
1255 
MetaTileList(int count,Common::SeekableReadStream * stream)1256 MetaTileList::MetaTileList(int count, Common::SeekableReadStream *stream) {
1257 	_count = count;
1258 	_tiles = (MetaTile **)malloc(_count * sizeof(MetaTile *));
1259 	for (int i = 0; i < _count; ++i) {
1260 		_tiles[i] = new MetaTile(this, i, stream);
1261 	}
1262 }
1263 
~MetaTileList()1264 MetaTileList::~MetaTileList() {
1265 	if (_tiles) {
1266 		for (int i = 0; i < _count; ++i) {
1267 			if (_tiles[i])
1268 				delete _tiles[i];
1269 		}
1270 
1271 		free(_tiles);
1272 	}
1273 }
1274 
ActiveItem(ActiveItemList * parent,int ind,Common::SeekableReadStream * stream)1275 ActiveItem::ActiveItem(ActiveItemList *parent, int ind, Common::SeekableReadStream *stream) {
1276 	_parent = parent;
1277 	_index = ind;
1278 	_nextHash = nullptr;
1279 	stream->readUint32LE();
1280 	_data.nextHashDummy = 0;
1281 	_data.scriptClassID = stream->readUint16LE();
1282 	_data.associationOffset = stream->readUint16LE();
1283 	_data.numAssociations = stream->readByte();
1284 	_data.itemType = stream->readByte();
1285 	_data.instance.groupID = stream->readUint16LE();
1286 	_data.instance.u = stream->readSint16LE();
1287 	_data.instance.v = stream->readSint16LE();
1288 	_data.instance.h = stream->readSint16LE();
1289 	_data.instance.stateIndex = stream->readUint16LE();
1290 	_data.instance.scriptFlags = stream->readUint16LE();
1291 	_data.instance.targetU = stream->readUint16LE();
1292 	_data.instance.targetV = stream->readUint16LE();
1293 	_data.instance.targetZ = stream->readByte();
1294 	_data.instance.worldNum = stream->readByte();
1295 	_data.aItem = this;
1296 }
1297 
ActiveItemList(WorldMapData * parent,int count,Common::SeekableReadStream * stream)1298 ActiveItemList::ActiveItemList(WorldMapData *parent, int count, Common::SeekableReadStream *stream) {
1299 	_parent = parent;
1300 	_count = count;
1301 	_items = (ActiveItem **)malloc(_count * sizeof(ActiveItem *));
1302 
1303 	for (int i = 0; i < _count; ++i) {
1304 		_items[i] = new ActiveItem(this, i, stream);
1305 	}
1306 }
1307 
~ActiveItemList()1308 ActiveItemList::~ActiveItemList() {
1309 	if (_items) {
1310 		for (int i = 0; i < _count; ++i) {
1311 			if (_items[i])
1312 				delete _items[i];
1313 		}
1314 
1315 		free(_items);
1316 	}
1317 }
1318 
initMaps(void)1319 void initMaps(void) {
1320 	int16       i;
1321 	Common::SeekableReadStream *stream;
1322 	const int metaTileSize = 30;
1323 	const int tileRefSize = 4;
1324 	const int assocSize = 2;
1325 	const int activeItemSize = 28;
1326 
1327 	//  Load all of the tile terrain banks
1328 	for (i = 0; i < maxBanks; i++) {
1329 		stream = loadResourceToStream(tileRes, tileTerrainID + i, "tile terrain bank");
1330 		tileBanks[i] = new TileBank(stream);
1331 		delete stream;
1332 		if (tileBanks[i]->_tileArray == nullptr) {
1333 			delete tileBanks[i];
1334 			tileBanks[i] = nullptr;
1335 		}
1336 	}
1337 
1338 	//  Count the worlds by seeking the map data
1339 	for (worldCount = 0;
1340 	        tileRes->seek(mapID + worldCount);
1341 	        worldCount++) {
1342 				warning("MapID: %s %08x res: %s %08x", tag2strP(mapID), mapID, tag2strP(mapID + worldCount), mapID + worldCount);
1343 			}
1344 
1345 	//  Allocate the map data array
1346 	mapList = new WorldMapData[worldCount]();
1347 	if (mapList == nullptr)
1348 		error("Unable to allocate map data array");
1349 
1350 	//  Iterate through the map data list initializing each element
1351 	for (i = 0; i < worldCount; i++) {
1352 		WorldMapData    *mapData = &mapList[i];
1353 		int16           j;
1354 		int iMapID = mapID + i;
1355 		int iMetaID = metaID + i;
1356 		int iTagRefID = tagDataID + i;
1357 		int iAssocID = assocID + i;
1358 		int iActiveItemID = tagID + i;
1359 
1360 		//  Initialize the world ID
1361 		mapData->worldID = WorldBaseID + i;
1362 
1363 		//  Load the map
1364 		stream = loadResourceToStream(tileRes, iMapID, "world map");
1365 		mapData->map = new MapHeader(stream);
1366 		delete stream;
1367 		if (mapData->map == nullptr)
1368 			error("Unable to load map");
1369 		debugC(2, kDebugTiles, "map: size = %d, mapData = %p", mapData->map->size, (void*)mapData->map->mapData);
1370 
1371 		int metaTileCount = tileRes->size(iMetaID) / metaTileSize;
1372 		stream = loadResourceToStream(tileRes, iMetaID, "meta tile list");
1373 		mapData->metaList = new MetaTileList(metaTileCount, stream);
1374 		delete stream;
1375 
1376 		if (mapData->metaList == nullptr || mapData->metaList->_tiles == nullptr)
1377 			error("Unable to load meta tile list");
1378 
1379 		//  If there is tag data, load it
1380 		if (tileRes->size(iTagRefID) > 0) {
1381 			int tileRefCount = tileRes->size(iTagRefID) / tileRefSize;
1382 			mapData->activeItemData = new TileRef[tileRefCount]();
1383 			if (mapData->activeItemData == nullptr)
1384 				error("Unable to load active item data");
1385 
1386 			stream = loadResourceToStream(tileRes, iTagRefID, "active item data");
1387 			for (int k = 0; k < tileRefCount; ++k) {
1388 				mapData->activeItemData[k].tile = stream->readUint16LE();
1389 				mapData->activeItemData[k].flags = stream->readByte();
1390 				mapData->activeItemData[k].tileHeight = stream->readByte();
1391 			}
1392 			delete stream;
1393 		} else
1394 			mapData->activeItemData = nullptr;
1395 
1396 		//  If there is an association list, load it
1397 		if (tileRes->size(iAssocID) > 0) {
1398 			int assocCount = tileRes->size(iAssocID) / assocSize;
1399 			mapData->assocList = new uint16[assocCount]();
1400 			if (mapData->assocList == nullptr)
1401 				error("Unable to load association list");
1402 
1403 			stream = loadResourceToStream(tileRes, iAssocID, "association list");
1404 			for (int k = 0; k < assocCount; ++k)
1405 				mapData->assocList[k] = stream->readUint16LE();
1406 		} else
1407 			mapData->assocList = nullptr;
1408 
1409 		//  If there is an active item list, load it
1410 		if (tileRes->size(iActiveItemID) > 0) {
1411 			int activeItemCount = tileRes->size(iActiveItemID) / activeItemSize;
1412 			stream = loadResourceToStream(tileRes, iActiveItemID, "active item list");
1413 			mapData->activeItemList = new ActiveItemList(mapData, activeItemCount, stream);
1414 			delete stream;
1415 
1416 			if (mapData->activeItemList == nullptr ||
1417 			    mapData->activeItemList->_items == nullptr)
1418 				error("Unable to load active item list");
1419 
1420 			mapData->activeCount = activeItemCount;
1421 
1422 		} else
1423 			mapData->activeItemList = nullptr;
1424 
1425 		//  Compute the number of meta tiles in list
1426 		mapData->metaCount     = metaTileCount;
1427 
1428 		//  Allocate an object ripping table ID list
1429 		mapData->ripTableIDList = new RipTableID[mapData->metaCount];
1430 		if (mapData->ripTableIDList == nullptr)
1431 			error("Unable to allocate rip table ID list");
1432 
1433 		//  Initialize the object ripping ID list
1434 		for (j = 0; j < mapData->metaCount; j++)
1435 			(mapData->ripTableIDList)[j] = -1;
1436 
1437 		//  Get the size of the map in meta tiles
1438 		mapData->mapSize = mapData->map->size;
1439 
1440 		//  Compute the height of the map in pixels
1441 		mapData->mapHeight = mapData->mapSize * kMetaTileHeight;
1442 
1443 		//  Build an active item instance hash table
1444 		mapData->buildInstanceHash();
1445 	}
1446 
1447 	ripTableList = new RipTable[RipTable::kRipTableSize];
1448 	for (int k = 0; k < RipTable::kRipTableSize; ++k) {
1449 		ripTableList[k].metaID = NoMetaTile;
1450 		ripTableList[k].ripID = 0;
1451 		memset(ripTableList[k].zTable, 0, sizeof(ripTableList[k].zTable));
1452 		ripTableList[k]._index = k;
1453 	}
1454 
1455 	initPlatformCache();
1456 	initMapFeatures();
1457 }
1458 
1459 //-----------------------------------------------------------------------
1460 //	Cleanup map data
1461 
cleanupMaps(void)1462 void cleanupMaps(void) {
1463 	int16       i;
1464 
1465 	termMapFeatures();
1466 
1467 	delete[] ripTableList;
1468 
1469 	delete[] platformCache;
1470 
1471 	//  Iterate through each map, dumping the data
1472 	for (i = 0; i < worldCount; i++) {
1473 		WorldMapData    *mapData = &mapList[i];
1474 
1475 		//  Dump the map
1476 		if (mapData->map != nullptr)
1477 			delete mapData->map;
1478 
1479 		//  Dump the meta tile list
1480 		if (mapData->metaList)
1481 			delete mapData->metaList;
1482 
1483 		//  If there is active item data, dump it
1484 		if (mapData->activeItemData != nullptr)
1485 			delete[] mapData->activeItemData;
1486 
1487 		//  If there is an association list, dump it
1488 		if (mapData->assocList != nullptr)
1489 			delete[] mapData->assocList;
1490 
1491 		//  If there is an active item list, dump it
1492 		if (mapData->activeItemList != nullptr)
1493 			delete mapData->activeItemList;
1494 
1495 		//  Dump the object ripping table ID list
1496 		delete[] mapData->ripTableIDList;
1497 	}
1498 
1499 	//  Dump the map data list
1500 	delete[] mapList;
1501 
1502 	//  Dump all of the tile terrain banks
1503 	for (i = 0; i < maxBanks; i++) {
1504 		if (tileBanks[i] != nullptr) {
1505 			delete tileBanks[i];
1506 			tileBanks[i] = nullptr;
1507 		}
1508 	}
1509 }
1510 
1511 //-----------------------------------------------------------------------
1512 //	Set a new current map
1513 
setCurrentMap(int mapNum)1514 void setCurrentMap(int mapNum) {
1515 	g_vm->_currentMapNum = mapNum;
1516 	if (lastMapNum != g_vm->_currentMapNum) {
1517 		lastMapNum = g_vm->_currentMapNum;
1518 		freeAllTileBanks();
1519 		audioEnvironmentSetWorld(mapNum);
1520 	}
1521 
1522 	lastUpdateTime = gameTime;
1523 }
1524 
1525 /* ===================================================================== *
1526    Automap management functions
1527  * ===================================================================== */
1528 
1529 //-----------------------------------------------------------------------
1530 
initAutoMap(void)1531 void initAutoMap(void) {
1532 	int16       i;
1533 
1534 	for (i = 0; i < worldCount; i++) {
1535 		MapHeader       *map;
1536 		int32           mapSize,
1537 		                mapIndex;
1538 		uint16          *mapData;
1539 
1540 		map = mapList[i].map;
1541 		mapSize = map->size;
1542 		mapSize *= mapSize;
1543 		mapData = map->mapData;
1544 
1545 		//  Clear the high bit for each map position
1546 		for (mapIndex = 0; mapIndex < mapSize; mapIndex++)
1547 			mapData[mapIndex] &= ~metaTileVisited;
1548 	}
1549 
1550 }
1551 
saveAutoMap(Common::OutSaveFile * outS)1552 void saveAutoMap(Common::OutSaveFile *outS) {
1553 	debugC(2, kDebugSaveload, "Saving AutoMap");
1554 
1555 	int32 totalMapSize = 0,
1556 	      totalMapIndex = 0;
1557 
1558 	uint8 *archiveBuffer;
1559 	int32 archiveBufSize;
1560 
1561 	for (int i = 0; i < worldCount; i++) {
1562 		MapHeader       *map;
1563 		int32           mapSize;
1564 
1565 		map = mapList[i].map;
1566 		mapSize = map->size;
1567 		mapSize *= mapSize;
1568 
1569 		totalMapSize += mapSize;
1570 	}
1571 
1572 	//  Compute the number of bytes needed to store the visited bit
1573 	//  for each map metatile slot
1574 	archiveBufSize = (totalMapSize + 7) >> 3;
1575 
1576 	outS->write("AMAP", 4);
1577 
1578 	archiveBuffer = (uint8 *)calloc(archiveBufSize, 1);
1579 	if (archiveBuffer == nullptr)
1580 		error("Unable to allocate auto map archive buffer");
1581 
1582 	for (int i = 0; i < worldCount; i++) {
1583 		MapHeader *map;
1584 		int32 mapSize,
1585 		      mapIndex;
1586 
1587 		uint16 *mapData;
1588 
1589 		map = mapList[i].map;
1590 		mapSize = map->size;
1591 		mapSize *= mapSize;
1592 		mapData = map->mapData;
1593 
1594 		for (mapIndex = 0; mapIndex < mapSize; mapIndex++) {
1595 			if (mapData[mapIndex] & metaTileVisited) {
1596 				//  Set the bit in the archive buffer
1597 				archiveBuffer[totalMapIndex >> 3] |=
1598 				    (1 << (totalMapIndex & 7));
1599 			} else {
1600 				//  Clear the bit in the archive buffer
1601 				archiveBuffer[totalMapIndex >> 3] &=
1602 				    ~(1 << (totalMapIndex & 7));
1603 			}
1604 
1605 			totalMapIndex++;
1606 		}
1607 	}
1608 
1609 	CHUNK_BEGIN;
1610 	out->write(archiveBuffer, archiveBufSize);
1611 	CHUNK_END;
1612 
1613 	free(archiveBuffer);
1614 }
1615 
loadAutoMap(Common::InSaveFile * in,int32 chunkSize)1616 void loadAutoMap(Common::InSaveFile *in, int32 chunkSize) {
1617 	int32       totalMapIndex = 0;
1618 	uint8       *archiveBuffer;
1619 	int32       archiveBufSize;
1620 
1621 	archiveBufSize = chunkSize;
1622 
1623 	archiveBuffer = (uint8 *)malloc(archiveBufSize);
1624 	if (archiveBuffer == nullptr)
1625 		error("Unable to allocate auto map archive buffer");
1626 
1627 	in->read(archiveBuffer, archiveBufSize);
1628 
1629 	for (int i = 0; i < worldCount; i++) {
1630 		MapHeader *map;
1631 		int32 mapSize,
1632 		      mapIndex;
1633 
1634 		uint16 *mapData;
1635 
1636 		map = mapList[i].map;
1637 		mapSize = map->size;
1638 		mapSize *= mapSize;
1639 		mapData = map->mapData;
1640 
1641 		for (mapIndex = 0; mapIndex < mapSize; mapIndex++) {
1642 			assert((totalMapIndex >> 3) < archiveBufSize);
1643 
1644 			//  If the bit is set in the archive buffer, set the visited
1645 			//  bit in the map data
1646 			if (archiveBuffer[totalMapIndex >> 3]
1647 			        & (1 << (totalMapIndex & 7)))
1648 				mapData[mapIndex] |= metaTileVisited;
1649 			else
1650 				mapData[mapIndex] &= ~metaTileVisited;
1651 
1652 			totalMapIndex++;
1653 		}
1654 	}
1655 
1656 	free(archiveBuffer);
1657 }
1658 
1659 /* ===================================================================== *
1660    Platform cache functions
1661  * ===================================================================== */
1662 
1663 //-----------------------------------------------------------------------
1664 //	Initialize the platform cache
1665 
initPlatformCache(void)1666 void initPlatformCache(void) {
1667 	platformCache = new PlatformCacheEntry[PlatformCacheEntry::kPlatformCacheSize];
1668 
1669 	for (int i = 0; i < PlatformCacheEntry::kPlatformCacheSize; i++) {
1670 		PlatformCacheEntry  *pce = &platformCache[i];
1671 
1672 		//  Fill up the LRU with empty platforms
1673 		pce->metaID = NoMetaTile;
1674 		g_vm->_platformLRU.push_back(i);
1675 	}
1676 }
1677 
1678 /* ===================================================================== *
1679    Returns the X/Y point in U/V coords
1680  * ===================================================================== */
1681 
XYToUV(const Point32 & pt)1682 TilePoint XYToUV(const Point32 &pt) {
1683 	int32       mapHeight = mapList[g_vm->_currentMapNum].mapHeight;
1684 	TilePoint   coords;
1685 
1686 	//  coordinates of the view in U,V
1687 
1688 	coords.u = (((pt.x + mapHeight) >> 1) - pt.y) >> 1;
1689 	coords.v = (mapHeight - pt.y - ((pt.x - mapHeight) >> 1)) >> 1;
1690 	coords.z = 0;
1691 
1692 	return coords;
1693 }
1694 
1695 /* ===================================================================== *
1696    Converts a (u,v,z) tilepoint to (x, y) screen coords;
1697  * ===================================================================== */
1698 
TileToScreenCoords(const TilePoint & tp,Point16 & p)1699 void TileToScreenCoords(const TilePoint &tp, Point16 &p) {
1700 	int32       mapHeight = mapList[g_vm->_currentMapNum].mapHeight;
1701 
1702 	//  screen coords of the point
1703 	p.x = (((int32)tp.u - (int32)tp.v) << 1) - tileScroll.x + mapHeight;
1704 	p.y = mapHeight - tileScroll.y - ((int32)tp.u + (int32)tp.v) - tp.z;
1705 }
1706 
TileToScreenCoords(const TilePoint & tp,StaticPoint16 & p)1707 void TileToScreenCoords(const TilePoint &tp, StaticPoint16 &p) {
1708 	int32       mapHeight = mapList[g_vm->_currentMapNum].mapHeight;
1709 
1710 	//  screen coords of the point
1711 	p.x = (((int32)tp.u - (int32)tp.v) << 1) - tileScroll.x + mapHeight;
1712 	p.y = mapHeight - tileScroll.y - ((int32)tp.u + (int32)tp.v) - tp.z;
1713 }
1714 
TilePoint(Common::SeekableReadStream * stream)1715 TilePoint::TilePoint(Common::SeekableReadStream *stream) {
1716 	u = stream->readSint16LE();
1717 	v = stream->readSint16LE();
1718 	z = stream->readSint16LE();
1719 }
1720 
1721 //-----------------------------------------------------------------------
1722 //	Converts a UV vector into a rough direction vector.
1723 
quickDir(void)1724 int16 TilePoint::quickDir(void) {
1725 	int16           u2 = u * 2,
1726 	                v2 = v * 2;
1727 
1728 	if (u < v2) {
1729 		if (v > -u2) return (v > u2 ? dirUpLeft : dirUp);
1730 		return (u > -v2 ? dirLeft : dirDownLeft);
1731 	} else {
1732 		if (v > -u2) return (u > -v2 ? dirUpRight : dirRight);
1733 		return (v > u2 ? dirDown : dirDownRight);
1734 	}
1735 }
1736 
1737 /* ===================================================================== *
1738    Do a bilinear interpolation of the four corner heights of a tile
1739    to determine the height of a point on the tile.
1740  * ===================================================================== */
1741 
ptHeight(const TilePoint & tp,uint8 * cornerHeight)1742 int16 ptHeight(const TilePoint &tp, uint8 *cornerHeight) {
1743 	int16   slopeHeight = cornerHeight[0];
1744 
1745 	if (cornerHeight[1] == slopeHeight &&
1746 	        cornerHeight[2] == slopeHeight &&
1747 	        cornerHeight[3] == slopeHeight)
1748 		return slopeHeight;
1749 
1750 	slopeHeight
1751 	    = (cornerHeight[0] * (kTileUVSize - tp.u)
1752 	       + cornerHeight[1] * tp.u)
1753 	      * (kTileUVSize - tp.v)
1754 	      + (cornerHeight[3] * (kTileUVSize - tp.u)
1755 	         + cornerHeight[2] * tp.u)
1756 	      * tp.v;
1757 
1758 	return slopeHeight >> (kTileUVShift + kTileUVShift);
1759 }
1760 
1761 /* ====================================================================== *
1762    Platform member functions
1763  * ====================================================================== */
1764 
1765 //-----------------------------------------------------------------------
1766 //	Fetch the REAL tile associated with a particular location, including
1767 //	indirection such as tile cycling and activity groups.
1768 //		REM: This is a likely candidate for downcoding...
1769 
fetchTile(int16 mapNum,const TilePoint & pt,const TilePoint & origin,int16 & height_,int16 & trFlags)1770 TileInfo *Platform::fetchTile(
1771     int16           mapNum,
1772     const TilePoint &pt,
1773     const TilePoint &origin,
1774     int16           &height_,
1775     int16           &trFlags) {
1776 	TileRef         *tr = &tiles[pt.u][pt.v];
1777 	TileInfo        *ti;
1778 
1779 	int16           h = tr->tileHeight * 8;
1780 
1781 	if (tr->flags & trTileTAG) {
1782 		ActiveItem  *groupItem,
1783 		            *instanceItem;
1784 		int16       state = 0;
1785 		TilePoint   relPos,
1786 		            absPos;
1787 
1788 		groupItem = ActiveItem::activeItemAddress(
1789 		                ActiveItemID(mapNum, tr->tile));
1790 
1791 		//  Relpos is the relative position of the
1792 		//  tile within the group
1793 
1794 		relPos.u = (tr->flags >> 1) & 0x07;
1795 		relPos.v = (tr->flags >> 4) & 0x07;
1796 
1797 		//  Abspos is the absolute position of the
1798 		//  group on the tile map.
1799 
1800 		absPos.u = pt.u - relPos.u + origin.u;
1801 		absPos.v = pt.v - relPos.v + origin.v;
1802 		absPos.z = h;
1803 
1804 		//  Look up the group instance in the hash.
1805 		instanceItem =  mapList[mapNum].findHashedInstance(
1806 		                    absPos,
1807 		                    tr->tile);
1808 		if (instanceItem) {
1809 			state = instanceItem->getInstanceState(mapNum);
1810 
1811 			//  Get the tile to be drawn from the tile group
1812 			tr = &(mapList[mapNum].activeItemData)[
1813 			         groupItem->_data.group.grDataOffset
1814 			         +   state * groupItem->_data.group.animArea
1815 			         +   relPos.u * groupItem->_data.group.vSize
1816 			         +   relPos.v];
1817 
1818 			h += tr->tileHeight * 8;
1819 		}
1820 #if DEBUG
1821 		else {
1822 			static  TileRef dummyRef = { 1, 0, 0 };
1823 			tr = &dummyRef;
1824 		}
1825 #endif
1826 	}
1827 
1828 
1829 	if ((ti = TileInfo::tileAddress(tr->tile)) == nullptr) return nullptr;
1830 
1831 	trFlags = tr->flags;
1832 	height_ = h;
1833 
1834 #if DEBUG
1835 	if (ti->offset > maxOffset
1836 	        || ti->attrs.height > kMaxTileHeight
1837 	        || ti->attrs.height < 0) {
1838 		int16       tileNo, tileBank;
1839 
1840 		TileID2Bank(tr->tile, tileBank, tileNo);
1841 		WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank);
1842 		return nullptr;
1843 	}
1844 #endif
1845 
1846 	return ti;
1847 }
1848 
1849 //  Fetch the tile and the active item it came from...
1850 //  REM: This is a likely candidate for downcoding...
1851 
fetchTAGInstance(int16 mapNum,const TilePoint & pt,const TilePoint & origin,StandingTileInfo & sti)1852 TileInfo *Platform::fetchTAGInstance(
1853     int16               mapNum,
1854     const TilePoint     &pt,
1855     const TilePoint     &origin,
1856     StandingTileInfo    &sti) {
1857 	TileRef         *tr = &tiles[pt.u][pt.v];
1858 	TileInfo        *ti;
1859 
1860 	int16           h = tr->tileHeight * 8;
1861 
1862 	if (tr->flags & trTileTAG) {
1863 		ActiveItem  *groupItem,
1864 		            *instanceItem;
1865 		int16       state = 0;
1866 		TilePoint   relPos,
1867 		            absPos;
1868 
1869 		groupItem = ActiveItem::activeItemAddress(
1870 		                ActiveItemID(mapNum, tr->tile));
1871 
1872 		//  Relpos is the relative position of the
1873 		//  tile within the group
1874 
1875 		relPos.u = (tr->flags >> 1) & 0x07;
1876 		relPos.v = (tr->flags >> 4) & 0x07;
1877 
1878 		//  Abspos is the absolute position of the
1879 		//  group on the tile map.
1880 
1881 		absPos.u = pt.u - relPos.u + origin.u;
1882 		absPos.v = pt.v - relPos.v + origin.v;
1883 		absPos.z = h;
1884 
1885 		//  Look up the group instance in the hash.
1886 		instanceItem =  mapList[mapNum].findHashedInstance(
1887 		                    absPos,
1888 		                    tr->tile);
1889 		if (instanceItem) {
1890 			state = instanceItem->getInstanceState(mapNum);
1891 			sti.surfaceTAG = instanceItem;
1892 
1893 			//  Get the tile to be drawn from the tile group
1894 			tr = &(mapList[mapNum].activeItemData)[
1895 			         groupItem->_data.group.grDataOffset
1896 			         +   state * groupItem->_data.group.animArea
1897 			         +   relPos.u * groupItem->_data.group.vSize
1898 			         +   relPos.v];
1899 
1900 			h += tr->tileHeight * 8;
1901 		}
1902 #if DEBUG
1903 		else {
1904 			static  TileRef dummyRef = { 1, 0, 0 };
1905 			tr = &dummyRef;
1906 		}
1907 #endif
1908 	} else {
1909 		sti.surfaceTAG = nullptr;
1910 	}
1911 
1912 	if ((ti = TileInfo::tileAddress(tr->tile)) == nullptr) return nullptr;
1913 
1914 	sti.surfaceTile = ti;
1915 	sti.surfaceRef = *tr;
1916 	sti.surfaceHeight = h;
1917 
1918 	return ti;
1919 }
1920 
1921 //-----------------------------------------------------------------------
1922 //	Fetch the REAL tile associated with a particular location, including
1923 //	indirection such as tile cycling and activity groups.
1924 //		REM: This is a likely candidate for downcoding...
1925 
fetchTile(int16 mapNum,const TilePoint & pt,const TilePoint & origin,uint8 ** imageData,int16 & height_,int16 & trFlags)1926 TileInfo *Platform::fetchTile(
1927     int16           mapNum,
1928     const TilePoint &pt,
1929     const TilePoint &origin,
1930     uint8           **imageData,
1931     int16           &height_,
1932     int16           &trFlags) {
1933 	TileRef         *tr = &tiles[pt.u][pt.v];
1934 	TileInfo        *ti;
1935 
1936 	int16           h = tr->tileHeight * 8;
1937 
1938 	if (tr->flags & trTileTAG) {
1939 		ActiveItem  *groupItem,
1940 		            *instanceItem;
1941 		int16       state = 0;
1942 		TilePoint   relPos,
1943 		            absPos;
1944 
1945 		groupItem = ActiveItem::activeItemAddress(
1946 		                ActiveItemID(mapNum, tr->tile));
1947 
1948 		//  Relpos is the relative position of the
1949 		//  tile within the group
1950 
1951 		relPos.u = (tr->flags >> 1) & 0x07;
1952 		relPos.v = (tr->flags >> 4) & 0x07;
1953 
1954 		//  Abspos is the absolute position of the
1955 		//  group on the tile map.
1956 
1957 		absPos.u = pt.u - relPos.u + origin.u;
1958 		absPos.v = pt.v - relPos.v + origin.v;
1959 		absPos.z = h;
1960 
1961 		//  Look up the group instance in the hash.
1962 		instanceItem =  mapList[mapNum].findHashedInstance(
1963 		                    absPos,
1964 		                    tr->tile);
1965 		if (instanceItem) {
1966 			state = instanceItem->getInstanceState(mapNum);
1967 
1968 			//  Get the tile to be drawn from the tile group
1969 			tr = &(mapList[mapNum].activeItemData)[
1970 			         groupItem->_data.group.grDataOffset
1971 			         +   state * groupItem->_data.group.animArea
1972 			         +   relPos.u * groupItem->_data.group.vSize
1973 			         +   relPos.v];
1974 
1975 			h += tr->tileHeight * 8;
1976 		}
1977 #if DEBUG
1978 		else {
1979 			static  TileRef dummyRef = { 1, 0, 0 };
1980 			tr = &dummyRef;
1981 		}
1982 #endif
1983 	}
1984 
1985 
1986 	if ((ti = TileInfo::tileAddress(tr->tile, imageData)) == nullptr) return nullptr;
1987 
1988 	trFlags = tr->flags;
1989 	height_ = h;
1990 
1991 #if DEBUG
1992 	if (ti->offset > maxOffset
1993 	        || ti->attrs.height > kMaxTileHeight
1994 	        || ti->attrs.height < 0) {
1995 		int16       tileNo, tileBank;
1996 
1997 		TileID2Bank(tr->tile, tileBank, tileNo);
1998 		WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank);
1999 		return nullptr;
2000 	}
2001 #endif
2002 
2003 	return ti;
2004 }
2005 
2006 //  Fetch the tile and the active item it came from...
2007 //  REM: This is a likely candidate for downcoding...
2008 
fetchTAGInstance(int16 mapNum,const TilePoint & pt,const TilePoint & origin,uint8 ** imageData,StandingTileInfo & sti)2009 TileInfo *Platform::fetchTAGInstance(
2010     int16               mapNum,
2011     const TilePoint     &pt,
2012     const TilePoint     &origin,
2013     uint8               **imageData,
2014     StandingTileInfo    &sti) {
2015 	TileRef         *tr = &tiles[pt.u][pt.v];
2016 	TileInfo        *ti;
2017 
2018 	int16           h = tr->tileHeight * 8;
2019 
2020 	if (tr->flags & trTileTAG) {
2021 		ActiveItem  *groupItem,
2022 		            *instanceItem;
2023 		int16       state = 0;
2024 		TilePoint   relPos,
2025 		            absPos;
2026 
2027 		groupItem = ActiveItem::activeItemAddress(
2028 		                ActiveItemID(mapNum, tr->tile));
2029 
2030 		//  Relpos is the relative position of the
2031 		//  tile within the group
2032 
2033 		relPos.u = (tr->flags >> 1) & 0x07;
2034 		relPos.v = (tr->flags >> 4) & 0x07;
2035 
2036 		//  Abspos is the absolute position of the
2037 		//  group on the tile map.
2038 
2039 		absPos.u = pt.u - relPos.u + origin.u;
2040 		absPos.v = pt.v - relPos.v + origin.v;
2041 		absPos.z = h;
2042 
2043 		//  Look up the group instance in the hash.
2044 		instanceItem =  mapList[mapNum].findHashedInstance(
2045 		                    absPos,
2046 		                    tr->tile);
2047 		if (instanceItem) {
2048 			state = instanceItem->getInstanceState(mapNum);
2049 			sti.surfaceTAG = instanceItem;
2050 
2051 			//  Get the tile to be drawn from the tile group
2052 			tr = &(mapList[mapNum].activeItemData)[
2053 			         groupItem->_data.group.grDataOffset
2054 			         +   state * groupItem->_data.group.animArea
2055 			         +   relPos.u * groupItem->_data.group.vSize
2056 			         +   relPos.v];
2057 
2058 			h += tr->tileHeight * 8;
2059 		}
2060 #if DEBUG
2061 		else {
2062 			static  TileRef dummyRef = { 1, 0, 0 };
2063 			tr = &dummyRef;
2064 		}
2065 #endif
2066 	} else {
2067 		sti.surfaceTAG = nullptr;
2068 	}
2069 
2070 	if ((ti = TileInfo::tileAddress(tr->tile, imageData)) == nullptr) return nullptr;
2071 
2072 	sti.surfaceTile = ti;
2073 	sti.surfaceRef = *tr;
2074 	sti.surfaceHeight = h;
2075 
2076 	return ti;
2077 }
2078 
2079 /* ====================================================================== *
2080    RipTable member functions
2081  * ====================================================================== */
2082 
2083 //-----------------------------------------------------------------------
2084 //	Return a pointer to a rip table give the rip table's ID
2085 
ripTableAddress(RipTableID id)2086 RipTable *RipTable::ripTableAddress(RipTableID id) {
2087 	return id != -1 ? &ripTableList[id] : nullptr;
2088 }
2089 
2090 //-----------------------------------------------------------------------
2091 //	Return a rip table's ID
2092 
thisID(void)2093 RipTableID RipTable::thisID(void) {
2094 	return _index;
2095 }
2096 
2097 /* ====================================================================== *
2098    MetaTile member functions
2099  * ====================================================================== */
2100 
2101 //-----------------------------------------------------------------------
2102 //	Return a pointer to a meta tile given its ID
2103 
metaTileAddress(MetaTileID id)2104 MetaTile *MetaTile::metaTileAddress(MetaTileID id) {
2105 	return  id.map != nullID && id.index != nullID
2106 	        ?   mapList[id.map].metaList->_tiles[id.index]
2107 	        :   nullptr;
2108 }
2109 
2110 //-----------------------------------------------------------------------
2111 //	Return this meta tile's ID
2112 
thisID(int16 mapNum)2113 MetaTileID MetaTile::thisID(int16 mapNum) {
2114 	return MetaTileID(mapNum, _index);
2115 }
2116 
2117 //-----------------------------------------------------------------------
2118 //	Return the audio theme associated with this metatile
2119 
HeavyMetaMusic(void)2120 metaTileNoise MetaTile::HeavyMetaMusic(void) {
2121 	return _properties & 0xFF;
2122 }
2123 
2124 //-----------------------------------------------------------------------
2125 //	Return a pointer to the specified platform
2126 
fetchPlatform(int16 mapNum,int16 layer)2127 Platform *MetaTile::fetchPlatform(int16 mapNum, int16 layer) {
2128 	const int			cacheFlag = 0x8000;
2129 	uint16              plIndex = _stack[layer];
2130 	PlatformCacheEntry  *pce;
2131 	Common::SeekableReadStream *stream;
2132 
2133 	assert(layer >= 0);
2134 	assert(_parent == mapList[mapNum].metaList);
2135 
2136 	if (plIndex == (uint16)nullID) {
2137 		return nullptr;
2138 	} else if (plIndex & cacheFlag) {
2139 		plIndex &= ~cacheFlag;
2140 
2141 		assert(plIndex < PlatformCacheEntry::kPlatformCacheSize);
2142 
2143 			//	Get the address of the pce from the cache
2144 		pce = &platformCache[plIndex];
2145 
2146 		assert(pce->metaID != NoMetaTile);
2147 		assert(pce->metaID == thisID(mapNum));
2148 
2149 			//	Move to the end of the LRU
2150 		g_vm->_platformLRU.remove(plIndex);
2151 		g_vm->_platformLRU.push_back(plIndex);
2152 
2153 			//	return the address of the platform
2154 		return &pce->pl;
2155 	} else {
2156 		debugC(2, kDebugLoading, "Fetching platform (%d,%d)", mapNum, layer);
2157 
2158 		//  Since the platform is not in the cache, we need to
2159 		//  dump something from the cache. Dump the one that
2160 		//  was least recently used.
2161 		//  Get head of LRU chain.
2162 		int cacheIndex = g_vm->_platformLRU.front();
2163 		g_vm->_platformLRU.pop_front();
2164 		g_vm->_platformLRU.push_back(cacheIndex);
2165 
2166 		pce = &platformCache[cacheIndex];
2167 
2168 		//  Compute the layer of this entry in the cache
2169 		assert(cacheIndex < PlatformCacheEntry::kPlatformCacheSize);
2170 		assert(cacheIndex >= 0);
2171 
2172 		if (pce->metaID != NoMetaTile) {
2173 			MetaTile *oldMeta = metaTileAddress(pce->metaID);
2174 
2175 			assert(pce->layerNum < maxPlatforms);
2176 			assert(oldMeta->_stack[pce->layerNum] == (cacheFlag | cacheIndex));
2177 			oldMeta->_stack[pce->layerNum] = pce->platformNum;
2178 		}
2179 
2180 		//  Initialize the cache entry to the new platform data.
2181 		pce->platformNum = plIndex;
2182 		pce->layerNum = layer;
2183 		pce->metaID = thisID(mapNum);
2184 		_stack[layer] = (cacheIndex | cacheFlag);
2185 
2186 		assert(plIndex * sizeof(Platform) < tileRes->size(platformID + mapNum));
2187 		debugC(3, kDebugLoading, "- plIndex: %d", plIndex);
2188 
2189 		// Now, load the actual metatile data...
2190 		if ((stream = loadResourceToStream(tileRes, platformID + mapNum, "platform"))) {
2191 			if (stream->skip(plIndex * sizeof(Platform))) {
2192 				pce->pl.load(stream);
2193 				delete stream;
2194 				return &pce->pl;
2195 			}
2196 		}
2197 
2198 		error("Unable to read Platform %d of map %d", plIndex, mapNum);
2199 		return nullptr;
2200 	}
2201 }
2202 
2203 //-----------------------------------------------------------------------
2204 //	Return a pointer to this metatile's current object ripping
2205 //	table
2206 
ripTable(int16 mapNum)2207 RipTable *MetaTile::ripTable(int16 mapNum) {
2208 	WorldMapData    *mapData = &mapList[mapNum];
2209 
2210 	return RipTable::ripTableAddress((mapData->ripTableIDList)[_index]);
2211 }
2212 
2213 //-----------------------------------------------------------------------
2214 //	Return a reference to this meta tile's rip table ID
2215 
ripTableID(int16 mapNum)2216 RipTableID &MetaTile::ripTableID(int16 mapNum) {
2217 	WorldMapData    *mapData = &mapList[mapNum];
2218 
2219 	return (mapData->ripTableIDList)[_index];
2220 }
2221 
2222 /* ====================================================================== *
2223    WorldMapData member functions
2224  * ====================================================================== */
2225 
2226 //-----------------------------------------------------------------------
2227 //	Return a pointer to the specified meta tile on this map
2228 
lookupMeta(TilePoint coords)2229 MetaTilePtr WorldMapData::lookupMeta(TilePoint coords) {
2230 	uint16          *mapData = map->mapData;
2231 	int16           mtile;
2232 
2233 #if 0
2234 	//  Note: Keep this code if we ever need to have variable
2235 	//  map edge types in the future.
2236 
2237 	TilePoint       clipCoords;
2238 	int16           mapSizeMask = mapSize - 1,
2239 	                mapEdgeType = (*map)->edgeType;
2240 
2241 	clipCoords.u = (uint16)coords.u % mapSize;
2242 	clipCoords.v = (uint16)coords.v % mapSize;
2243 	clipCoords.z = coords.z;
2244 
2245 	if (coords != clipCoords) {
2246 		switch (mapEdgeType) {
2247 		case edgeTypeBlack: // continue;
2248 		case edgeTypeFill0:
2249 			mtile = 0;
2250 			break;
2251 
2252 		case edgeTypeFill1:
2253 			mtile = 1;
2254 			break;
2255 
2256 		case edgeTypeRepeat:
2257 			coords.u = clamp(0, coords.u, mapSizeMask);
2258 			coords.v = clamp(0, coords.v, mapSizeMask);
2259 			mtile = mapData[clipCoords.u * mapSize + clipCoords.v];
2260 			break;
2261 
2262 		case edgeTypeWrap:
2263 			mtile = mapData[clipCoords.u * mapSize + clipCoords.v];
2264 			break;
2265 		}
2266 	} else mtile = mapData[clipCoords.u * mapSize + clipCoords.v];
2267 #else
2268 
2269 	//  Check to see if coords are less than zero or greater
2270 	//  than size of map. Note we can eliminate less than
2271 	//  zero test by doing an unsigned compare.
2272 
2273 	if ((uint32)coords.u >= (uint32)mapSize
2274 	        || (uint32)coords.v >= (uint32)mapSize) {
2275 		//  If off the edge of the map, it defaults to meta
2276 		//  tile #1.
2277 		mtile = 1;
2278 	} else {
2279 		//  When getting the metatile number, make sure to mask off the
2280 		//  bit indicating that this map square has been visited.
2281 		mtile = mapData[coords.u * mapSize + coords.v] & ~metaTileVisited;
2282 	}
2283 
2284 #endif
2285 
2286 	assert(mtile < metaCount);
2287 	assert(mtile >= 0);
2288 
2289 	return metaList->_tiles[mtile];
2290 
2291 }
2292 
2293 //-----------------------------------------------------------------------
2294 //	Builds an active item instance hash table for tile lookup
2295 
buildInstanceHash(void)2296 void WorldMapData::buildInstanceHash(void) {
2297 	int32           i;
2298 	int16           hashVal;
2299 	ActiveItem      **ail;
2300 
2301 	memset(instHash, 0, sizeof(instHash));
2302 
2303 	for (i = 0, ail = activeItemList->_items; i < activeCount; i++, ail++) {
2304 		ActiveItem *ai = *ail;
2305 		if (ai->_data.itemType == activeTypeInstance) {
2306 			hashVal = (((ai->_data.instance.u + ai->_data.instance.h) << 4)
2307 			           + ai->_data.instance.v + (ai->_data.instance.groupID << 2))
2308 			          % ARRAYSIZE(instHash);
2309 
2310 			ai->_nextHash = instHash[hashVal];
2311 			instHash[hashVal] = ai;
2312 		}
2313 	}
2314 }
2315 
2316 //-----------------------------------------------------------------------
2317 //	Lookup an active item instance given the active item group number
2318 //	an the meta tile coordinates
2319 
findHashedInstance(TilePoint & tp,int16 group)2320 ActiveItem *WorldMapData::findHashedInstance(
2321     TilePoint &tp,
2322     int16 group) {
2323 	int16           hashVal = (((tp.u + tp.z) << 4) + tp.v + (group << 2))
2324 	                          % ARRAYSIZE(instHash);
2325 
2326 	for (ActiveItem *ai = instHash[hashVal]; ai; ai = ai->_nextHash) {
2327 		if (ai->_data.instance.u == tp.u &&
2328 		    ai->_data.instance.v == tp.v &&
2329 		    ai->_data.instance.h == tp.z &&
2330 		    ai->_data.instance.groupID == group)
2331 			return ai;
2332 	}
2333 
2334 	return nullptr;
2335 }
2336 
2337 /* ====================================================================== *
2338    MetaTileIterator member functions
2339  * ====================================================================== */
2340 
iterate(void)2341 bool MetaTileIterator::iterate(void) {
2342 	if (++mCoords.v >= region.max.v) {
2343 		if (++mCoords.u >= region.max.u) return false;
2344 		mCoords.v = region.min.v;
2345 	}
2346 
2347 	return true;
2348 }
2349 
first(TilePoint * loc)2350 MetaTile *MetaTileIterator::first(TilePoint *loc) {
2351 	MetaTile    *mtRes;
2352 
2353 	mCoords = region.min;
2354 	if (mCoords.u >= region.max.u || mCoords.v >= region.max.v)
2355 		return nullptr;
2356 
2357 	mtRes = mapList[mapNum].lookupMeta(mCoords);
2358 	while (mtRes == nullptr) {
2359 		if (!iterate()) return nullptr;
2360 		mtRes = mapList[mapNum].lookupMeta(mCoords);
2361 	}
2362 
2363 	if (loc) *loc = mCoords << kPlatShift;
2364 	return mtRes;
2365 }
2366 
next(TilePoint * loc)2367 MetaTile *MetaTileIterator::next(TilePoint *loc) {
2368 	MetaTile    *mtRes = nullptr;
2369 
2370 	do {
2371 		if (!iterate()) return nullptr;
2372 		mtRes = mapList[mapNum].lookupMeta(mCoords);
2373 	} while (mtRes == nullptr);
2374 
2375 	if (loc) *loc = mCoords << kPlatShift;
2376 	return mtRes;
2377 }
2378 
2379 /* ====================================================================== *
2380    TileIterator member functions
2381  * ====================================================================== */
2382 
iterate(void)2383 bool TileIterator::iterate(void) {
2384 	if (++tCoords.v >= tCoordsReg.max.v) {
2385 		if (++tCoords.u >= tCoordsReg.max.u) {
2386 			do {
2387 				platIndex++;
2388 				if (platIndex >= maxPlatforms) {
2389 					if ((mt = metaIter.next(&origin)) != nullptr) {
2390 						tCoordsReg.min.u = tCoordsReg.min.v = 0;
2391 						tCoordsReg.max.u = tCoordsReg.max.v = kPlatformWidth;
2392 
2393 						if (origin.u < region.min.u)
2394 							tCoordsReg.min.u = region.min.u & kPlatMask;
2395 						if (origin.u + kPlatformWidth > region.max.u)
2396 							tCoordsReg.max.u = region.max.u & kPlatMask;
2397 						if (origin.v < region.min.v)
2398 							tCoordsReg.min.v = region.min.v & kPlatMask;
2399 						if (origin.v + kPlatformWidth > region.max.v)
2400 							tCoordsReg.max.v = region.max.v & kPlatMask;
2401 					} else
2402 						return false;
2403 
2404 					platIndex = 0;
2405 				}
2406 				platform =  mt->fetchPlatform(
2407 				                metaIter.getMapNum(),
2408 				                platIndex);
2409 			} while (platform == nullptr);
2410 
2411 			tCoords.u = tCoordsReg.min.u;
2412 		}
2413 		tCoords.v = tCoordsReg.min.v;
2414 	}
2415 
2416 	return true;
2417 }
2418 
first(TilePoint * loc,StandingTileInfo * stiResult)2419 TileInfo *TileIterator::first(TilePoint *loc, StandingTileInfo *stiResult) {
2420 	TileInfo            *tiRes;
2421 	StandingTileInfo    sti;
2422 
2423 	if (region.max.u <= region.min.u || region.max.v <= region.min.v)
2424 		return nullptr;
2425 
2426 	if ((mt = metaIter.first(&origin)) == nullptr) return nullptr;
2427 
2428 	platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex = 0);
2429 	while (platform == nullptr) {
2430 		platIndex++;
2431 		if (platIndex >= maxPlatforms) {
2432 			if ((mt = metaIter.next(&origin)) == nullptr) return nullptr;
2433 			platIndex = 0;
2434 		}
2435 		platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex);
2436 	}
2437 
2438 	tCoordsReg.min.u = tCoordsReg.min.v = 0;
2439 	tCoordsReg.max.u = tCoordsReg.max.v = kPlatformWidth;
2440 
2441 	if (origin.u < region.min.u)
2442 		tCoordsReg.min.u = region.min.u & kPlatMask;
2443 	if (origin.u + kPlatformWidth > region.max.u)
2444 		tCoordsReg.max.u = region.max.u & kPlatMask;
2445 	if (origin.v < region.min.v)
2446 		tCoordsReg.min.v = region.min.v & kPlatMask;
2447 	if (origin.v + kPlatformWidth > region.max.v)
2448 		tCoordsReg.max.v = region.max.v & kPlatMask;
2449 
2450 	tCoords = tCoordsReg.min;
2451 	tiRes = platform->fetchTAGInstance(
2452 	            metaIter.getMapNum(),
2453 	            tCoords,
2454 	            origin,
2455 	            sti);
2456 	while (tiRes == nullptr) {
2457 		if (!iterate()) return nullptr;
2458 		tiRes = platform->fetchTAGInstance(
2459 		            metaIter.getMapNum(),
2460 		            tCoords,
2461 		            origin,
2462 		            sti);
2463 	}
2464 
2465 	*loc = tCoords + origin;
2466 	if (stiResult) *stiResult = sti;
2467 	return tiRes;
2468 }
2469 
next(TilePoint * loc,StandingTileInfo * stiResult)2470 TileInfo *TileIterator::next(TilePoint *loc, StandingTileInfo *stiResult) {
2471 	TileInfo            *tiRes = nullptr;
2472 	StandingTileInfo    sti;
2473 
2474 	do {
2475 		if (!iterate()) return nullptr;
2476 		tiRes = platform->fetchTAGInstance(
2477 		            metaIter.getMapNum(),
2478 		            tCoords,
2479 		            origin,
2480 		            sti);
2481 	} while (tiRes == nullptr);
2482 
2483 	*loc = tCoords + origin;
2484 	if (stiResult) *stiResult = sti;
2485 	return tiRes;
2486 }
2487 
2488 /* ============================================================================ *
2489    NOTE: drawPlatform has been moved to TILELOAD.CPP for now
2490  * ============================================================================ */
2491 
2492 /* ============================================================================ *
2493    Map drawing functions
2494  * ============================================================================ */
2495 
drawMetaRow(gPixelMap & drawMap,TilePoint coords,Point16 pos)2496 inline void drawMetaRow(gPixelMap &drawMap, TilePoint coords, Point16 pos) {
2497 	WorldMapData    *curMap = &mapList[g_vm->_currentMapNum];
2498 
2499 	int16           uOrg = coords.u * kPlatformWidth,
2500 	                vOrg = coords.v * kPlatformWidth;
2501 
2502 	Platform        *drawList[maxPlatforms + 1],
2503 	                **put = drawList;
2504 
2505 	int16           mapSizeMask = curMap->mapSize - 1,
2506 	                mapEdgeType = curMap->map->edgeType;
2507 	uint16          *mapData = curMap->map->mapData;
2508 
2509 	MetaTilePtr     *metaArray = curMap->metaList->_tiles;
2510 
2511 	int16           layerLimit;
2512 
2513 	for (;
2514 	        pos.x < drawMap.size.x + kMetaDX;
2515 	        coords.u++,
2516 	        coords.v--,
2517 	        uOrg += kPlatformWidth,
2518 	        vOrg -= kPlatformWidth,
2519 	        pos.x += kMetaTileWidth
2520 	    ) {
2521 		TilePoint       clipCoords;
2522 		int16           mtile = 0;
2523 		MetaTilePtr     metaPtr;
2524 
2525 		clipCoords.u = (uint16)coords.u % curMap->mapSize;
2526 		clipCoords.v = (uint16)coords.v % curMap->mapSize;
2527 		clipCoords.z = 0;
2528 
2529 		if (coords != clipCoords) {
2530 			switch (mapEdgeType) {
2531 			case edgeTypeBlack: // continue;
2532 			case edgeTypeFill0:
2533 				mtile = 0;
2534 				break;
2535 
2536 			case edgeTypeFill1:
2537 				mtile = 1;
2538 				break;
2539 
2540 			case edgeTypeRepeat:
2541 				coords.u = CLIP(coords.u, (int16)0, mapSizeMask);
2542 				coords.v = CLIP(coords.v, (int16)0, mapSizeMask);
2543 				mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited;
2544 				break;
2545 
2546 			case edgeTypeWrap:
2547 				mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited;
2548 				break;
2549 			}
2550 		} else mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited;
2551 
2552 		if (mtile >= curMap->metaCount) mtile = curMap->metaCount - 1;
2553 
2554 		metaPtr = metaArray[mtile];
2555 		put = drawList;
2556 
2557 		if (metaPtr == nullptr) return;
2558 
2559 		//  REM: Reject whole metatiles based on coords, based on
2560 		//  max height
2561 
2562 		layerLimit = maxPlatforms;
2563 
2564 		for (int i = 0; i < layerLimit; i++) {
2565 			Platform    *p;
2566 
2567 			p = metaPtr->fetchPlatform(g_vm->_currentMapNum, i);
2568 
2569 			if (!p)
2570 				continue;
2571 
2572 			if (p->roofRipID() == rippedRoofID && rippedRoofID > 0) break;
2573 
2574 			if (p->flags & plVisible) {
2575 				//  REM: precompute this later, by scanning the platform
2576 				//  for individual altitudes
2577 
2578 				p->highestPixel = kTileHeight * (kPlatformWidth - 1) + kMaxTileHeight * 2 + 64;
2579 
2580 				if (pos.y <= 0
2581 				        || pos.y - p->highestPixel >= drawMap.size.y)
2582 					continue;
2583 
2584 				*put++ = p;
2585 			}
2586 		}
2587 		*put++ = nullptr;
2588 
2589 		if (drawList[0] != nullptr) {
2590 			drawPlatform(drawMap, drawList, pos, uOrg, vOrg);
2591 		}
2592 		//  gThread::yield();
2593 	}
2594 }
2595 
2596 //-----------------------------------------------------------------------
2597 //  Build the object ripping tables for a specified metatile with a
2598 //  specified roof ripping ID
2599 
buildRipTable(uint16 ripID,RipTable * ripTable,MetaTile * mt)2600 void buildRipTable(
2601     uint16      ripID,
2602     RipTable    *ripTable,
2603     MetaTile    *mt) {
2604 	const int32 initVal = ((int32)maxint16 << 16) | maxint16;
2605 	int32   *initPtr = (int32 *)ripTable->zTable;
2606 
2607 	//  Initialize table
2608 	mt->ripTableID(g_vm->_currentMapNum) = ripTable->thisID();
2609 	ripTable->metaID = mt->thisID(g_vm->_currentMapNum);
2610 	ripTable->ripID = ripID;
2611 
2612 	for (uint i = 0;
2613 	        i < sizeof(ripTable->zTable) / sizeof(initVal);
2614 	        i++)
2615 		*initPtr++ = initVal;
2616 
2617 	//  If there is no roof ripping ID, we're done
2618 	if (ripID == 0) return;
2619 
2620 	//  Determine number of tile positions in meta tile for which to
2621 	//  calculate object ripping altitude
2622 	int16   tilesToGo = kPlatformWidth * kPlatformWidth;
2623 
2624 	for (uint i = 0; i < maxPlatforms; i++) {
2625 		Platform    *p;
2626 
2627 		if ((p = mt->fetchPlatform(g_vm->_currentMapNum, i)) == nullptr) continue;
2628 
2629 		if (p->roofRipID() != ripID) continue;
2630 
2631 		for (; i < maxPlatforms && tilesToGo > 0; i++) {
2632 			if ((p = mt->fetchPlatform(g_vm->_currentMapNum, i)) == nullptr)
2633 				continue;
2634 
2635 			uint16      platHeight = p->height << 3;
2636 			int16       u, v;
2637 
2638 			for (u = 0; u < kPlatformWidth; u++)
2639 				for (v = 0; v < kPlatformWidth; v++)
2640 					if (ripTable->zTable[u][v] == maxint16) {
2641 						TileRef &tr = p->getTileRef(u, v);
2642 
2643 						if (tr.tile != 0) {
2644 							//  Calculate object ripping altitude for
2645 							//  tile position
2646 							ripTable->zTable[u][v] =
2647 							    platHeight + (tr.tileHeight << 3);
2648 							tilesToGo--;
2649 						}
2650 					}
2651 		}
2652 
2653 		break;
2654 	}
2655 }
2656 
2657 //-----------------------------------------------------------------------
2658 //  Build the object ripping tables for the metatiles in the vicinity of
2659 //	the center view object
2660 
buildRipTables(void)2661 void buildRipTables(void) {
2662 	const int16         regionRadius = kTileUVSize * kPlatformWidth * 2;
2663 
2664 	TilePoint           actorCoords;
2665 	MetaTile            *mt;
2666 	TileRegion          ripTableReg;
2667 
2668 	MetaTile            *mtTable[25]; // Largest region is 5x5
2669 	int16               mtTableSize = 0;
2670 
2671 	getViewTrackPos(actorCoords);
2672 	ripTableCoords.u = actorCoords.u >> (kTileUVShift + kPlatShift);
2673 	ripTableCoords.v = actorCoords.v >> (kTileUVShift + kPlatShift);
2674 	ripTableCoords.z = 0;
2675 
2676 	//  Calculate the region of meta tile for which to build object
2677 	//  ripping table
2678 	ripTableReg.min.u = (actorCoords.u - regionRadius) >> kTileUVShift;
2679 	ripTableReg.min.v = (actorCoords.v - regionRadius) >> kTileUVShift;
2680 	ripTableReg.max.u =
2681 	    (actorCoords.u + regionRadius + kTileUVMask) >> kTileUVShift;
2682 	ripTableReg.max.v =
2683 	    (actorCoords.v + regionRadius + kTileUVMask) >> kTileUVShift;
2684 
2685 	MetaTileIterator    mIter(g_vm->_currentMapNum, ripTableReg);
2686 
2687 	//  Build meta tile pointer array
2688 	mt = mIter.first();
2689 	while (mt) {
2690 		mtTable[mtTableSize++] = mt;
2691 
2692 		mt = mIter.next();
2693 	}
2694 
2695 	int16       tableIndex;
2696 
2697 	//  bit array of available rip tables
2698 	uint8       tableAvail[(RipTable::kRipTableSize + 7) >> 3];
2699 
2700 	memset(tableAvail, 0xFF, sizeof(tableAvail));
2701 
2702 	for (int i = 0; i < mtTableSize; i++) {
2703 		mt = mtTable[i];
2704 
2705 		RipTable    *mtRipTable = mt->ripTable(g_vm->_currentMapNum);
2706 
2707 		//  If meta tile aready has a valid object ripping table, simply
2708 		//  recycle it
2709 		if (mtRipTable && mtRipTable->ripID == rippedRoofID) {
2710 			//  Null out pointer
2711 			mtTable[i] = nullptr;
2712 			//  Mark the table as unavailable
2713 			tableIndex = mtRipTable->_index;
2714 			tableAvail[tableIndex >> 3] &= ~(1 << (tableIndex & 0x7));
2715 		}
2716 	}
2717 
2718 	//  Remove empty entries from meta tile pointer array
2719 	int16       oldMtTableSize = mtTableSize;
2720 	for (int i = 0, j = 0; i < oldMtTableSize; i++) {
2721 		if (mtTable[i] != nullptr)
2722 			mtTable[j++] = mtTable[i];
2723 		else
2724 			mtTableSize--;
2725 	}
2726 
2727 	for (int i = 0; i < mtTableSize; i++) {
2728 		mt = mtTable[i];
2729 
2730 		uint j;
2731 		//  Find available table
2732 		for (j = 0; j < RipTable::kRipTableSize; j++) {
2733 			if (tableAvail[j >> 3] & (1 << (j & 0x7)))
2734 				break;
2735 		}
2736 		tableAvail[j >> 3] &= ~(1 << (j & 0x7));
2737 
2738 		//  If rip table has a valid metatile, remove that meta tile's
2739 		//  reference to its rip table
2740 		if (ripTableList[j].metaID != NoMetaTile) {
2741 			MetaTileID ripID = ripTableList[j].metaID;
2742 			MetaTile *ripMt =   MetaTile::metaTileAddress(ripID);
2743 
2744 			RipTableID &rt = ripMt->ripTableID(ripID.map);
2745 
2746 			//  Assign -1 to the meta tile's rip table ID
2747 			if (RipTable::ripTableAddress(rt) == &ripTableList[j])
2748 				rt = -1;
2749 		}
2750 		//  Build meta tile's object ripping table
2751 		buildRipTable(rippedRoofID, &ripTableList[j], mt);
2752 	}
2753 }
2754 
2755 
2756 
2757 //  Determine which metatiles in the local area will have
2758 //  cutaway roofs...
2759 
buildRoofTable(void)2760 void buildRoofTable(void) {
2761 	uint16          newRoofID = objRoofID(getViewCenterObject());
2762 
2763 	if (newRoofID != rippedRoofID) {
2764 		rippedRoofID = newRoofID;
2765 
2766 		buildRipTables();
2767 	}
2768 }
2769 
2770 //  Draw all visible metatiles
2771 
drawMetaTiles(gPixelMap & drawMap)2772 void drawMetaTiles(gPixelMap &drawMap) {
2773 	Point32     viewPos;
2774 	Point16     metaPos;
2775 	TilePoint   baseCoords;
2776 
2777 	//updateHandleRefs(baseCoords);  // viewPoint, &sti );
2778 	//  coordinates of the view window on the map in X,Y (in 16 pixel units)
2779 
2780 	viewPos.x = (tileScroll.x >> kTileDXShift)
2781 	            - (kPlatformWidth * mapList[g_vm->_currentMapNum].mapSize),
2782 	viewPos.y = (kPlatformWidth
2783 	             *   mapList[g_vm->_currentMapNum].mapSize
2784 	             *   kTileDX)
2785 	             -   tileScroll.y;
2786 
2787 	debugC(2, kDebugTiles, "viewPos = (%d,%d)", viewPos.x, viewPos.y);
2788 
2789 	//  coordinates of the view window upper left corner in U,V
2790 
2791 	baseCoords.u = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) + viewPos.x)
2792 	               / (kPlatformWidth * 2);
2793 	baseCoords.v = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) - viewPos.x)
2794 	               / (kPlatformWidth * 2);
2795 	baseCoords.z = 0;
2796 
2797 	debugC(2, kDebugTiles, "baseCoords = (%d,%d,%d)", baseCoords.u, baseCoords.v, baseCoords.z);
2798 
2799 	setAreaSound(baseCoords);
2800 
2801 	updateHandleRefs(baseCoords);  // viewPoint, &sti );
2802 	//  coordinates of current metatile (in X,Y), relative to screen
2803 
2804 	metaPos.x   = (baseCoords.u - baseCoords.v) * kMetaDX
2805 	            	- viewPos.x * kTileDX;
2806 
2807 	metaPos.y   = viewPos.y
2808 	              - (baseCoords.u + baseCoords.v) * kMetaDY;
2809 
2810 	debugC(2, kDebugTiles, "metaPos = (%d,%d)", metaPos.x, metaPos.y);
2811 
2812 	//  Loop through each horizontal row of metatiles
2813 	//  REM: also account for highest possible platform
2814 	//      (replace 256 constant with better value)
2815 
2816 	for (;
2817 	        metaPos.y < drawMap.size.y + kMetaTileHeight * 4 ;
2818 	        baseCoords.u--,
2819 	        baseCoords.v--
2820 	    ) {
2821 		drawMetaRow(drawMap, baseCoords, metaPos);
2822 
2823 		metaPos.y += kMetaDY;
2824 		metaPos.x -= kMetaDX;
2825 
2826 		drawMetaRow(drawMap, TilePoint(baseCoords.u - 1, baseCoords.v, 0), metaPos);
2827 
2828 		metaPos.y += kMetaDY;
2829 		metaPos.x += kMetaDX;
2830 	}
2831 }
2832 
2833 /* ===================================================================== *
2834    Tile masking
2835  * ===================================================================== */
2836 
2837 enum maskRules {
2838 	maskRuleNever,                          // never mask
2839 	maskRuleAlways,                         // always mask
2840 
2841 	//  Mask based on U threshold
2842 	maskRuleUClose,
2843 	maskRuleUMed,
2844 	maskRuleUFar,
2845 
2846 	//  Mask based on V threshold
2847 	maskRuleVClose,
2848 	maskRuleVMed,
2849 	maskRuleVFar,
2850 
2851 	//  Mask based on vertical distance
2852 	maskRuleYClose,
2853 	maskRuleYMed,
2854 	maskRuleYFar,
2855 
2856 	//  Mask based on combined U & V
2857 	maskRuleConvexNear,
2858 	maskRuleConcaveFar,
2859 	maskRuleConvexFar,
2860 	maskRuleConcaveNear
2861 
2862 	//  More mask types to come!
2863 };
2864 
2865 const int           thresh1 = 0,
2866                     thresh2 = kTileUVSize / 4,
2867                     thresh3 = kTileUVSize - 1;
2868 
2869 //  l is the relative position of the character with repect
2870 //  to the tile in U,V coords.
2871 
maskRule(TilePoint & l,TileInfo & ti)2872 inline bool maskRule(TilePoint &l, TileInfo &ti) {
2873 	int16       slopeHeight = ptHeight(l, ti.attrs.cornerHeight);
2874 
2875 	//  If it's a tall tile, and character is above the height of
2876 	//  the tile, then don't mask.
2877 
2878 	/*  if (ti.attrs.height > tileDX * 2)
2879 	    {
2880 	        int16       a = 0;
2881 
2882 	        a++;
2883 	    }*/
2884 
2885 	if ((l.z >= ti.attrs.terrainHeight
2886 	        &&  l.z >= slopeHeight)
2887 	        ||  l.u < -3
2888 	        ||  l.v < -3)
2889 		return false;
2890 
2891 	if (l.u > 0 && l.v > 0) {
2892 		if (l.u > thresh3 || l.v > thresh3) {
2893 			if (l.z < slopeHeight - 8) return true;
2894 		} else {
2895 			if (l.z < slopeHeight - 56) return true;
2896 		}
2897 	}
2898 
2899 //	if (ti.attrs.height > tileDX * 2)
2900 //	{
2901 //		ti.attrs.maskRule = maskRuleConcaveNear;
2902 //	}
2903 //	else ti.attrs.maskRule = maskRuleNever;
2904 
2905 	//  REM: Check for special-shaped actors...
2906 
2907 	switch (ti.attrs.maskRule) {
2908 	case maskRuleNever:
2909 //		if (l.z < -8 && (l.u > thresh3 || l.v > thresh3)) return true;
2910 		return false;
2911 
2912 	case maskRuleAlways:
2913 		return true;
2914 
2915 	case maskRuleUClose:
2916 		return (l.u > thresh1);
2917 	case maskRuleUMed:
2918 		return (l.u > thresh2);
2919 	case maskRuleUFar:
2920 		return (l.u > thresh3);
2921 
2922 	case maskRuleVClose:
2923 		return (l.v > thresh1);
2924 	case maskRuleVMed:
2925 		return (l.v > thresh2);
2926 	case maskRuleVFar:
2927 		return (l.v > thresh3);
2928 
2929 	case maskRuleYClose:
2930 		return (l.u + l.v > thresh1 + thresh1);
2931 	case maskRuleYMed:
2932 		return (l.u + l.v > thresh2 + thresh2);
2933 	case maskRuleYFar:
2934 		return (l.u + l.v > thresh3 + thresh3);
2935 
2936 	case maskRuleConvexNear:
2937 		return (l.u > thresh1 && l.v > thresh1);
2938 	case maskRuleConvexFar:
2939 		return (l.u > thresh2 && l.v > thresh2);
2940 
2941 	case maskRuleConcaveNear:
2942 		return (l.u > thresh2 || l.v > thresh2);
2943 	case maskRuleConcaveFar:
2944 		return (l.u > thresh3 || l.v > thresh3);
2945 	}
2946 	return false;
2947 }
2948 
maskPlatform(gPixelMap & sMap,Platform ** pList,Point16 screenPos,TilePoint relLoc,int16 uOrg,int16 vOrg)2949 void maskPlatform(
2950     gPixelMap       &sMap,
2951     Platform        **pList,                // platforms to draw
2952     Point16         screenPos,              // screen position
2953     TilePoint       relLoc,                 // relative location
2954     int16           uOrg,                   // for TAG search
2955     int16           vOrg) {                 // for TAG search
2956 	int16           u, v;
2957 
2958 	int16           right = sMap.size.x,
2959 	                bottom = sMap.size.y;
2960 
2961 	Point16         tilePos;
2962 
2963 	int16           x = screenPos.x,
2964 	                x2 = x / kTileDX;
2965 	int16           length = 1;
2966 
2967 	TilePoint       rLoc;
2968 	TilePoint       origin(uOrg, vOrg, 0);
2969 
2970 	tilePos.y = screenPos.y - (kPlatformWidth - 1) * kTileHeight;
2971 
2972 	u = kPlatformWidth - 1;
2973 	v = kPlatformWidth - 1;
2974 
2975 	relLoc.u = - relLoc.u - (kPlatformWidth - 1) * kTileUVSize;
2976 	relLoc.v = - relLoc.v - (kPlatformWidth - 1) * kTileUVSize;
2977 
2978 	for (int row = 0; row < 15; row++) {
2979 		if (tilePos.y > 0) {
2980 			int16   col = 0;
2981 			TilePoint pCoords(u, v, 0);
2982 
2983 			tilePos.x = x;
2984 			rLoc = relLoc;
2985 
2986 			if (length > x2) {
2987 				int16   offset = (length - x2) >> 1;
2988 
2989 				pCoords.u += offset;
2990 				pCoords.v -= offset;
2991 				rLoc.u -= offset * kTileUVSize;
2992 				rLoc.v += offset * kTileUVSize;
2993 				offset <<= 1;
2994 				col += offset;
2995 				tilePos.x += kTileDX * offset;
2996 			}
2997 
2998 			for (;
2999 			        col < length && tilePos.x <= right;
3000 			        col += 2,
3001 			        pCoords.u++,
3002 			        pCoords.v--,
3003 			        rLoc.u -= kTileUVSize,
3004 			        rLoc.v += kTileUVSize,
3005 			        tilePos.x += kTileWidth
3006 			    ) {
3007 				Platform    **pGet;
3008 
3009 				if (tilePos.x < 0) continue;
3010 				if (rLoc.u <= -kTileUVSize || rLoc.v <= -kTileUVSize)
3011 					continue;
3012 
3013 				for (pGet = pList; *pGet; pGet++) {
3014 					Platform    &p = **pGet;
3015 					int16       h,
3016 					            y;
3017 					TileInfo    *ti;
3018 					uint8       *imageData;
3019 					int16       trFlags;
3020 
3021 					ti =    p.fetchTile(
3022 					            g_vm->_currentMapNum,
3023 					            pCoords,
3024 					            origin,
3025 					            &imageData,
3026 					            h,
3027 					            trFlags);
3028 					if (ti == nullptr) continue;
3029 
3030 					//  Compute height of character above tile.
3031 
3032 					rLoc.z = relLoc.z - h;
3033 
3034 					if (maskRule(rLoc, *ti)) {
3035 						y = tilePos.y - h;
3036 
3037 						//  REM: Check for AltMask!!!
3038 
3039 						if (ti->attrs.height > 0
3040 						        && y < bottom + ti->attrs.height - 1) {
3041 							maskTile(&sMap,
3042 							         tilePos.x, y, ti->attrs.height,
3043 							         imageData);
3044 						}
3045 					}
3046 				}
3047 			}
3048 		}
3049 
3050 		if (row < 7) {
3051 			x -= kTileDX;
3052 			x2++;
3053 			length += 2;
3054 			u--;
3055 			relLoc.u += kTileUVSize;
3056 		} else {
3057 			x += kTileDX;
3058 			x2--;
3059 			length -= 2;
3060 			v--;
3061 			relLoc.v += kTileUVSize;
3062 		}
3063 
3064 		tilePos.y += kTileDY;
3065 	}
3066 }
3067 
maskMetaRow(gPixelMap & sMap,TilePoint coords,TilePoint relLoc,Point16 pos,uint16 roofID)3068 void maskMetaRow(
3069     gPixelMap       &sMap,
3070     TilePoint       coords,
3071     TilePoint       relLoc,
3072     Point16         pos,
3073     uint16          roofID) {
3074 	WorldMapData    *curMap = &mapList[g_vm->_currentMapNum];
3075 
3076 	int16           uOrg = coords.u * kPlatformWidth,
3077 	                vOrg = coords.v * kPlatformWidth;
3078 
3079 	Platform        *drawList[maxPlatforms + 1],
3080 	                **put = drawList;
3081 
3082 	int16           mapSizeMask = curMap->mapSize - 1,
3083 	                mapEdgeType = curMap->map->edgeType;
3084 	uint16          *mapData = curMap->map->mapData;
3085 
3086 	MetaTilePtr     *metaArray = curMap->metaList->_tiles;
3087 
3088 	int16           layerLimit;
3089 
3090 	for (;
3091 	        pos.x < sMap.size.x + kMetaDX;
3092 	        coords.u++,
3093 	        coords.v--,
3094 	        relLoc.u += kPlatUVSize,
3095 	        relLoc.v -= kPlatUVSize,
3096 	        uOrg += kPlatformWidth,
3097 	        vOrg -= kPlatformWidth,
3098 	        pos.x += kMetaTileWidth
3099 	    ) {
3100 		TilePoint       clipCoords;
3101 		int16           mtile = 0;
3102 		MetaTilePtr     metaPtr;
3103 
3104 		clipCoords.u = (uint16)coords.u % curMap->mapSize;
3105 		clipCoords.v = (uint16)coords.v % curMap->mapSize;
3106 		clipCoords.z = 0;
3107 
3108 		if (coords != clipCoords) {
3109 			switch (mapEdgeType) {
3110 			case edgeTypeBlack: // continue;
3111 			case edgeTypeFill0:
3112 				mtile = 0;
3113 				break;
3114 
3115 			case edgeTypeFill1:
3116 				mtile = 1;
3117 				break;
3118 
3119 			case edgeTypeRepeat:
3120 				coords.u = clamp(0, coords.u, mapSizeMask);
3121 				coords.v = clamp(0, coords.v, mapSizeMask);
3122 				mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited;
3123 				break;
3124 
3125 			case edgeTypeWrap:
3126 				mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited;
3127 				break;
3128 			}
3129 		} else
3130 			mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited;
3131 
3132 		if (mtile >= curMap->metaCount)
3133 			mtile = curMap->metaCount - 1;
3134 
3135 		metaPtr = metaArray[mtile];
3136 		put = drawList;
3137 
3138 		if (metaPtr == nullptr) return;
3139 
3140 		//  REM: Reject whole metatiles based on coords, based on
3141 		//  max height
3142 
3143 		layerLimit = maxPlatforms;
3144 
3145 		for (int i = 0; i < layerLimit; i++) {
3146 			Platform    *p;
3147 
3148 			if ((p = metaPtr->fetchPlatform(g_vm->_currentMapNum, i)) == nullptr)
3149 				continue;
3150 
3151 			if (p->roofRipID() == roofID && roofID > 0) break;
3152 
3153 			if (p->flags & plVisible) {
3154 				//  REM: precompute this later, by scanning the platform
3155 				//  for individual altitudes
3156 
3157 				p->highestPixel = kTileHeight * (kPlatformWidth - 1) + kMaxTileHeight + 192;
3158 
3159 				if (pos.y <= 0
3160 				        || pos.y - p->highestPixel >= sMap.size.y)
3161 					continue;
3162 
3163 				*put++ = p;
3164 			}
3165 		}
3166 		*put++ = nullptr;
3167 
3168 		if (drawList[0] != nullptr) {
3169 			maskPlatform(sMap, drawList, pos, relLoc, uOrg, vOrg);
3170 		}
3171 	}
3172 }
3173 
drawTileMask(const Point16 & sPos,gPixelMap & sMap,TilePoint loc,uint16 roofID=rippedRoofID)3174 void drawTileMask(
3175     const Point16 &sPos,
3176     gPixelMap &sMap,
3177     TilePoint loc,
3178     uint16 roofID = rippedRoofID) {
3179 	Point32     aPos;
3180 	Point32     viewPos;
3181 	Point16     metaPos;
3182 	TilePoint   baseCoords;
3183 	TilePoint   relLoc;
3184 
3185 	//  Compute bitmap's position in absolute terms on map
3186 
3187 	aPos.x = sPos.x + tileScroll.x - fineScroll.x;
3188 	aPos.y = sPos.y + tileScroll.y - fineScroll.y;
3189 
3190 	//  coordinates of the view window on the map in X,Y (in 16 pixel units)
3191 
3192 	viewPos.x = (aPos.x >> kTileDXShift)
3193 	            - (kPlatformWidth * mapList[g_vm->_currentMapNum].mapSize),
3194 	            viewPos.y = (kPlatformWidth
3195 	                         *   mapList[g_vm->_currentMapNum].mapSize << kTileDXShift)
3196 	                        -   aPos.y;
3197 
3198 	//  coordinates of the view window upper left corner in U,V
3199 
3200 	baseCoords.u = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) + viewPos.x)
3201 	               / (kPlatformWidth * 2);
3202 	baseCoords.v = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) - viewPos.x)
3203 	               / (kPlatformWidth * 2);
3204 	baseCoords.z = 0;
3205 
3206 	//  coordinates of current metatile (in X,Y), relative to screen
3207 
3208 	metaPos.x   = (baseCoords.u - baseCoords.v) * kMetaDX
3209 	              - viewPos.x * kTileDX;
3210 
3211 	metaPos.y   = viewPos.y
3212 	              - (baseCoords.u + baseCoords.v) * kMetaDY;
3213 
3214 	//  Compute where the object is relative to the metatile coords
3215 
3216 	relLoc.u = (baseCoords.u * kPlatUVSize) - loc.u;
3217 	relLoc.v = (baseCoords.v * kPlatUVSize) - loc.v;
3218 	relLoc.z = loc.z;
3219 
3220 	//  Loop through each horizontal row of metatiles
3221 	//  REM: also account for highest possible platform
3222 	//      (replace 256 constant with better value)
3223 
3224 	for (;
3225 	        metaPos.y < sMap.size.y + kMetaTileHeight * 4 ;
3226 	        baseCoords.u--,
3227 	        baseCoords.v--
3228 	    ) {
3229 		maskMetaRow(sMap, baseCoords, relLoc, metaPos, roofID);
3230 
3231 		metaPos.y += kMetaDY;
3232 		metaPos.x -= kMetaDX;
3233 
3234 		relLoc.u -= kPlatUVSize;
3235 
3236 		maskMetaRow(sMap, TilePoint(baseCoords.u - 1, baseCoords.v, 0),
3237 		            relLoc, metaPos, roofID);
3238 
3239 		metaPos.y += kMetaDY;
3240 		metaPos.x += kMetaDX;
3241 
3242 		relLoc.v -= kPlatUVSize;
3243 	}
3244 }
3245 
3246 /* ===================================================================== *
3247    Tile picking
3248  * ===================================================================== */
3249 
3250 #if DEBUG
3251 
3252 bool showTile = false;
3253 
3254 
3255 const uint16 lowerRightMask = 0x1111;
3256 const uint16 lowerLeftMask = 0x000F;
3257 
drawSubTiles(const TilePoint & tp,uint16 subTileMask,uint8 * cornerHeight)3258 inline void drawSubTiles(const TilePoint &tp, uint16 subTileMask, uint8 *cornerHeight) {
3259 	TilePoint   pt1,
3260 	            pt2;
3261 
3262 	uint8   subTileNo;
3263 	uint16  curSubTileMask;
3264 
3265 	pt1.z = 0;
3266 	pt2.z = 0;
3267 
3268 	for (subTileNo = 0; subTileNo < 16; subTileNo++) {
3269 		curSubTileMask = (1 << subTileNo);
3270 		if (curSubTileMask & subTileMask) {
3271 			// check if we're drawing tile to lower left
3272 			if (!((curSubTileMask >> 4) & subTileMask) || curSubTileMask & lowerLeftMask) {
3273 				pt1.u = pt2.u = (subTileNo & 0x0C);
3274 				pt1.v = ((subTileNo & 0x03) << 2);
3275 				pt2.v = pt1.v + 4;
3276 				pt1.z = ptHeight(pt1, cornerHeight);
3277 				pt2.z = ptHeight(pt2, cornerHeight);
3278 				TPLine(tp + pt1, tp + pt2);
3279 			}
3280 
3281 			// check if we're drawing tile to lower right
3282 			if (!((curSubTileMask >> 1) & subTileMask) || curSubTileMask & lowerRightMask) {
3283 				pt1.u = (subTileNo & 0x0C);
3284 				pt2.u = pt1.u + 4;
3285 				pt1.v = pt2.v = ((subTileNo & 0x03) << 2);
3286 				pt1.z = ptHeight(pt1, cornerHeight);
3287 				pt2.z = ptHeight(pt2, cornerHeight);
3288 				TPLine(tp + pt1, tp + pt2);
3289 			}
3290 
3291 			// draw upper right
3292 			pt1.u = pt2.u = (subTileNo & 0x0C) + 4;
3293 			pt1.v = ((subTileNo & 0x03) << 2);
3294 			pt2.v = pt1.v + 4;
3295 			pt1.z = ptHeight(pt1, cornerHeight);
3296 			pt2.z = ptHeight(pt2, cornerHeight);
3297 			TPLine(tp + pt1, tp + pt2);
3298 
3299 			// draw upper left
3300 			pt1.u = (subTileNo & 0x0C);
3301 			pt2.u = pt1.u + 4;
3302 			pt1.v = pt2.v = ((subTileNo & 0x03) << 2) + 4;
3303 			pt1.z = ptHeight(pt1, cornerHeight);
3304 			pt2.z = ptHeight(pt2, cornerHeight);
3305 			TPLine(tp + pt1, tp + pt2);
3306 		}
3307 	}
3308 }
3309 
showAbstractTile(const TilePoint & tp,TileInfo * ti)3310 void showAbstractTile(const TilePoint &tp, TileInfo *ti) {
3311 	TilePoint   workTp = tp;
3312 	uint8       *chPtr;
3313 	uint8       raisedCornerHeight[4] = { 0, 0, 0, 0 };
3314 
3315 	if (ti->combinedTerrainMask() & terrainRaised) {
3316 		if ((1L << ti->attrs.bgdTerrain) & terrainRaised) {
3317 			workTp.z += ti->attrs.terrainHeight;
3318 			chPtr = raisedCornerHeight;
3319 		} else
3320 			chPtr = ti->attrs.cornerHeight;
3321 		drawSubTiles(workTp, ~ti->attrs.terrainMask, chPtr);
3322 		workTp.z = tp.z;
3323 		if ((1L << ti->attrs.fgdTerrain) & terrainRaised) {
3324 			workTp.z += ti->attrs.terrainHeight;
3325 			chPtr = raisedCornerHeight;
3326 		} else
3327 			chPtr = ti->attrs.cornerHeight;
3328 		drawSubTiles(workTp, ti->attrs.terrainMask, chPtr);
3329 	} else
3330 		drawSubTiles(workTp, 0xFFFF, ti->attrs.cornerHeight);
3331 
3332 	TilePoint   pt1 = tp + TilePoint(0,  0, 0),
3333 	            pt2 = tp + TilePoint(16,  0, 0),
3334 	            pt3 = tp + TilePoint(0, 16, 0),
3335 	            pt4 = tp + TilePoint(16, 16, 0);
3336 	TPLine(pt1, pt2);
3337 	TPLine(pt1, pt3);
3338 	TPLine(pt2, pt4);
3339 	TPLine(pt3, pt4);
3340 }
3341 #endif
3342 
3343 //  Compute the picked position as if the mouse were pointing at the same
3344 //  level as the protagainist's feet.
pickTilePos(Point32 pos,const TilePoint & protagPos)3345 StaticTilePoint pickTilePos(Point32 pos, const TilePoint &protagPos) {
3346 	StaticTilePoint coords = {0, 0, 0};
3347 
3348 	pos.x += tileScroll.x;
3349 	pos.y += tileScroll.y + protagPos.z;
3350 
3351 	coords.set(XYToUV(pos).u, XYToUV(pos).v, protagPos.z);
3352 
3353 	return coords;
3354 }
3355 
3356 
3357 //  Inspect packed tile bitmap to determine if a pixel is opaque.
isTilePixelOpaque(int16 baseX,int16 baseY,int16 mapHeight,uint8 * td)3358 bool isTilePixelOpaque(int16 baseX, int16 baseY, int16 mapHeight, uint8 *td) {
3359 	bool    opaque;
3360 	int16   x = baseX + kTileDX,
3361 	        y = mapHeight - baseY,
3362 	        accum = 0;
3363 
3364 	if (y < 0 || y >= mapHeight) return false;
3365 
3366 	while (y) {
3367 		//  skip initial transparency
3368 		accum = *td;
3369 		td++;
3370 		while (accum < kTileWidth) {
3371 			//  skip opaque run
3372 			accum += *td;
3373 			td += *td + 1;
3374 
3375 			//  skip transparency
3376 			accum += *td;
3377 			td++;
3378 		}
3379 		y--;
3380 	}
3381 
3382 	//  skip initial transparency
3383 	x -= *td;
3384 	td++;
3385 	opaque = false;
3386 	while (x >= 0) {
3387 		x -= *td;
3388 		if (opaque) {
3389 			//  skip transparency
3390 			td++;
3391 			opaque = false;
3392 		} else {
3393 			//  skip opaque run
3394 			td += *td + 1;
3395 			opaque = true;
3396 		}
3397 	}
3398 
3399 	return opaque;
3400 }
3401 
3402 //-----------------------------------------------------------------------
3403 //	Return the exact TilePoint the mouse is pointing at on the specified
3404 //	tile.
3405 
pointOnTile(TileInfo * ti,const Point32 & tileRel,int16 h,const TilePoint & tCoords,TilePoint & pickCoords,TilePoint & floorCoords)3406 SurfaceType pointOnTile(TileInfo            *ti,
3407                         const Point32      &tileRel,
3408                         int16              h,
3409                         const TilePoint    &tCoords,
3410                         TilePoint          &pickCoords,
3411                         TilePoint          &floorCoords) {
3412 	Point32     relPos = tileRel;
3413 	TilePoint   subTile;
3414 	Point16     subTileRel;
3415 	int16       sMask;
3416 	int32       combinedMask;
3417 	uint16      yBound;
3418 	uint8       pointH;
3419 	uint16      subUVPointRel;
3420 	TilePoint   subUVPoint;
3421 	SurfaceType type = surfaceHoriz;
3422 
3423 	//  Get the tile's terrain mask
3424 	combinedMask = ti->attrs.testTerrain((int16)0xFFFF);
3425 
3426 	//  Adjust the relative X coordinate to ensure it is actually within
3427 	//  the tile's boundaries.
3428 	relPos.x = clamp(-kTileDX + 2, relPos.x, kTileDX - 1);
3429 
3430 	//  If the tile has no raised terrain
3431 	if (!(combinedMask & terrainRaised)) {
3432 		//  Calculate the position of the first point on tile to check.
3433 		if (relPos.x > 0) {
3434 			subUVPoint.u = relPos.x >> 1;
3435 			subUVPoint.v = 0;
3436 			subUVPointRel = relPos.y - (relPos.x >> 1) - h;
3437 		} else {
3438 			subUVPoint.u = 0;
3439 			subUVPoint.v = (-relPos.x + 1) >> 1;
3440 			subUVPointRel = relPos.y + (relPos.x >> 1) - h;
3441 		}
3442 
3443 		//  Compute the terrain hieght of the first point
3444 		pointH = ptHeight(subUVPoint, ti->attrs.cornerHeight);
3445 
3446 		while (subUVPoint.u < 16 &&
3447 		       subUVPoint.v < 16) {
3448 			if (subUVPointRel < pointH + (kSubTileDY * 2) / kSubTileSize) {
3449 				pickCoords = (tCoords << kTileUVShift);
3450 				pickCoords.u += subUVPoint.u;
3451 				pickCoords.v += subUVPoint.v;
3452 				pickCoords.z = h + pointH;
3453 				floorCoords = pickCoords;
3454 				break;
3455 			}
3456 
3457 			//  Test next point on tile
3458 			subUVPoint.u++;
3459 			subUVPoint.v++;
3460 			if (subUVPoint.u < 16 && subUVPoint.v < 16) {
3461 				subUVPointRel -= (kSubTileDY * 2) / kSubTileSize;
3462 
3463 				//  Compute the terrain height of point
3464 				pointH = ptHeight(subUVPoint, ti->attrs.cornerHeight);
3465 			} else {
3466 				//  If we've moved past the last point on the tile,
3467 				//  adjust the subUVPointRel to point to the top of the
3468 				//  last point checked.
3469 				subUVPoint.u--;
3470 				subUVPoint.v--;
3471 				subUVPointRel = pointH + ((kSubTileDY * 2) / kSubTileSize) - 1;
3472 			}
3473 		}
3474 
3475 	}
3476 
3477 	//  The tile has raised terrain
3478 
3479 	else {
3480 
3481 		int16   y;
3482 		TilePoint   lastRaisedSubTile(-1, -1, -1);
3483 
3484 		//  Compute the closest subtile which is directly
3485 		//  underneath, and the coodinates of the mouse pick
3486 		//  relative to that subtile.
3487 
3488 		if (relPos.x > 0) {
3489 			subTile.u = relPos.x >> kSubTileDXShift;
3490 			subTile.v = 0;
3491 			subTileRel.x = relPos.x - (subTile.u << kSubTileDXShift);
3492 			subTileRel.y = relPos.y - (subTile.u << kSubTileDYShift) - h;
3493 		} else {
3494 			subTile.u = 0;
3495 			subTile.v = (-relPos.x + 1) >> kSubTileDXShift;
3496 			subTileRel.x = relPos.x + (subTile.v << kSubTileDXShift);
3497 			subTileRel.y = relPos.y - (subTile.v << kSubTileDYShift) - h;
3498 		}
3499 
3500 		//  Compute the mask which represents the subtile
3501 		sMask = calcSubTileMask(subTile.u, subTile.v);
3502 		yBound = ABS(subTileRel.x >> 1);
3503 
3504 		while (subTileRel.y >= 0
3505 		        && subTile.u < 4
3506 		        && subTile.v < 4) {
3507 			if (ti->attrs.testTerrain(sMask) & terrainRaised) {
3508 				lastRaisedSubTile = subTile;
3509 
3510 				//  mouse is on side of raised section
3511 				if (subTileRel.y <
3512 				        ti->attrs.terrainHeight + yBound) {
3513 					pickCoords = (tCoords << kTileUVShift);
3514 					pickCoords.u += (subTile.u << kSubTileShift);
3515 					pickCoords.v += (subTile.v << kSubTileShift);
3516 					if (subTileRel.x > 1) {
3517 						pickCoords.u += yBound;
3518 						type = surfaceVertU;
3519 					} else if (subTileRel.x < 0) {
3520 						pickCoords.v += yBound;
3521 						type = surfaceVertV;
3522 					} else {
3523 						bool    subTileToRight = false,
3524 						        subTileToLeft = false;
3525 
3526 						if (subTile.u > 0
3527 						        && (ti->attrs.testTerrain(
3528 						                calcSubTileMask(
3529 						                    subTile.u - 1,
3530 						                    subTile.v))
3531 						            &   terrainRaised))
3532 							subTileToLeft = true;
3533 
3534 						if (subTile.v > 0
3535 						        && (ti->attrs.testTerrain(
3536 						                calcSubTileMask(
3537 						                    subTile.u,
3538 						                    subTile.v - 1))
3539 						            &   terrainRaised))
3540 							subTileToRight = true;
3541 
3542 						if ((subTileToRight && subTileToLeft)
3543 						        || (!subTileToRight && ! subTileToLeft)) {
3544 							if (subTileRel.x > 0) {
3545 								pickCoords.u += yBound;
3546 								type = surfaceVertU;
3547 							} else {
3548 								pickCoords.v += yBound;
3549 								type = surfaceVertV;
3550 							}
3551 						} else if (subTileToLeft) {
3552 							pickCoords.u += yBound;
3553 							type = surfaceVertU;
3554 						} else {
3555 							pickCoords.v += yBound;
3556 							type = surfaceVertV;
3557 						}
3558 					}
3559 					floorCoords.u = pickCoords.u - 1;
3560 					floorCoords.v = pickCoords.v - 1;
3561 					pickCoords.z = h + subTileRel.y - yBound;
3562 					if (subTile.u < 1 || subTile.v < 1)
3563 						floorCoords.z = h;
3564 					else
3565 						floorCoords.z = h +
3566 						                ptHeight(TilePoint(floorCoords.u & kTileUVMask,
3567 						                                   floorCoords.v & kTileUVMask,
3568 						                                   0),
3569 						                         ti->attrs.cornerHeight);
3570 					break;
3571 				}
3572 				//  mouse is on top of raised section
3573 				if (subTileRel.y <
3574 				        ti->attrs.terrainHeight + kSubTileDY * 2 - yBound) {
3575 					pickCoords = (tCoords << kTileUVShift);
3576 					y = subTileRel.y - ti->attrs.terrainHeight;
3577 					pickCoords.u += (subTile.u << kSubTileShift) +
3578 					                (((subTileRel.x >> 1) + y) >> 1);
3579 					pickCoords.v += (subTile.v << kSubTileShift) +
3580 					                ((y - (subTileRel.x >> 1)) >> 1);
3581 					pickCoords.z = h + ti->attrs.terrainHeight;
3582 					floorCoords = pickCoords;
3583 					break;
3584 				}
3585 			} else {
3586 				//  mouse is on unraised section
3587 
3588 				bool        foundPoint = false;
3589 
3590 				//  Calculate the position of the first point on subtile
3591 				//  to check.
3592 				if (subTileRel.x > 0) {
3593 					subUVPoint.u = subTileRel.x >> 1;
3594 					subUVPoint.v = 0;
3595 					subUVPointRel = subTileRel.y - (subTileRel.x >> 1);
3596 				} else {
3597 					subUVPoint.u = 0;
3598 					subUVPoint.v = (-subTileRel.x + 1) >> 1;
3599 					subUVPointRel = subTileRel.y + (subTileRel.x >> 1);
3600 				}
3601 
3602 				//  Compute the terrain hieght of the first point
3603 				pointH = ptHeight((subTile << 2) + subUVPoint, ti->attrs.cornerHeight);
3604 
3605 				while (subUVPoint.u < 4 &&
3606 				       subUVPoint.v < 4) {
3607 					if (subUVPointRel < pointH + (kSubTileDY * 2) / kSubTileSize) {
3608 						pickCoords = (tCoords << kTileUVShift);
3609 						pickCoords.u += (subTile.u << kSubTileShift) + subUVPoint.u;
3610 						pickCoords.v += (subTile.v << kSubTileShift) + subUVPoint.v;
3611 						pickCoords.z = h + pointH;
3612 						floorCoords = pickCoords;
3613 						foundPoint = true;
3614 						break;
3615 					}
3616 
3617 					//  Test next point on subtile
3618 					subUVPoint.u++;
3619 					subUVPoint.v++;
3620 					subUVPointRel -= (kSubTileDY * 2) / kSubTileSize;
3621 					pointH = ptHeight((subTile << kSubTileShift) + subUVPoint,
3622 					                  ti->attrs.cornerHeight);
3623 				}
3624 				if (foundPoint) break;
3625 
3626 			}
3627 
3628 			if (subTileRel.x & 0xFFFE) {  //  if subTileRel.x != 0 or 1
3629 				//  crabwalk up the subtiles
3630 				if (subTileRel.x > 0) {
3631 					subTileRel.x -= kSubTileDX;
3632 					subTile.u++;
3633 					sMask <<= kSubTileMaskUShift;
3634 				} else {
3635 					subTileRel.x += kSubTileDX;
3636 					subTile.v++;
3637 					sMask <<= kSubTileMaskVShift;
3638 				}
3639 				subTileRel.y -= kSubTileDY;
3640 			} else { //  subTileRel.x == 0 or 1
3641 				//  move up to the next vertical subtile
3642 				subTile.u++;
3643 				subTile.v++;
3644 				sMask <<= kSubTileMaskUShift + kSubTileMaskVShift;
3645 				subTileRel.y -= kSubTileDY * 2;
3646 			}
3647 			yBound = ABS(subTileRel.x >> 1);
3648 
3649 			if (subTile.u >= 4 || subTile.v >= 4) {
3650 				//  No subtile was found, so lets move the pointer.
3651 
3652 				if (lastRaisedSubTile.u != -1) {
3653 					//  If a raised subtile was already checked move the
3654 					//  pointer back down to the top of that subtile and
3655 					//  try again.
3656 					subTile = lastRaisedSubTile;
3657 
3658 					subTileRel.x = relPos.x -
3659 					               ((subTile.u - subTile.v) << kSubTileDXShift);
3660 					subTileRel.y = ti->attrs.terrainHeight + kSubTileDY * 2 -
3661 					               ABS(subTileRel.x >> 1) - 1;
3662 
3663 					sMask = calcSubTileMask(subTile.u, subTile.v);
3664 					yBound = ABS(subTileRel.x >> 1);
3665 				} else {
3666 					//  If there were no raised subtiles checked, move the
3667 					//  pointer laterally to the nearest raised subtile.
3668 					uint16 colMask;
3669 
3670 					int8    curSubTileCol,
3671 					        rightSubTileCol,
3672 					        leftSubTileCol,
3673 					        raisedCol = -4;
3674 
3675 
3676 					if (relPos.x & (kSubTileDX - 1) & 0xFFFE) {
3677 						if (relPos.x > 0) {
3678 							curSubTileCol = relPos.x >> kSubTileDXShift;
3679 							rightSubTileCol = curSubTileCol + 2;
3680 							leftSubTileCol = curSubTileCol - 1;
3681 							goto testLeft;
3682 						} else {
3683 							curSubTileCol =
3684 							    (relPos.x + kSubTileDX - 1) >> kSubTileDXShift;
3685 							leftSubTileCol = curSubTileCol - 2;
3686 							rightSubTileCol = curSubTileCol + 1;
3687 						}
3688 					} else {
3689 						curSubTileCol = relPos.x >> kSubTileDXShift;
3690 						rightSubTileCol = curSubTileCol + 1;
3691 						leftSubTileCol = curSubTileCol - 1;
3692 					}
3693 
3694 
3695 					//  Search subtile columns to the left and right for raised
3696 					//  terrain.
3697 					while (rightSubTileCol < 4 || leftSubTileCol > -4) {
3698 						if (rightSubTileCol < 4) {
3699 							if (rightSubTileCol > 0)
3700 								colMask = 0x8421 << (rightSubTileCol << 2);
3701 							else
3702 								colMask = 0x8421 >> ((-rightSubTileCol) << 2);
3703 
3704 							if (ti->attrs.testTerrain(colMask) & terrainRaised) {
3705 								raisedCol = rightSubTileCol;
3706 								subTileRel.x = -kSubTileDX + 2;
3707 								break;
3708 							}
3709 
3710 							rightSubTileCol++;
3711 						}
3712 
3713 testLeft:
3714 						if (leftSubTileCol > -4) {
3715 							if (leftSubTileCol > 0)
3716 								colMask = 0x8421 << (leftSubTileCol << 2);
3717 							else
3718 								colMask = 0x8421 >> ((-leftSubTileCol) << 2);
3719 
3720 							if (ti->attrs.testTerrain(colMask) & terrainRaised) {
3721 								raisedCol = leftSubTileCol;
3722 								subTileRel.x = kSubTileDX - 1;
3723 								break;
3724 							}
3725 
3726 							leftSubTileCol--;
3727 						}
3728 					}
3729 
3730 					//  if no raised terrain was found, give up
3731 					if (raisedCol == -4) break;
3732 
3733 					//  compute the number of subtiles in column
3734 					int8 subsInCol = 4 - ABS(raisedCol);
3735 					relPos.x = (raisedCol << kSubTileDXShift) + subTileRel.x;
3736 
3737 					if (raisedCol > 0) {
3738 						colMask = 0x0001 << (raisedCol << 2);
3739 						subTile.u = raisedCol;
3740 						subTile.v = 0;
3741 					} else {
3742 						colMask = 0x0001 << (-raisedCol);
3743 						subTile.u = 0;
3744 						subTile.v = -raisedCol;
3745 					}
3746 
3747 					//  test each subtile in column for first raised
3748 					//  subtile
3749 					while (subsInCol && !(ti->attrs.testTerrain(colMask) & terrainRaised)) {
3750 						subsInCol--;
3751 						subTile.u++;
3752 						subTile.v++;
3753 						colMask <<= 5;
3754 					}
3755 
3756 					//  subTile is now the first raised subtile in
3757 					//  column
3758 					subTileRel.y = relPos.y - ((subTile.u + subTile.v) * kSubTileDY) - h;
3759 					sMask = calcSubTileMask(subTile.u, subTile.v);
3760 					yBound = ABS(subTileRel.x >> 1);
3761 				}
3762 			}
3763 		}
3764 	}
3765 
3766 	return type;
3767 }
3768 
3769 
3770 //-----------------------------------------------------------------------
3771 //  Determine if picked point on tile is on an exposed surface.
3772 
pointOnHiddenSurface(const TilePoint & tileCoords,const TilePoint & pickCoords,SurfaceType surfaceType)3773 bool pointOnHiddenSurface(
3774     const TilePoint &tileCoords,
3775     const TilePoint &pickCoords,
3776     SurfaceType     surfaceType) {
3777 	assert(surfaceType == surfaceVertU || surfaceType == surfaceVertV);
3778 
3779 	WorldMapData    *curMap = &mapList[g_vm->_currentMapNum];
3780 
3781 	TilePoint       testCoords,
3782 	                mCoords,
3783 	                tCoords,
3784 	                origin;
3785 	MetaTile        *mt;
3786 
3787 	//  Determine pick point relative to base of tile
3788 	testCoords = pickCoords;
3789 	testCoords.u &= kTileUVMask;
3790 	testCoords.v &= kTileUVMask;
3791 
3792 	//  If picked point is not along edge of tile, then its not hidden
3793 	if ((surfaceType == surfaceVertV && testCoords.u != 0)
3794 	        || (surfaceType == surfaceVertU && testCoords.v != 0))
3795 		return false;
3796 
3797 	TileInfo    *adjTile;
3798 	TilePoint   adjTCoords = tileCoords;
3799 	uint16      adjSubMask;
3800 
3801 	//  Determine the tile coordinates of adjacent tile and the mask
3802 	//  of the subtile to test on that tile.
3803 	if (surfaceType == surfaceVertV) {
3804 		assert(testCoords.u == 0);
3805 		adjTCoords.u--;
3806 		adjSubMask = 0x1000 << (testCoords.v >> kSubTileShift);
3807 	} else {
3808 		assert(testCoords.v == 0);
3809 		adjTCoords.v--;
3810 		adjSubMask = 0x0008 << (testCoords.u & ~kSubTileMask);
3811 	}
3812 
3813 	mCoords = adjTCoords >> kPlatShift;
3814 
3815 	//  If metatile of adjacent tile does not exist, the pick point
3816 	//  is valid.
3817 	if ((mt = curMap->lookupMeta(mCoords)) == nullptr) return false;
3818 
3819 	tCoords.u = adjTCoords.u & kPlatMask;
3820 	tCoords.v = adjTCoords.v & kPlatMask;
3821 	tCoords.z = 0;
3822 	origin  = mCoords << kPlatShift;
3823 
3824 	int             i;
3825 
3826 	for (i = 0; i < maxPlatforms; i++) {
3827 		Platform    *p;
3828 		int16       h,
3829 		            trFlags;
3830 
3831 		if ((p = mt->fetchPlatform(g_vm->_currentMapNum, i)) == nullptr)
3832 			continue;
3833 
3834 		if (!(p->flags & plVisible) || platformRipped(p)) continue;
3835 
3836 		//  Fetch the tile at this location
3837 		adjTile =   p->fetchTile(
3838 		                g_vm->_currentMapNum,
3839 		                tCoords,
3840 		                origin,
3841 		                h,
3842 		                trFlags);
3843 
3844 		if (adjTile == nullptr) continue;
3845 
3846 		//  If current tile is higher or lower than the picked point
3847 		//  skip this tile.
3848 		if (h > pickCoords.z) continue;
3849 		if (h + adjTile->attrs.terrainHeight <= pickCoords.z)
3850 			continue;
3851 
3852 		//  If adjacent subtile is not raised, skip this tile
3853 		if (!(adjTile->attrs.testTerrain(adjSubMask) & terrainRaised))
3854 			continue;
3855 
3856 		break;
3857 	}
3858 
3859 	//  If all platforms have been checked, the pick point is valid
3860 	if (i >= maxPlatforms) return false;
3861 
3862 	return true;
3863 }
3864 
3865 //-----------------------------------------------------------------------
3866 //	Return the TilePoint at which the mouse it pointing
3867 
pickTile(Point32 pos,const TilePoint & protagPos,StaticTilePoint * floorResult,ActiveItemPtr * pickTAI)3868 StaticTilePoint pickTile(Point32 pos,
3869                    const TilePoint &protagPos,
3870                    StaticTilePoint *floorResult,
3871                    ActiveItemPtr *pickTAI) {
3872 	WorldMapData    *curMap = &mapList[g_vm->_currentMapNum];
3873 	StaticTilePoint result = {0, 0, 0};
3874 
3875 	TilePoint       pickCoords,
3876 	                floorCoords,
3877 	                pCoords,
3878 	                fCoords,
3879 	                coords,
3880 	                tileCoords;
3881 	Point32         relPos;                 // pick pos relative to tile.
3882 	int16           zMax,
3883 	                zMin,
3884 	                mag;
3885 	TilePoint       mCoords,
3886 	                tCoords,
3887 	                origin,
3888 	                testCoords,
3889 	                deltaP;
3890 	MetaTile        *mt;
3891 	ActiveItemPtr   bestTileTAI = nullptr;
3892 	TileInfo        *ti,
3893 	                *bestTile = nullptr;
3894 	uint8           *imageData;
3895 	int             i;
3896 
3897 #ifdef DAVIDR
3898 	TilePoint       bestTP;
3899 #endif
3900 
3901 	//  First, calculate the mouse click coords naively -- in other
3902 	//  words, assume that the Z coordinate of the click is the same
3903 	//  as the protagonist's feet. These coordinates will be used
3904 	//  if no tiles can be located which contain surfaces.
3905 
3906 	//  Calculate the mouse click position on the map
3907 	pos.x += tileScroll.x;
3908 	pos.y += tileScroll.y + protagPos.z;
3909 	pickCoords = XYToUV(pos);
3910 	pickCoords.z = protagPos.z;
3911 	floorCoords = pickCoords;
3912 
3913 	//  Now we do a different pick routine, one that considers the
3914 	//  mouse click as a "rifle shot" which penetrates the screen.
3915 	//  We'll attempt to check which surfaces are penetrated by the
3916 	//  shot.
3917 
3918 	//  First, move the pick point back down the floor
3919 	pos.y -= protagPos.z;
3920 
3921 	//  Compute the pick coordinates as if the protagonist were
3922 	//  at ground level.
3923 	coords = XYToUV(pos);
3924 	coords.z = 0;
3925 
3926 	//  Compute the coords of the middle of the current tile.
3927 	coords.u = (coords.u & ~kTileUVMask) + kTileUVSize / 2;
3928 	coords.v = (coords.v & ~kTileUVMask) + kTileUVSize / 2;
3929 
3930 	//  Since the protagonist has a limited ability to "step" up or
3931 	//  down levels, only search for surfaces which could be stepped
3932 	//  on by the protagonist.
3933 	mag = (coords - protagPos).quickHDistance();
3934 	zMin = protagPos.z - kMaxPickHeight - mag;
3935 	zMax = protagPos.z + kMaxPickHeight + mag;
3936 
3937 	//  Compute the coords of the actual tile that they clicked on.
3938 	tileCoords = coords >> kTileUVShift;
3939 
3940 	//  Compute the X and Y offset of the exact mouse click point
3941 	//  relative to the base of the tile.
3942 	relPos.x = pos.x - curMap->mapHeight - (tileCoords.u - tileCoords.v) * kTileDX;
3943 	relPos.y = curMap->mapHeight - pos.y - (tileCoords.u + tileCoords.v) * kTileDY;
3944 
3945 	//  Compute which metatile the click occured on, and the tile
3946 	//  within that metatile, and the origin coords of the metatile
3947 	mCoords = tileCoords >> kPlatShift;
3948 	tCoords.u = tileCoords.u & kPlatMask;
3949 	tCoords.v = tileCoords.v & kPlatMask;
3950 	tCoords.z = 0;
3951 	origin  = mCoords << kPlatShift;
3952 
3953 	//  Lookup the metatile
3954 	mt = curMap->lookupMeta(mCoords);
3955 
3956 	//  While we are less than the pick altitude
3957 	while (relPos.y < zMax + kTileDX + kMaxStepHeight - ABS(relPos.x >> 1)) {
3958 		//  If there is a metatile on this spot
3959 		if (mt != nullptr) {
3960 			//  Iterate through all platforms
3961 			for (i = 0; i < maxPlatforms; i++) {
3962 				Platform            *p;
3963 				StandingTileInfo    sti;
3964 
3965 				if ((p = mt->fetchPlatform(g_vm->_currentMapNum, i)) == nullptr)
3966 					continue;
3967 
3968 				if (platformRipped(p)) break;
3969 				if (!(p->flags & plVisible)) continue;
3970 
3971 				//  Fetch the tile at this location
3972 
3973 				ti =    p->fetchTAGInstance(
3974 				            g_vm->_currentMapNum,
3975 				            tCoords,
3976 				            origin,
3977 				            &imageData,
3978 				            sti);
3979 				if (ti == nullptr) continue;
3980 
3981 				//  Reject the tile if it's too low.
3982 				if (sti.surfaceHeight + ti->attrs.terrainHeight < zMin)
3983 					continue;
3984 
3985 				//  Reject the tile if it's too high.
3986 				if (sti.surfaceHeight > zMax + kMaxStepHeight) continue;
3987 
3988 				//  Reject the tile if mouse position is below lower tile
3989 				//  boundary
3990 				if ((relPos.y - sti.surfaceHeight) < ABS(relPos.x >> 1))
3991 					continue;
3992 
3993 				if (ti->attrs.height > 0) {
3994 					if (isTilePixelOpaque(relPos.x,
3995 					                      relPos.y - sti.surfaceHeight,
3996 					                      ti->attrs.height,
3997 					                      imageData)) {
3998 						SurfaceType surface;
3999 
4000 						//  Determine picked point on tile
4001 						surface = pointOnTile(ti,
4002 						                      relPos,
4003 						                      sti.surfaceHeight,
4004 						                      tCoords + origin,
4005 						                      pCoords,
4006 						                      fCoords);
4007 
4008 						if (sti.surfaceTAG == nullptr) {
4009 							if (surface != surfaceHoriz
4010 							        &&  pointOnHiddenSurface(tCoords + origin, pCoords, surface)) {
4011 								surface = surface == surfaceVertU ? surfaceVertV : surfaceVertU;
4012 							}
4013 
4014 							//  If pick point is on vertical surface
4015 							//  not facing protaganist, reject tile
4016 							if (surface == surfaceVertU &&  pCoords.v < protagPos.v)
4017 								continue;
4018 							if (surface == surfaceVertV &&  pCoords.u < protagPos.u)
4019 								continue;
4020 						}
4021 
4022 						pickCoords = pCoords;
4023 						floorCoords = fCoords;
4024 						bestTile = ti;
4025 						bestTileTAI = sti.surfaceTAG;
4026 					}
4027 				}
4028 			}
4029 		}
4030 
4031 		//  Crabwalk down through the tile positions
4032 		if (relPos.x < 0) {
4033 			tCoords.u--;
4034 			coords.u -= kTileUVSize;
4035 			if (tCoords.u < 0) {
4036 				tCoords.u = kPlatformWidth - 1;
4037 				mCoords.u--;
4038 				origin = mCoords << kPlatShift;
4039 				mt = curMap->lookupMeta(mCoords);
4040 			}
4041 			relPos.x += kTileDX;
4042 		} else {
4043 			tCoords.v--;
4044 			coords.v -= kTileUVSize;
4045 			if (tCoords.v < 0) {
4046 				tCoords.v = kPlatformWidth - 1;
4047 				mCoords.v--;
4048 				origin = mCoords << kPlatShift;
4049 				mt = curMap->lookupMeta(mCoords);
4050 			}
4051 			relPos.x -= kTileDX;
4052 		}
4053 		relPos.y += kTileDY;
4054 
4055 		//  Compute new altitude range based upon the tile position
4056 		//  relative to the protaganist's position.
4057 		zMin = protagPos.z - kMaxPickHeight - (coords - protagPos).quickHDistance();
4058 		zMax = protagPos.z + kMaxPickHeight + (coords - protagPos).quickHDistance();
4059 	}
4060 
4061 	result.set(pickCoords.u, pickCoords.v, pickCoords.z);
4062 
4063 	//  If no tile was found, return the default.
4064 	if (!bestTile) {
4065 		if (floorResult)
4066 			floorResult->set(floorCoords.u, floorCoords.v, floorCoords.z);
4067 		if (pickTAI)
4068 			*pickTAI = nullptr;
4069 		return result;
4070 	}
4071 
4072 	if (floorResult)
4073 		floorResult->set(floorCoords.u, floorCoords.v, floorCoords.z);
4074 	if (pickTAI)
4075 		*pickTAI = bestTileTAI;
4076 	return result;
4077 }
4078 
4079 /* ===================================================================== *
4080    Tile cycling
4081  * ===================================================================== */
4082 
cycleTiles(int32 delta)4083 void cycleTiles(int32 delta) {
4084 	if (delta <= 0) return;
4085 
4086 	for (int i = 0; i < cycleCount; i++) {
4087 		TileCycleData   &tcd = cycleList[i];
4088 
4089 		tcd.counter += tcd.cycleSpeed * delta;
4090 		if (tcd.counter >= 400) {
4091 			tcd.counter = 0;
4092 			tcd.currentState++;
4093 			if (tcd.currentState >= tcd.numStates)
4094 				tcd.currentState = 0;
4095 		}
4096 	}
4097 }
4098 
4099 struct TileCycleArchive {
4100 	int32       counter;
4101 	uint8       currentState;
4102 };
4103 
4104 //-----------------------------------------------------------------------
4105 //	Initialize the tile cycling state array
4106 
initTileCyclingStates(void)4107 void initTileCyclingStates(void) {
4108 	Common::SeekableReadStream *stream;
4109 	const int tileCycleDataSize = 40;
4110 
4111 	cycleCount = tileRes->size(cycleID) / tileCycleDataSize;
4112 	cycleList = new TileCycleData[cycleCount];
4113 
4114 	if (cycleList == nullptr)
4115 		error("Unable to load tile cycling data");
4116 
4117 	if ((stream = loadResourceToStream(tileRes, cycleID, "cycle list"))) {
4118 		for (int i = 0; i < cycleCount; ++i)
4119 			cycleList[i].load(stream);
4120 
4121 		debugC(2, kDebugLoading, "Loaded Cycles: cycleCount = %d", cycleCount);
4122 		delete stream;
4123 	}
4124 }
4125 
saveTileCyclingStates(Common::OutSaveFile * outS)4126 void saveTileCyclingStates(Common::OutSaveFile *outS) {
4127 	debugC(2, kDebugSaveload, "Saving TileCyclingStates");
4128 	outS->write("CYCL", 4);
4129 	CHUNK_BEGIN;
4130 	for (int i = 0; i < cycleCount; i++) {
4131 		debugC(3, kDebugSaveload, "Saving TileCyclingState %d", i);
4132 
4133 		out->writeSint32LE(cycleList[i].counter);
4134 		out->writeByte(cycleList[i].currentState);
4135 
4136 		debugC(4, kDebugSaveload, "... counter = %d", cycleList[i].counter);
4137 		debugC(4, kDebugSaveload, "... currentState = %d", cycleList[i].currentState);
4138 	}
4139 	CHUNK_END;
4140 }
4141 
loadTileCyclingStates(Common::InSaveFile * in)4142 void loadTileCyclingStates(Common::InSaveFile *in) {
4143 	debugC(2, kDebugSaveload, "Loading TileCyclingStates");
4144 
4145 	initTileCyclingStates();
4146 
4147 	for (int i = 0; i < cycleCount; i++) {
4148 		debugC(3, kDebugSaveload, "Loading TileCyclingState %d", i);
4149 		cycleList[i].counter = in->readSint32LE();
4150 		cycleList[i].currentState = in->readByte();
4151 
4152 		debugC(4, kDebugSaveload, "... counter = %d", cycleList[i].counter);
4153 		debugC(4, kDebugSaveload, "... currentState = %d", cycleList[i].currentState);
4154 	}
4155 }
4156 
4157 //-----------------------------------------------------------------------
4158 //	Cleanup the tile cycling state array
4159 
cleanupTileCyclingStates(void)4160 void cleanupTileCyclingStates(void) {
4161 	if (cycleList != nullptr) {
4162 		delete[] cycleList;
4163 		cycleList = nullptr;
4164 	}
4165 }
4166 
4167 /* ===================================================================== *
4168    objRoofID() -- determine which roof is above object
4169  * ===================================================================== */
4170 
objRoofID(GameObject * obj)4171 uint16 objRoofID(GameObject *obj) {
4172 	return objRoofID(obj, obj->getMapNum(), obj->getLocation());
4173 }
4174 
objRoofID(GameObject * obj,int16 objMapNum,const TilePoint & objCoords)4175 uint16 objRoofID(GameObject *obj, int16 objMapNum, const TilePoint &objCoords) {
4176 	WorldMapData    *objMap = &mapList[objMapNum];
4177 
4178 	TileRegion      objTileReg,
4179 	                objMetaReg;
4180 	int16           objHeight;
4181 	uint16          objRoofID = 0;
4182 	int             objRoofPlatNum = -1;
4183 	int16           metaU, metaV;
4184 
4185 	debugC(3, kDebugTiles, "objRoofID:");
4186 	debugC(3, kDebugTiles, "- obj = %p; objMapNum = %d; objCoords = (%d,%d,%d)",
4187 	       (void *)obj, objMapNum, objCoords.u, objCoords.v, objCoords.z);
4188 
4189 	objHeight = objCoords.z;
4190 
4191 	objTileReg.min.u = (objCoords.u - kSubTileSize) >> kTileUVShift;
4192 	objTileReg.min.v = (objCoords.v - kSubTileSize) >> kTileUVShift;
4193 	objTileReg.max.u = (objCoords.u + kSubTileSize + kTileUVMask) >> kTileUVShift;
4194 	objTileReg.max.v = (objCoords.v + kSubTileSize + kTileUVMask) >> kTileUVShift;
4195 
4196 	debugC(3, kDebugTiles, "objTileReg = ((%d,%d), (%d,%d))", objTileReg.min.u, objTileReg.min.v, objTileReg.max.u, objTileReg.max.v);
4197 
4198 	objMetaReg.min.u = objTileReg.min.u >> kPlatShift;
4199 	objMetaReg.min.v = objTileReg.min.v >> kPlatShift;
4200 	objMetaReg.max.u = (objTileReg.max.u + kPlatMask) >> kPlatShift;
4201 	objMetaReg.max.v = (objTileReg.max.v + kPlatMask) >> kPlatShift;
4202 
4203 	debugC(3, kDebugTiles, "objMetaReg = ((%d,%d), (%d,%d))", objMetaReg.min.u, objMetaReg.min.v, objMetaReg.max.u, objMetaReg.max.v);
4204 
4205 	for (metaU = objMetaReg.min.u;
4206 	        metaU < objMetaReg.max.u;
4207 	        metaU++) {
4208 		for (metaV = objMetaReg.min.v;
4209 		        metaV < objMetaReg.max.v;
4210 		        metaV++) {
4211 			MetaTilePtr     meta;
4212 
4213 			meta = objMap->lookupMeta(TilePoint(metaU, metaV, 0));
4214 
4215 			if (meta == nullptr) continue;
4216 
4217 			TilePoint       origin;
4218 			TileRegion      relTileReg;
4219 			int16           tileU, tileV;
4220 
4221 			origin.u = metaU << kPlatShift;
4222 			origin.v = metaV << kPlatShift;
4223 
4224 			//  Compute the tile region relative to the origin of this
4225 			//  meta tile clipped to this meta tile region
4226 			relTileReg.min.u = MAX(objTileReg.min.u - origin.u, 0);
4227 			relTileReg.min.v = MAX(objTileReg.min.v - origin.v, 0);
4228 			relTileReg.max.u = MIN(objTileReg.max.u - origin.u, (int)kPlatformWidth);
4229 			relTileReg.max.v = MIN(objTileReg.max.v - origin.v, (int)kPlatformWidth);
4230 
4231 			for (tileU = relTileReg.min.u;
4232 			        tileU < relTileReg.max.u;
4233 			        tileU++) {
4234 				for (tileV = relTileReg.min.v;
4235 				        tileV < relTileReg.max.v;
4236 				        tileV++) {
4237 					uint16          tileRoofID = 0;
4238 					int             i,
4239 					                tilePlatNum = -1;
4240 
4241 					for (i = 0; i < maxPlatforms; i++) {
4242 						Platform    *p;
4243 						TileInfo    *t;
4244 						int16       height;
4245 						int16       trFlags;
4246 
4247 						if ((p = meta->fetchPlatform(objMapNum, i)) == nullptr)
4248 							continue;
4249 
4250 						if (!(p->flags & plVisible) || p->roofRipID() <= 0)
4251 							continue;
4252 
4253 						t = p->fetchTile(
4254 						        objMapNum,
4255 						        TilePoint(tileU, tileV, 0),
4256 						        origin,
4257 						        height,
4258 						        trFlags);
4259 
4260 						if (t != nullptr && height > objHeight + 32) {
4261 							tileRoofID = p->roofRipID();
4262 							tilePlatNum = i;
4263 							break;
4264 						}
4265 					}
4266 
4267 					if (tileRoofID != 0) {
4268 						if (tilePlatNum > objRoofPlatNum) {
4269 							objRoofID = tileRoofID;
4270 							objRoofPlatNum = tilePlatNum;
4271 						}
4272 					} else
4273 						return 0;
4274 				}
4275 			}
4276 		}
4277 	}
4278 
4279 	return objRoofID;
4280 }
4281 
4282 //  Determine if roof over an object is ripped
objRoofRipped(GameObject * obj)4283 bool objRoofRipped(GameObject *obj) {
4284 	return obj->world() != nullptr && objRoofID(obj) == rippedRoofID;
4285 }
4286 
4287 //  Determine if two objects are both under the same roof
underSameRoof(GameObject * obj1,GameObject * obj2)4288 bool underSameRoof(GameObject *obj1, GameObject *obj2) {
4289 	return      obj1->world() != nullptr
4290 	            &&  obj2->world() != nullptr
4291 	            &&  objRoofID(obj1) == objRoofID(obj2);
4292 }
4293 
4294 /* ===================================================================== *
4295    Main view update routine
4296  * ===================================================================== */
4297 
4298 extern void testSprites(void);
4299 
updateMainDisplay(void)4300 void updateMainDisplay(void) {
4301 	static TilePoint lastViewLoc = TilePoint(0, 0, 0);
4302 
4303 	int32           deltaTime = gameTime - lastUpdateTime;
4304 
4305 	assert(isActor(viewCenterObject));
4306 
4307 	Actor           *viewActor = (Actor *)GameObject::objectAddress(
4308 	                                 viewCenterObject);
4309 	TilePoint       viewDiff;
4310 
4311 	assert(isWorld(viewActor->IDParent()));
4312 
4313 	GameWorld       *viewWorld = (GameWorld *)viewActor->parent();
4314 
4315 	if (viewWorld != currentWorld) {
4316 		currentWorld = viewWorld;
4317 		setCurrentMap(currentWorld->mapNum);
4318 	}
4319 
4320 	WorldMapData    *curMap = &mapList[g_vm->_currentMapNum];
4321 
4322 	StaticPoint32   scrollCenter,
4323 	                scrollDelta;
4324 	int32           scrollSpeed = defaultScrollSpeed,
4325 	                scrollDistance;
4326 
4327 	TilePoint       trackPos,
4328 	                mCoords;
4329 
4330 	lastUpdateTime = gameTime;
4331 
4332 
4333 	//  Get the coordinates of the object which the camera is tracking
4334 	getViewTrackPos(trackPos);
4335 	debugC(1, kDebugTiles, "trackPos = (%d,%d,%d)", trackPos.u, trackPos.v, trackPos.z);
4336 
4337 	viewDiff = trackPos - lastViewLoc;
4338 	lastViewLoc = trackPos;
4339 
4340 	if (ABS(viewDiff.u) > 8 * kPlatformWidth * kTileUVSize
4341 	        ||  ABS(viewDiff.v) > 8 * kPlatformWidth * kTileUVSize)
4342 		freeAllTileBanks();
4343 
4344 	//  Add current coordinates to map if they have mapping
4345 	markMetaAsVisited(trackPos);
4346 
4347 	//  Convert to XY coordinates.
4348 	targetScroll.x =
4349 	    ((trackPos.u - trackPos.v) << 1)
4350 	    +   curMap->mapHeight - kTileRectWidth / 2;
4351 	targetScroll.y =
4352 	    curMap->mapHeight - (trackPos.u + trackPos.v)
4353 	    -   trackPos.z - kTileRectHeight / 2 - 32;
4354 	debugC(1, kDebugTiles, "targetScroll = (%d,%d)", targetScroll.x, targetScroll.y);
4355 
4356 	//  Compute the delta vector between the current scroll position
4357 	//  and the desired scroll position, and also compute the
4358 	//  magnitude of that vector.
4359 	scrollDelta     = targetScroll - tileScroll;
4360 	scrollDistance  = quickDistance(scrollDelta);
4361 
4362 	//  If the magnitude of the scroll vector is large, then
4363 	//  go to a faster scrolling method.
4364 	if (scrollDistance <= slowThreshhold) scrollSpeed = 0;
4365 	else if (scrollDistance > snapThreshhold) scrollSpeed = snapScrollSpeed;
4366 	else if (scrollDistance > fastThreshhold)
4367 		scrollSpeed = scrollDistance - fastThreshhold;
4368 
4369 	//  If the scroll distance is less than the current scroll
4370 	//  speed, then simply set the current scroll position to
4371 	//  the desired scroll position. Otherwise, scale the scroll
4372 	//  vector to the approximate magnitude of the scroll speed.
4373 	if (scrollDistance <= scrollSpeed) tileScroll = targetScroll;
4374 	else tileScroll += (scrollDelta * scrollSpeed) / scrollDistance;
4375 
4376 	//  Compute the fine scrolling offsets
4377 	fineScroll.x = tileScroll.x & kTileDXMask;
4378 	fineScroll.y = 0;
4379 
4380 	//  Compute the center of the screen in (u,v) coords.
4381 	scrollCenter.x = tileScroll.x + kTileRectWidth  / 2;
4382 	scrollCenter.y = tileScroll.y + kTileRectHeight / 2;
4383 	viewCenter.set(XYToUV(scrollCenter).u,
4384 	               XYToUV(scrollCenter).v,
4385 	               0);
4386 
4387 	//  Compute the largest U/V rectangle which completely
4388 	//  encloses the view area, and convert to sector coords.
4389 	buildRoofTable();
4390 
4391 	mCoords.u = trackPos.u >> (kTileUVShift + kPlatShift);
4392 	mCoords.v = trackPos.v >> (kTileUVShift + kPlatShift);
4393 	mCoords.z = 0;
4394 
4395 	//  If trackPos has crossed a metatile boundry, rebuild object
4396 	//  ripping tables
4397 	if (mCoords != ripTableCoords) buildRipTables();
4398 
4399 	//  Build the list of all displayed objects
4400 	buildDisplayList();
4401 	updateObjectAppearances(deltaTime);      // for object with no motion task.
4402 
4403 	//  Draw tiles onto back buffer
4404 
4405 	// Lock DirectDraw
4406 
4407 	//#define DIRECTDRAW
4408 	drawMainDisplay();
4409 	cycleTiles(deltaTime);
4410 }
4411 
drawMainDisplay(void)4412 void drawMainDisplay(void) {
4413 
4414 
4415 	// draws tiles to g_vm->_tileDrawMap.data
4416 	drawMetaTiles(g_vm->_tileDrawMap);
4417 
4418 	//  Draw sprites onto back buffer
4419 	drawDisplayList();
4420 
4421 	//  Render the text if any
4422 	updateSpeech();
4423 
4424 	Rect16 rect(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight);
4425 
4426 	//  Render floating windows
4427 	drawFloatingWindows(g_vm->_backPort,
4428 	                    Point16(kTileRectX - fineScroll.x, kTileRectY),
4429 	                    rect);
4430 	//  Blit it all onto the screen
4431 	drawPage->writePixels(
4432 	    rect,
4433 	    g_vm->_tileDrawMap.data
4434 	    + fineScroll.x
4435 	    + fineScroll.y * g_vm->_tileDrawMap.size.x,
4436 	    g_vm->_tileDrawMap.size.x);
4437 
4438 	updateFrameCount();
4439 }
4440 
4441 #if DEBUG
4442 
4443 #include "actor.h"
4444 
ShowObjectSection(GameObject * obj)4445 void ShowObjectSection(GameObject *obj) {
4446 	ProtoObj        *proto = obj->proto();
4447 	int16           crossSection = proto->crossSection;
4448 	TilePoint       tp = obj->getLocation(),
4449 	                tp1,
4450 	                tp2,
4451 	                tp3,
4452 	                tp4;
4453 
4454 	tp1 = tp + TilePoint(crossSection,  crossSection, 0);
4455 	tp2 = tp + TilePoint(-crossSection,  crossSection, 0);
4456 	tp3 = tp + TilePoint(-crossSection, -crossSection, 0);
4457 	tp4 = tp + TilePoint(crossSection, -crossSection, 0);
4458 
4459 	TPLine(tp1, tp2);
4460 	TPLine(tp2, tp3);
4461 	TPLine(tp3, tp4);
4462 	TPLine(tp4, tp1);
4463 }
4464 
4465 #endif
4466 
4467 //-----------------------------------------------------------------------
4468 //  mark this and surrounding metatiles as visited
4469 
4470 const int           mappingRadius = 2;
4471 
markMetaAsVisited(const TilePoint & pt)4472 void markMetaAsVisited(const TilePoint &pt) {
4473 	//  If (they have cartography)
4474 	{
4475 		WorldMapData    *curMap = &mapList[g_vm->_currentMapNum];
4476 		uint16          *mapData = curMap->map->mapData;
4477 
4478 		TilePoint       metaCoords = pt >> (kTileUVShift + kPlatShift);
4479 		int32           minU = MAX(metaCoords.u - mappingRadius, 0),
4480 		                maxU = MIN(metaCoords.u + mappingRadius, curMap->mapSize - 1),
4481 		                minV = MAX(metaCoords.v - mappingRadius, 0),
4482 		                maxV = MIN(metaCoords.v + mappingRadius, curMap->mapSize - 1),
4483 		                u, v;
4484 
4485 		for (u = minU; u <= maxU; u++) {
4486 			for (v = minV; v <= maxV; v++) {
4487 				if ((u == minU || u == maxU) && (v == minV || v == maxV)) continue;
4488 				mapData[u * curMap->mapSize + v] |= metaTileVisited;
4489 			}
4490 		}
4491 	}
4492 }
4493 
4494 /* ===================================================================== *
4495    Quick distance calculation subroutines
4496  * ===================================================================== */
4497 
quickDistance(const Point16 & p)4498 int16 quickDistance(const Point16 &p) {
4499 	int16       ax = ABS(p.x),
4500 	            ay = ABS(p.y);
4501 
4502 	if (ax > ay) return ax + (ay >> 1);
4503 	else return ay + (ax >> 1);
4504 }
4505 
quickDistance(const Point32 & p)4506 int32 quickDistance(const Point32 &p) {
4507 	int32       ax = ABS(p.x),
4508 	            ay = ABS(p.y);
4509 
4510 	if (ax > ay) return ax + (ay >> 1);
4511 	else return ay + (ax >> 1);
4512 }
4513 
magnitude(void)4514 int16 TilePoint::magnitude(void) {
4515 	int16       au = ABS(u),
4516 	            av = ABS(v),
4517 	            az = ABS(z);
4518 
4519 	if (az > au && az > av) return az + ((au + av) >> 1);
4520 	if (au > av)            return au + ((az + av) >> 1);
4521 	else                    return av + ((au + az) >> 1);
4522 }
4523 
4524 //  Determine the distance between a point and a line
lineDist(const TilePoint & p1,const TilePoint & p2,const TilePoint & m)4525 uint16 lineDist(
4526     const TilePoint &p1,
4527     const TilePoint &p2,
4528     const TilePoint &m) {
4529 	const int16     lineDistSlop = kTileUVSize * 4;
4530 	const int16     lineFar = maxint16;
4531 
4532 	int16       u = m.u,
4533 	            v = m.v;
4534 	int16       u2 = p2.u - p1.u,
4535 	            v2 = p2.v - p1.v;
4536 	int16       dist;
4537 
4538 	u -= p1.u;
4539 	v -= p1.v;
4540 
4541 	if (u2 < 0) {
4542 		u2 = -u2;
4543 		u = -u;
4544 	}
4545 
4546 	if (v2 < 0) {
4547 		v2 = -v2;
4548 		v = -v;
4549 	}
4550 
4551 	if (u < -lineDistSlop
4552 	        ||  u > u2 + lineDistSlop
4553 	        ||  v < -lineDistSlop
4554 	        ||  v > v2 + lineDistSlop)
4555 		return lineFar;
4556 
4557 	if (u2 != 0 && v2 != 0) {
4558 		if (u2 > v2)
4559 			dist = u - v2 * v / u2;
4560 		else
4561 			dist = v - u2 * u / v2;
4562 	} else if (u2 == 0)
4563 		dist = v;
4564 	else // here v2 == 0
4565 		dist = u;
4566 
4567 	return ABS(dist);
4568 }
4569 
4570 } // end of namespace Saga2
4571