/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * aint32 with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * Based on the original sources * Faery Tale II -- The Halls of the Dead * (c) 1993-1996 The Wyrmkeep Entertainment Co. */ #include "saga2/saga2.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(void) { 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(void) { 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->_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->_actorList.size() > id - ActorBaseID ? g_vm->_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->_actorList[i]->_data.nameIndex == ind) return g_vm->_actorList[i]->thisID(); if (g_vm->_actorList[i]->prototype && g_vm->_actorList[i]->prototype->nameIndex == ind) return g_vm->_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 GameObject::nameToID(Common::String name) { Common::Array 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->_actorList[i]->objName(); objName.toLowercase(); if (objName.contains(name)) array.push_back(g_vm->_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(void) { return prototype->containmentSet(); } // Calculates the ID of an object, given it's (implicit) address ObjectID GameObject::thisID(void) { // 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(void) { // 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(void) { 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(void) { return Location(getLocation(), IDParent()); } Location GameObject::notGetWorldLocation(void) { 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(void) { // 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(void) { 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(void) { 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(void) { GameObject *item, *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(void) { assert(prototype); return prototype->getChargeType(); } // this function recharges an object void GameObject::recharge(void) { // 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->_containerList->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->_containerList->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(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(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(void) { // 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(void) { 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->_containerList->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(void) { // 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(void) { 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(void) { 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; 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(void) { 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; 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; 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::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::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(void) { TimerList *timerList; // Get this object's timer list if ((timerList = fetchTimerList(this)) != nullptr) { for (Common::List::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::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::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(void) { SensorList *sensorList; // Get this object's sensor list if ((sensorList = fetchSensorList(this)) != nullptr) { // Iterate through the sensors for (Common::List::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(void) { 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->_containerList->setUpdate(oldParentID); } } //------------------------------------------------------------------- // Evaluate the effects of enchantments upon an object void GameObject::evalEnchantments(void) { 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(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)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->_containerList->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->_containerList->setUpdate(IDParent()); } return true; } return false; } //------------------------------------------------------------------- // Return the total mass of all objects contained within this object uint16 GameObject::totalContainedMass(void) { 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(void) { 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(void) { int32 bytes = 0; bytes += sizeof(size.u) + sizeof(mapNum) + sectorArraySize * sectorArraySize * sizeof(Sector); return bytes; } //------------------------------------------------------------------- // Cleanup void GameWorld::cleanup(void) { if (sectorArray != nullptr) { delete[] sectorArray; sectorArray = nullptr; } } /* ======================================================================= * World management * ======================================================================= */ extern int enchantmentProto; //------------------------------------------------------------------- // Load and construct object and actor prototype arrays void initPrototypes(void) { 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(void) { 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(void) { 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(void) { if (objectSoundFXTable != nullptr) { free(objectSoundFXTable); objectSoundFXTable = nullptr; } } //------------------------------------------------------------------- // Allocate array to hold the counts of the temp actors void initTempActorCount(void) { 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(void) { 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(void) { 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(void) { 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(void) { int16 i, resourceObjectCount; Common::Array 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; 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->_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(void) { 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(void) { return viewCenterObject != Nothing ? GameObject::objectAddress(viewCenterObject) : nullptr; } /* ======================================================================= * Sector member functions * ======================================================================= */ //------------------------------------------------------------------- // Activate all actors in sector if sector is not alreay active void Sector::activate(void) { 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(void) { 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(void) { 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(void) { int16 i; for (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(void) { static PlayerActorID playerIDArray[kPlayerActors] = { FTA_JULIAN, FTA_PHILIP, FTA_KEVIN }; int16 i; for (i = 0; i < kPlayerActors; i++) { ActiveRegion *reg = &g_vm->_activeRegionList[i]; ObjectID actorID = getPlayerActorAddress(playerIDArray[i])->getActorID(); reg->anchor = actorID; reg->anchorLoc = Nowhere; reg->worldID = Nothing; reg->region.min = Nowhere; reg->region.max = Nowhere; } } void saveActiveRegions(Common::OutSaveFile *outS) { debugC(2, kDebugSaveload, "Saving ActiveRegions"); outS->write("AREG", 4); CHUNK_BEGIN; for (int i = 0; i < kPlayerActors; ++i) { debugC(3, kDebugSaveload, "Saving Active Region %d", i); g_vm->_activeRegionList[i].write(out); } CHUNK_END; } void loadActiveRegions(Common::InSaveFile *in) { debugC(2, kDebugSaveload, "Loading ActiveRegions"); for (int i = 0; i < kPlayerActors; ++i) { debugC(3, kDebugSaveload, "Loading Active Region %d", i); g_vm->_activeRegionList[i].read(in); } } /* ======================================================================= * SectorRegionObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ // Constructor SectorRegionObjectIterator::SectorRegionObjectIterator(GameWorld *world) : searchWorld(world), _currentObject(nullptr) { assert(searchWorld != nullptr); assert(isWorld(searchWorld)); minSector = TilePoint(0, 0, 0); maxSector = searchWorld->sectorSize(); } //------------------------------------------------------------------------ // Initialize the object iterator and return the first object found ObjectID SectorRegionObjectIterator::first(GameObject **obj) { Sector *currentSector; _currentObject = nullptr; sectorCoords = minSector; currentSector = searchWorld->getSector(sectorCoords.u, sectorCoords.v); if (currentSector == nullptr) return Nothing; while (currentSector->childID == Nothing) { if (++sectorCoords.v >= maxSector.v) { sectorCoords.v = minSector.v; if (++sectorCoords.u >= maxSector.u) { if (obj != nullptr) *obj = nullptr; return Nothing; } } currentSector = searchWorld->getSector( sectorCoords.u, sectorCoords.v); } _currentObject = GameObject::objectAddress(currentSector->childID); if (obj != nullptr) *obj = _currentObject; return currentSector->childID; } //------------------------------------------------------------------------ // Return the next object found ObjectID SectorRegionObjectIterator::next(GameObject **obj) { assert(sectorCoords.u >= minSector.u); assert(sectorCoords.v >= minSector.v); assert(sectorCoords.u < maxSector.u); assert(sectorCoords.v < maxSector.v); ObjectID currentObjectID; currentObjectID = _currentObject->IDNext(); while (currentObjectID == Nothing) { Sector *currentSector; do { if (++sectorCoords.v >= maxSector.v) { sectorCoords.v = minSector.v; if (++sectorCoords.u >= maxSector.u) { if (obj != nullptr) *obj = nullptr; return Nothing; } } currentSector = searchWorld->getSector( sectorCoords.u, sectorCoords.v); } while (currentSector->childID == Nothing); currentObjectID = currentSector->childID; } _currentObject = GameObject::objectAddress(currentObjectID); if (obj != nullptr) *obj = _currentObject; return currentObjectID; } /* ======================================================================= * RadialObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ // Compute the sector region overlaying the specified radial region TileRegion RadialObjectIterator::computeSectorRegion( const TilePoint §ors, const TilePoint ¢er, int16 radius) { TileRegion sectorRegion; sectorRegion.min.u = clamp( 0, (center.u - radius) >> kSectorShift, sectors.u); sectorRegion.min.v = clamp( 0, (center.v - radius) >> kSectorShift, sectors.v); sectorRegion.max.u = clamp( 0, (center.u + radius + kSectorMask) >> kSectorShift, sectors.u); sectorRegion.max.v = clamp( 0, (center.v + radius + kSectorMask) >> kSectorShift, sectors.v); sectorRegion.min.z = sectorRegion.max.z = 0; return sectorRegion; } //------------------------------------------------------------------------ // Return the first object within the specified region ObjectID RadialObjectIterator::first(GameObject **obj, int16 *dist) { GameObject *currentObject = nullptr; ObjectID currentObjectID; int16 currentDist = 0; currentObjectID = SectorRegionObjectIterator::first(¤tObject); while (currentObjectID != Nothing && (currentDist = computeDist(currentObject->getLocation())) > radius) { currentObjectID = SectorRegionObjectIterator::next(¤tObject); } if (dist != nullptr) *dist = currentDist; if (obj != nullptr) *obj = currentObject; return currentObjectID; } //------------------------------------------------------------------------ // Return the next object within the specified region ObjectID RadialObjectIterator::next(GameObject **obj, int16 *dist) { GameObject *currentObject = nullptr; ObjectID currentObjectID; int16 currentDist = 0; do { currentObjectID = SectorRegionObjectIterator::next(¤tObject); } while (currentObjectID != Nothing && (currentDist = computeDist(currentObject->getLocation())) > radius); if (dist != nullptr) *dist = currentDist; if (obj != nullptr) *obj = currentObject; return currentObjectID; } /* ======================================================================= * CircularObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ // Compute the distance to the specified point from the center coordinates int16 CircularObjectIterator::computeDist(const TilePoint &tp) { // Simply use quickHDistance() return (tp - getCenter()).quickHDistance(); } /* ======================================================================= * RingObjectIterator member functions * ======================================================================= */ ObjectID RingObjectIterator::first(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; currentObjectID = CircularObjectIterator::first(¤tObject); while (currentObjectID != Nothing && computeDist(currentObject->getLocation()) < innerDist) { currentObjectID = CircularObjectIterator::next(¤tObject); } if (obj != nullptr) *obj = currentObject; return currentObjectID; } ObjectID RingObjectIterator::next(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; do { currentObjectID = CircularObjectIterator::next(¤tObject); } while (currentObjectID != Nothing && computeDist(currentObject->getLocation()) < innerDist); if (obj != nullptr) *obj = currentObject; return currentObjectID; } /* ======================================================================= * DispRegionObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ // Compute the distance to the specified point from the center coordinates int16 DispRegionObjectIterator::computeDist(const TilePoint &tp) { // Compute distance from object to screen center. // REM: remember to add in Z there somewhere. return ABS(getCenter().u - tp.u) + ABS(getCenter().v - tp.v); } /* ======================================================================= * RegionalObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ // Compute the sector region overlaying the specified tilepoint region TileRegion RegionalObjectIterator::computeSectorRegion( const TilePoint §ors, const TilePoint &min, const TilePoint &max) { TileRegion sectorRegion; sectorRegion.min.u = clamp( 0, min.u >> kSectorShift, sectors.u); sectorRegion.min.v = clamp( 0, min.v >> kSectorShift, sectors.v); sectorRegion.max.u = clamp( 0, (max.u + kSectorMask) >> kSectorShift, sectors.u); sectorRegion.max.v = clamp( 0, (max.v + kSectorMask) >> kSectorShift, sectors.v); sectorRegion.min.z = sectorRegion.max.z = 0; return sectorRegion; } //------------------------------------------------------------------------ // Determine if the specified point is within the region inline bool RegionalObjectIterator::inRegion(const TilePoint &tp) { return tp.u >= minCoords.u && tp.v >= minCoords.v && tp.u < maxCoords.u && tp.v < maxCoords.v; } //------------------------------------------------------------------------ // Return the first object within the specified region ObjectID RegionalObjectIterator::first(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; currentObjectID = SectorRegionObjectIterator::first(¤tObject); if (currentObjectID == Nothing) return Nothing; while (currentObjectID != Nothing && !inRegion(currentObject->getLocation())) { currentObjectID = SectorRegionObjectIterator::next(¤tObject); } if (obj != nullptr) *obj = currentObject; return currentObjectID; } //------------------------------------------------------------------------ // Return the next object within the specified region ObjectID RegionalObjectIterator::next(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; do { currentObjectID = SectorRegionObjectIterator::next(¤tObject); } while (currentObjectID != Nothing && !inRegion(currentObject->getLocation())); if (obj != nullptr) *obj = currentObject; return currentObjectID; } /* ======================================================================= * RectangularObjectIterator member functions * ======================================================================= */ // Constructor RectangularObjectIterator::RectangularObjectIterator( GameWorld *world, const TilePoint &c, const TilePoint &cdelta1, const TilePoint &cdelta2) : RegionalObjectIterator( world, MinTilePoint(c, c + cdelta1, c + cdelta2, c + cdelta1 + cdelta2), MaxTilePoint(c, c + cdelta1, c + cdelta2, c + cdelta1 + cdelta2)), coords1(c), coords2(c + cdelta1), coords3(c + cdelta1 + cdelta2), coords4(c + cdelta2), center((c + (cdelta1 + cdelta2) / 2)) { } //------------------------------------------------------------------------ // Determine if the specified point is within the region inline bool RectangularObjectIterator::inRegion(const TilePoint &tp) { return sameSide(coords1, coords2, center, tp) && sameSide(coords2, coords3, center, tp) && sameSide(coords3, coords4, center, tp) && sameSide(coords4, coords1, center, tp); } //------------------------------------------------------------------------ // Return the first object within the specified region ObjectID RectangularObjectIterator::first(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; currentObjectID = RegionalObjectIterator::first(¤tObject); while (currentObjectID != Nothing && !inRegion(currentObject->getLocation())) { currentObjectID = RegionalObjectIterator::next(¤tObject); } if (obj != nullptr) *obj = currentObject; return currentObjectID; } //------------------------------------------------------------------------ // Return the next object within the specified region ObjectID RectangularObjectIterator::next(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; do { currentObjectID = RegionalObjectIterator::next(¤tObject); } while (currentObjectID != Nothing && !inRegion(currentObject->getLocation())); if (obj != nullptr) *obj = currentObject; return currentObjectID; } /* ======================================================================= * TriangularObjectIterator member functions * ======================================================================= */ // Constructor TriangularObjectIterator::TriangularObjectIterator( GameWorld *world, const TilePoint &c1, const TilePoint &c2, const TilePoint &c3) : RegionalObjectIterator( world, MinTilePoint(c1, c2, c3), MaxTilePoint(c1, c2, c3)), coords1(c1), coords2(c2), coords3(c3) { } //------------------------------------------------------------------------ // Determine if the specified point is within the region inline bool TriangularObjectIterator::inRegion(const TilePoint &tp) { return sameSide(coords1, coords2, coords3, tp) && sameSide(coords1, coords3, coords2, tp) && sameSide(coords2, coords3, coords1, tp) ; } //------------------------------------------------------------------------ // Return the first object within the specified region ObjectID TriangularObjectIterator::first(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; currentObjectID = RegionalObjectIterator::first(¤tObject); while (currentObjectID != Nothing && !inRegion(currentObject->getLocation())) { currentObjectID = RegionalObjectIterator::next(¤tObject); } if (obj != nullptr) *obj = currentObject; return currentObjectID; } //------------------------------------------------------------------------ // Return the next object within the specified region ObjectID TriangularObjectIterator::next(GameObject **obj) { GameObject *currentObject; ObjectID currentObjectID; do { currentObjectID = RegionalObjectIterator::next(¤tObject); } while (currentObjectID != Nothing && !inRegion(currentObject->getLocation())); if (obj != nullptr) *obj = currentObject; return currentObjectID; } /* ======================================================================= * CenterRegionObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ GameWorld *CenterRegionObjectIterator::CenterWorld(void) { ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID()); return ar->getWorld(); } TilePoint CenterRegionObjectIterator::MinCenterRegion(void) { ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID()); return ar->getRegion().min; } TilePoint CenterRegionObjectIterator::MaxCenterRegion(void) { ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID()); return ar->getRegion().max; } /* ======================================================================= * ActiveRegionObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ bool ActiveRegionObjectIterator::firstActiveRegion(void) { activeRegionIndex = -1; return nextActiveRegion(); } //------------------------------------------------------------------------ bool ActiveRegionObjectIterator::nextActiveRegion(void) { 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(void) { if (!firstActiveRegion()) return false; sectorCoords.u = baseSectorCoords.u; sectorCoords.v = baseSectorCoords.v; if (sectorBitMask & 1) { if (!nextSector()) return false; } return true; } //------------------------------------------------------------------------ bool ActiveRegionObjectIterator::nextSector(void) { 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(void) { if (subIter != nullptr) delete subIter; } ObjectID RecursiveContainerIterator::first(GameObject **obj) { if (subIter != nullptr) delete subIter; id(container->IDChild()), if (obj != nullptr) *obj = id != Nothing ? GameObject::objectAddress(id) : nullptr; return id; } ObjectID RecursiveContainerIterator::next(GameObject **obj) { GameObject *currentObj; if (subIter) { ObjectID currentID = subIter->next(¤tObj); if (currentID != Nothing) { if (obj) *obj = currentObj; return currentID; } delete subIter; subIter = nullptr; currentObj = GameObject::objectAddress(id); } else { currentObj = GameObject::objectAddress(id); if (currentObj->IDChild()) { subIter = NEW_ITER RecursiveContainerIterator(currentObj); assert(subIter); return subIter->first(obj); } } id = currentObj->IDNext(); if (obj != nullptr) *obj = id != Nothing ? GameObject::objectAddress(id) : nullptr; return id; } #endif /* ======================================================================= * RecursiveContainerIterator member functions * ======================================================================= */ // This class iterates through every object within a container ObjectID RecursiveContainerIterator::first(GameObject **obj) { GameObject *rootObj = GameObject::objectAddress(root); id = rootObj->IDChild(); if (obj != nullptr) *obj = id != Nothing ? GameObject::objectAddress(id) : nullptr; return id; } ObjectID RecursiveContainerIterator::next(GameObject **obj) { GameObject *currentObj = GameObject::objectAddress(id); // If this object has a child, then the next object (id) is the child. // If it has no child, then check for sibling. if ((id = currentObj->IDChild()) == 0) { // If this object has a sibling, then the next object (id) is the sibling. // If it has no sibling, then check for parent. while ((id = currentObj->IDNext()) == 0) { // If this object has a parent, then the get the parent. if ((id = currentObj->IDParent()) != 0) { // If the parent is the root, then we're done. if (id == Nothing || id == root) return 0; // Set the current object to the parent, and then // Go around the loop once again and get the sibling of the parent. // The loop will keep going up until we either find an object that // has a sibling, or we hit the original root object. currentObj = GameObject::objectAddress(id); } } } if (obj != nullptr) *obj = id != Nothing ? GameObject::objectAddress(id) : nullptr; return id; } /* ======================================================================= * Test for object collision * ======================================================================= */ GameObject *objectCollision(GameObject *obj, GameWorld *world, const TilePoint &loc) { ProtoObj *proto = obj->proto(); TileRegion volume; GameObject *obstacle; volume.min.u = loc.u - proto->crossSection; volume.min.v = loc.v - proto->crossSection; volume.max.u = loc.u + proto->crossSection; volume.max.v = loc.v + proto->crossSection; volume.min.z = loc.z; volume.max.z = loc.z + proto->height; // Adjust MIN Z for the fact that they can step over obstacles. if (isActor(obj)) volume.min.z += kMaxStepHeight / 2; // Constructor CircularObjectIterator iter(world, loc, proto->crossSection + 32); for (iter.first(&obstacle); obstacle != nullptr; iter.next(&obstacle)) { TilePoint tp = obstacle->getLocation(); ProtoObj *obstacleProto = obstacle->proto(); if (obstacle == obj) continue; if (tp.z < volume.max.z && tp.z + obstacleProto->height > volume.min.z && tp.u - obstacleProto->crossSection < volume.max.u && tp.u + obstacleProto->crossSection > volume.min.u && tp.v - obstacleProto->crossSection < volume.max.v && tp.v + obstacleProto->crossSection > volume.min.v) { // If the actor is dead, then it is not an obstacle. if (isActor(obstacle) && ((Actor *)obstacle)->isDead()) continue; return obstacle; } } return nullptr; } /* ======================================================================= * Test for line of sight between two objects * ======================================================================= */ bool lineOfSight(GameObject *obj1, GameObject *obj2, uint32 terrainMask) { GameWorld *world; // If the two objects are not in the same world, there is no line // of sight if ((world = obj1->world()) != obj2->world()) return false; #if 0 if (isActor(obj1)) { Actor *a1 = (Actor *) obj1; if (!a1->hasEffect(actorSeeInvis)) { if (!isActor(obj2) && obj2->isInvisible()) return false; else if (isActor(obj2)) { Actor *a2 = (Actor *) obj2; if (a2->hasEffect(actorInvisible)) return false; } } } #endif uint32 opaqueTerrain = ~terrainMask; ProtoObj *obj1proto = obj1->proto(), *obj2proto = obj2->proto(); TilePoint obj1Loc = obj1->getWorldLocation(), obj2Loc = obj2->getWorldLocation(); obj1Loc.z += obj1proto->height * 7 / 8; obj2Loc.z += obj2proto->height * 7 / 8; return (lineTerrain( world->mapNum, obj1Loc, obj2Loc, opaqueTerrain) & opaqueTerrain) == 0; } /* ======================================================================= * Test for line of sight between object and _data.location * ======================================================================= */ bool lineOfSight(GameObject *obj, const TilePoint &loc, uint32 terrainMask) { GameWorld *world = obj->world(); uint32 opaqueTerrain = ~terrainMask; ProtoObj *proto = obj->proto(); TilePoint objLoc = obj->getWorldLocation(); objLoc.z += proto->height * 7 / 8; return (lineTerrain( world->mapNum, objLoc, loc, opaqueTerrain) & opaqueTerrain) == 0; } /* ======================================================================= * Test for line of sight between two _data.locations * ======================================================================= */ bool lineOfSight( GameWorld *world, const TilePoint &loc1, const TilePoint &loc2, uint32 terrainMask) { uint32 opaqueTerrain = ~terrainMask; return (lineTerrain( world->mapNum, loc1, loc2, opaqueTerrain) & opaqueTerrain) == 0; } /* ======================================================================= * Test if object is obscured by terrain * ======================================================================= */ bool objObscured(GameObject *testObj) { bool obscured = false; if (isObject(testObj)) { Point16 drawPos, org; ObjectSpriteInfo objSprInfo; ColorTable objColors; ProtoObj *proto = testObj->proto(); // Calculate X, Y coordinates of the sprite TileToScreenCoords(testObj->getLocation(), drawPos); drawPos.x += fineScroll.x; drawPos.y += fineScroll.y; objSprInfo = proto->getSprite(testObj, ProtoObj::objOnGround); testObj->getColorTranslation(objColors); if (visiblePixelsInSprite(objSprInfo.sp, objSprInfo.flipped, objColors, drawPos, testObj->getLocation(), objRoofID(testObj)) <= 5) obscured = true; } return obscured; } /* ======================================================================= * Setup object interaction test by placing two objects inside another and displaying a container * ======================================================================= */ extern gPanelList *playControls; extern gPanelList *trioControls; extern gPanelList *indivControls; // Kludge!!! int16 openMindType; #ifdef hasReadyContainers APPFUNC(cmdBrain) { int16 part = clamp(0, ev.mouse.x * 3 / ev.panel->getExtent().width, 2); //assert( indivControls->getEnabled() ); if (!indivControls->getEnabled()) return; if (ev.eventType == gEventNewValue) { //WriteStatusF( 4, "Brain Attempt " ); GameObject *container = indivCviewTop->containerObject; ContainerIterator iter(container); GameObject *item; openMindType = part; assert(container == indivCviewBot->containerObject); // Get the actor's mind container while (iter.next(&item) != Nothing) { ProtoObj *proto = item->proto(); if (proto->classType == protoClassIdeaContainer) { item->use(item->IDParent()); break; } } } else if (ev.eventType == gEventMouseMove) { if (ev.value == gCompImage::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(void) { 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, 0); 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, 0); 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, 0); indivCviewBot->setScrollOffset(1); // set the object draw up by one indivCviewBot->draw(); // >>> //new gGenericControl(*indivControls,Rect16(488,265,40,40),0,cmdBrain); } void cleanupReadyContainers(void) { 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(void) { } 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(void) { 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->_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(void) { backgroundSimulationPaused = true; } // ------------------------------------------------------------------------ void resumeBackgroundSimulation(void) { backgroundSimulationPaused = false; } //------------------------------------------------------------------- // This function simply calls the GameObject::updateState() method // for all active objects directly within a world. void updateObjectStates(void) { 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(void) { objectStatesPaused = true; } //------------------------------------------------------------------- void resumeObjectStates(void) { objectStatesPaused = false; } } // end of namespace Saga2