/* 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. */ #include "common/debug.h" #include "graphics/surface.h" #include "saga2/saga2.h" #include "saga2/detection.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 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 StaticTilePoint Nowhere = {(int16)minint16, (int16)minint16, (int16)minint16}; const StaticMetaTileID NoMetaTile = {nullID, nullID}; const StaticActiveItemID NoActiveItem = {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]; void updateHandleRefs(const TilePoint &pt); //, StandingTileInfo *stiResult ) void updateFrameCount(); /* ===================================================================== * Prototypes * ===================================================================== */ hResContext *tileRes; // tile resource handle void drawPlatform( gPixelMap &drawMap, 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(); extern void drawDisplayList(); //extern void evaluateActorNeeds( int32 ); extern void updateActorTasks(); extern void updateObjectAppearances(int32 deltaTime); extern void getViewTrackPos(TilePoint &tp); extern GameObject *getViewCenterObject(); extern TilePoint centerActorCoords(); void freeAllTileBanks(); 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 int16 worldCount; // Used as map count as well extern ObjectID viewCenterObject; // ID of object that view tracks /* ===================================================================== * Tile structure management * ===================================================================== */ int16 cycleCount; uint16 rippedRoofID; static StaticTilePoint ripTableCoords = Nowhere; static RipTable *ripTableList; 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 PlatformCacheEntry *platformCache; /* ===================================================================== * View state * ===================================================================== */ int16 defaultScrollSpeed = slowScrollSpeed; static StaticPoint32 tileScroll = {0, 0}, // current tile scroll pos targetScroll = {0, 0}; // where scroll going to StaticPoint16 fineScroll = {0, 0}; StaticTilePoint viewCenter = {0, 0, 0}; // coordinates of view on map // These two variables define which sectors overlap the view rect. int16 lastMapNum; int32 lastUpdateTime; // time of last display update /* also: -- height of center character -- what map we are on. */ /* ===================================================================== * ActiveItemID member functions * ===================================================================== */ //----------------------------------------------------------------------- // 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 = (*g_vm->_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() { 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() { 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() { 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() { 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 = nullptr; 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 = nullptr; 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() { 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"); } } void saveActiveItemStates(Common::OutSaveFile *outS) { debugC(2, kDebugSaveload, "Saving ActiveItemStates"); outS->write("TAGS", 4); CHUNK_BEGIN; for (int i = 0; i < worldCount; i++) { debugC(3, kDebugSaveload, "Saving ActiveItemState %d", i); if (stateArray[i] != nullptr) { WorldMapData *mapData = &mapList[i]; ActiveItemList *activeItemList = mapData->activeItemList; uint8 *bufferedStateArray; int16 activeItemCount = mapData->activeCount; int32 arraySize = tileRes->size(tagStateID + i); // Save the size of the state array out->writeSint16LE(arraySize); bufferedStateArray = new uint8[arraySize]; memcpy(bufferedStateArray, stateArray[i], arraySize); debugC(4, kDebugSaveload, "... arraySize = %d", arraySize); for (int 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); } // Copy the state data to the archive buffer out->write(bufferedStateArray, arraySize); delete[] bufferedStateArray; } else out->writeSint16LE(0); } CHUNK_END; } void loadActiveItemStates(Common::InSaveFile *in) { debugC(2, kDebugSaveload, "Loading ActiveItemStates"); stateArray = new byte *[worldCount](); if (stateArray == nullptr) error("Unable to allocate the active item state array array"); for (int i = 0; i < worldCount; i++) { debugC(3, kDebugSaveload, "Loading ActiveItemState %d", i); int32 arraySize; arraySize = in->readSint16LE(); debugC(4, kDebugSaveload, "... arraySize = %d", arraySize); stateArray[i] = (byte *)malloc(arraySize); in->read(stateArray[i], arraySize); if (arraySize > 0) { WorldMapData *mapData = &mapList[i]; ActiveItemList *activeItemList = mapData->activeItemList; int16 activeItemCount = mapData->activeCount; for (int 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 = &stateArray[i][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); } } else stateArray[i] = nullptr; } } //----------------------------------------------------------------------- // Cleanup the active item state arrays void cleanupActiveItemStates() { int16 i; for (i = 0; i < worldCount; i++) { if (stateArray[i] != nullptr) free(stateArray[i]); } delete[] stateArray; } /* ===================================================================== * TileActivityTaskList member functions * ===================================================================== */ //----------------------------------------------------------------------- // Constructor TileActivityTaskList::TileActivityTaskList() { } //----------------------------------------------------------------------- // Reconstruct the TileActivityTaskList from an archive buffer TileActivityTaskList::TileActivityTaskList(Common::SeekableReadStream *stream) { read(stream); } //----------------------------------------------------------------------- // Return the number of bytes needed to archive this // TileActivityTaskList void TileActivityTaskList::read(Common::InSaveFile *in) { int16 taskCount; // Retreive the task count taskCount = in->readSint16LE(); debugC(3, kDebugSaveload, "... taskCount = %d", taskCount); for (int i = 0; i < taskCount; i++) { ActiveItem *tai; uint8 activityType; int16 val = in->readSint16LE(); tai = ActiveItem::activeItemAddress(ActiveItemID(val)); debugC(4, kDebugSaveload, "...... activeItemID = %d", val); activityType = in->readByte(); debugC(4, kDebugSaveload, "...... activityType = %d", activityType); if (tai != nullptr) { TileActivityTask *tat; tat = newTask(tai); if (tat != nullptr) tat->activityType = activityType; } } } void TileActivityTaskList::write(Common::MemoryWriteStreamDynamic *out) { int16 taskCount = _list.size(); // Store the task count out->writeSint16LE(taskCount); debugC(3, kDebugSaveload, "... taskCount = %d", taskCount); for (Common::List::iterator it = _list.begin(); it != _list.end(); ++it) { ActiveItem *ai = (*it)->tai; // Store the activeItemID out->writeSint16LE(ai->thisID().val); debugC(4, kDebugSaveload, "...... activeItemID = %d", ai->thisID().val); // Store the task type out->writeByte((*it)->activityType); debugC(4, kDebugSaveload, "...... activityType = %d", (*it)->activityType); } } //----------------------------------------------------------------------- // Cleanup void TileActivityTaskList::cleanup() { for (Common::List::iterator it = _list.begin(); it != _list.end(); ++it) delete *it; _list.clear(); } //----------------------------------------------------------------------- // Get a new tile activity task, if there is one available, // and initialize it. TileActivityTask *TileActivityTaskList::newTask(ActiveItem *activeInstance) { TileActivityTask *tat = nullptr; // Check see if there's already tile activity task associated with // this instance. for (Common::List::iterator it = _list.begin(); it != _list.end(); ++it) if ((*it)->tai == activeInstance) { tat = *it; break; } if (tat) debugC(3, kDebugTasks, "Found old TAT"); if (tat == nullptr) { debugC(3, kDebugTasks, "Making new TAT"); tat = new TileActivityTask; tat->tai = activeInstance; tat->activityType = TileActivityTask::activityTypeNone; tat->script = NoThread; tat->targetState = 0; _list.push_back(tat); } // If we re-used an old task struct, then make sure script gets woken up. if (tat->script != NoThread) { debugC(3, kDebugTasks, "Waking up thread TAT"); 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() { debugC(3, kDebugTasks, "Removing TAT"); g_vm->_aTaskList->_list.remove(this); } //----------------------------------------------------------------------- // This initiates a tile activity task for opening a door void TileActivityTask::openDoor(ActiveItem &activeInstance) { debugC(3, kDebugTasks, "TAT Open Door"); TileActivityTask *tat; if ((tat = g_vm->_aTaskList->newTask(&activeInstance)) != nullptr) tat->activityType = activityTypeOpen; } //----------------------------------------------------------------------- // This initiates a tile activity task for closing a door void TileActivityTask::closeDoor(ActiveItem &activeInstance) { debugC(3, kDebugTasks, "TAT Close Door"); TileActivityTask *tat; if ((tat = g_vm->_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) { debugC(3, kDebugTasks, "TAT Do Script"); TileActivityTask *tat; if ((tat = g_vm->_aTaskList->newTask(&activeInstance)) != nullptr) { if (scr) debugC(3, kDebugTasks, "TAT Assign Script!"); tat->activityType = activityTypeScript; tat->targetState = finalState; tat->script = scr; } else { debugC(3, kDebugTasks, "Waking up thread 'cause newTask Failed"); wakeUpThread(scr); // If there were no threads available } } //----------------------------------------------------------------------- // Routine to update positions of all active terrain using TileActivityTasks void TileActivityTask::updateActiveItems() { int count = 0, scriptCount = 0; for (Common::List::iterator it = g_vm->_aTaskList->_list.begin(); it != g_vm->_aTaskList->_list.end();) { TileActivityTask *tat = *it; ActiveItem *activityInstance = tat->tai; bool activityTaskDone = false; int16 mapNum = activityInstance->getMapNum(); uint16 state = activityInstance->getInstanceState(mapNum); // collecting stats count++; if (tat->script != NoThread) scriptCount++; 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; } ++it; // Go to next task before potentially removing it if (activityTaskDone) { // Wake up the script... if (tat->script != NoThread) { debugC(3, kDebugTasks, "TAT Wake Up Thread"); wakeUpThread(tat->script); } tat->remove(); } } debugC(3, kDebugTasks, "TileTasks: %d SW:%d", count, scriptCount); } //----------------------------------------------------------------------- // Search for tile activity task matching an item TileActivityTask *TileActivityTask::find(ActiveItem *tai) { for (Common::List::iterator it = g_vm->_aTaskList->_list.begin(); it != g_vm->_aTaskList->_list.end(); ++it) { if (tai == (*it)->tai) return *it; } return nullptr; } //----------------------------------------------------------------------- // Add script to tile activity task... bool TileActivityTask::setWait(ActiveItem *tai, ThreadID script) { TileActivityTask *tat = find(tai); debugC(3, kDebugTasks, "Set Wait TAT\n"); if (tat) { if (tat->script != NoThread) { debugC(3, kDebugTasks, "TAT Waking Up Thread\n"); wakeUpThread(tat->script); } tat->script = script; return true; } debugC(3, kDebugTasks, "SetWait failed\n"); return false; } //----------------------------------------------------------------------- // Calls the handling routine for each tile activity task void moveActiveTerrain(int32 deltaTime) { TileActivityTask::updateActiveItems(); } //----------------------------------------------------------------------- // Initialize the tile activity task list void initTileTasks() { } void saveTileTasks(Common::OutSaveFile *outS) { debugC(2, kDebugSaveload, "Saving TileActivityTasks"); outS->write("TACT", 4); CHUNK_BEGIN; g_vm->_aTaskList->write(out); CHUNK_END; } void loadTileTasks(Common::InSaveFile *in, int32 chunkSize) { debugC(2, kDebugSaveload, "Loading TileActivityTasks"); // If there is no saved data, simply call the default constructor if (chunkSize == 0) return; // Reconstruct aTaskList from archived data g_vm->_aTaskList->read(in); } //----------------------------------------------------------------------- // Cleanup the tile activity task list void cleanupTileTasks() { // Simply call the aTaskList's cleanup g_vm->_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(); } } TileBank::~TileBank() { delete[] _tileArray; } 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(MetaTileList *parent, int ind, Common::SeekableReadStream *stream) { _parent = parent; _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(this, 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(); _data.aItem = this; } 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() { 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", tag2strP(mapID), mapID, tag2strP(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"); stream = loadResourceToStream(tileRes, iTagRefID, "active item data"); for (int k = 0; k < tileRefCount; ++k) { mapData->activeItemData[k].tile = stream->readUint16LE(); mapData->activeItemData[k].flags = stream->readByte(); mapData->activeItemData[k].tileHeight = stream->readByte(); } delete stream; } 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"); stream = loadResourceToStream(tileRes, iAssocID, "association list"); for (int k = 0; k < assocCount; ++k) mapData->assocList[k] = stream->readUint16LE(); } 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(); } ripTableList = new RipTable[RipTable::kRipTableSize]; for (int k = 0; k < RipTable::kRipTableSize; ++k) { ripTableList[k].metaID = NoMetaTile; ripTableList[k].ripID = 0; memset(ripTableList[k].zTable, 0, sizeof(ripTableList[k].zTable)); ripTableList[k]._index = k; } initPlatformCache(); initMapFeatures(); } //----------------------------------------------------------------------- // Cleanup map data void cleanupMaps() { int16 i; termMapFeatures(); delete[] ripTableList; delete[] platformCache; // 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) { delete tileBanks[i]; tileBanks[i] = nullptr; } } } //----------------------------------------------------------------------- // Set a new current map void setCurrentMap(int mapNum) { g_vm->_currentMapNum = mapNum; if (lastMapNum != g_vm->_currentMapNum) { lastMapNum = g_vm->_currentMapNum; freeAllTileBanks(); audioEnvironmentSetWorld(mapNum); } lastUpdateTime = gameTime; } /* ===================================================================== * Automap management functions * ===================================================================== */ //----------------------------------------------------------------------- void initAutoMap() { 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(Common::OutSaveFile *outS) { debugC(2, kDebugSaveload, "Saving AutoMap"); int32 totalMapSize = 0, totalMapIndex = 0; uint8 *archiveBuffer; int32 archiveBufSize; for (int 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; outS->write("AMAP", 4); archiveBuffer = (uint8 *)calloc(archiveBufSize, 1); if (archiveBuffer == nullptr) error("Unable to allocate auto map archive buffer"); for (int 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++; } } CHUNK_BEGIN; out->write(archiveBuffer, archiveBufSize); CHUNK_END; free(archiveBuffer); } void loadAutoMap(Common::InSaveFile *in, int32 chunkSize) { int32 totalMapIndex = 0; uint8 *archiveBuffer; int32 archiveBufSize; archiveBufSize = chunkSize; archiveBuffer = (uint8 *)malloc(archiveBufSize); if (archiveBuffer == nullptr) error("Unable to allocate auto map archive buffer"); in->read(archiveBuffer, archiveBufSize); for (int 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() { platformCache = new PlatformCacheEntry[PlatformCacheEntry::kPlatformCacheSize]; for (int i = 0; i < PlatformCacheEntry::kPlatformCacheSize; i++) { PlatformCacheEntry *pce = &platformCache[i]; // Fill up the LRU with empty platforms pce->metaID = NoMetaTile; g_vm->_platformLRU.push_back(i); } } /* ===================================================================== * Returns the X/Y point in U/V coords * ===================================================================== */ TilePoint XYToUV(const Point32 &pt) { int32 mapHeight = mapList[g_vm->_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[g_vm->_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; } void TileToScreenCoords(const TilePoint &tp, StaticPoint16 &p) { int32 mapHeight = mapList[g_vm->_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() { 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() { return _index; } /* ====================================================================== * 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() { return _properties & 0xFF; } //----------------------------------------------------------------------- // Return a pointer to the specified platform Platform *MetaTile::fetchPlatform(int16 mapNum, int16 layer) { const int cacheFlag = 0x8000; uint16 plIndex = _stack[layer]; PlatformCacheEntry *pce; Common::SeekableReadStream *stream; assert(layer >= 0); assert(_parent == mapList[mapNum].metaList); if (plIndex == (uint16)nullID) { return nullptr; } else if (plIndex & cacheFlag) { plIndex &= ~cacheFlag; assert(plIndex < PlatformCacheEntry::kPlatformCacheSize); // Get the address of the pce from the cache pce = &platformCache[plIndex]; assert(pce->metaID != NoMetaTile); assert(pce->metaID == thisID(mapNum)); // Move to the end of the LRU g_vm->_platformLRU.remove(plIndex); g_vm->_platformLRU.push_back(plIndex); // return the address of the platform return &pce->pl; } else { debugC(2, kDebugLoading, "Fetching platform (%d,%d)", mapNum, layer); // 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 = g_vm->_platformLRU.front(); g_vm->_platformLRU.pop_front(); g_vm->_platformLRU.push_back(cacheIndex); pce = &platformCache[cacheIndex]; // Compute the layer of this entry in the cache assert(cacheIndex < PlatformCacheEntry::kPlatformCacheSize); assert(cacheIndex >= 0); if (pce->metaID != NoMetaTile) { MetaTile *oldMeta = metaTileAddress(pce->metaID); assert(pce->layerNum < maxPlatforms); assert(oldMeta->_stack[pce->layerNum] == (cacheFlag | cacheIndex)); oldMeta->_stack[pce->layerNum] = pce->platformNum; } // 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 * sizeof(Platform) < tileRes->size(platformID + mapNum)); debugC(3, kDebugLoading, "- plIndex: %d", plIndex); // Now, load the actual metatile data... if ((stream = loadResourceToStream(tileRes, platformID + mapNum, "platform"))) { if (stream->skip(plIndex * sizeof(Platform))) { pce->pl.load(stream); delete stream; 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() { 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)) % ARRAYSIZE(instHash); ai->_nextHash = instHash[hashVal]; instHash[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)) % ARRAYSIZE(instHash); for (ActiveItem *ai = instHash[hashVal]; ai; ai = ai->_nextHash) { if (ai->_data.instance.u == tp.u && ai->_data.instance.v == tp.v && ai->_data.instance.h == tp.z && ai->_data.instance.groupID == group) return ai; } return nullptr; } /* ====================================================================== * MetaTileIterator member functions * ====================================================================== */ bool MetaTileIterator::iterate() { 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() { 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(gPixelMap &drawMap, TilePoint coords, Point16 pos) { WorldMapData *curMap = &mapList[g_vm->_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 < drawMap.size.x + kMetaDX; coords.u++, coords.v--, uOrg += kPlatformWidth, vOrg -= kPlatformWidth, pos.x += kMetaTileWidth ) { TilePoint clipCoords; int16 mtile = 0; 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(g_vm->_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 >= drawMap.size.y) continue; *put++ = p; } } *put++ = nullptr; if (drawList[0] != nullptr) { drawPlatform(drawMap, 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(g_vm->_currentMapNum) = ripTable->thisID(); ripTable->metaID = mt->thisID(g_vm->_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(g_vm->_currentMapNum, i)) == nullptr) continue; if (p->roofRipID() != ripID) continue; for (; i < maxPlatforms && tilesToGo > 0; i++) { if ((p = mt->fetchPlatform(g_vm->_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() { 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(g_vm->_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[(RipTable::kRipTableSize + 7) >> 3]; memset(tableAvail, 0xFF, sizeof(tableAvail)); for (int i = 0; i < mtTableSize; i++) { mt = mtTable[i]; RipTable *mtRipTable = mt->ripTable(g_vm->_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->_index; 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 < RipTable::kRipTableSize; 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) { MetaTileID ripID = ripTableList[j].metaID; MetaTile *ripMt = MetaTile::metaTileAddress(ripID); RipTableID &rt = ripMt->ripTableID(ripID.map); // 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() { uint16 newRoofID = objRoofID(getViewCenterObject()); if (newRoofID != rippedRoofID) { rippedRoofID = newRoofID; buildRipTables(); } } // Draw all visible metatiles void drawMetaTiles(gPixelMap &drawMap) { 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[g_vm->_currentMapNum].mapSize), viewPos.y = (kPlatformWidth * mapList[g_vm->_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); 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 < drawMap.size.y + kMetaTileHeight * 4 ; baseCoords.u--, baseCoords.v-- ) { drawMetaRow(drawMap, baseCoords, metaPos); metaPos.y += kMetaDY; metaPos.x -= kMetaDX; drawMetaRow(drawMap, TilePoint(baseCoords.u - 1, baseCoords.v, 0), metaPos); metaPos.y += kMetaDY; metaPos.x += kMetaDX; } } /* ===================================================================== * 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( g_vm->_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[g_vm->_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 = 0; 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(g_vm->_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[g_vm->_currentMapNum].mapSize), viewPos.y = (kPlatformWidth * mapList[g_vm->_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. StaticTilePoint pickTilePos(Point32 pos, const TilePoint &protagPos) { StaticTilePoint coords = {0, 0, 0}; pos.x += tileScroll.x; pos.y += tileScroll.y + protagPos.z; coords.set(XYToUV(pos).u, XYToUV(pos).v, 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[g_vm->_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(g_vm->_currentMapNum, i)) == nullptr) continue; if (!(p->flags & plVisible) || platformRipped(p)) continue; // Fetch the tile at this location adjTile = p->fetchTile( g_vm->_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 StaticTilePoint pickTile(Point32 pos, const TilePoint &protagPos, StaticTilePoint *floorResult, ActiveItemPtr *pickTAI) { WorldMapData *curMap = &mapList[g_vm->_currentMapNum]; StaticTilePoint result = {0, 0, 0}; 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 = nullptr; 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 occurred 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(g_vm->_currentMapNum, i)) == nullptr) continue; if (platformRipped(p)) break; if (!(p->flags & plVisible)) continue; // Fetch the tile at this location ti = p->fetchTAGInstance( g_vm->_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; } pickCoords = pCoords; floorCoords = fCoords; bestTile = ti; 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(); } result.set(pickCoords.u, pickCoords.v, pickCoords.z); // If no tile was found, return the default. if (!bestTile) { if (floorResult) floorResult->set(floorCoords.u, floorCoords.v, floorCoords.z); if (pickTAI) *pickTAI = nullptr; return result; } if (floorResult) floorResult->set(floorCoords.u, floorCoords.v, floorCoords.z); if (pickTAI) *pickTAI = bestTileTAI; return result; } /* ===================================================================== * 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 void initTileCyclingStates() { Common::SeekableReadStream *stream; const int tileCycleDataSize = 40; cycleCount = tileRes->size(cycleID) / tileCycleDataSize; cycleList = new TileCycleData[cycleCount]; if (cycleList == nullptr) error("Unable to load tile cycling data"); if ((stream = loadResourceToStream(tileRes, cycleID, "cycle list"))) { for (int i = 0; i < cycleCount; ++i) cycleList[i].load(stream); debugC(2, kDebugLoading, "Loaded Cycles: cycleCount = %d", cycleCount); delete stream; } } void saveTileCyclingStates(Common::OutSaveFile *outS) { debugC(2, kDebugSaveload, "Saving TileCyclingStates"); outS->write("CYCL", 4); CHUNK_BEGIN; for (int i = 0; i < cycleCount; i++) { debugC(3, kDebugSaveload, "Saving TileCyclingState %d", i); out->writeSint32LE(cycleList[i].counter); out->writeByte(cycleList[i].currentState); debugC(4, kDebugSaveload, "... counter = %d", cycleList[i].counter); debugC(4, kDebugSaveload, "... currentState = %d", cycleList[i].currentState); } CHUNK_END; } void loadTileCyclingStates(Common::InSaveFile *in) { debugC(2, kDebugSaveload, "Loading TileCyclingStates"); initTileCyclingStates(); for (int i = 0; i < cycleCount; i++) { debugC(3, kDebugSaveload, "Loading TileCyclingState %d", i); cycleList[i].counter = in->readSint32LE(); cycleList[i].currentState = in->readByte(); debugC(4, kDebugSaveload, "... counter = %d", cycleList[i].counter); debugC(4, kDebugSaveload, "... currentState = %d", cycleList[i].currentState); } } //----------------------------------------------------------------------- // Cleanup the tile cycling state array void cleanupTileCyclingStates() { if (cycleList != nullptr) { delete[] 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 updateMainDisplay() { // TODO: updateMainDisplay() for Dino if (g_vm->getGameId() == GID_DINO) return; 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[g_vm->_currentMapNum]; StaticPoint32 scrollCenter, scrollDelta; int32 scrollSpeed = defaultScrollSpeed, scrollDistance; 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 - kTileRectWidth / 2; targetScroll.y = curMap->mapHeight - (trackPos.u + trackPos.v) - trackPos.z - kTileRectHeight / 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 + kTileRectWidth / 2; scrollCenter.y = tileScroll.y + kTileRectHeight / 2; viewCenter.set(XYToUV(scrollCenter).u, XYToUV(scrollCenter).v, 0); // Compute the largest U/V rectangle which completely // encloses the view area, and convert to sector coords. 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 drawMainDisplay() { // draws tiles to g_vm->_tileDrawMap.data drawMetaTiles(g_vm->_tileDrawMap); // Draw sprites onto back buffer drawDisplayList(); // Render the text if any updateSpeech(); Rect16 rect(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight); // Render floating windows drawFloatingWindows(g_vm->_backPort, Point16(kTileRectX - fineScroll.x, kTileRectY), rect); // Blit it all onto the screen drawPage->writePixels( rect, g_vm->_tileDrawMap.data + fineScroll.x + fineScroll.y * g_vm->_tileDrawMap.size.x, g_vm->_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 //----------------------------------------------------------------------- // mark this and surrounding metatiles as visited const int mappingRadius = 2; void markMetaAsVisited(const TilePoint &pt) { // If (they have cartography) { WorldMapData *curMap = &mapList[g_vm->_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() { 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 // here v2 == 0 dist = u; return ABS(dist); } } // end of namespace Saga2