scummvm/engines/saga2/objects.cpp
2021-12-26 18:48:43 +01:00

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 &sectors,
const TilePoint &center,
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(&currentObject);
while (currentObjectID != Nothing
&& (currentDist =
computeDist(currentObject->getLocation()))
> radius) {
currentObjectID = SectorRegionObjectIterator::next(&currentObject);
}
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(&currentObject);
} 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(&currentObject);
while (currentObjectID != Nothing
&& computeDist(currentObject->getLocation()) < innerDist) {
currentObjectID = CircularObjectIterator::next(&currentObject);
}
if (obj != nullptr) *obj = currentObject;
return currentObjectID;
}
ObjectID RingObjectIterator::next(GameObject **obj) {
GameObject *currentObject;
ObjectID currentObjectID;
do {
currentObjectID = CircularObjectIterator::next(&currentObject);
} 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 &sectors,
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(&currentObject);
if (currentObjectID == Nothing)
return Nothing;
while (currentObjectID != Nothing
&& !inRegion(currentObject->getLocation())) {
currentObjectID = SectorRegionObjectIterator::next(&currentObject);
}
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(&currentObject);
} 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(&currentObject);
while (currentObjectID != Nothing
&& !inRegion(currentObject->getLocation())) {
currentObjectID = RegionalObjectIterator::next(&currentObject);
}
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(&currentObject);
} 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(&currentObject);
while (currentObjectID != Nothing
&& !inRegion(currentObject->getLocation())) {
currentObjectID = RegionalObjectIterator::next(&currentObject);
}
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(&currentObject);
} 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(&currentObj);
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