/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * aint32 with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * Based on the original sources * Faery Tale II -- The Halls of the Dead * (c) 1993-1996 The Wyrmkeep Entertainment Co. */ #define FORBIDDEN_SYMBOL_ALLOW_ALL // FIXME: Remove #include "saga2/std.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/savefile.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'), worldListID = MKTAG('W', 'R', 'L', 'D'), objProtoID = MKTAG('P', 'R', 'O', 0), actorProtoID = MKTAG('P', 'R', 'O', 1); /* ===================================================================== * Locals * ===================================================================== */ Common::Array nameList; // handle to list of names uint32 nameListCount; ProtoObj *objectProtos = nullptr; // object prototypes ActorProto *actorProtos = nullptr; // actor prototypes uint16 *tempActorCount = nullptr; // array of temporary actor counts int16 objectProtoCount, // object prototype count actorProtoCount; // actor prototype count GameObject *objectList = nullptr; // list of all objects int16 objectCount; // count of objects Actor *actorList = nullptr; // list of all actors int16 actorCount; 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 Point16 fineScroll; // current scroll pos extern hResContext *imageRes; // image resource handle extern PlayerActor playerList[]; // Master list of all PlayerActors extern SpellStuff spellBook[]; extern DisplayNodeList mainDisplayList; extern ObjectID pickedObject; const uint32 imageGroupID = MKTAG('I', 'M', 'A', 'G'); extern TilePoint incDirTable[]; 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; } //----------------------------------------------------------------------- // Constructor -- initial object construction GameObject::GameObject(const ResourceGameObject &res) { prototype = &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; } //----------------------------------------------------------------------- // Constructor -- reconstruct from archive buffer GameObject::GameObject(void **buf) { GameObjectArchive *a = (GameObjectArchive *)*buf; // Convert the protoype index into an object proto pointer prototype = a->protoIndex != -1 ? &objectProtos[a->protoIndex] : nullptr; _data.projectDummy = 0; _data.location = a->location; _data.nameIndex = a->nameIndex; _data.parentID = a->parentID; _data.siblingID = a->siblingID; _data.childID = a->childID; _data.script = a->script; _data.objectFlags = a->objectFlags; _data.hitPoints = a->hitPoints; _data.bParam = a->bParam; _data.massCount = a->misc; _data.missileFacing = a->missileFacing; _data.currentTAG = a->currentTAG; _data.sightCtr = a->sightCtr; memset(&_data.reserved, 0, sizeof(_data.reserved)); _data.obj = this; *buf = &a[1]; } //----------------------------------------------------------------------- // Return the number of bytes need to archive this object in an archive // buffer. int32 GameObject::archiveSize(void) { return sizeof(GameObjectArchive); } //----------------------------------------------------------------------- // Archive this object in a buffer void *GameObject::archive(void *buf) { GameObjectArchive *a = (GameObjectArchive *)buf; // Convert the prototype pointer to a prototype index a->protoIndex = prototype != nullptr ? prototype - objectProtos : -1; a->location = _data.location; a->nameIndex = _data.nameIndex; a->parentID = _data.parentID; a->siblingID = _data.siblingID; a->childID = _data.childID; a->script = _data.script; a->objectFlags = _data.objectFlags; a->hitPoints = _data.hitPoints; a->bParam = _data.bParam; a->misc = _data.massCount; a->missileFacing = _data.missileFacing; a->currentTAG = _data.currentTAG; a->sightCtr = _data.sightCtr; return &a[1]; } // Same as above but use object addresses instead of ID's bool isObject(GameObject *obj) { return (obj >= objectList && (uint8 *)obj < (uint8 *)objectList + objectListSize); } bool isActor(GameObject *obj) { return (obj >= actorList && (uint8 *)obj < (uint8 *)actorList + actorListSize); } bool isWorld(GameObject *obj) { return (obj >= worldList && (uint8 *)obj < (uint8 *)worldList + worldListSize); } // 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 >= actorCount) error("Invalid object ID: %d!", id); return actorList != nullptr ? &actorList[id - ActorBaseID] : nullptr; } ProtoObj *GameObject::protoAddress(ObjectID id) { GameObject *obj = objectAddress(id); return obj ? obj->prototype : nullptr ; } 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 ObjectID id; id = (GameObject *)this - objectList; if (id < objectCount) return id; id = (Actor *)this - actorList; if (id < actorCount) return id + ActorBaseID; id = (GameWorld *)this - worldList; if (id < worldCount) return id + WorldBaseID; return Nothing; } // 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 >= Nothing && 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); GameObject *parent = GameObject::objectAddress(newParent); // 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 strncpy(nameBuf, objName(), size - 1); nameBuf[size - 1] = 0; assert(strlen(objName()) < 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 = 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; int16 baseManaAmount; manaAmount = player->getEffStats()->mana(manaColor); baseManaAmount = player->getBaseStats().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())) { globalContainerList.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(); if (!isMoving()) { if (objObscured(this)) { _data.objectFlags |= objectObscured; } else { _data.objectFlags &= ~objectObscured; } } if (w->getSector(_data.location.u >> kSectorShift, _data.location.v >> kSectorShift)->isActivated()) { activate(); } } else { _data.objectFlags &= ~objectObscured; if ((isActor(_data.parentID) && isPlayerActor((Actor *)parent)) || (isObject(_data.parentID) && parent->isOpen()) ) { globalContainerList.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(); } // _data.massCount should never go negitive assert(_data.massCount >= 0); } 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(); } // _data.massCount should never go negitive assert(_data.massCount >= 0); } 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; // ObjectID id = thisID(); if (isWorld(this)) error("World copying not allowed."); if (isActor(this)) { // newObj = newActor(); // newObj->move( l ); // REM: Call actor copy function... 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 = _data.massCount; 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; 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 = globalContainerList.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 < elementsof(a->armorObjects); i++) if (a->armorObjects[i] == id) a->wear(Nothing, i); } unstack(); if (mouseInfo.getObject() == this) 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; 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; 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 * ======================================================================= */ char *GameObject::nameText(uint16 index) { if (index < 0 || index >= nameListCount) return "Bad Name Index"; return nameList[index]; } #define INTANGIBLE_MASK (ProtoObj::isEnchantment|ProtoObj::isSpell|ProtoObj::isSkill) TilePoint GameObject::getFirstEmptySlot(GameObject *obj) { int16 slotCount = 0; 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 = rand() & 0x7; do { TilePoint probeLoc; StandingTileInfo sti; // Compute a _data.location to place the object probeLoc = _data.location + incDirTable[dir] * dist; probeLoc.u += (rand() & 0x3) - 2; probeLoc.v += (rand() & 0x3) - 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; } } void GameObject::protoAddressToOffset() { ProtoObj *objectBase = &objectProtos[0]; ProtoObj *actorBase = &actorProtos[0]; int32 newProto, size = sizeof(ResourceObjectPrototype) + 4;//Add 4 for jump Table if (prototype == nullptr) return; warning("STUB: GameObject::protoAddressToOffset(): unsafe pointer arithmetics"); if (isActor(this)) newProto = (prototype - actorBase) / size; else newProto = (prototype - objectBase) / size; prototype = (ProtoObj *)newProto; } 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, *timerInList; // 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()) { timerList->_timers.remove(*it); delete *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) { timerList->_timers.remove(*it); delete *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) { timerList->_timers.remove(*it); delete *it; } delete timerList; } } // Sensor related member functions //----------------------------------------------------------------------- // Add the specified sensor to this object's sensor list bool GameObject::addSensor(Sensor *newSensor) { SensorList *sensorList; Sensor *sensorInList; // 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 (sensorInList = (Sensor *)sensorList->first(); sensorInList != nullptr; sensorInList = (Sensor *)sensorInList->next()) { assert(sensorInList->getObject() == this); if (newSensor->thisID() == sensorInList->thisID()) { sensorInList->remove(); delete sensorInList; break; } } // Put the new sensor into the list sensorList->addTail(*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) { Sensor *sensor; // Search the sensor list for a sensor with the specified ID for (sensor = (Sensor *)sensorList->first(); sensor != nullptr; sensor = (Sensor *)sensor->next()) { if (sensor->thisID() == id) { // Remove the sensor, then delete it sensor->remove(); delete sensor; // If the list is now empty, delete it if (sensorList->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) { Sensor *sensor, *nextSensor; // Iterate through the sensors for (sensor = (Sensor *)sensorList->first(); sensor != nullptr; sensor = nextSensor) { // Save the pointer to the next sensor nextSensor = (Sensor *)sensor->next(); // Remove the sensor, then delete it sensor->remove(); delete sensor; } // Delete this actor's sensor list 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 *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 // 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) { if (isActor(this)) return (ActorProto *)prototype - actorProtos; else return prototype - objectProtos; } //------------------------------------------------------------------- // Set the prototype of an object to a given prototype number void GameObject::setProtoNum(int32 nProto) { if (isActor(this)) prototype = &actorProtos[nProto]; else { ObjectID oldParentID = _data.parentID; bool wasStacked = unstack(); // Unstack if it was in a stack prototype = &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)) globalContainerList.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(); } globalContainerList.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++; globalContainerList.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) { if (tileRes->seek(MKTAG('M', 'A', 'P', (char)map))) { int16 mapSize; // Size of map in MetaTiles mapSize = tileRes->readU16LE(); 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; } else { size.u = size.v = 0; sectorArraySize = 0; sectorArray = nullptr; mapNum = -1; } } //------------------------------------------------------------------- // Constructor -- reconstruct from archive buffer GameWorld::GameWorld(void **buf) { int16 *bufferPtr = (int16 *)*buf; size.u = size.v = *bufferPtr++; mapNum = *bufferPtr++; if (size.u != 0) { int32 sectorArrayBytes; sectorArraySize = size.u / kSectorSize; sectorArrayBytes = sectorArraySize * sectorArraySize * sizeof(Sector), sectorArray = new Sector[sectorArrayBytes](); if (sectorArray == nullptr) error("Unable to allocate world %d sector array", mapNum); memcpy(sectorArray, bufferPtr, sectorArrayBytes); bufferPtr = (int16 *)((int8 *)bufferPtr + sectorArrayBytes); } else { sectorArraySize = 0; sectorArray = nullptr; } *buf = bufferPtr; } //------------------------------------------------------------------- // 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; } //------------------------------------------------------------------- // Make an archive of this world void *GameWorld::archive(void *buf) { int16 *bufferPtr = (int16 *)buf; int32 sectorArrayBytes = sectorArraySize * sectorArraySize * sizeof(Sector); *bufferPtr++ = size.u; *bufferPtr++ = mapNum; memcpy(bufferPtr, sectorArray, sectorArrayBytes); warning("FIXME: Unsafe pointer arithmetics in GameWorld::archive()"); return (void *)(bufferPtr + sectorArrayBytes); } //------------------------------------------------------------------- // 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 static void readObjectPrototype(hResContext *con, ResourceObjectPrototype &obj) { obj.classType = con->readS16LE(); obj.script = con->readU16LE(); obj.nameIndex = con->readS16LE(); obj.iconSprite = con->readU16LE(); obj.groundSprite = con->readU16LE(); for (int i = 0; i < 4; ++i) obj.colorMap[i] = con->readByte(); obj.mass = con->readByte(); obj.bulk = con->readByte(); obj.crossSection = con->readByte(); obj.height = con->readByte(); obj.toughness = con->readByte(); obj.breakType = con->readByte(); obj.maxCapacity = con->readU16LE(); obj.lockType = con->readByte(); obj.acceptableItems = con->readByte(); obj.weaponDamage = con->readByte(); obj.weaponFireRate = con->readByte(); obj.maximumRange = con->readByte(); obj.missileType = con->readByte(); obj.whereWearable = con->readByte(); obj.damageAbsorbtion = con->readSByte(); obj.damageDivider = con->readSByte(); obj.defenseBonus = con->readSByte(); obj.maxCharges = con->readByte(); obj.chargeType = con->readByte(); obj.flags = con->readS16LE(); obj.price = con->readS16LE(); obj.heldSpriteBase = con->readS16LE(); // union obj.resistance = con->readS16LE(); obj.immunity = con->readS16LE(); obj.soundFXClass = con->readByte(); for (int i = 0; i < 7; ++i) obj.reserved[i] = con->readByte(); } static void readActorPrototype(hResContext *con, ResourceActorPrototype &act) { readObjectPrototype(con, act.proto); ActorAttributes *att = &act.ext.baseStats; att->archery = con->readByte(); att->swordcraft = con->readByte(); att->shieldcraft = con->readByte(); att->bludgeon = con->readByte(); att->throwing = con->readByte(); att->spellcraft = con->readByte(); att->stealth = con->readByte(); att->agility = con->readByte(); att->brawn = con->readByte(); att->lockpick = con->readByte(); att->pilfer = con->readByte(); att->firstAid = con->readByte(); att->spotHidden = con->readByte(); att->pad = con->readSByte(); att->vitality = con->readS16LE(); att->redMana = con->readS16LE(); att->orangeMana = con->readS16LE(); att->yellowMana = con->readS16LE(); att->greenMana = con->readS16LE(); att->blueMana = con->readS16LE(); att->violetMana = con->readS16LE(); act.ext.combatBehavior = con->readByte(); act.ext.gruntStyle = con->readByte(); act.ext.baseEffectFlags = con->readU32LE(); } 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()); name[s.size()] = '\0'; nameList.push_back(name); } nameListCount = count; // Load the Object prototype table objectProtoCount = listRes->size(objProtoID) / resourceObjProtoSize; if (objectProtoCount < 1) error("Unable to load Object Prototypes"); objectProtos = (ProtoObj *)malloc(objectProtoCount * sizeof (ProtoObj)); if (listRes->seek(objProtoID) == 0) 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 = &objectProtos[i]; readObjectPrototype(listRes, ro); switch (ro.classType) { case protoClassInventory: new (pr) InventoryProto(ro); break; case protoClassPhysContainer: new (pr) PhysicalContainerProto(ro); break; case protoClassKey: new (pr) KeyProto(ro); break; case protoClassBottle: new (pr) BottleProto(ro); break; case protoClassFood: // Food ProtoType new (pr) FoodProto(ro); break; case protoClassBludgeoningWeapon: new (pr) BludgeoningWeaponProto(ro); break; case protoClassSlashingWeapon: new (pr) SlashingWeaponProto(ro); break; case protoClassBow: new (pr) BowProto(ro); break; case protoClassWeaponWand: new (pr) WeaponWandProto(ro); break; case protoClassArrow: new (pr) ArrowProto(ro); break; case protoClassShield: new (pr) ShieldProto(ro); break; case protoClassArmor: new (pr) ArmorProto(ro); break; case protoClassTool: new (pr) ToolProto(ro); break; case protoClassBookDoc: new (pr) BookProto(ro); break; case protoClassScrollDoc: new (pr) ScrollProto(ro); break; case protoClassMap: new (pr) AutoMapProto(ro); break; case protoClassIdea: new (pr) IdeaProto(ro); break; case protoClassMemory: new (pr) MemoryProto(ro); break; case protoClassPsych: new (pr) PsychProto(ro); break; case protoClassSkill: new (pr) SkillProto(ro); initializeSkill((SkillProto *) pr, ((SkillProto *) pr)->getSpellID()); //initializeSkill(i,((SkillProto *) pr)->getSpellID()); break; case protoClassIdeaContainer: new (pr) IdeaContainerProto(ro); break; case protoClassMemoryContainer: new (pr) MemoryContainerProto(ro); break; case protoClassPsychContainer: new (pr) PsychContainerProto(ro); break; case protoClassSkillContainer: new (pr) SkillContainerProto(ro); break; case protoClassEnchantment: new (pr) EnchantmentProto(ro); enchantmentProto = i; break; case protoClassMonsterGenerator: new (pr) MonsterGeneratorProto(ro); break; // REM: add this in when we change the database. case protoClassEncounterGenerator: new (pr) EncounterGeneratorProto(ro); break; case protoClassMissionGenerator: new (pr) MissionGeneratorProto(ro); break; default: // Unrecognized prototypes now default to // an inventory item. new (pr) InventoryProto(ro); break; } } listRes->rest(); // Load the Actor prototype table actorProtoCount = listRes->size(actorProtoID) / resourceActProtoSize; if (actorProtoCount < 1) error("Unable to load Actor Prototypes"); actorProtos = (ActorProto *)malloc(actorProtoCount * sizeof(ActorProto)); if (listRes->seek(actorProtoID) == 0) error("Unable to load Actor Prototypes"); for (int i = 0; i < actorProtoCount; i++) { ResourceActorPrototype ra; ActorProto *pr = &actorProtos[i]; readActorPrototype(listRes, ra); new (pr) ActorProto(ra); } listRes->rest(); } //------------------------------------------------------------------- // Cleanup the prototype lists void cleanupPrototypes(void) { for (uint i = 0; i < nameListCount; ++i) { if (nameList[i]) delete[] nameList[i]; nameList.clear(); } if (actorProtos != nullptr) { free(actorProtos); actorProtos = nullptr; } if (objectProtos != nullptr) { free(objectProtos); objectProtos = nullptr; } } //------------------------------------------------------------------- // 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; } //------------------------------------------------------------------- // Save the array of temp actor counts void saveTempActorCount(SaveFileConstructor &saveGame) { saveGame.writeChunk( MakeID('A', 'C', 'N', 'T'), tempActorCount, actorProtoCount * sizeof(uint16)); } //------------------------------------------------------------------- // Load the array of temp actor counts void loadTempActorCount(SaveFileReader &saveGame) { tempActorCount = new uint16[saveGame.getChunkSize()]; saveGame.read(tempActorCount, saveGame.getChunkSize()); } //------------------------------------------------------------------- // 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); } currentWorld = &worldList[0]; setCurrentMap(currentWorld->mapNum); } //------------------------------------------------------------------- // Save the worlds to a save game file void saveWorlds(SaveFileConstructor &saveGame) { int16 i; int32 archiveBufSize = 0; void *archiveBuffer; void *bufferPtr; // Accumulate size of archive buffer // Add size of the current world's ID archiveBufSize += sizeof(ObjectID); for (i = 0; i < worldCount; i++) archiveBufSize += worldList[i].archiveSize(); archiveBuffer = malloc(archiveBufSize); if (archiveBuffer == nullptr) error("Unable to allocate world archive buffer"); bufferPtr = archiveBuffer; // Store the current world's ID *((ObjectID *)bufferPtr) = currentWorld->thisID(); bufferPtr = (ObjectID *)bufferPtr + 1; // Store the world data in the archive buffer for (i = 0; i < worldCount; i++) bufferPtr = worldList[i].archive(bufferPtr); // Write the archive buffer to the save file saveGame.writeChunk( MakeID('W', 'R', 'L', 'D'), archiveBuffer, archiveBufSize); free(archiveBuffer); } //------------------------------------------------------------------- // Load the worlds from a save game file void loadWorlds(SaveFileReader &saveGame) { int16 i; ObjectID currentWorldID; void *archiveBuffer; void *bufferPtr; // 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"); archiveBuffer = malloc(saveGame.getChunkSize()); if (archiveBuffer == nullptr) error("Unable to allocate world data buffer"); saveGame.read(archiveBuffer, saveGame.getChunkSize()); bufferPtr = archiveBuffer; // Get the current world's ID currentWorldID = *((ObjectID *)bufferPtr); bufferPtr = (ObjectID *)bufferPtr + 1; // Iterate through the world data, initializing the world list for (i = 0; i < worldCount; i++) // Restore the world's data new (&worldList[i]) GameWorld(&bufferPtr); free(archiveBuffer); // 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; } //------------------------------------------------------------------- // Initialize the Objects list static void readGameObject(hResContext *con, ResourceGameObject &obj) { obj.protoIndex = con->readS16LE(); obj.location.u = con->readS16LE(); obj.location.v = con->readS16LE(); obj.location.z = con->readS16LE(); obj.nameIndex = con->readU16LE(); obj.parentID = con->readU16LE(); obj.script = con->readU16LE(); obj.objectFlags = con->readU16LE(); obj.hitPoints = con->readByte(); obj.misc = con->readU16LE(); } void initObjects(void) { int16 i, resourceObjectCount; ResourceGameObject *resourceObjectList; 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"); // Add extra space for alias objects objectCount = resourceObjectCount + extraObjects; // Allocate memory for the object list objectListSize = objectCount * sizeof(GameObject); objectList = new GameObject[objectCount](); if (objectList == nullptr) error("Unable to load Objects"); // Allocate memory for the resource objects resourceObjectList = new ResourceGameObject[resourceObjectCount](); if (resourceObjectList == nullptr || listRes->seek(objListID) == 0) error("Unable to load Objects"); // Read the resource Objects for (int k = 0; k < resourceObjectCount; ++k) readGameObject(listRes, resourceObjectList[k]); 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]); } for (; i < objectCount; i++) { GameObject *obj = &objectList[i]; // Use the default constructor for the extra actors if (i == 4970) new (obj) GameObject; else new (obj) GameObject; } delete[] resourceObjectList; // 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 < actorCount; i++) { Actor *a = &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 } //------------------------------------------------------------------- // Save the object list to a save file void saveObjects(SaveFileConstructor &saveGame) { int16 i; void *archiveBuffer, *bufferPtr; int32 archiveBufSize; GameObject *currentObj; archiveBufSize = sizeof(objectLimboCount) + sizeof(actorLimboCount) + sizeof(importantLimboCount) + objectListSize; archiveBuffer = malloc(archiveBufSize); if (archiveBuffer == nullptr) error("Cannot allocate object list archive buffer"); bufferPtr = archiveBuffer; // Store the limbo counts *((int16 *)bufferPtr) = objectLimboCount; *((int16 *)bufferPtr + 1) = actorLimboCount; *((int16 *)bufferPtr + 2) = importantLimboCount; bufferPtr = (int16 *)bufferPtr + 3; // Store the object list memcpy(bufferPtr, objectList, objectListSize); // Convert the prototype pointers to prototype indexes for (i = 0, currentObj = (GameObject *)bufferPtr; i < objectCount; i++, currentObj++) { *((int16 *)¤tObj->prototype) = currentObj->prototype != nullptr ? currentObj->prototype - objectProtos : -1; } saveGame.writeChunk( MakeID('O', 'B', 'J', 'S'), archiveBuffer, archiveBufSize); free(archiveBuffer); } //------------------------------------------------------------------- // Load the object list from a save file void loadObjects(SaveFileReader &saveGame) { int16 i; GameObject *obj; // Restore the limbo counts saveGame.read(&objectLimboCount, sizeof(objectLimboCount)); saveGame.read(&actorLimboCount, sizeof(actorLimboCount)); saveGame.read(&importantLimboCount, sizeof(importantLimboCount)); // Restore the object list objectListSize = saveGame.bytesLeftInChunk(); objectCount = objectListSize / sizeof(GameObject); objectList = new GameObject[objectCount](); if (objectList == nullptr) error("Unable to load Objects"); saveGame.read(objectList, objectListSize); for (i = 0, obj = (GameObject *)objectList; i < objectCount; i++, obj++) { // convert prototype ID number to actual prototype address obj->prototype = *((int16 *)&obj->prototype) != -1 ? &objectProtos[*((int16 *)&obj->prototype)] : nullptr; } } //------------------------------------------------------------------- // Cleanup object list void cleanupObjects(void) { if (objectList != nullptr) delete[] objectList; 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--; } /* ======================================================================= * ActiveRegion member functions * ======================================================================= */ //------------------------------------------------------------------- // Update this active region 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) world->getSector(u, v)->deactivate(); } } // 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) world->getSector(u, v)->activate(); } } // 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 * ======================================================================= */ // Global active region array static ActiveRegion activeRegionList[playerActors]; //------------------------------------------------------------------- // Iterate through the active regions, updating each void updateActiveRegions(void) { int16 i; for (i = 0; i < elementsof(activeRegionList); i++) activeRegionList[i].update(); } //------------------------------------------------------------------- // Return a pointer to an active region given its PlayerActor's ID ActiveRegion *getActiveRegion(PlayerActorID id) { return &activeRegionList[id]; } //------------------------------------------------------------------- // Initialize the state of the active regions void initActiveRegions(void) { static PlayerActorID playerIDArray[playerActors] = { FTA_JULIAN, FTA_PHILIP, FTA_KEVIN }; int16 i; for (i = 0; i < playerActors; i++) { ActiveRegion *reg = &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; } } //------------------------------------------------------------------- // Save the active regions to a save file void saveActiveRegions(SaveFileConstructor &saveGame) { saveGame.writeChunk( MakeID('A', 'R', 'E', 'G'), &activeRegionList, sizeof(activeRegionList)); } //------------------------------------------------------------------- // Load the active regions from a save file void loadActiveRegions(SaveFileReader &saveGame) { saveGame.read(&activeRegionList, sizeof(activeRegionList)); } /* ======================================================================= * SectorRegionObjectIterator member functions * ======================================================================= */ //------------------------------------------------------------------------ // Constructor SectorRegionObjectIterator::SectorRegionObjectIterator(GameWorld *world) : searchWorld(world) { 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); 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; ObjectID currentObjectID; int16 currentDist; 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; ObjectID currentObjectID; int16 currentDist; 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); 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 >= elementsof(activeRegionList)) return false; int16 prevRegionIndex; currentRegion = &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 = &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 < elementsof(activeRegionList)); 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 *proto = obstacle->proto(); if (obstacle == obj) continue; if (tp.z < volume.max.z && tp.z + proto->height > volume.min.z && tp.u - proto->crossSection < volume.max.u && tp.u + proto->crossSection > volume.min.u && tp.v - proto->crossSection < volume.max.v && tp.v + proto->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; } /* ======================================================================= * Randomly Scatter Object Location In World * ======================================================================= */ //void GameWorld::randomScatter() //{ // ObjectIterator objIter( this ); // GameObject *objPtr; // ObjectID id; // TilePoint randLoc; // Point16 maxUV(100,100);//Temp Test // Iterate through each object in the world // while ( ( id = objIter.next( &objPtr ) ) != Nothing ) // { // Select a random Location. // randLoc.u = rand() % maxUV.x; // randLoc.v = rand() % maxUV.y; // objPtr->move(randLoc); // } //} /* ======================================================================= * 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) { mouseInfo.setText(nullptr); } else { //if (ev.value == gCompImage::enter) // set the text in the cursor if (part == 0) mouseInfo.setText(IDEAS_INVENT); else if (part == 1) mouseInfo.setText(SPELL_INVENT); else 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 < playerActors ; i++) { 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), *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); } if (TrioCviews) { int16 i; //delete TrioCviews; for (i = 0; i < kNumViews && i < playerActors ; i++) { delete TrioCviews[i]; TrioCviews[i] = nullptr; delete playerList[i].readyNode; 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 = actorCount / 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()) && rand() % MIN(objectLimboCount / 2, 60) == 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 = &actorList[actorIndex++]; // Wrap the counter around to the beginning if needed if (actorIndex >= actorCount) 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