scummvm/engines/saga2/tile.cpp

4907 lines
137 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* aint32 with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL // FIXME: Remove
#include "common/debug.h"
#include "graphics/surface.h"
#include "saga2/saga2.h"
#include "saga2/blitters.h"
#include "saga2/hresmgr.h"
#include "saga2/objects.h"
#include "saga2/tile.h"
#include "saga2/oncall.h"
#include "saga2/input.h"
#include "saga2/cmisc.h"
#include "saga2/setup.h"
#include "saga2/tagnoise.h"
#include "saga2/player.h"
#include "saga2/mapfeatr.h"
#include "saga2/savefile.h"
// Include files needed for SAGA script dispatch
#include "saga2/script.h"
#include "saga2/methods.r" // generated by SAGA
namespace Saga2 {
extern void writeLog(char *str);
void PlayModeSetup();
void initBackPanel();
#define TATLOG 0
/* ===================================================================== *
Constants
* ===================================================================== */
const uint32 tileTerrainID = MKTAG('T', 'E', 'R', 0),
platformID = MKTAG('P', 'L', 'T', 0),
metaID = MKTAG('M', 'E', 'T', 0),
mapID = MKTAG('M', 'A', 'P', 0),
tagID = MKTAG('T', 'A', 'G', 0),
tagDataID = MKTAG('T', 'G', 'D', 0),
tagStateID = MKTAG('T', 'S', 'T', 0),
assocID = MKTAG('A', 'S', 'C', 0),
cycleID = MKTAG('C', 'Y', 'C', 'L');
// Scrolling Speed constants
const int slowScrollSpeed = 6,
snapScrollSpeed = maxint32,
slowThreshhold = 16,
// fastThreshhold = 100,
fastThreshhold = 16,
snapThreshhold = 400;
const StaticTilePoint Nowhere = {(int16)minint16, (int16)minint16, (int16)minint16};
const StaticMetaTileID NoMetaTile = {nullID, nullID};
const StaticActiveItemID NoActiveItem = {ActiveItemID::getVal(0, activeItemIndexNullID)};
enum SurfaceType {
surfaceHoriz, // Level surface
surfaceVertV, // Vertical surface, parallel to V axis
surfaceVertU // Vertical surface, parallel to U axis
};
void updateSpeech();
void setAreaSound(const TilePoint &baseCoords);
/* ===================================================================== *
Bank switching interface
* ===================================================================== */
TileBankPtr tileBanks[maxBanks];
extern HandleArray tileImageBanks;
void updateHandleRefs(const TilePoint &pt); //, StandingTileInfo *stiResult )
void updateFrameCount(void);
/* ===================================================================== *
Prototypes
* ===================================================================== */
hResContext *tileRes; // tile resource handle
void drawPlatform(
Platform **pList, // platforms to draw
Point16 screenPos, // screen position
int16 uOrg, // for TAG search
int16 vOrg); // for TAG search
bool isTilePixelOpaque(int16 baseX, // X coordinate relative to base
int16 baseY, // Y coordinate relative to base
int16 mapHeight, // pixel height of tile's bitmap
uint8 *td); // packed tile bitmap
SurfaceType pointOnTile(TileInfo *ti,
const Point32 &tileRel,
int16 h,
const TilePoint &tCoords,
TilePoint &pickCoords,
TilePoint &floorCoords);
bool validSurface(const TilePoint &tileCoords,
const TilePoint &pickCoords);
void markMetaAsVisited(const TilePoint &pt);
/* ===================================================================== *
Prototypes
* ===================================================================== */
extern void buildDisplayList(void);
extern void drawDisplayList(void);
//extern void evaluateActorNeeds( int32 );
extern void updateActorTasks(void);
extern void updateObjectAppearances(int32 deltaTime);
extern void getViewTrackPos(TilePoint &tp);
extern GameObject *getViewCenterObject(void);
extern TilePoint centerActorCoords(void);
void freeAllTileBanks(void);
void cycleTiles(uint32 elapsed);
#if DEBUG
void TPLine(const TilePoint &start, const TilePoint &stop);
#endif
void drawFloatingWindows(gPort &, const Point16 &, const Rect16 &clip);
/* ===================================================================== *
Imports
* ===================================================================== */
extern gMousePointer pointer; // the actual pointer
extern gMouseState mouseState;
extern gPort backPort;
extern int16 worldCount; // Used as map count as well
extern ObjectID viewCenterObject; // ID of object that view tracks
/* ===================================================================== *
Exports
* ===================================================================== */
gPixelMap tileDrawMap;
/* ===================================================================== *
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.
TilePoint minSector,
maxSector;
int16 currentMapNum; // which map is in use
int16 lastMapNum;
int32 lastUpdateTime; // time of last display update
gPort mouseSavePort; // for tweaking mouse backsave
/* also:
-- height of center character
-- what map we are on.
*/
BankBits LoadedBanks; // what banks are loaded?
/* ===================================================================== *
ActiveItemID member functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Return the address of a tile's TileInfo structure given that tile's ID
TileInfo *TileInfo::tileAddress(TileID id) {
TileInfo *ti;
TileBankPtr tbh;
int16 tileBank,
tileNum;
if (id == 0) return nullptr;
TileID2Bank(id, tileBank, tileNum);
if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
ti = tbh->tile(tileNum);
if (ti->attrs.cycleRange > 0) {
TileCycleData &tcd = cycleList[ti->attrs.cycleRange - 1];
TileID2Bank(tcd.cycleList[tcd.currentState],
tileBank,
tileNum);
if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
ti = tbh->tile(tileNum);
}
return ti;
}
//-----------------------------------------------------------------------
// Return the address of a tile's TileInfo structure and the address of
// the tile's image data given that tile's ID
TileInfo *TileInfo::tileAddress(TileID id, uint8 **imageData) {
TileInfo *ti;
TileBankPtr tbh;
byte *tibh;
int16 tileBank,
tileNum;
if (id == 0) return nullptr;
TileID2Bank(id, tileBank, tileNum);
debugC(3, kDebugTiles, "TileID2Bank: id = %d, tileBank = %d, tileNum = %d", id, tileBank, tileNum);
if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
ti = tbh->tile(tileNum);
if (ti->attrs.cycleRange > 0) {
TileCycleData &tcd = cycleList[ti->attrs.cycleRange - 1];
TileID2Bank(tcd.cycleList[tcd.currentState],
tileBank,
tileNum);
if ((tbh = tileBanks[tileBank]) == nullptr) return nullptr;
ti = tbh->tile(tileNum);
}
if (ti != nullptr) {
if ((tibh = tileImageBanks[tileBank]) != nullptr)
*imageData = &tibh[ti->offset];
else
*imageData = nullptr;
} else
*imageData = nullptr;
return ti;
}
/* ===================================================================== *
ActiveItem member functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Return the map number of this active item
int16 ActiveItem::getMapNum(void) {
int16 mapNum;
// Use a brute force search of all of the maps' active item lists
// to determine which map this active item is on.
for (mapNum = 0; mapNum < worldCount; mapNum++) {
WorldMapData *mapData = &mapList[mapNum];
// Determine if the active item in on this map's list
if (_parent == mapData->activeItemList)
break;
}
return mapNum;
}
//-----------------------------------------------------------------------
// Return the world context for a TAG
ObjectID ActiveItem::getInstanceContext(void) {
int16 mn = getMapNum();
assert(mn >= 0 && mn < 3);
if (mn < 0 || mn > 2)
return Nothing;
WorldMapData &map = mapList[mn]; // master map data array
return map.worldID;
}
//-----------------------------------------------------------------------
// Return the Location for a TAG
Location ActiveItem::getInstanceLocation(void) {
return Location(_data.instance.u << kTileUVShift,
_data.instance.v << kTileUVShift,
_data.instance.h << kTileZShift,
getInstanceContext());
}
//-----------------------------------------------------------------------
// Return the address of an active item, given its ID
ActiveItem *ActiveItem::activeItemAddress(ActiveItemID id) {
return id.getIndexNum() != activeItemIndexNullID
? mapList[id.getMapNum()].activeItemList->_items[id.getIndexNum()]
: nullptr;
}
//-----------------------------------------------------------------------
// Return this active item's ID
ActiveItemID ActiveItem::thisID(void) {
int16 mapNum = getMapNum();
return ActiveItemID(mapNum, _index);
}
//-----------------------------------------------------------------------
// Return this active item's ID
ActiveItemID ActiveItem::thisID(int16 mapNum) {
return ActiveItemID(mapNum, _index);
}
//-----------------------------------------------------------------------
// use() function for ActiveItem instance
bool ActiveItem::use(ObjectID enactor) {
// Get a pointer to the active item group
ActiveItem *groupPtr = activeItemAddress(
ActiveItemID(
getMapNum(),
_data.instance.groupID));
return groupPtr->use(this, enactor);
}
//-----------------------------------------------------------------------
// trigger() function for ActiveItem instance
bool ActiveItem::trigger(ObjectID enactor, ObjectID objID) {
// Get a pointer to the active item group
ActiveItem *groupPtr = activeItemAddress(
ActiveItemID(
getMapNum(),
_data.instance.groupID));
return groupPtr->trigger(this, enactor, objID);
}
//-----------------------------------------------------------------------
// release() function for ActiveItem instance
bool ActiveItem::release(ObjectID enactor, ObjectID objID) {
// Get a pointer to the active item group
ActiveItem *groupPtr = activeItemAddress(
ActiveItemID(
getMapNum(),
_data.instance.groupID));
return groupPtr->release(this, enactor, objID);
}
//-----------------------------------------------------------------------
// acceptLockToggle() function for ActiveItem instance
bool ActiveItem::acceptLockToggle(ObjectID enactor, uint8 keyCode) {
// Get a pointer to the active item group
ActiveItem *groupPtr = activeItemAddress(
ActiveItemID(
getMapNum(),
_data.instance.groupID));
return groupPtr->acceptLockToggle(this, enactor, keyCode);
}
//-----------------------------------------------------------------------
// inRange() function for ActiveItem instance
bool ActiveItem::inRange(const TilePoint &loc, int16 range) {
// Get a pointer to the active item group
ActiveItem *groupPtr = activeItemAddress(
ActiveItemID(
getMapNum(),
_data.instance.groupID));
return loc.u >= _data.instance.u - range
&& loc.v >= _data.instance.v - range
&& loc.u < _data.instance.u + groupPtr->_data.group.uSize + range
&& loc.v < _data.instance.v + groupPtr->_data.group.vSize + range;
}
//-----------------------------------------------------------------------
// TAG noise player
void ActiveItem::playTAGNoise(ActiveItem *ai, int16 tagNoiseID) {
playSoundAt(MKTAG('T', 'A', 'G', tagNoiseID), ai->getInstanceLocation());
}
//-----------------------------------------------------------------------
// use() function for ActiveItem group
bool ActiveItem::use(ActiveItem *ins, ObjectID enactor) {
int16 mapNum = getMapNum();
uint16 state = ins->getInstanceState(mapNum);
scriptCallFrame scf;
if (ins->_data.scriptClassID != 0) {
// Set up the arguments we want to pass to the script
scf.invokedTAI = ins->thisID();
scf.enactor = enactor;
scf.directTAI = scf.invokedTAI;
scf.indirectObject = Nothing;
// Fill in other params with data from TAG struct
scf.value = ins->_data.instance.worldNum;
scf.coords.u = ins->_data.instance.targetU;
scf.coords.v = ins->_data.instance.targetV;
scf.coords.z = ins->_data.instance.targetZ;
if (runTagMethod(
scf.invokedTAI,
Method_TileActivityInstance_onUse,
scf)
== scriptResultFinished) {
if (scf.returnVal != actionResultNotDone)
return scf.returnVal == actionResultSuccess;
}
}
switch (ins->builtInBehavior()) {
case builtInLamp:
ins->setInstanceState(mapNum, !state);
break;
case builtInDoor:
if (state < 3) {
if (!ins->isLocked()) {
TileActivityTask::openDoor(*ins);
playTAGNoise(ins, DEFAULT_OPEN);
} else {
playTAGNoise(ins, DOOR_LOCKED_NO_KEY);
return false;
}
} else {
TileActivityTask::closeDoor(*ins);
playTAGNoise(ins, DEFAULT_CLOSE);
}
break;
}
return true;
}
//-----------------------------------------------------------------------
// trigger() function for ActiveItem group
bool ActiveItem::trigger(ActiveItem *ins, ObjectID enactor, ObjectID objID) {
assert(objID != Nothing);
GameObject *obj = GameObject::objectAddress(objID);
GameWorld *world = (GameWorld *)GameObject::objectAddress(
mapList[getMapNum()].worldID);
TileRegion instanceRegion;
ActiveItemID instanceID = ins->thisID();
scriptCallFrame scf;
// Trap transporters to only react to the center actor
if (ins->builtInBehavior() == builtInTransporter
&& (!isActor(obj) || (Actor *)obj != getCenterActor()))
return true;
if (ins->_data.scriptClassID != 0) {
// Set up the arguments we want to pass to the script
scf.invokedTAI = ins->thisID();
scf.enactor = enactor;
scf.directTAI = scf.invokedTAI;
scf.indirectObject = objID;
if (runTagMethod(
scf.invokedTAI,
Method_TileActivityInstance_onCanTrigger,
scf)
== scriptResultFinished) {
if (!scf.returnVal) return true;
}
}
// Mark the object as triggering this TAG
obj->setTriggeringTAG(true);
instanceRegion.min.u = ins->_data.instance.u << kTileUVShift;
instanceRegion.min.v = ins->_data.instance.v << kTileUVShift;
instanceRegion.max.u = instanceRegion.min.u
+ (_data.group.uSize << kTileUVShift);
instanceRegion.max.v = instanceRegion.min.v
+ (_data.group.vSize << kTileUVShift);
RegionalObjectIterator iter(
world,
instanceRegion.min,
instanceRegion.max);
GameObject *testObject;
for (iter.first(&testObject);
testObject != nullptr;
iter.next(&testObject)) {
if (testObject != obj
&& testObject->_data.currentTAG == instanceID
&& testObject->isTriggeringTAG())
return true;
}
// if ( proto->mass < group.triggerWeight ) return false;
if (ins->_data.scriptClassID != 0) {
// Set up the arguments we want to pass to the script
scf.invokedTAI = ins->thisID();
scf.enactor = enactor;
scf.directTAI = scf.invokedTAI;
scf.indirectObject = objID;
// Fill in other params with data from TAG struct
scf.value = ins->_data.instance.worldNum;
scf.coords.u = ins->_data.instance.targetU;
scf.coords.v = ins->_data.instance.targetV;
scf.coords.z = ins->_data.instance.targetZ;
if (runTagMethod(
scf.invokedTAI,
Method_TileActivityInstance_onTrigger,
scf)
== scriptResultFinished) {
if (scf.returnVal != actionResultNotDone)
return scf.returnVal == actionResultSuccess;
}
}
switch (ins->builtInBehavior()) {
case builtInTransporter:
//playTAGNoise(BEAM_ME_UP);
{
Actor *a;
if (isActor(obj) && (a = (Actor *)obj) == getCenterActor()) {
transportCenterBand(
Location(
(ins->_data.instance.targetU << kTileUVShift)
+ kTileUVSize / 2,
(ins->_data.instance.targetV << kTileUVShift)
+ kTileUVSize / 2,
(int16)ins->_data.instance.targetZ << 3,
ins->_data.instance.worldNum + WorldBaseID));
}
}
break;
}
return true;
}
//-----------------------------------------------------------------------
// release() function for ActiveItem group
bool ActiveItem::release(ActiveItem *ins, ObjectID enactor, ObjectID objID) {
assert(objID != Nothing);
GameObject *obj = GameObject::objectAddress(objID);
GameWorld *world = (GameWorld *)GameObject::objectAddress(
mapList[getMapNum()].worldID);
TileRegion instanceRegion;
ActiveItemID instanceID = ins->thisID();
scriptCallFrame scf;
if (obj->isTriggeringTAG()) obj->setTriggeringTAG(false);
instanceRegion.min.u = ins->_data.instance.u << kTileUVShift;
instanceRegion.min.v = ins->_data.instance.v << kTileUVShift;
instanceRegion.max.u = instanceRegion.min.u
+ (_data.group.uSize << kTileUVShift);
instanceRegion.max.v = instanceRegion.min.v
+ (_data.group.vSize << kTileUVShift);
RegionalObjectIterator iter(
world,
instanceRegion.min,
instanceRegion.max);
GameObject *testObject;
for (iter.first(&testObject);
testObject != nullptr;
iter.next(&testObject)) {
if (testObject != obj
&& testObject->_data.currentTAG == instanceID
&& testObject->isTriggeringTAG())
return true;
}
if (ins->_data.scriptClassID != 0) {
// Set up the arguments we want to pass to the script
scf.invokedTAI = ins->thisID();
scf.enactor = enactor;
scf.directTAI = scf.invokedTAI;
scf.indirectObject = objID;
// Fill in other params with data from TAG struct
scf.value = ins->_data.instance.worldNum;
scf.coords.u = ins->_data.instance.targetU;
scf.coords.v = ins->_data.instance.targetV;
scf.coords.z = ins->_data.instance.targetZ;
if (runTagMethod(
scf.invokedTAI,
Method_TileActivityInstance_onRelease,
scf)
== scriptResultFinished) {
if (scf.returnVal != actionResultNotDone)
return scf.returnVal == actionResultSuccess;
}
}
return true;
}
//-----------------------------------------------------------------------
// acceptLockToggle() function for ActiveItem group
bool ActiveItem::acceptLockToggle(ActiveItem *ins, ObjectID enactor, uint8 keyCode) {
scriptCallFrame scf;
if (ins->_data.scriptClassID != 0) {
// Set up the arguments we want to pass to the script
scf.invokedTAI = ins->thisID();
scf.enactor = enactor;
scf.directTAI = scf.invokedTAI;
scf.indirectObject = Nothing;
// Fill in other params with data from TAG struct
scf.value = keyCode;
if (runTagMethod(
scf.invokedTAI,
Method_TileActivityInstance_onAcceptLockToggle,
scf)
== scriptResultFinished) {
if (scf.returnVal != actionResultNotDone)
return scf.returnVal == actionResultSuccess;
}
}
switch (ins->builtInBehavior()) {
case builtInDoor:
if (keyCode == ins->lockType()) {
playTAGNoise(ins, UNLOCK_RIGHT_KEY);
if (ins->isLocked())
ins->setLocked(false);
else {
if (ins->getInstanceState(getMapNum()) == 0)
ins->setLocked(true);
else
return false;
}
} else {
playTAGNoise(ins, UNLOCK_WRONG_KEY);
return false;
}
break;
}
return true;
}
//-----------------------------------------------------------------------
TilePoint getClosestPointOnTAI(ActiveItem *TAI, GameObject *obj) {
assert(TAI->_data.itemType == activeTypeInstance);
TilePoint objLoc = obj->getLocation(),
TAILoc;
TileRegion TAIReg;
ActiveItem *TAG = TAI->getGroup();
// Compute in points the region of the TAI
TAIReg.min.u = TAI->_data.instance.u << kTileUVShift;
TAIReg.min.v = TAI->_data.instance.v << kTileUVShift;
TAIReg.max.u = TAIReg.min.u
+ (TAG->_data.group.uSize << kTileUVShift);
TAIReg.max.v = TAIReg.min.v
+ (TAG->_data.group.vSize << kTileUVShift);
TAIReg.min.z = TAIReg.max.z = 0;
// Find the point on the TAI closest to the object
TAILoc.u = clamp(TAIReg.min.u - 1, objLoc.u, TAIReg.max.u);
TAILoc.v = clamp(TAIReg.min.v - 1, objLoc.v, TAIReg.max.v);
TAILoc.z = TAI->_data.instance.h + obj->proto()->height / 2;
return TAILoc;
}
/* ===================================================================== *
ActiveItem instance state management functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the active item state arrays
void initActiveItemStates(void) {
int16 i;
stateArray = new byte *[worldCount]();
if (stateArray == nullptr)
error("Unable to allocate the active item state array array");
for (i = 0; i < worldCount; i++) {
stateArray[i] = (byte *)LoadResource(tileRes, tagStateID + i,
"active item state array");
if (stateArray[i] == nullptr)
error("Unable to load active item state array");
}
}
//-----------------------------------------------------------------------
// Save the active item instance states in a save file
void saveActiveItemStates(SaveFileConstructor &saveGame) {
int16 i;
int32 archiveBufSize = 0;
void *archiveBuffer;
void *bufferPtr;
for (i = 0; i < worldCount; i++) {
int32 size = tileRes->size(tagStateID + i);
archiveBufSize += sizeof(int16);
if (stateArray[i] != nullptr)
archiveBufSize += size;
}
archiveBuffer = malloc(archiveBufSize);
if (archiveBuffer == nullptr)
error("Unable to allocate a state array archive buffer");
bufferPtr = archiveBuffer;
for (i = 0; i < worldCount; i++) {
if (stateArray[i] != nullptr) {
WorldMapData *mapData = &mapList[i];
ActiveItemList *activeItemList = mapData->activeItemList;
int16 activeItemCount = mapData->activeCount,
j;
int32 arraySize = tileRes->size(tagStateID + i);
uint8 *bufferedStateArray;
// Save the size of the state array
WRITE_LE_INT16(bufferPtr, arraySize / sizeof(uint8));
bufferPtr = (int16 *)bufferPtr + 1;
// Copy the state data to the archive buffer
memcpy(bufferPtr, stateArray[i], arraySize);
// Save a pointer to the buffered data
bufferedStateArray = (uint8 *)bufferPtr;
bufferPtr = (uint8 *)bufferPtr + arraySize;
for (j = 0; j < activeItemCount; j++) {
ActiveItem *activeItem = activeItemList->_items[j];
uint8 *statePtr;
if (activeItem->_data.itemType != activeTypeInstance)
continue;
// Get a pointer to the current active item's state
// data in the archive buffer
statePtr =
&bufferedStateArray[activeItem->_data.instance.stateIndex];
// Set the high bit of the state value based upon the
// active item's locked state
if (activeItem->isLocked())
*statePtr |= (1 << 7);
else
*statePtr &= ~(1 << 7);
}
} else {
WRITE_LE_INT16(bufferPtr, 0);
bufferPtr = (int16 *)bufferPtr + 1;
}
}
saveGame.writeChunk(
MakeID('T', 'A', 'G', 'S'),
archiveBuffer,
archiveBufSize);
free(archiveBuffer);
}
void saveActiveItemStates(Common::OutSaveFile *out) {
debugC(2, kDebugSaveload, "Saving ActiveItemStates");
int32 archiveBufSize = 0;
for (int i = 0; i < worldCount; i++) {
int32 size = tileRes->size(tagStateID + i);
archiveBufSize += sizeof(int16);
if (stateArray[i] != nullptr)
archiveBufSize += size;
}
out->write("TAGS", 4);
out->writeUint32LE(archiveBufSize);
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;
int16 activeItemCount = mapData->activeCount;
int32 arraySize = tileRes->size(tagStateID + i);
// Save the size of the state array
out->writeSint16LE(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 = &stateArray[i][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(stateArray[i], arraySize);
} else
out->writeSint16LE(0);
}
}
//-----------------------------------------------------------------------
// Load the active item instance states from a save file
void loadActiveItemStates(SaveFileReader &saveGame) {
int16 i;
void *archiveBuffer;
void *bufferPtr;
stateArray = new byte *[worldCount]();
if (stateArray == nullptr)
error("Unable to allocate the active item state array array");
archiveBuffer = malloc(saveGame.getChunkSize());
if (archiveBuffer == nullptr)
error("Unable to allocate state array archive buffer");
saveGame.read(archiveBuffer, saveGame.getChunkSize());
bufferPtr = archiveBuffer;
for (i = 0; i < worldCount; i++) {
int32 arraySize;
arraySize = READ_LE_INT16(bufferPtr) * sizeof(uint8);
bufferPtr = (int16 *)bufferPtr + 1;
if (arraySize > 0) {
WorldMapData *mapData = &mapList[i];
ActiveItemList *activeItemList = mapData->activeItemList;
int16 activeItemCount = mapData->activeCount,
j;
uint8 *bufferedStateArray = (uint8 *)bufferPtr;
for (j = 0; j < activeItemCount; j++) {
ActiveItem *activeItem = activeItemList->_items[j];
uint8 *statePtr;
if (activeItem->_data.itemType != activeTypeInstance)
continue;
// Get a pointer to the current active item's state
// data in the archive buffer
statePtr =
&bufferedStateArray[activeItem->_data.instance.stateIndex];
// Reset the locked state of the active item based
// upon the high bit of the buffered state value
activeItem->setLocked((*statePtr & (1 << 7)) != 0);
// Clear the high bit of the state value
*statePtr &= ~(1 << 7);
}
stateArray[i] = (byte *)malloc(arraySize);
if (stateArray[i] == nullptr)
error("Unable to allocate active item state array");
memcpy(stateArray[i], bufferPtr, arraySize);
bufferPtr = (uint8 *)bufferPtr + arraySize;
} else
stateArray[i] = nullptr;
}
free(archiveBuffer);
}
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(void) {
int16 i;
for (i = 0; i < worldCount; i++) {
if (stateArray[i] != nullptr)
free(stateArray[i]);
}
delete[] stateArray;
}
/* ===================================================================== *
TileActivityTaskList member functions
* ===================================================================== */
//-----------------------------------------------------------------------
// The list of active tile activity tasks
static TileActivityTaskList aTaskList;
//-----------------------------------------------------------------------
// Constructor
TileActivityTaskList::TileActivityTaskList(void) {
}
//-----------------------------------------------------------------------
// Reconstruct the TileActivityTaskList from an archive buffer
TileActivityTaskList::TileActivityTaskList(void **buf) {
warning("STUB: TileActivityTaskList::TileActivityTaskList(void **buf)");
#if 0
void *bufferPtr = *buf;
int16 taskCount;
for (uint i = 0; i < ARRAYSIZE(array); i++) {
free.addTail(array[i]);
}
// Retreive the task count
taskCount = READ_LE_INT16(bufferPtr);
bufferPtr = (int16 *)bufferPtr + 1;
for (int i = 0; i < taskCount; i++) {
ActiveItem *tai;
uint8 activityType;
tai = ActiveItem::activeItemAddress(*((ActiveItemID *)bufferPtr));
bufferPtr = (ActiveItemID *)bufferPtr + 1;
activityType = *((uint8 *)bufferPtr);
bufferPtr = (uint8 *)bufferPtr + 1;
if (tai != nullptr) {
TileActivityTask *tat;
tat = newTask(tai);
if (tat != nullptr)
tat->activityType = activityType;
}
}
*buf = bufferPtr;
#endif
}
//-----------------------------------------------------------------------
// Return the number of bytes needed to archive this
// TileActivityTaskList
int32 TileActivityTaskList::archiveSize(void) {
int32 size = sizeof(int16);
for (Common::List<TileActivityTask *>::iterator it = _list.begin(); it != _list.end(); ++it)
size += sizeof(ActiveItemID) + sizeof(uint8);
return size;
}
//-----------------------------------------------------------------------
// Create an archive of this TileActivityTaskList in the specified
// archive buffer
Common::MemorySeekableReadWriteStream *TileActivityTaskList::archive(Common::MemorySeekableReadWriteStream *stream) {
int16 taskCount = _list.size();
// Store the task count
stream->writeSint16LE(taskCount);
for (Common::List<TileActivityTask *>::iterator it = _list.begin(); it != _list.end(); ++it) {
ActiveItem *ai = (*it)->tai;
// Store the activeItemID
stream->writeSint16LE(ai->thisID());
// Store the task type
stream->writeByte((*it)->activityType);
}
return stream;
}
//-----------------------------------------------------------------------
// Cleanup
void TileActivityTaskList::cleanup(void) {
for (Common::List<TileActivityTask *>::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<TileActivityTask *>::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(void) {
debugC(3, kDebugTasks, "Removing TAT");
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 = 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 = 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 = 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(void) {
int count = 0, scriptCount = 0;
for (Common::List<TileActivityTask *>::iterator it = aTaskList._list.begin(); it != 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<TileActivityTask *>::iterator it = aTaskList._list.begin(); it != 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) {
}
//-----------------------------------------------------------------------
// Save the tile activity task list to a save file
void saveTileTasks(SaveFileConstructor &saveGame) {
int32 archiveBufSize;
byte *archiveBuffer;
Common::MemorySeekableReadWriteStream *stream;
archiveBufSize = aTaskList.archiveSize();
archiveBuffer = (byte *)malloc(archiveBufSize);
if (archiveBuffer == nullptr)
error("Unable to allocate tile activity task archive buffer");
stream = new Common::MemorySeekableReadWriteStream(archiveBuffer,
archiveBufSize,
DisposeAfterUse::YES);
aTaskList.archive(stream);
saveGame.writeChunk(
MakeID('T', 'A', 'C', 'T'),
archiveBuffer,
archiveBufSize);
delete stream;
}
//-----------------------------------------------------------------------
// Load the tile activity task list from a save file
void loadTileTasks(SaveFileReader &saveGame) {
// If there is no saved data, simply call the default constructor
if (saveGame.getChunkSize() == 0) {
new (&aTaskList) TileActivityTaskList;
return;
}
void *archiveBuffer;
void *bufferPtr;
archiveBuffer = malloc(saveGame.getChunkSize());
if (archiveBuffer == nullptr)
error("Unable to allocate tile activity task archive buffer");
// Read the archived tile activity task data
saveGame.read(archiveBuffer, saveGame.getChunkSize());
bufferPtr = archiveBuffer;
// Reconstruct aTaskList from archived data
new (&aTaskList) TileActivityTaskList(&bufferPtr);
free(archiveBuffer);
}
//-----------------------------------------------------------------------
// Cleanup the tile activity task list
void cleanupTileTasks(void) {
// Simply call the aTaskList's cleanup
aTaskList.cleanup();
}
/* ===================================================================== *
Map management functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize map data
TileBank::TileBank(Common::SeekableReadStream *stream) {
_numTiles = stream->readUint32LE();
_tileArray = new TileInfo[_numTiles];
for (uint i = 0; i < _numTiles; ++i) {
_tileArray[i].offset = stream->readUint32LE();
TileAttrs *att = &_tileArray[i].attrs;
att->terrainHeight = stream->readByte();
att->height = stream->readByte();
att->terrainMask = stream->readUint16LE();
att->fgdTerrain = stream->readByte();
att->bgdTerrain = stream->readByte();
stream->read(att->reserved0, 8);
att->maskRule = stream->readByte();
att->altMask = stream->readByte();
stream->read(att->cornerHeight, 4);
att->cycleRange = stream->readByte();
att->tileFlags = stream->readByte();
att->reserved1 = stream->readUint16LE();
}
}
MapHeader::MapHeader(Common::SeekableReadStream *stream) {
size = stream->readSint16LE();
edgeType = stream->readSint16LE();
mapData = new uint16[size * size];
for (int i = 0; i < size * size; ++i)
mapData[i] = stream->readUint16LE();
}
MapHeader::~MapHeader() {
if (mapData)
delete[] mapData;
}
MetaTile::MetaTile(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();
}
ActiveItemList::ActiveItemList(WorldMapData *parent, int count, Common::SeekableReadStream *stream) {
_parent = parent;
_count = count;
_items = (ActiveItem **)malloc(_count * sizeof(ActiveItem *));
for (int i = 0; i < _count; ++i) {
_items[i] = new ActiveItem(this, i, stream);
}
}
ActiveItemList::~ActiveItemList() {
if (_items) {
for (int i = 0; i < _count; ++i) {
if (_items[i])
delete _items[i];
}
free(_items);
}
}
void initMaps(void) {
int16 i;
Common::SeekableReadStream *stream;
const int metaTileSize = 30;
const int tileRefSize = 4;
const int assocSize = 2;
const int activeItemSize = 28;
// Load all of the tile terrain banks
for (i = 0; i < maxBanks; i++) {
stream = loadResourceToStream(tileRes, tileTerrainID + i, "tile terrain bank");
tileBanks[i] = new TileBank(stream);
delete stream;
if (tileBanks[i]->_tileArray == nullptr) {
delete tileBanks[i];
tileBanks[i] = nullptr;
}
}
// Count the worlds by seeking the map data
for (worldCount = 0;
tileRes->seek(mapID + worldCount);
worldCount++) {
warning("MapID: %s %08x res: %s %08x", 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));
}
initPlatformCache();
initMapFeatures();
}
//-----------------------------------------------------------------------
// Cleanup map data
void cleanupMaps(void) {
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) {
if (tileBanks[i]->_tileArray != nullptr)
delete[] tileBanks[i]->_tileArray;
delete tileBanks[i];
tileBanks[i] = nullptr;
}
}
}
//-----------------------------------------------------------------------
// Set a new current map
void setCurrentMap(int mapNum) {
currentMapNum = mapNum;
if (lastMapNum != currentMapNum) {
lastMapNum = currentMapNum;
freeAllTileBanks();
audioEnvironmentSetWorld(mapNum);
}
lastUpdateTime = gameTime;
}
/* ===================================================================== *
Automap management functions
* ===================================================================== */
//-----------------------------------------------------------------------
void initAutoMap(void) {
int16 i;
for (i = 0; i < worldCount; i++) {
MapHeader *map;
int32 mapSize,
mapIndex;
uint16 *mapData;
map = mapList[i].map;
mapSize = map->size;
mapSize *= mapSize;
mapData = map->mapData;
// Clear the high bit for each map position
for (mapIndex = 0; mapIndex < mapSize; mapIndex++)
mapData[mapIndex] &= ~metaTileVisited;
}
}
//-----------------------------------------------------------------------
void saveAutoMap(SaveFileConstructor &saveGame) {
int32 totalMapSize = 0,
totalMapIndex = 0;
int16 i;
uint8 *archiveBuffer;
int32 archiveBufSize;
for (i = 0; i < worldCount; i++) {
MapHeader *map;
int32 mapSize;
map = mapList[i].map;
mapSize = map->size;
mapSize *= mapSize;
totalMapSize += mapSize;
}
// Compute the number of bytes needed to store the visited bit
// for each map metatile slot
archiveBufSize = (totalMapSize + 7) >> 3;
archiveBuffer = (uint8 *)malloc(archiveBufSize);
if (archiveBuffer == nullptr)
error("Unable to allocate auto map archive buffer");
for (i = 0; i < worldCount; i++) {
MapHeader *map;
int32 mapSize,
mapIndex;
uint16 *mapData;
map = mapList[i].map;
mapSize = map->size;
mapSize *= mapSize;
mapData = map->mapData;
for (mapIndex = 0; mapIndex < mapSize; mapIndex++) {
if (mapData[mapIndex] & metaTileVisited) {
// Set the bit in the archive buffer
archiveBuffer[totalMapIndex >> 3] |=
(1 << (totalMapIndex & 7));
} else {
// Clear the bit in the archive buffer
archiveBuffer[totalMapIndex >> 3] &=
~(1 << (totalMapIndex & 7));
}
totalMapIndex++;
}
}
saveGame.writeChunk(
MakeID('A', 'M', 'A', 'P'),
archiveBuffer,
archiveBufSize);
free(archiveBuffer);
}
//-----------------------------------------------------------------------
void loadAutoMap(SaveFileReader &saveGame) {
int32 totalMapIndex = 0;
int16 i;
uint8 *archiveBuffer;
int32 archiveBufSize;
archiveBufSize = saveGame.getChunkSize();
archiveBuffer = (uint8 *)malloc(archiveBufSize);
if (archiveBuffer == nullptr)
error("Unable to allocate auto map archive buffer");
saveGame.read(archiveBuffer, archiveBufSize);
for (i = 0; i < worldCount; i++) {
MapHeader *map;
int32 mapSize,
mapIndex;
uint16 *mapData;
map = mapList[i].map;
mapSize = map->size;
mapSize *= mapSize;
mapData = map->mapData;
for (mapIndex = 0; mapIndex < mapSize; mapIndex++) {
assert((totalMapIndex >> 3) < archiveBufSize);
// If the bit is set in the archive buffer, set the visited
// bit in the map data
if (archiveBuffer[totalMapIndex >> 3]
& (1 << (totalMapIndex & 7)))
mapData[mapIndex] |= metaTileVisited;
else
mapData[mapIndex] &= ~metaTileVisited;
totalMapIndex++;
}
}
free(archiveBuffer);
}
/* ===================================================================== *
Platform cache functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the platform cache
void initPlatformCache(void) {
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[currentMapNum].mapHeight;
TilePoint coords;
// coordinates of the view in U,V
coords.u = (((pt.x + mapHeight) >> 1) - pt.y) >> 1;
coords.v = (mapHeight - pt.y - ((pt.x - mapHeight) >> 1)) >> 1;
coords.z = 0;
return coords;
}
/* ===================================================================== *
Converts a (u,v,z) tilepoint to (x, y) screen coords;
* ===================================================================== */
void TileToScreenCoords(const TilePoint &tp, Point16 &p) {
int32 mapHeight = mapList[currentMapNum].mapHeight;
// screen coords of the point
p.x = (((int32)tp.u - (int32)tp.v) << 1) - tileScroll.x + mapHeight;
p.y = mapHeight - tileScroll.y - ((int32)tp.u + (int32)tp.v) - tp.z;
}
TilePoint::TilePoint(Common::SeekableReadStream *stream) {
u = stream->readSint16LE();
v = stream->readSint16LE();
z = stream->readSint16LE();
}
//-----------------------------------------------------------------------
// Converts a UV vector into a rough direction vector.
int16 TilePoint::quickDir(void) {
int16 u2 = u * 2,
v2 = v * 2;
if (u < v2) {
if (v > -u2) return (v > u2 ? dirUpLeft : dirUp);
return (u > -v2 ? dirLeft : dirDownLeft);
} else {
if (v > -u2) return (u > -v2 ? dirUpRight : dirRight);
return (v > u2 ? dirDown : dirDownRight);
}
}
/* ===================================================================== *
Do a bilinear interpolation of the four corner heights of a tile
to determine the height of a point on the tile.
* ===================================================================== */
int16 ptHeight(const TilePoint &tp, uint8 *cornerHeight) {
int16 slopeHeight = cornerHeight[0];
if (cornerHeight[1] == slopeHeight &&
cornerHeight[2] == slopeHeight &&
cornerHeight[3] == slopeHeight)
return slopeHeight;
slopeHeight
= (cornerHeight[0] * (kTileUVSize - tp.u)
+ cornerHeight[1] * tp.u)
* (kTileUVSize - tp.v)
+ (cornerHeight[3] * (kTileUVSize - tp.u)
+ cornerHeight[2] * tp.u)
* tp.v;
return slopeHeight >> (kTileUVShift + kTileUVShift);
}
/* ====================================================================== *
Platform member functions
* ====================================================================== */
//-----------------------------------------------------------------------
// Fetch the REAL tile associated with a particular location, including
// indirection such as tile cycling and activity groups.
// REM: This is a likely candidate for downcoding...
TileInfo *Platform::fetchTile(
int16 mapNum,
const TilePoint &pt,
const TilePoint &origin,
int16 &height_,
int16 &trFlags) {
TileRef *tr = &tiles[pt.u][pt.v];
TileInfo *ti;
int16 h = tr->tileHeight * 8;
if (tr->flags & trTileTAG) {
ActiveItem *groupItem,
*instanceItem;
int16 state = 0;
TilePoint relPos,
absPos;
groupItem = ActiveItem::activeItemAddress(
ActiveItemID(mapNum, tr->tile));
// Relpos is the relative position of the
// tile within the group
relPos.u = (tr->flags >> 1) & 0x07;
relPos.v = (tr->flags >> 4) & 0x07;
// Abspos is the absolute position of the
// group on the tile map.
absPos.u = pt.u - relPos.u + origin.u;
absPos.v = pt.v - relPos.v + origin.v;
absPos.z = h;
// Look up the group instance in the hash.
instanceItem = mapList[mapNum].findHashedInstance(
absPos,
tr->tile);
if (instanceItem) {
state = instanceItem->getInstanceState(mapNum);
// Get the tile to be drawn from the tile group
tr = &(mapList[mapNum].activeItemData)[
groupItem->_data.group.grDataOffset
+ state * groupItem->_data.group.animArea
+ relPos.u * groupItem->_data.group.vSize
+ relPos.v];
h += tr->tileHeight * 8;
}
#if DEBUG
else {
static TileRef dummyRef = { 1, 0, 0 };
tr = &dummyRef;
}
#endif
}
if ((ti = TileInfo::tileAddress(tr->tile)) == nullptr) return nullptr;
trFlags = tr->flags;
height_ = h;
#if DEBUG
if (ti->offset > maxOffset
|| ti->attrs.height > kMaxTileHeight
|| ti->attrs.height < 0) {
int16 tileNo, tileBank;
TileID2Bank(tr->tile, tileBank, tileNo);
WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank);
return nullptr;
}
#endif
return ti;
}
// Fetch the tile and the active item it came from...
// REM: This is a likely candidate for downcoding...
TileInfo *Platform::fetchTAGInstance(
int16 mapNum,
const TilePoint &pt,
const TilePoint &origin,
StandingTileInfo &sti) {
TileRef *tr = &tiles[pt.u][pt.v];
TileInfo *ti;
int16 h = tr->tileHeight * 8;
if (tr->flags & trTileTAG) {
ActiveItem *groupItem,
*instanceItem;
int16 state = 0;
TilePoint relPos,
absPos;
groupItem = ActiveItem::activeItemAddress(
ActiveItemID(mapNum, tr->tile));
// Relpos is the relative position of the
// tile within the group
relPos.u = (tr->flags >> 1) & 0x07;
relPos.v = (tr->flags >> 4) & 0x07;
// Abspos is the absolute position of the
// group on the tile map.
absPos.u = pt.u - relPos.u + origin.u;
absPos.v = pt.v - relPos.v + origin.v;
absPos.z = h;
// Look up the group instance in the hash.
instanceItem = mapList[mapNum].findHashedInstance(
absPos,
tr->tile);
if (instanceItem) {
state = instanceItem->getInstanceState(mapNum);
sti.surfaceTAG = instanceItem;
// Get the tile to be drawn from the tile group
tr = &(mapList[mapNum].activeItemData)[
groupItem->_data.group.grDataOffset
+ state * groupItem->_data.group.animArea
+ relPos.u * groupItem->_data.group.vSize
+ relPos.v];
h += tr->tileHeight * 8;
}
#if DEBUG
else {
static TileRef dummyRef = { 1, 0, 0 };
tr = &dummyRef;
}
#endif
} else {
sti.surfaceTAG = nullptr;
}
if ((ti = TileInfo::tileAddress(tr->tile)) == nullptr) return nullptr;
sti.surfaceTile = ti;
sti.surfaceRef = *tr;
sti.surfaceHeight = h;
return ti;
}
//-----------------------------------------------------------------------
// Fetch the REAL tile associated with a particular location, including
// indirection such as tile cycling and activity groups.
// REM: This is a likely candidate for downcoding...
TileInfo *Platform::fetchTile(
int16 mapNum,
const TilePoint &pt,
const TilePoint &origin,
uint8 **imageData,
int16 &height_,
int16 &trFlags) {
TileRef *tr = &tiles[pt.u][pt.v];
TileInfo *ti;
int16 h = tr->tileHeight * 8;
if (tr->flags & trTileTAG) {
ActiveItem *groupItem,
*instanceItem;
int16 state = 0;
TilePoint relPos,
absPos;
groupItem = ActiveItem::activeItemAddress(
ActiveItemID(mapNum, tr->tile));
// Relpos is the relative position of the
// tile within the group
relPos.u = (tr->flags >> 1) & 0x07;
relPos.v = (tr->flags >> 4) & 0x07;
// Abspos is the absolute position of the
// group on the tile map.
absPos.u = pt.u - relPos.u + origin.u;
absPos.v = pt.v - relPos.v + origin.v;
absPos.z = h;
// Look up the group instance in the hash.
instanceItem = mapList[mapNum].findHashedInstance(
absPos,
tr->tile);
if (instanceItem) {
state = instanceItem->getInstanceState(mapNum);
// Get the tile to be drawn from the tile group
tr = &(mapList[mapNum].activeItemData)[
groupItem->_data.group.grDataOffset
+ state * groupItem->_data.group.animArea
+ relPos.u * groupItem->_data.group.vSize
+ relPos.v];
h += tr->tileHeight * 8;
}
#if DEBUG
else {
static TileRef dummyRef = { 1, 0, 0 };
tr = &dummyRef;
}
#endif
}
if ((ti = TileInfo::tileAddress(tr->tile, imageData)) == nullptr) return nullptr;
trFlags = tr->flags;
height_ = h;
#if DEBUG
if (ti->offset > maxOffset
|| ti->attrs.height > kMaxTileHeight
|| ti->attrs.height < 0) {
int16 tileNo, tileBank;
TileID2Bank(tr->tile, tileBank, tileNo);
WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank);
return nullptr;
}
#endif
return ti;
}
// Fetch the tile and the active item it came from...
// REM: This is a likely candidate for downcoding...
TileInfo *Platform::fetchTAGInstance(
int16 mapNum,
const TilePoint &pt,
const TilePoint &origin,
uint8 **imageData,
StandingTileInfo &sti) {
TileRef *tr = &tiles[pt.u][pt.v];
TileInfo *ti;
int16 h = tr->tileHeight * 8;
if (tr->flags & trTileTAG) {
ActiveItem *groupItem,
*instanceItem;
int16 state = 0;
TilePoint relPos,
absPos;
groupItem = ActiveItem::activeItemAddress(
ActiveItemID(mapNum, tr->tile));
// Relpos is the relative position of the
// tile within the group
relPos.u = (tr->flags >> 1) & 0x07;
relPos.v = (tr->flags >> 4) & 0x07;
// Abspos is the absolute position of the
// group on the tile map.
absPos.u = pt.u - relPos.u + origin.u;
absPos.v = pt.v - relPos.v + origin.v;
absPos.z = h;
// Look up the group instance in the hash.
instanceItem = mapList[mapNum].findHashedInstance(
absPos,
tr->tile);
if (instanceItem) {
state = instanceItem->getInstanceState(mapNum);
sti.surfaceTAG = instanceItem;
// Get the tile to be drawn from the tile group
tr = &(mapList[mapNum].activeItemData)[
groupItem->_data.group.grDataOffset
+ state * groupItem->_data.group.animArea
+ relPos.u * groupItem->_data.group.vSize
+ relPos.v];
h += tr->tileHeight * 8;
}
#if DEBUG
else {
static TileRef dummyRef = { 1, 0, 0 };
tr = &dummyRef;
}
#endif
} else {
sti.surfaceTAG = nullptr;
}
if ((ti = TileInfo::tileAddress(tr->tile, imageData)) == nullptr) return nullptr;
sti.surfaceTile = ti;
sti.surfaceRef = *tr;
sti.surfaceHeight = h;
return ti;
}
/* ====================================================================== *
RipTable member functions
* ====================================================================== */
//-----------------------------------------------------------------------
// Return a pointer to a rip table give the rip table's ID
RipTable *RipTable::ripTableAddress(RipTableID id) {
return id != -1 ? &ripTableList[id] : nullptr;
}
//-----------------------------------------------------------------------
// Return a rip table's ID
RipTableID RipTable::thisID(void) {
warning("RipTable::thisID(): Unsafe pointer arithmetics");
return this - ripTableList;
}
/* ====================================================================== *
MetaTile member functions
* ====================================================================== */
//-----------------------------------------------------------------------
// Return a pointer to a meta tile given its ID
MetaTile *MetaTile::metaTileAddress(MetaTileID id) {
return id.map != nullID && id.index != nullID
? mapList[id.map].metaList->_tiles[id.index]
: nullptr;
}
//-----------------------------------------------------------------------
// Return this meta tile's ID
MetaTileID MetaTile::thisID(int16 mapNum) {
return MetaTileID(mapNum, _index);
}
//-----------------------------------------------------------------------
// Return the audio theme associated with this metatile
metaTileNoise MetaTile::HeavyMetaMusic(void) {
return _properties & 0xFF;
}
//-----------------------------------------------------------------------
// Return a pointer to the specified platform
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);
return &pce->pl;
}
}
error("Unable to read Platform %d of map %d", plIndex, mapNum);
return nullptr;
}
}
//-----------------------------------------------------------------------
// Return a pointer to this metatile's current object ripping
// table
RipTable *MetaTile::ripTable(int16 mapNum) {
WorldMapData *mapData = &mapList[mapNum];
return RipTable::ripTableAddress((mapData->ripTableIDList)[_index]);
}
//-----------------------------------------------------------------------
// Return a reference to this meta tile's rip table ID
RipTableID &MetaTile::ripTableID(int16 mapNum) {
WorldMapData *mapData = &mapList[mapNum];
return (mapData->ripTableIDList)[_index];
}
/* ====================================================================== *
WorldMapData member functions
* ====================================================================== */
//-----------------------------------------------------------------------
// Return a pointer to the specified meta tile on this map
MetaTilePtr WorldMapData::lookupMeta(TilePoint coords) {
uint16 *mapData = map->mapData;
int16 mtile;
#if 0
// Note: Keep this code if we ever need to have variable
// map edge types in the future.
TilePoint clipCoords;
int16 mapSizeMask = mapSize - 1,
mapEdgeType = (*map)->edgeType;
clipCoords.u = (uint16)coords.u % mapSize;
clipCoords.v = (uint16)coords.v % mapSize;
clipCoords.z = coords.z;
if (coords != clipCoords) {
switch (mapEdgeType) {
case edgeTypeBlack: // continue;
case edgeTypeFill0:
mtile = 0;
break;
case edgeTypeFill1:
mtile = 1;
break;
case edgeTypeRepeat:
coords.u = clamp(0, coords.u, mapSizeMask);
coords.v = clamp(0, coords.v, mapSizeMask);
mtile = mapData[clipCoords.u * mapSize + clipCoords.v];
break;
case edgeTypeWrap:
mtile = mapData[clipCoords.u * mapSize + clipCoords.v];
break;
}
} else mtile = mapData[clipCoords.u * mapSize + clipCoords.v];
#else
// Check to see if coords are less than zero or greater
// than size of map. Note we can eliminate less than
// zero test by doing an unsigned compare.
if ((uint32)coords.u >= (uint32)mapSize
|| (uint32)coords.v >= (uint32)mapSize) {
// If off the edge of the map, it defaults to meta
// tile #1.
mtile = 1;
} else {
// When getting the metatile number, make sure to mask off the
// bit indicating that this map square has been visited.
mtile = mapData[coords.u * mapSize + coords.v] & ~metaTileVisited;
}
#endif
assert(mtile < metaCount);
assert(mtile >= 0);
return metaList->_tiles[mtile];
}
//-----------------------------------------------------------------------
// Builds an active item instance hash table for tile lookup
void WorldMapData::buildInstanceHash(void) {
int32 i;
int16 hashVal;
ActiveItem **ail;
memset(instHash, 0, sizeof(instHash));
for (i = 0, ail = activeItemList->_items; i < activeCount; i++, ail++) {
ActiveItem *ai = *ail;
if (ai->_data.itemType == activeTypeInstance) {
hashVal = (((ai->_data.instance.u + ai->_data.instance.h) << 4)
+ ai->_data.instance.v + (ai->_data.instance.groupID << 2))
% ARRAYSIZE(instHash);
itemHash.setVal(hashVal, ai);
}
}
}
//-----------------------------------------------------------------------
// Lookup an active item instance given the active item group number
// an the meta tile coordinates
ActiveItem *WorldMapData::findHashedInstance(
TilePoint &tp,
int16 group) {
int16 hashVal = (((tp.u + tp.z) << 4) + tp.v + (group << 2))
% ARRAYSIZE(instHash);
if (itemHash.contains(hashVal))
return itemHash.getVal(hashVal);
return nullptr;
}
/* ====================================================================== *
MetaTileIterator member functions
* ====================================================================== */
bool MetaTileIterator::iterate(void) {
if (++mCoords.v >= region.max.v) {
if (++mCoords.u >= region.max.u) return false;
mCoords.v = region.min.v;
}
return true;
}
MetaTile *MetaTileIterator::first(TilePoint *loc) {
MetaTile *mtRes;
mCoords = region.min;
if (mCoords.u >= region.max.u || mCoords.v >= region.max.v)
return nullptr;
mtRes = mapList[mapNum].lookupMeta(mCoords);
while (mtRes == nullptr) {
if (!iterate()) return nullptr;
mtRes = mapList[mapNum].lookupMeta(mCoords);
}
if (loc) *loc = mCoords << kPlatShift;
return mtRes;
}
MetaTile *MetaTileIterator::next(TilePoint *loc) {
MetaTile *mtRes = nullptr;
do {
if (!iterate()) return nullptr;
mtRes = mapList[mapNum].lookupMeta(mCoords);
} while (mtRes == nullptr);
if (loc) *loc = mCoords << kPlatShift;
return mtRes;
}
/* ====================================================================== *
TileIterator member functions
* ====================================================================== */
bool TileIterator::iterate(void) {
if (++tCoords.v >= tCoordsReg.max.v) {
if (++tCoords.u >= tCoordsReg.max.u) {
do {
platIndex++;
if (platIndex >= maxPlatforms) {
if ((mt = metaIter.next(&origin)) != nullptr) {
tCoordsReg.min.u = tCoordsReg.min.v = 0;
tCoordsReg.max.u = tCoordsReg.max.v = kPlatformWidth;
if (origin.u < region.min.u)
tCoordsReg.min.u = region.min.u & kPlatMask;
if (origin.u + kPlatformWidth > region.max.u)
tCoordsReg.max.u = region.max.u & kPlatMask;
if (origin.v < region.min.v)
tCoordsReg.min.v = region.min.v & kPlatMask;
if (origin.v + kPlatformWidth > region.max.v)
tCoordsReg.max.v = region.max.v & kPlatMask;
} else
return false;
platIndex = 0;
}
platform = mt->fetchPlatform(
metaIter.getMapNum(),
platIndex);
} while (platform == nullptr);
tCoords.u = tCoordsReg.min.u;
}
tCoords.v = tCoordsReg.min.v;
}
return true;
}
TileInfo *TileIterator::first(TilePoint *loc, StandingTileInfo *stiResult) {
TileInfo *tiRes;
StandingTileInfo sti;
if (region.max.u <= region.min.u || region.max.v <= region.min.v)
return nullptr;
if ((mt = metaIter.first(&origin)) == nullptr) return nullptr;
platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex = 0);
while (platform == nullptr) {
platIndex++;
if (platIndex >= maxPlatforms) {
if ((mt = metaIter.next(&origin)) == nullptr) return nullptr;
platIndex = 0;
}
platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex);
}
tCoordsReg.min.u = tCoordsReg.min.v = 0;
tCoordsReg.max.u = tCoordsReg.max.v = kPlatformWidth;
if (origin.u < region.min.u)
tCoordsReg.min.u = region.min.u & kPlatMask;
if (origin.u + kPlatformWidth > region.max.u)
tCoordsReg.max.u = region.max.u & kPlatMask;
if (origin.v < region.min.v)
tCoordsReg.min.v = region.min.v & kPlatMask;
if (origin.v + kPlatformWidth > region.max.v)
tCoordsReg.max.v = region.max.v & kPlatMask;
tCoords = tCoordsReg.min;
tiRes = platform->fetchTAGInstance(
metaIter.getMapNum(),
tCoords,
origin,
sti);
while (tiRes == nullptr) {
if (!iterate()) return nullptr;
tiRes = platform->fetchTAGInstance(
metaIter.getMapNum(),
tCoords,
origin,
sti);
}
*loc = tCoords + origin;
if (stiResult) *stiResult = sti;
return tiRes;
}
TileInfo *TileIterator::next(TilePoint *loc, StandingTileInfo *stiResult) {
TileInfo *tiRes = nullptr;
StandingTileInfo sti;
do {
if (!iterate()) return nullptr;
tiRes = platform->fetchTAGInstance(
metaIter.getMapNum(),
tCoords,
origin,
sti);
} while (tiRes == nullptr);
*loc = tCoords + origin;
if (stiResult) *stiResult = sti;
return tiRes;
}
/* ============================================================================ *
NOTE: drawPlatform has been moved to TILELOAD.CPP for now
* ============================================================================ */
/* ============================================================================ *
Map drawing functions
* ============================================================================ */
inline void drawMetaRow(TilePoint coords, Point16 pos) {
WorldMapData *curMap = &mapList[currentMapNum];
int16 uOrg = coords.u * kPlatformWidth,
vOrg = coords.v * kPlatformWidth;
Platform *drawList[maxPlatforms + 1],
**put = drawList;
int16 mapSizeMask = curMap->mapSize - 1,
mapEdgeType = curMap->map->edgeType;
uint16 *mapData = curMap->map->mapData;
MetaTilePtr *metaArray = curMap->metaList->_tiles;
int16 layerLimit;
for (;
pos.x < tileDrawMap.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(currentMapNum, i);
if (!p)
continue;
if (p->roofRipID() == rippedRoofID && rippedRoofID > 0) break;
if (p->flags & plVisible) {
// REM: precompute this later, by scanning the platform
// for individual altitudes
p->highestPixel = kTileHeight * (kPlatformWidth - 1) + kMaxTileHeight * 2 + 64;
if (pos.y <= 0
|| pos.y - p->highestPixel >= tileDrawMap.size.y)
continue;
*put++ = p;
}
}
*put++ = nullptr;
if (drawList[0] != nullptr) {
drawPlatform(drawList, pos, uOrg, vOrg);
}
// gThread::yield();
}
}
//-----------------------------------------------------------------------
// Build the object ripping tables for a specified metatile with a
// specified roof ripping ID
void buildRipTable(
uint16 ripID,
RipTable *ripTable,
MetaTile *mt) {
const int32 initVal = ((int32)maxint16 << 16) | maxint16;
int32 *initPtr = (int32 *)ripTable->zTable;
// Initialize table
mt->ripTableID(currentMapNum) = ripTable->thisID();
ripTable->metaID = mt->thisID(currentMapNum);
ripTable->ripID = ripID;
for (uint i = 0;
i < sizeof(ripTable->zTable) / sizeof(initVal);
i++)
*initPtr++ = initVal;
// If there is no roof ripping ID, we're done
if (ripID == 0) return;
// Determine number of tile positions in meta tile for which to
// calculate object ripping altitude
int16 tilesToGo = kPlatformWidth * kPlatformWidth;
for (uint i = 0; i < maxPlatforms; i++) {
Platform *p;
if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr) continue;
if (p->roofRipID() != ripID) continue;
for (; i < maxPlatforms && tilesToGo > 0; i++) {
if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr)
continue;
uint16 platHeight = p->height << 3;
int16 u, v;
for (u = 0; u < kPlatformWidth; u++)
for (v = 0; v < kPlatformWidth; v++)
if (ripTable->zTable[u][v] == maxint16) {
TileRef &tr = p->getTileRef(u, v);
if (tr.tile != 0) {
// Calculate object ripping altitude for
// tile position
ripTable->zTable[u][v] =
platHeight + (tr.tileHeight << 3);
tilesToGo--;
}
}
}
break;
}
}
//-----------------------------------------------------------------------
// Build the object ripping tables for the metatiles in the vicinity of
// the center view object
void buildRipTables(void) {
const int16 regionRadius = kTileUVSize * kPlatformWidth * 2;
TilePoint actorCoords;
MetaTile *mt;
TileRegion ripTableReg;
MetaTile *mtTable[25]; // Largest region is 5x5
int16 mtTableSize = 0;
getViewTrackPos(actorCoords);
ripTableCoords.u = actorCoords.u >> (kTileUVShift + kPlatShift);
ripTableCoords.v = actorCoords.v >> (kTileUVShift + kPlatShift);
ripTableCoords.z = 0;
// Calculate the region of meta tile for which to build object
// ripping table
ripTableReg.min.u = (actorCoords.u - regionRadius) >> kTileUVShift;
ripTableReg.min.v = (actorCoords.v - regionRadius) >> kTileUVShift;
ripTableReg.max.u =
(actorCoords.u + regionRadius + kTileUVMask) >> kTileUVShift;
ripTableReg.max.v =
(actorCoords.v + regionRadius + kTileUVMask) >> kTileUVShift;
MetaTileIterator mIter(currentMapNum, ripTableReg);
// Build meta tile pointer array
mt = mIter.first();
while (mt) {
mtTable[mtTableSize++] = mt;
mt = mIter.next();
}
int16 tableIndex;
// bit array of available rip tables
uint8 tableAvail[(RipTable::kRipTableSize + 7) >> 3];
memset(tableAvail, 0xFF, sizeof(tableAvail));
for (int i = 0; i < mtTableSize; i++) {
mt = mtTable[i];
RipTable *mtRipTable = mt->ripTable(currentMapNum);
// If meta tile aready has a valid object ripping table, simply
// recycle it
if (mtRipTable && mtRipTable->ripID == rippedRoofID) {
// Null out pointer
mtTable[i] = nullptr;
// Mark the table as unavailable
tableIndex = mtRipTable - ripTableList;
tableAvail[tableIndex >> 3] &= ~(1 << (tableIndex & 0x7));
}
}
// Remove empty entries from meta tile pointer array
int16 oldMtTableSize = mtTableSize;
for (int i = 0, j = 0; i < oldMtTableSize; i++) {
if (mtTable[i] != nullptr)
mtTable[j++] = mtTable[i];
else
mtTableSize--;
}
for (int i = 0; i < mtTableSize; i++) {
mt = mtTable[i];
uint j;
// Find available table
for (j = 0; j < 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) {
MetaTile *rip_mt = MetaTile::metaTileAddress(
ripTableList[j].metaID);
RipTableID &rt = rip_mt->ripTableID(currentMapNum);
// Assign -1 to the meta tile's rip table ID
if (RipTable::ripTableAddress(rt) == &ripTableList[j])
rt = -1;
}
// Build meta tile's object ripping table
buildRipTable(rippedRoofID, &ripTableList[j], mt);
}
}
// Determine which metatiles in the local area will have
// cutaway roofs...
void buildRoofTable(void) {
uint16 newRoofID = objRoofID(getViewCenterObject());
if (newRoofID != rippedRoofID) {
rippedRoofID = newRoofID;
buildRipTables();
}
}
// Draw all visible metatiles
inline void drawMetaTiles(void) {
Point32 viewPos;
Point16 metaPos;
TilePoint baseCoords;
//updateHandleRefs(baseCoords); // viewPoint, &sti );
// coordinates of the view window on the map in X,Y (in 16 pixel units)
viewPos.x = (tileScroll.x >> kTileDXShift)
- (kPlatformWidth * mapList[currentMapNum].mapSize),
viewPos.y = (kPlatformWidth
* mapList[currentMapNum].mapSize
* kTileDX)
- tileScroll.y;
debugC(2, kDebugTiles, "viewPos = (%d,%d)", viewPos.x, viewPos.y);
// coordinates of the view window upper left corner in U,V
baseCoords.u = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) + viewPos.x)
/ (kPlatformWidth * 2);
baseCoords.v = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) - viewPos.x)
/ (kPlatformWidth * 2);
baseCoords.z = 0;
debugC(2, kDebugTiles, "baseCoords = (%d,%d,%d)", baseCoords.u, baseCoords.v, baseCoords.z);
setAreaSound(baseCoords);
updateHandleRefs(baseCoords); // viewPoint, &sti );
// coordinates of current metatile (in X,Y), relative to screen
metaPos.x = (baseCoords.u - baseCoords.v) * kMetaDX
- viewPos.x * kTileDX;
metaPos.y = viewPos.y
- (baseCoords.u + baseCoords.v) * kMetaDY;
debugC(2, kDebugTiles, "metaPos = (%d,%d)", metaPos.x, metaPos.y);
// Loop through each horizontal row of metatiles
// REM: also account for highest possible platform
// (replace 256 constant with better value)
for (;
metaPos.y < tileDrawMap.size.y + kMetaTileHeight * 4 ;
baseCoords.u--,
baseCoords.v--
) {
drawMetaRow(baseCoords, metaPos);
metaPos.y += kMetaDY;
metaPos.x -= kMetaDX;
drawMetaRow(TilePoint(baseCoords.u - 1, baseCoords.v, 0), metaPos);
metaPos.y += kMetaDY;
metaPos.x += kMetaDX;
}
}
/* ===================================================================== *
Mouse Pointer Fixup Routine
* ===================================================================== */
// This routine draws an image of the mouse pointer onto the
// back buffer, and also updates the mouse pointer's backsave
// buffer with the new data underneath it.
//
// Since the blitting of the backsave buffer takes so long,
// it would cause annoying flicker to have to hide the mouse
// pointer during the blit. Instead, we go ahead and over-write
// the image of the mouse pointer with the image of the new
// mouse pointer.
inline void drawTileMousePointer(void) {
gPixelMap *currentPtr,
*saveMap;
Point16 offset;
Rect16 saveExtent,
blitExtent;
// Get the image of the pointer and the hotspot offset
currentPtr = pointer.getImage(offset);
// If pointer exists, and is in a visible state
if (currentPtr && pointer.isShown()) {
// Get address of pointer's backsave rect
saveMap = pointer.getSaveMap(saveExtent);
// If the pointer overlaps the tile scrolling area
if (saveExtent.overlap(Rect16(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight))) {
// get the intersecting area
blitExtent = intersect(saveExtent, Rect16(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight));
mouseSavePort.setMap(saveMap);
// Blit the tile data into the backsave buffer
mouseSavePort.bltPixels(
tileDrawMap,
blitExtent.x - kTileRectX + fineScroll.x,
blitExtent.y - kTileRectY + fineScroll.y,
blitExtent.x - saveExtent.x,
blitExtent.y - saveExtent.y,
blitExtent.width,
blitExtent.height);
// Blit the mouse pointer onto the tile map
int x, y;
x = mouseState.pos.x + offset.x + fineScroll.x - kTileRectX;
y = mouseState.pos.y + offset.y + fineScroll.y - kTileRectY;
// if ( x >=0 && y >=0 )
TBlit(&tileDrawMap, currentPtr, x, y);
}
}
}
/* ===================================================================== *
Tile masking
* ===================================================================== */
enum maskRules {
maskRuleNever, // never mask
maskRuleAlways, // always mask
// Mask based on U threshold
maskRuleUClose,
maskRuleUMed,
maskRuleUFar,
// Mask based on V threshold
maskRuleVClose,
maskRuleVMed,
maskRuleVFar,
// Mask based on vertical distance
maskRuleYClose,
maskRuleYMed,
maskRuleYFar,
// Mask based on combined U & V
maskRuleConvexNear,
maskRuleConcaveFar,
maskRuleConvexFar,
maskRuleConcaveNear
// More mask types to come!
};
const int thresh1 = 0,
thresh2 = kTileUVSize / 4,
thresh3 = kTileUVSize - 1;
// l is the relative position of the character with repect
// to the tile in U,V coords.
inline bool maskRule(TilePoint &l, TileInfo &ti) {
int16 slopeHeight = ptHeight(l, ti.attrs.cornerHeight);
// If it's a tall tile, and character is above the height of
// the tile, then don't mask.
/* if (ti.attrs.height > tileDX * 2)
{
int16 a = 0;
a++;
}*/
if ((l.z >= ti.attrs.terrainHeight
&& l.z >= slopeHeight)
|| l.u < -3
|| l.v < -3)
return false;
if (l.u > 0 && l.v > 0) {
if (l.u > thresh3 || l.v > thresh3) {
if (l.z < slopeHeight - 8) return true;
} else {
if (l.z < slopeHeight - 56) return true;
}
}
// if (ti.attrs.height > tileDX * 2)
// {
// ti.attrs.maskRule = maskRuleConcaveNear;
// }
// else ti.attrs.maskRule = maskRuleNever;
// REM: Check for special-shaped actors...
switch (ti.attrs.maskRule) {
case maskRuleNever:
// if (l.z < -8 && (l.u > thresh3 || l.v > thresh3)) return true;
return false;
case maskRuleAlways:
return true;
case maskRuleUClose:
return (l.u > thresh1);
case maskRuleUMed:
return (l.u > thresh2);
case maskRuleUFar:
return (l.u > thresh3);
case maskRuleVClose:
return (l.v > thresh1);
case maskRuleVMed:
return (l.v > thresh2);
case maskRuleVFar:
return (l.v > thresh3);
case maskRuleYClose:
return (l.u + l.v > thresh1 + thresh1);
case maskRuleYMed:
return (l.u + l.v > thresh2 + thresh2);
case maskRuleYFar:
return (l.u + l.v > thresh3 + thresh3);
case maskRuleConvexNear:
return (l.u > thresh1 && l.v > thresh1);
case maskRuleConvexFar:
return (l.u > thresh2 && l.v > thresh2);
case maskRuleConcaveNear:
return (l.u > thresh2 || l.v > thresh2);
case maskRuleConcaveFar:
return (l.u > thresh3 || l.v > thresh3);
}
return false;
}
void maskPlatform(
gPixelMap &sMap,
Platform **pList, // platforms to draw
Point16 screenPos, // screen position
TilePoint relLoc, // relative location
int16 uOrg, // for TAG search
int16 vOrg) { // for TAG search
int16 u, v;
int16 right = sMap.size.x,
bottom = sMap.size.y;
Point16 tilePos;
int16 x = screenPos.x,
x2 = x / kTileDX;
int16 length = 1;
TilePoint rLoc;
TilePoint origin(uOrg, vOrg, 0);
tilePos.y = screenPos.y - (kPlatformWidth - 1) * kTileHeight;
u = kPlatformWidth - 1;
v = kPlatformWidth - 1;
relLoc.u = - relLoc.u - (kPlatformWidth - 1) * kTileUVSize;
relLoc.v = - relLoc.v - (kPlatformWidth - 1) * kTileUVSize;
for (int row = 0; row < 15; row++) {
if (tilePos.y > 0) {
int16 col = 0;
TilePoint pCoords(u, v, 0);
tilePos.x = x;
rLoc = relLoc;
if (length > x2) {
int16 offset = (length - x2) >> 1;
pCoords.u += offset;
pCoords.v -= offset;
rLoc.u -= offset * kTileUVSize;
rLoc.v += offset * kTileUVSize;
offset <<= 1;
col += offset;
tilePos.x += kTileDX * offset;
}
for (;
col < length && tilePos.x <= right;
col += 2,
pCoords.u++,
pCoords.v--,
rLoc.u -= kTileUVSize,
rLoc.v += kTileUVSize,
tilePos.x += kTileWidth
) {
Platform **pGet;
if (tilePos.x < 0) continue;
if (rLoc.u <= -kTileUVSize || rLoc.v <= -kTileUVSize)
continue;
for (pGet = pList; *pGet; pGet++) {
Platform &p = **pGet;
int16 h,
y;
TileInfo *ti;
uint8 *imageData;
int16 trFlags;
ti = p.fetchTile(
currentMapNum,
pCoords,
origin,
&imageData,
h,
trFlags);
if (ti == nullptr) continue;
// Compute height of character above tile.
rLoc.z = relLoc.z - h;
if (maskRule(rLoc, *ti)) {
y = tilePos.y - h;
// REM: Check for AltMask!!!
if (ti->attrs.height > 0
&& y < bottom + ti->attrs.height - 1) {
maskTile(&sMap,
tilePos.x, y, ti->attrs.height,
imageData);
}
}
}
}
}
if (row < 7) {
x -= kTileDX;
x2++;
length += 2;
u--;
relLoc.u += kTileUVSize;
} else {
x += kTileDX;
x2--;
length -= 2;
v--;
relLoc.v += kTileUVSize;
}
tilePos.y += kTileDY;
}
}
void maskMetaRow(
gPixelMap &sMap,
TilePoint coords,
TilePoint relLoc,
Point16 pos,
uint16 roofID) {
WorldMapData *curMap = &mapList[currentMapNum];
int16 uOrg = coords.u * kPlatformWidth,
vOrg = coords.v * kPlatformWidth;
Platform *drawList[maxPlatforms + 1],
**put = drawList;
int16 mapSizeMask = curMap->mapSize - 1,
mapEdgeType = curMap->map->edgeType;
uint16 *mapData = curMap->map->mapData;
MetaTilePtr *metaArray = curMap->metaList->_tiles;
int16 layerLimit;
for (;
pos.x < sMap.size.x + kMetaDX;
coords.u++,
coords.v--,
relLoc.u += kPlatUVSize,
relLoc.v -= kPlatUVSize,
uOrg += kPlatformWidth,
vOrg -= kPlatformWidth,
pos.x += kMetaTileWidth
) {
TilePoint clipCoords;
int16 mtile = 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(currentMapNum, i)) == nullptr)
continue;
if (p->roofRipID() == roofID && roofID > 0) break;
if (p->flags & plVisible) {
// REM: precompute this later, by scanning the platform
// for individual altitudes
p->highestPixel = kTileHeight * (kPlatformWidth - 1) + kMaxTileHeight + 192;
if (pos.y <= 0
|| pos.y - p->highestPixel >= sMap.size.y)
continue;
*put++ = p;
}
}
*put++ = nullptr;
if (drawList[0] != nullptr) {
maskPlatform(sMap, drawList, pos, relLoc, uOrg, vOrg);
}
}
}
void drawTileMask(
const Point16 &sPos,
gPixelMap &sMap,
TilePoint loc,
uint16 roofID = rippedRoofID) {
Point32 aPos;
Point32 viewPos;
Point16 metaPos;
TilePoint baseCoords;
TilePoint relLoc;
// Compute bitmap's position in absolute terms on map
aPos.x = sPos.x + tileScroll.x - fineScroll.x;
aPos.y = sPos.y + tileScroll.y - fineScroll.y;
// coordinates of the view window on the map in X,Y (in 16 pixel units)
viewPos.x = (aPos.x >> kTileDXShift)
- (kPlatformWidth * mapList[currentMapNum].mapSize),
viewPos.y = (kPlatformWidth
* mapList[currentMapNum].mapSize << kTileDXShift)
- aPos.y;
// coordinates of the view window upper left corner in U,V
baseCoords.u = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) + viewPos.x)
/ (kPlatformWidth * 2);
baseCoords.v = ((2 * (viewPos.y >> kTileDXShift) + kMetaDY / 16) - viewPos.x)
/ (kPlatformWidth * 2);
baseCoords.z = 0;
// coordinates of current metatile (in X,Y), relative to screen
metaPos.x = (baseCoords.u - baseCoords.v) * kMetaDX
- viewPos.x * kTileDX;
metaPos.y = viewPos.y
- (baseCoords.u + baseCoords.v) * kMetaDY;
// Compute where the object is relative to the metatile coords
relLoc.u = (baseCoords.u * kPlatUVSize) - loc.u;
relLoc.v = (baseCoords.v * kPlatUVSize) - loc.v;
relLoc.z = loc.z;
// Loop through each horizontal row of metatiles
// REM: also account for highest possible platform
// (replace 256 constant with better value)
for (;
metaPos.y < sMap.size.y + kMetaTileHeight * 4 ;
baseCoords.u--,
baseCoords.v--
) {
maskMetaRow(sMap, baseCoords, relLoc, metaPos, roofID);
metaPos.y += kMetaDY;
metaPos.x -= kMetaDX;
relLoc.u -= kPlatUVSize;
maskMetaRow(sMap, TilePoint(baseCoords.u - 1, baseCoords.v, 0),
relLoc, metaPos, roofID);
metaPos.y += kMetaDY;
metaPos.x += kMetaDX;
relLoc.v -= kPlatUVSize;
}
}
/* ===================================================================== *
Tile picking
* ===================================================================== */
#if DEBUG
bool showTile = false;
const uint16 lowerRightMask = 0x1111;
const uint16 lowerLeftMask = 0x000F;
inline void drawSubTiles(const TilePoint &tp, uint16 subTileMask, uint8 *cornerHeight) {
TilePoint pt1,
pt2;
uint8 subTileNo;
uint16 curSubTileMask;
pt1.z = 0;
pt2.z = 0;
for (subTileNo = 0; subTileNo < 16; subTileNo++) {
curSubTileMask = (1 << subTileNo);
if (curSubTileMask & subTileMask) {
// check if we're drawing tile to lower left
if (!((curSubTileMask >> 4) & subTileMask) || curSubTileMask & lowerLeftMask) {
pt1.u = pt2.u = (subTileNo & 0x0C);
pt1.v = ((subTileNo & 0x03) << 2);
pt2.v = pt1.v + 4;
pt1.z = ptHeight(pt1, cornerHeight);
pt2.z = ptHeight(pt2, cornerHeight);
TPLine(tp + pt1, tp + pt2);
}
// check if we're drawing tile to lower right
if (!((curSubTileMask >> 1) & subTileMask) || curSubTileMask & lowerRightMask) {
pt1.u = (subTileNo & 0x0C);
pt2.u = pt1.u + 4;
pt1.v = pt2.v = ((subTileNo & 0x03) << 2);
pt1.z = ptHeight(pt1, cornerHeight);
pt2.z = ptHeight(pt2, cornerHeight);
TPLine(tp + pt1, tp + pt2);
}
// draw upper right
pt1.u = pt2.u = (subTileNo & 0x0C) + 4;
pt1.v = ((subTileNo & 0x03) << 2);
pt2.v = pt1.v + 4;
pt1.z = ptHeight(pt1, cornerHeight);
pt2.z = ptHeight(pt2, cornerHeight);
TPLine(tp + pt1, tp + pt2);
// draw upper left
pt1.u = (subTileNo & 0x0C);
pt2.u = pt1.u + 4;
pt1.v = pt2.v = ((subTileNo & 0x03) << 2) + 4;
pt1.z = ptHeight(pt1, cornerHeight);
pt2.z = ptHeight(pt2, cornerHeight);
TPLine(tp + pt1, tp + pt2);
}
}
}
void showAbstractTile(const TilePoint &tp, TileInfo *ti) {
TilePoint workTp = tp;
uint8 *chPtr;
uint8 raisedCornerHeight[4] = { 0, 0, 0, 0 };
if (ti->combinedTerrainMask() & terrainRaised) {
if ((1L << ti->attrs.bgdTerrain) & terrainRaised) {
workTp.z += ti->attrs.terrainHeight;
chPtr = raisedCornerHeight;
} else
chPtr = ti->attrs.cornerHeight;
drawSubTiles(workTp, ~ti->attrs.terrainMask, chPtr);
workTp.z = tp.z;
if ((1L << ti->attrs.fgdTerrain) & terrainRaised) {
workTp.z += ti->attrs.terrainHeight;
chPtr = raisedCornerHeight;
} else
chPtr = ti->attrs.cornerHeight;
drawSubTiles(workTp, ti->attrs.terrainMask, chPtr);
} else
drawSubTiles(workTp, 0xFFFF, ti->attrs.cornerHeight);
TilePoint pt1 = tp + TilePoint(0, 0, 0),
pt2 = tp + TilePoint(16, 0, 0),
pt3 = tp + TilePoint(0, 16, 0),
pt4 = tp + TilePoint(16, 16, 0);
TPLine(pt1, pt2);
TPLine(pt1, pt3);
TPLine(pt2, pt4);
TPLine(pt3, pt4);
}
#endif
// Compute the picked position as if the mouse were pointing at the same
// level as the protagainist's feet.
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[currentMapNum];
TilePoint testCoords,
mCoords,
tCoords,
origin;
MetaTile *mt;
// Determine pick point relative to base of tile
testCoords = pickCoords;
testCoords.u &= kTileUVMask;
testCoords.v &= kTileUVMask;
// If picked point is not along edge of tile, then its not hidden
if ((surfaceType == surfaceVertV && testCoords.u != 0)
|| (surfaceType == surfaceVertU && testCoords.v != 0))
return false;
TileInfo *adjTile;
TilePoint adjTCoords = tileCoords;
uint16 adjSubMask;
// Determine the tile coordinates of adjacent tile and the mask
// of the subtile to test on that tile.
if (surfaceType == surfaceVertV) {
assert(testCoords.u == 0);
adjTCoords.u--;
adjSubMask = 0x1000 << (testCoords.v >> kSubTileShift);
} else {
assert(testCoords.v == 0);
adjTCoords.v--;
adjSubMask = 0x0008 << (testCoords.u & ~kSubTileMask);
}
mCoords = adjTCoords >> kPlatShift;
// If metatile of adjacent tile does not exist, the pick point
// is valid.
if ((mt = curMap->lookupMeta(mCoords)) == nullptr) return false;
tCoords.u = adjTCoords.u & kPlatMask;
tCoords.v = adjTCoords.v & kPlatMask;
tCoords.z = 0;
origin = mCoords << kPlatShift;
int i;
for (i = 0; i < maxPlatforms; i++) {
Platform *p;
int16 h,
trFlags;
if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr)
continue;
if (!(p->flags & plVisible) || platformRipped(p)) continue;
// Fetch the tile at this location
adjTile = p->fetchTile(
currentMapNum,
tCoords,
origin,
h,
trFlags);
if (adjTile == nullptr) continue;
// If current tile is higher or lower than the picked point
// skip this tile.
if (h > pickCoords.z) continue;
if (h + adjTile->attrs.terrainHeight <= pickCoords.z)
continue;
// If adjacent subtile is not raised, skip this tile
if (!(adjTile->attrs.testTerrain(adjSubMask) & terrainRaised))
continue;
break;
}
// If all platforms have been checked, the pick point is valid
if (i >= maxPlatforms) return false;
return true;
}
//-----------------------------------------------------------------------
// Return the TilePoint at which the mouse it pointing
StaticTilePoint pickTile(Point32 pos,
const TilePoint &protagPos,
StaticTilePoint *floorResult,
ActiveItemPtr *pickTAI) {
WorldMapData *curMap = &mapList[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 occured on, and the tile
// within that metatile, and the origin coords of the metatile
mCoords = tileCoords >> kPlatShift;
tCoords.u = tileCoords.u & kPlatMask;
tCoords.v = tileCoords.v & kPlatMask;
tCoords.z = 0;
origin = mCoords << kPlatShift;
// Lookup the metatile
mt = curMap->lookupMeta(mCoords);
// While we are less than the pick altitude
while (relPos.y < zMax + kTileDX + kMaxStepHeight - ABS(relPos.x >> 1)) {
// If there is a metatile on this spot
if (mt != nullptr) {
// Iterate through all platforms
for (i = 0; i < maxPlatforms; i++) {
Platform *p;
StandingTileInfo sti;
if ((p = mt->fetchPlatform(currentMapNum, i)) == nullptr)
continue;
if (platformRipped(p)) break;
if (!(p->flags & plVisible)) continue;
// Fetch the tile at this location
ti = p->fetchTAGInstance(
currentMapNum,
tCoords,
origin,
&imageData,
sti);
if (ti == nullptr) continue;
// Reject the tile if it's too low.
if (sti.surfaceHeight + ti->attrs.terrainHeight < zMin)
continue;
// Reject the tile if it's too high.
if (sti.surfaceHeight > zMax + kMaxStepHeight) continue;
// Reject the tile if mouse position is below lower tile
// boundary
if ((relPos.y - sti.surfaceHeight) < ABS(relPos.x >> 1))
continue;
if (ti->attrs.height > 0) {
if (isTilePixelOpaque(relPos.x,
relPos.y - sti.surfaceHeight,
ti->attrs.height,
imageData)) {
SurfaceType surface;
// Determine picked point on tile
surface = pointOnTile(ti,
relPos,
sti.surfaceHeight,
tCoords + origin,
pCoords,
fCoords);
if (sti.surfaceTAG == nullptr) {
if (surface != surfaceHoriz
&& pointOnHiddenSurface(tCoords + origin, pCoords, surface)) {
surface = surface == surfaceVertU ? surfaceVertV : surfaceVertU;
}
// If pick point is on vertical surface
// not facing protaganist, reject tile
if (surface == surfaceVertU && pCoords.v < protagPos.v)
continue;
if (surface == surfaceVertV && pCoords.u < protagPos.u)
continue;
}
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(void) {
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;
}
}
//-----------------------------------------------------------------------
// Save the tile cycling state array in a save file
void saveTileCyclingStates(SaveFileConstructor &saveGame) {
TileCycleArchive *archiveBuffer;
int16 i;
archiveBuffer = new TileCycleArchive[cycleCount]();
if (archiveBuffer == nullptr)
error("Unable to allocate tile cycle data archive buffer");
for (i = 0; i < cycleCount; i++) {
archiveBuffer[i].counter = cycleList[i].counter;
archiveBuffer[i].currentState = cycleList[i].currentState;
}
saveGame.writeChunk(
MakeID('C', 'Y', 'C', 'L'),
archiveBuffer,
sizeof(TileCycleArchive) * cycleCount);
delete[] archiveBuffer;
}
void saveTileCyclingStates(Common::OutSaveFile *out) {
debugC(2, kDebugSaveload, "Saving TileCyclingStates");
const int tileCycleArchiveSize = 4 + 1;
out->write("CYCL", 4);
out->writeUint32LE(tileCycleArchiveSize * cycleCount);
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);
}
}
//-----------------------------------------------------------------------
// Load the tile cycling state array from a save file
void loadTileCyclingStates(SaveFileReader &saveGame) {
TileCycleArchive *archiveBuffer;
int16 i;
initTileCyclingStates();
assert(saveGame.getChunkSize() == (int)sizeof(TileCycleArchive) * cycleCount);
archiveBuffer = new TileCycleArchive[cycleCount]();
if (archiveBuffer == nullptr)
error("Unable to allocate tile cycle data archive buffer");
saveGame.read(archiveBuffer, sizeof(TileCycleArchive) * cycleCount);
for (i = 0; i < cycleCount; i++) {
cycleList[i].counter = archiveBuffer[i].counter;
cycleList[i].currentState = archiveBuffer[i].currentState;
}
delete[] archiveBuffer;
}
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(void) {
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);
void updateMainDisplay(void) {
static TilePoint lastViewLoc = TilePoint(0, 0, 0);
int32 deltaTime = gameTime - lastUpdateTime;
assert(isActor(viewCenterObject));
Actor *viewActor = (Actor *)GameObject::objectAddress(
viewCenterObject);
TilePoint viewDiff;
assert(isWorld(viewActor->IDParent()));
GameWorld *viewWorld = (GameWorld *)viewActor->parent();
if (viewWorld != currentWorld) {
currentWorld = viewWorld;
setCurrentMap(currentWorld->mapNum);
}
WorldMapData *curMap = &mapList[currentMapNum];
StaticPoint32 scrollCenter,
scrollDelta;
int32 scrollSpeed = defaultScrollSpeed,
scrollDistance;
int16 viewSize = kTileRectHeight;
int16 mapSectors = curMap->mapSize * 8 * 16 / kSectorSize;
TilePoint trackPos,
mCoords;
lastUpdateTime = gameTime;
// Get the coordinates of the object which the camera is tracking
getViewTrackPos(trackPos);
debugC(1, kDebugTiles, "trackPos = (%d,%d,%d)", trackPos.u, trackPos.v, trackPos.z);
viewDiff = trackPos - lastViewLoc;
lastViewLoc = trackPos;
if (ABS(viewDiff.u) > 8 * kPlatformWidth * kTileUVSize
|| ABS(viewDiff.v) > 8 * kPlatformWidth * kTileUVSize)
freeAllTileBanks();
// Add current coordinates to map if they have mapping
markMetaAsVisited(trackPos);
// Convert to XY coordinates.
targetScroll.x =
((trackPos.u - trackPos.v) << 1)
+ curMap->mapHeight - 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.
minSector.u = clamp(0, (viewCenter.u - viewSize) / kSectorSize, mapSectors - 1);
minSector.v = clamp(0, (viewCenter.v - viewSize) / kSectorSize, mapSectors - 1);
maxSector.u = clamp(0, (viewCenter.u + viewSize) / kSectorSize, mapSectors - 1);
maxSector.v = clamp(0, (viewCenter.v + viewSize) / kSectorSize, mapSectors - 1);
buildRoofTable();
mCoords.u = trackPos.u >> (kTileUVShift + kPlatShift);
mCoords.v = trackPos.v >> (kTileUVShift + kPlatShift);
mCoords.z = 0;
// If trackPos has crossed a metatile boundry, rebuild object
// ripping tables
if (mCoords != ripTableCoords) buildRipTables();
// Build the list of all displayed objects
buildDisplayList();
updateObjectAppearances(deltaTime); // for object with no motion task.
// Draw tiles onto back buffer
// Lock DirectDraw
//#define DIRECTDRAW
drawMainDisplay();
cycleTiles(deltaTime);
}
void drawMainDisplay(void) {
// draws tiles to tileDrawMap.data
drawMetaTiles();
// Draw sprites onto back buffer
drawDisplayList();
// Render the text if any
updateSpeech();
Rect16 rect(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight);
// Render floating windows
drawFloatingWindows(backPort,
Point16(kTileRectX - fineScroll.x, kTileRectY),
rect);
// Render the image of the mouse pointer on everything else
drawTileMousePointer();
// Blit it all onto the screen
drawPage->writePixels(
rect,
tileDrawMap.data
+ fineScroll.x
+ fineScroll.y * tileDrawMap.size.x,
tileDrawMap.size.x);
updateFrameCount();
}
#if DEBUG
#include "actor.h"
void ShowObjectSection(GameObject *obj) {
ProtoObj *proto = obj->proto();
int16 crossSection = proto->crossSection;
TilePoint tp = obj->getLocation(),
tp1,
tp2,
tp3,
tp4;
tp1 = tp + TilePoint(crossSection, crossSection, 0);
tp2 = tp + TilePoint(-crossSection, crossSection, 0);
tp3 = tp + TilePoint(-crossSection, -crossSection, 0);
tp4 = tp + TilePoint(crossSection, -crossSection, 0);
TPLine(tp1, tp2);
TPLine(tp2, tp3);
TPLine(tp3, tp4);
TPLine(tp4, tp1);
}
#endif
//-----------------------------------------------------------------------
// mark this and surrounding metatiles as visited
const int mappingRadius = 2;
void markMetaAsVisited(const TilePoint &pt) {
// If (they have cartography)
{
WorldMapData *curMap = &mapList[currentMapNum];
uint16 *mapData = curMap->map->mapData;
TilePoint metaCoords = pt >> (kTileUVShift + kPlatShift);
int32 minU = MAX(metaCoords.u - mappingRadius, 0),
maxU = MIN(metaCoords.u + mappingRadius, curMap->mapSize - 1),
minV = MAX(metaCoords.v - mappingRadius, 0),
maxV = MIN(metaCoords.v + mappingRadius, curMap->mapSize - 1),
u, v;
for (u = minU; u <= maxU; u++) {
for (v = minV; v <= maxV; v++) {
if ((u == minU || u == maxU) && (v == minV || v == maxV)) continue;
mapData[u * curMap->mapSize + v] |= metaTileVisited;
}
}
}
}
/* ===================================================================== *
Quick distance calculation subroutines
* ===================================================================== */
int16 quickDistance(const Point16 &p) {
int16 ax = ABS(p.x),
ay = ABS(p.y);
if (ax > ay) return ax + (ay >> 1);
else return ay + (ax >> 1);
}
int32 quickDistance(const Point32 &p) {
int32 ax = ABS(p.x),
ay = ABS(p.y);
if (ax > ay) return ax + (ay >> 1);
else return ay + (ax >> 1);
}
int16 TilePoint::magnitude(void) {
int16 au = ABS(u),
av = ABS(v),
az = ABS(z);
if (az > au && az > av) return az + ((au + av) >> 1);
if (au > av) return au + ((az + av) >> 1);
else return av + ((au + az) >> 1);
}
// Determine the distance between a point and a line
uint16 lineDist(
const TilePoint &p1,
const TilePoint &p2,
const TilePoint &m) {
const int16 lineDistSlop = kTileUVSize * 4;
const int16 lineFar = maxint16;
int16 u = m.u,
v = m.v;
int16 u2 = p2.u - p1.u,
v2 = p2.v - p1.v;
int16 dist;
u -= p1.u;
v -= p1.v;
if (u2 < 0) {
u2 = -u2;
u = -u;
}
if (v2 < 0) {
v2 = -v2;
v = -v;
}
if (u < -lineDistSlop
|| u > u2 + lineDistSlop
|| v < -lineDistSlop
|| v > v2 + lineDistSlop)
return lineFar;
if (u2 != 0 && v2 != 0) {
if (u2 > v2)
dist = u - v2 * v / u2;
else
dist = v - u2 * u / v2;
} else if (u2 == 0)
dist = v;
else if (v2 == 0)
dist = u;
else
dist = lineFar;
return ABS(dist);
}
} // end of namespace Saga2