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