/* 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 * along 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 "common/debug.h" #include "saga2/saga2.h" #include "saga2/dispnode.h" #include "saga2/tile.h" #include "saga2/motion.h" #include "saga2/task.h" #include "saga2/assign.h" #include "saga2/setup.h" #include "saga2/stimtype.h" #include "saga2/band.h" #include "saga2/sensor.h" #include "saga2/weapons.h" #include "saga2/localize.h" #include "saga2/intrface.h" #include "saga2/contain.h" #include "saga2/savefile.h" #include "saga2/combat.h" // Include files needed for SAGA script dispatch #include "saga2/script.h" #include "saga2/methods.r" // generated by SAGA namespace Saga2 { /* ===================================================================== * Constants * ===================================================================== */ // this is currently set to an arbitrary value for testing purposes. const uint16 defaultReach = 24; const uint32 actorListID = MKTAG('A', 'C', 'T', 'O'); /* ===================================================================== * Externals * ===================================================================== */ extern uint8 identityColors[256]; extern hResContext *listRes; // object list resource handle extern ProtoObj *objectProtos; extern ActorProto *actorProtos; extern Actor *actorList; extern int16 actorCount; extern int32 actorListSize; extern int16 actorLimboCount; extern PlayerActor playerList[]; // Master list of all PlayerActors bool unstickObject(GameObject *obj); extern ObjectSoundFXs *objectSoundFXTable; // the global object sound effects table #if DEBUG extern bool massAndBulkCount; #endif /* ===================================================================== * Globals -- might as well stick it here as anywhere. * ===================================================================== */ int16 factionTable[maxFactions][factionNumColumns]; // Indicates wether actor states should be paused bool actorStatesPaused; // Indicates wether player actors should have combat behavior bool combatBehaviorEnabled; /* ===================================================================== * ActorProto member functions * ===================================================================== */ //----------------------------------------------------------------------- // Return a bit mask indicating the properties of this object type uint16 ActorProto::containmentSet(void) { // All actors may also be weapons (indicating natural attacks) return ProtoObj::containmentSet() | isWeapon; } //----------------------------------------------------------------------- // Determine if the specified object can be contained by this object bool ActorProto::canContain(ObjectID dObj, ObjectID item) { assert(isActor(dObj)); assert(isObject(item) || isActor(item)); GameObject *itemPtr = GameObject::objectAddress(item); // Actors can contain any object, except worlds and other actors return isObject(item) && ((itemPtr->containmentSet() & ProtoObj::isIntangible) == 0 || itemPtr->possessor() == dObj); } //----------------------------------------------------------------------- // Determine if the specified object can be contained by this object at // the specified slot bool ActorProto::canContainAt( ObjectID dObj, ObjectID item, const TilePoint &) { assert(isActor(dObj)); assert(isObject(item) || isActor(item)); GameObject *itemPtr = GameObject::objectAddress(item); // Actors can contain any object, except worlds and other actors // REM: must add test to determine if specified slot is valid. return isObject(item) && ((itemPtr->containmentSet() & ProtoObj::isIntangible) == 0 || itemPtr->possessor() == dObj); } weaponID ActorProto::getWeaponID(void) { return weaponDamage; } //----------------------------------------------------------------------- // use this actor bool ActorProto::useAction(ObjectID dObj, ObjectID enactor) { assert(isActor(dObj)); Actor *a = (Actor *)GameObject::objectAddress(dObj); if (a->isDead()) return ((PhysicalContainerProto *)this)->PhysicalContainerProto::useAction(dObj, enactor); return false; } //----------------------------------------------------------------------- // Determine if this actor can be opened bool ActorProto::canOpen(ObjectID dObj, ObjectID) { assert(isActor(dObj)); return ((Actor *)GameObject::objectAddress(dObj))->isDead(); } //----------------------------------------------------------------------- // open this actor // Kludge! extern int16 openMindType; bool ActorProto::openAction(ObjectID dObj, ObjectID) { assert(isActor(dObj)); ContainerNode *cn; GameObject *dObjPtr = GameObject::objectAddress(dObj); assert(!dObjPtr->isOpen() && !dObjPtr->isLocked()); cn = CreateContainerNode(dObj, false, openMindType); cn->markForShow(); // Deferred open dObjPtr->_data.objectFlags |= objectOpen; // Set open bit; return true; } //----------------------------------------------------------------------- // close this actor bool ActorProto::closeAction(ObjectID dObj, ObjectID) { assert(isActor(dObj)); GameObject *dObjPtr = GameObject::objectAddress(dObj); ContainerNode *cn = globalContainerList.find(dObj, ContainerNode::deadType); assert(dObjPtr->isOpen()); assert(cn); // Delete the container (lazy delete) cn->markForDelete(); // Clear open bit dObjPtr->_data.objectFlags &= ~objectOpen; return true; } //----------------------------------------------------------------------- bool ActorProto::strikeAction( ObjectID dObj, ObjectID enactor, ObjectID item) { assert(isActor(dObj)); assert(isActor(enactor)); assert(isObject(item) || isActor(item)); Actor *a = (Actor *)GameObject::objectAddress(enactor); ActorAttributes *effStats = a->getStats(); GameObject *itemPtr = GameObject::objectAddress(item); ObjectSoundFXs *soundFXs; Location al = Location(a->getLocation(), a->IDParent()); if (itemPtr->acceptStrike(enactor, dObj, effStats->getSkillLevel(skillIDBludgeon))) return true; soundFXs = &objectSoundFXTable[soundFXClass]; makeCombatSound(soundFXs->soundFXMissed, al); return false; } bool ActorProto::damageAction( ObjectID dObj, ObjectID enactor, ObjectID target) { assert(isActor(dObj)); assert(isActor(enactor)); assert(isObject(target) || isActor(target)); Actor *a = (Actor *)GameObject::objectAddress(enactor); ActorAttributes *effStats = a->getStats(); WeaponStuff *ws = &getWeapon(getWeaponID()); GameObject *targetPtr = GameObject::objectAddress(target); uint8 damageSoundID; Location al = Location(a->getLocation(), a->IDParent()); damageSoundID = targetPtr->proto()->getDamageSound( objectSoundFXTable[soundFXClass]); if (damageSoundID != 0) makeCombatSound(damageSoundID, al); ws->implement( a, GameObject::objectAddress(target), GameObject::objectAddress(dObj), effStats->getSkillLevel(skillIDBrawn)); return true; } //----------------------------------------------------------------------- // Routine that is called when an object is dragged & dropped // onto an actor. bool ActorProto::acceptDropAction( ObjectID dObj, // object dropped on ObjectID enactor, // person doing dropping ObjectID droppedID, // ID of dropped object int count) { assert(isActor(dObj)); Actor *a = (Actor *)GameObject::objectAddress(dObj); GameObject *droppedObj = GameObject::objectAddress(droppedID); if (a->isDead()) { a->dropInventoryObject(droppedObj, count); return true; } Location newLoc; uint16 dropType; // For now, we'll just drop the object into the actor's // inventory. // // REM: We might want to arrange the inventory item in a // semi-sensible way, like putting the new item at the // head of the list. (Do this by changing the newLoc coord // fields...) // // REM: We might want to ask the object how it feels about // being given to an actor... // NOTE: Added check so that dropping an object on an actor who // already has the object will do nothing. if (droppedObj->IDParent() == dObj) return true; dropType = droppedObj->containmentSet(); scriptResult result; scriptCallFrame scf; scf.invokedObject = dObj; scf.enactor = enactor; scf.directObject = droppedID; scf.indirectObject = dObj; if (dropType & isIntangible) { // Set up the arguments we want to pass to the script scf.value = droppedObj->proto()->lockType + senseIdeaGreeting; // Invoke the script... if (dropType & isConcept) { result = runObjectMethod(dObj, Method_Actor_onTalkTo, scf); } else if (dropType & isPsych) { // What to do??? } else if (dropType & (isSpell | isSkill)) { // What to do??? // Cast the spell on the actor? } /* isConcept = (1<< 9), isPsych = (1<<10), isSpell = (1<<11), isSkill = (1<<12), isEnchantment = (1<<13), isTargetable = (1<<14), */ // mouseInfo.setIntent( GrabInfo::WalkTo ); } else { scf.value = count; result = runObjectMethod(dObj, Method_Actor_onReceive, scf); if (result == scriptResultFinished && scf.returnVal != actionResultNotDone) return scf.returnVal == actionResultSuccess; // Place the object in the actor's inventory (if possible) if (!a->placeObject(enactor, droppedID, true, count)) a->dropInventoryObject(droppedObj, count); } return true; } //----------------------------------------------------------------------- // Call the actor's "greet" script. bool ActorProto::greetActor( ObjectID dObj, // object dropped on ObjectID enactor) { // person doing dropping assert(isActor(dObj)); scriptCallFrame scf; scf.invokedObject = dObj; scf.enactor = enactor; scf.directObject = Nothing; scf.indirectObject = Nothing; scf.value = senseIdeaGreeting; return runObjectMethod(dObj, Method_Actor_onTalkTo, scf); } //----------------------------------------------------------------------- // cause damage directly bool ActorProto::acceptDamageAction( ObjectID dObj, ObjectID enactor, int8 absDamage, effectDamageTypes dType, int8 dice, uint8 sides, int8) { assert(isActor(dObj)); assert(isObject(enactor) || isActor(enactor)); int8 pdm = 0; //=perDieMod+(resistant ? -2 : 0); int16 damage = 0; Actor *a = (Actor *)GameObject::objectAddress(dObj); Actor *enactorPtr; int16 &vitality = a->effectiveStats.vitality; bool resistant = a->resists((effectResistTypes) dType); PlayerActorID pID; if (!a->isImmuneTo((effectImmuneTypes) dType)) { damage = absDamage; if (dice) for (int d = 0; d < abs(dice); d++) damage += ((rand() % sides) + pdm + 1) * (dice > 0 ? 1 : -1); } if (damage > 0 && resistant) damage /= 2; if (damage > 0 && isMagicDamage(dType) && makeSavingThrow()) damage /= 2; if (damage < 0) return acceptHealing(dObj, enactor, -damage); // Apply applicable armor adjustments if (dType == kDamageImpact || dType == kDamageSlash || dType == kDamageProjectile) { ArmorAttributes armorAttribs; a->totalArmorAttributes(armorAttribs); damage /= armorAttribs.damageDivider; damage = MAX(damage - armorAttribs.damageAbsorbtion, 0); } if (damage == 0) return false; if (isActor(enactor)) enactorPtr = (Actor *)GameObject::objectAddress(enactor); else { ObjectID possessorID; possessorID = GameObject::objectAddress(enactor)->possessor(); enactorPtr = possessorID != Nothing ? (Actor *)GameObject::objectAddress(possessorID) : NULL; } if (vitality > 0) { Location al = Location(a->getLocation(), a->IDParent()); if (gruntStyle > 0 && ((flags & ResourceObjectPrototype::objPropNoSurface) || (damage > 2 && (rand() % vitality) < (damage * 2)))) makeGruntSound(gruntStyle, al); if (enactorPtr != NULL) { enactorPtr->handleSuccessfulStrike( a, damage < vitality ? damage : vitality); } // If we've just lost all vitality, we're dead, else make a // morale check if (damage >= vitality) { MotionTask::die(*a); AddFactionTally(a->faction, factionNumKills, 1); if (enactorPtr != NULL) enactorPtr->handleSuccessfulKill(a); } else a->handleDamageTaken(damage); vitality -= damage; if (actorToPlayerID(a, pID)) { updateBrotherControls(pID); if (vitality > 0) { int16 baseVitality, oldVitality; baseVitality = a->getBaseStats()->vitality; oldVitality = vitality + damage; if (baseVitality >= vitality * 3 && baseVitality < oldVitality * 3) { StatusMsg(WOUNDED_STATUS, a->objName()); } else if (baseVitality * 2 >= vitality * 3 && baseVitality * 2 < oldVitality * 3) { StatusMsg(HURT_STATUS, a->objName()); } } } WriteStatusF(5, "Damage: %d", damage); } return true; } //----------------------------------------------------------------------- // cause healing directly bool ActorProto::acceptHealingAction( ObjectID dObj, ObjectID, int8 healing) { assert(isActor(dObj)); Actor *a = (Actor *)GameObject::objectAddress(dObj); int16 &vitality = a->effectiveStats.vitality; int16 maxVitality = (a->getBaseStats())->vitality; PlayerActorID pID; if (vitality > 0 && !a->hasEffect(actorDiseased)) { // If we've just lost all vitality, we're dead, else make a // morale check vitality += healing; vitality = clamp(0, vitality, maxVitality); if (actorToPlayerID(a, pID)) updateBrotherControls(pID); WriteStatusF(5, "Healing: %d", healing); } else return false; return true; } //----------------------------------------------------------------------- // Accept strike from an object (allows this actor to cause damage to // the striking object). bool ActorProto::acceptStrikeAction( ObjectID dObj, ObjectID enactor, ObjectID strikingObj, uint8 skillIndex) { assert(isActor(dObj)); assert(isActor(enactor)); const int toHitBase = 100; const int avgHitChance = toHitBase / 2; const int skillScalingFactor = (avgHitChance + ActorAttributes::skillLevels - 1) / ActorAttributes::skillLevels; const int dodgingBonus = 10; Actor *a = (Actor *)GameObject::objectAddress(dObj); ActorAttributes *effStats = a->getStats(); GameObject *weapon = GameObject::objectAddress(strikingObj); assert(weapon->proto()->containmentSet() & ProtoObj::isWeapon); Actor *enactorPtr = (Actor *)GameObject::objectAddress(enactor); ArmorAttributes armorAttribs; uint8 hitChance; if (a->isDead()) return weapon->damage(enactor, dObj); a->handleOffensiveAct((Actor *)GameObject::objectAddress(enactor)); // Sum up the armor attributes a->totalArmorAttributes(armorAttribs); // Determine "to hit" percentage hitChance = avgHitChance + ((int)skillIndex - (int)effStats->getSkillLevel(skillIDAgility)) * skillScalingFactor; // Factor in armor bonus hitChance -= armorAttribs.defenseBonus; // Factor in dodging bonus if any if (a->moveTask != NULL && a->moveTask->isDodging(enactorPtr)) hitChance -= dodgingBonus; hitChance = MAX(hitChance, 5); // Randomly determine hit success if (rand() % toHitBase < hitChance) { // Hit has succeeded GameObject *blockingObj = a->blockingObject(enactorPtr); bool blocked = false; // Test for block success if (blockingObj != NULL) { hitChance = avgHitChance + ((int)skillIndex - (int)blockingObj->proto()->getSkillValue(dObj)) * skillScalingFactor; if (rand() % toHitBase >= hitChance) { // The shield was hit blockingObj->acceptStrike( enactor, strikingObj, skillIndex); blocked = true; // Cause skill growth blockingObj->proto()->applySkillGrowth(dObj, 5); } } if (!blocked) { // The strike got through weapon->damage(enactor, dObj); // Notify the attacker of a successful strike enactorPtr->handleSuccessfulStrike(weapon); if (!a->isDead()) { int16 pmass = a->proto()->mass; if (pmass <= 100 || (rand() % 156) >= pmass - 100) { if ((rand() & 0x7) == 0) MotionTask::fallDown(*a, *enactorPtr); else MotionTask::acceptHit(*a, *enactorPtr); } } } return true; } else { // This actor has dodged the blow, apply agility growth PlayerActorID playerID; if (actorIDToPlayerID(dObj, playerID)) { PlayerActor *player = getPlayerActorAddress(playerID); player->skillAdvance(skillIDAgility, 1); } } return false; } //----------------------------------------------------------------------- // Insert another object into this object at the specified slot bool ActorProto::acceptInsertionAtAction( ObjectID dObj, ObjectID, ObjectID item, const TilePoint &where, int16 num) { enum { notInUse, heldInLeftHand, heldInRightHand, worn } inUseType; int wornWhere; assert(isActor(dObj)); assert(isObject(item)); GameObject *dObjPtr = GameObject::objectAddress(dObj); Actor *a = (Actor *)dObjPtr; GameObject *itemPtr = GameObject::objectAddress(item); GameObject *extractedObj = NULL; Location oldLoc(itemPtr->getLocation(), itemPtr->IDParent()); bool result; // Split the merged object if needed. if (itemPtr->isMergeable() // If mergeable && num < itemPtr->getExtra()) { // And not dropping whole pile if (num == 0) return false; // If mergeing zero, then do nothing extractedObj = itemPtr->extractMerged(itemPtr->getExtra() - num); if (extractedObj == NULL) return false; extractedObj->move(oldLoc); } // Determine if this object is simply being moved within this actor if (oldLoc.context == dObj) { // Determine if and where the object is in use by this actor if (a->leftHandObject == item) inUseType = heldInLeftHand; else if (a->rightHandObject == item) inUseType = heldInRightHand; else { int i; inUseType = notInUse; for (i = 0; i < ARMOR_COUNT; i++) { if (a->armorObjects[i] == item) { inUseType = worn; wornWhere = i; break; } } } } else inUseType = notInUse; // Do the deed itemPtr->move(Location(0, 0, 0, ImportantLimbo)); if (dObjPtr->canFitBulkwise(itemPtr) && dObjPtr->canFitMasswise(itemPtr)) { itemPtr->move(Location(where, dObj)); result = true; } else { itemPtr->move(oldLoc); if (extractedObj != NULL) GameObject::mergeWith(extractedObj, itemPtr, extractedObj->getExtra()); result = false; } // Re-equip the item if necessary if (inUseType != notInUse) { switch (inUseType) { case heldInLeftHand: a->holdInLeftHand(item); break; case heldInRightHand: a->holdInRightHand(item); break; case worn: a->wear(item, wornWhere); break; default: break; } } return result; } //----------------------------------------------------------------------- // Initiate a natural attack motion void ActorProto::initiateAttack(ObjectID attacker, ObjectID target) { assert(isActor(attacker)); assert(isObject(target) || isActor(target)); Actor *attackerPtr = (Actor *)GameObject::objectAddress(attacker); GameObject *targetPtr = GameObject::objectAddress(target); // Start the attack motion if (attackerPtr->appearance != NULL) { if (attackerPtr->isActionAvailable(actionSwingHigh)) MotionTask::oneHandedSwing(*attackerPtr, *targetPtr); else if (attackerPtr->isActionAvailable(actionTwoHandSwingHigh)) MotionTask::twoHandedSwing(*attackerPtr, *targetPtr); } else MotionTask::oneHandedSwing(*attackerPtr, *targetPtr); } //----------------------------------------------------------------------- // Given an object sound effect record, which sound should be made // when this object is damaged uint8 ActorProto::getDamageSound(const ObjectSoundFXs &soundFXs) { return !(flags & ResourceObjectPrototype::objPropNoSurface) ? !(flags & ResourceObjectPrototype::objPropHardSurface) ? soundFXs.soundFXHitFlesh : soundFXs.soundFXHitHard : 0; } //----------------------------------------------------------------------- // Do the background processing, if needed, for this object. void ActorProto::doBackgroundUpdate(GameObject *obj) { // get the ID for this object ObjectID actorID = obj->thisID(); // find out if this object is an actor if (isActor(actorID)) { // get a pointer to that actor GameObject *actorObj = GameObject::objectAddress(actorID); Actor *a = (Actor *)actorObj; if (!a->isActivated()) { // If this is a temporary actor waiting for expiration, // then decrement the expiration counter and possibly // delete the actor if ((a->flags & Actor::temporary) || a->isDead()) { if (a->deactivationCounter <= 0) { a->deleteObjectRecursive(); return; } else a->deactivationCounter--; } else { // If the actor has failed morale there is a random // chance of him regaining his courage if ((a->flags & Actor::afraid) && rand() % 128 == 0) a->flags &= ~Actor::afraid; } } // execute that actor's vitality update function ((Actor *)actorObj)->vitalityUpdate(); // do any updates directly related only to the brothers if (isPlayerActor(actorID)) { switch (actorID) { case ActorBaseID + FTA_JULIAN: playerList[FTA_JULIAN].recoveryUpdate(); break; case ActorBaseID + FTA_PHILIP: playerList[FTA_PHILIP].recoveryUpdate(); break; case ActorBaseID + FTA_KEVIN: playerList[FTA_KEVIN].recoveryUpdate(); break; default: // no action break; } } } // check for other updates ProtoObj::doBackgroundUpdate(obj); } // ------------------------------------------------------------------------ // Cause the user's associated skill to grow void ActorProto::applySkillGrowth(ObjectID enactor, uint8 points) { assert(isActor(enactor)); PlayerActorID playerID; if (actorIDToPlayerID(enactor, playerID)) { PlayerActor *player = getPlayerActorAddress(playerID); player->skillAdvance(skillIDBludgeon, points); if (rand() & 1) player->skillAdvance(skillIDBrawn, points); } } // ------------------------------------------------------------------------ bool ActorProto::canFitBulkwise(GameObject *container, GameObject *obj) { #if DEBUG if (massAndBulkCount) #endif { uint16 maxBulk = container->bulkCapacity(); uint16 totalBulk = container->totalContainedBulk(); return totalBulk + obj->totalBulk() <= maxBulk; } #if DEBUG return true; #endif } // ------------------------------------------------------------------------ bool ActorProto::canFitMasswise(GameObject *container, GameObject *obj) { assert(isActor(container)); #if DEBUG if (massAndBulkCount) #endif { Actor *a = (Actor *)container; // get the maxium amount of weight this character should be able to carry uint16 cmaxCapacity = container->massCapacity(); uint16 totalMass = a->totalContainedMass(); return totalMass + obj->totalMass() <= cmaxCapacity; } #if DEBUG return true; #endif } // ------------------------------------------------------------------------ // Return the maximum mass capacity for the specified container uint16 ActorProto::massCapacity(GameObject *container) { assert(isActor(container)); Actor *a = (Actor *)container; ActorAttributes *effStats = a->getStats(); return baseCarryingCapacity + effStats->getSkillLevel(skillIDBrawn) * carryingCapacityBonusPerBrawn; } // ------------------------------------------------------------------------ // Return the maximum bulk capacity for the specified container uint16 ActorProto::bulkCapacity(GameObject *) { return bulk * 4; } /* ===================================================================== * ActorArchive struct * ===================================================================== */ // This data structure is used in the creation of an actor archive. It // includes all of the fixed size data fields which must be preserved in // a save file without any of the overhead such as a base class or virtual // member functions. Some of the Actor data members, such as moveTask // currentTask and currentTransaction, are omitted because the links // to these other objects will archived with their respective 'Task' object. // Also, the assignment member was not included because it is a complex // variable sized data structure which will be asked to archive itself. struct ActorArchive { uint8 faction; uint8 colorScheme; int32 appearanceID; int8 attitude, mood; uint8 disposition; Direction currentFacing; int16 tetherLocU; int16 tetherLocV; int16 tetherDist; ObjectID leftHandObject, rightHandObject; uint16 knowledge[16]; uint16 schedule; uint8 conversationMemory[4]; uint8 currentAnimation, currentPose, animationFlags; uint8 flags; ActorPose poseInfo; int16 cycleCount; int16 kludgeCount; uint32 enchantmentFlags; uint8 currentGoal, deactivationCounter; ActorAttributes effectiveStats; uint8 actionCounter; uint16 effectiveResistance; uint16 effectiveImmunity; int16 recPointsPerUpdate; // fractional vitality recovery int16 currentRecoveryPoints; ObjectID leaderID; BandID followersID; ObjectID armorObjects[ARMOR_COUNT]; ObjectID currentTargetID; int16 scriptVar[actorScriptVars]; }; /* ===================================================================== * Actor member functions * ===================================================================== */ //----------------------------------------------------------------------- // Initialize all fields in the actor structure to neutral values. void Actor::init( int16 protoIndex, uint16 nameIndex, uint16 scriptIndex, int32 appearanceNum, uint8 colorSchemeIndex, uint8 factionNum, uint8 initFlags) { int i; debugC(1, kDebugActors, "Actor init flags: %d, permanent: %d", initFlags, initFlags & actorPermanent); // Fixup the prototype pointer to point to an actor prototype prototype = (ProtoObj *)&actorProtos[protoIndex]; // Initialize object fields // nameIndex = 0; setNameIndex(nameIndex); setScript(scriptIndex); _data.parentID = _data.siblingID = _data.childID = Nothing; _data.objectFlags = 0; _data.massCount = 0; _data.currentTAG = NoActiveItem; _data.hitPoints = 0; // Initialize actor field faction = factionNum; colorScheme = colorSchemeIndex; appearanceID = appearanceNum; attitude = 0; mood = 0; disposition = 0; currentFacing = dirDown; tetherLocU = 0; tetherLocV = 0; tetherDist = 0; leftHandObject = Nothing; rightHandObject = Nothing; schedule = 0; memset(&knowledge, 0, sizeof(knowledge)); // Initialize the rest of the data members *((uint32 *)conversationMemory) = 0L; currentAnimation = actionStand; currentPose = 0; animationFlags = 0; flags = 0; if (!(initFlags & actorPermanent)) flags |= temporary; poseInfo.flags = 0; poseInfo.actorFrameIndex = 0; poseInfo.actorFrameBank = 0; poseInfo.leftObjectIndex = 0; poseInfo.rightObjectIndex = 0; poseInfo.leftObjectOffset.x = poseInfo.leftObjectOffset.y = 0; poseInfo.rightObjectOffset.x = poseInfo.rightObjectOffset.y = 0; appearance = NULL; cycleCount = 0; kludgeCount = 0; moveTask = NULL; enchantmentFlags = 0L; curTask = NULL; currentGoal = actorGoalFollowAssignment; deactivationCounter = 0; _assignment = nullptr; memcpy( &effectiveStats, &((ActorProto *)prototype)->baseStats, sizeof(effectiveStats)); effectiveStats.vitality = MAX(effectiveStats.vitality, 1); actionCounter = 0; effectiveResistance = 0; effectiveImmunity = 0; recPointsPerUpdate = BASE_REC_RATE; currentRecoveryPoints = 0; leader = NULL; followers = NULL; for (i = 0; i < ARMOR_COUNT; i++) armorObjects[i] = Nothing; currentTarget = NULL; for (i = 0; i < actorScriptVars; i++) scriptVar[i] = 0; evalActorEnchantments(this); } //----------------------------------------------------------------------- // Actor constructor -- copies the resource fields and simply NULL's most // of the rest of the data members Actor::Actor(void) { prototype = nullptr; faction = 0; colorScheme = 0; appearanceID = 0; attitude = 0; mood = 0; disposition = 0; currentFacing = 0; tetherLocU = 0; tetherLocV = 0; tetherDist = 0; leftHandObject = 0; rightHandObject = 0; schedule = 0; for (int i = 0; i < 16; ++i) knowledge[i] = 0; // Initialize the rest of the data members for (int i = 0; i < 4; ++i) conversationMemory[i] = 0; currentAnimation = actionStand; currentPose = 0; animationFlags = 0; flags = 0; poseInfo.flags = 0; poseInfo.actorFrameIndex = 0; poseInfo.actorFrameBank = 0; poseInfo.leftObjectIndex = 0; poseInfo.rightObjectIndex = 0; poseInfo.leftObjectOffset.x = poseInfo.leftObjectOffset.y = 0; poseInfo.rightObjectOffset.x = poseInfo.rightObjectOffset.y = 0; appearance = nullptr; cycleCount = 0; kludgeCount = 0; moveTask = nullptr; enchantmentFlags = 0L; curTask = nullptr; currentGoal = actorGoalFollowAssignment; deactivationCounter = 0; _assignment = nullptr; memset(&effectiveStats, 0, sizeof(effectiveStats)); effectiveStats.vitality = MAX(effectiveStats.vitality, 1); actionCounter = 0; effectiveResistance = 0; effectiveImmunity = 0; recPointsPerUpdate = BASE_REC_RATE; currentRecoveryPoints = 0; leader = nullptr; followers = nullptr; for (int i = 0; i < ARMOR_COUNT; i++) armorObjects[i] = Nothing; currentTarget = nullptr; for (int i = 0; i < actorScriptVars; i++) scriptVar[i] = 0; } Actor::Actor(const ResourceActor &res) : GameObject(res) { int i; // Fixup the prototype pointer to point to an actor prototype prototype = prototype != NULL ? (ProtoObj *)&actorProtos[prototype - objectProtos] : NULL; // Copy the resource fields faction = res.faction; colorScheme = res.colorScheme; appearanceID = res.appearanceID; attitude = res.attitude; mood = res.mood; disposition = res.disposition; currentFacing = res.currentFacing; tetherLocU = res.tetherLocU; tetherLocV = res.tetherLocV; tetherDist = res.tetherDist; leftHandObject = res.leftHandObject; rightHandObject = res.rightHandObject; schedule = res.schedule; memcpy(&knowledge, &res.knowledge, sizeof(knowledge)); // Initialize the rest of the data members *((uint32 *)conversationMemory) = 0L; currentAnimation = actionStand; currentPose = 0; animationFlags = 0; flags = 0; poseInfo.flags = 0; poseInfo.actorFrameIndex = 0; poseInfo.actorFrameBank = 0; poseInfo.leftObjectIndex = 0; poseInfo.rightObjectIndex = 0; poseInfo.leftObjectOffset.x = poseInfo.leftObjectOffset.y = 0; poseInfo.rightObjectOffset.x = poseInfo.rightObjectOffset.y = 0; appearance = NULL; cycleCount = 0; kludgeCount = 0; moveTask = NULL; enchantmentFlags = 0L; curTask = NULL; currentGoal = actorGoalFollowAssignment; deactivationCounter = 0; _assignment = nullptr; memcpy( &effectiveStats, &((ActorProto *)prototype)->baseStats, sizeof(effectiveStats)); effectiveStats.vitality = MAX(effectiveStats.vitality, 1); actionCounter = 0; effectiveResistance = 0; effectiveImmunity = 0; recPointsPerUpdate = BASE_REC_RATE; currentRecoveryPoints = 0; leader = NULL; followers = NULL; for (i = 0; i < ARMOR_COUNT; i++) armorObjects[i] = Nothing; currentTarget = NULL; for (i = 0; i < actorScriptVars; i++) scriptVar[i] = 0; evalActorEnchantments(this); } //----------------------------------------------------------------------- // Reconstruct from archive buffer Actor::Actor(void **buf) : GameObject(buf) { void *bufferPtr = *buf; int i; // Fixup the prototype pointer to point to an actor prototype prototype = prototype != NULL ? (ProtoObj *)&actorProtos[prototype - objectProtos] : NULL; ActorArchive *a = (ActorArchive *)bufferPtr; // Read individual fields from buffer faction = a->faction; colorScheme = a->colorScheme; appearanceID = a->appearanceID; attitude = a->attitude; mood = a->mood; disposition = a->disposition; currentFacing = a->currentFacing; tetherLocU = a->tetherLocU; tetherLocV = a->tetherLocV; tetherDist = a->tetherDist; leftHandObject = a->leftHandObject; rightHandObject = a->rightHandObject; memcpy(&knowledge, &a->knowledge, sizeof(knowledge)); schedule = a->schedule; *((uint32 *)conversationMemory) = *((uint32 *)a->conversationMemory); currentAnimation = a->currentAnimation; currentPose = a->currentPose; animationFlags = a->animationFlags; flags = a->flags; memcpy(&poseInfo, &a->poseInfo, sizeof(poseInfo)); cycleCount = a->cycleCount; kludgeCount = a->kludgeCount; enchantmentFlags = a->enchantmentFlags; currentGoal = a->currentGoal; deactivationCounter = a->deactivationCounter; memcpy(&effectiveStats, &a->effectiveStats, sizeof(effectiveStats)); actionCounter = a->actionCounter; effectiveResistance = a->effectiveResistance; effectiveImmunity = a->effectiveImmunity; recPointsPerUpdate = a->recPointsPerUpdate; currentRecoveryPoints = a->currentRecoveryPoints; leader = a->leaderID != Nothing ? (Actor *)GameObject::objectAddress(a->leaderID) : NULL; followers = a->followersID != NoBand ? getBandAddress(a->followersID) : NULL; for (i = 0; i < ARMOR_COUNT; i++) armorObjects[i] = a->armorObjects[i]; currentTarget = a->currentTargetID != Nothing ? GameObject::objectAddress(a->currentTargetID) : NULL; for (i = 0; i < actorScriptVars; i++) scriptVar[i] = a->scriptVar[i]; bufferPtr = &a[1]; if (flags & hasAssignment) { delete _assignment; bufferPtr = constructAssignment(this, bufferPtr); } appearance = NULL; moveTask = NULL; curTask = NULL; // Return address of memory after actor archive *buf = bufferPtr; } //----------------------------------------------------------------------- // Destructor Actor::~Actor(void) { if (appearance != NULL) ReleaseActorAppearance(appearance); delete _assignment; } //----------------------------------------------------------------------- // Return the number of bytes needed to archive this actor int32 Actor::archiveSize(void) { int32 size = GameObject::archiveSize(); size += sizeof(ActorArchive); if (flags & hasAssignment) size += assignmentArchiveSize(this); return size; } //----------------------------------------------------------------------- // Archive this actor in a buffer void *Actor::archive(void *buf) { int i; ProtoObj *holdProto = prototype; // Modify the protoype temporarily so the GameObject::archive() // will store the index correctly if (prototype != NULL) prototype = &objectProtos[(ActorProto *)prototype - actorProtos]; // Let the base class archive its data buf = GameObject::archive(buf); // Restore the prototype pointer prototype = holdProto; ActorArchive *a = (ActorArchive *)buf; // Store individual fields in buffer a->faction = faction; a->colorScheme = colorScheme; a->appearanceID = appearanceID; a->attitude = attitude; a->mood = mood; a->disposition = disposition; a->currentFacing = currentFacing; a->tetherLocU = tetherLocU; a->tetherLocV = tetherLocV; a->tetherDist = tetherDist; a->leftHandObject = leftHandObject; a->rightHandObject = rightHandObject; memcpy(&a->knowledge, &knowledge, sizeof(a->knowledge)); a->schedule = schedule; *((uint32 *)a->conversationMemory) = *((uint32 *)conversationMemory); a->currentAnimation = currentAnimation; a->currentPose = currentPose; a->animationFlags = animationFlags; a->flags = flags; memcpy(&a->poseInfo, &poseInfo, sizeof(a->poseInfo)); a->cycleCount = cycleCount; a->kludgeCount = kludgeCount; a->enchantmentFlags = enchantmentFlags; a->currentGoal = currentGoal; a->deactivationCounter = deactivationCounter; memcpy(&a->effectiveStats, &effectiveStats, sizeof(a->effectiveStats)); a->actionCounter = actionCounter; a->effectiveResistance = effectiveResistance; a->effectiveImmunity = effectiveImmunity; a->recPointsPerUpdate = recPointsPerUpdate; a->currentRecoveryPoints = currentRecoveryPoints; a->leaderID = leader != NULL ? leader->thisID() : Nothing; a->followersID = followers != NULL ? getBandID(followers) : NoBand; for (i = 0; i < ARRAYSIZE(a->armorObjects); i++) a->armorObjects[i] = armorObjects[i]; a->currentTargetID = currentTarget != NULL ? currentTarget->thisID() : Nothing; for (i = 0; i < actorScriptVars; i++) a->scriptVar[i] = scriptVar[i]; buf = &a[1]; if (flags & hasAssignment) buf = archiveAssignment(this, buf); return buf; } //----------------------------------------------------------------------- // Return a newly created actor Actor *Actor::newActor( int16 protoNum, uint16 nameIndex, uint16 scriptIndex, int32 appearanceNum, uint8 colorSchemeIndex, uint8 factionNum, uint8 initFlags) { GameObject *limbo = objectAddress(ActorLimbo); Actor *a; debugC(2, kDebugActors, "Actor::newActor(protoNum = %d, nameIndex = %d, scriptIndex = %d, appearanceNum = %d, colorSchemeIndex = %d, factionNum = %d, initFlags = %d)", protoNum, nameIndex, scriptIndex, appearanceNum, colorSchemeIndex, factionNum, initFlags); if (limbo->IDChild() == Nothing) { int16 i; // Search actor list for first scavangable actor for (i = playerActors; i < actorCount; i++) { a = &actorList[i]; if ((a->flags & temporary) && !a->isActivated() && isWorld(a->IDParent())) break; } // REM: If things start getting really tight, we can // start recycling common objects... if (i >= actorCount) return NULL; } else { actorLimboCount--; a = (Actor *)limbo->child(); } a->setLocation(Location(0, 0, 0, Nothing)); a->init( protoNum, nameIndex, scriptIndex, appearanceNum, colorSchemeIndex, factionNum, initFlags); if (a->flags & temporary) { incTempActorCount(protoNum); debugC(1, kDebugActors, "Actors: Created temp actor %d (%s) new count:%d", a->thisID() - 32768, a->objName(), getTempActorCount(protoNum)); } return a; } //----------------------------------------------------------------------- // Delete this actor void Actor::deleteActor(void) { if (flags & temporary) { uint16 protoNum = (ActorProto *)prototype - actorProtos; decTempActorCount(protoNum); debugC(1, kDebugActors, "Actors: Deleting temp actor %d (%s) new count:%d", thisID() - 32768, objName(), getTempActorCount(protoNum)); } // Kill task if (curTask != NULL) { curTask->abortTask(); delete curTask; curTask = NULL; } // Kill motion task if (moveTask != NULL) moveTask->remove(); // If banded, remove from band if (leader != NULL) { assert(isActor(leader)); leader->removeFollower(this); leader = NULL; } else if (followers != NULL) { int16 i; for (i = 0; i < followers->size(); i++) { Actor *follower = (*followers)[i]; follower->leader = NULL; follower->evaluateNeeds(); } delete followers; followers = NULL; } // Place in limbo if (!(_data.objectFlags & objectNoRecycle)) { append(ActorLimbo); actorLimboCount++; } } //----------------------------------------------------------------------- // Cause the actor to stop his current motion task is he is interruptable void Actor::stopMoving(void) { if (moveTask != NULL && isInterruptable()) moveTask->remove(); } //----------------------------------------------------------------------- // Cause this actor to die void Actor::die(void) { if (!isDead()) return; ObjectID dObj = thisID(); scriptCallFrame scf; PlayerActorID playerID; scf.invokedObject = dObj; scf.enactor = dObj; scf.directObject = dObj; scf.indirectObject = Nothing; scf.value = 0; runObjectMethod(dObj, Method_Actor_onDie, scf); // Kill task if (curTask != NULL) { curTask->abortTask(); delete curTask; curTask = NULL; } // Kill motion task if (moveTask != NULL) moveTask->remove(); // If banded, remove from band if (leader != NULL) { assert(isActor(leader)); leader->removeFollower(this); leader = NULL; } if (actorToPlayerID(this, playerID)) handlePlayerActorDeath(playerID); } //----------------------------------------------------------------------- // Cause this actor to come back to life void Actor::imNotQuiteDead(void) { if (isDead()) { PlayerActorID pID; effectiveStats.vitality = 1; if (actorToPlayerID(this, pID)) updateBrotherControls(pID); evaluateNeeds(); } } //----------------------------------------------------------------------- // Cuase the actor to re-assess his/her vitality void Actor::vitalityUpdate(void) { // If we're dead, don't heal if (isDead()) return; // get the base stats for this actor ActorAttributes *baseStats = getBaseStats(); // first find out if this actor is wounded if (effectiveStats.vitality < baseStats->vitality) { // whole vitality number goes here int16 recover; int16 fractionRecover; // get the whole number first recover = recPointsPerUpdate / recPointsPerVitality; // get the fraction fractionRecover = recPointsPerUpdate % recPointsPerVitality; // if there is an overrun if (currentRecoveryPoints + fractionRecover > recPointsPerVitality) { // add the overrun to the whole number recover++; currentRecoveryPoints = (currentRecoveryPoints + fractionRecover) - recPointsPerVitality; } else { currentRecoveryPoints += fractionRecover; } if (effectiveStats.vitality + recover >= baseStats->vitality) { effectiveStats.vitality = baseStats->vitality; } else { effectiveStats.vitality += recover; //WriteStatusF( 5, " Healed: %d, rec: %d, part: %d ", effectiveStats.vitality, // recover, currentRecoveryPoints ); } } } //----------------------------------------------------------------------- // Perform actor specific activation tasks void Actor::activateActor(void) { debugC(1, kDebugActors, "Actors: Activated %d (%s)", thisID() - 32768, objName()); evaluateNeeds(); } //----------------------------------------------------------------------- // Perfrom actor specific deactivation tasks void Actor::deactivateActor(void) { debugC(1, kDebugActors, "Actors: De-activated %d (%s)", thisID() - 32768, objName()); // Kill task if (curTask != NULL) { curTask->abortTask(); delete curTask; curTask = NULL; } // Kill motion task if (moveTask != NULL) moveTask->remove(); // If banded, remove from band if (leader != NULL) { assert(isActor(leader)); leader->removeFollower(this); leader = NULL; } // Temporary actors get deleted upon deactivation if ((flags & temporary) || isDead()) { deactivationCounter = 10; // actor lasts for 50 seconds } } //----------------------------------------------------------------------- // Delobotomize this actor void Actor::delobotomize(void) { if (!(flags & lobotomized)) return; ObjectID dObj = thisID(); scriptCallFrame scf; flags &= ~lobotomized; scf.invokedObject = dObj; scf.enactor = dObj; scf.directObject = dObj; scf.indirectObject = Nothing; scf.value = 0; runObjectMethod(dObj, Method_Actor_onDelobotomize, scf); evaluateNeeds(); } //----------------------------------------------------------------------- // Lobotomize this actor void Actor::lobotomize(void) { if (flags & lobotomized) return; ObjectID dObj = thisID(); scriptCallFrame scf; // Kill task if (curTask != NULL) { curTask->abortTask(); delete curTask; curTask = NULL; } // Kill motion task if (moveTask != NULL) moveTask->remove(); flags |= lobotomized; scf.invokedObject = dObj; scf.enactor = dObj; scf.directObject = dObj; scf.indirectObject = Nothing; scf.value = 0; runObjectMethod(dObj, Method_Actor_onLobotomize, scf); } //----------------------------------------------------------------------- // Return a pointer to the base stats for this actor. If this actor // is a non-player actor, the base stats are in the prototype. If this // actor is a player actor, the base stats are in the PlayerActor // structure. ActorAttributes *Actor::getBaseStats(void) { if (disposition < dispositionPlayer) return &((ActorProto *)prototype)->baseStats; else return &playerList[disposition - dispositionPlayer].baseStats; } //----------------------------------------------------------------------- // Return the racial base enchantment flags. If this actor // is a non-player actor, the base stats are in the prototype. uint32 Actor::getBaseEnchantmentEffects(void) { //if ( disposition < dispositionPlayer ) return ((ActorProto *)prototype)->baseEffectFlags; } //----------------------------------------------------------------------- // Return the object base resistance flags. If this actor // is a non-player actor, the base stats are in the prototype. uint16 Actor::getBaseResistance(void) { //if ( disposition < dispositionPlayer ) return ((ActorProto *)prototype)->resistance; } //----------------------------------------------------------------------- // Return the object base immunity flags. If this actor // is a non-player actor, the base stats are in the prototype. uint16 Actor::getBaseImmunity(void) { //if ( disposition < dispositionPlayer ) return ((ActorProto *)prototype)->immunity; } //----------------------------------------------------------------------- // Return the base recovery rate uint16 Actor::getBaseRecovery(void) { return BASE_REC_RATE; } //----------------------------------------------------------------------- // Determine if specified point is within actor's reach bool Actor::inReach(const TilePoint &tp) { return inRange(tp, defaultReach); } //----------------------------------------------------------------------- // Determine if specified point is within an objects use range bool Actor::inUseRange(const TilePoint &tp, GameObject *obj) { uint16 range = obj->proto()->maximumRange; return inRange(tp, MAX(range, defaultReach)); } //----------------------------------------------------------------------- // Determine if actor is immobile (i.e. can't walk) bool Actor::isImmobile(void) { return isDead() || hasEffect(actorImmobile) || hasEffect(actorAsleep) || hasEffect(actorParalyzed); } //----------------------------------------------------------------------- // Return a pointer to this actor's currently readied offensive object GameObject *Actor::offensiveObject(void) { if (rightHandObject != Nothing) { assert(isObject(rightHandObject)); GameObject *obj = GameObject::objectAddress(rightHandObject); // Any object in an actor's right hand should be a weapon assert(obj->containmentSet() & ProtoObj::isWeapon); return obj; } if (leftHandObject != Nothing) { assert(isObject(leftHandObject)); GameObject *obj = GameObject::objectAddress(leftHandObject); if (obj->containmentSet() & ProtoObj::isWeapon) return obj; } // If not carrying a weapon attack with self return this; } //----------------------------------------------------------------------- // Returns pointers to this actor's readied primary defensive object // and optionally their scondary defensive object void Actor::defensiveObject(GameObject **priPtr, GameObject **secPtr) { assert(priPtr != NULL); GameObject *leftHandObjPtr, *rightHandObjPtr, *primary = NULL, *secondary = NULL; // Get a pointer to the left hand object leftHandObjPtr = leftHandObject != Nothing ? (assert(isObject(leftHandObject)) , GameObject::objectAddress(leftHandObject)) : NULL; // Get a pointer to the right hand object rightHandObjPtr = rightHandObject != Nothing ? (assert(isObject(rightHandObject)) , GameObject::objectAddress(rightHandObject)) : NULL; if (leftHandObjPtr != NULL) { GameObject **rightHandObjDest; if (leftHandObjPtr->proto()->canBlock()) { // Left hand object is primary. Right hand object may be // secondary primary = leftHandObjPtr; rightHandObjDest = &secondary; } else // Right hand object may be primary rightHandObjDest = &primary; if (rightHandObjPtr != NULL && rightHandObjPtr->proto()->canBlock()) // Right hand object is defensive *rightHandObjDest = rightHandObjPtr; } else { if (rightHandObjPtr != NULL && rightHandObjPtr->proto()->canBlock()) // Right hand object is primary defensive object primary = rightHandObjPtr; } // Return the primary pointer *priPtr = primary; // Return the secondary pointer if (secPtr != NULL) *secPtr = secondary; } //----------------------------------------------------------------------- // Returns a pointer to the object with which this actor is currently // blocking, if any GameObject *Actor::blockingObject(Actor *attacker) { return moveTask != NULL ? moveTask->blockingObject(attacker) : NULL; } //----------------------------------------------------------------------- // Return the total used armor attributes void Actor::totalArmorAttributes(ArmorAttributes &armorAttribs) { int i; ProtoObj *thisProto = proto(); // Plug in actor's natural values armorAttribs.damageAbsorbtion = thisProto->damageAbsorbtion; armorAttribs.damageDivider = MAX(thisProto->damageDivider, 1); armorAttribs.defenseBonus = thisProto->defenseBonus; // Accumulate values for all armor objects for (i = 0; i < ARMOR_COUNT; i++) { if (armorObjects[i] != Nothing) { ProtoObj *armorProto = GameObject::protoAddress(armorObjects[i]); assert(armorProto != NULL); armorAttribs.damageAbsorbtion += armorProto->damageAbsorbtion; if (armorProto->damageDivider != 0) armorAttribs.damageDivider *= armorProto->damageDivider; armorAttribs.defenseBonus += armorProto->defenseBonus; } } } //----------------------------------------------------------------------- // Determine if specified point is within actor's attack range bool Actor::inAttackRange(const TilePoint &tp) { GameObject *weapon = offensiveObject(); uint16 range = weapon != NULL ? weapon->proto()->maximumRange : 0; return inRange(tp, MAX(range, defaultReach)); } //----------------------------------------------------------------------- // Initiate an attack upon a specified target void Actor::attack(GameObject *target) { GameObject *weapon = offensiveObject(); if (weapon != NULL) weapon->proto()->initiateAttack(thisID(), target->thisID()); } //----------------------------------------------------------------------- // Stop all attacks on a specified target void Actor::stopAttack(GameObject *target) { if (moveTask && moveTask->isAttack() && moveTask->targetObj == target) moveTask->finishAttack(); } //----------------------------------------------------------------------- // Determine if this actor can block an attack bool Actor::canDefend(void) { if (isDead()) return false; // Look at left hand object, generally the defensive object if (leftHandObject != Nothing) { GameObject *obj = GameObject::objectAddress(leftHandObject); if (obj->proto()->canBlock()) return true; } // Look at right hand object, generally the offensive object if (rightHandObject != Nothing) { GameObject *obj = GameObject::objectAddress(rightHandObject); if (obj->proto()->canBlock()) return true; } return false; } //----------------------------------------------------------------------- // Return a numeric value which roughly estimates this actor's // offensive strength int16 Actor::offenseScore(void) { // REM: at this time this calculation is somewhat arbitrary int16 score = 0; GameObject *weapon = offensiveObject(); if (weapon != NULL) { ProtoObj *proto = weapon->proto(); score += proto->weaponDamage + (proto->maximumRange / kTileUVSize); } // Add average mana score += (effectiveStats.redMana + effectiveStats.orangeMana + effectiveStats.yellowMana + effectiveStats.greenMana + effectiveStats.blueMana + effectiveStats.violetMana) / 6; score += effectiveStats.spellcraft + effectiveStats.brawn; return score; } //----------------------------------------------------------------------- // Return a numeric value which roughly estimates this actor's // defensive strength int16 Actor::defenseScore(void) { // REM: at this time this calculation is somewhat arbitrary int16 score = 0; GameObject *shield; ArmorAttributes armorAttribs; defensiveObject(&shield); if (shield != NULL) { ProtoObj *proto = shield->proto(); score += proto->defenseBonus; } totalArmorAttributes(armorAttribs); score += (armorAttribs.defenseBonus + armorAttribs.damageAbsorbtion) * armorAttribs.damageDivider; score += effectiveStats.agility + effectiveStats.vitality; return score; } //----------------------------------------------------------------------- // Return the sprite color translation table based upon the actor's // color scheme void Actor::getColorTranslation(ColorTable map) { // If actor has color table loaded, then calculate the // translation table. if (appearance && appearance->schemeList) { buildColorTable(map, appearance->schemeList->_schemes[colorScheme]->bank, 11); } else memcpy(map, identityColors, 256); } //----------------------------------------------------------------------- // Set the current animation sequence for the actor. // // Each time the nextAnimationFrame() is called, it will increment // to the next frame in the sequence. int16 Actor::setAction(int16 newState, int16 flags_) { ActorAnimation *anim; int16 numPoses = 0; // Refresh the handles // RLockHandle( appearance->animations ); // RUnlockHandle( appearance->animations ); if (appearance == NULL) return 0; // If this animation has no frames, then return false anim = appearance->animation(newState); if (anim) numPoses = anim->count[currentFacing]; if (numPoses <= 0) return 0; // Set up the animation currentAnimation = newState; animationFlags = flags_; // If they haven't set the "no reset" flag, then if (!(flags_ & animateNoRestart)) { if (flags_ & animateReverse) currentPose = numPoses - 1; else currentPose = 0; } else { currentPose = clamp(0, currentPose, numPoses - 1); } return numPoses; } //----------------------------------------------------------------------- // returns true if the action is available in the current direction. // bool Actor::isActionAvailable(int16 newState, bool anyDir) { ActorAnimation *anim; // Refresh the handles // RLockHandle( appearance->animations ); // RUnlockHandle( appearance->animations ); if (appearance == nullptr) return false; // If this animation has no frames, then return false anim = appearance->animation(newState); if (anim == nullptr) return false; if (anyDir) { for (int i = 0; i < numPoseFacings; i++) { if (anim->count[i] > 0) return true; } } else { if (anim->count[currentFacing] > 0) return true; } return false; } //----------------------------------------------------------------------- // Return the number of animation frames in the specified action for the // specified direction int16 Actor::animationFrames(int16 actionType, Direction dir) { if (appearance == nullptr) return 0; ActorAnimation *anim; anim = appearance->animation(actionType); if (!anim) return 0; return anim->count[dir]; } //----------------------------------------------------------------------- // Update the current animation sequence to the next frame. // Returns true if the animation sequence has finished. bool Actor::nextAnimationFrame(void) { ActorAnimation *anim; int16 numPoses; // Refresh the handles // RLockHandle( appearance->animations ); // RUnlockHandle( appearance->animations ); if (appearance == NULL) { if (animationFlags & animateOnHold) { return false; } else if (animationFlags & animateRepeat) { animationFlags |= animateOnHold; return false; } else { animationFlags |= animateFinished; return true; } } else animationFlags &= ~animateOnHold; // Get the number of frames in the animation anim = appearance->animation(currentAnimation); numPoses = anim->count[currentFacing]; if (numPoses <= 0) { animationFlags |= animateFinished; return true; // no poses, return DONE } // If the sprite could not be displayed because it has not // been loaded, then don't update the animation state -- // wait until the sprite gets loaded, and then continue // with the action. if (animationFlags & animateNotLoaded) return false; // If the animation has reached the last frame, then exit. if (animationFlags & animateFinished) return true; if (animationFlags & animateRandom) { // Select a random frame from the series. currentPose = rand() % numPoses; } else if (animationFlags & animateReverse) { // Note that the logic for forward repeats is slightly // different for reverse repeats. Specifically, the // "alternate" flag is always checked when going forward, // but it's only checked when going backwards if the repeat // flag is also set. This means that an "alternate" with // no "repeat" will ping-pong exactly once. if (currentPose > 0) { currentPose--; // Check if this is the last frame if (currentPose <= 0 && !(animationFlags & animateRepeat)) { animationFlags |= animateFinished; } } else if (animationFlags & animateRepeat) { // If we're repeating, check for a back & forth, // or for a wraparound. Also checks for case of // a degenerate series (1 frame only) if (animationFlags & animateAlternate) { animationFlags &= ~animateReverse; currentPose = MIN(1, numPoses - 1); } else { currentPose = numPoses - 1; } } } else { if (currentPose < numPoses - 1) { // Increment the pose number currentPose++; // Check if this is the last frame if (currentPose >= numPoses - 1 && !(animationFlags & (animateAlternate | animateRepeat))) animationFlags |= animateFinished; } else if (animationFlags & animateAlternate) { // At the end of the sequence, reverse direction animationFlags |= animateReverse; currentPose = MAX(currentPose - 1, 0); } else if (animationFlags & animateRepeat) { // Wrap back to beginning currentPose = 0; } else //If Last Frame And Not Animate Repeat or Alternate animationFlags |= animateFinished; } return false; } //----------------------------------------------------------------------- // Drop the all of the actor's inventory void Actor::dropInventory(void) { GameObject *obj, *nextObj; for (obj = _data.childID != Nothing ? GameObject::objectAddress(_data.childID) : NULL; obj != NULL; obj = nextObj) { nextObj = obj->IDNext() != Nothing ? GameObject::objectAddress(obj->IDNext()) : NULL; // Delete intangible objects and drop tangible objects if (obj->containmentSet() & ProtoObj::isIntangible) obj->deleteObjectRecursive(); else dropInventoryObject(obj, obj->isMergeable() ? obj->getExtra() : 1); } } //----------------------------------------------------------------------- // Place an object into this actor's right or left hand void Actor::holdInRightHand(ObjectID objID) { assert(isObject(objID)); rightHandObject = objID; if (isPlayerActor(this)) globalContainerList.setUpdate(thisID()); evalActorEnchantments(this); } void Actor::holdInLeftHand(ObjectID objID) { assert(isObject(objID)); leftHandObject = objID; if (isPlayerActor(this)) globalContainerList.setUpdate(thisID()); evalActorEnchantments(this); } //----------------------------------------------------------------------- // Wear a piece of armor void Actor::wear(ObjectID objID, uint8 where) { assert(where < ARMOR_COUNT); PlayerActorID playerID; #if DEBUG if (objID != Nothing) { assert(isObject(objID)); GameObject *obj = GameObject::objectAddress(objID); assert(obj->proto()->containmentSet() & ProtoObj::isArmor); } #endif armorObjects[where] = objID; if (isPlayerActor(this)) globalContainerList.setUpdate(thisID()); evalActorEnchantments(this); if (actorToPlayerID(this, playerID)) { updateBrotherArmor(playerID); } } //----------------------------------------------------------------------- // Called when the actor is on the display list and has no motion task. void Actor::updateAppearance(int32) { // static uint16 count; // count++; if (isDead() || !isActivated() || (flags & lobotomized)) return; #if DEBUG*0 WriteStatusF(4, "Wait Count %d Attitude %d", cycleCount, attitude); #endif #if DEBUG*0 extern void ShowObjectSection(GameObject * obj); if (this != getCenterActor()) if (lineOfSight(getCenterActor(), this, terrainSurface)) ShowObjectSection(this); #endif if (appearance) { if (animationFrames(actionStand, currentFacing) == 1) { if (flags & fightStance) { GameObject *weapon = offensiveObject(); if (weapon == this) weapon = NULL; if (weapon != NULL) { ProtoObj *weaponProto = weapon->proto(); setAction(weaponProto->fightStanceAction(thisID()), 0); } else { if (isActionAvailable(actionSwingHigh)) setAction(actionSwingHigh, 0); else setAction(actionTwoHandSwingHigh, 0); } cycleCount = 0; } else { if (cycleCount > 0) { //If In Wait State Between Wait Animation cycleCount--; setAction(actionStand, 0); //Just stand still } else { // Wait Animation if (cycleCount == 0) { //If Just Starting Wait Animation cycleCount--; switch (attitude) { //Emotion And Character Type //Currently Attitude Not Set So Always Hits Zero case 0: //Returns True If Successful No Checking Yet SetAvailableAction(0, actionWaitAgressive, actionWaitImpatient, actionWaitFriendly, actionStand, -1);//Second To Last Parameter Is The Default break; case 1: SetAvailableAction(0, actionWaitImpatient, actionWaitFriendly, actionWaitAgressive, actionStand, -1); break; case 2: SetAvailableAction(0, actionWaitFriendly, actionWaitImpatient, actionWaitAgressive, actionStand, -1); } } else //Assume -1 if (nextAnimationFrame())//If Last Frame In Wait Animation cycleCount = rand() % 20; } } } else { if (currentAnimation != actionStand || (animationFlags & animateRepeat) == 0) setAction(actionStand, animateRepeat); else nextAnimationFrame(); } }// End if (appearance) } bool Actor::SetAvailableAction(int16 flags_, ...) { bool result = false; va_list Actions; va_start(Actions, flags_); //Initialize To First Argument Even Though We Dont Use It In The Loop for (;;) { //Infinite Loop int thisAction = va_arg(Actions, int); //Increment To Second Argument Ignoring Flags if (thisAction < 0) break; //Check If Last Parameter Since Last Always Should Be -1 if (setAction(thisAction, flags_)) { //Try To Set This Action result = true; //If Successful break; } } va_end(Actions); //Clean Up return result; } //----------------------------------------------------------------------- // Set a new goal for this actor void Actor::setGoal(uint8 newGoal) { if (currentGoal != newGoal) { if (curTask != NULL) { curTask->abortTask(); delete curTask; curTask = NULL; } currentGoal = newGoal; } } //----------------------------------------------------------------------- // Reevaluate actor's built-in needs void Actor::evaluateNeeds(void) { if (!isDead() && isActivated() && !(flags & lobotomized)) { if (disposition >= dispositionPlayer) { if (combatBehaviorEnabled) { SenseInfo info; if (canSenseActorProperty( info, maxSenseRange, actorPropIDEnemy) || canSenseActorPropertyIndirectly( info, maxSenseRange, actorPropIDEnemy)) { PlayerActorID playerID = disposition - dispositionPlayer; if (isAggressive(playerID)) setGoal(actorGoalAttackEnemy); else { if (leader != NULL && inBandingRange()) setGoal(actorGoalAvoidEnemies); else setGoal(actorGoalPreserveSelf); } } else if (leader != NULL && inBandingRange()) { setGoal(actorGoalFollowLeader); } else { setGoal(actorGoalFollowAssignment); } } else if (leader != NULL && inBandingRange()) { setGoal(actorGoalFollowLeader); } else { setGoal(actorGoalFollowAssignment); } } else { if (disposition == dispositionEnemy && appearance != NULL && !hasEffect(actorNotDefenseless)) { GameObject *obj; bool foundWeapon = false; ContainerIterator iter(this); while (iter.next(&obj) != Nothing) { ProtoObj *proto = obj->proto(); if ((proto->containmentSet() & ProtoObj::isWeapon) && isActionAvailable(proto->fightStanceAction(thisID()))) { foundWeapon = true; break; } } if (!foundWeapon && (isActionAvailable(actionSwingHigh) || isActionAvailable(actionTwoHandSwingHigh))) foundWeapon = true; if (!foundWeapon) flags |= afraid; } if (flags & afraid || hasEffect(actorFear) || hasEffect(actorRepelUndead)) { setGoal(actorGoalPreserveSelf); } else if (leader != NULL && inBandingRange()) { setGoal(leader->evaluateFollowerNeeds(this)); } else { SenseInfo info; if (disposition == dispositionEnemy && (getAssignment() == NULL || canSenseProtaganist( info, maxSenseRange) || canSenseProtaganistIndirectly( info, maxSenseRange))) { setGoal(actorGoalAttackEnemy); } else { setGoal(actorGoalFollowAssignment); } } } } } //----------------------------------------------------------------------- // Update the state of this actor. static int32 updatesViaScript = 0; void Actor::updateState(void) { // The actor should not be set permanently uninterruptable when // the actor does not have a motion task assert(isMoving() || actionCounter != maxuint8); GameObject::updateState(); if (flags & lobotomized) return; // Update the action counter if (actionCounter != 0 && actionCounter != maxuint8) actionCounter--; if (appearance != NULL && isDead() && isInterruptable() && (moveTask == NULL || moveTask->motionType != MotionTask::motionTypeDie)) { int16 deadState = isActionAvailable(actionDead) ? actionDead : isActionAvailable(actionDie) ? actionDie : actionStand; if (currentAnimation != deadState) MotionTask::die(*this); return; } if (!isDead()) { if (this == getCenterActor()) return; if (flags & specialAttack) { flags &= ~specialAttack; if (currentTarget != NULL) { scriptCallFrame scf; ObjectID dObj = thisID(); scf.invokedObject = dObj; scf.enactor = dObj; scf.directObject = dObj; scf.indirectObject = currentTarget->thisID(); scf.value = 0; runObjectMethod(dObj, Method_Actor_onSpecialAttack, scf); // If this actor is now deactivated or lobotomized // return immediately if (isDead() || !isActivated() || (flags & lobotomized)) return; } } switch (currentGoal) { case actorGoalFollowAssignment: { ActorAssignment *assign = getAssignment(); // Iterate until there is no assignment, or the current // assignment is valid while (assign != NULL && !assign->isValid()) { updatesViaScript++; scriptCallFrame scf; ObjectID dObj = thisID(); delete assign; // Notify the scripts that the assignment has ended scf.invokedObject = dObj; scf.enactor = dObj; scf.directObject = dObj; scf.indirectObject = Nothing; scf.value = 0; runObjectMethod(dObj, Method_Actor_onEndAssignment, scf); // If this actor is now deactivated or lobotomized // return immediately if (isDead() || !isActivated() || (flags & lobotomized)) return; // Re-get the assignment assign = getAssignment(); } // If there is no assignment at this point, call the // schedule to setup a new assignment. if (assign == NULL && schedule != 0) { updatesViaScript++; assert(curTask == NULL); scriptCallFrame scf; scf.invokedObject = Nothing; scf.enactor = Nothing; scf.directObject = thisID(); scf.indirectObject = Nothing; scf.value = 0; runScript(schedule, scf); // Re-get the assignment assign = getAssignment(); } // Have the assignment create a new task if (assign != NULL && curTask == NULL) curTask = assign->createTask(); } break; case actorGoalPreserveSelf: if (leader != NULL || followers != NULL) disband(); if (curTask == NULL) { if ((curTask = new TaskStack(this)) != NULL) { Task *task = new GoAwayFromActorTask( curTask, ActorPropertyTarget( disposition == dispositionEnemy ? actorPropIDPlayerActor : actorPropIDEnemy), true); if (task != NULL) curTask->setTask(task); else { delete curTask; curTask = NULL; } } } break; case actorGoalAttackEnemy: if (curTask == NULL) { if ((curTask = new TaskStack(this)) != NULL) { uint8 disp = leader != NULL ? leader->disposition : disposition; Task *task = new HuntToKillTask( curTask, ActorPropertyTarget( disp == dispositionEnemy ? actorPropIDPlayerActor : actorPropIDEnemy)); if (task != NULL) curTask->setTask(task); else { delete curTask; curTask = NULL; } } } break; case actorGoalFollowLeader: assert(isActor(leader)); assert(followers == NULL); if (curTask == NULL) curTask = leader->createFollowerTask(this); break; case actorGoalAvoidEnemies: assert(isActor(leader)); assert(followers == NULL); if (curTask == NULL) { if ((curTask = new TaskStack(this)) != NULL) { Task *task = new BandAndAvoidEnemiesTask(curTask); if (task != NULL) curTask->setTask(task); else { delete curTask; curTask = NULL; } } } } } } //----------------------------------------------------------------------- // This routine is used to notify the actor that a task has ended. The // actor should handle the situation appropriately void Actor::handleTaskCompletion(TaskResult result) { // The task is done, get rid of it delete curTask; curTask = NULL; switch (currentGoal) { case actorGoalFollowAssignment: { ActorAssignment *assign = getAssignment(); // If we've gotten to this point, there had better be an // assignment, or something is amiss assert(assign != NULL); // Notify the assignment assign->handleTaskCompletion(result); } break; } } //----------------------------------------------------------------------- // This function will cause the actor to react to an offensive act void Actor::handleOffensiveAct(Actor *attacker) { ObjectID dObj = thisID(); scriptCallFrame scf; scf.invokedObject = dObj; scf.enactor = dObj; scf.directObject = dObj; scf.indirectObject = attacker->thisID(); scf.value = 0; runObjectMethod(dObj, Method_Actor_onAttacked, scf); if (disposition == dispositionFriendly) { if (attacker->disposition >= dispositionPlayer) { disposition = dispositionEnemy; evaluateNeeds(); } } } //----------------------------------------------------------------------- // This function will cause the actor to react appropriately to taking // damage. void Actor::handleDamageTaken(uint8 damage) { uint8 combatBehavior = ((ActorProto *)prototype)->combatBehavior; if (combatBehavior == behaviorHungry) return; if (offensiveObject() == this && !isActionAvailable(actionSwingHigh) && !isActionAvailable(actionTwoHandSwingHigh) && !hasEffect(actorNotDefenseless)) { flags |= afraid; return; } if (combatBehavior != behaviorHungry && (flags & temporary) && !hasEffect(actorFear) && !hasEffect(actorRepelUndead)) { if (flags & afraid) { // Let's give monsters a small chance of regaining their courage if ((uint16)rand() <= 0x3fff) flags &= ~afraid; } else { int16 i, fellowBandMembers, vitality = effectiveStats.vitality; uint32 moraleBase = ((int32)damage << 16) / vitality, bonus = 0; // Adjustment added by Talin to globally reduce the amount of cowardice // in the game. I may reduce it further depending on playtesting. moraleBase /= 3; // Adjust morale base according to the combat behavior if (combatBehavior == behaviorCowardly) moraleBase += moraleBase / 2; else if (combatBehavior == behaviorBerserk) moraleBase -= moraleBase / 2; // Determine how many fellow band members this actor has. if (leader != NULL) fellowBandMembers = leader->followers->size(); else if (followers != NULL) fellowBandMembers = followers->size(); else fellowBandMembers = 0; // REM: this calculation can be done via a lookup table for (i = 0; i < fellowBandMembers; i++) bonus += ((1 << 16) - bonus) >> 4; // Adjust the morale base to acount for the number of fellow band // members moraleBase -= bonus * moraleBase >> 16; // Test this actor's morale if ((uint16)rand() <= moraleBase) flags |= afraid; } } } //----------------------------------------------------------------------- // This function is called when this actor successfully causes damage // to another actor. void Actor::handleSuccessfulStrike(Actor *target, int8 damage) { PlayerActorID playerID; if (actorToPlayerID(this, playerID)) { PlayerActor *player = getPlayerActorAddress(playerID); int16 ratio; // If it's a weak monster, then reduce amount of vitality advanced. // If we are twice as vital, then get half the exp's. If we are three times // as vital, get 1/3 the exp. etc. ratio = clamp(1, getBaseStats()->vitality / target->getBaseStats()->vitality, 4); player->vitalityAdvance(damage / ratio); } } //----------------------------------------------------------------------- // This function is called when this actor successfully kills another // actor. void Actor::handleSuccessfulKill(Actor *target) { PlayerActorID playerID; if (this != target && actorToPlayerID(this, playerID)) { static const char vowels[] = "AEIOU"; PlayerActor *player = getPlayerActorAddress(playerID); int16 ratio; int16 points = target->getBaseStats()->vitality; const char *monsterName = target->objName(); const char *aStr; // If it's a weak monster, then reduce amount of vitality advanced. // If we are twice as vital, then get half the exp's. If we are three times // as vital, get 1/3 the exp. etc. ratio = clamp(1, getBaseStats()->vitality / points, 4); player->vitalityAdvance(points / ratio); aStr = target->getNameIndex() == 0 ? strchr(vowels, toupper(monsterName[0])) == NULL ? "a " : "an " : ""; StatusMsg("%s has killed %s%s.", objName(), aStr, monsterName); } } //----------------------------------------------------------------------- // Determine if this actor can block a blow from the specified relative // direction with the specified defensive object. bool Actor::canBlockWith(GameObject *defenseObj, Direction relativeDir) { assert(defenseObj->proto()->canBlock()); assert(relativeDir < 8); // Assuming that the actor may increment or decrement their facing // to block, these masks represent the possible relative facings // based upon the current relative facing static uint8 dirMaskArray[8] = { 0x83, // 10000011 0x07, // 00000111 0x0E, // 00001110 0x1C, // 00011100 0x38, // 00111000 0x70, // 01110000 0xE0, // 11100000 0xC1 // 11000001 }; return (defenseObj->proto()->defenseDirMask() & dirMaskArray[relativeDir]) != 0; } //----------------------------------------------------------------------- // This function is called to notify this actor of an impending attack void Actor::evaluateMeleeAttack(Actor *attacker) { if (isInterruptable() && !isDead()) { Direction relativeDir; GameObject *defenseObj, *primary, *secondary; bool canBlockWithPrimary; // Compute the attacker's direction relative to this actor's // facing relativeDir = ((attacker->_data.location - _data.location).quickDir() - currentFacing) & 0x7; // Get pointers to this actors primary and secondary defensive // objects defensiveObject(&primary, &secondary); canBlockWithPrimary = primary != NULL && canBlockWith(primary, relativeDir); if (canBlockWithPrimary) { bool canBlockWithSecondary; canBlockWithSecondary = secondary != NULL && canBlockWith( secondary, relativeDir); if (canBlockWithSecondary) { // If we can block with either primary or secondary // there is a 25% chance of using the secondary defenseObj = ((rand() & 0x3) != 0) ? primary : secondary; } else { // The primary defensive object will be used defenseObj = primary; } } else defenseObj = NULL; if (defenseObj != NULL) { // Start a defensive motion defenseObj->proto()->initiateDefense( defenseObj->thisID(), thisID(), attacker->thisID()); } else { if (isActionAvailable(actionJumpUp)) MotionTask::dodge(*this, *attacker); } } } //----------------------------------------------------------------------- // Cause this actor to accept another actor as his leader. If the actor // has followers, this will band those followers to the new leader as // well. void Actor::bandWith(Actor *newLeader) { assert(leader == NULL); // If the actor we're banding with is not the leader, then band // with his leader if (newLeader->leader != NULL) { newLeader = newLeader->leader; assert(newLeader->leader == NULL); } // If this actor himself does not have followers then its really // simple, otherwise we need to band all of this actor's followers // with the new leader. if (followers == NULL) { if (newLeader->addFollower(this)) leader = newLeader; } else { int16 i, oldFollowerCount = followers->size(); Actor **oldFollowers = new Actor * [oldFollowerCount]; if (oldFollowers != NULL) { // Copy the list followers for (i = 0; i < oldFollowerCount; i++) { oldFollowers[i] = (*followers)[i]; assert(oldFollowers[i]->leader == this); } // Disband all of the old followers for (i = 0; i < oldFollowerCount; i++) oldFollowers[i]->disband(); assert(followers == NULL); // Add this actor and all of the old followers to the new // leader's followers. if (newLeader->addFollower(this)) { leader = newLeader; for (i = 0; i < oldFollowerCount; i++) oldFollowers[i]->bandWith(newLeader); } delete [] oldFollowers; } } evaluateNeeds(); } //----------------------------------------------------------------------- // Simply causes this actor to be removed from his current band. void Actor::disband(void) { if (leader != NULL) { leader->removeFollower(this); leader = NULL; evaluateNeeds(); } else if (followers != NULL) { int16 i; for (i = 0; i < followers->size(); i++) { Actor *follower = (*followers)[i]; follower->leader = NULL; follower->evaluateNeeds(); } delete followers; followers = NULL; } } //----------------------------------------------------------------------- // Add the specified actor to the list of this actor's followers. bool Actor::addFollower(Actor *newBandMember) { // The new band member should not be a leader of another band or // a follower of another leader assert(newBandMember->leader == NULL); assert(newBandMember->followers == NULL); // Allocate a new band, if needed if (followers == NULL && (followers = new Band(this)) == NULL) return false; return followers->add(newBandMember); } //----------------------------------------------------------------------- // Remove the specified actor from this actor's list of followers. void Actor::removeFollower(Actor *bandMember) { assert(bandMember->leader == this); assert(followers != NULL); int16 i; followers->remove(bandMember); if (followers->size() == 0) { delete followers; followers = NULL; } else { uint16 moraleBonus = 0; for (i = 0; i < followers->size(); i++) moraleBonus += ((1 << 16) - moraleBonus) >> 4; for (i = 0; i < followers->size(); i++) { Actor *follower = (*followers)[i]; ActorProto *proto = (ActorProto *)follower->prototype; uint8 combatBehavior = proto->combatBehavior; if (follower->currentGoal == actorGoalAttackEnemy && combatBehavior != behaviorHungry) { uint32 moraleBase; moraleBase = combatBehavior == behaviorCowardly ? (1 << 16) / 4 : combatBehavior == behaviorSmart ? (1 << 16) / 8 : (1 << 16) / 16; moraleBase -= moraleBase * moraleBonus >> 16; if ((uint16)rand() <= moraleBase) follower->flags |= afraid; } } } } //----------------------------------------------------------------------- // Create a task for a follower of this actor. This is called when a // follower has no task. TaskStack *Actor::createFollowerTask(Actor *bandMember) { assert(bandMember->leader == this); TaskStack *ts = NULL; if ((ts = new TaskStack(bandMember)) != NULL) { Task *task = new BandTask(ts); if (task != NULL) ts->setTask(task); else { delete ts; ts = NULL; } } return ts; } //----------------------------------------------------------------------- // Evaluate a follower's needs and give him an approriate goal. uint8 Actor::evaluateFollowerNeeds(Actor *follower) { assert(follower->leader == this); SenseInfo info; if ((disposition == dispositionEnemy && follower->canSenseProtaganist(info, maxSenseRange)) || (disposition >= dispositionPlayer && follower->canSenseActorProperty( info, maxSenseRange, actorPropIDEnemy))) return actorGoalAttackEnemy; return actorGoalFollowLeader; } #if DEBUG uint32 objectTerrain(GameObject *obj, StandingTileInfo &); void showObjectTerrain(GameObject *obj) { StandingTileInfo sti; uint32 terrain = objectTerrain(obj, sti); char terrLetters[] = "NERWSHWFRSL0000"; char str[33]; for (int i = 0; i < 32; i++) { str[i] = terrain & (1 << i) ? terrLetters[i] : '-'; } str[32] = '\0'; // WriteStatusF( 4, str ); } #endif // Returns 0 if not moving, 1 if path being calculated, // 2 if path being followed. bool Actor::pathFindState(void) { if (moveTask == NULL) return 0; if (moveTask->pathFindTask) return 1; return 2; } //----------------------------------------------------------------------- // Add knowledge package to actor bool Actor::addKnowledge(uint16 kID) { for (int i = 0; i < ARRAYSIZE(knowledge); i++) { if (knowledge[i] == 0) { knowledge[i] = kID; return true; } } return false; } //----------------------------------------------------------------------- // Remove knowledge package from actor bool Actor::removeKnowledge(uint16 kID) { for (int i = 0; i < ARRAYSIZE(knowledge); i++) { if (knowledge[i] == kID) { knowledge[i] = 0; return true; } } return false; } //----------------------------------------------------------------------- // Remove all knowledge package from actor void Actor::clearKnowledge(void) { for (int i = 0; i < ARRAYSIZE(knowledge); i++) { knowledge[i] = 0; } } //----------------------------------------------------------------------- // Called to evaluate actor knowledge void Actor::useKnowledge(scriptCallFrame &scf) { uint16 bestResponsePri = 0, bestResponseClass = 0, bestResponseCode = 0; // First, search for the class with the best response for (int i = 0; i < ARRAYSIZE(knowledge); i++) { if (knowledge[i]) { scriptResult res; // Run the script to eval the response of this // knowledge package res = runMethod(knowledge[i], builtinAbstract, 0, Method_KnowledgePackage_evalResponse, scf); // If script ran OK, then look at result if (res == scriptResultFinished) { // break up return code into priority and // response code int16 pri = scf.returnVal >> 8, response = scf.returnVal & 0xff; if (pri > 0) { // Add a bit of jitter to response pri += rand() & 3; if (pri > bestResponsePri) { bestResponsePri = pri; bestResponseClass = knowledge[i]; bestResponseCode = response; } } } } } // Then, callback whichever one responded best if (bestResponsePri > 0) { // Run the script to eval the response of this // knowledge package scf.responseType = bestResponseCode; runMethod(bestResponseClass, builtinAbstract, 0, Method_KnowledgePackage_executeResponse, scf); } else { scf.returnVal = actionResultNotDone; } } //----------------------------------------------------------------------- // Polling function to determine if any of this actor's followers can // sense a protaganist within a specified range bool Actor::canSenseProtaganistIndirectly(SenseInfo &info, int16 range) { if (followers != NULL) { int i; for (i = 0; i < followers->size(); i++) { if ((*followers)[i]->canSenseProtaganist(info, range)) return true; } } return false; } //----------------------------------------------------------------------- // Polling function to determine if any of this actor's followers can // sense a specific actor within a specified range bool Actor::canSenseSpecificActorIndirectly( SenseInfo &info, int16 range, Actor *a) { if (followers != NULL) { int i; for (i = 0; i < followers->size(); i++) { if ((*followers)[i]->canSenseSpecificActor(info, range, a)) return true; } } return false; } //----------------------------------------------------------------------- // Polling function to determine if any of this actor's followers can // sense a specific object within a specified range bool Actor::canSenseSpecificObjectIndirectly( SenseInfo &info, int16 range, ObjectID obj) { if (followers != NULL) { int i; for (i = 0; i < followers->size(); i++) { if ((*followers)[i]->canSenseSpecificObject(info, range, obj)) return true; } } return false; } //----------------------------------------------------------------------- // Polling function to determine if any of this actor's followers can // sense an actor with a specified property within a specified range bool Actor::canSenseActorPropertyIndirectly( SenseInfo &info, int16 range, ActorPropertyID prop) { if (followers != NULL) { int i; for (i = 0; i < followers->size(); i++) { if ((*followers)[i]->canSenseActorProperty(info, range, prop)) return true; } } return false; } //----------------------------------------------------------------------- // Polling function to determine if any of this actor's followers can // sense an object with a specified property within a specified range bool Actor::canSenseObjectPropertyIndirectly( SenseInfo &info, int16 range, ObjectPropertyID prop) { if (followers != NULL) { int i; for (i = 0; i < followers->size(); i++) { if ((*followers)[i]->canSenseObjectProperty(info, range, prop)) return true; } } return false; } //----------------------------------------------------------------------- // Mana check - spell casting uses this to check whether an actor // has enough mana to cast a spell & to remove that mana if // it's there #define NO_MONSTER_MANA 1 bool Actor::takeMana(ActorManaID i, int8 dMana) { #if NO_MONSTER_MANA if (!isPlayerActor(this)) return true; #endif assert(i >= manaIDRed && i <= manaIDViolet); if ((&effectiveStats.redMana)[i] < dMana) return false; (&effectiveStats.redMana)[i] -= dMana; updateIndicators(); return true; } bool Actor::hasMana(ActorManaID i, int8 dMana) { #if NO_MONSTER_MANA if (!isPlayerActor(this)) return true; #endif assert(i >= manaIDRed && i <= manaIDViolet); if ((&effectiveStats.redMana)[i] < dMana) return false; return true; } //----------------------------------------------------------------------- // Saving throw funcion bool Actor::makeSavingThrow(void) { return false; } //------------------------------------------------------------------- // Determine if the actors are currently initialized bool areActorsInitialized(void) { return actorList != NULL; } int16 GetRandomBetween(int start, int end) { // Here's a more efficient way to express this. if (start == end) return start; else return (rand() % abs(end - start)) + start; } void updateActorStates(void) { if (actorStatesPaused) return; static const int32 evalRate = 8; static const int32 evalRateMask = evalRate - 1; static int32 baseActorIndex = evalRateMask; extern Actor *actorList; extern int16 actorCount; int32 actorIndex; actorIndex = baseActorIndex = (baseActorIndex + 1) & evalRateMask; while (actorIndex < actorCount) { Actor *a = &actorList[actorIndex]; if (isWorld(a->IDParent())) a->evaluateNeeds(); actorIndex += evalRate; } updatesViaScript = 0; for (actorIndex = 0; actorIndex < actorCount; actorIndex++) { Actor *a = &actorList[actorIndex]; if (isWorld(a->IDParent()) && a->isActivated()) a->updateState(); } //WriteStatusF((useLine%10)+10,"%d actor updates by script",updatesViaScript); //WriteStatusF(((useLine+1)%10)+10," "); //useLine++; } //------------------------------------------------------------------- void pauseActorStates(void) { actorStatesPaused = true; } //------------------------------------------------------------------- void resumeActorStates(void) { actorStatesPaused = false; } //------------------------------------------------------------------- void setCombatBehavior(bool enabled) { PlayerActor *player; LivingPlayerActorIterator iter; combatBehaviorEnabled = enabled; for (player = iter.first(); player != NULL; player = iter.next()) player->getActor()->evaluateNeeds(); } //------------------------------------------------------------------- // Initialize the actor list ResourceActor::ResourceActor(Common::SeekableReadStream *stream) : ResourceGameObject(stream) { faction = stream->readByte(); colorScheme = stream->readByte(); appearanceID = stream->readSint32BE(); attitude = stream->readSByte(); mood = stream->readSByte(); disposition = stream->readByte(); currentFacing = stream->readByte(); tetherLocU = stream->readSint16LE(); tetherLocV = stream->readSint16LE(); tetherDist = stream->readSint16LE(); leftHandObject = stream->readUint16LE(); rightHandObject = stream->readUint16LE(); for (int i = 0; i < 16; ++i) { knowledge[i] = stream->readUint16LE(); } schedule = stream->readUint16LE(); for (int i = 0; i < 18; ++i) { // padding bytes = not neccessary? reserved[i] = stream->readByte(); } } void initActors(void) { // Load actors int i, resourceActorCount; Common::Array resourceActorList; Common::SeekableReadStream *stream; const int resourceActorSize = 91; // size of the packed struct resourceActorCount = listRes->size(actorListID) / resourceActorSize; if (resourceActorCount < 1) error("Unable to load Actors"); // Add extra space for alias actors actorCount = resourceActorCount + extraActors; // Allocate memory for the actor list actorListSize = actorCount * sizeof(Actor); actorList = new Actor[actorCount](); if (!actorList) error("Unable to load Actors"); if ((stream = loadResourceToStream(listRes, actorListID, "res actor list")) == nullptr) error("Unable to load Actors"); // Read the resource actors for (int k = 0; k < resourceActorCount; ++k) { ResourceActor res(stream); resourceActorList.push_back(res); } delete stream; for (i = 0; i < resourceActorCount; i++) { Actor *a = &actorList[i]; // Initialize the actors with the resource data new (a) Actor(resourceActorList[i]); } // Place all of the extra actors in actor limbo for (; i < actorCount; i++) { Actor *a = &actorList[i]; new (a) Actor; } actorList[0].disposition = dispositionPlayer + 0; actorList[1].disposition = dispositionPlayer + 1; actorList[2].disposition = dispositionPlayer + 2; } //------------------------------------------------------------------- // Save actor list to a save file void saveActors(SaveFileConstructor &saveGame) { int16 i; int32 archiveBufSize = 0; void *archiveBuffer; int16 *bufferPtr; // Accumulate size of archive buffer // Add size of actor count archiveBufSize += sizeof(int16); for (i = 0; i < actorCount; i++) archiveBufSize += actorList[i].archiveSize(); archiveBuffer = malloc(archiveBufSize); if (archiveBuffer == NULL) error("Unable to allocate actor archive buffer"); bufferPtr = (int16 *)archiveBuffer; // Store the number of actors in the archive buffer *bufferPtr++ = actorCount; // Store the actor data in the archive buffer for (i = 0; i < actorCount; i++) bufferPtr = (int16 *)actorList[i].archive(bufferPtr); // Write the archive buffer to the save file saveGame.writeChunk( MakeID('A', 'C', 'T', 'R'), archiveBuffer, archiveBufSize); free(archiveBuffer); } //------------------------------------------------------------------- // Load the actor list from a save file void loadActors(SaveFileReader &saveGame) { int16 i; int32 archiveBufSize; void *archiveBuffer; void *bufferPtr; // Read in the actor count saveGame.read(&actorCount, sizeof(actorCount)); // Allocate the actor array actorListSize = actorCount * sizeof(Actor); actorList = new Actor[actorCount](); if (actorList == NULL) error("Unable to load Actors"); // Allocate memory for the archive buffer archiveBufSize = saveGame.bytesLeftInChunk(); archiveBuffer = malloc(archiveBufSize); if (archiveBuffer == NULL) error("Unable to load Actors"); saveGame.read(archiveBuffer, archiveBufSize); for (i = 0, bufferPtr = archiveBuffer; i < actorCount; i++) // Initilize actors with archive data new (&actorList[i]) Actor(&bufferPtr); assert(bufferPtr == &((char *)archiveBuffer)[archiveBufSize]); // Deallocate the archive buffer free(archiveBuffer); } //------------------------------------------------------------------- // Cleanup the actor list void cleanupActors(void) { if (actorList != NULL) { int16 i; for (i = 0; i < actorCount; i++) actorList[i].~Actor(); delete[] actorList; actorList = NULL; } } /* ============================================================================ * Actor faction tallies * ============================================================================ */ int16 AddFactionTally(int faction, enum factionTallyTypes act, int amt) { #if DEBUG if (faction >= maxFactions) error("Scripter: Tell Talin to increase maxFactions!\n"); assert(faction >= 0); assert(act >= 0); assert(act < factionNumColumns); #endif /* // If faction attitude counts get to big then down-scale all of them // in proportion. if ( factionTable[faction][act] + amt > maxint16 ) { for (int i = 0; i < factionNumColumns; i++) factionTable[faction][i] >>= 1; } // Otherwise, if it doesn;t underflow, then add it in. if ( factionTable[faction][act] + amt > minint16 ) { factionTable[faction][act] += amt; } */ factionTable[faction][act] = clamp(minint16, factionTable[faction][act] + amt, maxint16); return factionTable[faction][act]; } // Get the attitude a particular faction has for a char. int16 GetFactionTally(int faction, enum factionTallyTypes act) { #if DEBUG if (faction >= maxFactions) error("Scripter: Tell Talin to increase maxFactions!\n"); assert(faction >= 0); assert(act >= 0); assert(act < factionNumColumns); #endif return factionTable[faction][act]; } //------------------------------------------------------------------- // Initialize the faction tally table void initFactionTallies(void) { memset(&factionTable, 0, sizeof(factionTable)); } //------------------------------------------------------------------- // Save the faction tallies to a save file void saveFactionTallies(SaveFileConstructor &saveGame) { saveGame.writeChunk( MakeID('F', 'A', 'C', 'T'), &factionTable, sizeof(factionTable)); } //------------------------------------------------------------------- // Load the faction tallies from a save file void loadFactionTallies(SaveFileReader &saveGame) { saveGame.read(&factionTable, sizeof(factionTable)); } }