mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 14:18:37 +00:00
4907 lines
137 KiB
C++
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
|