mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-27 05:32:45 +00:00
4862 lines
134 KiB
C++
4862 lines
134 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.
|
||
|
*/
|
||
|
|
||
|
#include "saga2/std.h"
|
||
|
#include "saga2/tcoords.h"
|
||
|
#include "saga2/objects.h"
|
||
|
#include "saga2/tile.h"
|
||
|
#include "saga2/oncall.h"
|
||
|
#include "saga2/motion.h"
|
||
|
#include "saga2/input.h"
|
||
|
#include "saga2/cmisc.h"
|
||
|
#include "saga2/setup.h"
|
||
|
|
||
|
#include "saga2/annoy.h"
|
||
|
#include "saga2/tagnoise.h"
|
||
|
#include "saga2/player.h"
|
||
|
#include "saga2/mapfeatr.h"
|
||
|
|
||
|
// Include files needed for SAGA script dispatch
|
||
|
#include "saga2/script.h"
|
||
|
#include "saga2/methods.r" // generated by SAGA
|
||
|
|
||
|
namespace Saga2 {
|
||
|
|
||
|
extern void writeLog(char *str);
|
||
|
|
||
|
#define TATLOG 0
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Constants
|
||
|
* ===================================================================== */
|
||
|
|
||
|
const uint32 tileTerrainID = RES_ID('T', 'E', 'R', 0),
|
||
|
tileImageID = RES_ID('T', 'I', 'L', 0),
|
||
|
platformID = RES_ID('P', 'L', 'T', 0),
|
||
|
metaID = RES_ID('M', 'E', 'T', 0),
|
||
|
mapID = RES_ID('M', 'A', 'P', 0),
|
||
|
tagID = RES_ID('T', 'A', 'G', 0),
|
||
|
tagDataID = RES_ID('T', 'G', 'D', 0),
|
||
|
tagStateID = RES_ID('T', 'S', 'T', 0),
|
||
|
assocID = RES_ID('A', 'S', 'C', 0),
|
||
|
cycleID = RES_ID('C', 'Y', 'C', 'L');
|
||
|
|
||
|
// Scrolling Speed constants
|
||
|
|
||
|
const int slowScrollSpeed = 6,
|
||
|
fastScrollSpeed = 16,
|
||
|
snapScrollSpeed = maxint32,
|
||
|
slowThreshhold = 16,
|
||
|
// fastThreshhold = 100,
|
||
|
fastThreshhold = 16,
|
||
|
snapThreshhold = 400;
|
||
|
|
||
|
const int32 maxOffset = 2048 * 1024;
|
||
|
|
||
|
|
||
|
const TilePoint Nowhere((int16)minint16, (int16)minint16, (int16)minint16);
|
||
|
|
||
|
const MetaTileID NoMetaTile(nullID, nullID);
|
||
|
const ActiveItemID NoActiveItem(0, activeItemIndexNullID);
|
||
|
|
||
|
enum SurfaceType {
|
||
|
surfaceHoriz, // Level surface
|
||
|
surfaceVertV, // Vertical surface, parallel to V axis
|
||
|
surfaceVertU, // Vertical surface, parallel to U axis
|
||
|
};
|
||
|
|
||
|
|
||
|
void updateSpeech();
|
||
|
void setAreaSound(const TilePoint &baseCoords);
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Bank switching interface
|
||
|
* ===================================================================== */
|
||
|
|
||
|
TileBankPtr tileBanks[ maxBanks ];
|
||
|
extern LoadOnCall<UByteHandle> tileImageBanks;
|
||
|
|
||
|
void tileFault(int bank, int num);
|
||
|
void updateHandleRefs(const TilePoint &pt); //, StandingTileInfo *stiResult )
|
||
|
void updateFrameCount(void);
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Prototypes
|
||
|
* ===================================================================== */
|
||
|
|
||
|
hResContext *tileRes; // tile resource handle
|
||
|
|
||
|
void drawPlatform(
|
||
|
Platform **pList, // platforms to draw
|
||
|
Point16 screenPos, // screen position
|
||
|
int16 uOrg, // for TAG search
|
||
|
int16 vOrg); // for TAG search
|
||
|
|
||
|
bool isTilePixelOpaque(int16 baseX, // X coordinate relative to base
|
||
|
int16 baseY, // Y coordinate relative to base
|
||
|
int16 mapHeight, // pixel height of tile's bitmap
|
||
|
uint8 *td); // packed tile bitmap
|
||
|
|
||
|
SurfaceType pointOnTile(TileInfo *ti,
|
||
|
const Point32 &tileRel,
|
||
|
int16 h,
|
||
|
const TilePoint &tCoords,
|
||
|
TilePoint &pickCoords,
|
||
|
TilePoint &floorCoords);
|
||
|
|
||
|
bool validSurface(const TilePoint &tileCoords,
|
||
|
const TilePoint &pickCoords);
|
||
|
|
||
|
void markMetaAsVisited(const TilePoint &pt);
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Prototypes
|
||
|
* ===================================================================== */
|
||
|
|
||
|
|
||
|
extern void buildDisplayList(void);
|
||
|
extern void drawDisplayList(void);
|
||
|
//extern void evaluateActorNeeds( int32 );
|
||
|
extern void updateActorTasks(void);
|
||
|
extern void updateObjectAppearances(int32 deltaTime);
|
||
|
extern void getViewTrackPos(TilePoint &tp);
|
||
|
extern GameObject *getViewCenterObject(void);
|
||
|
extern TilePoint centerActorCoords(void);
|
||
|
void freeAllTileBanks(void);
|
||
|
|
||
|
void cycleTiles(uint32 elapsed);
|
||
|
|
||
|
#if DEBUG
|
||
|
void TPLine(const TilePoint &start, const TilePoint &stop);
|
||
|
#endif
|
||
|
|
||
|
void drawFloatingWindows(gPort &, const Point16 &, const Rect16 &clip);
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Imports
|
||
|
* ===================================================================== */
|
||
|
|
||
|
extern gMousePointer pointer; // the actual pointer
|
||
|
extern gMouseState mouseState;
|
||
|
extern gPort backPort;
|
||
|
|
||
|
extern int16 worldCount; // Used as map count as well
|
||
|
|
||
|
extern ObjectID viewCenterObject; // ID of object that view tracks
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Exports
|
||
|
* ===================================================================== */
|
||
|
|
||
|
Rect16 tileRect(tileRectX, tileRectY, tileRectWidth, tileRectHeight);
|
||
|
gPixelMap tileDrawMap;
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Tile structure management
|
||
|
* ===================================================================== */
|
||
|
|
||
|
int16 cycleCount;
|
||
|
|
||
|
uint16 rippedRoofID;
|
||
|
|
||
|
TilePoint ripTableCoords = Nowhere;
|
||
|
|
||
|
static RipTable ripTableList[ 25 ];
|
||
|
|
||
|
WorldMapData *mapList; // master map data array
|
||
|
|
||
|
UByteHandle *stateArray; // Array of active item instance
|
||
|
// state arrays
|
||
|
|
||
|
CycleHandle cycleList; // list of tile cycling info
|
||
|
|
||
|
// Platform caching management
|
||
|
const int platformCacheSize = 256;
|
||
|
|
||
|
DList platformLRU; // least recently used
|
||
|
PlatformCacheEntry platformCache[ platformCacheSize ];
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
View state
|
||
|
* ===================================================================== */
|
||
|
|
||
|
int16 defaultScrollSpeed = slowScrollSpeed;
|
||
|
|
||
|
Point32 tileScroll, // current tile scroll pos
|
||
|
// backScroll, // quantized scroll pos
|
||
|
targetScroll; // where scroll going to
|
||
|
Point16 fineScroll;
|
||
|
|
||
|
TilePoint viewCenter; // coordinates of view on map
|
||
|
|
||
|
// These two variables define which sectors overlap the view rect.
|
||
|
|
||
|
TilePoint minSector,
|
||
|
maxSector;
|
||
|
|
||
|
int16 currentMapNum; // which map is in use
|
||
|
int16 lastMapNum;
|
||
|
|
||
|
int32 lastUpdateTime; // time of last display update
|
||
|
|
||
|
gPort mouseSavePort; // for tweaking mouse backsave
|
||
|
|
||
|
|
||
|
/* also:
|
||
|
-- height of center character
|
||
|
-- what map we are on.
|
||
|
*/
|
||
|
|
||
|
BankBits LoadedBanks; // what banks are loaded?
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
ActiveItemID member functions
|
||
|
* ===================================================================== */
|
||
|
|
||
|
#if DEBUG
|
||
|
ActiveItemID::ActiveItemID(int16 m, int16 i) :
|
||
|
val((m << activeItemMapShift) | (i & activeItemIndexMask)) {
|
||
|
VERIFY(m < 0x8);
|
||
|
VERIFY((uint16)i <= activeItemIndexNullID);
|
||
|
}
|
||
|
|
||
|
void ActiveItemID::setMapNum(int16 m) {
|
||
|
VERIFY(m < 0x8);
|
||
|
val &= ~activeItemMapMask;
|
||
|
val |= (m << activeItemMapShift);
|
||
|
}
|
||
|
|
||
|
void ActiveItemID::setIndexNum(int16 i) {
|
||
|
VERIFY((uint16)i <= activeItemIndexNullID);
|
||
|
val &= ~activeItemIndexMask;
|
||
|
val |= i & activeItemIndexMask;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Finds the address of a tile associated with a TileID
|
||
|
* ===================================================================== */
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return the address of a tile's TileInfo structure given that tile's ID
|
||
|
|
||
|
TileInfo *TileInfo::tileAddress(TileID id) {
|
||
|
TileInfo *ti;
|
||
|
TileBankPtr tbh;
|
||
|
int16 tileBank,
|
||
|
tileNum;
|
||
|
|
||
|
if (id == 0) return NULL;
|
||
|
|
||
|
TileID2Bank(id, tileBank, tileNum);
|
||
|
if ((tbh = tileBanks[ tileBank ]) == NULL) return NULL;
|
||
|
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 ]) == NULL) return NULL;
|
||
|
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;
|
||
|
UByteHandle tibh;
|
||
|
int16 tileBank,
|
||
|
tileNum;
|
||
|
|
||
|
if (id == 0) return NULL;
|
||
|
|
||
|
TileID2Bank(id, tileBank, tileNum);
|
||
|
if ((tbh = tileBanks[ tileBank ]) == NULL) return NULL;
|
||
|
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 ]) == NULL) return NULL;
|
||
|
ti = tbh->tile(tileNum);
|
||
|
}
|
||
|
|
||
|
if (ti != NULL) {
|
||
|
if ((tibh = tileImageBanks[ tileBank ]) != NULL)
|
||
|
* imageData = &(*tibh)[ ti->offset ];
|
||
|
else
|
||
|
*imageData = NULL;
|
||
|
} else
|
||
|
*imageData = NULL;
|
||
|
|
||
|
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 (this >= mapData->activeItemList
|
||
|
&& this < &mapData->activeItemList[ mapData->activeCount ])
|
||
|
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(instance.u << tileUVShift,
|
||
|
instance.v << tileUVShift,
|
||
|
instance.h << tileZShift,
|
||
|
getInstanceContext());
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return the address of an active item, given its ID
|
||
|
|
||
|
ActiveItem *ActiveItem::activeItemAddress(ActiveItemID id) {
|
||
|
return id.getIndexNum() != activeItemIndexNullID
|
||
|
? &mapList[ id.getMapNum() ].activeItemList[ id.getIndexNum() ]
|
||
|
: NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return this active item's ID
|
||
|
|
||
|
ActiveItemID ActiveItem::thisID(void) {
|
||
|
int16 mapNum = getMapNum();
|
||
|
|
||
|
return ActiveItemID(
|
||
|
mapNum,
|
||
|
this - mapList[ mapNum ].activeItemList);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return this active item's ID
|
||
|
|
||
|
ActiveItemID ActiveItem::thisID(int16 mapNum) {
|
||
|
return ActiveItemID(mapNum, this - mapList[ mapNum ].activeItemList);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// use() function for ActiveItem instance
|
||
|
|
||
|
bool ActiveItem::use(ObjectID enactor) {
|
||
|
// Get a pointer to the active item group
|
||
|
ActiveItem *groupPtr = activeItemAddress(
|
||
|
ActiveItemID(
|
||
|
getMapNum(),
|
||
|
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(),
|
||
|
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(),
|
||
|
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(),
|
||
|
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(),
|
||
|
instance.groupID));
|
||
|
|
||
|
return loc.u >= instance.u - range
|
||
|
&& loc.v >= instance.v - range
|
||
|
&& loc.u < instance.u + groupPtr->group.uSize + range
|
||
|
&& loc.v < instance.v + groupPtr->group.vSize + range;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// TAG noise player
|
||
|
|
||
|
void ActiveItem::playTAGNoise(ActiveItem *ai, int16 tagNoiseID) {
|
||
|
playSoundAt(RES_ID('T', 'A', 'G', tagNoiseID), ai->getInstanceLocation());
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// use() function for ActiveItem group
|
||
|
|
||
|
bool ActiveItem::use(ActiveItem *ins, ObjectID enactor) {
|
||
|
Actor *actor = (Actor *)GameObject::objectAddress(enactor);
|
||
|
TilePoint actorLoc = actor->getLocation() >> tileUVShift;
|
||
|
int16 mapNum = getMapNum();
|
||
|
uint16 state = ins->getInstanceState(mapNum);
|
||
|
scriptCallFrame scf;
|
||
|
|
||
|
if (ins->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->instance.worldNum;
|
||
|
scf.coords.u = ins->instance.targetU;
|
||
|
scf.coords.v = ins->instance.targetV;
|
||
|
scf.coords.z = ins->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);
|
||
|
ProtoObj *proto = obj->proto();
|
||
|
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->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->instance.u << tileUVShift;
|
||
|
instanceRegion.min.v = ins->instance.v << tileUVShift;
|
||
|
instanceRegion.max.u = instanceRegion.min.u
|
||
|
+ (group.uSize << tileUVShift);
|
||
|
instanceRegion.max.v = instanceRegion.min.v
|
||
|
+ (group.vSize << tileUVShift);
|
||
|
|
||
|
RegionalObjectIterator iter(
|
||
|
world,
|
||
|
instanceRegion.min,
|
||
|
instanceRegion.max);
|
||
|
GameObject *testObject;
|
||
|
|
||
|
for (iter.first(&testObject);
|
||
|
testObject != NULL;
|
||
|
iter.next(&testObject)) {
|
||
|
if (testObject != obj
|
||
|
&& testObject->currentTAG == instanceID
|
||
|
&& testObject->isTriggeringTAG())
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// if ( proto->mass < group.triggerWeight ) return FALSE;
|
||
|
|
||
|
if (ins->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->instance.worldNum;
|
||
|
scf.coords.u = ins->instance.targetU;
|
||
|
scf.coords.v = ins->instance.targetV;
|
||
|
scf.coords.z = ins->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->instance.targetU << tileUVShift)
|
||
|
+ tileUVSize / 2,
|
||
|
(ins->instance.targetV << tileUVShift)
|
||
|
+ tileUVSize / 2,
|
||
|
(int16)ins->instance.targetZ << 3,
|
||
|
ins->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->instance.u << tileUVShift;
|
||
|
instanceRegion.min.v = ins->instance.v << tileUVShift;
|
||
|
instanceRegion.max.u = instanceRegion.min.u
|
||
|
+ (group.uSize << tileUVShift);
|
||
|
instanceRegion.max.v = instanceRegion.min.v
|
||
|
+ (group.vSize << tileUVShift);
|
||
|
|
||
|
RegionalObjectIterator iter(
|
||
|
world,
|
||
|
instanceRegion.min,
|
||
|
instanceRegion.max);
|
||
|
GameObject *testObject;
|
||
|
|
||
|
for (iter.first(&testObject);
|
||
|
testObject != NULL;
|
||
|
iter.next(&testObject)) {
|
||
|
if (testObject != obj
|
||
|
&& testObject->currentTAG == instanceID
|
||
|
&& testObject->isTriggeringTAG())
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
if (ins->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->instance.worldNum;
|
||
|
scf.coords.u = ins->instance.targetU;
|
||
|
scf.coords.v = ins->instance.targetV;
|
||
|
scf.coords.z = ins->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) {
|
||
|
TilePoint actorLoc =
|
||
|
GameObject::objectAddress(enactor)->getLocation() >>
|
||
|
tileUVShift;
|
||
|
scriptCallFrame scf;
|
||
|
|
||
|
if (ins->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->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->instance.u << tileUVShift;
|
||
|
TAIReg.min.v = TAI->instance.v << tileUVShift;
|
||
|
TAIReg.max.u = TAIReg.min.u
|
||
|
+ (TAG->group.uSize << tileUVShift);
|
||
|
TAIReg.max.v = TAIReg.min.v
|
||
|
+ (TAG->group.vSize << tileUVShift);
|
||
|
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->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 = (UByteHandle *)RNewPtr(
|
||
|
worldCount * sizeof(UByteHandle),
|
||
|
NULL,
|
||
|
"active item state array array");
|
||
|
|
||
|
if (stateArray == NULL)
|
||
|
error("Unable to allocate the active item state array array");
|
||
|
|
||
|
for (i = 0; i < worldCount; i++) {
|
||
|
stateArray[ i ] = (UByteHandle)LoadResourceToHandle(
|
||
|
tileRes,
|
||
|
tagStateID + RES_ID(0, 0, 0, uint8(i)),
|
||
|
"active item state array");
|
||
|
|
||
|
if (stateArray[ i ] == NULL)
|
||
|
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++) {
|
||
|
archiveBufSize += sizeof(int16);
|
||
|
if (stateArray[ i ] != NULL)
|
||
|
archiveBufSize += RPtrSize(*stateArray[ i ]);
|
||
|
}
|
||
|
|
||
|
archiveBuffer = RNewPtr(archiveBufSize, NULL, "archive buffer");
|
||
|
if (archiveBuffer == NULL)
|
||
|
error("Unable to allocate a state array archive buffer");
|
||
|
|
||
|
bufferPtr = archiveBuffer;
|
||
|
|
||
|
for (i = 0; i < worldCount; i++) {
|
||
|
if (stateArray[ i ] != NULL) {
|
||
|
WorldMapData *mapData = &mapList[ i ];
|
||
|
ActiveItemPtr activeItemList = mapData->activeItemList;
|
||
|
int16 activeItemCount = mapData->activeCount,
|
||
|
j;
|
||
|
int32 arraySize = RPtrSize(*stateArray[ i ]);
|
||
|
uint8 *bufferedStateArray;
|
||
|
|
||
|
// Save the size of the state array
|
||
|
*((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[ j ];
|
||
|
uint8 *statePtr;
|
||
|
|
||
|
if (activeItem->itemType != activeTypeInstance)
|
||
|
continue;
|
||
|
|
||
|
// Get a pointer to the current active item's state
|
||
|
// data in the archive buffer
|
||
|
statePtr =
|
||
|
&bufferedStateArray[ activeItem->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 {
|
||
|
*((int16 *)bufferPtr) = 0;
|
||
|
bufferPtr = (int16 *)bufferPtr + 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
saveGame.writeChunk(
|
||
|
MakeID('T', 'A', 'G', 'S'),
|
||
|
archiveBuffer,
|
||
|
archiveBufSize);
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Load the active item instance states from a save file
|
||
|
|
||
|
void loadActiveItemStates(SaveFileReader &saveGame) {
|
||
|
int16 i;
|
||
|
|
||
|
void *archiveBuffer;
|
||
|
void *bufferPtr;
|
||
|
|
||
|
stateArray = (UByteHandle *)RNewPtr(
|
||
|
worldCount * sizeof(UByteHandle),
|
||
|
NULL,
|
||
|
"active item state array array");
|
||
|
|
||
|
if (stateArray == NULL)
|
||
|
error("Unable to allocate the active item state array array");
|
||
|
|
||
|
archiveBuffer = RNewPtr(
|
||
|
saveGame.getChunkSize(),
|
||
|
NULL,
|
||
|
"archive buffer");
|
||
|
|
||
|
if (archiveBuffer == NULL)
|
||
|
error("Unable to allocate state array archive buffer");
|
||
|
|
||
|
saveGame.read(archiveBuffer, saveGame.getChunkSize());
|
||
|
|
||
|
bufferPtr = archiveBuffer;
|
||
|
|
||
|
for (i = 0; i < worldCount; i++) {
|
||
|
int32 arraySize;
|
||
|
|
||
|
arraySize = *((int16 *)bufferPtr) * sizeof(uint8);
|
||
|
bufferPtr = (int16 *)bufferPtr + 1;
|
||
|
|
||
|
if (arraySize > 0) {
|
||
|
WorldMapData *mapData = &mapList[ i ];
|
||
|
ActiveItemPtr activeItemList = mapData->activeItemList;
|
||
|
int16 activeItemCount = mapData->activeCount,
|
||
|
j;
|
||
|
uint8 *bufferedStateArray = (uint8 *)bufferPtr;
|
||
|
|
||
|
for (j = 0; j < activeItemCount; j++) {
|
||
|
ActiveItem *activeItem = &activeItemList[ j ];
|
||
|
uint8 *statePtr;
|
||
|
|
||
|
if (activeItem->itemType != activeTypeInstance)
|
||
|
continue;
|
||
|
|
||
|
// Get a pointer to the current active item's state
|
||
|
// data in the archive buffer
|
||
|
statePtr =
|
||
|
&bufferedStateArray[ activeItem->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 ] = (UByteHandle)RNewHandle(
|
||
|
arraySize,
|
||
|
NULL,
|
||
|
"active item state array");
|
||
|
if (stateArray[ i ] == NULL)
|
||
|
error("Unable to allocate active item state array");
|
||
|
|
||
|
memcpy(*stateArray[ i ], bufferPtr, arraySize);
|
||
|
bufferPtr = (uint8 *)bufferPtr + arraySize;
|
||
|
} else
|
||
|
stateArray[ i ] = NULL;
|
||
|
}
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Cleanup the active item state arrays
|
||
|
|
||
|
void cleanupActiveItemStates(void) {
|
||
|
int16 i;
|
||
|
|
||
|
for (i = 0; i < worldCount; i++) {
|
||
|
if (stateArray[ i ] != NULL)
|
||
|
RDisposeHandle((RHANDLE)stateArray[ i ]);
|
||
|
}
|
||
|
|
||
|
RDisposePtr(stateArray);
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
TileActivityTaskList member functions
|
||
|
* ===================================================================== */
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// The list of active tile activity tasks
|
||
|
|
||
|
static uint8 aTaskListBuffer[ sizeof(TileActivityTaskList) ];
|
||
|
|
||
|
static TileActivityTaskList &aTaskList =
|
||
|
*((TileActivityTaskList *)aTaskListBuffer);
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Constructor
|
||
|
|
||
|
TileActivityTaskList::TileActivityTaskList(void) {
|
||
|
for (int i = 0; i < elementsof(array); i++) {
|
||
|
free.addTail(array[ i ]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Reconstruct the TileActivityTaskList from an archive buffer
|
||
|
|
||
|
TileActivityTaskList::TileActivityTaskList(void **buf) {
|
||
|
void *bufferPtr = *buf;
|
||
|
|
||
|
int16 i,
|
||
|
taskCount;
|
||
|
|
||
|
for (i = 0; i < elementsof(array); i++) {
|
||
|
free.addTail(array[ i ]);
|
||
|
}
|
||
|
|
||
|
// Retreive the task count
|
||
|
taskCount = *((int16 *)bufferPtr);
|
||
|
bufferPtr = (int16 *)bufferPtr + 1;
|
||
|
|
||
|
for (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 != NULL) {
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
tat = newTask(tai);
|
||
|
if (tat != NULL)
|
||
|
tat->activityType = activityType;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*buf = bufferPtr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return the number of bytes needed to archive this
|
||
|
// TileActivityTaskList
|
||
|
|
||
|
int32 TileActivityTaskList::archiveSize(void) {
|
||
|
int32 size = sizeof(int16);
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
for (tat = (TileActivityTask *)list.first();
|
||
|
tat != NULL;
|
||
|
tat = (TileActivityTask *)tat->next())
|
||
|
size += sizeof(ActiveItemID) + sizeof(uint8);
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Create an archive of this TileActivityTaskList in the specified
|
||
|
// archive buffer
|
||
|
|
||
|
void *TileActivityTaskList::archive(void *buf) {
|
||
|
int16 taskCount;
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
for (tat = (TileActivityTask *)list.first(), taskCount = 0;
|
||
|
tat != NULL;
|
||
|
tat = (TileActivityTask *)tat->next())
|
||
|
taskCount++;
|
||
|
|
||
|
// Store the task count
|
||
|
*((int16 *)buf) = taskCount;
|
||
|
buf = (int16 *)buf + 1;
|
||
|
|
||
|
for (tat = (TileActivityTask *)list.first();
|
||
|
tat != NULL;
|
||
|
tat = (TileActivityTask *)tat->next()) {
|
||
|
ActiveItem *ai = tat->tai;
|
||
|
|
||
|
// Store the activeItemID
|
||
|
*((ActiveItemID *)buf) = ai->thisID();
|
||
|
buf = (ActiveItemID *)buf + 1;
|
||
|
|
||
|
// Store the task type
|
||
|
*((uint8 *)buf) = tat->activityType;
|
||
|
buf = (uint8 *)buf + 1;
|
||
|
}
|
||
|
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Cleanup
|
||
|
|
||
|
void TileActivityTaskList::cleanup(void) {
|
||
|
TileActivityTask *tat;
|
||
|
TileActivityTask *nextTat;
|
||
|
|
||
|
for (tat = (TileActivityTask *)list.first();
|
||
|
tat != NULL;
|
||
|
tat = nextTat) {
|
||
|
nextTat = (TileActivityTask *)tat->next();
|
||
|
tat->remove();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Get a new tile activity task, if there is one available,
|
||
|
// and initialize it.
|
||
|
|
||
|
TileActivityTask *TileActivityTaskList::newTask(ActiveItem *activeInstance) {
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
// Check see if there's already tile activity task associated with
|
||
|
// this instance.
|
||
|
for (tat = (TileActivityTask *)list.first();
|
||
|
tat;
|
||
|
tat = (TileActivityTask *)tat->next()) {
|
||
|
if (tat->tai == activeInstance) break;
|
||
|
}
|
||
|
|
||
|
#if TATLOG
|
||
|
if (tat) writeLog("Found old TAT\n");
|
||
|
#endif
|
||
|
|
||
|
if (tat == NULL) {
|
||
|
tat = (TileActivityTask *)free.remHead();
|
||
|
|
||
|
#if TATLOG
|
||
|
writeLog("Making new TAT\n");
|
||
|
#endif
|
||
|
if (tat) {
|
||
|
tat->tai = activeInstance;
|
||
|
tat->activityType = TileActivityTask::activityTypeNone;
|
||
|
tat->script = NoThread;
|
||
|
tat->targetState = 0;
|
||
|
|
||
|
list.addTail(*tat);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we re-used an old task struct, then make sure script gets woken up.
|
||
|
if (tat->script != NoThread) {
|
||
|
#if TATLOG
|
||
|
writeLog("Waking up thread TAT\n");
|
||
|
#endif
|
||
|
wakeUpThread(tat->script);
|
||
|
tat->script = NoThread;
|
||
|
}
|
||
|
|
||
|
return tat;
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
TileActivityTask member functions
|
||
|
* ===================================================================== */
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// When a tile activity task is finished, call this function to delete it.
|
||
|
|
||
|
void TileActivityTask::remove(void) {
|
||
|
#if TATLOG
|
||
|
writeLog("Removing TAT\n");
|
||
|
#endif
|
||
|
DNode::remove();
|
||
|
aTaskList.free.addTail(*this);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// This initiates a tile activity task for opening a door
|
||
|
|
||
|
void TileActivityTask::openDoor(ActiveItem &activeInstance) {
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
#if TATLOG
|
||
|
writeLog("TAT Open Door\n");
|
||
|
#endif
|
||
|
if ((tat = aTaskList.newTask(&activeInstance)) != NULL)
|
||
|
tat->activityType = activityTypeOpen;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// This initiates a tile activity task for closing a door
|
||
|
|
||
|
void TileActivityTask::closeDoor(ActiveItem &activeInstance) {
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
#if TATLOG
|
||
|
writeLog("TAT Close Door\n");
|
||
|
#endif
|
||
|
if ((tat = aTaskList.newTask(&activeInstance)) != NULL)
|
||
|
tat->activityType = activityTypeClose;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// This initiates a tile activity task for script-based activity
|
||
|
|
||
|
void TileActivityTask::doScript(ActiveItem &activeInstance, uint8 finalState, ThreadID scr) {
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
#if TATLOG
|
||
|
writeLog("TAT Do Script\n");
|
||
|
#endif
|
||
|
if ((tat = aTaskList.newTask(&activeInstance)) != NULL) {
|
||
|
#if TATLOG
|
||
|
if (scr) writeLog("TAT Assign Script!\n");
|
||
|
#endif
|
||
|
tat->activityType = activityTypeScript;
|
||
|
tat->targetState = finalState;
|
||
|
tat->script = scr;
|
||
|
} else {
|
||
|
#if TATLOG
|
||
|
writeLog("Waking up thread 'cause newTask Failed\n");
|
||
|
#endif
|
||
|
wakeUpThread(scr); // If there were no threads available
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Routine to update positions of all active terrain using TileActivityTasks
|
||
|
|
||
|
void TileActivityTask::updateActiveItems(void) {
|
||
|
TileActivityTask *tat,
|
||
|
*nextTat;
|
||
|
#if DEBUG
|
||
|
int count = 0,
|
||
|
scriptCount = 0;
|
||
|
#endif
|
||
|
|
||
|
for (tat = (TileActivityTask *)aTaskList.list.first(); tat; tat = nextTat) {
|
||
|
ActiveItem *activityInstance = tat->tai;
|
||
|
bool activityTaskDone = FALSE;
|
||
|
|
||
|
int16 mapNum = activityInstance->getMapNum();
|
||
|
uint16 state = activityInstance->getInstanceState(mapNum);
|
||
|
|
||
|
nextTat = (TileActivityTask *)tat->next();
|
||
|
|
||
|
#if TATLOG
|
||
|
count++;
|
||
|
if (tat->script != NoThread) scriptCount++;
|
||
|
#endif
|
||
|
switch (tat->activityType) {
|
||
|
|
||
|
case activityTypeOpen:
|
||
|
if (state < 3)
|
||
|
activityInstance->setInstanceState(mapNum, state + 1);
|
||
|
else
|
||
|
activityTaskDone = TRUE;
|
||
|
break;
|
||
|
|
||
|
case activityTypeClose:
|
||
|
if (state > 0)
|
||
|
activityInstance->setInstanceState(mapNum, state - 1);
|
||
|
else
|
||
|
activityTaskDone = TRUE;
|
||
|
break;
|
||
|
|
||
|
case activityTypeScript:
|
||
|
if (state > tat->targetState)
|
||
|
activityInstance->setInstanceState(mapNum, state - 1);
|
||
|
else if (state < tat->targetState)
|
||
|
activityInstance->setInstanceState(mapNum, state + 1);
|
||
|
else
|
||
|
activityTaskDone = TRUE;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
activityTaskDone = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (activityTaskDone) {
|
||
|
// Wake up the script...
|
||
|
#if TATLOG
|
||
|
if (tat->script != NoThread) writeLog("TAT Wake Up Thread\n");
|
||
|
#endif
|
||
|
if (tat->script != NoThread) wakeUpThread(tat->script);
|
||
|
tat->remove();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if DEBUG
|
||
|
WriteStatusF(16, "TileTasks: %d SW:%d", count, scriptCount);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Search for tile activity task matching an item
|
||
|
|
||
|
TileActivityTask *TileActivityTask::find(ActiveItem *tai) {
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
for (tat = (TileActivityTask *)aTaskList.list.first(); tat; tat = (TileActivityTask *)tat->next()) {
|
||
|
if (tai == tat->tai) return tat;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Add script to tile activity task...
|
||
|
|
||
|
bool TileActivityTask::setWait(ActiveItem *tai, ThreadID script) {
|
||
|
TileActivityTask *tat;
|
||
|
|
||
|
tat = find(tai);
|
||
|
|
||
|
#if TATLOG
|
||
|
writeLog("Set Wait TAT\n");
|
||
|
#endif
|
||
|
if (tat) {
|
||
|
#if TATLOG
|
||
|
if (tat->script != NoThread) writeLog("TAT Waking Up Thread\n");
|
||
|
#endif
|
||
|
if (tat->script != NoThread) wakeUpThread(tat->script);
|
||
|
tat->script = script;
|
||
|
return TRUE;
|
||
|
}
|
||
|
#if TATLOG
|
||
|
writeLog("SetWait failed\n");
|
||
|
#endif
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Calls the handling routine for each tile activity task
|
||
|
|
||
|
void moveActiveTerrain(int32 deltaTime) {
|
||
|
deltaTime = 0;
|
||
|
|
||
|
TileActivityTask::updateActiveItems();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Initialize the tile activity task list
|
||
|
|
||
|
void initTileTasks(void) {
|
||
|
// Simply call the default constructor
|
||
|
new (&aTaskList) TileActivityTaskList;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Save the tile activity task list to a save file
|
||
|
|
||
|
void saveTileTasks(SaveFileConstructor &saveGame) {
|
||
|
int32 archiveBufSize;
|
||
|
void *archiveBuffer;
|
||
|
|
||
|
archiveBufSize = aTaskList.archiveSize();
|
||
|
|
||
|
archiveBuffer = RNewPtr(archiveBufSize, NULL, "archive buffer");
|
||
|
if (archiveBuffer == NULL)
|
||
|
error("Unable to allocate tile activity task archive buffer");
|
||
|
|
||
|
aTaskList.archive(archiveBuffer);
|
||
|
|
||
|
saveGame.writeChunk(
|
||
|
MakeID('T', 'A', 'C', 'T'),
|
||
|
archiveBuffer,
|
||
|
archiveBufSize);
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// 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 = RNewPtr(
|
||
|
saveGame.getChunkSize(),
|
||
|
NULL,
|
||
|
"archive buffer");
|
||
|
if (archiveBuffer == NULL)
|
||
|
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);
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Cleanup the tile activity task list
|
||
|
|
||
|
void cleanupTileTasks(void) {
|
||
|
// Simply call the aTaskList's cleanup
|
||
|
aTaskList.cleanup();
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Map management functions
|
||
|
* ===================================================================== */
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Initialize map data
|
||
|
|
||
|
void initMaps(void) {
|
||
|
int16 i;
|
||
|
|
||
|
// Load all of the tile terrain banks
|
||
|
for (i = 0; i < maxBanks; i++) {
|
||
|
if (tileRes->seek(tileTerrainID + RES_ID(0, 0, 0, (uint8)i))) {
|
||
|
tileBanks[ i ] =
|
||
|
(TileBankPtr)LoadResource(
|
||
|
tileRes,
|
||
|
tileTerrainID + RES_ID(0, 0, 0, (uint8)i),
|
||
|
"tile terrain bank");
|
||
|
} else
|
||
|
tileBanks[ i ] = NULL;
|
||
|
}
|
||
|
|
||
|
// Count the worlds by seeking the map data
|
||
|
for (worldCount = 0;
|
||
|
tileRes->seek(mapID + RES_ID(0, 0, 0, (uint8)worldCount));
|
||
|
worldCount++) ;
|
||
|
|
||
|
// Allocate the map data array
|
||
|
mapList = (WorldMapData *)RNewPtr(
|
||
|
sizeof(WorldMapData) * worldCount,
|
||
|
NULL,
|
||
|
"map data array");
|
||
|
if (mapList == NULL)
|
||
|
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;
|
||
|
|
||
|
// Initialize the world ID
|
||
|
mapData->worldID = WorldBaseID + i;
|
||
|
|
||
|
// Load the map
|
||
|
mapData->map = (MapHandle)LoadResourceToHandle(
|
||
|
tileRes,
|
||
|
mapID + RES_ID(0, 0, 0, (uint8)i),
|
||
|
"world map");
|
||
|
if (mapData->map == NULL)
|
||
|
error("Unable to load map");
|
||
|
|
||
|
// Load the meta tile list
|
||
|
mapData->metaList = (MetaTileHandle)LoadResourceToHandle(
|
||
|
tileRes,
|
||
|
metaID + RES_ID(0, 0, 0, (uint8)i),
|
||
|
"meta tile list");
|
||
|
if (mapData->metaList == NULL)
|
||
|
error("Unable to load meta tile list");
|
||
|
|
||
|
// If there is tag data, load it
|
||
|
if (tileRes->size(tagDataID + RES_ID(0, 0, 0, (uint8)i)) > 0) {
|
||
|
mapData->activeItemData =
|
||
|
(TileRefHandle)LoadResourceToHandle(
|
||
|
tileRes,
|
||
|
tagDataID + RES_ID(0, 0, 0, (uint8)i),
|
||
|
"active item data");
|
||
|
if (mapData->activeItemData == NULL)
|
||
|
error("Unable to load active item data");
|
||
|
} else
|
||
|
mapData->activeItemData = NULL;
|
||
|
|
||
|
// If there is an association list, load it
|
||
|
if (tileRes->size(assocID + RES_ID(0, 0, 0, (uint8)i)) > 0) {
|
||
|
mapData->assocList =
|
||
|
(UWordHandle)LoadResourceToHandle(
|
||
|
tileRes,
|
||
|
assocID + RES_ID(0, 0, 0, (uint8)i),
|
||
|
"association list");
|
||
|
if (mapData->assocList == NULL)
|
||
|
error("Unable to load association list");
|
||
|
} else
|
||
|
mapData->assocList = NULL;
|
||
|
|
||
|
// If there is an active item list, load it
|
||
|
if (tileRes->size(tagID + RES_ID(0, 0, 0, (uint8)i)) > 0) {
|
||
|
mapData->activeItemList =
|
||
|
(ActiveItemPtr)LoadResource(
|
||
|
tileRes,
|
||
|
tagID + RES_ID(0, 0, 0, (uint8)i),
|
||
|
"active item list");
|
||
|
if (mapData->activeItemList == NULL)
|
||
|
error("Unable to load active item list");
|
||
|
} else
|
||
|
mapData->activeItemList = NULL;
|
||
|
|
||
|
// Compute the number of meta tiles in list
|
||
|
mapData->metaCount = RPtrSize(*mapData->metaList)
|
||
|
/ sizeof(MetaTile);
|
||
|
|
||
|
// Compute the number of active items in list
|
||
|
mapData->activeCount = RPtrSize(mapData->activeItemList)
|
||
|
/ sizeof(ActiveItem);
|
||
|
|
||
|
/* // Initialize the state index for the each active item
|
||
|
// instance. NOTE: This code will need to be removed when
|
||
|
// the active item instance states are their own resource.
|
||
|
uint16 instanceCtr;
|
||
|
|
||
|
for ( j = 0, instanceCtr = 0; j < mapData->activeCount; j++ )
|
||
|
{
|
||
|
ActiveItem *ai = &mapData->activeItemList[ j ];
|
||
|
|
||
|
if ( ai->itemType == activeTypeInstance )
|
||
|
ai->instance.stateIndex = instanceCtr++;
|
||
|
}
|
||
|
*/
|
||
|
// Allocate an object ripping table ID list
|
||
|
mapData->ripTableIDList =
|
||
|
(RipTableIDHandle)RNewHandle(
|
||
|
sizeof(RipTableID) * mapData->metaCount,
|
||
|
NULL,
|
||
|
"rip table ID list");
|
||
|
if (mapData->ripTableIDList == NULL)
|
||
|
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 * metaTileHeight;
|
||
|
|
||
|
// Build an active item instance hash table
|
||
|
mapData->buildInstanceHash();
|
||
|
}
|
||
|
|
||
|
initPlatformCache();
|
||
|
initMapFeatures();
|
||
|
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Cleanup map data
|
||
|
|
||
|
void cleanupMaps(void) {
|
||
|
int16 i;
|
||
|
|
||
|
termMapFeatures();
|
||
|
// Iterate through each map, dumping the data
|
||
|
for (i = 0; i < worldCount; i++) {
|
||
|
WorldMapData *mapData = &mapList[ i ];
|
||
|
|
||
|
// Dump the map
|
||
|
RDisposeHandle((RHANDLE)mapData->map);
|
||
|
|
||
|
// Dump the meta tile list
|
||
|
RDisposeHandle((RHANDLE)mapData->metaList);
|
||
|
|
||
|
// If there is active item data, dump it
|
||
|
if (mapData->activeItemData != NULL)
|
||
|
RDisposeHandle((RHANDLE)mapData->activeItemData);
|
||
|
|
||
|
// If there is an association list, dump it
|
||
|
if (mapData->assocList != NULL)
|
||
|
RDisposeHandle((RHANDLE)mapData->assocList);
|
||
|
|
||
|
// If there is an active item list, dump it
|
||
|
if (mapData->activeItemList != NULL)
|
||
|
RDisposePtr(mapData->activeItemList);
|
||
|
|
||
|
// Dump the object ripping table ID list
|
||
|
RDisposeHandle((RHANDLE)mapData->ripTableIDList);
|
||
|
}
|
||
|
|
||
|
// Dump the map data list
|
||
|
RDisposePtr(mapList);
|
||
|
|
||
|
// Dump all of the tile terrain banks
|
||
|
for (i = 0; i < maxBanks; i++) {
|
||
|
if (tileBanks[ i ] != NULL)
|
||
|
RDisposePtr(tileBanks[ i ]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Set a new current map
|
||
|
|
||
|
void setCurrentMap(int mapNum) {
|
||
|
currentMapNum = mapNum;
|
||
|
if (lastMapNum != currentMapNum) {
|
||
|
lastMapNum = currentMapNum;
|
||
|
freeAllTileBanks();
|
||
|
audioEnvironmentSetWorld(mapNum);
|
||
|
}
|
||
|
|
||
|
// tileScroll.x = mapList[ mapNum ].mapHeight - tileRect.width - 800;
|
||
|
// tileScroll.y = mapList[ mapNum ].mapHeight - tileRect.width / 2;
|
||
|
|
||
|
lastUpdateTime = gameTime;
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Automap management functions
|
||
|
* ===================================================================== */
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
|
||
|
void initAutoMap(void) {
|
||
|
int16 i;
|
||
|
|
||
|
for (i = 0; i < worldCount; i++) {
|
||
|
MapHeader *map;
|
||
|
int32 mapSize,
|
||
|
mapIndex;
|
||
|
uint16 *mapData;
|
||
|
|
||
|
map = *mapList[ i ].map;
|
||
|
mapSize = map->size;
|
||
|
mapSize *= mapSize;
|
||
|
mapData = map->mapData;
|
||
|
|
||
|
// Clear the high bit for each map position
|
||
|
for (mapIndex = 0; mapIndex < mapSize; mapIndex++)
|
||
|
mapData[ mapIndex ] &= ~metaTileVisited;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
|
||
|
void saveAutoMap(SaveFileConstructor &saveGame) {
|
||
|
int32 totalMapSize = 0,
|
||
|
totalMapIndex = 0;
|
||
|
int16 i;
|
||
|
|
||
|
uint8 *archiveBuffer;
|
||
|
int32 archiveBufSize;
|
||
|
|
||
|
for (i = 0; i < worldCount; i++) {
|
||
|
MapHeader *map;
|
||
|
int32 mapSize;
|
||
|
|
||
|
map = *mapList[ i ].map;
|
||
|
mapSize = map->size;
|
||
|
mapSize *= mapSize;
|
||
|
|
||
|
totalMapSize += mapSize;
|
||
|
}
|
||
|
|
||
|
// Compute the number of bytes needed to store the visited bit
|
||
|
// for each map metatile slot
|
||
|
archiveBufSize = (totalMapSize + 7) >> 3;
|
||
|
|
||
|
archiveBuffer = (uint8 *)RNewPtr(archiveBufSize, NULL, "archive buffer");
|
||
|
if (archiveBuffer == NULL)
|
||
|
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);
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
|
||
|
void loadAutoMap(SaveFileReader &saveGame) {
|
||
|
int32 totalMapSize = 0,
|
||
|
totalMapIndex = 0;
|
||
|
int16 i;
|
||
|
|
||
|
uint8 *archiveBuffer;
|
||
|
int32 archiveBufSize;
|
||
|
|
||
|
archiveBufSize = saveGame.getChunkSize();
|
||
|
|
||
|
archiveBuffer = (uint8 *)RNewPtr(archiveBufSize, NULL, "archive buffer");
|
||
|
if (archiveBuffer == NULL)
|
||
|
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++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Platform cache functions
|
||
|
* ===================================================================== */
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Initialize the platform cache
|
||
|
|
||
|
void initPlatformCache(void) {
|
||
|
for (int i = 0; i < platformCacheSize; i++) {
|
||
|
PlatformCacheEntry *pce = &platformCache[ i ];
|
||
|
|
||
|
// Fill up the LRU with empty platforms
|
||
|
pce->metaID = NoMetaTile;
|
||
|
platformLRU.addTail(*pce);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// 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 ] * (tileUVSize - tp.u)
|
||
|
+ cornerHeight[ 1 ] * tp.u)
|
||
|
* (tileUVSize - tp.v)
|
||
|
+ (cornerHeight[ 3 ] * (tileUVSize - tp.u)
|
||
|
+ cornerHeight[ 2 ] * tp.u)
|
||
|
* tp.v;
|
||
|
|
||
|
return slopeHeight >> (tileUVShift + tileUVShift);
|
||
|
}
|
||
|
|
||
|
/* ====================================================================== *
|
||
|
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->group.grDataOffset
|
||
|
+ state * groupItem->group.animArea
|
||
|
+ relPos.u * groupItem->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)) == NULL) return NULL;
|
||
|
|
||
|
trFlags = tr->flags;
|
||
|
height = h;
|
||
|
|
||
|
#if DEBUG
|
||
|
if (ti->offset > maxOffset
|
||
|
|| ti->attrs.height > maxTileHeight
|
||
|
|| ti->attrs.height < 0) {
|
||
|
int16 tileNo, tileBank;
|
||
|
|
||
|
TileID2Bank(tr->tile, tileBank, tileNo);
|
||
|
WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank);
|
||
|
return NULL;
|
||
|
}
|
||
|
#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->group.grDataOffset
|
||
|
+ state * groupItem->group.animArea
|
||
|
+ relPos.u * groupItem->group.vSize
|
||
|
+ relPos.v ];
|
||
|
|
||
|
h += tr->tileHeight * 8;
|
||
|
}
|
||
|
#if DEBUG
|
||
|
else {
|
||
|
static TileRef dummyRef = { 1, 0, 0 };
|
||
|
tr = &dummyRef;
|
||
|
}
|
||
|
#endif
|
||
|
} else {
|
||
|
sti.surfaceTAG = NULL;
|
||
|
}
|
||
|
|
||
|
if ((ti = TileInfo::tileAddress(tr->tile)) == NULL) return NULL;
|
||
|
|
||
|
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->group.grDataOffset
|
||
|
+ state * groupItem->group.animArea
|
||
|
+ relPos.u * groupItem->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)) == NULL) return NULL;
|
||
|
|
||
|
trFlags = tr->flags;
|
||
|
height = h;
|
||
|
|
||
|
#if DEBUG
|
||
|
if (ti->offset > maxOffset
|
||
|
|| ti->attrs.height > maxTileHeight
|
||
|
|| ti->attrs.height < 0) {
|
||
|
int16 tileNo, tileBank;
|
||
|
|
||
|
TileID2Bank(tr->tile, tileBank, tileNo);
|
||
|
WriteStatusF(0, "Bad Tile: %d/%d", tileNo, tileBank);
|
||
|
return NULL;
|
||
|
}
|
||
|
#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->group.grDataOffset
|
||
|
+ state * groupItem->group.animArea
|
||
|
+ relPos.u * groupItem->group.vSize
|
||
|
+ relPos.v ];
|
||
|
|
||
|
h += tr->tileHeight * 8;
|
||
|
}
|
||
|
#if DEBUG
|
||
|
else {
|
||
|
static TileRef dummyRef = { 1, 0, 0 };
|
||
|
tr = &dummyRef;
|
||
|
}
|
||
|
#endif
|
||
|
} else {
|
||
|
sti.surfaceTAG = NULL;
|
||
|
}
|
||
|
|
||
|
if ((ti = TileInfo::tileAddress(tr->tile, imageData)) == NULL) return NULL;
|
||
|
|
||
|
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 ] : NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return a rip table's ID
|
||
|
|
||
|
RipTableID RipTable::thisID(void) {
|
||
|
return this - ripTableList;
|
||
|
}
|
||
|
|
||
|
/* ====================================================================== *
|
||
|
MetaTile member functions
|
||
|
* ====================================================================== */
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return a pointer to a meta tile given its ID
|
||
|
|
||
|
MetaTile *MetaTile::metaTileAddress(MetaTileID id) {
|
||
|
return id.map != nullID && id.index != nullID
|
||
|
? &(*mapList[ id.map ].metaList)[ id.index ]
|
||
|
: NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return this meta tile's ID
|
||
|
|
||
|
MetaTileID MetaTile::thisID(int16 mapNum) {
|
||
|
return MetaTileID(mapNum, this - *mapList[ mapNum ].metaList);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// 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;
|
||
|
|
||
|
ASSERT(layer >= 0);
|
||
|
ASSERT(this >= *mapList[ mapNum ].metaList
|
||
|
&& this < & (*mapList[ mapNum ].metaList)[
|
||
|
mapList[ mapNum ].metaCount ]);
|
||
|
|
||
|
if (plIndex == nullID) {
|
||
|
return NULL;
|
||
|
}
|
||
|
// if platform in cache
|
||
|
else if (plIndex & cacheFlag) {
|
||
|
plIndex &= ~cacheFlag;
|
||
|
|
||
|
ASSERT(plIndex < platformCacheSize);
|
||
|
|
||
|
// Get the address of the pce from the cache
|
||
|
pce = &platformCache[ plIndex ];
|
||
|
|
||
|
ASSERT(pce->platformNum >= 0);
|
||
|
ASSERT(pce->metaID != NoMetaTile);
|
||
|
ASSERT(pce->metaID == thisID(mapNum));
|
||
|
|
||
|
// Move to the end of the LRU
|
||
|
pce->remove();
|
||
|
platformLRU.addTail(*pce);
|
||
|
|
||
|
// return the address of the platform
|
||
|
return &pce->pl;
|
||
|
}
|
||
|
// if platform not in memory
|
||
|
else {
|
||
|
int cacheIndex;
|
||
|
|
||
|
// 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.
|
||
|
pce = (PlatformCacheEntry *)platformLRU.remHead();
|
||
|
platformLRU.addTail(*pce);
|
||
|
|
||
|
// Compute the layer of this entry in the cache
|
||
|
cacheIndex = pce - platformCache;
|
||
|
ASSERT(cacheIndex < platformCacheSize);
|
||
|
ASSERT(cacheIndex >= 0);
|
||
|
|
||
|
// Now, flush the old mt from the cache.
|
||
|
// This assumes that all metatiles from all worlds are loaded.
|
||
|
|
||
|
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 ] = (cacheFlag | cacheIndex);
|
||
|
|
||
|
ASSERT(plIndex >= 0);
|
||
|
ASSERT(plIndex * sizeof(Platform) < tileRes->size(platformID + RES_ID(0, 0, 0, mapNum)));
|
||
|
|
||
|
// Now, load the actual metatile data...
|
||
|
if (tileRes->seek(platformID + RES_ID(0, 0, 0, mapNum))) {
|
||
|
if (tileRes->skip(plIndex * sizeof(Platform))) {
|
||
|
if (tileRes->read(&pce->pl, sizeof(Platform)));
|
||
|
{
|
||
|
return &pce->pl;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
error("Unable to read Platform %d of map %d", plIndex, mapNum);
|
||
|
#ifdef _WIN32
|
||
|
return NULL;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// 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)[
|
||
|
this
|
||
|
- *mapData->metaList ]);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return a reference to this meta tile's rip table ID
|
||
|
|
||
|
RipTableID &MetaTile::ripTableID(int16 mapNum) {
|
||
|
WorldMapData *mapData = &mapList[ mapNum ];
|
||
|
|
||
|
return (*mapData->ripTableIDList)[ this - *mapData->metaList ];
|
||
|
}
|
||
|
|
||
|
/* ====================================================================== *
|
||
|
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)[ mtile ];
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Builds an active item instance hash table for tile lookup
|
||
|
|
||
|
void WorldMapData::buildInstanceHash(void) {
|
||
|
int32 i;
|
||
|
int16 hashVal;
|
||
|
ActiveItem *ai;
|
||
|
|
||
|
memset(instHash, 0, sizeof(instHash));
|
||
|
|
||
|
for (i = 0, ai = activeItemList; i < activeCount; i++, ai++) {
|
||
|
if (ai->itemType == activeTypeInstance) {
|
||
|
hashVal = (((ai->instance.u + ai->instance.h) << 4)
|
||
|
+ ai->instance.v + (ai->instance.groupID << 2))
|
||
|
% elementsof(instHash);
|
||
|
|
||
|
ai->nextHash = instHash[ hashVal ];
|
||
|
instHash[ hashVal ] = ai;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Lookup an active item instance given the active item group number
|
||
|
// an the meta tile coordinates
|
||
|
|
||
|
ActiveItem *WorldMapData::findHashedInstance(
|
||
|
TilePoint &tp,
|
||
|
int16 group) {
|
||
|
int16 hashVal = (((tp.u + tp.z) << 4) + tp.v + (group << 2))
|
||
|
% elementsof(instHash);
|
||
|
ActiveItem *ai;
|
||
|
|
||
|
for (ai = instHash[ hashVal ];
|
||
|
ai;
|
||
|
ai = ai->nextHash) {
|
||
|
if (ai->instance.u == tp.u
|
||
|
&& ai->instance.v == tp.v
|
||
|
&& ai->instance.h == tp.z
|
||
|
&& ai->instance.groupID == group)
|
||
|
return ai;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* ====================================================================== *
|
||
|
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 NULL;
|
||
|
|
||
|
mtRes = mapList[ mapNum ].lookupMeta(mCoords);
|
||
|
while (mtRes == NULL) {
|
||
|
if (!iterate()) return NULL;
|
||
|
mtRes = mapList[ mapNum ].lookupMeta(mCoords);
|
||
|
}
|
||
|
|
||
|
if (loc) *loc = mCoords << platShift;
|
||
|
return mtRes;
|
||
|
}
|
||
|
|
||
|
MetaTile *MetaTileIterator::next(TilePoint *loc) {
|
||
|
MetaTile *mtRes = NULL;
|
||
|
|
||
|
do {
|
||
|
if (!iterate()) return NULL;
|
||
|
mtRes = mapList[ mapNum ].lookupMeta(mCoords);
|
||
|
} while (mtRes == NULL);
|
||
|
|
||
|
if (loc) *loc = mCoords << platShift;
|
||
|
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)) != NULL) {
|
||
|
tCoordsReg.min.u = tCoordsReg.min.v = 0;
|
||
|
tCoordsReg.max.u = tCoordsReg.max.v = platformWidth;
|
||
|
|
||
|
if (origin.u < region.min.u)
|
||
|
tCoordsReg.min.u = region.min.u & platMask;
|
||
|
if (origin.u + platformWidth > region.max.u)
|
||
|
tCoordsReg.max.u = region.max.u & platMask;
|
||
|
if (origin.v < region.min.v)
|
||
|
tCoordsReg.min.v = region.min.v & platMask;
|
||
|
if (origin.v + platformWidth > region.max.v)
|
||
|
tCoordsReg.max.v = region.max.v & platMask;
|
||
|
} else return NULL;
|
||
|
|
||
|
platIndex = 0;
|
||
|
}
|
||
|
platform = mt->fetchPlatform(
|
||
|
metaIter.getMapNum(),
|
||
|
platIndex);
|
||
|
} while (platform == NULL);
|
||
|
|
||
|
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 NULL;
|
||
|
|
||
|
if ((mt = metaIter.first(&origin)) == NULL) return NULL;
|
||
|
|
||
|
platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex = 0);
|
||
|
while (platform == NULL) {
|
||
|
platIndex++;
|
||
|
if (platIndex >= maxPlatforms) {
|
||
|
if ((mt = metaIter.next(&origin)) == NULL) return NULL;
|
||
|
platIndex = 0;
|
||
|
}
|
||
|
platform = mt->fetchPlatform(metaIter.getMapNum(), platIndex);
|
||
|
}
|
||
|
|
||
|
tCoordsReg.min.u = tCoordsReg.min.v = 0;
|
||
|
tCoordsReg.max.u = tCoordsReg.max.v = platformWidth;
|
||
|
|
||
|
if (origin.u < region.min.u)
|
||
|
tCoordsReg.min.u = region.min.u & platMask;
|
||
|
if (origin.u + platformWidth > region.max.u)
|
||
|
tCoordsReg.max.u = region.max.u & platMask;
|
||
|
if (origin.v < region.min.v)
|
||
|
tCoordsReg.min.v = region.min.v & platMask;
|
||
|
if (origin.v + platformWidth > region.max.v)
|
||
|
tCoordsReg.max.v = region.max.v & platMask;
|
||
|
|
||
|
tCoords = tCoordsReg.min;
|
||
|
tiRes = platform->fetchTAGInstance(
|
||
|
metaIter.getMapNum(),
|
||
|
tCoords,
|
||
|
origin,
|
||
|
sti);
|
||
|
while (tiRes == NULL) {
|
||
|
if (!iterate()) return NULL;
|
||
|
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 = NULL;
|
||
|
StandingTileInfo sti;
|
||
|
|
||
|
do {
|
||
|
if (!iterate()) return NULL;
|
||
|
tiRes = platform->fetchTAGInstance(
|
||
|
metaIter.getMapNum(),
|
||
|
tCoords,
|
||
|
origin,
|
||
|
sti);
|
||
|
} while (tiRes == NULL);
|
||
|
|
||
|
*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 * platformWidth,
|
||
|
vOrg = coords.v * platformWidth;
|
||
|
|
||
|
Platform *drawList[ maxPlatforms + 1 ],
|
||
|
**put = drawList;
|
||
|
|
||
|
int16 mapSizeMask = curMap->mapSize - 1,
|
||
|
mapEdgeType = (*curMap->map)->edgeType;
|
||
|
uint16 *mapData = (*curMap->map)->mapData;
|
||
|
|
||
|
MetaTilePtr metaArray = *curMap->metaList;
|
||
|
|
||
|
int16 layerLimit;
|
||
|
|
||
|
// Poll the mouse so that we can detect double-clicks
|
||
|
PollMouse();
|
||
|
|
||
|
|
||
|
for (;
|
||
|
pos.x < tileDrawMap.size.x + metaDX;
|
||
|
coords.u++,
|
||
|
coords.v--,
|
||
|
uOrg += platformWidth,
|
||
|
vOrg -= platformWidth,
|
||
|
pos.x += metaTileWidth
|
||
|
) {
|
||
|
TilePoint clipCoords;
|
||
|
int16 mtile;
|
||
|
MetaTilePtr metaPtr;
|
||
|
|
||
|
clipCoords.u = (uint16)coords.u % curMap->mapSize;
|
||
|
clipCoords.v = (uint16)coords.v % curMap->mapSize;
|
||
|
clipCoords.z = 0;
|
||
|
|
||
|
if (coords != clipCoords) {
|
||
|
switch (mapEdgeType) {
|
||
|
case edgeTypeBlack: // continue;
|
||
|
case edgeTypeFill0:
|
||
|
mtile = 0;
|
||
|
break;
|
||
|
|
||
|
case edgeTypeFill1:
|
||
|
mtile = 1;
|
||
|
break;
|
||
|
|
||
|
case edgeTypeRepeat:
|
||
|
coords.u = clamp(0, coords.u, mapSizeMask);
|
||
|
coords.v = clamp(0, coords.v, mapSizeMask);
|
||
|
mtile = mapData[ clipCoords.u * curMap->mapSize + clipCoords.v ] & ~metaTileVisited;
|
||
|
break;
|
||
|
|
||
|
case edgeTypeWrap:
|
||
|
mtile = mapData[ clipCoords.u * curMap->mapSize + clipCoords.v ] & ~metaTileVisited;
|
||
|
break;
|
||
|
}
|
||
|
} else mtile = mapData[ clipCoords.u * curMap->mapSize + clipCoords.v ] & ~metaTileVisited;
|
||
|
|
||
|
if (mtile >= curMap->metaCount) mtile = curMap->metaCount - 1;
|
||
|
|
||
|
metaPtr = &metaArray[ mtile ];
|
||
|
put = drawList;
|
||
|
|
||
|
if (metaPtr == NULL) 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 = tileHeight * (platformWidth - 1) + maxTileHeight * 2 + 64;
|
||
|
|
||
|
if (pos.y <= 0
|
||
|
|| pos.y - p->highestPixel >= tileDrawMap.size.y)
|
||
|
continue;
|
||
|
|
||
|
*put++ = p;
|
||
|
}
|
||
|
}
|
||
|
*put++ = NULL;
|
||
|
|
||
|
if (drawList[ 0 ] != NULL) {
|
||
|
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;
|
||
|
|
||
|
int i;
|
||
|
|
||
|
// Initialize table
|
||
|
mt->ripTableID(currentMapNum) = ripTable->thisID();
|
||
|
ripTable->metaID = mt->thisID(currentMapNum);
|
||
|
ripTable->ripID = ripID;
|
||
|
|
||
|
for (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 = platformWidth * platformWidth;
|
||
|
|
||
|
for (i = 0; i < maxPlatforms; i++) {
|
||
|
Platform *p;
|
||
|
|
||
|
if ((p = mt->fetchPlatform(currentMapNum, i)) == NULL) continue;
|
||
|
|
||
|
if (p->roofRipID() != ripID) continue;
|
||
|
|
||
|
for (; i < maxPlatforms && tilesToGo > 0; i++) {
|
||
|
if ((p = mt->fetchPlatform(currentMapNum, i)) == NULL)
|
||
|
continue;
|
||
|
|
||
|
uint16 platHeight = p->height << 3;
|
||
|
int16 u, v;
|
||
|
|
||
|
for (u = 0; u < platformWidth; u++)
|
||
|
for (v = 0; v < platformWidth; 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 = tileUVSize * platformWidth * 2;
|
||
|
|
||
|
TilePoint actorCoords;
|
||
|
MetaTile *mt;
|
||
|
TileRegion ripTableReg;
|
||
|
|
||
|
MetaTile *mtTable[ 25 ]; // Largest region is 5x5
|
||
|
int16 mtTableSize = 0;
|
||
|
|
||
|
getViewTrackPos(actorCoords);
|
||
|
ripTableCoords.u = actorCoords.u >> (tileUVShift + platShift);
|
||
|
ripTableCoords.v = actorCoords.v >> (tileUVShift + platShift);
|
||
|
ripTableCoords.z = 0;
|
||
|
|
||
|
// Calculate the region of meta tile for which to build object
|
||
|
// ripping table
|
||
|
ripTableReg.min.u = (actorCoords.u - regionRadius) >> tileUVShift;
|
||
|
ripTableReg.min.v = (actorCoords.v - regionRadius) >> tileUVShift;
|
||
|
ripTableReg.max.u =
|
||
|
(actorCoords.u + regionRadius + tileUVMask) >> tileUVShift;
|
||
|
ripTableReg.max.v =
|
||
|
(actorCoords.v + regionRadius + tileUVMask) >> tileUVShift;
|
||
|
|
||
|
MetaTileIterator mIter(currentMapNum, ripTableReg);
|
||
|
|
||
|
// Build meta tile pointer array
|
||
|
mt = mIter.first();
|
||
|
while (mt) {
|
||
|
mtTable[ mtTableSize++ ] = mt;
|
||
|
|
||
|
mt = mIter.next();
|
||
|
}
|
||
|
|
||
|
int16 i, j;
|
||
|
|
||
|
int16 tableIndex;
|
||
|
|
||
|
// bit array of available rip tables
|
||
|
uint8 tableAvail[(elementsof(ripTableList) + 7) >> 3 ];
|
||
|
|
||
|
memset(tableAvail, 0xFF, sizeof(tableAvail));
|
||
|
|
||
|
for (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 ] = NULL;
|
||
|
// 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 (i = 0, j = 0; i < oldMtTableSize; i++) {
|
||
|
if (mtTable[ i ] != NULL)
|
||
|
mtTable[ j++ ] = mtTable[ i ];
|
||
|
else
|
||
|
mtTableSize--;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < mtTableSize; i++) {
|
||
|
mt = mtTable[ i ];
|
||
|
|
||
|
// Find available table
|
||
|
for (j = 0; j < elementsof(ripTableList); j++) {
|
||
|
if (tableAvail[ j >> 3 ] & (1 << (j & 0x7)))
|
||
|
break;
|
||
|
}
|
||
|
tableAvail[ j >> 3 ] &= ~(1 << (j & 0x7));
|
||
|
|
||
|
// If rip table has a valid metatile, remove that meta tile's
|
||
|
// reference to its rip table
|
||
|
if (ripTableList[ j ].metaID != NoMetaTile) {
|
||
|
MetaTile *mt = MetaTile::metaTileAddress(
|
||
|
ripTableList[ j ].metaID);
|
||
|
|
||
|
RipTableID &rt = 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 >> tileDXShift)
|
||
|
- (platformWidth * mapList[ currentMapNum ].mapSize),
|
||
|
viewPos.y = (platformWidth
|
||
|
* mapList[ currentMapNum ].mapSize
|
||
|
* tileDX)
|
||
|
- tileScroll.y;
|
||
|
|
||
|
// coordinates of the view window upper left corner in U,V
|
||
|
|
||
|
baseCoords.u = ((2 * (viewPos.y >> tileDXShift) + metaDY / 16) + viewPos.x)
|
||
|
/ (platformWidth * 2);
|
||
|
baseCoords.v = ((2 * (viewPos.y >> tileDXShift) + metaDY / 16) - viewPos.x)
|
||
|
/ (platformWidth * 2);
|
||
|
baseCoords.z = 0;
|
||
|
|
||
|
setAreaSound(baseCoords); //+TilePoint(tileRectWidth, tileRectHeight,0));
|
||
|
|
||
|
updateHandleRefs(baseCoords); // viewPoint, &sti );
|
||
|
// coordinates of current metatile (in X,Y), relative to screen
|
||
|
|
||
|
metaPos.x = (baseCoords.u - baseCoords.v) * metaDX
|
||
|
- viewPos.x * tileDX;
|
||
|
|
||
|
metaPos.y = viewPos.y
|
||
|
- (baseCoords.u + baseCoords.v) * metaDY;
|
||
|
|
||
|
// 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 + metaTileHeight * 4 ;
|
||
|
baseCoords.u--,
|
||
|
baseCoords.v--
|
||
|
) {
|
||
|
drawMetaRow(baseCoords, metaPos);
|
||
|
|
||
|
metaPos.y += metaDY;
|
||
|
metaPos.x -= metaDX;
|
||
|
|
||
|
drawMetaRow(TilePoint(baseCoords.u - 1, baseCoords.v, 0), metaPos);
|
||
|
|
||
|
metaPos.y += metaDY;
|
||
|
metaPos.x += metaDX;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Mouse Pointer Fixup Routine
|
||
|
* ===================================================================== */
|
||
|
|
||
|
// This routine draws an image of the mouse pointer onto the
|
||
|
// back buffer, and also updates the mouse pointer's backsave
|
||
|
// buffer with the new data underneath it.
|
||
|
//
|
||
|
// Since the blitting of the backsave buffer takes so long,
|
||
|
// it would cause annoying flicker to have to hide the mouse
|
||
|
// pointer during the blit. Instead, we go ahead and over-write
|
||
|
// the image of the mouse pointer with the image of the new
|
||
|
// mouse pointer.
|
||
|
|
||
|
inline void drawTileMousePointer(void) {
|
||
|
gPixelMap *currentPtr,
|
||
|
*saveMap;
|
||
|
Point16 offset;
|
||
|
Rect16 saveExtent,
|
||
|
blitExtent;
|
||
|
|
||
|
// Get the image of the pointer and the hotspot offset
|
||
|
currentPtr = pointer.getImage(offset);
|
||
|
|
||
|
// If pointer exists, and is in a visible state
|
||
|
if (currentPtr && pointer.isShown()) {
|
||
|
// Get address of pointer's backsave rect
|
||
|
saveMap = pointer.getSaveMap(saveExtent);
|
||
|
|
||
|
// If the pointer overlaps the tile scrolling area
|
||
|
if (saveExtent.overlap(tileRect)) {
|
||
|
// get the intersecting area
|
||
|
blitExtent = intersect(saveExtent, tileRect);
|
||
|
|
||
|
mouseSavePort.setMap(saveMap);
|
||
|
|
||
|
// Blit the tile data into the backsave buffer
|
||
|
mouseSavePort.bltPixels(
|
||
|
tileDrawMap,
|
||
|
blitExtent.x - tileRect.x + fineScroll.x,
|
||
|
blitExtent.y - tileRect.y + fineScroll.y,
|
||
|
blitExtent.x - saveExtent.x,
|
||
|
blitExtent.y - saveExtent.y,
|
||
|
blitExtent.width,
|
||
|
blitExtent.height);
|
||
|
|
||
|
// Blit the mouse pointer onto the tile map
|
||
|
int x, y;
|
||
|
|
||
|
x = mouseState.pos.x + offset.x + fineScroll.x - tileRect.x;
|
||
|
y = mouseState.pos.y + offset.y + fineScroll.y - tileRect.y;
|
||
|
|
||
|
// if ( x >=0 && y >=0 )
|
||
|
TBlit(&tileDrawMap, currentPtr, x, y);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Tile masking
|
||
|
* ===================================================================== */
|
||
|
|
||
|
enum maskRules {
|
||
|
maskRuleNever, // never mask
|
||
|
maskRuleAlways, // always mask
|
||
|
|
||
|
// Mask based on U threshold
|
||
|
maskRuleUClose,
|
||
|
maskRuleUMed,
|
||
|
maskRuleUFar,
|
||
|
|
||
|
// Mask based on V threshold
|
||
|
maskRuleVClose,
|
||
|
maskRuleVMed,
|
||
|
maskRuleVFar,
|
||
|
|
||
|
// Mask based on vertical distance
|
||
|
maskRuleYClose,
|
||
|
maskRuleYMed,
|
||
|
maskRuleYFar,
|
||
|
|
||
|
// Mask based on combined U & V
|
||
|
maskRuleConvexNear,
|
||
|
maskRuleConcaveFar,
|
||
|
maskRuleConvexFar,
|
||
|
maskRuleConcaveNear,
|
||
|
|
||
|
// More mask types to come!
|
||
|
};
|
||
|
|
||
|
const int thresh1 = 0,
|
||
|
thresh2 = tileUVSize / 4,
|
||
|
thresh3 = tileUVSize - 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 / tileDX;
|
||
|
int16 length = 1;
|
||
|
|
||
|
TilePoint rLoc;
|
||
|
TilePoint origin(uOrg, vOrg, 0);
|
||
|
|
||
|
tilePos.y = screenPos.y - (platformWidth - 1) * tileHeight;
|
||
|
|
||
|
u = platformWidth - 1;
|
||
|
v = platformWidth - 1;
|
||
|
|
||
|
relLoc.u = - relLoc.u - (platformWidth - 1) * tileUVSize;
|
||
|
relLoc.v = - relLoc.v - (platformWidth - 1) * tileUVSize;
|
||
|
|
||
|
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 * tileUVSize;
|
||
|
rLoc.v += offset * tileUVSize;
|
||
|
offset <<= 1;
|
||
|
col += offset;
|
||
|
tilePos.x += tileDX * offset;
|
||
|
}
|
||
|
|
||
|
for (;
|
||
|
col < length && tilePos.x <= right;
|
||
|
col += 2,
|
||
|
pCoords.u++,
|
||
|
pCoords.v--,
|
||
|
rLoc.u -= tileUVSize,
|
||
|
rLoc.v += tileUVSize,
|
||
|
tilePos.x += tileWidth
|
||
|
) {
|
||
|
Platform **pGet;
|
||
|
|
||
|
if (tilePos.x < 0) continue;
|
||
|
if (rLoc.u <= -tileUVSize || rLoc.v <= -tileUVSize)
|
||
|
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 == NULL) 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 -= tileDX;
|
||
|
x2++;
|
||
|
length += 2;
|
||
|
u--;
|
||
|
relLoc.u += tileUVSize;
|
||
|
} else {
|
||
|
x += tileDX;
|
||
|
x2--;
|
||
|
length -= 2;
|
||
|
v--;
|
||
|
relLoc.v += tileUVSize;
|
||
|
}
|
||
|
|
||
|
tilePos.y += tileDY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void maskMetaRow(
|
||
|
gPixelMap &sMap,
|
||
|
TilePoint coords,
|
||
|
TilePoint relLoc,
|
||
|
Point16 pos,
|
||
|
uint16 roofID) {
|
||
|
WorldMapData *curMap = &mapList[ currentMapNum ];
|
||
|
|
||
|
int16 uOrg = coords.u * platformWidth,
|
||
|
vOrg = coords.v * platformWidth;
|
||
|
|
||
|
Platform *drawList[ maxPlatforms + 1 ],
|
||
|
**put = drawList;
|
||
|
|
||
|
int16 mapSizeMask = curMap->mapSize - 1,
|
||
|
mapEdgeType = (*curMap->map)->edgeType;
|
||
|
uint16 *mapData = (*curMap->map)->mapData;
|
||
|
|
||
|
MetaTilePtr metaArray = *curMap->metaList;
|
||
|
|
||
|
int16 layerLimit;
|
||
|
|
||
|
for (;
|
||
|
pos.x < sMap.size.x + metaDX;
|
||
|
coords.u++,
|
||
|
coords.v--,
|
||
|
relLoc.u += platUVSize,
|
||
|
relLoc.v -= platUVSize,
|
||
|
uOrg += platformWidth,
|
||
|
vOrg -= platformWidth,
|
||
|
pos.x += metaTileWidth
|
||
|
) {
|
||
|
TilePoint clipCoords;
|
||
|
int16 mtile;
|
||
|
MetaTilePtr metaPtr;
|
||
|
|
||
|
clipCoords.u = (uint16)coords.u % curMap->mapSize;
|
||
|
clipCoords.v = (uint16)coords.v % curMap->mapSize;
|
||
|
clipCoords.z = 0;
|
||
|
|
||
|
if (coords != clipCoords) {
|
||
|
switch (mapEdgeType) {
|
||
|
case edgeTypeBlack: // continue;
|
||
|
case edgeTypeFill0:
|
||
|
mtile = 0;
|
||
|
break;
|
||
|
|
||
|
case edgeTypeFill1:
|
||
|
mtile = 1;
|
||
|
break;
|
||
|
|
||
|
case edgeTypeRepeat:
|
||
|
coords.u = clamp(0, coords.u, mapSizeMask);
|
||
|
coords.v = clamp(0, coords.v, mapSizeMask);
|
||
|
mtile = mapData[ clipCoords.u * curMap->mapSize + clipCoords.v ] & ~metaTileVisited;
|
||
|
break;
|
||
|
|
||
|
case edgeTypeWrap:
|
||
|
mtile = mapData[ clipCoords.u * curMap->mapSize + clipCoords.v ] & ~metaTileVisited;
|
||
|
break;
|
||
|
}
|
||
|
} else mtile = mapData[ clipCoords.u * curMap->mapSize + clipCoords.v ] & ~metaTileVisited;
|
||
|
|
||
|
if (mtile >= curMap->metaCount) mtile = curMap->metaCount - 1;
|
||
|
|
||
|
metaPtr = &metaArray[ mtile ];
|
||
|
put = drawList;
|
||
|
|
||
|
if (metaPtr == NULL) 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)) == NULL)
|
||
|
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 = tileHeight * (platformWidth - 1) + maxTileHeight + 192;
|
||
|
|
||
|
if (pos.y <= 0
|
||
|
|| pos.y - p->highestPixel >= sMap.size.y)
|
||
|
continue;
|
||
|
|
||
|
*put++ = p;
|
||
|
}
|
||
|
}
|
||
|
*put++ = NULL;
|
||
|
|
||
|
if (drawList[ 0 ] != NULL) {
|
||
|
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 >> tileDXShift)
|
||
|
- (platformWidth * mapList[ currentMapNum ].mapSize),
|
||
|
viewPos.y = (platformWidth
|
||
|
* mapList[ currentMapNum ].mapSize << tileDXShift)
|
||
|
- aPos.y;
|
||
|
|
||
|
// coordinates of the view window upper left corner in U,V
|
||
|
|
||
|
baseCoords.u = ((2 * (viewPos.y >> tileDXShift) + metaDY / 16) + viewPos.x)
|
||
|
/ (platformWidth * 2);
|
||
|
baseCoords.v = ((2 * (viewPos.y >> tileDXShift) + metaDY / 16) - viewPos.x)
|
||
|
/ (platformWidth * 2);
|
||
|
baseCoords.z = 0;
|
||
|
|
||
|
// coordinates of current metatile (in X,Y), relative to screen
|
||
|
|
||
|
metaPos.x = (baseCoords.u - baseCoords.v) * metaDX
|
||
|
- viewPos.x * tileDX;
|
||
|
|
||
|
metaPos.y = viewPos.y
|
||
|
- (baseCoords.u + baseCoords.v) * metaDY;
|
||
|
|
||
|
// Compute where the object is relative to the metatile coords
|
||
|
|
||
|
relLoc.u = (baseCoords.u * platUVSize) - loc.u;
|
||
|
relLoc.v = (baseCoords.v * platUVSize) - 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 + metaTileHeight * 4 ;
|
||
|
baseCoords.u--,
|
||
|
baseCoords.v--
|
||
|
) {
|
||
|
maskMetaRow(sMap, baseCoords, relLoc, metaPos, roofID);
|
||
|
|
||
|
metaPos.y += metaDY;
|
||
|
metaPos.x -= metaDX;
|
||
|
|
||
|
relLoc.u -= platUVSize;
|
||
|
|
||
|
maskMetaRow(sMap, TilePoint(baseCoords.u - 1, baseCoords.v, 0),
|
||
|
relLoc, metaPos, roofID);
|
||
|
|
||
|
metaPos.y += metaDY;
|
||
|
metaPos.x += metaDX;
|
||
|
|
||
|
relLoc.v -= platUVSize;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Tile picking
|
||
|
* ===================================================================== */
|
||
|
|
||
|
#if DEBUG
|
||
|
|
||
|
bool showTile = FALSE;
|
||
|
|
||
|
|
||
|
const uint16 lowerRightMask = 0x1111;
|
||
|
const uint16 lowerLeftMask = 0x000F;
|
||
|
|
||
|
inline void drawSubTiles(const TilePoint &tp, uint16 subTileMask, uint8 *cornerHeight) {
|
||
|
TilePoint pt1,
|
||
|
pt2;
|
||
|
|
||
|
uint8 subTileNo;
|
||
|
uint16 curSubTileMask;
|
||
|
|
||
|
pt1.z = 0;
|
||
|
pt2.z = 0;
|
||
|
|
||
|
for (subTileNo = 0; subTileNo < 16; subTileNo++) {
|
||
|
curSubTileMask = (1 << subTileNo);
|
||
|
if (curSubTileMask & subTileMask) {
|
||
|
// check if we're drawing tile to lower left
|
||
|
if (!((curSubTileMask >> 4) & subTileMask) || curSubTileMask & lowerLeftMask) {
|
||
|
pt1.u = pt2.u = (subTileNo & 0x0C);
|
||
|
pt1.v = ((subTileNo & 0x03) << 2);
|
||
|
pt2.v = pt1.v + 4;
|
||
|
pt1.z = ptHeight(pt1, cornerHeight);
|
||
|
pt2.z = ptHeight(pt2, cornerHeight);
|
||
|
TPLine(tp + pt1, tp + pt2);
|
||
|
}
|
||
|
|
||
|
// check if we're drawing tile to lower right
|
||
|
if (!((curSubTileMask >> 1) & subTileMask) || curSubTileMask & lowerRightMask) {
|
||
|
pt1.u = (subTileNo & 0x0C);
|
||
|
pt2.u = pt1.u + 4;
|
||
|
pt1.v = pt2.v = ((subTileNo & 0x03) << 2);
|
||
|
pt1.z = ptHeight(pt1, cornerHeight);
|
||
|
pt2.z = ptHeight(pt2, cornerHeight);
|
||
|
TPLine(tp + pt1, tp + pt2);
|
||
|
}
|
||
|
|
||
|
// draw upper right
|
||
|
pt1.u = pt2.u = (subTileNo & 0x0C) + 4;
|
||
|
pt1.v = ((subTileNo & 0x03) << 2);
|
||
|
pt2.v = pt1.v + 4;
|
||
|
pt1.z = ptHeight(pt1, cornerHeight);
|
||
|
pt2.z = ptHeight(pt2, cornerHeight);
|
||
|
TPLine(tp + pt1, tp + pt2);
|
||
|
|
||
|
// draw upper left
|
||
|
pt1.u = (subTileNo & 0x0C);
|
||
|
pt2.u = pt1.u + 4;
|
||
|
pt1.v = pt2.v = ((subTileNo & 0x03) << 2) + 4;
|
||
|
pt1.z = ptHeight(pt1, cornerHeight);
|
||
|
pt2.z = ptHeight(pt2, cornerHeight);
|
||
|
TPLine(tp + pt1, tp + pt2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void showAbstractTile(const TilePoint &tp, TileInfo *ti) {
|
||
|
TilePoint workTp = tp;
|
||
|
uint8 *chPtr;
|
||
|
uint8 raisedCornerHeight[ 4 ] = { 0, 0, 0, 0 };
|
||
|
|
||
|
if (ti->combinedTerrainMask() & terrainRaised) {
|
||
|
if ((1L << ti->attrs.bgdTerrain) & terrainRaised) {
|
||
|
workTp.z += ti->attrs.terrainHeight;
|
||
|
chPtr = raisedCornerHeight;
|
||
|
} else
|
||
|
chPtr = ti->attrs.cornerHeight;
|
||
|
drawSubTiles(workTp, ~ti->attrs.terrainMask, chPtr);
|
||
|
workTp.z = tp.z;
|
||
|
if ((1L << ti->attrs.fgdTerrain) & terrainRaised) {
|
||
|
workTp.z += ti->attrs.terrainHeight;
|
||
|
chPtr = raisedCornerHeight;
|
||
|
} else
|
||
|
chPtr = ti->attrs.cornerHeight;
|
||
|
drawSubTiles(workTp, ti->attrs.terrainMask, chPtr);
|
||
|
} else
|
||
|
drawSubTiles(workTp, 0xFFFF, ti->attrs.cornerHeight);
|
||
|
|
||
|
TilePoint pt1 = tp + TilePoint(0, 0, 0),
|
||
|
pt2 = tp + TilePoint(16, 0, 0),
|
||
|
pt3 = tp + TilePoint(0, 16, 0),
|
||
|
pt4 = tp + TilePoint(16, 16, 0);
|
||
|
TPLine(pt1, pt2);
|
||
|
TPLine(pt1, pt3);
|
||
|
TPLine(pt2, pt4);
|
||
|
TPLine(pt3, pt4);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Compute the picked position as if the mouse were pointing at the same
|
||
|
// level as the protagainist's feet.
|
||
|
TilePoint pickTilePos(Point32 pos, const TilePoint &protagPos) {
|
||
|
TilePoint coords;
|
||
|
|
||
|
pos.x += tileScroll.x;
|
||
|
pos.y += tileScroll.y + protagPos.z;
|
||
|
|
||
|
coords = XYToUV(pos);
|
||
|
coords.z = protagPos.z;
|
||
|
|
||
|
return coords;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Inspect packed tile bitmap to determine if a pixel is opaque.
|
||
|
bool isTilePixelOpaque(int16 baseX, int16 baseY, int16 mapHeight, uint8 *td) {
|
||
|
bool opaque;
|
||
|
int16 x = baseX + tileDX,
|
||
|
y = mapHeight - baseY,
|
||
|
accum = 0;
|
||
|
|
||
|
if (y < 0 || y >= mapHeight) return FALSE;
|
||
|
|
||
|
while (y) {
|
||
|
// skip initial transparency
|
||
|
accum = *td;
|
||
|
td++;
|
||
|
while (accum < tileWidth) {
|
||
|
// 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(-tileDX + 2, relPos.x, tileDX - 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 (subUVPointRel >= 0 &&
|
||
|
subUVPoint.u < 16 &&
|
||
|
subUVPoint.v < 16) {
|
||
|
if (subUVPointRel < pointH + (subTileDY * 2) / subTileSize) {
|
||
|
pickCoords = (tCoords << tileUVShift);
|
||
|
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 -= (subTileDY * 2) / subTileSize;
|
||
|
|
||
|
// 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 + ((subTileDY * 2) / subTileSize) - 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 >> subTileDXShift;
|
||
|
subTile.v = 0;
|
||
|
subTileRel.x = relPos.x - (subTile.u << subTileDXShift);
|
||
|
subTileRel.y = relPos.y - (subTile.u << subTileDYShift) - h;
|
||
|
} else {
|
||
|
subTile.u = 0;
|
||
|
subTile.v = (-relPos.x + 1) >> subTileDXShift;
|
||
|
subTileRel.x = relPos.x + (subTile.v << subTileDXShift);
|
||
|
subTileRel.y = relPos.y - (subTile.v << subTileDYShift) - 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 << tileUVShift);
|
||
|
pickCoords.u += (subTile.u << subTileShift);
|
||
|
pickCoords.v += (subTile.v << subTileShift);
|
||
|
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 & tileUVMask,
|
||
|
floorCoords.v & tileUVMask,
|
||
|
0),
|
||
|
ti->attrs.cornerHeight);
|
||
|
break;
|
||
|
}
|
||
|
// mouse is on top of raised section
|
||
|
if (subTileRel.y <
|
||
|
ti->attrs.terrainHeight + subTileDY * 2 - yBound) {
|
||
|
pickCoords = (tCoords << tileUVShift);
|
||
|
y = subTileRel.y - ti->attrs.terrainHeight;
|
||
|
pickCoords.u += (subTile.u << subTileShift) +
|
||
|
(((subTileRel.x >> 1) + y) >> 1);
|
||
|
pickCoords.v += (subTile.v << subTileShift) +
|
||
|
((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 (subUVPointRel >= 0 &&
|
||
|
subUVPoint.u < 4 &&
|
||
|
subUVPoint.v < 4) {
|
||
|
if (subUVPointRel < pointH + (subTileDY * 2) / subTileSize) {
|
||
|
pickCoords = (tCoords << tileUVShift);
|
||
|
pickCoords.u += (subTile.u << subTileShift) + subUVPoint.u;
|
||
|
pickCoords.v += (subTile.v << subTileShift) + subUVPoint.v;
|
||
|
pickCoords.z = h + pointH;
|
||
|
floorCoords = pickCoords;
|
||
|
foundPoint = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Test next point on subtile
|
||
|
subUVPoint.u++;
|
||
|
subUVPoint.v++;
|
||
|
subUVPointRel -= (subTileDY * 2) / subTileSize;
|
||
|
pointH = ptHeight((subTile << subTileShift) + 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 -= subTileDX;
|
||
|
subTile.u++;
|
||
|
sMask <<= subTileMaskUShift;
|
||
|
} else {
|
||
|
subTileRel.x += subTileDX;
|
||
|
subTile.v++;
|
||
|
sMask <<= subTileMaskVShift;
|
||
|
}
|
||
|
subTileRel.y -= subTileDY;
|
||
|
} else { // subTileRel.x == 0 or 1
|
||
|
// move up to the next vertical subtile
|
||
|
subTile.u++;
|
||
|
subTile.v++;
|
||
|
sMask <<= subTileMaskUShift + subTileMaskVShift;
|
||
|
subTileRel.y -= subTileDY * 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) << subTileDXShift);
|
||
|
subTileRel.y = ti->attrs.terrainHeight + subTileDY * 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 & (subTileDX - 1) & 0xFFFE) {
|
||
|
if (relPos.x > 0) {
|
||
|
curSubTileCol = relPos.x >> subTileDXShift;
|
||
|
rightSubTileCol = curSubTileCol + 2;
|
||
|
leftSubTileCol = curSubTileCol - 1;
|
||
|
goto testLeft;
|
||
|
} else {
|
||
|
curSubTileCol =
|
||
|
(relPos.x + subTileDX - 1) >> subTileDXShift;
|
||
|
leftSubTileCol = curSubTileCol - 2;
|
||
|
rightSubTileCol = curSubTileCol + 1;
|
||
|
}
|
||
|
} else {
|
||
|
curSubTileCol = relPos.x >> subTileDXShift;
|
||
|
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 = -subTileDX + 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 = subTileDX - 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 << subTileDXShift) + 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) * subTileDY) - 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 &= tileUVMask;
|
||
|
testCoords.v &= tileUVMask;
|
||
|
|
||
|
// 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 >> subTileShift);
|
||
|
} else {
|
||
|
ASSERT(testCoords.v == 0);
|
||
|
adjTCoords.v--;
|
||
|
adjSubMask = 0x0008 << (testCoords.u & ~subTileMask);
|
||
|
}
|
||
|
|
||
|
mCoords = adjTCoords >> platShift;
|
||
|
|
||
|
// If metatile of adjacent tile does not exist, the pick point
|
||
|
// is valid.
|
||
|
if ((mt = curMap->lookupMeta(mCoords)) == NULL) return FALSE;
|
||
|
|
||
|
tCoords.u = adjTCoords.u & platMask;
|
||
|
tCoords.v = adjTCoords.v & platMask;
|
||
|
tCoords.z = 0;
|
||
|
origin = mCoords << platShift;
|
||
|
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < maxPlatforms; i++) {
|
||
|
Platform *p;
|
||
|
int16 h,
|
||
|
trFlags;
|
||
|
|
||
|
if ((p = mt->fetchPlatform(currentMapNum, i)) == NULL)
|
||
|
continue;
|
||
|
|
||
|
if (!(p->flags & plVisible) || platformRipped(p)) continue;
|
||
|
|
||
|
// Fetch the tile at this location
|
||
|
adjTile = p->fetchTile(
|
||
|
currentMapNum,
|
||
|
tCoords,
|
||
|
origin,
|
||
|
h,
|
||
|
trFlags);
|
||
|
|
||
|
if (adjTile == NULL) continue;
|
||
|
|
||
|
// If current tile is higher or lower than the picked point
|
||
|
// skip this tile.
|
||
|
if (h > pickCoords.z) continue;
|
||
|
if (h + adjTile->attrs.terrainHeight <= pickCoords.z)
|
||
|
continue;
|
||
|
|
||
|
// If adjacent subtile is not raised, skip this tile
|
||
|
if (!(adjTile->attrs.testTerrain(adjSubMask) & terrainRaised))
|
||
|
continue;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If all platforms have been checked, the pick point is valid
|
||
|
if (i >= maxPlatforms) return FALSE;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Return the TilePoint at which the mouse it pointing
|
||
|
|
||
|
TilePoint pickTile(Point32 pos,
|
||
|
const TilePoint &protagPos,
|
||
|
TilePoint *floorResult,
|
||
|
ActiveItemPtr *pickTAI) {
|
||
|
WorldMapData *curMap = &mapList[ currentMapNum ];
|
||
|
|
||
|
TilePoint pickCoords,
|
||
|
floorCoords,
|
||
|
pCoords,
|
||
|
fCoords,
|
||
|
coords,
|
||
|
tileCoords;
|
||
|
Point32 relPos; // pick pos relative to tile.
|
||
|
int16 zMax,
|
||
|
zMin,
|
||
|
mag;
|
||
|
TilePoint mCoords,
|
||
|
tCoords,
|
||
|
origin,
|
||
|
testCoords,
|
||
|
deltaP;
|
||
|
MetaTile *mt;
|
||
|
ActiveItemPtr bestTileTAI;
|
||
|
TileInfo *ti,
|
||
|
*bestTile = NULL;
|
||
|
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 & ~tileUVMask) + tileUVSize / 2;
|
||
|
coords.v = (coords.v & ~tileUVMask) + tileUVSize / 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 - maxPickHeight - mag;
|
||
|
zMax = protagPos.z + maxPickHeight + mag;
|
||
|
|
||
|
// Compute the coords of the actual tile that they clicked on.
|
||
|
tileCoords = coords >> tileUVShift;
|
||
|
|
||
|
// 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) * tileDX;
|
||
|
relPos.y = curMap->mapHeight - pos.y - (tileCoords.u + tileCoords.v) * tileDY;
|
||
|
|
||
|
// Compute which metatile the click occured on, and the tile
|
||
|
// within that metatile, and the origin coords of the metatile
|
||
|
mCoords = tileCoords >> platShift;
|
||
|
tCoords.u = tileCoords.u & platMask;
|
||
|
tCoords.v = tileCoords.v & platMask;
|
||
|
tCoords.z = 0;
|
||
|
origin = mCoords << platShift;
|
||
|
|
||
|
// Lookup the metatile
|
||
|
mt = curMap->lookupMeta(mCoords);
|
||
|
|
||
|
// While we are less than the pick altitude
|
||
|
while (relPos.y < zMax + tileDX + maxStepHeight - abs(relPos.x >> 1)) {
|
||
|
// If there is a metatile on this spot
|
||
|
if (mt != NULL) {
|
||
|
// Iterate through all platforms
|
||
|
for (i = 0; i < maxPlatforms; i++) {
|
||
|
Platform *p;
|
||
|
StandingTileInfo sti;
|
||
|
|
||
|
if ((p = mt->fetchPlatform(currentMapNum, i)) == NULL)
|
||
|
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 == NULL) 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 + maxStepHeight) 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 == NULL) {
|
||
|
if (surface != surfaceHoriz
|
||
|
&& pointOnHiddenSurface(
|
||
|
tCoords + origin,
|
||
|
pCoords,
|
||
|
surface)) {
|
||
|
surface = surface == surfaceVertU
|
||
|
? surfaceVertV
|
||
|
: surfaceVertU;
|
||
|
}
|
||
|
|
||
|
// If pick point is on vertical surface
|
||
|
// not facing protaganist, reject tile
|
||
|
if (surface == surfaceVertU
|
||
|
&& pCoords.v < protagPos.v)
|
||
|
continue;
|
||
|
if (surface == surfaceVertV
|
||
|
&& pCoords.u < protagPos.u)
|
||
|
continue;
|
||
|
/* // Make sure surface is exposed, else reject it
|
||
|
if ( surface != surfaceHoriz
|
||
|
&& !validSurface( tCoords + origin, pCoords ) )
|
||
|
continue;*/
|
||
|
}
|
||
|
|
||
|
pickCoords = pCoords;
|
||
|
floorCoords = fCoords;
|
||
|
bestTile = ti;
|
||
|
#ifdef DAVIDR
|
||
|
bestTP = tCoords + origin;
|
||
|
bestTP.z = sti.surfaceHeight;
|
||
|
#endif
|
||
|
bestTileTAI = sti.surfaceTAG;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Crabwalk down through the tile positions
|
||
|
if (relPos.x < 0) {
|
||
|
tCoords.u--;
|
||
|
coords.u -= tileUVSize;
|
||
|
if (tCoords.u < 0) {
|
||
|
tCoords.u = platformWidth - 1;
|
||
|
mCoords.u--;
|
||
|
origin = mCoords << platShift;
|
||
|
mt = curMap->lookupMeta(mCoords);
|
||
|
}
|
||
|
relPos.x += tileDX;
|
||
|
} else {
|
||
|
tCoords.v--;
|
||
|
coords.v -= tileUVSize;
|
||
|
if (tCoords.v < 0) {
|
||
|
tCoords.v = platformWidth - 1;
|
||
|
mCoords.v--;
|
||
|
origin = mCoords << platShift;
|
||
|
mt = curMap->lookupMeta(mCoords);
|
||
|
}
|
||
|
relPos.x -= tileDX;
|
||
|
}
|
||
|
relPos.y += tileDY;
|
||
|
|
||
|
// Compute new altitude range based upon the tile position
|
||
|
// relative to the protaganist's position.
|
||
|
zMin = protagPos.z - maxPickHeight - (coords - protagPos).quickHDistance();
|
||
|
zMax = protagPos.z + maxPickHeight + (coords - protagPos).quickHDistance();
|
||
|
}
|
||
|
|
||
|
// If no tile was found, return the default.
|
||
|
if (!bestTile) {
|
||
|
if (floorResult) *floorResult = floorCoords;
|
||
|
if (pickTAI) *pickTAI = NULL;
|
||
|
return pickCoords;
|
||
|
}
|
||
|
|
||
|
#ifdef DAVIDR
|
||
|
if (showTile) {
|
||
|
if (bestTile) {
|
||
|
bestTP.u <<= tileUVShift;
|
||
|
bestTP.v <<= tileUVShift;
|
||
|
showAbstractTile(bestTP, bestTile);
|
||
|
|
||
|
TilePoint pt1, pt2;
|
||
|
pt1 = pt2 = pickCoords;
|
||
|
pt1.u += 3;
|
||
|
pt2.u -= 3;
|
||
|
TPLine(pt1, pt2);
|
||
|
pt1 = pt2 = pickCoords;
|
||
|
pt1.v += 3;
|
||
|
pt2.v -= 3;
|
||
|
TPLine(pt1, pt2);
|
||
|
|
||
|
pt1 = pt2 = floorCoords;
|
||
|
pt1.u += 2;
|
||
|
pt1.v += 2;
|
||
|
pt2.u -= 2;
|
||
|
pt2.v -= 2;
|
||
|
TPLine(pt1, pt2);
|
||
|
pt1 = pt2 = floorCoords;
|
||
|
pt1.u += 2;
|
||
|
pt1.v -= 2;
|
||
|
pt2.u -= 2;
|
||
|
pt2.v += 2;
|
||
|
TPLine(pt1, pt2);
|
||
|
|
||
|
pt1 = pt2 = pickCoords;
|
||
|
pt1.z = bestTP.z;
|
||
|
TPLine(pt1, pt2);
|
||
|
pt2.z = bestTP.z;
|
||
|
pt2.u = pt1.u & 0xFFF0;
|
||
|
TPLine(pt1, pt2);
|
||
|
pt2.u = pt1.u;
|
||
|
pt2.v = pt1.v & 0xFFF0;
|
||
|
TPLine(pt1, pt2);
|
||
|
|
||
|
WriteStatusF(8, "%4.4x:%4.4x:%4.4x", pickCoords.u, pickCoords.v, pickCoords.z);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (floorResult) *floorResult = floorCoords;
|
||
|
if (pickTAI) *pickTAI = bestTileTAI;
|
||
|
return pickCoords;
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Tile cycling
|
||
|
* ===================================================================== */
|
||
|
|
||
|
void cycleTiles(int32 delta) {
|
||
|
if (delta <= 0) return;
|
||
|
|
||
|
for (int i = 0; i < cycleCount; i++) {
|
||
|
TileCycleData &tcd = (*cycleList)[ i ];
|
||
|
|
||
|
tcd.counter += tcd.cycleSpeed * delta;
|
||
|
if (tcd.counter >= 400) {
|
||
|
tcd.counter = 0;
|
||
|
tcd.currentState++;
|
||
|
if (tcd.currentState >= tcd.numStates)
|
||
|
tcd.currentState = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct TileCycleArchive {
|
||
|
int32 counter;
|
||
|
uint8 currentState;
|
||
|
};
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Initialize the tile cycling state array
|
||
|
|
||
|
void initTileCyclingStates(void) {
|
||
|
cycleList = (CycleHandle)LoadResourceToHandle(tileRes, cycleID, "tile cycle data");
|
||
|
if (cycleList == NULL)
|
||
|
error("Unable to load tile cycling data");
|
||
|
|
||
|
cycleCount = RPtrSize(*cycleList) / sizeof(TileCycleData);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Save the tile cycling state array in a save file
|
||
|
|
||
|
void saveTileCyclingStates(SaveFileConstructor &saveGame) {
|
||
|
TileCycleArchive *archiveBuffer;
|
||
|
int16 i;
|
||
|
|
||
|
archiveBuffer = (TileCycleArchive *)RNewPtr(
|
||
|
sizeof(TileCycleArchive) * cycleCount,
|
||
|
NULL,
|
||
|
"archive buffer");
|
||
|
if (archiveBuffer == NULL)
|
||
|
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);
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Load the tile cycling state array from a save file
|
||
|
|
||
|
void loadTileCyclingStates(SaveFileReader &saveGame) {
|
||
|
TileCycleArchive *archiveBuffer;
|
||
|
int16 i;
|
||
|
|
||
|
initTileCyclingStates();
|
||
|
|
||
|
ASSERT(saveGame.getChunkSize() == sizeof(TileCycleArchive) * cycleCount);
|
||
|
|
||
|
archiveBuffer = (TileCycleArchive *)RNewPtr(
|
||
|
sizeof(TileCycleArchive) * cycleCount,
|
||
|
NULL,
|
||
|
"archive buffer");
|
||
|
if (archiveBuffer == NULL)
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
RDisposePtr(archiveBuffer);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// Cleanup the tile cycling state array
|
||
|
|
||
|
void cleanupTileCyclingStates(void) {
|
||
|
if (cycleList != NULL) {
|
||
|
RDisposeHandle((RHANDLE)cycleList);
|
||
|
cycleList = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
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;
|
||
|
|
||
|
objHeight = objCoords.z;
|
||
|
|
||
|
objTileReg.min.u = (objCoords.u - subTileSize) >> tileUVShift;
|
||
|
objTileReg.min.v = (objCoords.v - subTileSize) >> tileUVShift;
|
||
|
objTileReg.max.u = (objCoords.u + subTileSize + tileUVMask) >> tileUVShift;
|
||
|
objTileReg.max.v = (objCoords.v + subTileSize + tileUVMask) >> tileUVShift;
|
||
|
|
||
|
objMetaReg.min.u = objTileReg.min.u >> platShift;
|
||
|
objMetaReg.min.v = objTileReg.min.v >> platShift;
|
||
|
objMetaReg.max.u = (objTileReg.max.u + platMask) >> platShift;
|
||
|
objMetaReg.max.v = (objTileReg.max.v + platMask) >> platShift;
|
||
|
|
||
|
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 == NULL) continue;
|
||
|
|
||
|
TilePoint origin;
|
||
|
TileRegion relTileReg;
|
||
|
int16 tileU, tileV;
|
||
|
|
||
|
origin.u = metaU << platShift;
|
||
|
origin.v = metaV << platShift;
|
||
|
|
||
|
// 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, platformWidth);
|
||
|
relTileReg.max.v = MIN(objTileReg.max.v - origin.v, platformWidth);
|
||
|
|
||
|
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)) == NULL)
|
||
|
continue;
|
||
|
|
||
|
if (!(p->flags & plVisible) || p->roofRipID() <= 0)
|
||
|
continue;
|
||
|
|
||
|
t = p->fetchTile(
|
||
|
objMapNum,
|
||
|
TilePoint(tileU, tileV, 0),
|
||
|
origin,
|
||
|
height,
|
||
|
trFlags);
|
||
|
|
||
|
if (t != NULL && 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() != NULL && objRoofID(obj) == rippedRoofID;
|
||
|
}
|
||
|
|
||
|
// Determine if two objects are both under the same roof
|
||
|
bool underSameRoof(GameObject *obj1, GameObject *obj2) {
|
||
|
return obj1->world() != NULL
|
||
|
&& obj2->world() != NULL
|
||
|
&& objRoofID(obj1) == objRoofID(obj2);
|
||
|
}
|
||
|
|
||
|
/* ===================================================================== *
|
||
|
Main view update routine
|
||
|
* ===================================================================== */
|
||
|
|
||
|
extern void testSprites(void);
|
||
|
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
void *LockTileDrawMapSurface(LPRECT rc = NULL);
|
||
|
bool UnlockTileDrawMapSurface(void *);
|
||
|
void drawTileMapToScreen(Rect16 tileRect, void *);
|
||
|
#include "ftawin.h"
|
||
|
extern CFTWindow *pWindow;
|
||
|
extern uint32 tileSurfaceWidth;
|
||
|
extern uint32 tileSurfaceHeight;
|
||
|
|
||
|
#endif
|
||
|
|
||
|
void updateMainDisplay(void) {
|
||
|
static TilePoint lastViewLoc = TilePoint(0, 0, 0);
|
||
|
|
||
|
int32 deltaTime = gameTime - lastUpdateTime;
|
||
|
|
||
|
ASSERT(isActor(viewCenterObject));
|
||
|
|
||
|
Actor *viewActor = (Actor *)GameObject::objectAddress(
|
||
|
viewCenterObject);
|
||
|
TilePoint viewDiff;
|
||
|
|
||
|
ASSERT(isWorld(viewActor->IDParent()));
|
||
|
|
||
|
GameWorld *viewWorld = (GameWorld *)viewActor->parent();
|
||
|
|
||
|
if (viewWorld != currentWorld) {
|
||
|
currentWorld = viewWorld;
|
||
|
setCurrentMap(currentWorld->mapNum);
|
||
|
}
|
||
|
|
||
|
WorldMapData *curMap = &mapList[ currentMapNum ];
|
||
|
|
||
|
Point32 scrollCenter,
|
||
|
scrollDelta;
|
||
|
int32 scrollSpeed = defaultScrollSpeed,
|
||
|
scrollDistance;
|
||
|
|
||
|
int16 viewSize = tileRect.height;
|
||
|
int16 mapSectors = curMap->mapSize * 8 * 16 / sectorSize;
|
||
|
TilePoint trackPos,
|
||
|
mCoords;
|
||
|
|
||
|
lastUpdateTime = gameTime;
|
||
|
|
||
|
|
||
|
// Get the coordinates of the object which the camera is tracking
|
||
|
getViewTrackPos(trackPos);
|
||
|
|
||
|
viewDiff = trackPos - lastViewLoc;
|
||
|
lastViewLoc = trackPos;
|
||
|
|
||
|
if (abs(viewDiff.u) > 8 * platformWidth * tileUVSize
|
||
|
|| abs(viewDiff.v) > 8 * platformWidth * tileUVSize)
|
||
|
freeAllTileBanks();
|
||
|
|
||
|
// Add current coordinates to map if they have mapping
|
||
|
markMetaAsVisited(trackPos);
|
||
|
|
||
|
// Convert to XY coordinates.
|
||
|
targetScroll.x =
|
||
|
((trackPos.u - trackPos.v) << 1)
|
||
|
+ curMap->mapHeight - tileRect.width / 2;
|
||
|
targetScroll.y =
|
||
|
curMap->mapHeight - (trackPos.u + trackPos.v)
|
||
|
- trackPos.z - tileRect.height / 2 - 32;
|
||
|
|
||
|
// 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 & tileDXMask;
|
||
|
fineScroll.y = 0;
|
||
|
|
||
|
// Compute the center of the screen in (u,v) coords.
|
||
|
scrollCenter.x = tileScroll.x + tileRect.width / 2;
|
||
|
scrollCenter.y = tileScroll.y + tileRect.height / 2;
|
||
|
viewCenter = XYToUV(scrollCenter);
|
||
|
|
||
|
// Compute the largest U/V rectangle which completely
|
||
|
// encloses the view area, and convert to sector coords.
|
||
|
minSector.u = clamp(0, (viewCenter.u - viewSize) / sectorSize, mapSectors - 1);
|
||
|
minSector.v = clamp(0, (viewCenter.v - viewSize) / sectorSize, mapSectors - 1);
|
||
|
maxSector.u = clamp(0, (viewCenter.u + viewSize) / sectorSize, mapSectors - 1);
|
||
|
maxSector.v = clamp(0, (viewCenter.v + viewSize) / sectorSize, mapSectors - 1);
|
||
|
|
||
|
buildRoofTable();
|
||
|
|
||
|
mCoords.u = trackPos.u >> (tileUVShift + platShift);
|
||
|
mCoords.v = trackPos.v >> (tileUVShift + platShift);
|
||
|
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();
|
||
|
|
||
|
// Render floating windows
|
||
|
drawFloatingWindows(backPort,
|
||
|
Point16(tileRect.x - fineScroll.x, tileRect.y),
|
||
|
tileRect);
|
||
|
|
||
|
// Render the image of the mouse pointer on everything else
|
||
|
drawTileMousePointer();
|
||
|
|
||
|
// Blit it all onto the screen
|
||
|
drawPage->writePixels(
|
||
|
tileRect,
|
||
|
tileDrawMap.data
|
||
|
+ fineScroll.x
|
||
|
+ fineScroll.y * tileDrawMap.size.x,
|
||
|
tileDrawMap.size.x);
|
||
|
|
||
|
updateFrameCount();
|
||
|
}
|
||
|
|
||
|
#if DEBUG
|
||
|
|
||
|
#include "actor.h"
|
||
|
|
||
|
void ShowObjectSection(GameObject *obj) {
|
||
|
ProtoObj *proto = obj->proto();
|
||
|
int16 crossSection = proto->crossSection;
|
||
|
TilePoint tp = obj->getLocation(),
|
||
|
tp1,
|
||
|
tp2,
|
||
|
tp3,
|
||
|
tp4;
|
||
|
|
||
|
tp1 = tp + TilePoint(crossSection, crossSection, 0);
|
||
|
tp2 = tp + TilePoint(-crossSection, crossSection, 0);
|
||
|
tp3 = tp + TilePoint(-crossSection, -crossSection, 0);
|
||
|
tp4 = tp + TilePoint(crossSection, -crossSection, 0);
|
||
|
|
||
|
TPLine(tp1, tp2);
|
||
|
TPLine(tp2, tp3);
|
||
|
TPLine(tp3, tp4);
|
||
|
TPLine(tp4, tp1);
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#if 0
|
||
|
|
||
|
// Tile drawing speed test
|
||
|
|
||
|
/*
|
||
|
int32 tclock0,
|
||
|
tclock1;
|
||
|
|
||
|
extern int32 gameTime;
|
||
|
|
||
|
for (int i=0; i<30000; i++)
|
||
|
{
|
||
|
drawTile( &tileDrawMap,
|
||
|
0, 64, ti->attrs.height,
|
||
|
(*th)->tileData( *ti ) );
|
||
|
}
|
||
|
|
||
|
tclock1 = gameTime;
|
||
|
debugf( "Time = %d\n",
|
||
|
tclock1 - tclock0 );
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
// Tile drawing speed test 2
|
||
|
|
||
|
for (int i = 0; i < 256; i+=4)
|
||
|
{
|
||
|
for (int y=0; y <= tileDrawMap.size.y + 32; y += tileHeight)
|
||
|
{
|
||
|
for (int x=0; x <= tileDrawMap.size.x; x += tileWidth)
|
||
|
{
|
||
|
drawTile( &tileDrawMap,
|
||
|
x, y, ti->attrs.height,
|
||
|
(*th)->tileData( *ti ) );
|
||
|
drawTile( &tileDrawMap,
|
||
|
x, y, ti->attrs.height,
|
||
|
(*th)->tileData( *ti ) );
|
||
|
}
|
||
|
|
||
|
for (x=tileDX; x <= tileDrawMap.size.x; x += tileWidth)
|
||
|
{
|
||
|
drawTile( &tileDrawMap,
|
||
|
x, y + tileDY, ti->attrs.height,
|
||
|
(*th)->tileData( *ti ) );
|
||
|
drawTile( &tileDrawMap,
|
||
|
x, y + tileDY, ti->attrs.height,
|
||
|
(*th)->tileData( *ti ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pointer.hide( mainPort, tileRect );
|
||
|
drawPage->writePixels( tileRect,
|
||
|
tileDrawMap.data,
|
||
|
tileDrawMap.size.x );
|
||
|
pointer.show( mainPort, tileRect );
|
||
|
}
|
||
|
*/
|
||
|
#endif
|
||
|
|
||
|
//-----------------------------------------------------------------------
|
||
|
// mark this and surrounding metatiles as visited
|
||
|
|
||
|
const int mappingRadius = 2;
|
||
|
|
||
|
void markMetaAsVisited(const TilePoint &pt) {
|
||
|
// If (they have cartography)
|
||
|
{
|
||
|
WorldMapData *curMap = &mapList[ currentMapNum ];
|
||
|
uint16 *mapData = (*curMap->map)->mapData;
|
||
|
|
||
|
TilePoint metaCoords = pt >> (tileUVShift + platShift);
|
||
|
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 = tileUVSize * 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
|