/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * aint32 with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * Based on the original sources * Faery Tale II -- The Halls of the Dead * (c) 1993-1996 The Wyrmkeep Entertainment Co. */ #define FORBIDDEN_SYMBOL_ALLOW_ALL // FIXME: Remove #include "common/debug.h" #include "graphics/surface.h" #include "saga2/std.h" #include "saga2/blitters.h" #include "saga2/hresmgr.h" #include "saga2/objects.h" #include "saga2/tile.h" #include "saga2/oncall.h" #include "saga2/input.h" #include "saga2/cmisc.h" #include "saga2/setup.h" #include "saga2/tagnoise.h" #include "saga2/player.h" #include "saga2/mapfeatr.h" #include "saga2/savefile.h" // Include files needed for SAGA script dispatch #include "saga2/script.h" #include "saga2/methods.r" // generated by SAGA namespace Saga2 { extern void writeLog(char *str); void PlayModeSetup(); void initBackPanel(); #define TATLOG 0 /* ===================================================================== * Constants * ===================================================================== */ const uint32 tileTerrainID = MKTAG('T', 'E', 'R', 0), platformID = MKTAG('P', 'L', 'T', 0), metaID = MKTAG('M', 'E', 'T', 0), mapID = MKTAG('M', 'A', 'P', 0), tagID = MKTAG('T', 'A', 'G', 0), tagDataID = MKTAG('T', 'G', 'D', 0), tagStateID = MKTAG('T', 'S', 'T', 0), assocID = MKTAG('A', 'S', 'C', 0), cycleID = MKTAG('C', 'Y', 'C', 'L'); // Scrolling Speed constants const int slowScrollSpeed = 6, snapScrollSpeed = maxint32, slowThreshhold = 16, // fastThreshhold = 100, fastThreshhold = 16, snapThreshhold = 400; const TilePoint Nowhere((int16)minint16, (int16)minint16, (int16)minint16); const MetaTileID NoMetaTile(nullID, nullID); const ActiveItemID NoActiveItem(0, activeItemIndexNullID); enum SurfaceType { surfaceHoriz, // Level surface surfaceVertV, // Vertical surface, parallel to V axis surfaceVertU, // Vertical surface, parallel to U axis }; void updateSpeech(); void setAreaSound(const TilePoint &baseCoords); /* ===================================================================== * Bank switching interface * ===================================================================== */ TileBankPtr tileBanks[maxBanks]; extern HandleArray tileImageBanks; void updateHandleRefs(const TilePoint &pt); //, StandingTileInfo *stiResult ) void updateFrameCount(void); /* ===================================================================== * Prototypes * ===================================================================== */ hResContext *tileRes; // tile resource handle void drawPlatform( Platform **pList, // platforms to draw Point16 screenPos, // screen position int16 uOrg, // for TAG search int16 vOrg); // for TAG search bool isTilePixelOpaque(int16 baseX, // X coordinate relative to base int16 baseY, // Y coordinate relative to base int16 mapHeight, // pixel height of tile's bitmap uint8 *td); // packed tile bitmap SurfaceType pointOnTile(TileInfo *ti, const Point32 &tileRel, int16 h, const TilePoint &tCoords, TilePoint &pickCoords, TilePoint &floorCoords); bool validSurface(const TilePoint &tileCoords, const TilePoint &pickCoords); void markMetaAsVisited(const TilePoint &pt); /* ===================================================================== * Prototypes * ===================================================================== */ extern void buildDisplayList(void); extern void drawDisplayList(void); //extern void evaluateActorNeeds( int32 ); extern void updateActorTasks(void); extern void updateObjectAppearances(int32 deltaTime); extern void getViewTrackPos(TilePoint &tp); extern GameObject *getViewCenterObject(void); extern TilePoint centerActorCoords(void); void freeAllTileBanks(void); void cycleTiles(uint32 elapsed); #if DEBUG void TPLine(const TilePoint &start, const TilePoint &stop); #endif void drawFloatingWindows(gPort &, const Point16 &, const Rect16 &clip); /* ===================================================================== * Imports * ===================================================================== */ extern gMousePointer pointer; // the actual pointer extern gMouseState mouseState; extern gPort backPort; extern int16 worldCount; // Used as map count as well extern ObjectID viewCenterObject; // ID of object that view tracks /* ===================================================================== * Exports * ===================================================================== */ Rect16 tileRect(tileRectX, tileRectY, tileRectWidth, tileRectHeight); gPixelMap tileDrawMap; /* ===================================================================== * Tile structure management * ===================================================================== */ int16 cycleCount; uint16 rippedRoofID; TilePoint ripTableCoords = Nowhere; static RipTable ripTableList[25]; WorldMapData *mapList; // master map data array byte **stateArray; // Array of active item instance // state arrays CyclePtr cycleList; // list of tile cycling info // Platform caching management const int platformCacheSize = 256; Common::List platformLRU; // least recently used PlatformCacheEntry platformCache[platformCacheSize]; /* ===================================================================== * View state * ===================================================================== */ int16 defaultScrollSpeed = slowScrollSpeed; Point32 tileScroll, // current tile scroll pos // backScroll, // quantized scroll pos targetScroll; // where scroll going to Point16 fineScroll; TilePoint viewCenter; // coordinates of view on map // These two variables define which sectors overlap the view rect. TilePoint minSector, maxSector; int16 currentMapNum; // which map is in use int16 lastMapNum; int32 lastUpdateTime; // time of last display update gPort mouseSavePort; // for tweaking mouse backsave /* also: -- height of center character -- what map we are on. */ BankBits LoadedBanks; // what banks are loaded? /* ===================================================================== * ActiveItemID member functions * ===================================================================== */ #if DEBUG ActiveItemID::ActiveItemID(int16 m, int16 i) : val((m << activeItemMapShift) | (i & activeItemIndexMask)) { assert(m < 0x8); assert((uint16)i <= activeItemIndexNullID); } void ActiveItemID::setMapNum(int16 m) { assert(m < 0x8); val &= ~activeItemMapMask; val |= (m << activeItemMapShift); } void ActiveItemID::setIndexNum(int16 i) { assert((uint16)i <= activeItemIndexNullID); val &= ~activeItemIndexMask; val |= i & activeItemIndexMask; } #endif /* ===================================================================== * Finds the address of a tile associated with a TileID * ===================================================================== */ //----------------------------------------------------------------------- // Return the address of a tile's TileInfo structure given that tile's ID TileInfo *TileInfo::tileAddress(TileID id) { TileInfo *ti; TileBankPtr tbh; int16 tileBank, tileNum; if (id == 0) return nullptr; TileID2Bank(id, tileBank, tileNum); if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr; ti = tbh->tile(tileNum); if (ti->attrs.cycleRange > 0) { TileCycleData &tcd = cycleList[ti->attrs.cycleRange - 1]; TileID2Bank(tcd.cycleList[tcd.currentState], tileBank, tileNum); if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr; ti = tbh->tile(tileNum); } return ti; } //----------------------------------------------------------------------- // Return the address of a tile's TileInfo structure and the address of // the tile's image data given that tile's ID TileInfo *TileInfo::tileAddress(TileID id, uint8 **imageData) { TileInfo *ti; TileBankPtr tbh; byte *tibh; int16 tileBank, tileNum; if (id == 0) return nullptr; TileID2Bank(id, tileBank, tileNum); debugC(3, kDebugTiles, "TileID2Bank: id = %d, tileBank = %d, tileNum = %d", id, tileBank, tileNum); if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr; ti = tbh->tile(tileNum); if (ti->attrs.cycleRange > 0) { TileCycleData &tcd = cycleList[ti->attrs.cycleRange - 1]; TileID2Bank(tcd.cycleList[tcd.currentState], tileBank, tileNum); if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr; ti = tbh->tile(tileNum); } if (ti != nullptr) { if ((tibh = tileImageBanks[tileBank]) != nullptr) *imageData = &tibh[ti->offset]; else *imageData = nullptr; } else *imageData = nullptr; return ti; } /* ===================================================================== * ActiveItem member functions * ===================================================================== */ //----------------------------------------------------------------------- // Return the map number of this active item int16 ActiveItem::getMapNum(void) { int16 mapNum; // Use a brute force search of all of the maps' active item lists // to determine which map this active item is on. for (mapNum = 0; mapNum < worldCount; mapNum++) { WorldMapData *mapData = &mapList[mapNum]; // Determine if the active item in on this map's list if (_parent == mapData->activeItemList) break; } return mapNum; } //----------------------------------------------------------------------- // Return the world context for a TAG ObjectID ActiveItem::getInstanceContext(void) { int16 mn = getMapNum(); assert(mn >= 0 && mn < 3); if (mn < 0 || mn > 2) return Nothing; WorldMapData &map = mapList[mn]; // master map data array return map.worldID; } //----------------------------------------------------------------------- // Return the Location for a TAG Location ActiveItem::getInstanceLocation(void) { return Location(_data.instance.u << kTileUVShift, _data.instance.v << kTileUVShift, _data.instance.h << kTileZShift, getInstanceContext()); } //----------------------------------------------------------------------- // Return the address of an active item, given its ID ActiveItem *ActiveItem::activeItemAddress(ActiveItemID id) { return id.getIndexNum() != activeItemIndexNullID ? mapList[id.getMapNum()].activeItemList->_items[id.getIndexNum()] : nullptr; } //----------------------------------------------------------------------- // Return this active item's ID ActiveItemID ActiveItem::thisID(void) { int16 mapNum = getMapNum(); return ActiveItemID(mapNum, _index); } //----------------------------------------------------------------------- // Return this active item's ID ActiveItemID ActiveItem::thisID(int16 mapNum) { return ActiveItemID(mapNum, _index); } //----------------------------------------------------------------------- // use() function for ActiveItem instance bool ActiveItem::use(ObjectID enactor) { // Get a pointer to the active item group ActiveItem *groupPtr = activeItemAddress( ActiveItemID( getMapNum(), _data.instance.groupID)); return groupPtr->use(this, enactor); } //----------------------------------------------------------------------- // trigger() function for ActiveItem instance bool ActiveItem::trigger(ObjectID enactor, ObjectID objID) { // Get a pointer to the active item group ActiveItem *groupPtr = activeItemAddress( ActiveItemID( getMapNum(), _data.instance.groupID)); return groupPtr->trigger(this, enactor, objID); } //----------------------------------------------------------------------- // release() function for ActiveItem instance bool ActiveItem::release(ObjectID enactor, ObjectID objID) { // Get a pointer to the active item group ActiveItem *groupPtr = activeItemAddress( ActiveItemID( getMapNum(), _data.instance.groupID)); return groupPtr->release(this, enactor, objID); } //----------------------------------------------------------------------- // acceptLockToggle() function for ActiveItem instance bool ActiveItem::acceptLockToggle(ObjectID enactor, uint8 keyCode) { // Get a pointer to the active item group ActiveItem *groupPtr = activeItemAddress( ActiveItemID( getMapNum(), _data.instance.groupID)); return groupPtr->acceptLockToggle(this, enactor, keyCode); } //----------------------------------------------------------------------- // inRange() function for ActiveItem instance bool ActiveItem::inRange(const TilePoint &loc, int16 range) { // Get a pointer to the active item group ActiveItem *groupPtr = activeItemAddress( ActiveItemID( getMapNum(), _data.instance.groupID)); return loc.u >= _data.instance.u - range && loc.v >= _data.instance.v - range && loc.u < _data.instance.u + groupPtr->_data.group.uSize + range && loc.v < _data.instance.v + groupPtr->_data.group.vSize + range; } //----------------------------------------------------------------------- // TAG noise player void ActiveItem::playTAGNoise(ActiveItem *ai, int16 tagNoiseID) { playSoundAt(MKTAG('T', 'A', 'G', tagNoiseID), ai->getInstanceLocation()); } //----------------------------------------------------------------------- // use() function for ActiveItem group bool ActiveItem::use(ActiveItem *ins, ObjectID enactor) { int16 mapNum = getMapNum(); uint16 state = ins->getInstanceState(mapNum); scriptCallFrame scf; if (ins->_data.scriptClassID != 0) { // Set up the arguments we want to pass to the script scf.invokedTAI = ins->thisID(); scf.enactor = enactor; scf.directTAI = scf.invokedTAI; scf.indirectObject = Nothing; // Fill in other params with data from TAG struct scf.value = ins->_data.instance.worldNum; scf.coords.u = ins->_data.instance.targetU; scf.coords.v = ins->_data.instance.targetV; scf.coords.z = ins->_data.instance.targetZ; if (runTagMethod( scf.invokedTAI, Method_TileActivityInstance_onUse, scf) == scriptResultFinished) { if (scf.returnVal != actionResultNotDone) return scf.returnVal == actionResultSuccess; } } switch (ins->builtInBehavior()) { case builtInLamp: ins->setInstanceState(mapNum, !state); break; case builtInDoor: if (state < 3) { if (!ins->isLocked()) { TileActivityTask::openDoor(*ins); playTAGNoise(ins, DEFAULT_OPEN); } else { playTAGNoise(ins, DOOR_LOCKED_NO_KEY); return false; } } else { TileActivityTask::closeDoor(*ins); playTAGNoise(ins, DEFAULT_CLOSE); } break; } return true; } //----------------------------------------------------------------------- // trigger() function for ActiveItem group bool ActiveItem::trigger(ActiveItem *ins, ObjectID enactor, ObjectID objID) { assert(objID != Nothing); GameObject *obj = GameObject::objectAddress(objID); GameWorld *world = (GameWorld *)GameObject::objectAddress( mapList[getMapNum()].worldID); TileRegion instanceRegion; ActiveItemID instanceID = ins->thisID(); scriptCallFrame scf; // Trap transporters to only react to the center actor if (ins->builtInBehavior() == builtInTransporter && (!isActor(obj) || (Actor *)obj != getCenterActor())) return true; if (ins->_data.scriptClassID != 0) { // Set up the arguments we want to pass to the script scf.invokedTAI = ins->thisID(); scf.enactor = enactor; scf.directTAI = scf.invokedTAI; scf.indirectObject = objID; if (runTagMethod( scf.invokedTAI, Method_TileActivityInstance_onCanTrigger, scf) == scriptResultFinished) { if (!scf.returnVal) return true; } } // Mark the object as triggering this TAG obj->setTriggeringTAG(true); instanceRegion.min.u = ins->_data.instance.u << kTileUVShift; instanceRegion.min.v = ins->_data.instance.v << kTileUVShift; instanceRegion.max.u = instanceRegion.min.u + (_data.group.uSize << kTileUVShift); instanceRegion.max.v = instanceRegion.min.v + (_data.group.vSize << kTileUVShift); RegionalObjectIterator iter( world, instanceRegion.min, instanceRegion.max); GameObject *testObject; for (iter.first(&testObject); testObject != nullptr; iter.next(&testObject)) { if (testObject != obj && testObject->_data.currentTAG == instanceID && testObject->isTriggeringTAG()) return true; } // if ( proto->mass < group.triggerWeight ) return false; if (ins->_data.scriptClassID != 0) { // Set up the arguments we want to pass to the script scf.invokedTAI = ins->thisID(); scf.enactor = enactor; scf.directTAI = scf.invokedTAI; scf.indirectObject = objID; // Fill in other params with data from TAG struct scf.value = ins->_data.instance.worldNum; scf.coords.u = ins->_data.instance.targetU; scf.coords.v = ins->_data.instance.targetV; scf.coords.z = ins->_data.instance.targetZ; if (runTagMethod( scf.invokedTAI, Method_TileActivityInstance_onTrigger, scf) == scriptResultFinished) { if (scf.returnVal != actionResultNotDone) return scf.returnVal == actionResultSuccess; } } switch (ins->builtInBehavior()) { case builtInTransporter: //playTAGNoise(BEAM_ME_UP); { Actor *a; if (isActor(obj) && (a = (Actor *)obj) == getCenterActor()) { transportCenterBand( Location( (ins->_data.instance.targetU << kTileUVShift) + kTileUVSize / 2, (ins->_data.instance.targetV << kTileUVShift) + kTileUVSize / 2, (int16)ins->_data.instance.targetZ << 3, ins->_data.instance.worldNum + WorldBaseID)); } } break; } return true; } //----------------------------------------------------------------------- // release() function for ActiveItem group bool ActiveItem::release(ActiveItem *ins, ObjectID enactor, ObjectID objID) { assert(objID != Nothing); GameObject *obj = GameObject::objectAddress(objID); GameWorld *world = (GameWorld *)GameObject::objectAddress( mapList[getMapNum()].worldID); TileRegion instanceRegion; ActiveItemID instanceID = ins->thisID(); scriptCallFrame scf; if (obj->isTriggeringTAG()) obj->setTriggeringTAG(false); instanceRegion.min.u = ins->_data.instance.u << kTileUVShift; instanceRegion.min.v = ins->_data.instance.v << kTileUVShift; instanceRegion.max.u = instanceRegion.min.u + (_data.group.uSize << kTileUVShift); instanceRegion.max.v = instanceRegion.min.v + (_data.group.vSize << kTileUVShift); RegionalObjectIterator iter( world, instanceRegion.min, instanceRegion.max); GameObject *testObject; for (iter.first(&testObject); testObject != nullptr; iter.next(&testObject)) { if (testObject != obj && testObject->_data.currentTAG == instanceID && testObject->isTriggeringTAG()) return true; } if (ins->_data.scriptClassID != 0) { // Set up the arguments we want to pass to the script scf.invokedTAI = ins->thisID(); scf.enactor = enactor; scf.directTAI = scf.invokedTAI; scf.indirectObject = objID; // Fill in other params with data from TAG struct scf.value = ins->_data.instance.worldNum; scf.coords.u = ins->_data.instance.targetU; scf.coords.v = ins->_data.instance.targetV; scf.coords.z = ins->_data.instance.targetZ; if (runTagMethod( scf.invokedTAI, Method_TileActivityInstance_onRelease, scf) == scriptResultFinished) { if (scf.returnVal != actionResultNotDone) return scf.returnVal == actionResultSuccess; } } return true; } //----------------------------------------------------------------------- // acceptLockToggle() function for ActiveItem group bool ActiveItem::acceptLockToggle(ActiveItem *ins, ObjectID enactor, uint8 keyCode) { scriptCallFrame scf; if (ins->_data.scriptClassID != 0) { // Set up the arguments we want to pass to the script scf.invokedTAI = ins->thisID(); scf.enactor = enactor; scf.directTAI = scf.invokedTAI; scf.indirectObject = Nothing; // Fill in other params with data from TAG struct scf.value = keyCode; if (runTagMethod( scf.invokedTAI, Method_TileActivityInstance_onAcceptLockToggle, scf) == scriptResultFinished) { if (scf.returnVal != actionResultNotDone) return scf.returnVal == actionResultSuccess; } } switch (ins->builtInBehavior()) { case builtInDoor: if (keyCode == ins->lockType()) { playTAGNoise(ins, UNLOCK_RIGHT_KEY); if (ins->isLocked()) ins->setLocked(false); else { if (ins->getInstanceState(getMapNum()) == 0) ins->setLocked(true); else return false; } } else { playTAGNoise(ins, UNLOCK_WRONG_KEY); return false; } break; } return true; } //----------------------------------------------------------------------- TilePoint getClosestPointOnTAI(ActiveItem *TAI, GameObject *obj) { assert(TAI->_data.itemType == activeTypeInstance); TilePoint objLoc = obj->getLocation(), TAILoc; TileRegion TAIReg; ActiveItem *TAG = TAI->getGroup(); // Compute in points the region of the TAI TAIReg.min.u = TAI->_data.instance.u << kTileUVShift; TAIReg.min.v = TAI->_data.instance.v << kTileUVShift; TAIReg.max.u = TAIReg.min.u + (TAG->_data.group.uSize << kTileUVShift); TAIReg.max.v = TAIReg.min.v + (TAG->_data.group.vSize << kTileUVShift); TAIReg.min.z = TAIReg.max.z = 0; // Find the point on the TAI closest to the object TAILoc.u = clamp(TAIReg.min.u - 1, objLoc.u, TAIReg.max.u); TAILoc.v = clamp(TAIReg.min.v - 1, objLoc.v, TAIReg.max.v); TAILoc.z = TAI->_data.instance.h + obj->proto()->height / 2; return TAILoc; } /* ===================================================================== * ActiveItem instance state management functions * ===================================================================== */ //----------------------------------------------------------------------- // Initialize the active item state arrays void initActiveItemStates(void) { int16 i; stateArray = new byte *[worldCount](); if (stateArray == nullptr) error("Unable to allocate the active item state array array"); for (i = 0; i < worldCount; i++) { stateArray[i] = (byte *)LoadResource(tileRes, tagStateID + i, "active item state array"); if (stateArray[i] == nullptr) error("Unable to load active item state array"); } } //----------------------------------------------------------------------- // Save the active item instance states in a save file void saveActiveItemStates(SaveFileConstructor &saveGame) { int16 i; int32 archiveBufSize = 0; void *archiveBuffer; void *bufferPtr; for (i = 0; i < worldCount; i++) { int32 size = tileRes->size(tagStateID + i); archiveBufSize += sizeof(int16); if (stateArray[i] != nullptr) archiveBufSize += size; } archiveBuffer = malloc(archiveBufSize); if (archiveBuffer == nullptr) error("Unable to allocate a state array archive buffer"); bufferPtr = archiveBuffer; for (i = 0; i < worldCount; i++) { if (stateArray[i] != nullptr) { WorldMapData *mapData = &mapList[i]; ActiveItemList *activeItemList = mapData->activeItemList; int16 activeItemCount = mapData->activeCount, j; int32 arraySize = tileRes->size(tagStateID + i); uint8 *bufferedStateArray; // Save the size of the state array WRITE_LE_INT16(bufferPtr, arraySize / sizeof(uint8)); bufferPtr = (int16 *)bufferPtr + 1; // Copy the state data to the archive buffer memcpy(bufferPtr, stateArray[i], arraySize); // Save a pointer to the buffered data bufferedStateArray = (uint8 *)bufferPtr; bufferPtr = (uint8 *)bufferPtr + arraySize; for (j = 0; j < activeItemCount; j++) { ActiveItem *activeItem = activeItemList->_items[j]; uint8 *statePtr; if (activeItem->_data.itemType != activeTypeInstance) continue; // Get a pointer to the current active item's state // data in the archive buffer statePtr = &bufferedStateArray[activeItem->_data.instance.stateIndex]; // Set the high bit of the state value based upon the // active item's locked state if (activeItem->isLocked()) *statePtr |= (1 << 7); else *statePtr &= ~(1 << 7); } } else { WRITE_LE_INT16(bufferPtr, 0); bufferPtr = (int16 *)bufferPtr + 1; } } saveGame.writeChunk( MakeID('T', 'A', 'G', 'S'), archiveBuffer, archiveBufSize); free(archiveBuffer); } //----------------------------------------------------------------------- // Load the active item instance states from a save file void loadActiveItemStates(SaveFileReader &saveGame) { int16 i; void *archiveBuffer; void *bufferPtr; stateArray = new byte *[worldCount](); if (stateArray == nullptr) error("Unable to allocate the active item state array array"); archiveBuffer = malloc(saveGame.getChunkSize()); if (archiveBuffer == nullptr) error("Unable to allocate state array archive buffer"); saveGame.read(archiveBuffer, saveGame.getChunkSize()); bufferPtr = archiveBuffer; for (i = 0; i < worldCount; i++) { int32 arraySize; arraySize = READ_LE_INT16(bufferPtr) * sizeof(uint8); bufferPtr = (int16 *)bufferPtr + 1; if (arraySize > 0) { WorldMapData *mapData = &mapList[i]; ActiveItemList *activeItemList = mapData->activeItemList; int16 activeItemCount = mapData->activeCount, j; uint8 *bufferedStateArray = (uint8 *)bufferPtr; for (j = 0; j < activeItemCount; j++) { ActiveItem *activeItem = activeItemList->_items[j]; uint8 *statePtr; if (activeItem->_data.itemType != activeTypeInstance) continue; // Get a pointer to the current active item's state // data in the archive buffer statePtr = &bufferedStateArray[activeItem->_data.instance.stateIndex]; // Reset the locked state of the active item based // upon the high bit of the buffered state value activeItem->setLocked((*statePtr & (1 << 7)) != 0); // Clear the high bit of the state value *statePtr &= ~(1 << 7); } stateArray[i] = (byte *)malloc(arraySize); if (stateArray[i] == nullptr) error("Unable to allocate active item state array"); memcpy(stateArray[i], bufferPtr, arraySize); bufferPtr = (uint8 *)bufferPtr + arraySize; } else stateArray[i] = nullptr; } free(archiveBuffer); } //----------------------------------------------------------------------- // Cleanup the active item state arrays void cleanupActiveItemStates(void) { int16 i; for (i = 0; i < worldCount; i++) { if (stateArray[i] != nullptr) free(stateArray[i]); } delete[] stateArray; } /* ===================================================================== * TileActivityTaskList member functions * ===================================================================== */ //----------------------------------------------------------------------- // The list of active tile activity tasks static uint8 aTaskListBuffer[sizeof(TileActivityTaskList)]; static TileActivityTaskList &aTaskList = *((TileActivityTaskList *)aTaskListBuffer); //----------------------------------------------------------------------- // Constructor TileActivityTaskList::TileActivityTaskList(void) { for (uint i = 0; i < elementsof(array); i++) { free.addTail(array[i]); } } //----------------------------------------------------------------------- // Reconstruct the TileActivityTaskList from an archive buffer TileActivityTaskList::TileActivityTaskList(void **buf) { void *bufferPtr = *buf; int16 taskCount; for (uint i = 0; i < elementsof(array); i++) { free.addTail(array[i]); } // Retreive the task count taskCount = READ_LE_INT16(bufferPtr); bufferPtr = (int16 *)bufferPtr + 1; for (int i = 0; i < taskCount; i++) { ActiveItem *tai; uint8 activityType; tai = ActiveItem::activeItemAddress(*((ActiveItemID *)bufferPtr)); bufferPtr = (ActiveItemID *)bufferPtr + 1; activityType = *((uint8 *)bufferPtr); bufferPtr = (uint8 *)bufferPtr + 1; if (tai != nullptr) { TileActivityTask *tat; tat = newTask(tai); if (tat != nullptr) tat->activityType = activityType; } } *buf = bufferPtr; } //----------------------------------------------------------------------- // Return the number of bytes needed to archive this // TileActivityTaskList int32 TileActivityTaskList::archiveSize(void) { int32 size = sizeof(int16); TileActivityTask *tat; for (tat = (TileActivityTask *)list.first(); tat != nullptr; tat = (TileActivityTask *)tat->next()) size += sizeof(ActiveItemID) + sizeof(uint8); return size; } //----------------------------------------------------------------------- // Create an archive of this TileActivityTaskList in the specified // archive buffer Common::MemorySeekableReadWriteStream *TileActivityTaskList::archive(Common::MemorySeekableReadWriteStream *stream) { int16 taskCount; TileActivityTask *tat; for (tat = (TileActivityTask *)list.first(), taskCount = 0; tat != nullptr; tat = (TileActivityTask *)tat->next()) taskCount++; // Store the task count stream->writeSint16LE(taskCount); for (tat = (TileActivityTask *)list.first(); tat != nullptr; tat = (TileActivityTask *)tat->next()) { ActiveItem *ai = tat->tai; // Store the activeItemID stream->writeSint16LE(ai->thisID()); // Store the task type stream->writeByte(tat->activityType); } return stream; } //----------------------------------------------------------------------- // Cleanup void TileActivityTaskList::cleanup(void) { TileActivityTask *tat; TileActivityTask *nextTat; for (tat = (TileActivityTask *)list.first(); tat != nullptr; tat = nextTat) { nextTat = (TileActivityTask *)tat->next(); tat->remove(); } } //----------------------------------------------------------------------- // Get a new tile activity task, if there is one available, // and initialize it. TileActivityTask *TileActivityTaskList::newTask(ActiveItem *activeInstance) { TileActivityTask *tat; // Check see if there's already tile activity task associated with // this instance. for (tat = (TileActivityTask *)list.first(); tat; tat = (TileActivityTask *)tat->next()) { if (tat->tai == activeInstance) break; } #if TATLOG if (tat) writeLog("Found old TAT\n"); #endif if (tat == nullptr) { tat = (TileActivityTask *)free.remHead(); #if TATLOG writeLog("Making new TAT\n"); #endif if (tat) { tat->tai = activeInstance; tat->activityType = TileActivityTask::activityTypeNone; tat->script = NoThread; tat->targetState = 0; list.addTail(*tat); } } // If we re-used an old task struct, then make sure script gets woken up. if (tat->script != NoThread) { #if TATLOG writeLog("Waking up thread TAT\n"); #endif wakeUpThread(tat->script); tat->script = NoThread; } return tat; } /* ===================================================================== * TileActivityTask member functions * ===================================================================== */ //----------------------------------------------------------------------- // When a tile activity task is finished, call this function to delete it. void TileActivityTask::remove(void) { #if TATLOG writeLog("Removing TAT\n"); #endif DNode::remove(); aTaskList.free.addTail(*this); } //----------------------------------------------------------------------- // This initiates a tile activity task for opening a door void TileActivityTask::openDoor(ActiveItem &activeInstance) { TileActivityTask *tat; #if TATLOG writeLog("TAT Open Door\n"); #endif if ((tat = aTaskList.newTask(&activeInstance)) != nullptr) tat->activityType = activityTypeOpen; } //----------------------------------------------------------------------- // This initiates a tile activity task for closing a door void TileActivityTask::closeDoor(ActiveItem &activeInstance) { TileActivityTask *tat; #if TATLOG writeLog("TAT Close Door\n"); #endif if ((tat = aTaskList.newTask(&activeInstance)) != nullptr) tat->activityType = activityTypeClose; } //----------------------------------------------------------------------- // This initiates a tile activity task for script-based activity void TileActivityTask::doScript(ActiveItem &activeInstance, uint8 finalState, ThreadID scr) { TileActivityTask *tat; #if TATLOG writeLog("TAT Do Script\n"); #endif if ((tat = aTaskList.newTask(&activeInstance)) != nullptr) { #if TATLOG if (scr) writeLog("TAT Assign Script!\n"); #endif tat->activityType = activityTypeScript; tat->targetState = finalState; tat->script = scr; } else { #if TATLOG writeLog("Waking up thread 'cause newTask Failed\n"); #endif wakeUpThread(scr); // If there were no threads available } } //----------------------------------------------------------------------- // Routine to update positions of all active terrain using TileActivityTasks void TileActivityTask::updateActiveItems(void) { TileActivityTask *tat, *nextTat; #if DEBUG int count = 0, scriptCount = 0; #endif for (tat = (TileActivityTask *)aTaskList.list.first(); tat; tat = nextTat) { ActiveItem *activityInstance = tat->tai; bool activityTaskDone = false; int16 mapNum = activityInstance->getMapNum(); uint16 state = activityInstance->getInstanceState(mapNum); nextTat = (TileActivityTask *)tat->next(); #if TATLOG count++; if (tat->script != NoThread) scriptCount++; #endif switch (tat->activityType) { case activityTypeOpen: if (state < 3) activityInstance->setInstanceState(mapNum, state + 1); else activityTaskDone = true; break; case activityTypeClose: if (state > 0) activityInstance->setInstanceState(mapNum, state - 1); else activityTaskDone = true; break; case activityTypeScript: if (state > tat->targetState) activityInstance->setInstanceState(mapNum, state - 1); else if (state < tat->targetState) activityInstance->setInstanceState(mapNum, state + 1); else activityTaskDone = true; break; default: activityTaskDone = true; break; } if (activityTaskDone) { // Wake up the script... #if TATLOG if (tat->script != NoThread) writeLog("TAT Wake Up Thread\n"); #endif if (tat->script != NoThread) wakeUpThread(tat->script); tat->remove(); } } #if DEBUG WriteStatusF(16, "TileTasks: %d SW:%d", count, scriptCount); #endif } //----------------------------------------------------------------------- // Search for tile activity task matching an item TileActivityTask *TileActivityTask::find(ActiveItem *tai) { TileActivityTask *tat; for (tat = (TileActivityTask *)aTaskList.list.first(); tat; tat = (TileActivityTask *)tat->next()) { if (tai == tat->tai) return tat; } return nullptr; } //----------------------------------------------------------------------- // Add script to tile activity task... bool TileActivityTask::setWait(ActiveItem *tai, ThreadID script) { TileActivityTask *tat; tat = find(tai); #if TATLOG writeLog("Set Wait TAT\n"); #endif if (tat) { #if TATLOG if (tat->script != NoThread) writeLog("TAT Waking Up Thread\n"); #endif if (tat->script != NoThread) wakeUpThread(tat->script); tat->script = script; return true; } #if TATLOG writeLog("SetWait failed\n"); #endif return false; } //----------------------------------------------------------------------- // Calls the handling routine for each tile activity task void moveActiveTerrain(int32 deltaTime) { deltaTime = 0; TileActivityTask::updateActiveItems(); } //----------------------------------------------------------------------- // Initialize the tile activity task list void initTileTasks(void) { // Simply call the default constructor new (&aTaskList) TileActivityTaskList; } //----------------------------------------------------------------------- // Save the tile activity task list to a save file void saveTileTasks(SaveFileConstructor &saveGame) { int32 archiveBufSize; byte *archiveBuffer; Common::MemorySeekableReadWriteStream *stream; archiveBufSize = aTaskList.archiveSize(); archiveBuffer = (byte *)malloc(archiveBufSize); if (archiveBuffer == nullptr) error("Unable to allocate tile activity task archive buffer"); stream = new Common::MemorySeekableReadWriteStream(archiveBuffer, archiveBufSize, DisposeAfterUse::YES); aTaskList.archive(stream); saveGame.writeChunk( MakeID('T', 'A', 'C', 'T'), archiveBuffer, archiveBufSize); delete stream; } //----------------------------------------------------------------------- // Load the tile activity task list from a save file void loadTileTasks(SaveFileReader &saveGame) { // If there is no saved data, simply call the default constructor if (saveGame.getChunkSize() == 0) { new (&aTaskList) TileActivityTaskList; return; } void *archiveBuffer; void *bufferPtr; archiveBuffer = malloc(saveGame.getChunkSize()); if (archiveBuffer == nullptr) error("Unable to allocate tile activity task archive buffer"); // Read the archived tile activity task data saveGame.read(archiveBuffer, saveGame.getChunkSize()); bufferPtr = archiveBuffer; // Reconstruct aTaskList from archived data new (&aTaskList) TileActivityTaskList(&bufferPtr); free(archiveBuffer); } //----------------------------------------------------------------------- // Cleanup the tile activity task list void cleanupTileTasks(void) { // Simply call the aTaskList's cleanup aTaskList.cleanup(); } /* ===================================================================== * Map management functions * ===================================================================== */ //----------------------------------------------------------------------- // Initialize map data TileBank::TileBank(Common::SeekableReadStream *stream) { _numTiles = stream->readUint32LE(); _tileArray = new TileInfo[_numTiles]; for (uint i = 0; i < _numTiles; ++i) { _tileArray[i].offset = stream->readUint32LE(); TileAttrs *att = &_tileArray[i].attrs; att->terrainHeight = stream->readByte(); att->height = stream->readByte(); att->terrainMask = stream->readUint16LE(); att->fgdTerrain = stream->readByte(); att->bgdTerrain = stream->readByte(); stream->read(att->reserved0, 8); att->maskRule = stream->readByte(); att->altMask = stream->readByte(); stream->read(att->cornerHeight, 4); att->cycleRange = stream->readByte(); att->tileFlags = stream->readByte(); att->reserved1 = stream->readUint16LE(); } } MapHeader::MapHeader(Common::SeekableReadStream *stream) { size = stream->readSint16LE(); edgeType = stream->readSint16LE(); mapData = new uint16[size * size]; for (int i = 0; i < size * size; ++i) mapData[i] = stream->readUint16LE(); } MapHeader::~MapHeader() { if (mapData) delete[] mapData; } MetaTile::MetaTile(int ind, Common::SeekableReadStream *stream) { _index = ind; _highestPixel = stream->readUint16LE(); _banksNeeded._b[0] = stream->readUint32LE(); _banksNeeded._b[1] = stream->readUint32LE(); for (int i = 0; i < maxPlatforms; ++i) _stack[i] = stream->readUint16LE(); _properties = stream->readUint32LE(); } MetaTileList::MetaTileList(int count, Common::SeekableReadStream *stream) { _count = count; _tiles = (MetaTile **)malloc(_count * sizeof(MetaTile *)); for (int i = 0; i < _count; ++i) { _tiles[i] = new MetaTile(i, stream); } } MetaTileList::~MetaTileList() { if (_tiles) { for (int i = 0; i < _count; ++i) { if (_tiles[i]) delete _tiles[i]; } free(_tiles); } } ActiveItem::ActiveItem(ActiveItemList *parent, int ind, Common::SeekableReadStream *stream) { _parent = parent; _index = ind; _nextHash = nullptr; stream->readUint32LE(); _data.nextHashDummy = 0; _data.scriptClassID = stream->readUint16LE(); _data.associationOffset = stream->readUint16LE(); _data.numAssociations = stream->readByte(); _data.itemType = stream->readByte(); _data.instance.groupID = stream->readUint16LE(); _data.instance.u = stream->readSint16LE(); _data.instance.v = stream->readSint16LE(); _data.instance.h = stream->readSint16LE(); _data.instance.stateIndex = stream->readUint16LE(); _data.instance.scriptFlags = stream->readUint16LE(); _data.instance.targetU = stream->readUint16LE(); _data.instance.targetV = stream->readUint16LE(); _data.instance.targetZ = stream->readByte(); _data.instance.worldNum = stream->readByte(); } ActiveItemList::ActiveItemList(WorldMapData *parent, int count, Common::SeekableReadStream *stream) { _parent = parent; _count = count; _items = (ActiveItem **)malloc(_count * sizeof(ActiveItem *)); for (int i = 0; i < _count; ++i) { _items[i] = new ActiveItem(this, i, stream); } } ActiveItemList::~ActiveItemList() { if (_items) { for (int i = 0; i < _count; ++i) { if (_items[i]) delete _items[i]; } free(_items); } } void initMaps(void) { int16 i; Common::SeekableReadStream *stream; const int metaTileSize = 30; const int tileRefSize = 4; const int assocSize = 2; const int activeItemSize = 28; // Load all of the tile terrain banks for (i = 0; i < maxBanks; i++) { stream = loadResourceToStream(tileRes, tileTerrainID + i, "tile terrain bank"); tileBanks[i] = new TileBank(stream); delete stream; if (tileBanks[i]->_tileArray == nullptr) { delete tileBanks[i]; tileBanks[i] = nullptr; } } // Count the worlds by seeking the map data for (worldCount = 0; tileRes->seek(mapID + worldCount); worldCount++) { warning("MapID: %s %08x res: %s %08x", tag2str(mapID), mapID, tag2str(mapID + worldCount), mapID + worldCount); } // Allocate the map data array mapList = new WorldMapData[worldCount](); if (mapList == nullptr) error("Unable to allocate map data array"); // Iterate through the map data list initializing each element for (i = 0; i < worldCount; i++) { WorldMapData *mapData = &mapList[i]; int16 j; int iMapID = mapID + i; int iMetaID = metaID + i; int iTagRefID = tagDataID + i; int iAssocID = assocID + i; int iActiveItemID = tagID + i; // Initialize the world ID mapData->worldID = WorldBaseID + i; // Load the map stream = loadResourceToStream(tileRes, iMapID, "world map"); mapData->map = new MapHeader(stream); delete stream; if (mapData->map == nullptr) error("Unable to load map"); debugC(2, kDebugTiles, "map: size = %d, mapData = %p", mapData->map->size, (void*)mapData->map->mapData); int metaTileCount = tileRes->size(iMetaID) / metaTileSize; stream = loadResourceToStream(tileRes, iMetaID, "meta tile list"); mapData->metaList = new MetaTileList(metaTileCount, stream); delete stream; if (mapData->metaList == nullptr || mapData->metaList->_tiles == nullptr) error("Unable to load meta tile list"); // If there is tag data, load it if (tileRes->size(iTagRefID) > 0) { int tileRefCount = tileRes->size(iTagRefID) / tileRefSize; mapData->activeItemData = new TileRef[tileRefCount](); if (mapData->activeItemData == nullptr) error("Unable to load active item data"); tileRes->seek(iTagRefID); for (int k = 0; k < tileRefCount; ++k) { mapData->activeItemData[k].tile = tileRes->readU16LE(); mapData->activeItemData[k].flags = tileRes->readByte(); mapData->activeItemData[k].tileHeight = tileRes->readByte(); } } else mapData->activeItemData = nullptr; // If there is an association list, load it if (tileRes->size(iAssocID) > 0) { int assocCount = tileRes->size(iAssocID) / assocSize; mapData->assocList = new uint16[assocCount](); if (mapData->assocList == nullptr) error("Unable to load association list"); tileRes->seek(iAssocID); for (int k = 0; k < assocCount; ++k) mapData->assocList[k] = tileRes->readU16LE(); } else mapData->assocList = nullptr; // If there is an active item list, load it if (tileRes->size(iActiveItemID) > 0) { int activeItemCount = tileRes->size(iActiveItemID) / activeItemSize; stream = loadResourceToStream(tileRes, iActiveItemID, "active item list"); mapData->activeItemList = new ActiveItemList(mapData, activeItemCount, stream); delete stream; if (mapData->activeItemList == nullptr || mapData->activeItemList->_items == nullptr) error("Unable to load active item list"); mapData->activeCount = activeItemCount; } else mapData->activeItemList = nullptr; // Compute the number of meta tiles in list mapData->metaCount = metaTileCount; // Allocate an object ripping table ID list mapData->ripTableIDList = new RipTableID[mapData->metaCount]; if (mapData->ripTableIDList == nullptr) error("Unable to allocate rip table ID list"); // Initialize the object ripping ID list for (j = 0; j < mapData->metaCount; j++) (mapData->ripTableIDList)[j] = -1; // Get the size of the map in meta tiles mapData->mapSize = mapData->map->size; // Compute the height of the map in pixels mapData->mapHeight = mapData->mapSize * kMetaTileHeight; // Build an active item instance hash table mapData->buildInstanceHash(); } initPlatformCache(); initMapFeatures(); } //----------------------------------------------------------------------- // Cleanup map data void cleanupMaps(void) { int16 i; termMapFeatures(); // Iterate through each map, dumping the data for (i = 0; i < worldCount; i++) { WorldMapData *mapData = &mapList[i]; // Dump the map if (mapData->map != nullptr) delete mapData->map; // Dump the meta tile list if (mapData->metaList) delete mapData->metaList; // If there is active item data, dump it if (mapData->activeItemData != nullptr) delete[] mapData->activeItemData; // If there is an association list, dump it if (mapData->assocList != nullptr) delete[] mapData->assocList; // If there is an active item list, dump it if (mapData->activeItemList != nullptr) delete mapData->activeItemList; // Dump the object ripping table ID list delete[] mapData->ripTableIDList; } // Dump the map data list delete[] mapList; // Dump all of the tile terrain banks for (i = 0; i < maxBanks; i++) { if (tileBanks[i] != nullptr) { if (tileBanks[i]->_tileArray != nullptr) delete[] tileBanks[i]->_tileArray; delete tileBanks[i]; tileBanks[i] = nullptr; } } } //----------------------------------------------------------------------- // Set a new current map void setCurrentMap(int mapNum) { currentMapNum = mapNum; if (lastMapNum != currentMapNum) { lastMapNum = currentMapNum; freeAllTileBanks(); audioEnvironmentSetWorld(mapNum); } // tileScroll.x = mapList[mapNum].mapHeight - tileRect.width - 800; // tileScroll.y = mapList[mapNum].mapHeight - tileRect.width / 2; lastUpdateTime = gameTime; } /* ===================================================================== * Automap management functions * ===================================================================== */ //----------------------------------------------------------------------- void initAutoMap(void) { int16 i; for (i = 0; i < worldCount; i++) { MapHeader *map; int32 mapSize, mapIndex; uint16 *mapData; map = mapList[i].map; mapSize = map->size; mapSize *= mapSize; mapData = map->mapData; // Clear the high bit for each map position for (mapIndex = 0; mapIndex < mapSize; mapIndex++) mapData[mapIndex] &= ~metaTileVisited; } } //----------------------------------------------------------------------- void saveAutoMap(SaveFileConstructor &saveGame) { int32 totalMapSize = 0, totalMapIndex = 0; int16 i; uint8 *archiveBuffer; int32 archiveBufSize; for (i = 0; i < worldCount; i++) { MapHeader *map; int32 mapSize; map = mapList[i].map; mapSize = map->size; mapSize *= mapSize; totalMapSize += mapSize; } // Compute the number of bytes needed to store the visited bit // for each map metatile slot archiveBufSize = (totalMapSize + 7) >> 3; archiveBuffer = (uint8 *)malloc(archiveBufSize); if (archiveBuffer == nullptr) error("Unable to allocate auto map archive buffer"); for (i = 0; i < worldCount; i++) { MapHeader *map; int32 mapSize, mapIndex; uint16 *mapData; map = mapList[i].map; mapSize = map->size; mapSize *= mapSize; mapData = map->mapData; for (mapIndex = 0; mapIndex < mapSize; mapIndex++) { if (mapData[mapIndex] & metaTileVisited) { // Set the bit in the archive buffer archiveBuffer[totalMapIndex >> 3] |= (1 << (totalMapIndex & 7)); } else { // Clear the bit in the archive buffer archiveBuffer[totalMapIndex >> 3] &= ~(1 << (totalMapIndex & 7)); } totalMapIndex++; } } saveGame.writeChunk( MakeID('A', 'M', 'A', 'P'), archiveBuffer, archiveBufSize); free(archiveBuffer); } //----------------------------------------------------------------------- void loadAutoMap(SaveFileReader &saveGame) { int32 totalMapIndex = 0; int16 i; uint8 *archiveBuffer; int32 archiveBufSize; archiveBufSize = saveGame.getChunkSize(); archiveBuffer = (uint8 *)malloc(archiveBufSize); if (archiveBuffer == nullptr) error("Unable to allocate auto map archive buffer"); saveGame.read(archiveBuffer, archiveBufSize); for (i = 0; i < worldCount; i++) { MapHeader *map; int32 mapSize, mapIndex; uint16 *mapData; map = mapList[i].map; mapSize = map->size; mapSize *= mapSize; mapData = map->mapData; for (mapIndex = 0; mapIndex < mapSize; mapIndex++) { assert((totalMapIndex >> 3) < archiveBufSize); // If the bit is set in the archive buffer, set the visited // bit in the map data if (archiveBuffer[totalMapIndex >> 3] & (1 << (totalMapIndex & 7))) mapData[mapIndex] |= metaTileVisited; else mapData[mapIndex] &= ~metaTileVisited; totalMapIndex++; } } free(archiveBuffer); } /* ===================================================================== * Platform cache functions * ===================================================================== */ //----------------------------------------------------------------------- // Initialize the platform cache void initPlatformCache(void) { for (int i = 0; i < platformCacheSize; i++) { PlatformCacheEntry *pce = &platformCache[i]; // Fill up the LRU with empty platforms pce->metaID = NoMetaTile; platformLRU.push_back(i); } } /* ===================================================================== * Returns the X/Y point in U/V coords * ===================================================================== */ TilePoint XYToUV(const Point32 &pt) { int32 mapHeight = mapList[currentMapNum].mapHeight; TilePoint coords; // coordinates of the view in U,V coords.u = (((pt.x + mapHeight) >> 1) - pt.y) >> 1; coords.v = (mapHeight - pt.y - ((pt.x - mapHeight) >> 1)) >> 1; coords.z = 0; return coords; } /* ===================================================================== * Converts a (u,v,z) tilepoint to (x, y) screen coords; * ===================================================================== */ void TileToScreenCoords(const TilePoint &tp, Point16 &p) { int32 mapHeight = mapList[currentMapNum].mapHeight; // screen coords of the point p.x = (((int32)tp.u - (int32)tp.v) << 1) - tileScroll.x + mapHeight; p.y = mapHeight - tileScroll.y - ((int32)tp.u + (int32)tp.v) - tp.z; } TilePoint::TilePoint(Common::SeekableReadStream *stream) { u = stream->readSint16LE(); v = stream->readSint16LE(); z = stream->readSint16LE(); } //----------------------------------------------------------------------- // Converts a UV vector into a rough direction vector. int16 TilePoint::quickDir(void) { int16 u2 = u * 2, v2 = v * 2; if (u < v2) { if (v > -u2) return (v > u2 ? dirUpLeft : dirUp); return (u > -v2 ? dirLeft : dirDownLeft); } else { if (v > -u2) return (u > -v2 ? dirUpRight : dirRight); return (v > u2 ? dirDown : dirDownRight); } } /* ===================================================================== * Do a bilinear interpolation of the four corner heights of a tile to determine the height of a point on the tile. * ===================================================================== */ int16 ptHeight(const TilePoint &tp, uint8 *cornerHeight) { int16 slopeHeight = cornerHeight[0]; if (cornerHeight[1] == slopeHeight && cornerHeight[2] == slopeHeight && cornerHeight[3] == slopeHeight) return slopeHeight; slopeHeight = (cornerHeight[0] * (kTileUVSize - tp.u) + cornerHeight[1] * tp.u) * (kTileUVSize - tp.v) + (cornerHeight[3] * (kTileUVSize - tp.u) + cornerHeight[2] * tp.u) * tp.v; return slopeHeight >> (kTileUVShift + kTileUVShift); } /* ====================================================================== * Platform member functions * ====================================================================== */ //----------------------------------------------------------------------- // Fetch the REAL tile associated with a particular location, including // indirection such as tile cycling and activity groups. // REM: This is a likely candidate for downcoding... TileInfo *Platform::fetchTile( int16 mapNum, const TilePoint &pt, const TilePoint &origin, int16 &height, int16 &trFlags) { TileRef *tr = &tiles[pt.u][pt.v]; TileInfo *ti; int16 h = tr->tileHeight * 8; if (tr->flags & trTileTAG) { ActiveItem *groupItem, *instanceItem; int16 state = 0; TilePoint relPos, absPos; groupItem = ActiveItem::activeItemAddress( ActiveItemID(mapNum, tr->tile)); // Relpos is the relative position of the // tile within the group relPos.u = (tr->flags >> 1) & 0x07; relPos.v = (tr->flags >> 4) & 0x07; // Abspos is the absolute position of the // group on the tile map. absPos.u = pt.u - relPos.u + origin.u; absPos.v = pt.v - relPos.v + origin.v; absPos.z = h; // Look up the group instance in the hash. instanceItem = mapList[mapNum].findHashedInstance( absPos, tr->tile); if (instanceItem) { state = instanceItem->getInstanceState(mapNum); // Get the tile to be drawn from the tile group tr = &(mapList[mapNum].activeItemData)[ groupItem->_data.group.grDataOffset + state * groupItem->_data.group.animArea + relPos.u * groupItem->_data.group.vSize + relPos.v]; h += tr->tileHeight * 8; } #if DEBUG else { static TileRef dummyRef = { 1, 0, 0 }; tr = &dummyRef; } #endif } if ((ti = TileInfo::tileAddress(tr->tile)) == nullptr) return nullptr; trFlags = tr->flags; height = h; #if DEBUG if (ti->offset > maxOffset || ti->attrs.height > kMaxTileHeight || ti->attrs.height < 0) { int16 tileNo, tileBank; TileID2Bank(tr->tile, tileBank, tileNo); WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank); return nullptr; } #endif return ti; } // Fetch the tile and the active item it came from... // REM: This is a likely candidate for downcoding... TileInfo *Platform::fetchTAGInstance( int16 mapNum, const TilePoint &pt, const TilePoint &origin, StandingTileInfo &sti) { TileRef *tr = &tiles[pt.u][pt.v]; TileInfo *ti; int16 h = tr->tileHeight * 8; if (tr->flags & trTileTAG) { ActiveItem *groupItem, *instanceItem; int16 state = 0; TilePoint relPos, absPos; groupItem = ActiveItem::activeItemAddress( ActiveItemID(mapNum, tr->tile)); // Relpos is the relative position of the // tile within the group relPos.u = (tr->flags >> 1) & 0x07; relPos.v = (tr->flags >> 4) & 0x07; // Abspos is the absolute position of the // group on the tile map. absPos.u = pt.u - relPos.u + origin.u; absPos.v = pt.v - relPos.v + origin.v; absPos.z = h; // Look up the group instance in the hash. instanceItem = mapList[mapNum].findHashedInstance( absPos, tr->tile); if (instanceItem) { state = instanceItem->getInstanceState(mapNum); sti.surfaceTAG = instanceItem; // Get the tile to be drawn from the tile group tr = &(mapList[mapNum].activeItemData)[ groupItem->_data.group.grDataOffset + state * groupItem->_data.group.animArea + relPos.u * groupItem->_data.group.vSize + relPos.v]; h += tr->tileHeight * 8; } #if DEBUG else { static TileRef dummyRef = { 1, 0, 0 }; tr = &dummyRef; } #endif } else { sti.surfaceTAG = nullptr; } if ((ti = TileInfo::tileAddress(tr->tile)) == nullptr) return nullptr; sti.surfaceTile = ti; sti.surfaceRef = *tr; sti.surfaceHeight = h; return ti; } //----------------------------------------------------------------------- // Fetch the REAL tile associated with a particular location, including // indirection such as tile cycling and activity groups. // REM: This is a likely candidate for downcoding... TileInfo *Platform::fetchTile( int16 mapNum, const TilePoint &pt, const TilePoint &origin, uint8 **imageData, int16 &height, int16 &trFlags) { TileRef *tr = &tiles[pt.u][pt.v]; TileInfo *ti; int16 h = tr->tileHeight * 8; if (tr->flags & trTileTAG) { ActiveItem *groupItem, *instanceItem; int16 state = 0; TilePoint relPos, absPos; groupItem = ActiveItem::activeItemAddress( ActiveItemID(mapNum, tr->tile)); // Relpos is the relative position of the // tile within the group relPos.u = (tr->flags >> 1) & 0x07; relPos.v = (tr->flags >> 4) & 0x07; // Abspos is the absolute position of the // group on the tile map. absPos.u = pt.u - relPos.u + origin.u; absPos.v = pt.v - relPos.v + origin.v; absPos.z = h; // Look up the group instance in the hash. instanceItem = mapList[mapNum].findHashedInstance( absPos, tr->tile); if (instanceItem) { state = instanceItem->getInstanceState(mapNum); // Get the tile to be drawn from the tile group tr = &(mapList[mapNum].activeItemData)[ groupItem->_data.group.grDataOffset + state * groupItem->_data.group.animArea + relPos.u * groupItem->_data.group.vSize + relPos.v]; h += tr->tileHeight * 8; } #if DEBUG else { static TileRef dummyRef = { 1, 0, 0 }; tr = &dummyRef; } #endif } if ((ti = TileInfo::tileAddress(tr->tile, imageData)) == nullptr) return nullptr; trFlags = tr->flags; height = h; #if DEBUG if (ti->offset > maxOffset || ti->attrs.height > kMaxTileHeight || ti->attrs.height < 0) { int16 tileNo, tileBank; TileID2Bank(tr->tile, tileBank, tileNo); WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank); return nullptr; } #endif return ti; } // Fetch the tile and the active item it came from... // REM: This is a likely candidate for downcoding... TileInfo *Platform::fetchTAGInstance( int16 mapNum, const TilePoint &pt, const TilePoint &origin, uint8 **imageData, StandingTileInfo &sti) { TileRef *tr = &tiles[pt.u][pt.v]; TileInfo *ti; int16 h = tr->tileHeight * 8; if (tr->flags & trTileTAG) { ActiveItem *groupItem, *instanceItem; int16 state = 0; TilePoint relPos, absPos; groupItem = ActiveItem::activeItemAddress( ActiveItemID(mapNum, tr->tile)); // Relpos is the relative position of the // tile within the group relPos.u = (tr->flags >> 1) & 0x07; relPos.v = (tr->flags >> 4) & 0x07; // Abspos is the absolute position of the // group on the tile map. absPos.u = pt.u - relPos.u + origin.u; absPos.v = pt.v - relPos.v + origin.v; absPos.z = h; // Look up the group instance in the hash. instanceItem = mapList[mapNum].findHashedInstance( absPos, tr->tile); if (instanceItem) { state = instanceItem->getInstanceState(mapNum); sti.surfaceTAG = instanceItem; // Get the tile to be drawn from the tile group tr = &(mapList[mapNum].activeItemData)[ groupItem->_data.group.grDataOffset + state * groupItem->_data.group.animArea + relPos.u * groupItem->_data.group.vSize + relPos.v]; h += tr->tileHeight * 8; } #if DEBUG else { static TileRef dummyRef = { 1, 0, 0 }; tr = &dummyRef; } #endif } else { sti.surfaceTAG = nullptr; } if ((ti = TileInfo::tileAddress(tr->tile, imageData)) == nullptr) return nullptr; sti.surfaceTile = ti; sti.surfaceRef = *tr; sti.surfaceHeight = h; return ti; } /* ====================================================================== * RipTable member functions * ====================================================================== */ //----------------------------------------------------------------------- // Return a pointer to a rip table give the rip table's ID RipTable *RipTable::ripTableAddress(RipTableID id) { return id != -1 ? &ripTableList[id] : nullptr; } //----------------------------------------------------------------------- // Return a rip table's ID RipTableID RipTable::thisID(void) { return this - ripTableList; } /* ====================================================================== * MetaTile member functions * ====================================================================== */ //----------------------------------------------------------------------- // Return a pointer to a meta tile given its ID MetaTile *MetaTile::metaTileAddress(MetaTileID id) { return id.map != nullID && id.index != nullID ? mapList[id.map].metaList->_tiles[id.index] : nullptr; } //----------------------------------------------------------------------- // Return this meta tile's ID MetaTileID MetaTile::thisID(int16 mapNum) { return MetaTileID(mapNum, _index); } //----------------------------------------------------------------------- // Return the audio theme associated with this metatile metaTileNoise MetaTile::HeavyMetaMusic(void) { return _properties & 0xFF; } //----------------------------------------------------------------------- // Return a pointer to the specified platform static void readPlatform(hResContext *con, Platform &plt) { plt.height = con->readU16LE(); plt.highestPixel = con->readU16LE(); plt.flags = con->readU16LE(); for (int j = 0; j < kPlatformWidth; ++j) { for (int i = 0; i < kPlatformWidth; ++i) { plt.tiles[j][i].tile = con->readU16LE(); plt.tiles[j][i].flags = con->readByte(); plt.tiles[j][i].tileHeight = con->readByte(); } } } Platform *MetaTile::fetchPlatform(int16 mapNum, int16 layer) { const int cacheFlag = 0x8000; uint16 plIndex = _stack[layer]; PlatformCacheEntry *pce; assert(layer >= 0); assert(_index != -1); if (plIndex == nullID) { return nullptr; } else if (plIndex & cacheFlag) { plIndex &= ~cacheFlag; assert(plIndex < platformCacheSize); // Get the address of the pce from the cache pce = &platformCache[plIndex]; assert(pce->platformNum >= 0); assert(pce->metaID != NoMetaTile); assert(pce->metaID == thisID(mapNum)); // Move to the end of the LRU platformLRU.push_back(plIndex); // return the address of the platform return &pce->pl; } else { debugC(2, kDebugLoading, "Fetching platform (%d,%d)", mapNum, layer); if (mapNum == 0 && layer == 6) debug(""); // Since the platform is not in the cache, we need to // dump something from the cache. Dump the one that // was least recently used. // Get head of LRU chain. int cacheIndex = platformLRU.front(); platformLRU.pop_front(); platformLRU.push_back(cacheIndex); pce = &platformCache[cacheIndex]; // Compute the layer of this entry in the cache assert(cacheIndex < platformCacheSize); assert(cacheIndex >= 0); // Initialize the cache entry to the new platform data. pce->platformNum = plIndex; pce->layerNum = layer; pce->metaID = thisID(mapNum); _stack[layer] = (cacheIndex | cacheFlag); assert(plIndex >= 0); assert(plIndex * sizeof(Platform) < tileRes->size(platformID + mapNum)); debugC(3, kDebugLoading, "- plIndex: %d", plIndex); // Now, load the actual metatile data... if (tileRes->seek(platformID + mapNum)) { if (tileRes->skip(plIndex * sizeof(Platform))) { readPlatform(tileRes, pce->pl); return &pce->pl; } } error("Unable to read Platform %d of map %d", plIndex, mapNum); return nullptr; } } //----------------------------------------------------------------------- // Return a pointer to this metatile's current object ripping // table RipTable *MetaTile::ripTable(int16 mapNum) { WorldMapData *mapData = &mapList[mapNum]; return RipTable::ripTableAddress((mapData->ripTableIDList)[_index]); } //----------------------------------------------------------------------- // Return a reference to this meta tile's rip table ID RipTableID &MetaTile::ripTableID(int16 mapNum) { WorldMapData *mapData = &mapList[mapNum]; return (mapData->ripTableIDList)[_index]; } /* ====================================================================== * WorldMapData member functions * ====================================================================== */ //----------------------------------------------------------------------- // Return a pointer to the specified meta tile on this map MetaTilePtr WorldMapData::lookupMeta(TilePoint coords) { uint16 *mapData = map->mapData; int16 mtile; #if 0 // Note: Keep this code if we ever need to have variable // map edge types in the future. TilePoint clipCoords; int16 mapSizeMask = mapSize - 1, mapEdgeType = (*map)->edgeType; clipCoords.u = (uint16)coords.u % mapSize; clipCoords.v = (uint16)coords.v % mapSize; clipCoords.z = coords.z; if (coords != clipCoords) { switch (mapEdgeType) { case edgeTypeBlack: // continue; case edgeTypeFill0: mtile = 0; break; case edgeTypeFill1: mtile = 1; break; case edgeTypeRepeat: coords.u = clamp(0, coords.u, mapSizeMask); coords.v = clamp(0, coords.v, mapSizeMask); mtile = mapData[clipCoords.u * mapSize + clipCoords.v]; break; case edgeTypeWrap: mtile = mapData[clipCoords.u * mapSize + clipCoords.v]; break; } } else mtile = mapData[clipCoords.u * mapSize + clipCoords.v]; #else // Check to see if coords are less than zero or greater // than size of map. Note we can eliminate less than // zero test by doing an unsigned compare. if ((uint32)coords.u >= (uint32)mapSize || (uint32)coords.v >= (uint32)mapSize) { // If off the edge of the map, it defaults to meta // tile #1. mtile = 1; } else { // When getting the metatile number, make sure to mask off the // bit indicating that this map square has been visited. mtile = mapData[coords.u * mapSize + coords.v] & ~metaTileVisited; } #endif assert(mtile < metaCount); assert(mtile >= 0); return metaList->_tiles[mtile]; } //----------------------------------------------------------------------- // Builds an active item instance hash table for tile lookup void WorldMapData::buildInstanceHash(void) { int32 i; int16 hashVal; ActiveItem **ail; memset(instHash, 0, sizeof(instHash)); for (i = 0, ail = activeItemList->_items; i < activeCount; i++, ail++) { ActiveItem *ai = *ail; if (ai->_data.itemType == activeTypeInstance) { hashVal = (((ai->_data.instance.u + ai->_data.instance.h) << 4) + ai->_data.instance.v + (ai->_data.instance.groupID << 2)) % elementsof(instHash); itemHash.setVal(hashVal, ai); } } } //----------------------------------------------------------------------- // Lookup an active item instance given the active item group number // an the meta tile coordinates ActiveItem *WorldMapData::findHashedInstance( TilePoint &tp, int16 group) { int16 hashVal = (((tp.u + tp.z) << 4) + tp.v + (group << 2)) % elementsof(instHash); if (itemHash.contains(hashVal)) return itemHash.getVal(hashVal); return nullptr; } /* ====================================================================== * MetaTileIterator member functions * ====================================================================== */ bool MetaTileIterator::iterate(void) { if (++mCoords.v >= region.max.v) { if (++mCoords.u >= region.max.u) return false; mCoords.v = region.min.v; } return true; } MetaTile *MetaTileIterator::first(TilePoint *loc) { MetaTile *mtRes; mCoords = region.min; if (mCoords.u >= region.max.u || mCoords.v >= region.max.v) return nullptr; mtRes = mapList[mapNum].lookupMeta(mCoords); while (mtRes == nullptr) { if (!iterate()) return nullptr; mtRes = mapList[mapNum].lookupMeta(mCoords); } if (loc) *loc = mCoords << kPlatShift; return mtRes; } MetaTile *MetaTileIterator::next(TilePoint *loc) { MetaTile *mtRes = nullptr; do { if (!iterate()) return nullptr; mtRes = mapList[mapNum].lookupMeta(mCoords); } while (mtRes == nullptr); if (loc) *loc = mCoords << kPlatShift; return mtRes; } /* ====================================================================== * TileIterator member functions * ====================================================================== */ bool TileIterator::iterate(void) { if (++tCoords.v >= tCoordsReg.max.v) { if (++tCoords.u >= tCoordsReg.max.u) { do { platIndex++; if (platIndex >= maxPlatforms) { if ((mt = metaIter.next(&origin)) != nullptr) { tCoordsReg.min.u = tCoordsReg.min.v = 0; tCoordsReg.max.u = tCoordsReg.max.v = kPlatformWidth; if (origin.u < region.min.u) tCoordsReg.min.u = region.min.u & kPlatMask; if (origin.u + kPlatformWidth > region.max.u) tCoordsReg.max.u = region.max.u & kPlatMask; if (origin.v < region.min.v) tCoordsReg.min.v = region.min.v & kPlatMask; if (origin.v + kPlatformWidth > region.max.v) tCoordsReg.max.v = region.max.v & kPlatMask; } else return false; platIndex = 0; } platform = mt->fetchPlatform( metaIter.getMapNum(), platIndex); } while (platform == nullptr); tCoords.u = tCoordsReg.min.u; } tCoords.v = tCoordsReg.min.v; } return true; } TileInfo *TileIterator::first(TilePoint *loc, StandingTileInfo *stiResult) { TileInfo *tiRes; StandingTileInfo sti; if (region.max.u <= region.min.u || region.max.v <= region.min.v) return nullptr; if ((mt = metaIter.first(&origin)) == nullptr) return nullptr; platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex = 0); while (platform == nullptr) { platIndex++; if (platIndex >= maxPlatforms) { if ((mt = metaIter.next(&origin)) == nullptr) return nullptr; platIndex = 0; } platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex); } tCoordsReg.min.u = tCoordsReg.min.v = 0; tCoordsReg.max.u = tCoordsReg.max.v = kPlatformWidth; if (origin.u < region.min.u) tCoordsReg.min.u = region.min.u & kPlatMask; if (origin.u + kPlatformWidth > region.max.u) tCoordsReg.max.u = region.max.u & kPlatMask; if (origin.v < region.min.v) tCoordsReg.min.v = region.min.v & kPlatMask; if (origin.v + kPlatformWidth > region.max.v) tCoordsReg.max.v = region.max.v & kPlatMask; tCoords = tCoordsReg.min; tiRes = platform->fetchTAGInstance( metaIter.getMapNum(), tCoords, origin, sti); while (tiRes == nullptr) { if (!iterate()) return nullptr; tiRes = platform->fetchTAGInstance( metaIter.getMapNum(), tCoords, origin, sti); } *loc = tCoords + origin; if (stiResult) *stiResult = sti; return tiRes; } TileInfo *TileIterator::next(TilePoint *loc, StandingTileInfo *stiResult) { TileInfo *tiRes = nullptr; StandingTileInfo sti; do { if (!iterate()) return nullptr; tiRes = platform->fetchTAGInstance( metaIter.getMapNum(), tCoords, origin, sti); } while (tiRes == nullptr); *loc = tCoords + origin; if (stiResult) *stiResult = sti; return tiRes; } /* ============================================================================ * NOTE: drawPlatform has been moved to TILELOAD.CPP for now * ============================================================================ */ /* ============================================================================ * Map drawing functions * ============================================================================ */ inline void drawMetaRow(TilePoint coords, Point16 pos) { WorldMapData *curMap = &mapList[currentMapNum]; int16 uOrg = coords.u * kPlatformWidth, vOrg = coords.v * kPlatformWidth; Platform *drawList[maxPlatforms + 1], **put = drawList; int16 mapSizeMask = curMap->mapSize - 1, mapEdgeType = curMap->map->edgeType; uint16 *mapData = curMap->map->mapData; MetaTilePtr *metaArray = curMap->metaList->_tiles; int16 layerLimit; // Poll the mouse so that we can detect double-clicks //PollMouse(); warning("STUB: Check if we need to poll mouse here"); for (; pos.x < tileDrawMap.size.x + kMetaDX; coords.u++, coords.v--, uOrg += kPlatformWidth, vOrg -= kPlatformWidth, pos.x += kMetaTileWidth ) { TilePoint clipCoords; int16 mtile; MetaTilePtr metaPtr; clipCoords.u = (uint16)coords.u % curMap->mapSize; clipCoords.v = (uint16)coords.v % curMap->mapSize; clipCoords.z = 0; if (coords != clipCoords) { switch (mapEdgeType) { case edgeTypeBlack: // continue; case edgeTypeFill0: mtile = 0; break; case edgeTypeFill1: mtile = 1; break; case edgeTypeRepeat: coords.u = CLIP(coords.u, (int16)0, mapSizeMask); coords.v = CLIP(coords.v, (int16)0, mapSizeMask); mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited; break; case edgeTypeWrap: mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited; break; } } else mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited; if (mtile >= curMap->metaCount) mtile = curMap->metaCount - 1; metaPtr = metaArray[mtile]; put = drawList; if (metaPtr == nullptr) return; // REM: Reject whole metatiles based on coords, based on // max height layerLimit = maxPlatforms; for (int i = 0; i < layerLimit; i++) { Platform *p; p = metaPtr->fetchPlatform(currentMapNum, i); if (!p) continue; if (p->roofRipID() == rippedRoofID && rippedRoofID > 0) break; if (p->flags & plVisible) { // REM: precompute this later, by scanning the platform // for individual altitudes p->highestPixel = kTileHeight * (kPlatformWidth - 1) + kMaxTileHeight * 2 + 64; if (pos.y <= 0 || pos.y - p->highestPixel >= tileDrawMap.size.y) continue; *put++ = p; } } *put++ = nullptr; if (drawList[0] != nullptr) { drawPlatform(drawList, pos, uOrg, vOrg); } // gThread::yield(); } } //----------------------------------------------------------------------- // Build the object ripping tables for a specified metatile with a // specified roof ripping ID void buildRipTable( uint16 ripID, RipTable *ripTable, MetaTile *mt) { const int32 initVal = ((int32)maxint16 << 16) | maxint16; int32 *initPtr = (int32 *)ripTable->zTable; // Initialize table mt->ripTableID(currentMapNum) = ripTable->thisID(); ripTable->metaID = mt->thisID(currentMapNum); ripTable->ripID = ripID; for (uint i = 0; i < sizeof(ripTable->zTable) / sizeof(initVal); i++) *initPtr++ = initVal; // If there is no roof ripping ID, we're done if (ripID == 0) return; // Determine number of tile positions in meta tile for which to // calculate object ripping altitude int16 tilesToGo = kPlatformWidth * kPlatformWidth; for (uint i = 0; i < maxPlatforms; i++) { Platform *p; if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr) continue; if (p->roofRipID() != ripID) continue; for (; i < maxPlatforms && tilesToGo > 0; i++) { if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr) continue; uint16 platHeight = p->height << 3; int16 u, v; for (u = 0; u < kPlatformWidth; u++) for (v = 0; v < kPlatformWidth; v++) if (ripTable->zTable[u][v] == maxint16) { TileRef &tr = p->getTileRef(u, v); if (tr.tile != 0) { // Calculate object ripping altitude for // tile position ripTable->zTable[u][v] = platHeight + (tr.tileHeight << 3); tilesToGo--; } } } break; } } //----------------------------------------------------------------------- // Build the object ripping tables for the metatiles in the vicinity of // the center view object void buildRipTables(void) { const int16 regionRadius = kTileUVSize * kPlatformWidth * 2; TilePoint actorCoords; MetaTile *mt; TileRegion ripTableReg; MetaTile *mtTable[25]; // Largest region is 5x5 int16 mtTableSize = 0; getViewTrackPos(actorCoords); ripTableCoords.u = actorCoords.u >> (kTileUVShift + kPlatShift); ripTableCoords.v = actorCoords.v >> (kTileUVShift + kPlatShift); ripTableCoords.z = 0; // Calculate the region of meta tile for which to build object // ripping table ripTableReg.min.u = (actorCoords.u - regionRadius) >> kTileUVShift; ripTableReg.min.v = (actorCoords.v - regionRadius) >> kTileUVShift; ripTableReg.max.u = (actorCoords.u + regionRadius + kTileUVMask) >> kTileUVShift; ripTableReg.max.v = (actorCoords.v + regionRadius + kTileUVMask) >> kTileUVShift; MetaTileIterator mIter(currentMapNum, ripTableReg); // Build meta tile pointer array mt = mIter.first(); while (mt) { mtTable[mtTableSize++] = mt; mt = mIter.next(); } int16 tableIndex; // bit array of available rip tables uint8 tableAvail[(elementsof(ripTableList) + 7) >> 3]; memset(tableAvail, 0xFF, sizeof(tableAvail)); for (int i = 0; i < mtTableSize; i++) { mt = mtTable[i]; RipTable *mtRipTable = mt->ripTable(currentMapNum); // If meta tile aready has a valid object ripping table, simply // recycle it if (mtRipTable && mtRipTable->ripID == rippedRoofID) { // Null out pointer mtTable[i] = nullptr; // Mark the table as unavailable tableIndex = mtRipTable - ripTableList; tableAvail[tableIndex >> 3] &= ~(1 << (tableIndex & 0x7)); } } // Remove empty entries from meta tile pointer array int16 oldMtTableSize = mtTableSize; for (int i = 0, j = 0; i < oldMtTableSize; i++) { if (mtTable[i] != nullptr) mtTable[j++] = mtTable[i]; else mtTableSize--; } for (int i = 0; i < mtTableSize; i++) { mt = mtTable[i]; uint j; // Find available table for (j = 0; j < elementsof(ripTableList); j++) { if (tableAvail[j >> 3] & (1 << (j & 0x7))) break; } tableAvail[j >> 3] &= ~(1 << (j & 0x7)); // If rip table has a valid metatile, remove that meta tile's // reference to its rip table if (ripTableList[j].metaID != NoMetaTile) { MetaTile *rip_mt = MetaTile::metaTileAddress( ripTableList[j].metaID); RipTableID &rt = rip_mt->ripTableID(currentMapNum); // Assign -1 to the meta tile's rip table ID if (RipTable::ripTableAddress(rt) == &ripTableList[j]) rt = -1; } // Build meta tile's object ripping table buildRipTable(rippedRoofID, &ripTableList[j], mt); } } // Determine which metatiles in the local area will have // cutaway roofs... void buildRoofTable(void) { uint16 newRoofID = objRoofID(getViewCenterObject()); if (newRoofID != rippedRoofID) { rippedRoofID = newRoofID; buildRipTables(); } } // Draw all visible metatiles inline void drawMetaTiles(void) { Point32 viewPos; Point16 metaPos; TilePoint baseCoords; //updateHandleRefs(baseCoords); // viewPoint, &sti ); // coordinates of the view window on the map in X,Y (in 16 pixel units) viewPos.x = (tileScroll.x >> kTileDXShift) - (kPlatformWidth * mapList[currentMapNum].mapSize), viewPos.y = (kPlatformWidth * mapList[currentMapNum].mapSize * kTileDX) - tileScroll.y; debugC(2, kDebugTiles, "viewPos = (%d,%d)", viewPos.x, viewPos.y); // coordinates of the view window upper left corner in U,V baseCoords.u = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) + viewPos.x) / (kPlatformWidth * 2); baseCoords.v = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) - viewPos.x) / (kPlatformWidth * 2); baseCoords.z = 0; debugC(2, kDebugTiles, "baseCoords = (%d,%d,%d)", baseCoords.u, baseCoords.v, baseCoords.z); setAreaSound(baseCoords); //+TilePoint(tileRectWidth, tileRectHeight,0)); updateHandleRefs(baseCoords); // viewPoint, &sti ); // coordinates of current metatile (in X,Y), relative to screen metaPos.x = (baseCoords.u - baseCoords.v) * kMetaDX - viewPos.x * kTileDX; metaPos.y = viewPos.y - (baseCoords.u + baseCoords.v) * kMetaDY; debugC(2, kDebugTiles, "metaPos = (%d,%d)", metaPos.x, metaPos.y); // Loop through each horizontal row of metatiles // REM: also account for highest possible platform // (replace 256 constant with better value) for (; metaPos.y < tileDrawMap.size.y + kMetaTileHeight * 4 ; baseCoords.u--, baseCoords.v-- ) { drawMetaRow(baseCoords, metaPos); metaPos.y += kMetaDY; metaPos.x -= kMetaDX; drawMetaRow(TilePoint(baseCoords.u - 1, baseCoords.v, 0), metaPos); metaPos.y += kMetaDY; metaPos.x += kMetaDX; } } /* ===================================================================== * Mouse Pointer Fixup Routine * ===================================================================== */ // This routine draws an image of the mouse pointer onto the // back buffer, and also updates the mouse pointer's backsave // buffer with the new data underneath it. // // Since the blitting of the backsave buffer takes so long, // it would cause annoying flicker to have to hide the mouse // pointer during the blit. Instead, we go ahead and over-write // the image of the mouse pointer with the image of the new // mouse pointer. inline void drawTileMousePointer(void) { gPixelMap *currentPtr, *saveMap; Point16 offset; Rect16 saveExtent, blitExtent; // Get the image of the pointer and the hotspot offset currentPtr = pointer.getImage(offset); // If pointer exists, and is in a visible state if (currentPtr && pointer.isShown()) { // Get address of pointer's backsave rect saveMap = pointer.getSaveMap(saveExtent); // If the pointer overlaps the tile scrolling area if (saveExtent.overlap(tileRect)) { // get the intersecting area blitExtent = intersect(saveExtent, tileRect); mouseSavePort.setMap(saveMap); // Blit the tile data into the backsave buffer mouseSavePort.bltPixels( tileDrawMap, blitExtent.x - tileRect.x + fineScroll.x, blitExtent.y - tileRect.y + fineScroll.y, blitExtent.x - saveExtent.x, blitExtent.y - saveExtent.y, blitExtent.width, blitExtent.height); // Blit the mouse pointer onto the tile map int x, y; x = mouseState.pos.x + offset.x + fineScroll.x - tileRect.x; y = mouseState.pos.y + offset.y + fineScroll.y - tileRect.y; // if ( x >=0 && y >=0 ) TBlit(&tileDrawMap, currentPtr, x, y); } } } /* ===================================================================== * Tile masking * ===================================================================== */ enum maskRules { maskRuleNever, // never mask maskRuleAlways, // always mask // Mask based on U threshold maskRuleUClose, maskRuleUMed, maskRuleUFar, // Mask based on V threshold maskRuleVClose, maskRuleVMed, maskRuleVFar, // Mask based on vertical distance maskRuleYClose, maskRuleYMed, maskRuleYFar, // Mask based on combined U & V maskRuleConvexNear, maskRuleConcaveFar, maskRuleConvexFar, maskRuleConcaveNear, // More mask types to come! }; const int thresh1 = 0, thresh2 = kTileUVSize / 4, thresh3 = kTileUVSize - 1; // l is the relative position of the character with repect // to the tile in U,V coords. inline bool maskRule(TilePoint &l, TileInfo &ti) { int16 slopeHeight = ptHeight(l, ti.attrs.cornerHeight); // If it's a tall tile, and character is above the height of // the tile, then don't mask. /* if (ti.attrs.height > tileDX * 2) { int16 a = 0; a++; }*/ if ((l.z >= ti.attrs.terrainHeight && l.z >= slopeHeight) || l.u < -3 || l.v < -3) return false; if (l.u > 0 && l.v > 0) { if (l.u > thresh3 || l.v > thresh3) { if (l.z < slopeHeight - 8) return true; } else { if (l.z < slopeHeight - 56) return true; } } // if (ti.attrs.height > tileDX * 2) // { // ti.attrs.maskRule = maskRuleConcaveNear; // } // else ti.attrs.maskRule = maskRuleNever; // REM: Check for special-shaped actors... switch (ti.attrs.maskRule) { case maskRuleNever: // if (l.z < -8 && (l.u > thresh3 || l.v > thresh3)) return true; return false; case maskRuleAlways: return true; case maskRuleUClose: return (l.u > thresh1); case maskRuleUMed: return (l.u > thresh2); case maskRuleUFar: return (l.u > thresh3); case maskRuleVClose: return (l.v > thresh1); case maskRuleVMed: return (l.v > thresh2); case maskRuleVFar: return (l.v > thresh3); case maskRuleYClose: return (l.u + l.v > thresh1 + thresh1); case maskRuleYMed: return (l.u + l.v > thresh2 + thresh2); case maskRuleYFar: return (l.u + l.v > thresh3 + thresh3); case maskRuleConvexNear: return (l.u > thresh1 && l.v > thresh1); case maskRuleConvexFar: return (l.u > thresh2 && l.v > thresh2); case maskRuleConcaveNear: return (l.u > thresh2 || l.v > thresh2); case maskRuleConcaveFar: return (l.u > thresh3 || l.v > thresh3); } return false; } void maskPlatform( gPixelMap &sMap, Platform **pList, // platforms to draw Point16 screenPos, // screen position TilePoint relLoc, // relative location int16 uOrg, // for TAG search int16 vOrg) { // for TAG search int16 u, v; int16 right = sMap.size.x, bottom = sMap.size.y; Point16 tilePos; int16 x = screenPos.x, x2 = x / kTileDX; int16 length = 1; TilePoint rLoc; TilePoint origin(uOrg, vOrg, 0); tilePos.y = screenPos.y - (kPlatformWidth - 1) * kTileHeight; u = kPlatformWidth - 1; v = kPlatformWidth - 1; relLoc.u = - relLoc.u - (kPlatformWidth - 1) * kTileUVSize; relLoc.v = - relLoc.v - (kPlatformWidth - 1) * kTileUVSize; for (int row = 0; row < 15; row++) { if (tilePos.y > 0) { int16 col = 0; TilePoint pCoords(u, v, 0); tilePos.x = x; rLoc = relLoc; if (length > x2) { int16 offset = (length - x2) >> 1; pCoords.u += offset; pCoords.v -= offset; rLoc.u -= offset * kTileUVSize; rLoc.v += offset * kTileUVSize; offset <<= 1; col += offset; tilePos.x += kTileDX * offset; } for (; col < length && tilePos.x <= right; col += 2, pCoords.u++, pCoords.v--, rLoc.u -= kTileUVSize, rLoc.v += kTileUVSize, tilePos.x += kTileWidth ) { Platform **pGet; if (tilePos.x < 0) continue; if (rLoc.u <= -kTileUVSize || rLoc.v <= -kTileUVSize) continue; for (pGet = pList; *pGet; pGet++) { Platform &p = **pGet; int16 h, y; TileInfo *ti; uint8 *imageData; int16 trFlags; ti = p.fetchTile( currentMapNum, pCoords, origin, &imageData, h, trFlags); if (ti == nullptr) continue; // Compute height of character above tile. rLoc.z = relLoc.z - h; if (maskRule(rLoc, *ti)) { y = tilePos.y - h; // REM: Check for AltMask!!! if (ti->attrs.height > 0 && y < bottom + ti->attrs.height - 1) { maskTile(&sMap, tilePos.x, y, ti->attrs.height, imageData); } } } } } if (row < 7) { x -= kTileDX; x2++; length += 2; u--; relLoc.u += kTileUVSize; } else { x += kTileDX; x2--; length -= 2; v--; relLoc.v += kTileUVSize; } tilePos.y += kTileDY; } } void maskMetaRow( gPixelMap &sMap, TilePoint coords, TilePoint relLoc, Point16 pos, uint16 roofID) { WorldMapData *curMap = &mapList[currentMapNum]; int16 uOrg = coords.u * kPlatformWidth, vOrg = coords.v * kPlatformWidth; Platform *drawList[maxPlatforms + 1], **put = drawList; int16 mapSizeMask = curMap->mapSize - 1, mapEdgeType = curMap->map->edgeType; uint16 *mapData = curMap->map->mapData; MetaTilePtr *metaArray = curMap->metaList->_tiles; int16 layerLimit; for (; pos.x < sMap.size.x + kMetaDX; coords.u++, coords.v--, relLoc.u += kPlatUVSize, relLoc.v -= kPlatUVSize, uOrg += kPlatformWidth, vOrg -= kPlatformWidth, pos.x += kMetaTileWidth ) { TilePoint clipCoords; int16 mtile; MetaTilePtr metaPtr; clipCoords.u = (uint16)coords.u % curMap->mapSize; clipCoords.v = (uint16)coords.v % curMap->mapSize; clipCoords.z = 0; if (coords != clipCoords) { switch (mapEdgeType) { case edgeTypeBlack: // continue; case edgeTypeFill0: mtile = 0; break; case edgeTypeFill1: mtile = 1; break; case edgeTypeRepeat: coords.u = clamp(0, coords.u, mapSizeMask); coords.v = clamp(0, coords.v, mapSizeMask); mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited; break; case edgeTypeWrap: mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited; break; } } else mtile = mapData[clipCoords.u * curMap->mapSize + clipCoords.v] & ~metaTileVisited; if (mtile >= curMap->metaCount) mtile = curMap->metaCount - 1; metaPtr = metaArray[mtile]; put = drawList; if (metaPtr == nullptr) return; // REM: Reject whole metatiles based on coords, based on // max height layerLimit = maxPlatforms; for (int i = 0; i < layerLimit; i++) { Platform *p; if ((p = metaPtr->fetchPlatform(currentMapNum, i)) == nullptr) continue; if (p->roofRipID() == roofID && roofID > 0) break; if (p->flags & plVisible) { // REM: precompute this later, by scanning the platform // for individual altitudes p->highestPixel = kTileHeight * (kPlatformWidth - 1) + kMaxTileHeight + 192; if (pos.y <= 0 || pos.y - p->highestPixel >= sMap.size.y) continue; *put++ = p; } } *put++ = nullptr; if (drawList[0] != nullptr) { maskPlatform(sMap, drawList, pos, relLoc, uOrg, vOrg); } } } void drawTileMask( const Point16 &sPos, gPixelMap &sMap, TilePoint loc, uint16 roofID = rippedRoofID) { Point32 aPos; Point32 viewPos; Point16 metaPos; TilePoint baseCoords; TilePoint relLoc; // Compute bitmap's position in absolute terms on map aPos.x = sPos.x + tileScroll.x - fineScroll.x; aPos.y = sPos.y + tileScroll.y - fineScroll.y; // coordinates of the view window on the map in X,Y (in 16 pixel units) viewPos.x = (aPos.x >> kTileDXShift) - (kPlatformWidth * mapList[currentMapNum].mapSize), viewPos.y = (kPlatformWidth * mapList[currentMapNum].mapSize << kTileDXShift) - aPos.y; // coordinates of the view window upper left corner in U,V baseCoords.u = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) + viewPos.x) / (kPlatformWidth * 2); baseCoords.v = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) - viewPos.x) / (kPlatformWidth * 2); baseCoords.z = 0; // coordinates of current metatile (in X,Y), relative to screen metaPos.x = (baseCoords.u - baseCoords.v) * kMetaDX - viewPos.x * kTileDX; metaPos.y = viewPos.y - (baseCoords.u + baseCoords.v) * kMetaDY; // Compute where the object is relative to the metatile coords relLoc.u = (baseCoords.u * kPlatUVSize) - loc.u; relLoc.v = (baseCoords.v * kPlatUVSize) - loc.v; relLoc.z = loc.z; // Loop through each horizontal row of metatiles // REM: also account for highest possible platform // (replace 256 constant with better value) for (; metaPos.y < sMap.size.y + kMetaTileHeight * 4 ; baseCoords.u--, baseCoords.v-- ) { maskMetaRow(sMap, baseCoords, relLoc, metaPos, roofID); metaPos.y += kMetaDY; metaPos.x -= kMetaDX; relLoc.u -= kPlatUVSize; maskMetaRow(sMap, TilePoint(baseCoords.u - 1, baseCoords.v, 0), relLoc, metaPos, roofID); metaPos.y += kMetaDY; metaPos.x += kMetaDX; relLoc.v -= kPlatUVSize; } } /* ===================================================================== * Tile picking * ===================================================================== */ #if DEBUG bool showTile = false; const uint16 lowerRightMask = 0x1111; const uint16 lowerLeftMask = 0x000F; inline void drawSubTiles(const TilePoint &tp, uint16 subTileMask, uint8 *cornerHeight) { TilePoint pt1, pt2; uint8 subTileNo; uint16 curSubTileMask; pt1.z = 0; pt2.z = 0; for (subTileNo = 0; subTileNo < 16; subTileNo++) { curSubTileMask = (1 << subTileNo); if (curSubTileMask & subTileMask) { // check if we're drawing tile to lower left if (!((curSubTileMask >> 4) & subTileMask) || curSubTileMask & lowerLeftMask) { pt1.u = pt2.u = (subTileNo & 0x0C); pt1.v = ((subTileNo & 0x03) << 2); pt2.v = pt1.v + 4; pt1.z = ptHeight(pt1, cornerHeight); pt2.z = ptHeight(pt2, cornerHeight); TPLine(tp + pt1, tp + pt2); } // check if we're drawing tile to lower right if (!((curSubTileMask >> 1) & subTileMask) || curSubTileMask & lowerRightMask) { pt1.u = (subTileNo & 0x0C); pt2.u = pt1.u + 4; pt1.v = pt2.v = ((subTileNo & 0x03) << 2); pt1.z = ptHeight(pt1, cornerHeight); pt2.z = ptHeight(pt2, cornerHeight); TPLine(tp + pt1, tp + pt2); } // draw upper right pt1.u = pt2.u = (subTileNo & 0x0C) + 4; pt1.v = ((subTileNo & 0x03) << 2); pt2.v = pt1.v + 4; pt1.z = ptHeight(pt1, cornerHeight); pt2.z = ptHeight(pt2, cornerHeight); TPLine(tp + pt1, tp + pt2); // draw upper left pt1.u = (subTileNo & 0x0C); pt2.u = pt1.u + 4; pt1.v = pt2.v = ((subTileNo & 0x03) << 2) + 4; pt1.z = ptHeight(pt1, cornerHeight); pt2.z = ptHeight(pt2, cornerHeight); TPLine(tp + pt1, tp + pt2); } } } void showAbstractTile(const TilePoint &tp, TileInfo *ti) { TilePoint workTp = tp; uint8 *chPtr; uint8 raisedCornerHeight[4] = { 0, 0, 0, 0 }; if (ti->combinedTerrainMask() & terrainRaised) { if ((1L << ti->attrs.bgdTerrain) & terrainRaised) { workTp.z += ti->attrs.terrainHeight; chPtr = raisedCornerHeight; } else chPtr = ti->attrs.cornerHeight; drawSubTiles(workTp, ~ti->attrs.terrainMask, chPtr); workTp.z = tp.z; if ((1L << ti->attrs.fgdTerrain) & terrainRaised) { workTp.z += ti->attrs.terrainHeight; chPtr = raisedCornerHeight; } else chPtr = ti->attrs.cornerHeight; drawSubTiles(workTp, ti->attrs.terrainMask, chPtr); } else drawSubTiles(workTp, 0xFFFF, ti->attrs.cornerHeight); TilePoint pt1 = tp + TilePoint(0, 0, 0), pt2 = tp + TilePoint(16, 0, 0), pt3 = tp + TilePoint(0, 16, 0), pt4 = tp + TilePoint(16, 16, 0); TPLine(pt1, pt2); TPLine(pt1, pt3); TPLine(pt2, pt4); TPLine(pt3, pt4); } #endif // Compute the picked position as if the mouse were pointing at the same // level as the protagainist's feet. TilePoint pickTilePos(Point32 pos, const TilePoint &protagPos) { TilePoint coords; pos.x += tileScroll.x; pos.y += tileScroll.y + protagPos.z; coords = XYToUV(pos); coords.z = protagPos.z; return coords; } // Inspect packed tile bitmap to determine if a pixel is opaque. bool isTilePixelOpaque(int16 baseX, int16 baseY, int16 mapHeight, uint8 *td) { bool opaque; int16 x = baseX + kTileDX, y = mapHeight - baseY, accum = 0; if (y < 0 || y >= mapHeight) return false; while (y) { // skip initial transparency accum = *td; td++; while (accum < kTileWidth) { // skip opaque run accum += *td; td += *td + 1; // skip transparency accum += *td; td++; } y--; } // skip initial transparency x -= *td; td++; opaque = false; while (x >= 0) { x -= *td; if (opaque) { // skip transparency td++; opaque = false; } else { // skip opaque run td += *td + 1; opaque = true; } } return opaque; } //----------------------------------------------------------------------- // Return the exact TilePoint the mouse is pointing at on the specified // tile. SurfaceType pointOnTile(TileInfo *ti, const Point32 &tileRel, int16 h, const TilePoint &tCoords, TilePoint &pickCoords, TilePoint &floorCoords) { Point32 relPos = tileRel; TilePoint subTile; Point16 subTileRel; int16 sMask; int32 combinedMask; uint16 yBound; uint8 pointH; uint16 subUVPointRel; TilePoint subUVPoint; SurfaceType type = surfaceHoriz; // Get the tile's terrain mask combinedMask = ti->attrs.testTerrain((int16)0xFFFF); // Adjust the relative X coordinate to ensure it is actually within // the tile's boundaries. relPos.x = clamp(-kTileDX + 2, relPos.x, kTileDX - 1); // If the tile has no raised terrain if (!(combinedMask & terrainRaised)) { // Calculate the position of the first point on tile to check. if (relPos.x > 0) { subUVPoint.u = relPos.x >> 1; subUVPoint.v = 0; subUVPointRel = relPos.y - (relPos.x >> 1) - h; } else { subUVPoint.u = 0; subUVPoint.v = (-relPos.x + 1) >> 1; subUVPointRel = relPos.y + (relPos.x >> 1) - h; } // Compute the terrain hieght of the first point pointH = ptHeight(subUVPoint, ti->attrs.cornerHeight); while (subUVPoint.u < 16 && subUVPoint.v < 16) { if (subUVPointRel < pointH + (kSubTileDY * 2) / kSubTileSize) { pickCoords = (tCoords << kTileUVShift); pickCoords.u += subUVPoint.u; pickCoords.v += subUVPoint.v; pickCoords.z = h + pointH; floorCoords = pickCoords; break; } // Test next point on tile subUVPoint.u++; subUVPoint.v++; if (subUVPoint.u < 16 && subUVPoint.v < 16) { subUVPointRel -= (kSubTileDY * 2) / kSubTileSize; // Compute the terrain height of point pointH = ptHeight(subUVPoint, ti->attrs.cornerHeight); } else { // If we've moved past the last point on the tile, // adjust the subUVPointRel to point to the top of the // last point checked. subUVPoint.u--; subUVPoint.v--; subUVPointRel = pointH + ((kSubTileDY * 2) / kSubTileSize) - 1; } } } // The tile has raised terrain else { int16 y; TilePoint lastRaisedSubTile(-1, -1, -1); // Compute the closest subtile which is directly // underneath, and the coodinates of the mouse pick // relative to that subtile. if (relPos.x > 0) { subTile.u = relPos.x >> kSubTileDXShift; subTile.v = 0; subTileRel.x = relPos.x - (subTile.u << kSubTileDXShift); subTileRel.y = relPos.y - (subTile.u << kSubTileDYShift) - h; } else { subTile.u = 0; subTile.v = (-relPos.x + 1) >> kSubTileDXShift; subTileRel.x = relPos.x + (subTile.v << kSubTileDXShift); subTileRel.y = relPos.y - (subTile.v << kSubTileDYShift) - h; } // Compute the mask which represents the subtile sMask = calcSubTileMask(subTile.u, subTile.v); yBound = abs(subTileRel.x >> 1); while (subTileRel.y >= 0 && subTile.u < 4 && subTile.v < 4) { if (ti->attrs.testTerrain(sMask) & terrainRaised) { lastRaisedSubTile = subTile; // mouse is on side of raised section if (subTileRel.y < ti->attrs.terrainHeight + yBound) { pickCoords = (tCoords << kTileUVShift); pickCoords.u += (subTile.u << kSubTileShift); pickCoords.v += (subTile.v << kSubTileShift); if (subTileRel.x > 1) { pickCoords.u += yBound; type = surfaceVertU; } else if (subTileRel.x < 0) { pickCoords.v += yBound; type = surfaceVertV; } else { bool subTileToRight = false, subTileToLeft = false; if (subTile.u > 0 && (ti->attrs.testTerrain( calcSubTileMask( subTile.u - 1, subTile.v)) & terrainRaised)) subTileToLeft = true; if (subTile.v > 0 && (ti->attrs.testTerrain( calcSubTileMask( subTile.u, subTile.v - 1)) & terrainRaised)) subTileToRight = true; if ((subTileToRight && subTileToLeft) || (!subTileToRight && ! subTileToLeft)) { if (subTileRel.x > 0) { pickCoords.u += yBound; type = surfaceVertU; } else { pickCoords.v += yBound; type = surfaceVertV; } } else if (subTileToLeft) { pickCoords.u += yBound; type = surfaceVertU; } else { pickCoords.v += yBound; type = surfaceVertV; } } floorCoords.u = pickCoords.u - 1; floorCoords.v = pickCoords.v - 1; pickCoords.z = h + subTileRel.y - yBound; if (subTile.u < 1 || subTile.v < 1) floorCoords.z = h; else floorCoords.z = h + ptHeight(TilePoint(floorCoords.u & kTileUVMask, floorCoords.v & kTileUVMask, 0), ti->attrs.cornerHeight); break; } // mouse is on top of raised section if (subTileRel.y < ti->attrs.terrainHeight + kSubTileDY * 2 - yBound) { pickCoords = (tCoords << kTileUVShift); y = subTileRel.y - ti->attrs.terrainHeight; pickCoords.u += (subTile.u << kSubTileShift) + (((subTileRel.x >> 1) + y) >> 1); pickCoords.v += (subTile.v << kSubTileShift) + ((y - (subTileRel.x >> 1)) >> 1); pickCoords.z = h + ti->attrs.terrainHeight; floorCoords = pickCoords; break; } } else { // mouse is on unraised section bool foundPoint = false; // Calculate the position of the first point on subtile // to check. if (subTileRel.x > 0) { subUVPoint.u = subTileRel.x >> 1; subUVPoint.v = 0; subUVPointRel = subTileRel.y - (subTileRel.x >> 1); } else { subUVPoint.u = 0; subUVPoint.v = (-subTileRel.x + 1) >> 1; subUVPointRel = subTileRel.y + (subTileRel.x >> 1); } // Compute the terrain hieght of the first point pointH = ptHeight((subTile << 2) + subUVPoint, ti->attrs.cornerHeight); while (subUVPoint.u < 4 && subUVPoint.v < 4) { if (subUVPointRel < pointH + (kSubTileDY * 2) / kSubTileSize) { pickCoords = (tCoords << kTileUVShift); pickCoords.u += (subTile.u << kSubTileShift) + subUVPoint.u; pickCoords.v += (subTile.v << kSubTileShift) + subUVPoint.v; pickCoords.z = h + pointH; floorCoords = pickCoords; foundPoint = true; break; } // Test next point on subtile subUVPoint.u++; subUVPoint.v++; subUVPointRel -= (kSubTileDY * 2) / kSubTileSize; pointH = ptHeight((subTile << kSubTileShift) + subUVPoint, ti->attrs.cornerHeight); } if (foundPoint) break; } if (subTileRel.x & 0xFFFE) { // if subTileRel.x != 0 or 1 // crabwalk up the subtiles if (subTileRel.x > 0) { subTileRel.x -= kSubTileDX; subTile.u++; sMask <<= kSubTileMaskUShift; } else { subTileRel.x += kSubTileDX; subTile.v++; sMask <<= kSubTileMaskVShift; } subTileRel.y -= kSubTileDY; } else { // subTileRel.x == 0 or 1 // move up to the next vertical subtile subTile.u++; subTile.v++; sMask <<= kSubTileMaskUShift + kSubTileMaskVShift; subTileRel.y -= kSubTileDY * 2; } yBound = abs(subTileRel.x >> 1); if (subTile.u >= 4 || subTile.v >= 4) { // No subtile was found, so lets move the pointer. if (lastRaisedSubTile.u != -1) { // If a raised subtile was already checked move the // pointer back down to the top of that subtile and // try again. subTile = lastRaisedSubTile; subTileRel.x = relPos.x - ((subTile.u - subTile.v) << kSubTileDXShift); subTileRel.y = ti->attrs.terrainHeight + kSubTileDY * 2 - abs(subTileRel.x >> 1) - 1; sMask = calcSubTileMask(subTile.u, subTile.v); yBound = abs(subTileRel.x >> 1); } else { // If there were no raised subtiles checked, move the // pointer laterally to the nearest raised subtile. uint16 colMask; int8 curSubTileCol, rightSubTileCol, leftSubTileCol, raisedCol = -4; if (relPos.x & (kSubTileDX - 1) & 0xFFFE) { if (relPos.x > 0) { curSubTileCol = relPos.x >> kSubTileDXShift; rightSubTileCol = curSubTileCol + 2; leftSubTileCol = curSubTileCol - 1; goto testLeft; } else { curSubTileCol = (relPos.x + kSubTileDX - 1) >> kSubTileDXShift; leftSubTileCol = curSubTileCol - 2; rightSubTileCol = curSubTileCol + 1; } } else { curSubTileCol = relPos.x >> kSubTileDXShift; rightSubTileCol = curSubTileCol + 1; leftSubTileCol = curSubTileCol - 1; } // Search subtile columns to the left and right for raised // terrain. while (rightSubTileCol < 4 || leftSubTileCol > -4) { if (rightSubTileCol < 4) { if (rightSubTileCol > 0) colMask = 0x8421 << (rightSubTileCol << 2); else colMask = 0x8421 >> ((-rightSubTileCol) << 2); if (ti->attrs.testTerrain(colMask) & terrainRaised) { raisedCol = rightSubTileCol; subTileRel.x = -kSubTileDX + 2; break; } rightSubTileCol++; } testLeft: if (leftSubTileCol > -4) { if (leftSubTileCol > 0) colMask = 0x8421 << (leftSubTileCol << 2); else colMask = 0x8421 >> ((-leftSubTileCol) << 2); if (ti->attrs.testTerrain(colMask) & terrainRaised) { raisedCol = leftSubTileCol; subTileRel.x = kSubTileDX - 1; break; } leftSubTileCol--; } } // if no raised terrain was found, give up if (raisedCol == -4) break; // compute the number of subtiles in column int8 subsInCol = 4 - abs(raisedCol); relPos.x = (raisedCol << kSubTileDXShift) + subTileRel.x; if (raisedCol > 0) { colMask = 0x0001 << (raisedCol << 2); subTile.u = raisedCol; subTile.v = 0; } else { colMask = 0x0001 << (-raisedCol); subTile.u = 0; subTile.v = -raisedCol; } // test each subtile in column for first raised // subtile while (subsInCol && !(ti->attrs.testTerrain(colMask) & terrainRaised)) { subsInCol--; subTile.u++; subTile.v++; colMask <<= 5; } // subTile is now the first raised subtile in // column subTileRel.y = relPos.y - ((subTile.u + subTile.v) * kSubTileDY) - h; sMask = calcSubTileMask(subTile.u, subTile.v); yBound = abs(subTileRel.x >> 1); } } } } return type; } //----------------------------------------------------------------------- // Determine if picked point on tile is on an exposed surface. bool pointOnHiddenSurface( const TilePoint &tileCoords, const TilePoint &pickCoords, SurfaceType surfaceType) { assert(surfaceType == surfaceVertU || surfaceType == surfaceVertV); WorldMapData *curMap = &mapList[currentMapNum]; TilePoint testCoords, mCoords, tCoords, origin; MetaTile *mt; // Determine pick point relative to base of tile testCoords = pickCoords; testCoords.u &= kTileUVMask; testCoords.v &= kTileUVMask; // If picked point is not along edge of tile, then its not hidden if ((surfaceType == surfaceVertV && testCoords.u != 0) || (surfaceType == surfaceVertU && testCoords.v != 0)) return false; TileInfo *adjTile; TilePoint adjTCoords = tileCoords; uint16 adjSubMask; // Determine the tile coordinates of adjacent tile and the mask // of the subtile to test on that tile. if (surfaceType == surfaceVertV) { assert(testCoords.u == 0); adjTCoords.u--; adjSubMask = 0x1000 << (testCoords.v >> kSubTileShift); } else { assert(testCoords.v == 0); adjTCoords.v--; adjSubMask = 0x0008 << (testCoords.u & ~kSubTileMask); } mCoords = adjTCoords >> kPlatShift; // If metatile of adjacent tile does not exist, the pick point // is valid. if ((mt = curMap->lookupMeta(mCoords)) == nullptr) return false; tCoords.u = adjTCoords.u & kPlatMask; tCoords.v = adjTCoords.v & kPlatMask; tCoords.z = 0; origin = mCoords << kPlatShift; int i; for (i = 0; i < maxPlatforms; i++) { Platform *p; int16 h, trFlags; if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr) continue; if (!(p->flags & plVisible) || platformRipped(p)) continue; // Fetch the tile at this location adjTile = p->fetchTile( currentMapNum, tCoords, origin, h, trFlags); if (adjTile == nullptr) continue; // If current tile is higher or lower than the picked point // skip this tile. if (h > pickCoords.z) continue; if (h + adjTile->attrs.terrainHeight <= pickCoords.z) continue; // If adjacent subtile is not raised, skip this tile if (!(adjTile->attrs.testTerrain(adjSubMask) & terrainRaised)) continue; break; } // If all platforms have been checked, the pick point is valid if (i >= maxPlatforms) return false; return true; } //----------------------------------------------------------------------- // Return the TilePoint at which the mouse it pointing TilePoint pickTile(Point32 pos, const TilePoint &protagPos, TilePoint *floorResult, ActiveItemPtr *pickTAI) { WorldMapData *curMap = &mapList[currentMapNum]; TilePoint pickCoords, floorCoords, pCoords, fCoords, coords, tileCoords; Point32 relPos; // pick pos relative to tile. int16 zMax, zMin, mag; TilePoint mCoords, tCoords, origin, testCoords, deltaP; MetaTile *mt; ActiveItemPtr bestTileTAI; TileInfo *ti, *bestTile = nullptr; uint8 *imageData; int i; #ifdef DAVIDR TilePoint bestTP; #endif // First, calculate the mouse click coords naively -- in other // words, assume that the Z coordinate of the click is the same // as the protagonist's feet. These coordinates will be used // if no tiles can be located which contain surfaces. // Calculate the mouse click position on the map pos.x += tileScroll.x; pos.y += tileScroll.y + protagPos.z; pickCoords = XYToUV(pos); pickCoords.z = protagPos.z; floorCoords = pickCoords; // Now we do a different pick routine, one that considers the // mouse click as a "rifle shot" which penetrates the screen. // We'll attempt to check which surfaces are penetrated by the // shot. // First, move the pick point back down the floor pos.y -= protagPos.z; // Compute the pick coordinates as if the protagonist were // at ground level. coords = XYToUV(pos); coords.z = 0; // Compute the coords of the middle of the current tile. coords.u = (coords.u & ~kTileUVMask) + kTileUVSize / 2; coords.v = (coords.v & ~kTileUVMask) + kTileUVSize / 2; // Since the protagonist has a limited ability to "step" up or // down levels, only search for surfaces which could be stepped // on by the protagonist. mag = (coords - protagPos).quickHDistance(); zMin = protagPos.z - kMaxPickHeight - mag; zMax = protagPos.z + kMaxPickHeight + mag; // Compute the coords of the actual tile that they clicked on. tileCoords = coords >> kTileUVShift; // Compute the X and Y offset of the exact mouse click point // relative to the base of the tile. relPos.x = pos.x - curMap->mapHeight - (tileCoords.u - tileCoords.v) * kTileDX; relPos.y = curMap->mapHeight - pos.y - (tileCoords.u + tileCoords.v) * kTileDY; // Compute which metatile the click occured on, and the tile // within that metatile, and the origin coords of the metatile mCoords = tileCoords >> kPlatShift; tCoords.u = tileCoords.u & kPlatMask; tCoords.v = tileCoords.v & kPlatMask; tCoords.z = 0; origin = mCoords << kPlatShift; // Lookup the metatile mt = curMap->lookupMeta(mCoords); // While we are less than the pick altitude while (relPos.y < zMax + kTileDX + kMaxStepHeight - abs(relPos.x >> 1)) { // If there is a metatile on this spot if (mt != nullptr) { // Iterate through all platforms for (i = 0; i < maxPlatforms; i++) { Platform *p; StandingTileInfo sti; if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr) continue; if (platformRipped(p)) break; if (!(p->flags & plVisible)) continue; // Fetch the tile at this location ti = p->fetchTAGInstance( currentMapNum, tCoords, origin, &imageData, sti); if (ti == nullptr) continue; // Reject the tile if it's too low. if (sti.surfaceHeight + ti->attrs.terrainHeight < zMin) continue; // Reject the tile if it's too high. if (sti.surfaceHeight > zMax + kMaxStepHeight) continue; // Reject the tile if mouse position is below lower tile // boundary if ((relPos.y - sti.surfaceHeight) < abs(relPos.x >> 1)) continue; if (ti->attrs.height > 0) { if (isTilePixelOpaque(relPos.x, relPos.y - sti.surfaceHeight, ti->attrs.height, imageData)) { SurfaceType surface; // Determine picked point on tile surface = pointOnTile(ti, relPos, sti.surfaceHeight, tCoords + origin, pCoords, fCoords); if (sti.surfaceTAG == nullptr) { if (surface != surfaceHoriz && pointOnHiddenSurface( tCoords + origin, pCoords, surface)) { surface = surface == surfaceVertU ? surfaceVertV : surfaceVertU; } // If pick point is on vertical surface // not facing protaganist, reject tile if (surface == surfaceVertU && pCoords.v < protagPos.v) continue; if (surface == surfaceVertV && pCoords.u < protagPos.u) continue; /* // Make sure surface is exposed, else reject it if ( surface != surfaceHoriz && !validSurface( tCoords + origin, pCoords ) ) continue;*/ } pickCoords = pCoords; floorCoords = fCoords; bestTile = ti; #ifdef DAVIDR bestTP = tCoords + origin; bestTP.z = sti.surfaceHeight; #endif bestTileTAI = sti.surfaceTAG; } } } } // Crabwalk down through the tile positions if (relPos.x < 0) { tCoords.u--; coords.u -= kTileUVSize; if (tCoords.u < 0) { tCoords.u = kPlatformWidth - 1; mCoords.u--; origin = mCoords << kPlatShift; mt = curMap->lookupMeta(mCoords); } relPos.x += kTileDX; } else { tCoords.v--; coords.v -= kTileUVSize; if (tCoords.v < 0) { tCoords.v = kPlatformWidth - 1; mCoords.v--; origin = mCoords << kPlatShift; mt = curMap->lookupMeta(mCoords); } relPos.x -= kTileDX; } relPos.y += kTileDY; // Compute new altitude range based upon the tile position // relative to the protaganist's position. zMin = protagPos.z - kMaxPickHeight - (coords - protagPos).quickHDistance(); zMax = protagPos.z + kMaxPickHeight + (coords - protagPos).quickHDistance(); } // If no tile was found, return the default. if (!bestTile) { if (floorResult) *floorResult = floorCoords; if (pickTAI) *pickTAI = nullptr; return pickCoords; } #ifdef DAVIDR if (showTile) { if (bestTile) { bestTP.u <<= kTileUVShift; bestTP.v <<= kTileUVShift; showAbstractTile(bestTP, bestTile); TilePoint pt1, pt2; pt1 = pt2 = pickCoords; pt1.u += 3; pt2.u -= 3; TPLine(pt1, pt2); pt1 = pt2 = pickCoords; pt1.v += 3; pt2.v -= 3; TPLine(pt1, pt2); pt1 = pt2 = floorCoords; pt1.u += 2; pt1.v += 2; pt2.u -= 2; pt2.v -= 2; TPLine(pt1, pt2); pt1 = pt2 = floorCoords; pt1.u += 2; pt1.v -= 2; pt2.u -= 2; pt2.v += 2; TPLine(pt1, pt2); pt1 = pt2 = pickCoords; pt1.z = bestTP.z; TPLine(pt1, pt2); pt2.z = bestTP.z; pt2.u = pt1.u & 0xFFF0; TPLine(pt1, pt2); pt2.u = pt1.u; pt2.v = pt1.v & 0xFFF0; TPLine(pt1, pt2); WriteStatusF(8, "%4.4x:%4.4x:%4.4x", pickCoords.u, pickCoords.v, pickCoords.z); } } #endif if (floorResult) *floorResult = floorCoords; if (pickTAI) *pickTAI = bestTileTAI; return pickCoords; } /* ===================================================================== * Tile cycling * ===================================================================== */ void cycleTiles(int32 delta) { if (delta <= 0) return; for (int i = 0; i < cycleCount; i++) { TileCycleData &tcd = cycleList[i]; tcd.counter += tcd.cycleSpeed * delta; if (tcd.counter >= 400) { tcd.counter = 0; tcd.currentState++; if (tcd.currentState >= tcd.numStates) tcd.currentState = 0; } } } struct TileCycleArchive { int32 counter; uint8 currentState; }; //----------------------------------------------------------------------- // Initialize the tile cycling state array static void readCycle(hResContext *con, TileCycleData &cyc) { cyc.counter = con->readS32LE(); cyc.pad = con->readByte(); cyc.numStates = con->readByte(); cyc.currentState = con->readByte(); cyc.cycleSpeed = con->readByte(); for (int i = 0; i < 16; ++i) cyc.cycleList[i] = con->readU16LE(); } void initTileCyclingStates(void) { const int tileCycleDataSize = 40; cycleCount = tileRes->size(cycleID) / tileCycleDataSize; cycleList = new TileCycleData[cycleCount]; tileRes->seek(cycleID); for (int i = 0; i < cycleCount; ++i) readCycle(tileRes, cycleList[i]); debugC(2, kDebugLoading, "Loaded Cycles: cycleCount = %d", cycleCount); if (cycleList == nullptr) error("Unable to load tile cycling data"); } //----------------------------------------------------------------------- // Save the tile cycling state array in a save file void saveTileCyclingStates(SaveFileConstructor &saveGame) { TileCycleArchive *archiveBuffer; int16 i; archiveBuffer = new TileCycleArchive[cycleCount](); if (archiveBuffer == nullptr) error("Unable to allocate tile cycle data archive buffer"); for (i = 0; i < cycleCount; i++) { archiveBuffer[i].counter = cycleList[i].counter; archiveBuffer[i].currentState = cycleList[i].currentState; } saveGame.writeChunk( MakeID('C', 'Y', 'C', 'L'), archiveBuffer, sizeof(TileCycleArchive) * cycleCount); delete[] archiveBuffer; } //----------------------------------------------------------------------- // Load the tile cycling state array from a save file void loadTileCyclingStates(SaveFileReader &saveGame) { TileCycleArchive *archiveBuffer; int16 i; initTileCyclingStates(); assert(saveGame.getChunkSize() == (int)sizeof(TileCycleArchive) * cycleCount); archiveBuffer = new TileCycleArchive[cycleCount](); if (archiveBuffer == nullptr) error("Unable to allocate tile cycle data archive buffer"); saveGame.read(archiveBuffer, sizeof(TileCycleArchive) * cycleCount); for (i = 0; i < cycleCount; i++) { cycleList[i].counter = archiveBuffer[i].counter; cycleList[i].currentState = archiveBuffer[i].currentState; } delete[] archiveBuffer; } //----------------------------------------------------------------------- // Cleanup the tile cycling state array void cleanupTileCyclingStates(void) { if (cycleList != nullptr) { free(cycleList); cycleList = nullptr; } } /* ===================================================================== * objRoofID() -- determine which roof is above object * ===================================================================== */ uint16 objRoofID(GameObject *obj) { return objRoofID(obj, obj->getMapNum(), obj->getLocation()); } uint16 objRoofID(GameObject *obj, int16 objMapNum, const TilePoint &objCoords) { WorldMapData *objMap = &mapList[objMapNum]; TileRegion objTileReg, objMetaReg; int16 objHeight; uint16 objRoofID = 0; int objRoofPlatNum = -1; int16 metaU, metaV; debugC(3, kDebugTiles, "objRoofID:"); debugC(3, kDebugTiles, "- obj = %p; objMapNum = %d; objCoords = (%d,%d,%d)", (void *)obj, objMapNum, objCoords.u, objCoords.v, objCoords.z); objHeight = objCoords.z; objTileReg.min.u = (objCoords.u - kSubTileSize) >> kTileUVShift; objTileReg.min.v = (objCoords.v - kSubTileSize) >> kTileUVShift; objTileReg.max.u = (objCoords.u + kSubTileSize + kTileUVMask) >> kTileUVShift; objTileReg.max.v = (objCoords.v + kSubTileSize + kTileUVMask) >> kTileUVShift; debugC(3, kDebugTiles, "objTileReg = ((%d,%d), (%d,%d))", objTileReg.min.u, objTileReg.min.v, objTileReg.max.u, objTileReg.max.v); objMetaReg.min.u = objTileReg.min.u >> kPlatShift; objMetaReg.min.v = objTileReg.min.v >> kPlatShift; objMetaReg.max.u = (objTileReg.max.u + kPlatMask) >> kPlatShift; objMetaReg.max.v = (objTileReg.max.v + kPlatMask) >> kPlatShift; debugC(3, kDebugTiles, "objMetaReg = ((%d,%d), (%d,%d))", objMetaReg.min.u, objMetaReg.min.v, objMetaReg.max.u, objMetaReg.max.v); for (metaU = objMetaReg.min.u; metaU < objMetaReg.max.u; metaU++) { for (metaV = objMetaReg.min.v; metaV < objMetaReg.max.v; metaV++) { MetaTilePtr meta; meta = objMap->lookupMeta(TilePoint(metaU, metaV, 0)); if (meta == nullptr) continue; TilePoint origin; TileRegion relTileReg; int16 tileU, tileV; origin.u = metaU << kPlatShift; origin.v = metaV << kPlatShift; // Compute the tile region relative to the origin of this // meta tile clipped to this meta tile region relTileReg.min.u = MAX(objTileReg.min.u - origin.u, 0); relTileReg.min.v = MAX(objTileReg.min.v - origin.v, 0); relTileReg.max.u = MIN(objTileReg.max.u - origin.u, (int)kPlatformWidth); relTileReg.max.v = MIN(objTileReg.max.v - origin.v, (int)kPlatformWidth); for (tileU = relTileReg.min.u; tileU < relTileReg.max.u; tileU++) { for (tileV = relTileReg.min.v; tileV < relTileReg.max.v; tileV++) { uint16 tileRoofID = 0; int i, tilePlatNum = -1; for (i = 0; i < maxPlatforms; i++) { Platform *p; TileInfo *t; int16 height; int16 trFlags; if ((p = meta->fetchPlatform(objMapNum, i)) == nullptr) continue; if (!(p->flags & plVisible) || p->roofRipID() <= 0) continue; t = p->fetchTile( objMapNum, TilePoint(tileU, tileV, 0), origin, height, trFlags); if (t != nullptr && height > objHeight + 32) { tileRoofID = p->roofRipID(); tilePlatNum = i; break; } } if (tileRoofID != 0) { if (tilePlatNum > objRoofPlatNum) { objRoofID = tileRoofID; objRoofPlatNum = tilePlatNum; } } else return 0; } } } } return objRoofID; } // Determine if roof over an object is ripped bool objRoofRipped(GameObject *obj) { return obj->world() != nullptr && objRoofID(obj) == rippedRoofID; } // Determine if two objects are both under the same roof bool underSameRoof(GameObject *obj1, GameObject *obj2) { return obj1->world() != nullptr && obj2->world() != nullptr && objRoofID(obj1) == objRoofID(obj2); } /* ===================================================================== * Main view update routine * ===================================================================== */ extern void testSprites(void); #ifdef _WIN32 void *LockTileDrawMapSurface(LPRECT rc = nullptr); bool UnlockTileDrawMapSurface(void *); void drawTileMapToScreen(Rect16 tileRect, void *); #include "ftawin.h" extern CFTWindow *pWindow; extern uint32 tileSurfaceWidth; extern uint32 tileSurfaceHeight; #endif void updateMainDisplay(void) { static TilePoint lastViewLoc = TilePoint(0, 0, 0); int32 deltaTime = gameTime - lastUpdateTime; assert(isActor(viewCenterObject)); Actor *viewActor = (Actor *)GameObject::objectAddress( viewCenterObject); TilePoint viewDiff; assert(isWorld(viewActor->IDParent())); GameWorld *viewWorld = (GameWorld *)viewActor->parent(); if (viewWorld != currentWorld) { currentWorld = viewWorld; setCurrentMap(currentWorld->mapNum); } WorldMapData *curMap = &mapList[currentMapNum]; Point32 scrollCenter, scrollDelta; int32 scrollSpeed = defaultScrollSpeed, scrollDistance; int16 viewSize = tileRect.height; int16 mapSectors = curMap->mapSize * 8 * 16 / kSectorSize; TilePoint trackPos, mCoords; lastUpdateTime = gameTime; // Get the coordinates of the object which the camera is tracking getViewTrackPos(trackPos); debugC(1, kDebugTiles, "trackPos = (%d,%d,%d)", trackPos.u, trackPos.v, trackPos.z); viewDiff = trackPos - lastViewLoc; lastViewLoc = trackPos; if (abs(viewDiff.u) > 8 * kPlatformWidth * kTileUVSize || abs(viewDiff.v) > 8 * kPlatformWidth * kTileUVSize) freeAllTileBanks(); // Add current coordinates to map if they have mapping markMetaAsVisited(trackPos); // Convert to XY coordinates. targetScroll.x = ((trackPos.u - trackPos.v) << 1) + curMap->mapHeight - tileRect.width / 2; targetScroll.y = curMap->mapHeight - (trackPos.u + trackPos.v) - trackPos.z - tileRect.height / 2 - 32; debugC(1, kDebugTiles, "targetScroll = (%d,%d)", targetScroll.x, targetScroll.y); // Compute the delta vector between the current scroll position // and the desired scroll position, and also compute the // magnitude of that vector. scrollDelta = targetScroll - tileScroll; scrollDistance = quickDistance(scrollDelta); // If the magnitude of the scroll vector is large, then // go to a faster scrolling method. if (scrollDistance <= slowThreshhold) scrollSpeed = 0; else if (scrollDistance > snapThreshhold) scrollSpeed = snapScrollSpeed; else if (scrollDistance > fastThreshhold) scrollSpeed = scrollDistance - fastThreshhold; // If the scroll distance is less than the current scroll // speed, then simply set the current scroll position to // the desired scroll position. Otherwise, scale the scroll // vector to the approximate magnitude of the scroll speed. if (scrollDistance <= scrollSpeed) tileScroll = targetScroll; else tileScroll += (scrollDelta * scrollSpeed) / scrollDistance; // Compute the fine scrolling offsets fineScroll.x = tileScroll.x & kTileDXMask; fineScroll.y = 0; // Compute the center of the screen in (u,v) coords. scrollCenter.x = tileScroll.x + tileRect.width / 2; scrollCenter.y = tileScroll.y + tileRect.height / 2; viewCenter = XYToUV(scrollCenter); // Compute the largest U/V rectangle which completely // encloses the view area, and convert to sector coords. minSector.u = clamp(0, (viewCenter.u - viewSize) / kSectorSize, mapSectors - 1); minSector.v = clamp(0, (viewCenter.v - viewSize) / kSectorSize, mapSectors - 1); maxSector.u = clamp(0, (viewCenter.u + viewSize) / kSectorSize, mapSectors - 1); maxSector.v = clamp(0, (viewCenter.v + viewSize) / kSectorSize, mapSectors - 1); buildRoofTable(); mCoords.u = trackPos.u >> (kTileUVShift + kPlatShift); mCoords.v = trackPos.v >> (kTileUVShift + kPlatShift); mCoords.z = 0; // If trackPos has crossed a metatile boundry, rebuild object // ripping tables if (mCoords != ripTableCoords) buildRipTables(); // Build the list of all displayed objects buildDisplayList(); updateObjectAppearances(deltaTime); // for object with no motion task. // Draw tiles onto back buffer // Lock DirectDraw //#define DIRECTDRAW drawMainDisplay(); cycleTiles(deltaTime); } void testTiles() { //initBackPanel(); //initMaps(); initTileCyclingStates(); setCurrentMap(0); PlayModeSetup(); //buildRoofTable(); //buildDisplayList(); // draws tiles to tileDrawMap.data drawMetaTiles(); //uint8 *img = (uint8*)mapList[0].map->mapData; //int16 size = mapList[0].map->size; uint8 *img = tileDrawMap.data; Point16 size = tileDrawMap.size; debugC(3, kDebugTiles, "img = %p, size = %d,%d", (void *)img, size.x, size.y); //Common::hexdump(img, size*size); Graphics::Surface sur; sur.create(size.x, size.y, Graphics::PixelFormat::createFormatCLUT8()); sur.setPixels(img); sur.debugPrint(); g_system->copyRectToScreen(sur.getPixels(), sur.pitch, 0, 0, sur.w, sur.h); // Draw sprites onto back buffer //drawDisplayList(); // Render the image of the mouse pointer on everything else //drawTileMousePointer(); // Blit it all onto the screen //drawPage->writePixels( // tileRect, // tileDrawMap.data // + fineScroll.x // + fineScroll.y * tileDrawMap.size.x, // tileDrawMap.size.x); cleanupTileCyclingStates(); cleanupMaps(); } void drawMainDisplay(void) { // draws tiles to tileDrawMap.data drawMetaTiles(); // Draw sprites onto back buffer drawDisplayList(); // Render the text if any updateSpeech(); // Render floating windows drawFloatingWindows(backPort, Point16(tileRect.x - fineScroll.x, tileRect.y), tileRect); // Render the image of the mouse pointer on everything else drawTileMousePointer(); // Blit it all onto the screen drawPage->writePixels( tileRect, tileDrawMap.data + fineScroll.x + fineScroll.y * tileDrawMap.size.x, tileDrawMap.size.x); updateFrameCount(); } #if DEBUG #include "actor.h" void ShowObjectSection(GameObject *obj) { ProtoObj *proto = obj->proto(); int16 crossSection = proto->crossSection; TilePoint tp = obj->getLocation(), tp1, tp2, tp3, tp4; tp1 = tp + TilePoint(crossSection, crossSection, 0); tp2 = tp + TilePoint(-crossSection, crossSection, 0); tp3 = tp + TilePoint(-crossSection, -crossSection, 0); tp4 = tp + TilePoint(crossSection, -crossSection, 0); TPLine(tp1, tp2); TPLine(tp2, tp3); TPLine(tp3, tp4); TPLine(tp4, tp1); } #endif #if 0 // Tile drawing speed test /* int32 tclock0, tclock1; extern int32 gameTime; for (int i=0; i<30000; i++) { drawTile( &tileDrawMap, 0, 64, ti->attrs.height, (*th)->tileData( *ti ) ); } tclock1 = gameTime; debug( "Time = %d", tclock1 - tclock0 ); */ /* // Tile drawing speed test 2 for (int i = 0; i < 256; i+=4) { for (int y=0; y <= tileDrawMap.size.y + 32; y += tileHeight) { for (int x=0; x <= tileDrawMap.size.x; x += tileWidth) { drawTile( &tileDrawMap, x, y, ti->attrs.height, (*th)->tileData( *ti ) ); drawTile( &tileDrawMap, x, y, ti->attrs.height, (*th)->tileData( *ti ) ); } for (x=tileDX; x <= tileDrawMap.size.x; x += tileWidth) { drawTile( &tileDrawMap, x, y + tileDY, ti->attrs.height, (*th)->tileData( *ti ) ); drawTile( &tileDrawMap, x, y + tileDY, ti->attrs.height, (*th)->tileData( *ti ) ); } } pointer.hide( mainPort, tileRect ); drawPage->writePixels( tileRect, tileDrawMap.data, tileDrawMap.size.x ); pointer.show( mainPort, tileRect ); } */ #endif //----------------------------------------------------------------------- // mark this and surrounding metatiles as visited const int mappingRadius = 2; void markMetaAsVisited(const TilePoint &pt) { // If (they have cartography) { WorldMapData *curMap = &mapList[currentMapNum]; uint16 *mapData = curMap->map->mapData; TilePoint metaCoords = pt >> (kTileUVShift + kPlatShift); int32 minU = MAX(metaCoords.u - mappingRadius, 0), maxU = MIN(metaCoords.u + mappingRadius, curMap->mapSize - 1), minV = MAX(metaCoords.v - mappingRadius, 0), maxV = MIN(metaCoords.v + mappingRadius, curMap->mapSize - 1), u, v; for (u = minU; u <= maxU; u++) { for (v = minV; v <= maxV; v++) { if ((u == minU || u == maxU) && (v == minV || v == maxV)) continue; mapData[u * curMap->mapSize + v] |= metaTileVisited; } } } } /* ===================================================================== * Quick distance calculation subroutines * ===================================================================== */ int16 quickDistance(const Point16 &p) { int16 ax = abs(p.x), ay = abs(p.y); if (ax > ay) return ax + (ay >> 1); else return ay + (ax >> 1); } int32 quickDistance(const Point32 &p) { int32 ax = abs(p.x), ay = abs(p.y); if (ax > ay) return ax + (ay >> 1); else return ay + (ax >> 1); } int16 TilePoint::magnitude(void) { int16 au = abs(u), av = abs(v), az = abs(z); if (az > au && az > av) return az + ((au + av) >> 1); if (au > av) return au + ((az + av) >> 1); else return av + ((au + az) >> 1); } // Determine the distance between a point and a line uint16 lineDist( const TilePoint &p1, const TilePoint &p2, const TilePoint &m) { const int16 lineDistSlop = kTileUVSize * 4; const int16 lineFar = maxint16; int16 u = m.u, v = m.v; int16 u2 = p2.u - p1.u, v2 = p2.v - p1.v; int16 dist; u -= p1.u; v -= p1.v; if (u2 < 0) { u2 = -u2; u = -u; } if (v2 < 0) { v2 = -v2; v = -v; } if (u < -lineDistSlop || u > u2 + lineDistSlop || v < -lineDistSlop || v > v2 + lineDistSlop) return lineFar; if (u2 != 0 && v2 != 0) { if (u2 > v2) dist = u - v2 * v / u2; else dist = v - u2 * u / v2; } else if (u2 == 0) dist = v; else if (v2 == 0) dist = u; else dist = lineFar; return abs(dist); } } // end of namespace Saga2