mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 14:18:37 +00:00
4684 lines
133 KiB
C++
4684 lines
133 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 3 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
|
|
*
|
|
*
|
|
* Based on the original sources
|
|
* Faery Tale II -- The Halls of the Dead
|
|
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
|
|
*/
|
|
|
|
#include "saga2/saga2.h"
|
|
#include "saga2/detection.h"
|
|
#include "saga2/objects.h"
|
|
#include "saga2/tile.h"
|
|
#include "saga2/motion.h"
|
|
#include "saga2/contain.h"
|
|
#include "saga2/setup.h"
|
|
#include "saga2/script.h"
|
|
#include "saga2/target.h"
|
|
#include "saga2/uimetrcs.h"
|
|
#include "saga2/magic.h"
|
|
#include "saga2/intrface.h"
|
|
#include "saga2/sensor.h"
|
|
#include "saga2/timers.h"
|
|
#include "saga2/grabinfo.h"
|
|
#include "saga2/localize.h"
|
|
#include "saga2/spellbuk.h"
|
|
#include "saga2/tilevect.h"
|
|
#include "saga2/dispnode.h"
|
|
#include "saga2/saveload.h"
|
|
|
|
#include "saga2/methods.r" // generated by SAGA
|
|
#include "saga2/pclass.r"
|
|
namespace Saga2 {
|
|
|
|
APPFUNC(cmdControl);
|
|
|
|
/* ===================================================================== *
|
|
Resource ID constants
|
|
* ===================================================================== */
|
|
|
|
const uint32 nameListID = MKTAG('N', 'A', 'M', 'E'),
|
|
objListID = MKTAG('O', 'B', 'J', 'E'),
|
|
objProtoID = MKTAG('P', 'R', 'O', 0),
|
|
actorProtoID = MKTAG('P', 'R', 'O', 1);
|
|
|
|
/* ===================================================================== *
|
|
Locals
|
|
* ===================================================================== */
|
|
|
|
uint32 nameListCount;
|
|
|
|
uint16 *tempActorCount = nullptr; // array of temporary actor counts
|
|
|
|
int16 objectProtoCount, // object prototype count
|
|
actorProtoCount; // actor prototype count
|
|
|
|
GameObject *objectList = nullptr; // list of all objects
|
|
const int16 objectCount = 4971; // count of objects
|
|
|
|
GameWorld *worldList = nullptr; // list of all worlds
|
|
int16 worldCount; // number of worlds
|
|
|
|
int32 objectListSize,
|
|
actorListSize,
|
|
worldListSize;
|
|
|
|
GameWorld *currentWorld; // pointer to the current world
|
|
|
|
ObjectID viewCenterObject; // ID of object that view tracks
|
|
|
|
hResContext *listRes; // object list resource handle
|
|
extern hResContext *tileRes;
|
|
|
|
uint8 *ProtoObj::nextAvailObj;
|
|
|
|
|
|
// trio ready container consts
|
|
const ContainerInfo trioReadyContInfo[kNumViews] = { { 476, 105 + 0, 1, 3 },
|
|
{ 476, 105 + 150, 1, 3 },
|
|
{ 476, 105 + 300, 1, 3 }
|
|
};
|
|
// indiv ready container consts
|
|
const ContainerInfo indivReadyContInfoTop = { 476, 105 + 0, 1, 3 };
|
|
const ContainerInfo indivReadyContInfoBot = { 476, 105 + 57, 2, 3 };
|
|
|
|
// view controls
|
|
ReadyContainerView *TrioCviews[kNumViews] = { nullptr, nullptr, nullptr };
|
|
ReadyContainerView *indivCviewTop = nullptr,
|
|
*indivCviewBot = nullptr;
|
|
ContainerNode *indivReadyNode;
|
|
|
|
// array of image pointers for ready container backgrounds
|
|
void **backImages;
|
|
|
|
// number of images resources to load for the back images
|
|
int8 numReadyContRes = 4;
|
|
|
|
int16 objectLimboCount, // the number of objects in object limbo
|
|
actorLimboCount, // the number of actors in actor limbo
|
|
importantLimboCount; // the number of objects in important limbo
|
|
|
|
// Indicates wether object states should be paused
|
|
bool objectStatesPaused;
|
|
|
|
ObjectSoundFXs *objectSoundFXTable; // the global object sound effects table
|
|
|
|
#if DEBUG
|
|
bool massAndBulkCount;
|
|
#endif
|
|
|
|
/* ===================================================================== *
|
|
Imports
|
|
* ===================================================================== */
|
|
|
|
extern BackWindow *mainWindow;
|
|
extern StaticPoint16 fineScroll; // current scroll pos
|
|
extern hResContext *imageRes; // image resource handle
|
|
extern SpellStuff *spellBook;
|
|
extern ObjectID pickedObject;
|
|
|
|
const uint32 imageGroupID = MKTAG('I', 'M', 'A', 'G');
|
|
|
|
bool unstickObject(GameObject *obj);
|
|
|
|
/* ===================================================================== *
|
|
Functions
|
|
* ===================================================================== */
|
|
|
|
void **LoadImageRes(hResContext *con, int16 resID, int16 numRes, char a, char b, char c);
|
|
void UnloadImageRes(void **images, int16 numRes);
|
|
void drown(GameObject *obj);
|
|
|
|
/* ===================================================================== *
|
|
class Location member functions
|
|
* ===================================================================== */
|
|
|
|
/*
|
|
void Location::screenPos( Point16 &screenCoords );
|
|
int16 Location::screenDepth( void );
|
|
bool Location::visible( void );
|
|
*/
|
|
|
|
/* ======================================================================= *
|
|
Member functions for class GameObject
|
|
* ======================================================================= */
|
|
|
|
struct GameObjectArchive {
|
|
int16 protoIndex;
|
|
TilePoint location;
|
|
uint16 nameIndex;
|
|
ObjectID parentID,
|
|
siblingID,
|
|
childID;
|
|
uint16 script;
|
|
uint16 objectFlags;
|
|
uint8 hitPoints,
|
|
bParam;
|
|
uint16 misc;
|
|
uint8 missileFacing;
|
|
ActiveItemID currentTAG;
|
|
uint8 sightCtr;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Default constructor
|
|
|
|
GameObject::GameObject() {
|
|
prototype = nullptr;
|
|
_data.projectDummy = 0;
|
|
_data.location = Nowhere;
|
|
_data.nameIndex = 0;
|
|
_data.parentID = Nothing;
|
|
_data.siblingID = Nothing;
|
|
_data.childID = Nothing;
|
|
_data.script = 0;
|
|
_data.objectFlags = 0;
|
|
_data.hitPoints = 0;
|
|
_data.bParam = 0;
|
|
_data.massCount = 0;
|
|
_data.missileFacing = missileRt;
|
|
_data.currentTAG = NoActiveItem;
|
|
_data.sightCtr = 0;
|
|
memset(&_data.reserved, 0, sizeof(_data.reserved));
|
|
|
|
_data.obj = this;
|
|
_index = 0;
|
|
|
|
_godmode = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Constructor -- initial object construction
|
|
|
|
GameObject::GameObject(const ResourceGameObject &res) {
|
|
prototype = g_vm->_objectProtos[res.protoIndex];
|
|
_data.projectDummy = 0;
|
|
_data.location = res.location;
|
|
_data.nameIndex = res.nameIndex;
|
|
_data.parentID = res.parentID;
|
|
_data.siblingID = Nothing;
|
|
_data.childID = Nothing;
|
|
_data.script = res.script;
|
|
_data.objectFlags = res.objectFlags;
|
|
_data.hitPoints = res.hitPoints;
|
|
_data.bParam = prototype->getChargeType() ? prototype->maxCharges : 0;
|
|
_data.massCount = res.misc; //prototype->getInitialItemCount();
|
|
_data.missileFacing = missileRt;
|
|
_data.currentTAG = NoActiveItem;
|
|
_data.sightCtr = 0;
|
|
memset(&_data.reserved, 0, sizeof(_data.reserved));
|
|
|
|
_data.obj = this;
|
|
_index = 0;
|
|
|
|
_godmode = false;
|
|
}
|
|
|
|
GameObject::GameObject(Common::InSaveFile *in) {
|
|
read(in, false);
|
|
_index = 0;
|
|
_godmode = false;
|
|
}
|
|
|
|
void GameObject::read(Common::InSaveFile *in, bool expandProto) {
|
|
int16 pInd = in->readSint16LE();
|
|
if (expandProto)
|
|
in->readSint16LE();
|
|
// Convert the protoype index into an object proto pointer
|
|
prototype = pInd != -1
|
|
? g_vm->_objectProtos[pInd]
|
|
: nullptr;
|
|
|
|
_data.projectDummy = 0;
|
|
_data.location.load(in);
|
|
_data.nameIndex = in->readUint16LE();
|
|
_data.parentID = in->readUint16LE();
|
|
_data.siblingID = in->readUint16LE();
|
|
_data.childID = in->readUint16LE();
|
|
_data.script = in->readUint16LE();
|
|
_data.objectFlags = in->readUint16LE();
|
|
_data.hitPoints = in->readByte();
|
|
_data.bParam = in->readByte();
|
|
_data.massCount = in->readUint16LE();
|
|
_data.missileFacing = in->readByte();
|
|
_data.currentTAG.val = in->readSint16LE();
|
|
_data.sightCtr = in->readByte();
|
|
memset(&_data.reserved, 0, sizeof(_data.reserved));
|
|
|
|
_data.obj = this;
|
|
|
|
debugC(4, kDebugSaveload, "... protoIndex = %d", pInd);
|
|
debugC(4, kDebugSaveload, "... _data.location = (%d, %d, %d)",
|
|
_data.location.u, _data.location.v, _data.location.z);
|
|
debugC(4, kDebugSaveload, "... _data.nameIndex = %d", _data.nameIndex);
|
|
debugC(4, kDebugSaveload, "... _data.parentID = %d", _data.parentID);
|
|
debugC(4, kDebugSaveload, "... _data.siblingID = %d", _data.siblingID);
|
|
debugC(4, kDebugSaveload, "... _data.childID = %d", _data.childID);
|
|
debugC(4, kDebugSaveload, "... _data.script = %d", _data.script);
|
|
debugC(4, kDebugSaveload, "... _data.objectFlags = %d", _data.objectFlags);
|
|
debugC(4, kDebugSaveload, "... _data.hitPoints = %d", _data.hitPoints);
|
|
debugC(4, kDebugSaveload, "... _data.bParam = %d", _data.bParam);
|
|
debugC(4, kDebugSaveload, "... _data.massCount = %d", _data.massCount);
|
|
debugC(4, kDebugSaveload, "... _data.missileFacing = %d", _data.missileFacing);
|
|
debugC(4, kDebugSaveload, "... _data.currentTAG.val = %d", _data.currentTAG.val);
|
|
debugC(4, kDebugSaveload, "... _data.sightCtr = %d", _data.sightCtr);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the number of bytes need to archive this object in an archive
|
|
// buffer.
|
|
|
|
int32 GameObject::archiveSize() {
|
|
return sizeof(GameObjectArchive);
|
|
}
|
|
|
|
void GameObject::write(Common::MemoryWriteStreamDynamic *out, bool expandProto) {
|
|
debugC(2, kDebugSaveload, "Saving object %d", thisID());
|
|
|
|
int16 pInd = prototype != nullptr ? getProtoNum() : -1;
|
|
out->writeSint16LE(pInd);
|
|
if (expandProto)
|
|
out->writeSint16LE(0);
|
|
_data.location.write(out);
|
|
out->writeUint16LE(_data.nameIndex);
|
|
out->writeUint16LE(_data.parentID);
|
|
out->writeUint16LE(_data.siblingID);
|
|
out->writeUint16LE(_data.childID);
|
|
out->writeUint16LE(_data.script);
|
|
out->writeUint16LE(_data.objectFlags);
|
|
out->writeByte(_data.hitPoints);
|
|
out->writeByte(_data.bParam);
|
|
out->writeUint16LE(_data.massCount);
|
|
out->writeByte(_data.missileFacing);
|
|
out->writeSint16LE(_data.currentTAG);
|
|
out->writeByte(_data.sightCtr);
|
|
|
|
debugC(4, kDebugSaveload, "... protoIndex = %d", pInd);
|
|
debugC(4, kDebugSaveload, "... _data.location = (%d, %d, %d)",
|
|
_data.location.u, _data.location.v, _data.location.z);
|
|
debugC(4, kDebugSaveload, "... _data.nameIndex = %d", _data.nameIndex);
|
|
debugC(4, kDebugSaveload, "... _data.parentID = %d", _data.parentID);
|
|
debugC(4, kDebugSaveload, "... _data.siblingID = %d", _data.siblingID);
|
|
debugC(4, kDebugSaveload, "... _data.childID = %d", _data.childID);
|
|
debugC(4, kDebugSaveload, "... _data.script = %d", _data.script);
|
|
debugC(4, kDebugSaveload, "... _data.objectFlags = %d", _data.objectFlags);
|
|
debugC(4, kDebugSaveload, "... _data.hitPoints = %d", _data.hitPoints);
|
|
debugC(4, kDebugSaveload, "... _data.bParam = %d", _data.bParam);
|
|
debugC(4, kDebugSaveload, "... _data.massCount = %d", _data.massCount);
|
|
debugC(4, kDebugSaveload, "... _data.missileFacing = %d", _data.missileFacing);
|
|
debugC(4, kDebugSaveload, "... _data.currentTAG.val = %d", _data.currentTAG.val);
|
|
debugC(4, kDebugSaveload, "... _data.sightCtr = %d", _data.sightCtr);
|
|
}
|
|
|
|
// Same as above but use object addresses instead of ID's
|
|
|
|
bool isObject(GameObject *obj) {
|
|
if (obj == nullptr)
|
|
return false;
|
|
|
|
if (obj->_index >= objectCount)
|
|
return false;
|
|
|
|
return (&objectList[obj->_index] == obj);
|
|
}
|
|
|
|
bool isActor(GameObject *obj) {
|
|
if (obj == nullptr)
|
|
return false;
|
|
|
|
if (obj->_index >= kActorCount + ActorBaseID || obj->_index < ActorBaseID)
|
|
return false;
|
|
|
|
return (g_vm->_act->_actorList[obj->_index - ActorBaseID] == obj);
|
|
}
|
|
|
|
bool isWorld(GameObject *obj) {
|
|
if (obj == nullptr)
|
|
return false;
|
|
|
|
if (obj->_index >= (uint)worldCount + WorldBaseID || obj->_index < WorldBaseID)
|
|
return false;
|
|
|
|
return (&worldList[obj->_index - WorldBaseID] == obj);
|
|
}
|
|
|
|
// returns the address of the object based on the ID, and this
|
|
// includes accounting for actors and worlds.
|
|
|
|
GameObject *GameObject::objectAddress(ObjectID id) {
|
|
if (isObject(id)) {
|
|
if (id >= objectCount)
|
|
error("Invalid object ID: %d", id);
|
|
|
|
return objectList != nullptr ? &objectList[id] : nullptr;
|
|
}
|
|
|
|
if (isWorld(id)) {
|
|
if (id - WorldBaseID >= worldCount)
|
|
error("Invalid object ID: %d", id);
|
|
|
|
return worldList != nullptr ? &worldList[id - WorldBaseID] : nullptr;
|
|
}
|
|
|
|
if (id - ActorBaseID >= kActorCount)
|
|
error("Invalid object ID: %d!", id);
|
|
|
|
return (int)g_vm->_act->_actorList.size() > id - ActorBaseID ? g_vm->_act->_actorList[id - ActorBaseID] : nullptr;
|
|
}
|
|
|
|
ProtoObj *GameObject::protoAddress(ObjectID id) {
|
|
GameObject *obj = objectAddress(id);
|
|
|
|
return obj ? obj->prototype : nullptr ;
|
|
}
|
|
|
|
int32 GameObject::nameIndexToID(uint16 ind) {
|
|
for (int i = 0; i < objectCount; ++i) {
|
|
if (objectList[i]._data.nameIndex == ind)
|
|
return objectList[i].thisID();
|
|
|
|
if (objectList[i].prototype && objectList[i].prototype->nameIndex == ind)
|
|
return objectList[i].thisID();
|
|
}
|
|
|
|
for (int i = 0; i < kActorCount; ++i) {
|
|
if (g_vm->_act->_actorList[i]->_data.nameIndex == ind)
|
|
return g_vm->_act->_actorList[i]->thisID();
|
|
|
|
if (g_vm->_act->_actorList[i]->prototype && g_vm->_act->_actorList[i]->prototype->nameIndex == ind)
|
|
return g_vm->_act->_actorList[i]->thisID();
|
|
}
|
|
|
|
for (int i = 0; i < worldCount; ++i) {
|
|
if (worldList[i]._data.nameIndex == ind)
|
|
return worldList[i].thisID();
|
|
|
|
if (worldList[i].prototype && worldList[i].prototype->nameIndex == ind)
|
|
return worldList[i].thisID();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
Common::Array<ObjectID> GameObject::nameToID(Common::String name) {
|
|
Common::Array<ObjectID> array;
|
|
name.toLowercase();
|
|
|
|
for (int i = 0; i < objectCount; ++i) {
|
|
Common::String objName = objectList[i].objName();
|
|
objName.toLowercase();
|
|
if (objName.contains(name))
|
|
array.push_back(objectList[i].thisID());
|
|
}
|
|
|
|
for (int i = 0; i < kActorCount; ++i) {
|
|
Common::String objName = g_vm->_act->_actorList[i]->objName();
|
|
objName.toLowercase();
|
|
if (objName.contains(name))
|
|
array.push_back(g_vm->_act->_actorList[i]->thisID());
|
|
}
|
|
|
|
for (int i = 0; i < worldCount; ++i) {
|
|
Common::String objName = worldList[i].objName();
|
|
objName.toLowercase();
|
|
if (objName.contains(name))
|
|
array.push_back(worldList[i].thisID());
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
|
|
uint16 GameObject::containmentSet() {
|
|
return prototype->containmentSet();
|
|
}
|
|
|
|
// Calculates the ID of an object, given it's (implicit) address
|
|
|
|
ObjectID GameObject::thisID() { // calculate our own id
|
|
return _index;
|
|
}
|
|
|
|
// Since Worlds have more than one object chain, we need a function
|
|
// to calculate which object chain to use, based on a _data.location.
|
|
// This function returns the address of the appropriate "_data.childID"
|
|
// pointer (i.e. the ObjectID of the first object in the chain
|
|
// of child objects) for any type of object.
|
|
|
|
ObjectID *GameObject::getHeadPtr(ObjectID parentID, TilePoint &l) {
|
|
GameObject *parentObj = objectAddress(parentID);
|
|
|
|
if (isWorld(parentID)) {
|
|
GameWorld *world = (GameWorld *)parentObj;
|
|
TilePoint sectors = world->sectorSize();
|
|
|
|
int16 u = clamp(0, l.u / kSectorSize, sectors.u - 1),
|
|
v = clamp(0, l.v / kSectorSize, sectors.v - 1);
|
|
|
|
return &(world->sectorArray)[
|
|
v * world->sectorArraySize + u].childID;
|
|
} else return &parentObj->_data.childID;
|
|
}
|
|
|
|
// Removes an object from it's chain.
|
|
|
|
void GameObject::remove() { // removes from old list
|
|
ObjectID id = thisID(),
|
|
*headPtr;
|
|
|
|
// If object has not parent, then it's not on a list
|
|
if (_data.parentID == Nothing) return;
|
|
if (id <= ImportantLimbo) return;
|
|
|
|
// Get the head of the object chain. Worlds have more than
|
|
// one, so we need to get the right one.
|
|
headPtr = getHeadPtr(_data.parentID, _data.location);
|
|
|
|
// Search the chain until we find ourself.
|
|
while (*headPtr != id) {
|
|
GameObject *obj;
|
|
|
|
if (*headPtr == Nothing)
|
|
error("Inconsistant Object Chain! ('%s#%d' not on parent %s#%d chain)",
|
|
objName(), id, objectAddress(_data.parentID)->objName(), _data.parentID);
|
|
|
|
obj = objectAddress(*headPtr);
|
|
headPtr = &obj->_data.siblingID;
|
|
}
|
|
|
|
// Remove us from the chain
|
|
*headPtr = _data.siblingID;
|
|
_data.parentID = Nothing;
|
|
}
|
|
|
|
// Add an object to a new chain.
|
|
|
|
void GameObject::append(ObjectID newParent) {
|
|
ObjectID *headPtr;
|
|
|
|
// If object has not parent, then it's not on a list
|
|
if (newParent == Nothing) return;
|
|
|
|
// Get the head of the object chain. Worlds have more than
|
|
// one, so we need to get the right one.
|
|
headPtr = getHeadPtr(newParent, _data.location);
|
|
|
|
// Link us in to the parent's chain
|
|
|
|
_data.parentID = newParent;
|
|
_data.siblingID = *headPtr;
|
|
*headPtr = thisID();
|
|
|
|
}
|
|
|
|
// Inserts an object in a chain immediately after another one
|
|
// This is the fastest method for inserting an object into a
|
|
// chain since the parent's address does not need to be
|
|
// computed.
|
|
|
|
void GameObject::insert(ObjectID newPrev) {
|
|
GameObject *obj = objectAddress(newPrev);
|
|
|
|
// If object has not parent, then it's not on a list
|
|
if (newPrev == Nothing) return;
|
|
|
|
// Link us in to the parent's chain
|
|
_data.siblingID = obj->_data.siblingID;
|
|
obj->_data.siblingID = thisID();
|
|
_data.parentID = obj->_data.parentID;
|
|
}
|
|
|
|
// Returns the identity of the actor possessing the object
|
|
|
|
ObjectID GameObject::possessor() {
|
|
GameObject *obj;
|
|
ObjectID id = _data.parentID;
|
|
|
|
while (id != Nothing && isObject(id)) {
|
|
obj = objectAddress(id);
|
|
id = obj->_data.parentID;
|
|
}
|
|
|
|
return isActor(id) ? id : Nothing ;
|
|
}
|
|
|
|
// A different version of getWorldLocation that fills in a _data.location
|
|
// structure.
|
|
|
|
bool GameObject::getWorldLocation(Location &loc) {
|
|
GameObject *obj = this;
|
|
ObjectID id;
|
|
uint8 objHeight = prototype->height;
|
|
|
|
for (;;) {
|
|
id = obj->_data.parentID;
|
|
if (isWorld(id)) {
|
|
loc = obj->_data.location;
|
|
loc.z += (obj->prototype->height - objHeight) / 2;
|
|
loc.context = id;
|
|
return true;
|
|
} else if (id == Nothing) {
|
|
loc = Nowhere;
|
|
loc.context = Nothing;
|
|
return false;
|
|
}
|
|
|
|
obj = objectAddress(id);
|
|
}
|
|
}
|
|
|
|
Location GameObject::notGetLocation() {
|
|
return Location(getLocation(), IDParent());
|
|
}
|
|
|
|
Location GameObject::notGetWorldLocation() {
|
|
GameObject *obj = this;
|
|
ObjectID id;
|
|
uint8 objHeight = prototype->height;
|
|
|
|
for (;;) {
|
|
id = obj->_data.parentID;
|
|
if (isWorld(id)) {
|
|
TilePoint loc = obj->_data.location;
|
|
|
|
loc.z += (obj->prototype->height - objHeight) / 2;
|
|
return Location(loc, obj->_data.parentID);
|
|
} else if (id == Nothing) return Location(Nowhere, Nothing);
|
|
|
|
obj = objectAddress(id);
|
|
}
|
|
}
|
|
|
|
|
|
void GameObject::objCursorText(char nameBuf[], const int8 size, int16 count) {
|
|
const int addTextSize = 10;
|
|
|
|
// put the object name into the buffer as a default value
|
|
Common::strlcpy(nameBuf, objName(), size);
|
|
|
|
assert(strlen(objName()) < (uint)(size - addTextSize));
|
|
|
|
// check to see if this item is a physical object
|
|
// if so, then give the count of the item ( if stacked )
|
|
if (prototype->containmentSet() & ProtoObj::isTangible) {
|
|
// display charges if item is a chargeable item
|
|
if (prototype->chargeType != 0
|
|
&& prototype->maxCharges != Permanent
|
|
&& _data.bParam != Permanent) {
|
|
uint16 charges = _data.bParam;
|
|
|
|
if (charges == 1) {
|
|
sprintf(nameBuf, SINGLE_CHARGE, objName(), charges);
|
|
} else {
|
|
sprintf(nameBuf, MULTI_CHARGE, objName(), charges); // get the count
|
|
}
|
|
}
|
|
|
|
if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
|
|
// make a buffer that contains the name of
|
|
// the object and it's count
|
|
// add only if a mergable item
|
|
if (_data.massCount != 1) {
|
|
if (count != -1) {
|
|
if (count != 1) {
|
|
sprintf(nameBuf, PLURAL_DESC, count, objName()); // get the count
|
|
}
|
|
} else {
|
|
sprintf(nameBuf, PLURAL_DESC, _data.massCount, objName()); // get the count
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
int16 manaColor = -1;
|
|
int16 manaCost = 0;
|
|
|
|
// figure out if it's a skill or spell
|
|
if (prototype->containmentSet() & (ProtoObj::isSkill | ProtoObj::isSpell)) {
|
|
// get skill proto for this spell or skill
|
|
SkillProto *sProto = skillProtoFromID(thisID());
|
|
|
|
// determine if this is a skill icon
|
|
manaColor = spellBook[sProto->getSpellID()].getManaType();
|
|
manaCost = spellBook[sProto->getSpellID()].getManaAmt();
|
|
}
|
|
|
|
if (manaColor == sManaIDSkill) { // It's a skill
|
|
// get the level of the skill for the brother in question
|
|
uint16 brotherID = getCenterActor()->thisID();
|
|
uint16 level;
|
|
|
|
// get skill prototype for this spell or skill
|
|
SkillProto *sProto = skillProtoFromID(thisID());
|
|
|
|
// check to make sure this is a brother
|
|
if (brotherID == ActorBaseID + FTA_JULIAN ||
|
|
brotherID == ActorBaseID + FTA_PHILIP ||
|
|
brotherID == ActorBaseID + FTA_KEVIN) {
|
|
// get base 0 level
|
|
level = g_vm->_playerList[brotherID - ActorBaseID]->getSkillLevel(sProto);
|
|
|
|
// normalize and output
|
|
sprintf(nameBuf, "%s-%d", objName(), ++level);
|
|
}
|
|
} else if (manaColor >= sManaIDRed
|
|
&& manaColor <= sManaIDViolet // A spell
|
|
&& manaCost > 0) {
|
|
ObjectID aID = possessor(); // Who owns the spell
|
|
PlayerActorID pID;
|
|
|
|
if (actorIDToPlayerID(aID, pID)) {
|
|
PlayerActor *player = getPlayerActorAddress(pID);
|
|
assert(player);
|
|
|
|
int16 manaAmount;
|
|
|
|
manaAmount = player->getEffStats()->mana(manaColor);
|
|
|
|
sprintf(nameBuf, "%s [x%d]", objName(), manaAmount / manaCost);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GameObject::isTrueSkill() {
|
|
// figure out if it's a skill or spell
|
|
if (prototype->containmentSet() & (ProtoObj::isSkill | ProtoObj::isSpell)) {
|
|
// get skill proto for this spell or skill
|
|
SkillProto *sProto = skillProtoFromID(thisID());
|
|
|
|
// determine if this is a skill icon
|
|
if (spellBook[sProto->getSpellID()].getManaType() == sManaIDSkill) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Returns the _data.location of an object within the world
|
|
TilePoint GameObject::getWorldLocation() {
|
|
GameObject *obj = this;
|
|
ObjectID id;
|
|
uint8 objHeight = prototype->height;
|
|
|
|
for (;;) {
|
|
id = obj->_data.parentID;
|
|
if (isWorld(id)) {
|
|
TilePoint loc = obj->_data.location;
|
|
|
|
loc.z += (obj->prototype->height - objHeight) / 2;
|
|
return loc;
|
|
} else if (id == Nothing) return Nowhere;
|
|
|
|
obj = objectAddress(id);
|
|
}
|
|
}
|
|
|
|
// Return a pointer to the world on which this object resides
|
|
GameWorld *GameObject::world() {
|
|
if (isWorld(this)) return (GameWorld *)this;
|
|
|
|
GameObject *obj = this;
|
|
ObjectID id;
|
|
|
|
for (;;) {
|
|
id = obj->_data.parentID;
|
|
if (isWorld(id)) return &worldList[id - WorldBaseID];
|
|
else if (id == Nothing) return nullptr;
|
|
|
|
obj = objectAddress(id);
|
|
}
|
|
}
|
|
|
|
|
|
int32 GameObject::getSprOffset(int16 num) {
|
|
// sprite offset delimiters for mergeable objects
|
|
enum spriteDelimiters {
|
|
spriteNumFew = 2,
|
|
spriteNumSome = 10,
|
|
spriteNumMany = 25
|
|
};
|
|
|
|
// default return offset is zero ( no change )
|
|
int32 value = 0;
|
|
int32 units;
|
|
|
|
if (num != -1) {
|
|
units = (int32)num;
|
|
} else {
|
|
units = (int32)_data.massCount;
|
|
}
|
|
|
|
// if this is a mergeable object
|
|
if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
|
|
if (units >= spriteNumFew) {
|
|
value = 1;
|
|
}
|
|
|
|
if (units >= spriteNumSome) {
|
|
value = 2;
|
|
}
|
|
|
|
if (units >= spriteNumMany) {
|
|
value = 3;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// Remove an object from a stack of objects
|
|
bool GameObject::unstack() {
|
|
GameObject *item = nullptr,
|
|
*base = nullptr,
|
|
*zero = nullptr;
|
|
int16 count = 0;
|
|
|
|
// If this is a world, or it's parent object is a world, or it is
|
|
// an intangible object, then it cannot be stacked so ignore. If it's
|
|
// Z-coord == 1 then it is not stacked now.
|
|
if (isWorld(this)
|
|
|| isWorld(parent())
|
|
|| IDParent() == Nothing
|
|
|| _data.location.z == 1
|
|
|| prototype == nullptr
|
|
|| (prototype->containmentSet() & ProtoObj::isIntangible)) return false;
|
|
|
|
ContainerIterator iter(parent());
|
|
|
|
// Iterate through all the objects in the container.
|
|
// Count how many objects are in this stack
|
|
// Also, check to find the base item, and any non-base item
|
|
while (iter.next(&item) != Nothing) {
|
|
if (item->_data.location.u == _data.location.u
|
|
&& item->_data.location.v == _data.location.v
|
|
&& item->prototype == prototype) {
|
|
count++;
|
|
if (item->_data.location.z != 0) base = item;
|
|
else zero = item;
|
|
}
|
|
}
|
|
|
|
// If this object is the base item, and there is another item which
|
|
// can become the base item, then make this other item the new base
|
|
// item by transferring all but one of the count to the new base.
|
|
//
|
|
// Else if this item is not the base item, then decrement the base
|
|
// item's count by setting it to all but one of the count of items.
|
|
if (this == base && zero != nullptr)
|
|
zero->_data.location.z = count - 1;
|
|
else if (base != nullptr) base->_data.location.z = count - 1;
|
|
|
|
// Set this item's count to 1
|
|
_data.location.z = 1;
|
|
return true;
|
|
}
|
|
|
|
// Move the object to a new _data.location, and change context if needed.
|
|
void GameObject::setLocation(const Location &l) {
|
|
if (l.context != _data.parentID) {
|
|
unstack(); // if it's in a stack, unstack it.
|
|
remove(); // remove from old list
|
|
_data.location = (TilePoint)l; // change _data.location
|
|
append(l.context); // append to new list
|
|
} else if (isWorld(l.context)) {
|
|
GameWorld *world = (GameWorld *)objectAddress(l.context);
|
|
TilePoint sectors = world->sectorSize();
|
|
|
|
int16 u0 = clamp(0, _data.location.u / kSectorSize, sectors.u - 1),
|
|
v0 = clamp(0, _data.location.v / kSectorSize, sectors.v - 1),
|
|
u1 = clamp(0, l.u / kSectorSize, sectors.u - 1),
|
|
v1 = clamp(0, l.v / kSectorSize, sectors.v - 1);
|
|
|
|
if (u0 != u1 || v0 != v1) { // If sector changed
|
|
remove(); // Remove from old list
|
|
_data.location = (TilePoint)l; // Set object coords
|
|
append(l.context); // append to appropriate list
|
|
} else {
|
|
_data.location = (TilePoint)l; // Set object coords
|
|
}
|
|
} else {
|
|
unstack(); // if it's in a stack, unstack it.
|
|
_data.location = (TilePoint)l; // Set object coords
|
|
}
|
|
}
|
|
|
|
// Move the object without changing worlds...
|
|
void GameObject::setLocation(const TilePoint &tp) {
|
|
if (isWorld(_data.parentID)) {
|
|
GameWorld *world = (GameWorld *)objectAddress(_data.parentID);
|
|
TilePoint sectors = world->sectorSize();
|
|
|
|
int16 u0 = clamp(0, _data.location.u / kSectorSize, sectors.u - 1),
|
|
v0 = clamp(0, _data.location.v / kSectorSize, sectors.v - 1),
|
|
u1 = clamp(0, tp.u / kSectorSize, sectors.u - 1),
|
|
v1 = clamp(0, tp.v / kSectorSize, sectors.v - 1);
|
|
|
|
if (u0 != u1 || v0 != v1) { // If sector changed
|
|
ObjectID saveParent = _data.parentID;
|
|
|
|
remove(); // Remove from old list
|
|
_data.location = tp; // Set object coords
|
|
_data.parentID = saveParent; // restore parent (cleared by remove())
|
|
append(_data.parentID); // append to appropriate list
|
|
} else {
|
|
_data.location = tp; // Set object coords
|
|
}
|
|
} else {
|
|
_data.location = tp; // Set object coords
|
|
}
|
|
}
|
|
|
|
void GameObject::move(const Location &location) {
|
|
// move as usual
|
|
ObjectID oldParentID = _data.parentID;
|
|
|
|
setLocation(location);
|
|
|
|
updateImage(oldParentID);
|
|
}
|
|
|
|
void GameObject::move(const Location &location, int16 num) {
|
|
// if this object is merged or stacked
|
|
// with others, check for their
|
|
// moves ( or copies ) first.
|
|
if (!moveMerged(location, num)) {
|
|
// do a normal move
|
|
move(location);
|
|
} else {
|
|
ObjectID oldParentID = _data.parentID;
|
|
|
|
// update panel after move
|
|
updateImage(oldParentID);
|
|
|
|
// update the ready containers
|
|
updateReadyContainers();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
int16 GameObject::getChargeType() {
|
|
assert(prototype);
|
|
|
|
return prototype->getChargeType();
|
|
}
|
|
|
|
// this function recharges an object
|
|
void GameObject::recharge() {
|
|
// if this object has a charge type
|
|
// other then none, then reset
|
|
// it's charges to maximum
|
|
if (getChargeType()) {
|
|
ProtoObj *po = GameObject::protoAddress(thisID());
|
|
assert(po);
|
|
_data.bParam = po->maxCharges;
|
|
}
|
|
}
|
|
|
|
// take a charge
|
|
bool GameObject::deductCharge(ActorManaID manaID, uint16 manaCost) {
|
|
ProtoObj *po = GameObject::protoAddress(thisID());
|
|
assert(po);
|
|
|
|
// if this is not a chargeable item, then return false
|
|
if (!getChargeType()) {
|
|
return false;
|
|
}
|
|
|
|
if (po->maxCharges == Permanent || _data.bParam == Permanent) {
|
|
return true;
|
|
}
|
|
|
|
if (po->maxCharges == 0) {
|
|
GameObject *parentObj = parent();
|
|
|
|
if (isActor(parentObj)) {
|
|
return ((Actor *)parentObj)->takeMana(manaID, manaCost);
|
|
}
|
|
}
|
|
|
|
if (_data.bParam == 0) {
|
|
// not enough mana to use item
|
|
return false;
|
|
}
|
|
|
|
if (_data.bParam > 0 && _data.bParam < Permanent) {
|
|
_data.bParam--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GameObject::hasCharge(ActorManaID manaID, uint16 manaCost) {
|
|
ProtoObj *po = GameObject::protoAddress(thisID());
|
|
assert(po);
|
|
|
|
// if this is not a chargeable item, then return false
|
|
if (!getChargeType()) {
|
|
return false;
|
|
}
|
|
|
|
if (_data.bParam == Permanent) {
|
|
return true;
|
|
}
|
|
|
|
if (po->maxCharges == 0) {
|
|
GameObject *parentObj = parent();
|
|
|
|
if (isActor(parentObj)) {
|
|
return ((Actor *)parentObj)->hasMana(manaID, manaCost);
|
|
}
|
|
}
|
|
|
|
if (_data.bParam == 0) {
|
|
// not enough mana to use item
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void GameObject::move(const TilePoint &tilePoint) {
|
|
// I'm making the assumption that only objects in containers
|
|
// can be merged or stacked
|
|
// move as usual
|
|
ObjectID oldParentID = _data.parentID;
|
|
|
|
setLocation(tilePoint);
|
|
|
|
updateImage(oldParentID);
|
|
}
|
|
|
|
void GameObject::updateImage(ObjectID oldParentID) {
|
|
GameObject *parent,
|
|
*oldParent;
|
|
|
|
parent = objectAddress(_data.parentID);
|
|
oldParent = objectAddress(oldParentID);
|
|
|
|
if ((isActor(oldParentID)
|
|
&& isPlayerActor((Actor *)oldParent))
|
|
|| (isObject(oldParentID)
|
|
&& oldParent->isOpen())) {
|
|
g_vm->_cnm->setUpdate(oldParentID);
|
|
}
|
|
|
|
if (_data.parentID != oldParentID && isActor(oldParentID)) {
|
|
ObjectID id = thisID();
|
|
Actor *a = (Actor *)oldParent;
|
|
int i;
|
|
|
|
if (a->_leftHandObject == id)
|
|
a->_leftHandObject = Nothing;
|
|
else if (a->_rightHandObject == id)
|
|
a->_rightHandObject = Nothing;
|
|
|
|
for (i = 0; i < ARMOR_COUNT; i++) {
|
|
if (a->_armorObjects[i] == id) {
|
|
a->wear(Nothing, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isWorld(_data.parentID)) {
|
|
GameWorld *w = world();
|
|
Sector *sect;
|
|
|
|
if (!isMoving()) {
|
|
if (objObscured(this)) {
|
|
_data.objectFlags |= objectObscured;
|
|
} else {
|
|
_data.objectFlags &= ~objectObscured;
|
|
}
|
|
}
|
|
int u = _data.location.u >> kSectorShift;
|
|
int v = _data.location.v >> kSectorShift;
|
|
|
|
sect = w->getSector(u, v);
|
|
if (sect) {
|
|
if (sect->isActivated())
|
|
activate();
|
|
}
|
|
else
|
|
warning("GameObject::updateImage: Invalid Sector (%d, %d))", u, v);
|
|
} else {
|
|
_data.objectFlags &= ~objectObscured;
|
|
|
|
if ((isActor(_data.parentID)
|
|
&& isPlayerActor((Actor *)parent))
|
|
|| (isObject(_data.parentID) && parent->isOpen())
|
|
) {
|
|
g_vm->_cnm->setUpdate(_data.parentID);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GameObject::moveMerged(const Location &loc, int16 num) {
|
|
if (num < _data.massCount
|
|
&& !extractMerged(Location(_data.location, _data.parentID), _data.massCount - num))
|
|
return false;
|
|
move(loc);
|
|
return true;
|
|
}
|
|
|
|
|
|
// Extract a merged object with specified merge number from another
|
|
// merged object and return its ID
|
|
ObjectID GameObject::extractMerged(const Location &loc, int16 num) {
|
|
ObjectID extractedID;
|
|
|
|
// determine whether this object can be merged
|
|
// with duplicates of it's kind
|
|
if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
|
|
// get the number requested or all that's there...
|
|
int16 moveCount = MIN<uint16>(num, _data.massCount);
|
|
|
|
// make a new pile with that many items in it.
|
|
if ((extractedID = copy(loc, moveCount)) != Nothing) {
|
|
// and subtract that amount from the currect pile
|
|
_data.massCount -= moveCount;
|
|
|
|
// delete object if count goes to zero
|
|
if (_data.massCount == 0) {
|
|
this->deleteObject();
|
|
}
|
|
} else
|
|
return Nothing;
|
|
} else {
|
|
// not a mergable object return unsuccess
|
|
return Nothing;
|
|
}
|
|
|
|
return extractedID;
|
|
}
|
|
|
|
GameObject *GameObject::extractMerged(int16 num) {
|
|
ObjectID extractedID;
|
|
|
|
// determine whether this object can be merged
|
|
// with duplicates of it's kind
|
|
if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
|
|
Location loc(0, 0, 0, 0);
|
|
|
|
// get the number requested or all that's there...
|
|
int16 moveCount = MIN<uint16>(num, _data.massCount);
|
|
|
|
// make a new pile with that many items in it.
|
|
if ((extractedID = copy(loc, moveCount)) != Nothing) {
|
|
// and subtract that amount from the currect pile
|
|
_data.massCount -= moveCount;
|
|
|
|
// delete object if count goes to zero
|
|
if (_data.massCount == 0) {
|
|
this->deleteObject();
|
|
}
|
|
} else
|
|
return nullptr;
|
|
} else {
|
|
// not a mergable object return unsuccess
|
|
return nullptr;
|
|
}
|
|
|
|
return GameObject::objectAddress(extractedID);
|
|
}
|
|
|
|
// Move the object to a new random _data.location
|
|
void GameObject::moveRandom(const TilePoint &minLoc, const TilePoint &maxLoc) {
|
|
//We Should Also Send Flags For Conditional Movements
|
|
//One Consideration Is Whether We Should Get One Random Location
|
|
//And Poke Around That Location If We Cant Go There Or Try Another
|
|
//Random Location ???
|
|
|
|
TilePoint newLoc;
|
|
const int maxMoveAttempts = 1;//This Is Max Tries If Conditional Move
|
|
|
|
for (int i = 0; i < maxMoveAttempts; i++) {
|
|
newLoc.u = GetRandomBetween(minLoc.u, maxLoc.u);
|
|
newLoc.v = GetRandomBetween(minLoc.v, maxLoc.v);
|
|
newLoc.z = _data.location.z; //For Now Keep Z Coord Same
|
|
//If Flags == Collision Check
|
|
if (objectCollision(this, world(), newLoc) == nullptr) { //If No Collision
|
|
move(newLoc);//Move It Else Try Again
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// this will need another method to let it know about multiple
|
|
// object moves.
|
|
// Copy the object to a new _data.location.
|
|
ObjectID GameObject::copy(const Location &l) {
|
|
GameObject *newObj;
|
|
// ObjectID id = thisID();
|
|
|
|
if (isWorld(this))
|
|
error("World copying not allowed.\n");
|
|
|
|
if (isActor(this)) {
|
|
// newObj = newActor();
|
|
// newObj->move( l );
|
|
// REM: Call actor copy function...
|
|
|
|
error("Actor copying not yet implemented.\n");
|
|
} else {
|
|
if ((newObj = newObject()) == nullptr) return Nothing;
|
|
|
|
newObj->prototype = prototype;
|
|
newObj->_data.nameIndex = _data.nameIndex;
|
|
newObj->_data.script = _data.script;
|
|
newObj->_data.objectFlags = _data.objectFlags;
|
|
newObj->_data.hitPoints = _data.hitPoints;
|
|
newObj->_data.massCount = _data.massCount;
|
|
newObj->_data.bParam = _data.bParam;
|
|
newObj->_data.missileFacing = _data.missileFacing;
|
|
newObj->_data.currentTAG = _data.currentTAG;
|
|
|
|
newObj->move(l); //>>> could this cause the same problem as below?
|
|
}
|
|
|
|
return newObj->thisID();
|
|
}
|
|
|
|
ObjectID GameObject::copy(const Location &l, int16 num) {
|
|
GameObject *newObj;
|
|
|
|
if (isWorld(this))
|
|
error("World copying not allowed.");
|
|
|
|
if (isActor(this)) {
|
|
error("Actor copying not yet implemented.");
|
|
} else {
|
|
if ((newObj = newObject()) == nullptr) return Nothing;
|
|
|
|
|
|
newObj->prototype = prototype;
|
|
newObj->_data.nameIndex = _data.nameIndex;
|
|
newObj->_data.script = _data.script;
|
|
newObj->_data.objectFlags = _data.objectFlags;
|
|
newObj->_data.hitPoints = _data.hitPoints;
|
|
newObj->_data.massCount = num;
|
|
|
|
// this did occur before any of the assignments
|
|
// but that caused a crash when the it tried to update
|
|
// the image during the move and tried to access the prototype
|
|
// pointer field, which is set to nullptr after a newObject()
|
|
newObj->move(l);
|
|
}
|
|
|
|
return newObj->thisID();
|
|
}
|
|
|
|
// Create an alias of this object
|
|
ObjectID GameObject::makeAlias(const Location &l) {
|
|
ObjectID newObjID = copy(l);
|
|
|
|
if (newObjID != Nothing) {
|
|
GameObject *newObject = objectAddress(newObjID);
|
|
|
|
newObject->_data.objectFlags |= objectAlias;
|
|
}
|
|
|
|
return newObjID;
|
|
}
|
|
|
|
// Creates a new object (if one is available), and
|
|
// return it's address
|
|
GameObject *GameObject::newObject() { // get a newly created object
|
|
GameObject *limbo = objectAddress(ObjectLimbo),
|
|
*obj = nullptr;
|
|
|
|
if (limbo->_data.childID == Nothing) {
|
|
int16 i;
|
|
|
|
// Search object list for the first scavengable object we can find
|
|
for (i = ImportantLimbo + 1; i < objectCount; i++) {
|
|
obj = &objectList[i];
|
|
|
|
if (obj->isScavengable()
|
|
&& !obj->isActivated()
|
|
&& isWorld(obj->IDParent()))
|
|
break;
|
|
}
|
|
|
|
// REM: If things start getting really tight, we can
|
|
// start recycling common objects...
|
|
|
|
if (i >= objectCount)
|
|
return nullptr;
|
|
} else {
|
|
objectLimboCount--;
|
|
obj = limbo->child();
|
|
}
|
|
|
|
obj->remove();
|
|
obj->prototype = nullptr;
|
|
obj->_data.nameIndex = 0;
|
|
obj->_data.script = 0;
|
|
obj->_data.objectFlags = 0;
|
|
obj->_data.hitPoints = 0;
|
|
obj->_data.massCount = 0;
|
|
obj->_data.bParam = 0;
|
|
obj->_data.missileFacing = 0;
|
|
obj->_data.currentTAG = NoActiveItem;
|
|
|
|
return obj;
|
|
}
|
|
|
|
// Deletes an object by adding it to either the actor limbo list
|
|
// or the object limbo list.
|
|
|
|
void GameObject::deleteObject() {
|
|
ObjectID dObj = thisID();
|
|
scriptCallFrame scf;
|
|
ContainerNode *cn;
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_GameObject_onDeletion, scf);
|
|
|
|
// Move objects to appropriate junkyards.
|
|
// Actors --> actor limbo
|
|
// Important objects --> important limbo
|
|
// Normal Objects --> object limbo
|
|
|
|
// Remove all timers and sensors
|
|
removeAllTimers();
|
|
removeAllSensors();
|
|
|
|
// Delete any container nodes for this object
|
|
while ((cn = g_vm->_cnm->find(dObj)) != nullptr)
|
|
delete cn;
|
|
|
|
if (isActor(_data.parentID)) {
|
|
ObjectID id = thisID();
|
|
Actor *a = (Actor *)objectAddress(_data.parentID);
|
|
int i;
|
|
|
|
if (a->_leftHandObject == id) a->_leftHandObject = Nothing;
|
|
if (a->_rightHandObject == id) a->_rightHandObject = Nothing;
|
|
|
|
for (i = 0; i < ARRAYSIZE(a->_armorObjects); i++)
|
|
if (a->_armorObjects[i] == id)
|
|
a->wear(Nothing, i);
|
|
}
|
|
|
|
unstack();
|
|
|
|
if (g_vm->_mouseInfo->getObject() == this)
|
|
g_vm->_mouseInfo->replaceObject();
|
|
if (pickedObject == thisID())
|
|
pickedObject = Nothing;
|
|
|
|
remove();
|
|
|
|
if (isActor(this))
|
|
((Actor *)this)->deleteActor();
|
|
else if (_data.objectFlags & objectImportant) {
|
|
append(ImportantLimbo);
|
|
_data.parentID = ImportantLimbo;
|
|
importantLimboCount++;
|
|
} else if (!(_data.objectFlags & objectNoRecycle)) {
|
|
append(ObjectLimbo);
|
|
_data.parentID = ObjectLimbo;
|
|
objectLimboCount++;
|
|
} else
|
|
_data.parentID = Nothing;
|
|
}
|
|
|
|
// Delete this object and every object it contains
|
|
|
|
void GameObject::deleteObjectRecursive() {
|
|
// If this is an important object let's not delete it but try to drop
|
|
// it on the ground instead.
|
|
if (isImportant()) {
|
|
assert((prototype->containmentSet() & ProtoObj::isTangible) != 0);
|
|
|
|
// If the object is already in a world there's nothing to do.
|
|
if (isWorld(_data.parentID))
|
|
return;
|
|
else {
|
|
ObjectID ancestorID = _data.parentID;
|
|
|
|
// Search up the parent chain
|
|
while (ancestorID > ImportantLimbo) {
|
|
GameObject *ancestor = objectAddress(ancestorID);
|
|
|
|
// If this ancestor is in a world, drop the object and
|
|
// we're done.
|
|
if (isWorld(ancestor->_data.parentID)) {
|
|
ancestor->dropInventoryObject(
|
|
this,
|
|
isMergeable()
|
|
? _data.massCount
|
|
: 1);
|
|
return;
|
|
}
|
|
|
|
ancestorID = ancestor->_data.parentID;
|
|
}
|
|
}
|
|
}
|
|
// The object is not important so recursively call this function
|
|
// for all of its children.
|
|
else {
|
|
if (_data.childID != Nothing) {
|
|
GameObject *childObj,
|
|
*nextChildObj;
|
|
|
|
for (childObj = objectAddress(_data.childID);
|
|
childObj != nullptr;
|
|
childObj = nextChildObj) {
|
|
nextChildObj = childObj->_data.siblingID != Nothing
|
|
? objectAddress(childObj->_data.siblingID)
|
|
: nullptr;
|
|
childObj->deleteObjectRecursive();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do the dirty deed.
|
|
deleteObject();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Activate this object
|
|
|
|
void GameObject::activate() {
|
|
if (_data.objectFlags & objectActivated)
|
|
return;
|
|
|
|
debugC(1, kDebugActors, "GameObject::activate %d (%s)", thisID(), objName());
|
|
|
|
ObjectID dObj = thisID();
|
|
scriptCallFrame scf;
|
|
|
|
_data.objectFlags |= objectActivated;
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_GameObject_onActivate, scf);
|
|
|
|
|
|
|
|
if (isActor(this)) {
|
|
((Actor *)this)->activateActor();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Deactivate this object
|
|
|
|
void GameObject::deactivate() {
|
|
if (!(_data.objectFlags & objectActivated))
|
|
return;
|
|
|
|
debugC(1, kDebugActors, "GameObject::deactivate %d (%s)", thisID(), objName());
|
|
|
|
ObjectID dObj = thisID();
|
|
scriptCallFrame scf;
|
|
|
|
// Clear activated flag
|
|
_data.objectFlags &= ~objectActivated;
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_GameObject_onDeactivate, scf);
|
|
|
|
// Remove all timers and sensors
|
|
removeAllTimers();
|
|
removeAllSensors();
|
|
|
|
if (isActor(this))
|
|
((Actor *)this)->deactivateActor();
|
|
}
|
|
|
|
// Determine if an object is contained in this object
|
|
bool GameObject::isContaining(GameObject *item) {
|
|
ContainerIterator iter(this);
|
|
GameObject *containedObj = nullptr;
|
|
|
|
while (iter.next(&containedObj) != Nothing) {
|
|
if (containedObj == item) return true;
|
|
|
|
if (containedObj->_data.childID != Nothing)
|
|
if (containedObj->isContaining(item)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Determine if an instance of the specified target is contained in this
|
|
// object
|
|
bool GameObject::isContaining(ObjectTarget *objTarget) {
|
|
ContainerIterator iter(this);
|
|
GameObject *containedObj;
|
|
|
|
while (iter.next(&containedObj) != Nothing) {
|
|
if (objTarget->isTarget(containedObj)) return true;
|
|
|
|
if (containedObj->_data.childID != Nothing)
|
|
if (containedObj->isContaining(objTarget)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const int32 harmfulTerrain = terrainHot | terrainCold | terrainIce | terrainSlash | terrainBash;
|
|
|
|
void GameObject::updateState() {
|
|
int16 tHeight;
|
|
static TilePoint nullVelocity(0, 0, 0);
|
|
StandingTileInfo sti;
|
|
|
|
tHeight = tileSlopeHeight(_data.location, this, &sti);
|
|
|
|
if (!(_data.location.z >= 0 || prototype->height > 8 - _data.location.z))
|
|
drown(this);
|
|
|
|
TilePoint subTile((_data.location.u >> kSubTileShift) & kSubTileMask,
|
|
(_data.location.v >> kSubTileShift) & kSubTileMask,
|
|
0);
|
|
|
|
|
|
int32 subTileTerrain =
|
|
sti.surfaceTile != nullptr
|
|
? sti.surfaceTile->attrs.testTerrain(calcSubTileMask(subTile.u,
|
|
subTile.v))
|
|
: 0;
|
|
|
|
if (isActor(this) && 0 != (subTileTerrain & harmfulTerrain)) {
|
|
if (subTileTerrain & terrainHot)
|
|
lavaDamage(this);
|
|
if (subTileTerrain & (terrainCold | terrainIce))
|
|
coldDamage(this);
|
|
if (subTileTerrain & terrainSlash)
|
|
terrainDamageSlash(this);
|
|
if (subTileTerrain & terrainBash)
|
|
terrainDamageBash(this);
|
|
}
|
|
// If terrain is HIGHER (or even sligtly lower) than we are
|
|
// currently at, then raise us up a bit.
|
|
if (isMoving()) return;
|
|
|
|
if (_data.objectFlags & objectFloating) return;
|
|
|
|
if (tHeight > _data.location.z + kMaxStepHeight) {
|
|
unstickObject(this);
|
|
tHeight = tileSlopeHeight(_data.location, this, &sti);
|
|
}
|
|
if (tHeight >= _data.location.z - gravity * 4) {
|
|
setObjectSurface(this, sti);
|
|
_data.location.z = tHeight;
|
|
return;
|
|
}
|
|
|
|
// We're due for a fall, I think...
|
|
MotionTask::throwObject(*this, nullVelocity);
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
Object Names
|
|
* ======================================================================= */
|
|
|
|
const char *GameObject::nameText(uint16 index) {
|
|
if (index >= nameListCount)
|
|
return "Bad Name Index";
|
|
|
|
return g_vm->_nameList[index];
|
|
}
|
|
|
|
#define INTANGIBLE_MASK (ProtoObj::isEnchantment|ProtoObj::isSpell|ProtoObj::isSkill)
|
|
|
|
TilePoint GameObject::getFirstEmptySlot(GameObject *obj) {
|
|
ObjectID objID;
|
|
GameObject *item = nullptr;
|
|
TilePoint newLoc, temp;
|
|
uint16 numRows = prototype->getMaxRows(),
|
|
numCols = prototype->getMaxCols();
|
|
ProtoObj *mObjProto = obj->proto();
|
|
bool objIsEnchantment = (mObjProto->containmentSet() & INTANGIBLE_MASK);
|
|
bool isReadyCont = isActor(this);
|
|
|
|
// Enchantments don't follow the normal rules for row count, since container type
|
|
// is also used for ready containers which are 3x3
|
|
if (objIsEnchantment) numRows = 20;
|
|
|
|
ContainerIterator iter(this);
|
|
|
|
//This Is The Largest The Row Column Can Be
|
|
static bool slotTable[maxRow][maxCol];
|
|
|
|
memset(&slotTable, '\0', sizeof(slotTable)); //Initialize Table To false
|
|
|
|
// Iterate through all the objects in the container.
|
|
// Set The Filled Spots To True In Table
|
|
while ((objID = iter.next(&item)) != Nothing) {
|
|
ProtoObj *cObjProto = item->proto();
|
|
|
|
// If we are dropping an enchantment then don't consider non-enchantments
|
|
// and vice-versa. Extra '!' are to force boolean-ness.
|
|
|
|
if (!isReadyCont &&
|
|
!((cObjProto->containmentSet() & INTANGIBLE_MASK) != !objIsEnchantment))
|
|
continue;
|
|
|
|
temp = item->getLocation();
|
|
|
|
//Verify Not Writing Outside Array
|
|
if (temp.u >= 0 && temp.v >= 0 && temp.u < numRows && temp.v < numCols) {
|
|
slotTable[temp.u][temp.v] = true;
|
|
}
|
|
}
|
|
|
|
//Go Through Table Until Find A false and Return That Value
|
|
for (int16 u = 0; u < numRows; u++) {
|
|
for (int16 v = 0; v < numCols; v++) {
|
|
if (!slotTable[u][v]) {
|
|
newLoc.v = v;
|
|
newLoc.u = u;
|
|
newLoc.z = 1;
|
|
return (newLoc);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Nowhere;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the _data.location of the first available slot within this object
|
|
// in which to place the specified object
|
|
|
|
bool GameObject::getAvailableSlot(
|
|
GameObject *obj,
|
|
TilePoint *tp,
|
|
bool canMerge,
|
|
GameObject **mergeObj) {
|
|
assert(isObject(obj));
|
|
assert(tp != nullptr);
|
|
assert(!canMerge || mergeObj != nullptr);
|
|
|
|
if (prototype == nullptr) return false;
|
|
|
|
ProtoObj *objProto = obj->proto();
|
|
|
|
if (canMerge) *mergeObj = nullptr;
|
|
|
|
// Determine if the specified object is an intagible container
|
|
if ((objProto->containmentSet()
|
|
& (ProtoObj::isContainer | ProtoObj::isIntangible))
|
|
== (ProtoObj::isContainer | ProtoObj::isIntangible)) {
|
|
// assert( isActor( obj ) );
|
|
|
|
// Set intangible container _data.locations to -1, -1.
|
|
tp->u = -1;
|
|
tp->v = -1;
|
|
return true;
|
|
}
|
|
|
|
// Only actors or containers may contain other objects
|
|
if (isActor(this)
|
|
|| (prototype->containmentSet() & ProtoObj::isContainer)) {
|
|
TilePoint firstEmptySlot;
|
|
|
|
if (canMerge) {
|
|
GameObject *inventoryObj = nullptr;
|
|
ContainerIterator iter(this);
|
|
|
|
// Iterate through the objects in this container
|
|
while (iter.next(&inventoryObj) != Nothing) {
|
|
if (canStackOrMerge(obj, inventoryObj)
|
|
!= cannotStackOrMerge) {
|
|
*tp = inventoryObj->getLocation();
|
|
*mergeObj = inventoryObj;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nothing to merge with, so get an empty slot
|
|
if ((firstEmptySlot = getFirstEmptySlot(obj)) != Nowhere) {
|
|
*tp = firstEmptySlot;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Find a slot to place the specified object within this object and
|
|
// drop it in that slot
|
|
// If merge count == 0, then no auto-merging allowed
|
|
|
|
bool GameObject::placeObject(
|
|
ObjectID enactor,
|
|
ObjectID objID,
|
|
bool canMerge,
|
|
int16 num) {
|
|
assert(isActor(enactor));
|
|
assert(isObject(objID));
|
|
|
|
TilePoint slot;
|
|
GameObject *obj = GameObject::objectAddress(objID),
|
|
*mergeObj;
|
|
|
|
if (getAvailableSlot(obj, &slot, canMerge, &mergeObj)) {
|
|
if (canMerge && mergeObj != nullptr)
|
|
return obj->dropOn(enactor, mergeObj->thisID(), num);
|
|
else
|
|
return obj->drop(enactor, Location(slot, thisID()), num);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Drop the specified object on the ground in a semi-random _data.location
|
|
|
|
void GameObject::dropInventoryObject(GameObject *obj, int16 count) {
|
|
assert(isWorld(_data.parentID));
|
|
|
|
int16 dist;
|
|
int16 mapNum = getMapNum();
|
|
|
|
dist = prototype->crossSection + obj->proto()->crossSection;
|
|
|
|
// Iterate until the object is placed
|
|
for (;;) {
|
|
Direction startDir,
|
|
dir;
|
|
|
|
startDir = dir = g_vm->_rnd->getRandomNumber(7);
|
|
|
|
do {
|
|
TilePoint probeLoc;
|
|
StandingTileInfo sti;
|
|
|
|
// Compute a _data.location to place the object
|
|
probeLoc = _data.location + incDirTable[dir] * dist;
|
|
probeLoc.u += g_vm->_rnd->getRandomNumber(3) - 2;
|
|
probeLoc.v += g_vm->_rnd->getRandomNumber(3) - 2;
|
|
probeLoc.z = tileSlopeHeight(probeLoc, mapNum, obj, &sti);
|
|
|
|
// If _data.location is not blocked, drop the object
|
|
if (checkBlocked(obj, mapNum, probeLoc) == blockageNone) {
|
|
// If we're dropping the object on a TAI, make sure
|
|
// we call the correct drop function
|
|
if (sti.surfaceTAG == nullptr) {
|
|
obj->drop(
|
|
thisID(),
|
|
Location(probeLoc, _data.parentID),
|
|
count);
|
|
} else {
|
|
obj->dropOn(
|
|
thisID(),
|
|
sti.surfaceTAG,
|
|
Location(probeLoc, _data.parentID),
|
|
count);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
dir = (dir + 1) & 0x7;
|
|
} while (dir != startDir);
|
|
|
|
dist += 4;
|
|
}
|
|
}
|
|
|
|
GameObject *GameObject::getIntangibleContainer(int containerType) {
|
|
|
|
ObjectID objID;
|
|
GameObject *item;
|
|
|
|
ContainerIterator iter(this);
|
|
while ((objID = iter.next(&item)) != Nothing) {
|
|
ProtoObj *proto = item->proto();
|
|
if (proto->classType == containerType)
|
|
return (item);
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Generic range checking function
|
|
|
|
bool GameObject::inRange(const TilePoint &tp, uint16 range) {
|
|
uint8 crossSection = prototype->crossSection;
|
|
TilePoint loc = getLocation();
|
|
|
|
loc = TilePoint(
|
|
clamp(loc.u - crossSection, tp.u, loc.u + crossSection),
|
|
clamp(loc.v - crossSection, tp.v, loc.v + crossSection),
|
|
clamp(loc.z, tp.z, loc.z + prototype->height));
|
|
|
|
TilePoint vector = tp - loc;
|
|
|
|
return vector.quickHDistance() <= range
|
|
&& ABS(vector.z) <= range;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// A timer for this object has ticked
|
|
|
|
void GameObject::timerTick(TimerID timer) {
|
|
scriptCallFrame scf;
|
|
|
|
scf.invokedObject = thisID();
|
|
scf.enactor = scf.invokedObject;
|
|
scf.idNum = timer;
|
|
|
|
runObjectMethod(scf.invokedObject, Method_GameObject_onTimerTick, scf);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// A sensor for this object has sensed an object
|
|
|
|
void GameObject::senseObject(SensorID sensor, ObjectID sensedObj) {
|
|
scriptCallFrame scf;
|
|
|
|
scf.invokedObject = thisID();
|
|
scf.enactor = scf.invokedObject;
|
|
scf.directObject = sensedObj;
|
|
scf.idNum = sensor;
|
|
|
|
runObjectMethod(scf.invokedObject, Method_GameObject_onSenseObject, scf);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// A sensor for this object has sensed an event
|
|
|
|
void GameObject::senseEvent(
|
|
SensorID sensor,
|
|
int16 type,
|
|
ObjectID directObject,
|
|
ObjectID indirectObject) {
|
|
scriptCallFrame scf;
|
|
|
|
scf.invokedObject = thisID();
|
|
scf.enactor = scf.invokedObject;
|
|
scf.directObject = directObject;
|
|
scf.indirectObject = indirectObject;
|
|
scf.idNum = sensor;
|
|
scf.value = type;
|
|
|
|
runObjectMethod(scf.invokedObject, Method_GameObject_onSenseEvent, scf);
|
|
}
|
|
|
|
// Timer related member functions
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add a new timer to this objects's timer list
|
|
|
|
bool GameObject::addTimer(TimerID id) {
|
|
return addTimer(id, sensorCheckRate);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add a new timer to this objects's timer list
|
|
|
|
bool GameObject::addTimer(TimerID id, int16 frameInterval) {
|
|
TimerList *timerList;
|
|
Timer *newTimer;
|
|
|
|
// Create the new timer
|
|
if ((newTimer = new Timer(this, id, frameInterval)) == nullptr)
|
|
return false;
|
|
|
|
// Fetch the existing timer list for this object or create a
|
|
// new one
|
|
if ((timerList = fetchTimerList(this)) == nullptr && (timerList = new TimerList(this)) == nullptr) {
|
|
delete newTimer;
|
|
return false;
|
|
}
|
|
|
|
assert(timerList->getObject() == this);
|
|
|
|
// Search the list to see if there is already a timer with same
|
|
// ID as the new timer. If so, remove it and delete it.
|
|
for (Common::List<Timer *>::iterator it = timerList->_timers.begin(); it != timerList->_timers.end(); ++it) {
|
|
assert((*it)->getObject() == this);
|
|
|
|
if (newTimer->thisID() == (*it)->thisID()) {
|
|
deleteTimer(*it);
|
|
delete *it;
|
|
timerList->_timers.erase(it);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Put the new timer into the list
|
|
timerList->_timers.push_back(newTimer);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Remove a specified timer from this object's timer list
|
|
|
|
void GameObject::removeTimer(TimerID id) {
|
|
TimerList *timerList;
|
|
|
|
// Get this object's timer list
|
|
if ((timerList = fetchTimerList(this)) != nullptr) {
|
|
for (Common::List<Timer *>::iterator it = timerList->_timers.begin(); it != timerList->_timers.end(); ++it) {
|
|
if ((*it)->thisID() == id) {
|
|
(*it)->_active = false;
|
|
timerList->_timers.erase(it);
|
|
|
|
if (timerList->_timers.empty())
|
|
delete timerList;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Remove all timer's from this objects's timer list
|
|
|
|
void GameObject::removeAllTimers() {
|
|
TimerList *timerList;
|
|
|
|
// Get this object's timer list
|
|
if ((timerList = fetchTimerList(this)) != nullptr) {
|
|
for (Common::List<Timer *>::iterator it = timerList->_timers.begin(); it != timerList->_timers.end(); ++it) {
|
|
deleteTimer(*it);
|
|
delete *it;
|
|
}
|
|
|
|
timerList->_timers.clear();
|
|
|
|
delete timerList;
|
|
}
|
|
}
|
|
|
|
// Sensor related member functions
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add the specified sensor to this object's sensor list
|
|
|
|
bool GameObject::addSensor(Sensor *newSensor) {
|
|
SensorList *sensorList;
|
|
|
|
// Fetch the existing sensor list for this object or allocate a
|
|
// new one
|
|
if ((sensorList = fetchSensorList(this)) == nullptr
|
|
&& (sensorList = new SensorList(this)) == nullptr)
|
|
return false;
|
|
|
|
assert(sensorList->getObject() == this);
|
|
|
|
// Search the list to see if there is already a sensor with same
|
|
// ID as the new sensor. If so, remove it and delete it.
|
|
for (Common::List<Sensor *>::iterator it = sensorList->_list.begin(); it != sensorList->_list.end(); ++it) {
|
|
assert((*it)->getObject() == this);
|
|
|
|
if (newSensor->thisID() == (*it)->thisID()) {
|
|
delete *it;
|
|
it = sensorList->_list.erase(it);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Put the new sensor into the list
|
|
sensorList->_list.push_back(newSensor);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add a protaganist sensor to this object's sensor list
|
|
|
|
bool GameObject::addProtaganistSensor(SensorID id, int16 range) {
|
|
ProtaganistSensor *newSensor;
|
|
bool sensorAdded;
|
|
|
|
newSensor = new ProtaganistSensor(this, id, range);
|
|
if (newSensor == nullptr) return false;
|
|
|
|
sensorAdded = addSensor(newSensor);
|
|
if (!sensorAdded) delete newSensor;
|
|
|
|
return sensorAdded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add a specific actor sensor to this object's sensor list
|
|
|
|
bool GameObject::addSpecificActorSensor(SensorID id, int16 range, Actor *a) {
|
|
SpecificActorSensor *newSensor;
|
|
bool sensorAdded;
|
|
|
|
newSensor = new SpecificActorSensor(this, id, range, a);
|
|
if (newSensor == nullptr) return false;
|
|
|
|
sensorAdded = addSensor(newSensor);
|
|
if (!sensorAdded) delete newSensor;
|
|
|
|
return sensorAdded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add a specific object sensor to this object's sensor list
|
|
|
|
bool GameObject::addSpecificObjectSensor(
|
|
SensorID id,
|
|
int16 range,
|
|
ObjectID obj) {
|
|
SpecificObjectSensor *newSensor;
|
|
bool sensorAdded;
|
|
|
|
newSensor = new SpecificObjectSensor(this, id, range, obj);
|
|
if (newSensor == nullptr) return false;
|
|
|
|
sensorAdded = addSensor(newSensor);
|
|
if (!sensorAdded) delete newSensor;
|
|
|
|
return sensorAdded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add an actor property sensor to this object's sensor list
|
|
|
|
bool GameObject::addActorPropertySensor(
|
|
SensorID id,
|
|
int16 range,
|
|
ActorPropertyID prop) {
|
|
ActorPropertySensor *newSensor;
|
|
bool sensorAdded;
|
|
|
|
newSensor = new ActorPropertySensor(this, id, range, prop);
|
|
if (newSensor == nullptr) return false;
|
|
|
|
sensorAdded = addSensor(newSensor);
|
|
if (!sensorAdded) delete newSensor;
|
|
|
|
return sensorAdded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add an object property sensor to this object's sensor list
|
|
|
|
bool GameObject::addObjectPropertySensor(
|
|
SensorID id,
|
|
int16 range,
|
|
ObjectPropertyID prop) {
|
|
ObjectPropertySensor *newSensor;
|
|
bool sensorAdded;
|
|
|
|
newSensor = new ObjectPropertySensor(this, id, range, prop);
|
|
if (newSensor == nullptr) return false;
|
|
|
|
sensorAdded = addSensor(newSensor);
|
|
if (!sensorAdded) delete newSensor;
|
|
|
|
return sensorAdded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add an event sensor to this object's sensor list
|
|
|
|
bool GameObject::addEventSensor(
|
|
SensorID id,
|
|
int16 range,
|
|
int16 eventType) {
|
|
EventSensor *newSensor;
|
|
bool sensorAdded;
|
|
|
|
newSensor = new EventSensor(this, id, range, eventType);
|
|
if (newSensor == nullptr) return false;
|
|
|
|
sensorAdded = addSensor(newSensor);
|
|
if (!sensorAdded) delete newSensor;
|
|
|
|
return sensorAdded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Remove a specified sensor from this object's sensor list
|
|
|
|
void GameObject::removeSensor(SensorID id) {
|
|
SensorList *sensorList;
|
|
|
|
// Get this object's sensor list
|
|
if ((sensorList = fetchSensorList(this)) != nullptr) {
|
|
// Search the sensor list for a sensor with the specified ID
|
|
for (Common::List<Sensor *>::iterator it = sensorList->_list.begin(); it != sensorList->_list.end(); ++it) {
|
|
if ((*it)->thisID() == id) {
|
|
// Remove the sensor, then delete it
|
|
(*it)->_active = false;
|
|
sensorList->_list.erase(it);
|
|
|
|
// If the list is now empty, delete it
|
|
if (sensorList->_list.empty()) {
|
|
delete sensorList;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Remove all sensors from this object's sensor list
|
|
|
|
void GameObject::removeAllSensors() {
|
|
SensorList *sensorList;
|
|
|
|
// Get this object's sensor list
|
|
if ((sensorList = fetchSensorList(this)) != nullptr) {
|
|
// Iterate through the sensors
|
|
for (Common::List<Sensor *>::iterator it = sensorList->_list.begin(); it != sensorList->_list.end(); ++it)
|
|
delete *it;
|
|
|
|
deleteSensorList(sensorList);
|
|
delete sensorList;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if this object can sense a protaganist
|
|
// within a specified range
|
|
|
|
bool GameObject::canSenseProtaganist(SenseInfo &info, int16 range) {
|
|
ProtaganistSensor sensor(this, 0, range);
|
|
|
|
if (isActor(this)) {
|
|
Actor *a = (Actor *) this;
|
|
return sensor.check(info, a->_enchantmentFlags);
|
|
}
|
|
return sensor.check(info, nonActorSenseFlags);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if this object can sense a specific
|
|
// actor within a specified range
|
|
|
|
bool GameObject::canSenseSpecificActor(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
Actor *a) {
|
|
SpecificActorSensor sensor(this, 0, range, a);
|
|
|
|
if (isActor(this)) {
|
|
Actor *ac = (Actor *)this;
|
|
return sensor.check(info, ac->_enchantmentFlags);
|
|
}
|
|
return sensor.check(info, nonActorSenseFlags);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if this object can sense a specific
|
|
// object within a specified range
|
|
|
|
bool GameObject::canSenseSpecificObject(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
ObjectID obj) {
|
|
SpecificObjectSensor sensor(this, 0, range, obj);
|
|
|
|
if (isActor(this)) {
|
|
Actor *a = (Actor *) this;
|
|
return sensor.check(info, a->_enchantmentFlags);
|
|
}
|
|
return sensor.check(info, nonActorSenseFlags);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if this object can sense an actor with
|
|
// a specified property within a specified range
|
|
|
|
bool GameObject::canSenseActorProperty(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
ActorPropertyID prop) {
|
|
ActorPropertySensor sensor(this, 0, range, prop);
|
|
|
|
if (isActor(this)) {
|
|
Actor *a = (Actor *) this;
|
|
return sensor.check(info, a->_enchantmentFlags);
|
|
}
|
|
return sensor.check(info, nonActorSenseFlags);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if this object can sense an object with
|
|
// a specified property within a specified range
|
|
|
|
bool GameObject::canSenseObjectProperty(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
ObjectPropertyID prop) {
|
|
ObjectPropertySensor sensor(this, 0, range, prop);
|
|
|
|
if (isActor(this)) {
|
|
Actor *a = (Actor *) this;
|
|
return sensor.check(info, a->_enchantmentFlags);
|
|
}
|
|
return sensor.check(info, nonActorSenseFlags);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Given an object, returns the prototype number
|
|
|
|
int32 GameObject::getProtoNum() {
|
|
for (uint i = 0; i < g_vm->_actorProtos.size(); ++i) {
|
|
if (prototype == g_vm->_actorProtos[i])
|
|
return i;
|
|
}
|
|
|
|
for (uint i = 0; i < g_vm->_objectProtos.size(); ++i) {
|
|
if (prototype == g_vm->_objectProtos[i])
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Set the prototype of an object to a given prototype number
|
|
|
|
void GameObject::setProtoNum(int32 nProto) {
|
|
if (isActor(this))
|
|
prototype = g_vm->_actorProtos[nProto];
|
|
else {
|
|
ObjectID oldParentID = _data.parentID;
|
|
bool wasStacked = unstack(); // Unstack if it was in a stack
|
|
|
|
prototype = g_vm->_objectProtos[nProto];
|
|
|
|
if (wasStacked) {
|
|
ObjectID pos = possessor();
|
|
|
|
move(Location(0, 0, 0, ImportantLimbo));
|
|
|
|
// Attempt to replace stacked object in inventory
|
|
if (!dropOn((pos != Nothing ? pos : getCenterActorID()), oldParentID, 1))
|
|
deleteObjectRecursive();
|
|
}
|
|
|
|
// If this object is in a container, then redraw the container window
|
|
if (!isWorld(oldParentID))
|
|
g_vm->_cnm->setUpdate(oldParentID);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Evaluate the effects of enchantments upon an object
|
|
|
|
void GameObject::evalEnchantments() {
|
|
if (isActor(this)) {
|
|
evalActorEnchantments((Actor *)this);
|
|
} else if (isObject(this)) {
|
|
evalObjectEnchantments(this);
|
|
}
|
|
}
|
|
|
|
#define noMergeFlags (objectImportant|\
|
|
objectGhosted|\
|
|
objectInvisible|\
|
|
objectFloating|\
|
|
objectNoRecycle)
|
|
|
|
int32 GameObject::canStackOrMerge(GameObject *dropObj, GameObject *target) {
|
|
int32 cSet = dropObj->proto()->containmentSet();
|
|
|
|
// If the names are the same, and the prototype is the same, and the object
|
|
// being dropped is neither an intangible object nor a container,
|
|
// then stacking / merging may be possible.
|
|
|
|
if (dropObj->getNameIndex() == target->getNameIndex()
|
|
&& dropObj->proto() == target->proto()
|
|
&& !(cSet & (ProtoObj::isIntangible | ProtoObj::isContainer))) {
|
|
// If it is a mergeable object
|
|
if (dropObj->proto()->flags & ResourceObjectPrototype::objPropMergeable) {
|
|
// If the flags are the same, and neither object has children,
|
|
// then we can merge
|
|
if (((dropObj->_data.objectFlags & noMergeFlags) == (target->_data.objectFlags & noMergeFlags))
|
|
&& dropObj->IDChild() == Nothing
|
|
&& target->IDChild() == Nothing) {
|
|
return canMerge;
|
|
}
|
|
} else if (!(cSet & (ProtoObj::isWearable | ProtoObj::isWeapon | ProtoObj::isArmor))
|
|
|| !isActor(target->IDParent())) {
|
|
// We can stack if the pile we are stacking on is in a container.
|
|
if (!isWorld(target->IDParent())
|
|
&& target->getLocation().z != 0)
|
|
return canStack;
|
|
}
|
|
}
|
|
return cannotStackOrMerge;
|
|
}
|
|
|
|
void GameObject::mergeWith(GameObject *dropObj, GameObject *target, int16 count) {
|
|
// get the smaller of the two as a move variable
|
|
int16 moveCount = MIN<uint16>(count, dropObj->getExtra());
|
|
|
|
// REM: We need to check and see if the container can hold this....
|
|
|
|
// Inc the masscount on the target object
|
|
// If we overflow a short, then simply truncate the amount (stuff is lost,
|
|
// but that's OK for mergeables!)
|
|
target->setExtra(MIN<long>((long)target->getExtra() + moveCount, 0x7fff));
|
|
|
|
// decrement the count on the other pile
|
|
dropObj->setExtra(dropObj->getExtra() - moveCount);
|
|
|
|
// if that was the last object,
|
|
// then delete it from the container
|
|
if (dropObj->getExtra() <= 0) {
|
|
dropObj->deleteObject();
|
|
}
|
|
|
|
g_vm->_cnm->setUpdate(target->IDParent());
|
|
}
|
|
|
|
|
|
bool GameObject::merge(ObjectID enactor, ObjectID objToMergeID, int16 count) {
|
|
GameObject *objToMerge = objectAddress(objToMergeID);
|
|
Location loc(getLocation(), IDParent());
|
|
|
|
if (objToMerge->drop(enactor, loc, count)) {
|
|
if (!objToMerge->isMoving())
|
|
mergeWith(objToMerge, this, count);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::stack(ObjectID enactor, ObjectID objToStackID) {
|
|
GameObject *objToStack = objectAddress(objToStackID);
|
|
|
|
// Stack the object
|
|
Location loc(getLocation(), IDParent());
|
|
|
|
loc.z = 0;
|
|
|
|
if (objToStack->drop(enactor, loc)) {
|
|
if (!objToStack->isMoving()) {
|
|
// Increase the stack count
|
|
_data.location.z++;
|
|
g_vm->_cnm->setUpdate(IDParent());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Return the total mass of all objects contained within this object
|
|
|
|
uint16 GameObject::totalContainedMass() {
|
|
uint16 total = 0;
|
|
GameObject *childObj;
|
|
ContainerIterator iter(this);
|
|
|
|
while (iter.next(&childObj) != Nothing) {
|
|
uint16 objMass;
|
|
|
|
if (!(childObj->containmentSet() & ProtoObj::isTangible))
|
|
continue;
|
|
|
|
objMass = childObj->prototype->mass;
|
|
if (childObj->isMergeable())
|
|
objMass *= childObj->getExtra();
|
|
total += objMass;
|
|
|
|
if (childObj->_data.childID != Nothing)
|
|
total += childObj->totalContainedMass();
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Return the total bulk of all objects contained within this object
|
|
|
|
uint16 GameObject::totalContainedBulk() {
|
|
uint16 total = 0;
|
|
GameObject *childObj;
|
|
ContainerIterator iter(this);
|
|
|
|
while (iter.next(&childObj) != Nothing) {
|
|
uint16 objBulk;
|
|
|
|
if (!(childObj->containmentSet() & ProtoObj::isTangible))
|
|
continue;
|
|
|
|
objBulk = childObj->prototype->bulk;
|
|
if (childObj->isMergeable())
|
|
objBulk *= childObj->getExtra();
|
|
total += objBulk;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
GameWorld member functions
|
|
* ======================================================================= */
|
|
|
|
//-------------------------------------------------------------------
|
|
// Initial constructor
|
|
|
|
GameWorld::GameWorld(int16 map) {
|
|
Common::SeekableReadStream *stream;
|
|
if ((stream = loadResourceToStream(tileRes, MKTAG('M', 'A', 'P', (char)map), "game map"))) {
|
|
int16 mapSize; // Size of map in MetaTiles
|
|
|
|
mapSize = stream->readSint16LE();
|
|
size.u = (mapSize << kPlatShift) << kTileUVShift;
|
|
size.v = size.u;
|
|
|
|
sectorArraySize = size.u / kSectorSize;
|
|
sectorArray = new Sector[sectorArraySize * sectorArraySize]();
|
|
|
|
if (sectorArray == nullptr)
|
|
error("Unable to allocate world %d sector array", map);
|
|
|
|
mapNum = map;
|
|
delete stream;
|
|
} else {
|
|
size.u = size.v = 0;
|
|
sectorArraySize = 0;
|
|
sectorArray = nullptr;
|
|
|
|
mapNum = -1;
|
|
}
|
|
}
|
|
|
|
GameWorld::GameWorld(Common::SeekableReadStream *stream) {
|
|
size.u = size.v = stream->readSint16LE();
|
|
mapNum = stream->readSint16LE();
|
|
|
|
debugC(3, kDebugSaveload, "... size.u = size.v = %d", size.u);
|
|
debugC(3, kDebugSaveload, "... mapNum = %d", mapNum);
|
|
|
|
if (size.u != 0) {
|
|
int32 sectorArrayCount;
|
|
|
|
sectorArraySize = size.u / kSectorSize;
|
|
sectorArrayCount = sectorArraySize * sectorArraySize;
|
|
sectorArray = new Sector[sectorArrayCount]();
|
|
|
|
if (sectorArray == nullptr)
|
|
error("Unable to allocate world %d sector array", mapNum);
|
|
|
|
for (int i = 0; i < sectorArrayCount; ++i) {
|
|
sectorArray[i].read(stream);
|
|
debugC(4, kDebugSaveload, "...... sectArray[%d].activationCount = %d", i, sectorArray[i].activationCount);
|
|
debugC(4, kDebugSaveload, "...... sectArray[%d].childID = %d", i, sectorArray[i].childID);
|
|
}
|
|
} else {
|
|
sectorArraySize = 0;
|
|
sectorArray = nullptr;
|
|
}
|
|
}
|
|
|
|
GameWorld::~GameWorld() {
|
|
if (sectorArray)
|
|
delete[] sectorArray;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Return the number of bytes need to make an archive of this world
|
|
|
|
int32 GameWorld::archiveSize() {
|
|
int32 bytes = 0;
|
|
|
|
bytes += sizeof(size.u)
|
|
+ sizeof(mapNum)
|
|
+ sectorArraySize * sectorArraySize * sizeof(Sector);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Cleanup
|
|
|
|
void GameWorld::cleanup() {
|
|
if (sectorArray != nullptr) {
|
|
delete[] sectorArray;
|
|
sectorArray = nullptr;
|
|
}
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
World management
|
|
* ======================================================================= */
|
|
|
|
extern int enchantmentProto;
|
|
|
|
//-------------------------------------------------------------------
|
|
// Load and construct object and actor prototype arrays
|
|
|
|
void initPrototypes() {
|
|
const int resourceObjProtoSize = 52;
|
|
const int resourceActProtoSize = 86;
|
|
uint count = 0;
|
|
Common::SeekableReadStream *stream;
|
|
Common::String s;
|
|
|
|
debugC(1, kDebugLoading, "Initializing Prototypes");
|
|
|
|
stream = loadResourceToStream(listRes, nameListID, "name list");
|
|
for (uint16 offset = 0; offset < stream->size(); ++count) {
|
|
stream->seek(2 * count);
|
|
offset = stream->readUint16LE();
|
|
|
|
stream->seek(offset);
|
|
s = stream->readString();
|
|
debugC(5, kDebugLoading, "Read string (size %d): %s", s.size(), s.c_str());
|
|
|
|
char *name = new char[s.size() + 1];
|
|
Common::strlcpy(name, s.c_str(), s.size() + 1);
|
|
g_vm->_nameList.push_back(name);
|
|
}
|
|
nameListCount = count;
|
|
|
|
delete stream;
|
|
|
|
// Load the Object prototype table
|
|
|
|
objectProtoCount = listRes->size(objProtoID)
|
|
/ resourceObjProtoSize;
|
|
|
|
if (objectProtoCount < 1)
|
|
error("Unable to load Object Prototypes");
|
|
|
|
if ((stream = loadResourceToStream(listRes, objProtoID, "object prototypes")) == nullptr)
|
|
error("Unable to load Object Prototypes");
|
|
|
|
// Load each individual prototype. Read in everything except
|
|
// the virtual function pointer.
|
|
|
|
for (int i = 0; i < objectProtoCount; i++) {
|
|
ResourceObjectPrototype ro;
|
|
ProtoObj *pr;
|
|
|
|
ro.load(stream);
|
|
|
|
switch (ro.classType) {
|
|
case protoClassInventory:
|
|
pr = new InventoryProto(ro);
|
|
break;
|
|
case protoClassPhysContainer:
|
|
pr = new PhysicalContainerProto(ro);
|
|
break;
|
|
case protoClassKey:
|
|
pr = new KeyProto(ro);
|
|
break;
|
|
|
|
case protoClassBottle:
|
|
pr = new BottleProto(ro);
|
|
break;
|
|
|
|
case protoClassFood: // Food ProtoType
|
|
pr = new FoodProto(ro);
|
|
break;
|
|
|
|
case protoClassBludgeoningWeapon:
|
|
pr = new BludgeoningWeaponProto(ro);
|
|
break;
|
|
|
|
case protoClassSlashingWeapon:
|
|
pr = new SlashingWeaponProto(ro);
|
|
break;
|
|
|
|
case protoClassBow:
|
|
pr = new BowProto(ro);
|
|
break;
|
|
|
|
case protoClassWeaponWand:
|
|
pr = new WeaponWandProto(ro);
|
|
break;
|
|
|
|
case protoClassArrow:
|
|
pr = new ArrowProto(ro);
|
|
break;
|
|
|
|
case protoClassShield:
|
|
pr = new ShieldProto(ro);
|
|
break;
|
|
|
|
case protoClassArmor:
|
|
pr = new ArmorProto(ro);
|
|
break;
|
|
|
|
case protoClassTool:
|
|
pr = new ToolProto(ro);
|
|
break;
|
|
|
|
case protoClassBookDoc:
|
|
pr = new BookProto(ro);
|
|
break;
|
|
|
|
case protoClassScrollDoc:
|
|
pr = new ScrollProto(ro);
|
|
break;
|
|
|
|
case protoClassMap:
|
|
pr = new AutoMapProto(ro);
|
|
break;
|
|
|
|
case protoClassIdea:
|
|
pr = new IdeaProto(ro);
|
|
break;
|
|
|
|
case protoClassMemory:
|
|
pr = new MemoryProto(ro);
|
|
break;
|
|
|
|
case protoClassPsych:
|
|
pr = new PsychProto(ro);
|
|
break;
|
|
|
|
case protoClassSkill:
|
|
pr = new SkillProto(ro);
|
|
initializeSkill((SkillProto *) pr, ((SkillProto *) pr)->getSpellID());
|
|
//initializeSkill(i,((SkillProto *) pr)->getSpellID());
|
|
break;
|
|
|
|
case protoClassIdeaContainer:
|
|
pr = new IdeaContainerProto(ro);
|
|
break;
|
|
|
|
case protoClassMemoryContainer:
|
|
pr = new MemoryContainerProto(ro);
|
|
break;
|
|
|
|
case protoClassPsychContainer:
|
|
pr = new PsychContainerProto(ro);
|
|
break;
|
|
|
|
case protoClassSkillContainer:
|
|
pr = new SkillContainerProto(ro);
|
|
break;
|
|
|
|
case protoClassEnchantment:
|
|
pr = new EnchantmentProto(ro);
|
|
enchantmentProto = i;
|
|
break;
|
|
|
|
case protoClassMonsterGenerator:
|
|
pr = new MonsterGeneratorProto(ro);
|
|
break;
|
|
|
|
// REM: add this in when we change the database.
|
|
case protoClassEncounterGenerator:
|
|
pr = new EncounterGeneratorProto(ro);
|
|
break;
|
|
|
|
case protoClassMissionGenerator:
|
|
pr = new MissionGeneratorProto(ro);
|
|
break;
|
|
|
|
default:
|
|
// Unrecognized prototypes now default to
|
|
// an inventory item.
|
|
pr = new InventoryProto(ro);
|
|
break;
|
|
}
|
|
|
|
g_vm->_objectProtos.push_back(pr);
|
|
}
|
|
|
|
listRes->rest();
|
|
delete stream;
|
|
|
|
// Load the Actor prototype table
|
|
|
|
actorProtoCount = listRes->size(actorProtoID)
|
|
/ resourceActProtoSize;
|
|
|
|
if (actorProtoCount < 1)
|
|
error("Unable to load Actor Prototypes");
|
|
|
|
if ((stream = loadResourceToStream(listRes, actorProtoID, "actor prototypes")) == nullptr)
|
|
error("Unable to load Actor Prototypes");
|
|
|
|
for (int i = 0; i < actorProtoCount; i++) {
|
|
ResourceActorPrototype ra;
|
|
ra.load(stream);
|
|
|
|
ActorProto *pr = new ActorProto(ra);
|
|
g_vm->_actorProtos.push_back(pr);
|
|
}
|
|
|
|
listRes->rest();
|
|
delete stream;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Cleanup the prototype lists
|
|
|
|
void cleanupPrototypes() {
|
|
for (uint i = 0; i < nameListCount; ++i) {
|
|
if (g_vm->_nameList[i])
|
|
delete[] g_vm->_nameList[i];
|
|
}
|
|
|
|
g_vm->_nameList.clear();
|
|
|
|
for (uint i = 0; i < g_vm->_actorProtos.size(); ++i) {
|
|
if (g_vm->_actorProtos[i])
|
|
delete g_vm->_actorProtos[i];
|
|
}
|
|
|
|
g_vm->_actorProtos.clear();
|
|
|
|
for (uint i = 0; i < g_vm->_objectProtos.size(); ++i) {
|
|
if (g_vm->_objectProtos[i])
|
|
delete g_vm->_objectProtos[i];
|
|
}
|
|
|
|
g_vm->_objectProtos.clear();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Load the sound effects table
|
|
|
|
void initObjectSoundFXTable() {
|
|
hResContext *itemRes;
|
|
|
|
itemRes = auxResFile->newContext(
|
|
MKTAG('I', 'T', 'E', 'M'),
|
|
"item resources");
|
|
if (itemRes == nullptr || !itemRes->_valid)
|
|
error("Error accessing item resource group.\n");
|
|
|
|
objectSoundFXTable =
|
|
(ObjectSoundFXs *)LoadResource(
|
|
itemRes,
|
|
MKTAG('S', 'N', 'D', 'T'),
|
|
"object sound effect table");
|
|
|
|
if (objectSoundFXTable == nullptr)
|
|
error("Unable to load object sound effects table");
|
|
|
|
auxResFile->disposeContext(itemRes);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Cleanup the sound effects table
|
|
|
|
void cleanupObjectSoundFXTable() {
|
|
if (objectSoundFXTable != nullptr) {
|
|
free(objectSoundFXTable);
|
|
objectSoundFXTable = nullptr;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Allocate array to hold the counts of the temp actors
|
|
|
|
void initTempActorCount() {
|
|
uint16 i;
|
|
|
|
// Allocate and initialize the temp actor count array
|
|
tempActorCount = new uint16[actorProtoCount];
|
|
for (i = 0; i < actorProtoCount; i++)
|
|
tempActorCount[i] = 0;
|
|
}
|
|
|
|
void saveTempActorCount(Common::OutSaveFile *outS) {
|
|
debugC(2, kDebugSaveload, "Saving TempActorCount");
|
|
|
|
outS->write("ACNT", 4);
|
|
CHUNK_BEGIN;
|
|
for (int i = 0; i < actorProtoCount; ++i)
|
|
out->writeUint16LE(tempActorCount[i]);
|
|
CHUNK_END;
|
|
}
|
|
|
|
void loadTempActorCount(Common::InSaveFile *in, int32 chunkSize) {
|
|
debugC(2, kDebugSaveload, "Loading TempActorCount");
|
|
|
|
int count = chunkSize / sizeof(uint16);
|
|
tempActorCount = new uint16[count];
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
tempActorCount[i] = in->readUint16LE();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Cleanup the array to temp actor counts
|
|
|
|
void cleanupTempActorCount() {
|
|
if (tempActorCount != nullptr) {
|
|
delete[] tempActorCount;
|
|
tempActorCount = nullptr;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Increment the temporary actor count for the specified prototype
|
|
|
|
void incTempActorCount(uint16 protoNum) {
|
|
tempActorCount[protoNum]++;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Decrement the temporary actor count for the specified prototype
|
|
|
|
void decTempActorCount(uint16 protoNum) {
|
|
tempActorCount[protoNum]--;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Return the number of temporary actors for the specified prototype
|
|
|
|
uint16 getTempActorCount(uint16 protoNum) {
|
|
return tempActorCount[protoNum];
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Initialize the worlds
|
|
|
|
void initWorlds() {
|
|
int i;
|
|
|
|
// worldCount must be set by the map data initialization
|
|
worldListSize = worldCount * sizeof(GameWorld);
|
|
|
|
worldList = new GameWorld[worldListSize]();
|
|
if (worldList == nullptr)
|
|
error("Unable to allocate world list");
|
|
|
|
for (i = 0; i < worldCount; i++) {
|
|
GameWorld *gw = &worldList[i];
|
|
|
|
new (gw) GameWorld(i);
|
|
|
|
worldList[i]._index = i + WorldBaseID;
|
|
}
|
|
|
|
currentWorld = &worldList[0];
|
|
setCurrentMap(currentWorld->mapNum);
|
|
}
|
|
|
|
void saveWorlds(Common::OutSaveFile *outS) {
|
|
debugC(2, kDebugSaveload, "Saving worlds");
|
|
|
|
outS->write("WRLD", 4);
|
|
CHUNK_BEGIN;
|
|
out->writeUint16LE(currentWorld->thisID());
|
|
|
|
debugC(3, kDebugSaveload, "... currentWorld->thisID() = %d", currentWorld->thisID());
|
|
|
|
for (int i = 0; i < worldCount; ++i) {
|
|
Sector *sectArray = worldList[i].sectorArray;
|
|
int32 sectorArrayCount = worldList[i].sectorArraySize *
|
|
worldList[i].sectorArraySize;
|
|
|
|
out->writeSint16LE(worldList[i].size.u);
|
|
out->writeSint16LE(worldList[i].mapNum);
|
|
|
|
debugC(3, kDebugSaveload, "... worldList[%d].size.u = %d", i, worldList[i].size.u);
|
|
debugC(3, kDebugSaveload, "... worldList[%d].mapNum = %d", i, worldList[i].mapNum);
|
|
|
|
for (int j = 0; j < sectorArrayCount; ++j) {
|
|
sectArray[j].write(out);
|
|
debugC(4, kDebugSaveload, "...... sectArray[%d].activationCount = %d", j, sectArray[j].activationCount);
|
|
debugC(4, kDebugSaveload, "...... sectArray[%d].childID = %d", j, sectArray[j].childID);
|
|
}
|
|
}
|
|
CHUNK_END;
|
|
}
|
|
|
|
void loadWorlds(Common::InSaveFile *in) {
|
|
debugC(2, kDebugSaveload, "Loading worlds");
|
|
|
|
ObjectID currentWorldID;
|
|
|
|
worldList = new GameWorld[worldListSize]();
|
|
if (worldList == nullptr)
|
|
error("Unable to allocate world list");
|
|
|
|
currentWorldID = in->readUint16LE();
|
|
|
|
debugC(3, kDebugSaveload, "... currentWorldID = %d", currentWorldID);
|
|
|
|
for (int i = 0; i < worldCount; ++i) {
|
|
debugC(3, kDebugSaveload, "Loading World %d", i);
|
|
|
|
new (&worldList[i]) GameWorld(in);
|
|
|
|
worldList[i]._index = i + WorldBaseID;
|
|
}
|
|
|
|
// Reset the current world
|
|
currentWorld = (GameWorld *)GameObject::objectAddress(currentWorldID);
|
|
setCurrentMap(currentWorld->mapNum);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Cleanup the GameWorld list
|
|
|
|
void cleanupWorlds() {
|
|
for (int i = 0; i < worldCount; i++) {
|
|
GameWorld *gw = &worldList[i];
|
|
|
|
gw->cleanup();
|
|
}
|
|
|
|
if (worldList != nullptr) {
|
|
delete[] worldList;
|
|
worldList = nullptr;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Initialize the Objects list
|
|
|
|
ResourceGameObject::ResourceGameObject(Common::SeekableReadStream *stream) {
|
|
protoIndex = stream->readSint16LE();
|
|
location.u = stream->readSint16LE();
|
|
location.v = stream->readSint16LE();
|
|
location.z = stream->readSint16LE();
|
|
nameIndex = stream->readUint16LE();
|
|
parentID = stream->readUint16LE();
|
|
script = stream->readUint16LE();
|
|
objectFlags = stream->readUint16LE();
|
|
hitPoints = stream->readByte();
|
|
misc = stream->readUint16LE();
|
|
}
|
|
|
|
void initObjects() {
|
|
int16 i, resourceObjectCount;
|
|
Common::Array<ResourceGameObject> resourceObjectList;
|
|
Common::SeekableReadStream *stream;
|
|
const int resourceGameObjSize = 19;
|
|
|
|
// Initialize the limbo counts
|
|
objectLimboCount = 0;
|
|
actorLimboCount = 0;
|
|
importantLimboCount = 0;
|
|
|
|
resourceObjectCount = listRes->size(objListID)
|
|
/ resourceGameObjSize;
|
|
|
|
if (resourceObjectCount < 4)
|
|
error("Unable to load Objects");
|
|
|
|
// Allocate memory for the object list
|
|
objectListSize = objectCount * sizeof(GameObject);
|
|
objectList = new GameObject[objectCount]();
|
|
|
|
if (objectList == nullptr)
|
|
error("Unable to load Objects");
|
|
|
|
if ((stream = loadResourceToStream(listRes, objListID, "res object list")) == nullptr)
|
|
error("Unable to load Objects");
|
|
|
|
// Read the resource Objects
|
|
for (int k = 0; k < resourceObjectCount; ++k) {
|
|
ResourceGameObject res(stream);
|
|
resourceObjectList.push_back(res);
|
|
}
|
|
|
|
delete stream;
|
|
|
|
if (g_vm->getGameId() == GID_DINO) {
|
|
warning("TODO: initObjects() for Dino");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < resourceObjectCount; i++) {
|
|
GameObject *obj = &objectList[i];
|
|
|
|
if (i < 4)
|
|
// First four object are limbos, so use the default
|
|
// constructor
|
|
new (obj) GameObject;
|
|
else
|
|
// Initialize the objects with the resource data
|
|
new (obj) GameObject(resourceObjectList[i]);
|
|
|
|
objectList[i]._index = i;
|
|
}
|
|
|
|
for (; i < objectCount; i++) {
|
|
GameObject *obj = &objectList[i];
|
|
|
|
// Use the default constructor for the extra actors
|
|
new (obj) GameObject;
|
|
|
|
objectList[i]._index = i;
|
|
}
|
|
|
|
// Go through the object list and initialize all objects.
|
|
|
|
//Add Object To World
|
|
for (i = 0; i < resourceObjectCount; i++) {
|
|
GameObject *obj = &objectList[i],
|
|
*parent;
|
|
TilePoint slot;
|
|
|
|
//skip linking the first four into chain since they are in limbo
|
|
|
|
if (i < 4)
|
|
continue;
|
|
|
|
// Objects which are inside other objects need to have their
|
|
// Z-coords initially forced to be 1 so that stacking works OK.
|
|
if (!isWorld(obj->_data.parentID)) obj->_data.location.z = 1;
|
|
|
|
parent = GameObject::objectAddress(obj->_data.parentID);
|
|
if (parent->getAvailableSlot(obj, &slot))
|
|
obj->move(Location(slot, obj->_data.parentID));
|
|
|
|
// Add object to world.
|
|
if (obj->_data.parentID == Nothing) {
|
|
obj->append(ObjectLimbo);
|
|
obj->_data.parentID = ObjectLimbo;
|
|
objectLimboCount++;
|
|
} else
|
|
obj->append(obj->_data.parentID);
|
|
}
|
|
|
|
for (; i < objectCount; i++) {
|
|
GameObject *obj = &objectList[i];
|
|
|
|
obj->_data.siblingID = obj->_data.childID = Nothing;
|
|
obj->append(ObjectLimbo);
|
|
obj->_data.parentID = ObjectLimbo;
|
|
objectLimboCount++;
|
|
}
|
|
|
|
// Make a pass over the actor list appending each actor to their
|
|
// parent's child list
|
|
for (i = 0; i < kActorCount; i++) {
|
|
Actor *a = g_vm->_act->_actorList[i];
|
|
|
|
if (a->_data.parentID == Nothing) {
|
|
a->append(ActorLimbo);
|
|
actorLimboCount++;
|
|
} else
|
|
a->append(a->_data.parentID);
|
|
}
|
|
|
|
#if DEBUG
|
|
massAndBulkCount = GetPrivateProfileInt("Debug", "MassAndBulkCount", 1, iniFile);
|
|
#endif
|
|
}
|
|
|
|
void saveObjects(Common::OutSaveFile *outS) {
|
|
outS->write("OBJS", 4);
|
|
CHUNK_BEGIN;
|
|
// Store the limbo counts
|
|
out->writeSint16LE(objectLimboCount);
|
|
out->writeSint16LE(actorLimboCount);
|
|
out->writeSint16LE(importantLimboCount);
|
|
|
|
// Store the object list
|
|
for (int i = 0; i < objectCount; i++) {
|
|
objectList[i].write(out, true);
|
|
out->writeUint16LE(0); // reserved bits
|
|
}
|
|
CHUNK_END;
|
|
}
|
|
|
|
void loadObjects(Common::InSaveFile *in) {
|
|
// Restore the limbo counts
|
|
objectLimboCount = in->readSint16LE();
|
|
actorLimboCount = in->readSint16LE();
|
|
importantLimboCount = in->readSint16LE();
|
|
|
|
objectList = new GameObject[objectCount]();
|
|
if (objectList == nullptr)
|
|
error("Unable to load Objects");
|
|
|
|
for (int i = 0; i < objectCount; i++) {
|
|
debugC(3, kDebugSaveload, "Loading object %d", i);
|
|
|
|
objectList[i].read(in, true);
|
|
in->readUint16LE();
|
|
objectList[i]._index = i;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Cleanup object list
|
|
|
|
void cleanupObjects() {
|
|
if (objectList != nullptr)
|
|
delete[] objectList;
|
|
g_vm->_mainDisplayList->reset();
|
|
}
|
|
|
|
void setCurrentWorld(ObjectID worldID) {
|
|
if (!isWorld(worldID)) {
|
|
error("Cannot set current world to non-world object.\n");
|
|
}
|
|
|
|
currentWorld = (GameWorld *)GameObject::objectAddress(worldID);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Return the coordinates of the currently viewed object
|
|
|
|
void getViewTrackPos(TilePoint &tp) {
|
|
if (viewCenterObject != Nothing) {
|
|
GameObject *obj = GameObject::objectAddress(viewCenterObject);
|
|
|
|
tp = obj->getLocation();
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Return a pointer to the currently viewed object
|
|
|
|
GameObject *getViewCenterObject() {
|
|
return viewCenterObject != Nothing
|
|
? GameObject::objectAddress(viewCenterObject)
|
|
: nullptr;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
Sector member functions
|
|
* ======================================================================= */
|
|
|
|
//-------------------------------------------------------------------
|
|
// Activate all actors in sector if sector is not alreay active
|
|
|
|
void Sector::activate() {
|
|
if (activationCount++ == 0) {
|
|
ObjectID id = childID;
|
|
|
|
while (id != Nothing) {
|
|
GameObject *obj = GameObject::objectAddress(id);
|
|
|
|
obj->activate();
|
|
|
|
id = obj->IDNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Decrement the activation count of the sector and deactivate all
|
|
// actors in sector if activation count has reached zero.
|
|
|
|
void Sector::deactivate() {
|
|
assert(activationCount != 0);
|
|
|
|
activationCount--;
|
|
}
|
|
|
|
void Sector::read(Common::InSaveFile *in) {
|
|
activationCount = in->readUint16LE();
|
|
childID = in->readUint16LE();
|
|
}
|
|
|
|
void Sector::write(Common::MemoryWriteStreamDynamic *out) {
|
|
out->writeUint16LE(activationCount);
|
|
out->writeUint16LE(childID);
|
|
}
|
|
|
|
|
|
/* ======================================================================= *
|
|
ActiveRegion member functions
|
|
* ======================================================================= */
|
|
|
|
//-------------------------------------------------------------------
|
|
// Update this active region
|
|
|
|
void ActiveRegion::read(Common::InSaveFile *in) {
|
|
anchor = in->readUint16LE();
|
|
anchorLoc.load(in);
|
|
worldID = in->readUint16LE();
|
|
region.read(in);
|
|
|
|
debugC(4, kDebugSaveload, "... anchor = %d", anchor);
|
|
debugC(4, kDebugSaveload, "... anchorLoc = (%d, %d, %d)", anchorLoc.u, anchorLoc.v, anchorLoc.z);
|
|
debugC(4, kDebugSaveload, "... worldID = %d", worldID);
|
|
debugC(4, kDebugSaveload, "... region = (min: (%d, %d, %d), max: (%d, %d, %d))",
|
|
region.min.u, region.min.v, region.min.z, region.max.u, region.max.v, region.max.z);
|
|
}
|
|
|
|
void ActiveRegion::write(Common::MemoryWriteStreamDynamic *out) {
|
|
out->writeUint16LE(anchor);
|
|
anchorLoc.write(out);
|
|
out->writeUint16LE(worldID);
|
|
region.write(out);
|
|
|
|
debugC(4, kDebugSaveload, "... anchor = %d", anchor);
|
|
debugC(4, kDebugSaveload, "... anchorLoc = (%d, %d, %d)", anchorLoc.u, anchorLoc.v, anchorLoc.z);
|
|
debugC(4, kDebugSaveload, "... worldID = %d", worldID);
|
|
debugC(4, kDebugSaveload, "... region = (min: (%d, %d, %d), max: (%d, %d, %d))",
|
|
region.min.u, region.min.v, region.min.z, region.max.u, region.max.v, region.max.z);
|
|
}
|
|
|
|
void ActiveRegion::update() {
|
|
GameObject *obj = GameObject::objectAddress(anchor);
|
|
GameWorld *world = (GameWorld *)GameObject::objectAddress(worldID);
|
|
ObjectID objWorldID = obj->world()->thisID();
|
|
|
|
// Determine if the world for this active region has changed
|
|
if (worldID != objWorldID) {
|
|
int16 u, v;
|
|
|
|
// Deactivate all of the old sectors
|
|
for (u = region.min.u; u < region.max.u; u++) {
|
|
for (v = region.min.v; v < region.max.v; v++) {
|
|
world->getSector(u, v)->deactivate();
|
|
}
|
|
}
|
|
|
|
// Initialize active region for new world
|
|
worldID = objWorldID;
|
|
world = (GameWorld *)GameObject::objectAddress(worldID);
|
|
anchorLoc = Nowhere;
|
|
region.min = Nowhere;
|
|
region.max = Nowhere;
|
|
}
|
|
|
|
TilePoint loc = obj->getLocation();
|
|
|
|
// Determine if anchor has moved since the last time
|
|
if (loc != anchorLoc) {
|
|
TileRegion ptRegion,
|
|
newRegion;
|
|
|
|
// Update the anchor _data.location
|
|
anchorLoc = loc;
|
|
|
|
// Determine the active region in points
|
|
ptRegion.min.u = loc.u - kSectorSize / 2;
|
|
ptRegion.min.v = loc.v - kSectorSize / 2;
|
|
ptRegion.max.u = ptRegion.min.u + kSectorSize;
|
|
ptRegion.max.v = ptRegion.min.v + kSectorSize;
|
|
|
|
// Convert to sector coordinates
|
|
newRegion.min.u = ptRegion.min.u >> kSectorShift;
|
|
newRegion.min.v = ptRegion.min.v >> kSectorShift;
|
|
newRegion.max.u = (ptRegion.max.u + kSectorMask) >> kSectorShift;
|
|
newRegion.max.v = (ptRegion.max.v + kSectorMask) >> kSectorShift;
|
|
|
|
if (region.min.u != newRegion.min.u
|
|
|| region.min.v != newRegion.min.v
|
|
|| region.max.u != newRegion.max.u
|
|
|| region.max.v != newRegion.max.v) {
|
|
int16 u, v;
|
|
|
|
// Deactivate all sectors from the old region which are
|
|
// not in the new region
|
|
for (u = region.min.u; u < region.max.u; u++) {
|
|
bool uOutOfRange;
|
|
|
|
uOutOfRange = u < newRegion.min.u || u >= newRegion.max.u;
|
|
|
|
for (v = region.min.v; v < region.max.v; v++) {
|
|
if (uOutOfRange
|
|
|| v < newRegion.min.v
|
|
|| v >= newRegion.max.v) {
|
|
|
|
if(Sector *sect = world->getSector(u, v))
|
|
sect->deactivate();
|
|
else
|
|
warning("ActiveRegion::update: Invalid Sector (%d, %d)", u, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Activate all sectors in the new region which were not
|
|
// in the old region
|
|
for (u = newRegion.min.u; u < newRegion.max.u; u++) {
|
|
bool uOutOfRange;
|
|
|
|
uOutOfRange = u < region.min.u || u >= region.max.u;
|
|
|
|
for (v = newRegion.min.v; v < newRegion.max.v; v++) {
|
|
if (uOutOfRange
|
|
|| v < region.min.v
|
|
|| v >= region.max.v) {
|
|
|
|
if(Sector *sect = world->getSector(u, v))
|
|
sect->activate();
|
|
else
|
|
warning("ActiveRegion::update: Invalid Sector (%d, %d)", u, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the region coordinates
|
|
region.min.u = newRegion.min.u;
|
|
region.min.v = newRegion.min.v;
|
|
region.max.u = newRegion.max.u;
|
|
region.max.v = newRegion.max.v;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
ActiveRegion array
|
|
* ======================================================================= */
|
|
|
|
//-------------------------------------------------------------------
|
|
// Iterate through the active regions, updating each
|
|
|
|
void updateActiveRegions() {
|
|
// TODO: updateActiveRegions() for Dino
|
|
if (g_vm->getGameId() == GID_DINO)
|
|
return;
|
|
|
|
for (int16 i = 0; i < kPlayerActors; i++)
|
|
g_vm->_activeRegionList[i].update();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Return a pointer to an active region given its PlayerActor's ID
|
|
|
|
ActiveRegion *getActiveRegion(PlayerActorID id) {
|
|
return &g_vm->_activeRegionList[id];
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Initialize the state of the active regions
|
|
|
|
void initActiveRegions() {
|
|
if (g_vm->getGameId() == GID_DINO) {
|
|
warning("TODO: initActiveRegions() for Dino");
|
|
return;
|
|
}
|
|
|
|
static PlayerActorID playerIDArray[kPlayerActors] =
|
|
{ FTA_JULIAN, FTA_PHILIP, FTA_KEVIN };
|
|
|
|
int16 i;
|
|
|
|
for (i = 0; i < kPlayerActors; i++) {
|
|
ActiveRegion *reg = &g_vm->_activeRegionList[i];
|
|
ObjectID actorID = getPlayerActorAddress(playerIDArray[i])->getActorID();
|
|
|
|
reg->anchor = actorID;
|
|
reg->anchorLoc = Nowhere;
|
|
reg->worldID = Nothing;
|
|
reg->region.min = Nowhere;
|
|
reg->region.max = Nowhere;
|
|
}
|
|
}
|
|
|
|
void saveActiveRegions(Common::OutSaveFile *outS) {
|
|
debugC(2, kDebugSaveload, "Saving ActiveRegions");
|
|
|
|
outS->write("AREG", 4);
|
|
CHUNK_BEGIN;
|
|
for (int i = 0; i < kPlayerActors; ++i) {
|
|
debugC(3, kDebugSaveload, "Saving Active Region %d", i);
|
|
g_vm->_activeRegionList[i].write(out);
|
|
}
|
|
CHUNK_END;
|
|
}
|
|
|
|
void loadActiveRegions(Common::InSaveFile *in) {
|
|
debugC(2, kDebugSaveload, "Loading ActiveRegions");
|
|
|
|
for (int i = 0; i < kPlayerActors; ++i) {
|
|
debugC(3, kDebugSaveload, "Loading Active Region %d", i);
|
|
g_vm->_activeRegionList[i].read(in);
|
|
}
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
SectorRegionObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
//------------------------------------------------------------------------
|
|
// Constructor
|
|
|
|
SectorRegionObjectIterator::SectorRegionObjectIterator(GameWorld *world) :
|
|
searchWorld(world), _currentObject(nullptr) {
|
|
assert(searchWorld != nullptr);
|
|
assert(isWorld(searchWorld));
|
|
|
|
minSector = TilePoint(0, 0, 0);
|
|
maxSector = searchWorld->sectorSize();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Initialize the object iterator and return the first object found
|
|
|
|
ObjectID SectorRegionObjectIterator::first(GameObject **obj) {
|
|
Sector *currentSector;
|
|
|
|
_currentObject = nullptr;
|
|
|
|
sectorCoords = minSector;
|
|
currentSector = searchWorld->getSector(sectorCoords.u, sectorCoords.v);
|
|
|
|
if (currentSector == nullptr)
|
|
return Nothing;
|
|
|
|
while (currentSector->childID == Nothing) {
|
|
if (++sectorCoords.v >= maxSector.v) {
|
|
sectorCoords.v = minSector.v;
|
|
if (++sectorCoords.u >= maxSector.u) {
|
|
if (obj != nullptr) *obj = nullptr;
|
|
return Nothing;
|
|
}
|
|
}
|
|
|
|
currentSector = searchWorld->getSector(
|
|
sectorCoords.u,
|
|
sectorCoords.v);
|
|
}
|
|
|
|
_currentObject = GameObject::objectAddress(currentSector->childID);
|
|
|
|
if (obj != nullptr) *obj = _currentObject;
|
|
return currentSector->childID;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the next object found
|
|
|
|
ObjectID SectorRegionObjectIterator::next(GameObject **obj) {
|
|
assert(sectorCoords.u >= minSector.u);
|
|
assert(sectorCoords.v >= minSector.v);
|
|
assert(sectorCoords.u < maxSector.u);
|
|
assert(sectorCoords.v < maxSector.v);
|
|
|
|
ObjectID currentObjectID;
|
|
|
|
currentObjectID = _currentObject->IDNext();
|
|
|
|
while (currentObjectID == Nothing) {
|
|
Sector *currentSector;
|
|
|
|
do {
|
|
if (++sectorCoords.v >= maxSector.v) {
|
|
sectorCoords.v = minSector.v;
|
|
if (++sectorCoords.u >= maxSector.u) {
|
|
if (obj != nullptr) *obj = nullptr;
|
|
return Nothing;
|
|
}
|
|
}
|
|
|
|
currentSector = searchWorld->getSector(
|
|
sectorCoords.u,
|
|
sectorCoords.v);
|
|
|
|
} while (currentSector->childID == Nothing);
|
|
|
|
currentObjectID = currentSector->childID;
|
|
}
|
|
|
|
_currentObject = GameObject::objectAddress(currentObjectID);
|
|
|
|
if (obj != nullptr) *obj = _currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
RadialObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
//------------------------------------------------------------------------
|
|
// Compute the sector region overlaying the specified radial region
|
|
|
|
TileRegion RadialObjectIterator::computeSectorRegion(
|
|
const TilePoint §ors,
|
|
const TilePoint ¢er,
|
|
int16 radius) {
|
|
TileRegion sectorRegion;
|
|
|
|
sectorRegion.min.u = clamp(
|
|
0,
|
|
(center.u - radius) >> kSectorShift,
|
|
sectors.u);
|
|
sectorRegion.min.v = clamp(
|
|
0,
|
|
(center.v - radius) >> kSectorShift,
|
|
sectors.v);
|
|
sectorRegion.max.u = clamp(
|
|
0,
|
|
(center.u + radius + kSectorMask) >> kSectorShift,
|
|
sectors.u);
|
|
sectorRegion.max.v = clamp(
|
|
0,
|
|
(center.v + radius + kSectorMask) >> kSectorShift,
|
|
sectors.v);
|
|
sectorRegion.min.z = sectorRegion.max.z = 0;
|
|
|
|
return sectorRegion;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the first object within the specified region
|
|
|
|
ObjectID RadialObjectIterator::first(GameObject **obj, int16 *dist) {
|
|
GameObject *currentObject = nullptr;
|
|
ObjectID currentObjectID;
|
|
int16 currentDist = 0;
|
|
|
|
currentObjectID = SectorRegionObjectIterator::first(¤tObject);
|
|
while (currentObjectID != Nothing
|
|
&& (currentDist =
|
|
computeDist(currentObject->getLocation()))
|
|
> radius) {
|
|
currentObjectID = SectorRegionObjectIterator::next(¤tObject);
|
|
}
|
|
|
|
if (dist != nullptr) *dist = currentDist;
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the next object within the specified region
|
|
|
|
ObjectID RadialObjectIterator::next(GameObject **obj, int16 *dist) {
|
|
GameObject *currentObject = nullptr;
|
|
ObjectID currentObjectID;
|
|
int16 currentDist = 0;
|
|
|
|
do {
|
|
currentObjectID = SectorRegionObjectIterator::next(¤tObject);
|
|
} while (currentObjectID != Nothing
|
|
&& (currentDist = computeDist(currentObject->getLocation())) > radius);
|
|
|
|
if (dist != nullptr)
|
|
*dist = currentDist;
|
|
if (obj != nullptr)
|
|
*obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
CircularObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
//------------------------------------------------------------------------
|
|
// Compute the distance to the specified point from the center coordinates
|
|
|
|
int16 CircularObjectIterator::computeDist(const TilePoint &tp) {
|
|
// Simply use quickHDistance()
|
|
return (tp - getCenter()).quickHDistance();
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
RingObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
ObjectID RingObjectIterator::first(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
currentObjectID = CircularObjectIterator::first(¤tObject);
|
|
while (currentObjectID != Nothing
|
|
&& computeDist(currentObject->getLocation()) < innerDist) {
|
|
currentObjectID = CircularObjectIterator::next(¤tObject);
|
|
}
|
|
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
|
|
}
|
|
|
|
ObjectID RingObjectIterator::next(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
do {
|
|
currentObjectID = CircularObjectIterator::next(¤tObject);
|
|
} while (currentObjectID != Nothing
|
|
&& computeDist(currentObject->getLocation()) < innerDist);
|
|
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
|
|
/* ======================================================================= *
|
|
DispRegionObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
//------------------------------------------------------------------------
|
|
// Compute the distance to the specified point from the center coordinates
|
|
|
|
int16 DispRegionObjectIterator::computeDist(const TilePoint &tp) {
|
|
// Compute distance from object to screen center.
|
|
// REM: remember to add in Z there somewhere.
|
|
return ABS(getCenter().u - tp.u)
|
|
+ ABS(getCenter().v - tp.v);
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
RegionalObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
//------------------------------------------------------------------------
|
|
// Compute the sector region overlaying the specified tilepoint region
|
|
|
|
TileRegion RegionalObjectIterator::computeSectorRegion(
|
|
const TilePoint §ors,
|
|
const TilePoint &min,
|
|
const TilePoint &max) {
|
|
TileRegion sectorRegion;
|
|
|
|
sectorRegion.min.u = clamp(
|
|
0,
|
|
min.u >> kSectorShift,
|
|
sectors.u);
|
|
sectorRegion.min.v = clamp(
|
|
0,
|
|
min.v >> kSectorShift,
|
|
sectors.v);
|
|
sectorRegion.max.u = clamp(
|
|
0,
|
|
(max.u + kSectorMask) >> kSectorShift,
|
|
sectors.u);
|
|
sectorRegion.max.v = clamp(
|
|
0,
|
|
(max.v + kSectorMask) >> kSectorShift,
|
|
sectors.v);
|
|
sectorRegion.min.z = sectorRegion.max.z = 0;
|
|
|
|
return sectorRegion;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Determine if the specified point is within the region
|
|
|
|
inline bool RegionalObjectIterator::inRegion(const TilePoint &tp) {
|
|
return tp.u >= minCoords.u
|
|
&& tp.v >= minCoords.v
|
|
&& tp.u < maxCoords.u
|
|
&& tp.v < maxCoords.v;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the first object within the specified region
|
|
|
|
ObjectID RegionalObjectIterator::first(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
currentObjectID = SectorRegionObjectIterator::first(¤tObject);
|
|
|
|
if (currentObjectID == Nothing)
|
|
return Nothing;
|
|
|
|
while (currentObjectID != Nothing
|
|
&& !inRegion(currentObject->getLocation())) {
|
|
currentObjectID = SectorRegionObjectIterator::next(¤tObject);
|
|
}
|
|
|
|
if (obj != nullptr)
|
|
*obj = currentObject;
|
|
|
|
return currentObjectID;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the next object within the specified region
|
|
|
|
ObjectID RegionalObjectIterator::next(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
do {
|
|
currentObjectID = SectorRegionObjectIterator::next(¤tObject);
|
|
} while (currentObjectID != Nothing
|
|
&& !inRegion(currentObject->getLocation()));
|
|
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
RectangularObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
// Constructor
|
|
RectangularObjectIterator::RectangularObjectIterator(
|
|
GameWorld *world,
|
|
const TilePoint &c,
|
|
const TilePoint &cdelta1,
|
|
const TilePoint &cdelta2) :
|
|
RegionalObjectIterator(
|
|
world,
|
|
MinTilePoint(c, c + cdelta1, c + cdelta2, c + cdelta1 + cdelta2),
|
|
MaxTilePoint(c, c + cdelta1, c + cdelta2, c + cdelta1 + cdelta2)),
|
|
coords1(c),
|
|
coords2(c + cdelta1),
|
|
coords3(c + cdelta1 + cdelta2),
|
|
coords4(c + cdelta2),
|
|
center((c + (cdelta1 + cdelta2) / 2)) {
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Determine if the specified point is within the region
|
|
|
|
inline bool RectangularObjectIterator::inRegion(const TilePoint &tp) {
|
|
return sameSide(coords1, coords2, center, tp) &&
|
|
sameSide(coords2, coords3, center, tp) &&
|
|
sameSide(coords3, coords4, center, tp) &&
|
|
sameSide(coords4, coords1, center, tp);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the first object within the specified region
|
|
|
|
ObjectID RectangularObjectIterator::first(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
currentObjectID = RegionalObjectIterator::first(¤tObject);
|
|
while (currentObjectID != Nothing
|
|
&& !inRegion(currentObject->getLocation())) {
|
|
currentObjectID = RegionalObjectIterator::next(¤tObject);
|
|
}
|
|
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the next object within the specified region
|
|
|
|
ObjectID RectangularObjectIterator::next(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
do {
|
|
currentObjectID = RegionalObjectIterator::next(¤tObject);
|
|
} while (currentObjectID != Nothing
|
|
&& !inRegion(currentObject->getLocation()));
|
|
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
|
|
|
|
/* ======================================================================= *
|
|
TriangularObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
// Constructor
|
|
TriangularObjectIterator::TriangularObjectIterator(
|
|
GameWorld *world,
|
|
const TilePoint &c1,
|
|
const TilePoint &c2,
|
|
const TilePoint &c3) :
|
|
RegionalObjectIterator(
|
|
world,
|
|
MinTilePoint(c1, c2, c3),
|
|
MaxTilePoint(c1, c2, c3)),
|
|
coords1(c1),
|
|
coords2(c2),
|
|
coords3(c3) {
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Determine if the specified point is within the region
|
|
|
|
inline bool TriangularObjectIterator::inRegion(const TilePoint &tp) {
|
|
return sameSide(coords1, coords2, coords3, tp) &&
|
|
sameSide(coords1, coords3, coords2, tp) &&
|
|
sameSide(coords2, coords3, coords1, tp) ;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the first object within the specified region
|
|
|
|
ObjectID TriangularObjectIterator::first(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
currentObjectID = RegionalObjectIterator::first(¤tObject);
|
|
while (currentObjectID != Nothing
|
|
&& !inRegion(currentObject->getLocation())) {
|
|
currentObjectID = RegionalObjectIterator::next(¤tObject);
|
|
}
|
|
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the next object within the specified region
|
|
|
|
ObjectID TriangularObjectIterator::next(GameObject **obj) {
|
|
GameObject *currentObject;
|
|
ObjectID currentObjectID;
|
|
|
|
do {
|
|
currentObjectID = RegionalObjectIterator::next(¤tObject);
|
|
} while (currentObjectID != Nothing
|
|
&& !inRegion(currentObject->getLocation()));
|
|
|
|
if (obj != nullptr) *obj = currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
CenterRegionObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
GameWorld *CenterRegionObjectIterator::CenterWorld() {
|
|
ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID());
|
|
return ar->getWorld();
|
|
}
|
|
|
|
TilePoint CenterRegionObjectIterator::MinCenterRegion() {
|
|
ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID());
|
|
return ar->getRegion().min;
|
|
}
|
|
|
|
TilePoint CenterRegionObjectIterator::MaxCenterRegion() {
|
|
ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID());
|
|
return ar->getRegion().max;
|
|
}
|
|
|
|
|
|
/* ======================================================================= *
|
|
ActiveRegionObjectIterator member functions
|
|
* ======================================================================= */
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
bool ActiveRegionObjectIterator::firstActiveRegion() {
|
|
activeRegionIndex = -1;
|
|
|
|
return nextActiveRegion();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
bool ActiveRegionObjectIterator::nextActiveRegion() {
|
|
int16 currentRegionSectors;
|
|
ActiveRegion *currentRegion;
|
|
TilePoint currentRegionSize;
|
|
|
|
do {
|
|
if (++activeRegionIndex >= kPlayerActors)
|
|
return false;
|
|
|
|
int16 prevRegionIndex;
|
|
|
|
currentRegion = &g_vm->_activeRegionList[activeRegionIndex];
|
|
|
|
sectorBitMask = 0;
|
|
currentRegionSize.u = currentRegion->region.max.u
|
|
- currentRegion->region.min.u;
|
|
currentRegionSize.v = currentRegion->region.max.v
|
|
- currentRegion->region.min.v;
|
|
currentRegionSectors = currentRegionSize.u * currentRegionSize.v;
|
|
|
|
for (prevRegionIndex = 0;
|
|
prevRegionIndex < activeRegionIndex;
|
|
prevRegionIndex++) {
|
|
ActiveRegion *prevRegion;
|
|
|
|
prevRegion = &g_vm->_activeRegionList[prevRegionIndex];
|
|
|
|
// Determine if the current region and the previous region
|
|
// overlap.
|
|
if (currentRegion->worldID != prevRegion->worldID
|
|
|| prevRegion->region.min.u >= currentRegion->region.max.u
|
|
|| currentRegion->region.min.u >= prevRegion->region.max.u
|
|
|| prevRegion->region.min.v >= currentRegion->region.max.v
|
|
|| currentRegion->region.min.v >= prevRegion->region.max.v)
|
|
continue;
|
|
|
|
TileRegion intersection;
|
|
int16 u, v;
|
|
|
|
intersection.min.u = MAX(
|
|
currentRegion->region.min.u,
|
|
prevRegion->region.min.u)
|
|
- currentRegion->region.min.u;
|
|
intersection.max.u = MIN(
|
|
currentRegion->region.max.u,
|
|
prevRegion->region.max.u)
|
|
- currentRegion->region.min.u;
|
|
intersection.min.v = MAX(
|
|
currentRegion->region.min.v,
|
|
prevRegion->region.min.v)
|
|
- currentRegion->region.min.v;
|
|
intersection.max.v = MIN(
|
|
currentRegion->region.max.v,
|
|
prevRegion->region.max.v)
|
|
- currentRegion->region.min.v;
|
|
|
|
for (u = intersection.min.u;
|
|
u < intersection.max.u;
|
|
u++) {
|
|
for (v = intersection.min.v;
|
|
v < intersection.max.v;
|
|
v++) {
|
|
uint8 sectorBit;
|
|
|
|
sectorBit = 1 << (u * currentRegionSize.v + v);
|
|
|
|
if (!(sectorBitMask & sectorBit)) {
|
|
currentRegionSectors--;
|
|
assert(currentRegionSectors >= 0);
|
|
|
|
// Set the bit in the bit mask indicating that this
|
|
// sector overlaps with a previouse active region
|
|
sectorBitMask |= sectorBit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If all of the current regions sectors are intersecting
|
|
// with previous active regions there is no need to check
|
|
// any further
|
|
if (currentRegionSectors == 0) break;
|
|
}
|
|
|
|
} while (currentRegionSectors == 0);
|
|
|
|
baseSectorCoords.u = currentRegion->region.min.u;
|
|
baseSectorCoords.v = currentRegion->region.min.v;
|
|
size.u = currentRegionSize.u;
|
|
size.v = currentRegionSize.v;
|
|
currentWorld = (GameWorld *)GameObject::objectAddress(
|
|
currentRegion->worldID);
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
bool ActiveRegionObjectIterator::firstSector() {
|
|
if (!firstActiveRegion())
|
|
return false;
|
|
|
|
sectorCoords.u = baseSectorCoords.u;
|
|
sectorCoords.v = baseSectorCoords.v;
|
|
|
|
if (sectorBitMask & 1) {
|
|
if (!nextSector())
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
bool ActiveRegionObjectIterator::nextSector() {
|
|
int16 u, v;
|
|
|
|
do {
|
|
sectorCoords.v++;
|
|
|
|
if (sectorCoords.v >= baseSectorCoords.v + size.v) {
|
|
sectorCoords.v = baseSectorCoords.v;
|
|
sectorCoords.u++;
|
|
|
|
if (sectorCoords.u >= baseSectorCoords.u + size.u) {
|
|
if (!nextActiveRegion()) return false;
|
|
|
|
sectorCoords.u = baseSectorCoords.u;
|
|
sectorCoords.v = baseSectorCoords.v;
|
|
}
|
|
}
|
|
|
|
u = sectorCoords.u - baseSectorCoords.u;
|
|
v = sectorCoords.v - baseSectorCoords.v;
|
|
} while (sectorBitMask & (1 << (u * size.v + v)));
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the first object within the specified region
|
|
|
|
ObjectID ActiveRegionObjectIterator::first(GameObject **obj) {
|
|
ObjectID currentObjectID = Nothing;
|
|
|
|
_currentObject = nullptr;
|
|
|
|
if (firstSector()) {
|
|
Sector *currentSector;
|
|
|
|
currentSector = currentWorld->getSector(
|
|
sectorCoords.u,
|
|
sectorCoords.v);
|
|
|
|
assert(currentSector != nullptr);
|
|
|
|
currentObjectID = currentSector->childID;
|
|
_currentObject = currentObjectID != Nothing
|
|
? GameObject::objectAddress(currentObjectID)
|
|
: nullptr;
|
|
|
|
while (currentObjectID == Nothing) {
|
|
if (!nextSector()) break;
|
|
|
|
currentSector = currentWorld->getSector(
|
|
sectorCoords.u,
|
|
sectorCoords.v);
|
|
|
|
assert(currentSector != nullptr);
|
|
|
|
currentObjectID = currentSector->childID;
|
|
_currentObject = currentObjectID != Nothing
|
|
? GameObject::objectAddress(currentObjectID)
|
|
: nullptr;
|
|
}
|
|
}
|
|
|
|
if (obj != nullptr) *obj = _currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Return the next object within the specified region
|
|
|
|
ObjectID ActiveRegionObjectIterator::next(GameObject **obj) {
|
|
assert(activeRegionIndex >= 0);
|
|
assert(activeRegionIndex < kPlayerActors);
|
|
|
|
ObjectID currentObjectID;
|
|
|
|
currentObjectID = _currentObject->IDNext();
|
|
_currentObject = currentObjectID != Nothing
|
|
? GameObject::objectAddress(currentObjectID)
|
|
: nullptr;
|
|
|
|
while (currentObjectID == Nothing) {
|
|
Sector *currentSector;
|
|
|
|
if (!nextSector()) break;
|
|
|
|
currentSector = currentWorld->getSector(
|
|
sectorCoords.u,
|
|
sectorCoords.v);
|
|
|
|
assert(currentSector != nullptr);
|
|
|
|
currentObjectID = currentSector->childID;
|
|
_currentObject = currentObjectID != Nothing
|
|
? GameObject::objectAddress(currentObjectID)
|
|
: nullptr;
|
|
}
|
|
|
|
if (obj != nullptr) *obj = _currentObject;
|
|
return currentObjectID;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
ContainerIterator member functions
|
|
* ======================================================================= */
|
|
|
|
// This class iterates through every object within a container
|
|
|
|
ContainerIterator::ContainerIterator(GameObject *container) {
|
|
// Get the ID of the 1st object in the sector list
|
|
nextID = container->_data.childID;
|
|
object = nullptr;
|
|
}
|
|
|
|
ObjectID ContainerIterator::next(GameObject **obj) {
|
|
ObjectID id = nextID;
|
|
|
|
if (id == Nothing) return Nothing;
|
|
|
|
object = GameObject::objectAddress(id);
|
|
nextID = object->_data.siblingID;
|
|
|
|
if (obj) *obj = object;
|
|
return id;
|
|
}
|
|
|
|
#if 0
|
|
/* ======================================================================= *
|
|
RecursiveContainerIterator member functions
|
|
* ======================================================================= */
|
|
|
|
// This class iterates through every object within a container
|
|
|
|
RecursiveContainerIterator::~RecursiveContainerIterator() {
|
|
if (subIter != nullptr) delete subIter;
|
|
}
|
|
|
|
ObjectID RecursiveContainerIterator::first(GameObject **obj) {
|
|
if (subIter != nullptr) delete subIter;
|
|
|
|
id(container->IDChild()),
|
|
|
|
|
|
if (obj != nullptr)
|
|
*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
|
|
|
|
return id;
|
|
}
|
|
|
|
ObjectID RecursiveContainerIterator::next(GameObject **obj) {
|
|
GameObject *currentObj;
|
|
|
|
if (subIter) {
|
|
ObjectID currentID = subIter->next(¤tObj);
|
|
|
|
if (currentID != Nothing) {
|
|
if (obj) *obj = currentObj;
|
|
return currentID;
|
|
}
|
|
|
|
delete subIter;
|
|
subIter = nullptr;
|
|
currentObj = GameObject::objectAddress(id);
|
|
} else {
|
|
currentObj = GameObject::objectAddress(id);
|
|
|
|
if (currentObj->IDChild()) {
|
|
subIter = NEW_ITER RecursiveContainerIterator(currentObj);
|
|
assert(subIter);
|
|
return subIter->first(obj);
|
|
}
|
|
}
|
|
id = currentObj->IDNext();
|
|
|
|
if (obj != nullptr)
|
|
*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
|
|
return id;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= *
|
|
RecursiveContainerIterator member functions
|
|
* ======================================================================= */
|
|
|
|
// This class iterates through every object within a container
|
|
|
|
ObjectID RecursiveContainerIterator::first(GameObject **obj) {
|
|
GameObject *rootObj = GameObject::objectAddress(root);
|
|
|
|
id = rootObj->IDChild();
|
|
|
|
if (obj != nullptr)
|
|
*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
|
|
|
|
return id;
|
|
}
|
|
|
|
ObjectID RecursiveContainerIterator::next(GameObject **obj) {
|
|
GameObject *currentObj = GameObject::objectAddress(id);
|
|
|
|
// If this object has a child, then the next object (id) is the child.
|
|
// If it has no child, then check for sibling.
|
|
if ((id = currentObj->IDChild()) == 0) {
|
|
// If this object has a sibling, then the next object (id) is the sibling.
|
|
// If it has no sibling, then check for parent.
|
|
while ((id = currentObj->IDNext()) == 0) {
|
|
// If this object has a parent, then the get the parent.
|
|
if ((id = currentObj->IDParent()) != 0) {
|
|
// If the parent is the root, then we're done.
|
|
if (id == Nothing || id == root) return 0;
|
|
|
|
// Set the current object to the parent, and then
|
|
// Go around the loop once again and get the sibling of the parent.
|
|
|
|
// The loop will keep going up until we either find an object that
|
|
// has a sibling, or we hit the original root object.
|
|
currentObj = GameObject::objectAddress(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj != nullptr)
|
|
*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
|
|
|
|
return id;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
Test for object collision
|
|
* ======================================================================= */
|
|
|
|
GameObject *objectCollision(GameObject *obj, GameWorld *world, const TilePoint &loc) {
|
|
ProtoObj *proto = obj->proto();
|
|
TileRegion volume;
|
|
GameObject *obstacle;
|
|
|
|
volume.min.u = loc.u - proto->crossSection;
|
|
volume.min.v = loc.v - proto->crossSection;
|
|
volume.max.u = loc.u + proto->crossSection;
|
|
volume.max.v = loc.v + proto->crossSection;
|
|
volume.min.z = loc.z;
|
|
volume.max.z = loc.z + proto->height;
|
|
|
|
// Adjust MIN Z for the fact that they can step over obstacles.
|
|
if (isActor(obj)) volume.min.z += kMaxStepHeight / 2;
|
|
|
|
// Constructor
|
|
CircularObjectIterator iter(world, loc, proto->crossSection + 32);
|
|
|
|
for (iter.first(&obstacle);
|
|
obstacle != nullptr;
|
|
iter.next(&obstacle)) {
|
|
TilePoint tp = obstacle->getLocation();
|
|
ProtoObj *obstacleProto = obstacle->proto();
|
|
|
|
if (obstacle == obj) continue;
|
|
|
|
if (tp.z < volume.max.z
|
|
&& tp.z + obstacleProto->height > volume.min.z
|
|
&& tp.u - obstacleProto->crossSection < volume.max.u
|
|
&& tp.u + obstacleProto->crossSection > volume.min.u
|
|
&& tp.v - obstacleProto->crossSection < volume.max.v
|
|
&& tp.v + obstacleProto->crossSection > volume.min.v) {
|
|
// If the actor is dead, then it is not an obstacle.
|
|
if (isActor(obstacle) && ((Actor *)obstacle)->isDead()) continue;
|
|
|
|
return obstacle;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
Test for line of sight between two objects
|
|
* ======================================================================= */
|
|
|
|
bool lineOfSight(GameObject *obj1, GameObject *obj2, uint32 terrainMask) {
|
|
GameWorld *world;
|
|
|
|
// If the two objects are not in the same world, there is no line
|
|
// of sight
|
|
if ((world = obj1->world()) != obj2->world()) return false;
|
|
#if 0
|
|
if (isActor(obj1)) {
|
|
Actor *a1 = (Actor *) obj1;
|
|
if (!a1->hasEffect(actorSeeInvis)) {
|
|
if (!isActor(obj2) && obj2->isInvisible())
|
|
return false;
|
|
else if (isActor(obj2)) {
|
|
Actor *a2 = (Actor *) obj2;
|
|
if (a2->hasEffect(actorInvisible))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
uint32 opaqueTerrain = ~terrainMask;
|
|
|
|
ProtoObj *obj1proto = obj1->proto(),
|
|
*obj2proto = obj2->proto();
|
|
|
|
TilePoint obj1Loc = obj1->getWorldLocation(),
|
|
obj2Loc = obj2->getWorldLocation();
|
|
|
|
obj1Loc.z += obj1proto->height * 7 / 8;
|
|
obj2Loc.z += obj2proto->height * 7 / 8;
|
|
|
|
return (lineTerrain(
|
|
world->mapNum,
|
|
obj1Loc,
|
|
obj2Loc,
|
|
opaqueTerrain)
|
|
& opaqueTerrain)
|
|
== 0;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
Test for line of sight between object and _data.location
|
|
* ======================================================================= */
|
|
|
|
bool lineOfSight(GameObject *obj, const TilePoint &loc, uint32 terrainMask) {
|
|
GameWorld *world = obj->world();
|
|
uint32 opaqueTerrain = ~terrainMask;
|
|
ProtoObj *proto = obj->proto();
|
|
TilePoint objLoc = obj->getWorldLocation();
|
|
|
|
objLoc.z += proto->height * 7 / 8;
|
|
|
|
return (lineTerrain(
|
|
world->mapNum,
|
|
objLoc,
|
|
loc,
|
|
opaqueTerrain)
|
|
& opaqueTerrain)
|
|
== 0;
|
|
}
|
|
|
|
|
|
/* ======================================================================= *
|
|
Test for line of sight between two _data.locations
|
|
* ======================================================================= */
|
|
|
|
bool lineOfSight(
|
|
GameWorld *world,
|
|
const TilePoint &loc1,
|
|
const TilePoint &loc2,
|
|
uint32 terrainMask) {
|
|
uint32 opaqueTerrain = ~terrainMask;
|
|
|
|
return (lineTerrain(
|
|
world->mapNum,
|
|
loc1,
|
|
loc2,
|
|
opaqueTerrain)
|
|
& opaqueTerrain)
|
|
== 0;
|
|
}
|
|
|
|
|
|
/* ======================================================================= *
|
|
Test if object is obscured by terrain
|
|
* ======================================================================= */
|
|
|
|
bool objObscured(GameObject *testObj) {
|
|
|
|
bool obscured = false;
|
|
|
|
if (isObject(testObj)) {
|
|
Point16 drawPos,
|
|
org;
|
|
ObjectSpriteInfo objSprInfo;
|
|
ColorTable objColors;
|
|
ProtoObj *proto = testObj->proto();
|
|
|
|
// Calculate X, Y coordinates of the sprite
|
|
TileToScreenCoords(testObj->getLocation(), drawPos);
|
|
drawPos.x += fineScroll.x;
|
|
drawPos.y += fineScroll.y;
|
|
|
|
objSprInfo = proto->getSprite(testObj, ProtoObj::objOnGround);
|
|
|
|
testObj->getColorTranslation(objColors);
|
|
|
|
if (visiblePixelsInSprite(objSprInfo.sp,
|
|
objSprInfo.flipped,
|
|
objColors,
|
|
drawPos,
|
|
testObj->getLocation(),
|
|
objRoofID(testObj)) <= 5)
|
|
obscured = true;
|
|
}
|
|
|
|
return obscured;
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
Setup object interaction test by placing two objects inside another and
|
|
displaying a container
|
|
* ======================================================================= */
|
|
|
|
extern gPanelList *playControls;
|
|
extern gPanelList *trioControls;
|
|
extern gPanelList *indivControls;
|
|
|
|
// Kludge!!!
|
|
int16 openMindType;
|
|
|
|
#ifdef hasReadyContainers
|
|
|
|
APPFUNC(cmdBrain) {
|
|
int16 part = clamp(0, ev.mouse.x * 3 / ev.panel->getExtent().width, 2);
|
|
|
|
//assert( indivControls->getEnabled() );
|
|
if (!indivControls->getEnabled())
|
|
return;
|
|
|
|
if (ev.eventType == gEventNewValue) {
|
|
//WriteStatusF( 4, "Brain Attempt " );
|
|
|
|
GameObject *container = indivCviewTop->containerObject;
|
|
ContainerIterator iter(container);
|
|
GameObject *item;
|
|
|
|
openMindType = part;
|
|
|
|
assert(container == indivCviewBot->containerObject);
|
|
|
|
// Get the actor's mind container
|
|
while (iter.next(&item) != Nothing) {
|
|
ProtoObj *proto = item->proto();
|
|
|
|
if (proto->classType == protoClassIdeaContainer) {
|
|
item->use(item->IDParent());
|
|
break;
|
|
}
|
|
}
|
|
} else if (ev.eventType == gEventMouseMove) {
|
|
if (ev.value == GfxCompImage::leave) {
|
|
g_vm->_mouseInfo->setText(nullptr);
|
|
} else { //if (ev.value == gCompImage::enter)
|
|
// set the text in the cursor
|
|
if (part == 0) g_vm->_mouseInfo->setText(IDEAS_INVENT);
|
|
else if (part == 1) g_vm->_mouseInfo->setText(SPELL_INVENT);
|
|
else g_vm->_mouseInfo->setText(SKILL_INVENT);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move to playerActor structure!!!
|
|
void readyContainerSetup() {
|
|
int8 i;
|
|
int8 resStart = 28;
|
|
|
|
// init the resource handle with the image group context
|
|
imageRes = resFile->newContext(imageGroupID, "image resources");
|
|
|
|
backImages = loadImageRes(imageRes, resStart, numReadyContRes, 'B', 'T', 'N');
|
|
|
|
indivReadyNode = CreateReadyContainerNode(0);
|
|
|
|
for (i = 0; i < kNumViews && i < kPlayerActors ; i++) {
|
|
g_vm->_playerList[i]->readyNode = CreateReadyContainerNode(i);
|
|
|
|
TrioCviews[i] = new ReadyContainerView(
|
|
*trioControls,
|
|
Rect16(trioReadyContInfo[i].xPos,
|
|
trioReadyContInfo[i].yPos + 8,
|
|
iconOriginX * 2 + iconWidth * trioReadyContInfo[i].cols + iconSpacingY * (trioReadyContInfo[i].cols - 1),
|
|
iconOriginY + (iconOriginY * trioReadyContInfo[i].rows) + (trioReadyContInfo[i].rows * iconHeight) - 23),
|
|
*g_vm->_playerList[i]->readyNode,
|
|
backImages,
|
|
numReadyContRes,
|
|
trioReadyContInfo[i].rows,
|
|
trioReadyContInfo[i].cols,
|
|
trioReadyContInfo[i].rows,
|
|
nullptr);
|
|
|
|
TrioCviews[i]->draw();
|
|
}
|
|
|
|
indivCviewTop = new ReadyContainerView(*indivControls,
|
|
Rect16(indivReadyContInfoTop.xPos,
|
|
indivReadyContInfoTop.yPos + 8,
|
|
iconOriginX * 2 + iconWidth * indivReadyContInfoTop.cols + iconSpacingY * (indivReadyContInfoTop.cols - 1),
|
|
iconOriginY + (iconOriginY * indivReadyContInfoTop.rows) + (indivReadyContInfoTop.rows * iconHeight) - 23),
|
|
*indivReadyNode,
|
|
backImages,
|
|
numReadyContRes,
|
|
indivReadyContInfoTop.rows,
|
|
indivReadyContInfoTop.cols,
|
|
indivReadyContInfoTop.rows,
|
|
nullptr);
|
|
|
|
indivCviewTop->draw();
|
|
|
|
indivCviewBot = new ReadyContainerView(*indivControls,
|
|
Rect16(indivReadyContInfoBot.xPos,
|
|
indivReadyContInfoBot.yPos + 8,
|
|
iconOriginX * 2 + iconWidth * indivReadyContInfoBot.cols + iconSpacingY * (indivReadyContInfoBot.cols - 1),
|
|
iconOriginY + (iconOriginY * indivReadyContInfoBot.rows) + (indivReadyContInfoBot.rows * iconHeight) - 24),
|
|
*indivReadyNode,
|
|
backImages,
|
|
numReadyContRes,
|
|
indivReadyContInfoBot.rows,
|
|
indivReadyContInfoBot.cols,
|
|
indivReadyContInfoBot.rows,
|
|
nullptr);
|
|
indivCviewBot->setScrollOffset(1); // set the object draw up by one
|
|
indivCviewBot->draw();
|
|
|
|
// >>>
|
|
//new gGenericControl(*indivControls,Rect16(488,265,40,40),0,cmdBrain);
|
|
}
|
|
|
|
void cleanupReadyContainers() {
|
|
if (backImages) {
|
|
// unload the images in the array and the array itself and nulls
|
|
// the appropriate pointers
|
|
unloadImageRes(backImages, numReadyContRes);
|
|
}
|
|
|
|
for (int16 i = 0; i < kNumViews && i < kPlayerActors ; i++) {
|
|
delete TrioCviews[i];
|
|
TrioCviews[i] = nullptr;
|
|
|
|
delete g_vm->_playerList[i]->readyNode;
|
|
g_vm->_playerList[i]->readyNode = nullptr;
|
|
}
|
|
delete indivReadyNode;
|
|
|
|
if (indivCviewTop) {
|
|
delete indivCviewTop;
|
|
indivCviewTop = nullptr;
|
|
}
|
|
|
|
if (indivCviewBot) {
|
|
delete indivCviewBot;
|
|
indivCviewBot = nullptr;
|
|
}
|
|
|
|
//
|
|
if (imageRes) resFile->disposeContext(imageRes);
|
|
imageRes = nullptr;
|
|
}
|
|
|
|
#endif
|
|
|
|
void objectTest() {
|
|
}
|
|
|
|
APPFUNC(cmdControl) {
|
|
int newContainer = protoClassIdeaContainer;
|
|
|
|
if (ev.eventType == gEventMouseUp) {
|
|
|
|
GameObject *object = (GameObject *)getCenterActor();
|
|
ContainerIterator iter(object);
|
|
GameObject *item;
|
|
ObjectID id;
|
|
|
|
//Get Center Actors Mind Container
|
|
while ((id = iter.next(&item)) != Nothing) {
|
|
ProtoObj *proto = item->proto();
|
|
//Default First Time To Idea Container
|
|
if (proto->classType == newContainer)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ======================================================================= *
|
|
Background simulation code
|
|
* ======================================================================= */
|
|
|
|
// This is the time, in game ticks, that we want each
|
|
// actor or object to be visited
|
|
// Let's assume that we want each object and/or actor
|
|
// to be updated once every 10 seconds.
|
|
const int32 objectCycleTime = (10 * frameRate),
|
|
actorCycleTime = (5 * frameRate);
|
|
|
|
// Indexes into the array of actors and objects
|
|
int32 objectIndex,
|
|
actorIndex;
|
|
|
|
// Indicates paused state of background simulation
|
|
bool backgroundSimulationPaused;
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Main background simulation function
|
|
// This function does background processing on a few actors, objects
|
|
|
|
void doBackgroundSimulation() {
|
|
// TODO: doBackgroundSimulation() for Dino
|
|
if (g_vm->getGameId() == GID_DINO)
|
|
return;
|
|
|
|
if (backgroundSimulationPaused) return;
|
|
|
|
// Debug code to verify the validity of the limbo counts
|
|
#if DEBUG
|
|
int16 count;
|
|
ObjectID _data.childID;
|
|
|
|
count = 0;
|
|
for (_data.childID = GameObject::objectAddress(ObjectLimbo)->IDChild();
|
|
_data.childID != Nothing;
|
|
_data.childID = GameObject::objectAddress(_data.childID)->IDNext())
|
|
count++;
|
|
assert(objectLimboCount == count);
|
|
|
|
count = 0;
|
|
for (_data.childID = GameObject::objectAddress(ActorLimbo)->IDChild();
|
|
_data.childID != Nothing;
|
|
_data.childID = GameObject::objectAddress(_data.childID)->IDNext())
|
|
count++;
|
|
assert(actorLimboCount == count);
|
|
|
|
count = 0;
|
|
for (_data.childID = GameObject::objectAddress(ImportantLimbo)->IDChild();
|
|
_data.childID != Nothing;
|
|
_data.childID = GameObject::objectAddress(_data.childID)->IDNext())
|
|
count++;
|
|
assert(importantLimboCount == count);
|
|
#endif
|
|
|
|
int32 objectUpdateCount,
|
|
actorUpdateCount;
|
|
|
|
// Calculate how many actors and/or objects we want to
|
|
// update in this cycle
|
|
objectUpdateCount = objectCount / objectCycleTime;
|
|
actorUpdateCount = kActorCount / actorCycleTime;
|
|
|
|
// do background processing on a few objects, based on clock time.
|
|
while (objectUpdateCount--) {
|
|
GameObject *obj;
|
|
|
|
obj = &objectList[objectIndex++];
|
|
|
|
// Wrap the counter around to the beginning if needed
|
|
if (objectIndex >= objectCount) objectIndex = 0;
|
|
|
|
// If object is not deleted, then tell that object to do
|
|
// a background update
|
|
if (obj->IDParent() > ImportantLimbo) {
|
|
assert(obj->proto());
|
|
|
|
// If an object has been abandoned by the player,
|
|
// and is not sitting inside a container,
|
|
// then the object will be scavenged after an average
|
|
// of 600 seconds (10 minutes).
|
|
|
|
if (obj->isScavengable()
|
|
&& !obj->isActivated()
|
|
&& isWorld(obj->IDParent())
|
|
&& g_vm->_rnd->getRandomNumber(MIN(objectLimboCount / 2, 60) - 1) == 0) {
|
|
obj->deleteObjectRecursive();
|
|
}
|
|
|
|
// REM: might want to check for object abandonment here
|
|
obj->proto()->doBackgroundUpdate(obj);
|
|
}
|
|
}
|
|
|
|
// do background processing on a few actors, based on clock time
|
|
while (actorUpdateCount--) {
|
|
Actor *a;
|
|
|
|
a = g_vm->_act->_actorList[actorIndex++];
|
|
|
|
// Wrap the counter around to the beginning if needed
|
|
if (actorIndex >= kActorCount) actorIndex = 0;
|
|
|
|
// If actor is not deleted, then tell that actor to do
|
|
// a background update
|
|
if (a->IDParent() > ImportantLimbo) {
|
|
assert(a->proto());
|
|
|
|
a->proto()->doBackgroundUpdate(a);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
void pauseBackgroundSimulation() {
|
|
backgroundSimulationPaused = true;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
void resumeBackgroundSimulation() {
|
|
backgroundSimulationPaused = false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// This function simply calls the GameObject::updateState() method
|
|
// for all active objects directly within a world.
|
|
|
|
void updateObjectStates() {
|
|
if (objectStatesPaused) return;
|
|
|
|
GameObject *obj,
|
|
*last = &objectList[objectCount];
|
|
|
|
static int16 baseIndex = 0;
|
|
|
|
// baseIndex = (baseIndex + 1) & ~3;
|
|
baseIndex = 0;
|
|
|
|
// for ( obj = &objectList[baseIndex]; obj < last; obj += 4 )
|
|
for (obj = &objectList[baseIndex]; obj < last; obj++) {
|
|
if (isWorld(obj->IDParent()) && obj->isActivated())
|
|
obj->updateState();
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
|
|
void pauseObjectStates() {
|
|
objectStatesPaused = true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
|
|
void resumeObjectStates() {
|
|
objectStatesPaused = false;
|
|
}
|
|
|
|
} // end of namespace Saga2
|