/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * * Based on the original sources * Faery Tale II -- The Halls of the Dead * (c) 1993-1996 The Wyrmkeep Entertainment Co. */ #include "common/debug.h" #include "saga2/saga2.h" #include "saga2/detection.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/combat.h" // Include files needed for SAGA script dispatch #include "saga2/script.h" #include "saga2/methods.r" // generated by SAGA namespace Saga2 { /* ===================================================================== * Externals * ===================================================================== */ extern uint8 identityColors[256]; extern hResContext *listRes; // object list resource handle extern int16 actorLimboCount; bool unstickObject(GameObject *obj); extern ObjectSoundFXs *objectSoundFXTable; // the global object sound effects table #if DEBUG extern bool massAndBulkCount; #endif /* ===================================================================== * ActorProto member functions * ===================================================================== */ //----------------------------------------------------------------------- // Return a bit mask indicating the properties of this object type uint16 ActorProto::containmentSet() { // All actors may also be weapons (indicating natural attacks) return ProtoObj::containmentSet() | kIsWeapon; } //----------------------------------------------------------------------- // 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::kIsIntangible) == 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::kIsIntangible) == 0 || itemPtr->possessor() == dObj); } weaponID ActorProto::getWeaponID() { 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 |= kObjectOpen; // Set open bit; return true; } //----------------------------------------------------------------------- // close this actor bool ActorProto::closeAction(ObjectID dObj, ObjectID) { assert(isActor(dObj)); GameObject *dObjPtr = GameObject::objectAddress(dObj); ContainerNode *cn = g_vm->_cnm->find(dObj, ContainerNode::kDeadType); assert(dObjPtr->isOpen()); assert(cn); // Delete the container (lazy delete) cn->markForDelete(); // Clear open bit dObjPtr->_data.objectFlags &= ~kObjectOpen; 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(kSkillIDBludgeon))) 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(kSkillIDBrawn)); 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 & kIsIntangible) { // Set up the arguments we want to pass to the script scf.value = droppedObj->proto()->lockType + kSenseIdeaGreeting; // Invoke the script... if (dropType & kIsConcept) { runObjectMethod(dObj, Method_Actor_onTalkTo, scf); } else if (dropType & kIsPsych) { // What to do??? } else if (dropType & (kIsSpell | kIsSkill)) { // What to do??? // Cast the spell on the actor? } } else { scf.value = count; result = runObjectMethod(dObj, Method_Actor_onReceive, scf); if (result == kScriptResultFinished && scf.returnVal != kActionResultNotDone) return scf.returnVal == kActionResultSuccess; // 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 = kSenseIdeaGreeting; 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 damageScore = 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)) { damageScore = absDamage; if (dice) for (int d = 0; d < ABS(dice); d++) damageScore += (g_vm->_rnd->getRandomNumber(sides - 1) + pdm + 1) * (dice > 0 ? 1 : -1); } if (damageScore > 0 && resistant) damageScore /= 2; if (damageScore > 0 && isMagicDamage(dType) && makeSavingThrow()) damageScore /= 2; if (damageScore < 0) return acceptHealing(dObj, enactor, -damageScore); // Apply applicable armor adjustments if (dType == kDamageImpact || dType == kDamageSlash || dType == kDamageProjectile) { ArmorAttributes armorAttribs; a->totalArmorAttributes(armorAttribs); damageScore /= armorAttribs.damageDivider; damageScore = MAX(damageScore - armorAttribs.damageAbsorbtion, 0); } if (damageScore == 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) : nullptr; } if (vitality > 0) { Location al = Location(a->getLocation(), a->IDParent()); if (gruntStyle > 0 && ((flags & ResourceObjectPrototype::kObjPropNoSurface) || (damageScore > 2 && (int16)g_vm->_rnd->getRandomNumber(vitality - 1) < (damageScore * 2)))) makeGruntSound(gruntStyle, al); if (enactorPtr != nullptr) { enactorPtr->handleSuccessfulStrike( a, damageScore < vitality ? damageScore : vitality); } // If we've just lost all vitality, we're dead, else make a // morale check if (damageScore >= vitality) { MotionTask::die(*a); AddFactionTally(a->_faction, kFactionNumKills, 1); if (enactorPtr != nullptr) enactorPtr->handleSuccessfulKill(a); } else a->handleDamageTaken(damageScore); vitality -= damageScore; if (actorToPlayerID(a, pID)) { updateBrotherControls(pID); if (vitality > 0) { int16 baseVitality, oldVitality; baseVitality = a->getBaseStats()->vitality; oldVitality = vitality + damageScore; 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", damageScore); } 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(kActorDiseased)) { // 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::kSkillLevels - 1) / ActorAttributes::kSkillLevels; const int dodgingBonus = 10; Actor *a = (Actor *)GameObject::objectAddress(dObj); ActorAttributes *effStats = a->getStats(); GameObject *weapon = GameObject::objectAddress(strikingObj); assert(weapon->proto()->containmentSet() & ProtoObj::kIsWeapon); 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(kSkillIDAgility)) * skillScalingFactor; // Factor in armor bonus hitChance -= armorAttribs.defenseBonus; // Factor in dodging bonus if any if (a->_moveTask != nullptr && a->_moveTask->isDodging(enactorPtr)) hitChance -= dodgingBonus; hitChance = MAX(hitChance, 5); // Randomly determine hit success if (g_vm->_rnd->getRandomNumber(toHitBase - 1) < hitChance) { // Hit has succeeded GameObject *blockingObj = a->blockingObject(enactorPtr); bool blocked = false; // Test for block success if (blockingObj != nullptr) { hitChance = avgHitChance + ((int)skillIndex - (int)blockingObj->proto()->getSkillValue(dObj)) * skillScalingFactor; if (g_vm->_rnd->getRandomNumber(toHitBase - 1) >= 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 || (int16)g_vm->_rnd->getRandomNumber(155) >= pmass - 100) { if (g_vm->_rnd->getRandomNumber(7) == 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(kSkillIDAgility, 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 = 0; assert(isActor(dObj)); assert(isObject(item)); GameObject *dObjPtr = GameObject::objectAddress(dObj); Actor *a = (Actor *)dObjPtr; GameObject *itemPtr = GameObject::objectAddress(item); GameObject *extractedObj = nullptr; 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 == nullptr) 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 != nullptr) 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 != nullptr) { if (attackerPtr->isActionAvailable(kActionSwingHigh)) MotionTask::oneHandedSwing(*attackerPtr, *targetPtr); else if (attackerPtr->isActionAvailable(kActionTwoHandSwingHigh)) 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::kObjPropNoSurface) ? !(flags & ResourceObjectPrototype::kObjPropHardSurface) ? 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::kAFTemporary) || 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::kAFAfraid) && g_vm->_rnd->getRandomNumber(127) == 0) a->_flags &= ~Actor::kAFAfraid; } } // 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: g_vm->_playerList[FTA_JULIAN]->recoveryUpdate(); break; case ActorBaseID + FTA_PHILIP: g_vm->_playerList[FTA_PHILIP]->recoveryUpdate(); break; case ActorBaseID + FTA_KEVIN: g_vm->_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(kSkillIDBludgeon, points); if (g_vm->_rnd->getRandomNumber(1)) player->skillAdvance(kSkillIDBrawn, 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 kBaseCarryingCapacity + effStats->getSkillLevel(kSkillIDBrawn) * kCarryingCapacityBonusPerBrawn; } // ------------------------------------------------------------------------ // 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[kActorScriptVars]; }; /* ===================================================================== * 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) { debugC(1, kDebugActors, "Actor init flags: %d, permanent: %d", initFlags, initFlags & kActorPermanent); // Fixup the prototype pointer to point to an actor prototype _prototype = (ProtoObj *)g_vm->_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 = kDirDown; _tetherLocU = 0; _tetherLocV = 0; _tetherDist = 0; _leftHandObject = Nothing; _rightHandObject = Nothing; _schedule = 0; for (uint i = 0; i < ARRAYSIZE(_knowledge); ++i) _knowledge[i] = 0; // Initialize the rest of the data members for (uint i = 0; i < ARRAYSIZE(_conversationMemory); ++i) _conversationMemory[i] = 0; _currentAnimation = kActionStand; _currentPose = 0; _animationFlags = 0; _flags = 0; if (!(initFlags & kActorPermanent)) _flags |= kAFTemporary; _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 = kActorGoalFollowAssignment; _deactivationCounter = 0; _assignment = nullptr; _effectiveStats = ((ActorProto *)_prototype)->baseStats; _effectiveStats.vitality = MAX(_effectiveStats.vitality, 1); _actionCounter = 0; _effectiveResistance = 0; _effectiveImmunity = 0; _recPointsPerUpdate = BASE_REC_RATE; _currentRecoveryPoints = 0; _leader = nullptr; _followers = nullptr; _followersID = NoBand; for (int i = 0; i < ARMOR_COUNT; i++) _armorObjects[i] = Nothing; _currentTarget = nullptr; for (int i = 0; i < kActorScriptVars; 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() { _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 (uint i = 0; i < ARRAYSIZE(_knowledge); ++i) _knowledge[i] = 0; // Initialize the rest of the data members for (uint i = 0; i < ARRAYSIZE(_conversationMemory); ++i) _conversationMemory[i] = 0; _currentAnimation = kActionStand; _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 = kActorGoalFollowAssignment; _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; _leaderID = Nothing; _followers = nullptr; _followersID = NoBand; for (int i = 0; i < ARMOR_COUNT; i++) _armorObjects[i] = Nothing; _currentTarget = nullptr; _currentTargetID = Nothing; for (int i = 0; i < kActorScriptVars; i++) _scriptVar[i] = 0; } Actor::Actor(const ResourceActor &res) : GameObject(res) { // Fixup the prototype pointer to point to an actor prototype _prototype = _prototype != nullptr ? (ProtoObj *)g_vm->_actorProtos[getProtoNum()] : nullptr; // 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 for (uint i = 0; i < ARRAYSIZE(_conversationMemory); ++i) _conversationMemory[i] = 0; _currentAnimation = kActionStand; _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 = kActorGoalFollowAssignment; _deactivationCounter = 0; _assignment = nullptr; if (_prototype) _effectiveStats = ((ActorProto *)_prototype)->baseStats; _effectiveStats.vitality = MAX(_effectiveStats.vitality, 1); _actionCounter = 0; _effectiveResistance = 0; _effectiveImmunity = 0; _recPointsPerUpdate = BASE_REC_RATE; _currentRecoveryPoints = 0; _leader = nullptr; _leaderID = Nothing; _followers = nullptr; _followersID = NoBand; for (int i = 0; i < ARMOR_COUNT; i++) _armorObjects[i] = Nothing; _currentTarget = nullptr; _currentTargetID = Nothing; for (int i = 0; i < kActorScriptVars; i++) _scriptVar[i] = 0; evalActorEnchantments(this); } Actor::Actor(Common::InSaveFile *in) : GameObject(in) { // Fixup the prototype pointer to point to an actor prototype _prototype = _prototype != nullptr ? (ProtoObj *)g_vm->_actorProtos[getProtoNum()] : nullptr; _faction = in->readByte(); _colorScheme = in->readByte(); _appearanceID = in->readSint32BE(); _attitude = in->readSByte(); _mood = in->readSByte(); _disposition = in->readByte(); _currentFacing = in->readByte(); _tetherLocU = in->readSint16LE(); _tetherLocV = in->readSint16LE(); _tetherDist = in->readSint16LE(); _leftHandObject = in->readUint16LE(); _rightHandObject = in->readUint16LE(); for (int i = 0; i < ARRAYSIZE(_knowledge); ++i) _knowledge[i] = in->readUint16LE(); _schedule = in->readUint16LE(); for (int i = 0; i < ARRAYSIZE(_conversationMemory); ++i) _conversationMemory[i] = in->readByte(); _currentAnimation = in->readByte(); _currentPose = in->readByte(); _animationFlags = in->readByte(); _flags = in->readByte(); _poseInfo.load(in); _cycleCount = in->readSint16LE(); _kludgeCount = in->readSint16LE(); _enchantmentFlags = in->readUint32LE(); _currentGoal = in->readByte(); _deactivationCounter = in->readByte(); _effectiveStats.read(in); _actionCounter = in->readByte(); _effectiveResistance = in->readUint16LE(); _effectiveImmunity = in->readUint16LE(); _recPointsPerUpdate = in->readSint16LE(); _currentRecoveryPoints = in->readUint16LE(); _leaderID = in->readUint16LE(); _leader = nullptr; _followersID = in->readSint16LE(); _followers = nullptr; for (int i = 0; i < ARRAYSIZE(_armorObjects); ++i) _armorObjects[i] = in->readUint16LE(); _currentTargetID = in->readUint16LE(); _currentTarget = nullptr; for (int i = 0; i < ARRAYSIZE(_scriptVar); ++i) _scriptVar[i] = in->readSint16LE(); if (_flags & kAFHasAssignment) { readAssignment(this, in); } else { _assignment = nullptr; } _appearance = nullptr; _moveTask = nullptr; _curTask = nullptr; debugC(4, kDebugSaveload, "... _faction = %d", _faction); debugC(4, kDebugSaveload, "... _colorScheme = %d", _colorScheme); debugC(4, kDebugSaveload, "... _appearanceID = %d", _appearanceID); debugC(4, kDebugSaveload, "... _attitude = %d", _attitude); debugC(4, kDebugSaveload, "... _mood = %d", _mood); debugC(4, kDebugSaveload, "... _disposition = %d", _disposition); debugC(4, kDebugSaveload, "... _currentFacing = %d", _currentFacing); debugC(4, kDebugSaveload, "... _tetherLocU = %d", _tetherLocU); debugC(4, kDebugSaveload, "... _tetherLocV = %d", _tetherLocV); debugC(4, kDebugSaveload, "... _tetherDist = %d", _tetherDist); debugC(4, kDebugSaveload, "... _leftHandObject = %d", _leftHandObject); debugC(4, kDebugSaveload, "... _rightHandObject = %d", _rightHandObject); // debugC(4, kDebugSaveload, "... knowledge = %d", knowledge); debugC(4, kDebugSaveload, "... _schedule = %d", _schedule); // debugC(4, kDebugSaveload, "... conversationMemory = %d", conversationMemory); debugC(4, kDebugSaveload, "... _currentAnimation = %d", _currentAnimation); debugC(4, kDebugSaveload, "... _currentPose = %d", _currentPose); debugC(4, kDebugSaveload, "... _animationFlags = %d", _animationFlags); debugC(4, kDebugSaveload, "... _flags = %d", _flags); // debugC(4, kDebugSaveload, "... out = %d", out); debugC(4, kDebugSaveload, "... _cycleCount = %d", _cycleCount); debugC(4, kDebugSaveload, "... _kludgeCount = %d", _kludgeCount); debugC(4, kDebugSaveload, "... _enchantmentFlags = %d", _enchantmentFlags); debugC(4, kDebugSaveload, "... _currentGoal = %d", _currentGoal); debugC(4, kDebugSaveload, "... _deactivationCounter = %d", _deactivationCounter); // debugC(4, kDebugSaveload, "... out = %d", out); debugC(4, kDebugSaveload, "... _actionCounter = %d", _actionCounter); debugC(4, kDebugSaveload, "... _effectiveResistance = %d", _effectiveResistance); debugC(4, kDebugSaveload, "... _effectiveImmunity = %d", _effectiveImmunity); debugC(4, kDebugSaveload, "... _recPointsPerUpdate = %d", _recPointsPerUpdate); debugC(4, kDebugSaveload, "... _currentRecoveryPoints = %d", _currentRecoveryPoints); debugC(4, kDebugSaveload, "... _leaderID = %d", _leaderID); debugC(4, kDebugSaveload, "... _followersID = %d", _followersID); // debugC(4, kDebugSaveload, "... armorObjects = %d", armorObjects); debugC(4, kDebugSaveload, "... _currentTargetID = %d", _currentTargetID); // debugC(4, kDebugSaveload, "... scriptVar = %d", scriptVar); } //----------------------------------------------------------------------- // Destructor Actor::~Actor() { if (_appearance != nullptr) ReleaseActorAppearance(_appearance); if (getAssignment()) delete getAssignment(); } //----------------------------------------------------------------------- // Return the number of bytes needed to archive this actor int32 Actor::archiveSize() { int32 size = GameObject::archiveSize(); size += sizeof(ActorArchive); if (_flags & kAFHasAssignment) size += assignmentArchiveSize(this); return size; } void Actor::write(Common::MemoryWriteStreamDynamic *out) { ProtoObj *holdProto = _prototype; debugC(3, kDebugSaveload, "Saving actor %d", thisID()); // Modify the protoype temporarily so the GameObject::write() // will store the index correctly if (_prototype != nullptr) _prototype = g_vm->_objectProtos[getProtoNum()]; GameObject::write(out, false); // Restore the prototype pointer _prototype = holdProto; out->writeByte(_faction); out->writeByte(_colorScheme); out->writeSint32BE(_appearanceID); out->writeSByte(_attitude); out->writeSByte(_mood); out->writeByte(_disposition); out->writeByte(_currentFacing); out->writeSint16LE(_tetherLocU); out->writeSint16LE(_tetherLocV); out->writeSint16LE(_tetherDist); out->writeUint16LE(_leftHandObject); out->writeUint16LE(_rightHandObject); out->write(_knowledge, sizeof(_knowledge)); out->writeUint16LE(_schedule); out->write(_conversationMemory, sizeof(_conversationMemory)); out->writeByte(_currentAnimation); out->writeByte(_currentPose); out->writeByte(_animationFlags); out->writeByte(_flags); _poseInfo.write(out); out->writeSint16LE(_cycleCount); out->writeSint16LE(_kludgeCount); out->writeUint32LE(_enchantmentFlags); out->writeByte(_currentGoal); out->writeByte(_deactivationCounter); _effectiveStats.write(out); out->writeByte(_actionCounter); out->writeUint16LE(_effectiveResistance); out->writeUint16LE(_effectiveImmunity); out->writeSint16LE(_recPointsPerUpdate); out->writeUint16LE(_currentRecoveryPoints); _leaderID = (_leader != nullptr) ? _leader->thisID() : Nothing; out->writeUint16LE(_leaderID); _followersID = (_followers != nullptr) ? getBandID(_followers) : NoBand; out->writeSint16LE(_followersID); out->write(_armorObjects, ARMOR_COUNT * 2); _currentTargetID = _currentTarget != nullptr ? _currentTarget->thisID() : Nothing; out->writeUint16LE(_currentTargetID); out->write(_scriptVar, sizeof(_scriptVar)); if (_flags & kAFHasAssignment) writeAssignment(this, out); debugC(4, kDebugSaveload, "... _faction = %d", _faction); debugC(4, kDebugSaveload, "... _colorScheme = %d", _colorScheme); debugC(4, kDebugSaveload, "... _appearanceID = %d", _appearanceID); debugC(4, kDebugSaveload, "... _attitude = %d", _attitude); debugC(4, kDebugSaveload, "... _mood = %d", _mood); debugC(4, kDebugSaveload, "... _disposition = %d", _disposition); debugC(4, kDebugSaveload, "... _currentFacing = %d", _currentFacing); debugC(4, kDebugSaveload, "... _tetherLocU = %d", _tetherLocU); debugC(4, kDebugSaveload, "... _tetherLocV = %d", _tetherLocV); debugC(4, kDebugSaveload, "... _tetherDist = %d", _tetherDist); debugC(4, kDebugSaveload, "... _leftHandObject = %d", _leftHandObject); debugC(4, kDebugSaveload, "... _rightHandObject = %d", _rightHandObject); // debugC(4, kDebugSaveload, "... knowledge = %d", knowledge); debugC(4, kDebugSaveload, "... _schedule = %d", _schedule); // debugC(4, kDebugSaveload, "... conversationMemory = %d", conversationMemory); debugC(4, kDebugSaveload, "... _currentAnimation = %d", _currentAnimation); debugC(4, kDebugSaveload, "... _currentPose = %d", _currentPose); debugC(4, kDebugSaveload, "... _animationFlags = %d", _animationFlags); debugC(4, kDebugSaveload, "... _flags = %d", _flags); // debugC(4, kDebugSaveload, "... out = %d", out); debugC(4, kDebugSaveload, "... _cycleCount = %d", _cycleCount); debugC(4, kDebugSaveload, "... _kludgeCount = %d", _kludgeCount); debugC(4, kDebugSaveload, "... _enchantmentFlags = %d", _enchantmentFlags); debugC(4, kDebugSaveload, "... _currentGoal = %d", _currentGoal); debugC(4, kDebugSaveload, "... _deactivationCounter = %d", _deactivationCounter); // debugC(4, kDebugSaveload, "... out = %d", out); debugC(4, kDebugSaveload, "... _actionCounter = %d", _actionCounter); debugC(4, kDebugSaveload, "... _effectiveResistance = %d", _effectiveResistance); debugC(4, kDebugSaveload, "... _effectiveImmunity = %d", _effectiveImmunity); debugC(4, kDebugSaveload, "... _recPointsPerUpdate = %d", _recPointsPerUpdate); debugC(4, kDebugSaveload, "... _currentRecoveryPoints = %d", _currentRecoveryPoints); debugC(4, kDebugSaveload, "... _leaderID = %d", _leader != nullptr ? _leader->thisID() : Nothing); debugC(4, kDebugSaveload, "... _followersID = %d", _followers != nullptr ? getBandID(_followers) : NoBand); // debugC(4, kDebugSaveload, "... armorObjects = %d", armorObjects); debugC(4, kDebugSaveload, "... _currentTargetID = %d", _currentTarget != nullptr ? _currentTarget->thisID() : Nothing); // debugC(4, kDebugSaveload, "... scriptVar = %d", scriptVar); } //----------------------------------------------------------------------- // 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 = nullptr; 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 = kPlayerActors; i < kActorCount; i++) { a = g_vm->_act->_actorList[i]; if ((a->_flags & kAFTemporary) && !a->isActivated() && isWorld(a->IDParent())) break; } // REM: If things start getting really tight, we can // start recycling common objects... if (i >= kActorCount) return nullptr; } else { actorLimboCount--; a = (Actor *)limbo->child(); } if (!a) return nullptr; a->setLocation(Location(0, 0, 0, Nothing)); a->init( protoNum, nameIndex, scriptIndex, appearanceNum, colorSchemeIndex, factionNum, initFlags); if (a->_flags & kAFTemporary) { 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() { if (_flags & kAFTemporary) { uint16 protoNum = getProtoNum(); decTempActorCount(protoNum); debugC(1, kDebugActors, "Actors: Deleting temp actor %d (%s) new count:%d", thisID() - 32768, objName(), getTempActorCount(protoNum)); } // Kill task if (_curTask != nullptr) { _curTask->abortTask(); delete _curTask; _curTask = nullptr; } // Kill motion task if (_moveTask != nullptr) _moveTask->remove(); // If banded, remove from band if (_leader != nullptr) { assert(isActor(_leader)); _leader->removeFollower(this); _leader = nullptr; } else if (_followers != nullptr) { int16 i; for (i = 0; i < _followers->size(); i++) { Actor *follower = (*_followers)[i]; follower->_leader = nullptr; follower->evaluateNeeds(); } delete _followers; _followers = nullptr; } // Place in limbo if (!(_data.objectFlags & kObjectNoRecycle)) { append(ActorLimbo); actorLimboCount++; } } //----------------------------------------------------------------------- // Cause the actor to stop his current motion task is he is interruptable void Actor::stopMoving() { if (_moveTask != nullptr && isInterruptable()) _moveTask->remove(); } //----------------------------------------------------------------------- // Cause this actor to die void Actor::die() { 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 != nullptr) { _curTask->abortTask(); delete _curTask; _curTask = nullptr; } // Kill motion task if (_moveTask != nullptr) _moveTask->remove(); // If banded, remove from band if (_leader != nullptr) { assert(isActor(_leader)); _leader->removeFollower(this); _leader = nullptr; } if (actorToPlayerID(this, playerID)) handlePlayerActorDeath(playerID); } //----------------------------------------------------------------------- // Cause this actor to come back to life void Actor::imNotQuiteDead() { 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() { // 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 / kRecPointsPerVitality; // get the fraction fractionRecover = _recPointsPerUpdate % kRecPointsPerVitality; // if there is an overrun if (_currentRecoveryPoints + fractionRecover > kRecPointsPerVitality) { // add the overrun to the whole number recover++; _currentRecoveryPoints = (_currentRecoveryPoints + fractionRecover) - kRecPointsPerVitality; } 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() { debugC(1, kDebugActors, "Actors: Activated %d (%s)", thisID() - 32768, objName()); evaluateNeeds(); } //----------------------------------------------------------------------- // Perfrom actor specific deactivation tasks void Actor::deactivateActor() { debugC(1, kDebugActors, "Actors: De-activated %d (%s)", thisID() - 32768, objName()); // Kill task if (_curTask != nullptr) { _curTask->abortTask(); delete _curTask; _curTask = nullptr; } // Kill motion task if (_moveTask != nullptr) _moveTask->remove(); // If banded, remove from band if (_leader != nullptr) { assert(isActor(_leader)); _leader->removeFollower(this); _leader = nullptr; } // Temporary actors get deleted upon deactivation if ((_flags & kAFTemporary) || isDead()) { _deactivationCounter = 10; // actor lasts for 50 seconds } } //----------------------------------------------------------------------- // Delobotomize this actor void Actor::delobotomize() { if (!(_flags & kAFLobotomized)) return; ObjectID dObj = thisID(); scriptCallFrame scf; _flags &= ~kAFLobotomized; 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() { if (_flags & kAFLobotomized) return; ObjectID dObj = thisID(); scriptCallFrame scf; // Kill task if (_curTask != nullptr) { _curTask->abortTask(); delete _curTask; _curTask = nullptr; } // Kill motion task if (_moveTask != nullptr) _moveTask->remove(); _flags |= kAFLobotomized; 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() { if (_disposition < kDispositionPlayer) return &((ActorProto *)_prototype)->baseStats; else return &g_vm->_playerList[_disposition - kDispositionPlayer]->_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() { //if ( disposition < kDispositionPlayer ) 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() { //if ( disposition < kDispositionPlayer ) 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() { //if ( disposition < kDispositionPlayer ) return ((ActorProto *)_prototype)->immunity; } //----------------------------------------------------------------------- // Return the base recovery rate uint16 Actor::getBaseRecovery() { return BASE_REC_RATE; } //----------------------------------------------------------------------- // Determine if specified point is within actor's reach bool Actor::inReach(const TilePoint &tp) { return inRange(tp, kDefaultReach); } //----------------------------------------------------------------------- // 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, (uint16)kDefaultReach)); } //----------------------------------------------------------------------- // Determine if actor is immobile (i.e. can't walk) bool Actor::isImmobile() { return isDead() || hasEffect(kActorImmobile) || hasEffect(kActorAsleep) || hasEffect(kActorParalyzed); } //----------------------------------------------------------------------- // Return a pointer to this actor's currently readied offensive object GameObject *Actor::offensiveObject() { 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::kIsWeapon); return obj; } if (_leftHandObject != Nothing) { assert(isObject(_leftHandObject)); GameObject *obj = GameObject::objectAddress(_leftHandObject); if (obj->containmentSet() & ProtoObj::kIsWeapon) 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 != nullptr); GameObject *leftHandObjPtr, *rightHandObjPtr, *primary = nullptr, *secondary = nullptr; // Get a pointer to the left hand object leftHandObjPtr = _leftHandObject != Nothing ? (assert(isObject(_leftHandObject)) , GameObject::objectAddress(_leftHandObject)) : nullptr; // Get a pointer to the right hand object rightHandObjPtr = _rightHandObject != Nothing ? (assert(isObject(_rightHandObject)) , GameObject::objectAddress(_rightHandObject)) : nullptr; if (leftHandObjPtr != nullptr) { 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 != nullptr && rightHandObjPtr->proto()->canBlock()) // Right hand object is defensive *rightHandObjDest = rightHandObjPtr; } else { if (rightHandObjPtr != nullptr && rightHandObjPtr->proto()->canBlock()) // Right hand object is primary defensive object primary = rightHandObjPtr; } // Return the primary pointer *priPtr = primary; // Return the secondary pointer if (secPtr != nullptr) *secPtr = secondary; } //----------------------------------------------------------------------- // Returns a pointer to the object with which this actor is currently // blocking, if any GameObject *Actor::blockingObject(Actor *attacker) { return _moveTask != nullptr ? _moveTask->blockingObject(attacker) : nullptr; } //----------------------------------------------------------------------- // 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 != nullptr); 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 != nullptr ? weapon->proto()->maximumRange : 0; return inRange(tp, MAX(range, (uint16)kDefaultReach)); } //----------------------------------------------------------------------- // Initiate an attack upon a specified target void Actor::attack(GameObject *target) { GameObject *weapon = offensiveObject(); if (weapon != nullptr) 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() { 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() { // REM: at this time this calculation is somewhat arbitrary int16 score = 0; GameObject *weapon = offensiveObject(); if (weapon != nullptr) { 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() { // REM: at this time this calculation is somewhat arbitrary int16 score = 0; GameObject *shield; ArmorAttributes armorAttribs; defensiveObject(&shield); if (shield != nullptr) { 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 == nullptr) 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 & kAnimateNoRestart)) { if (flags & kAnimateReverse) _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 < kNumPoseFacings; 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() { ActorAnimation *anim; int16 numPoses; // Refresh the handles // RLockHandle( appearance->animations ); // RUnlockHandle( appearance->animations ); if (_appearance == nullptr) { if (_animationFlags & kAnimateOnHold) { return false; } else if (_animationFlags & kAnimateRepeat) { _animationFlags |= kAnimateOnHold; return false; } else { _animationFlags |= kAnimateFinished; return true; } } else _animationFlags &= ~kAnimateOnHold; // Get the number of frames in the animation anim = _appearance->animation(_currentAnimation); numPoses = anim->count[_currentFacing]; if (numPoses <= 0) { _animationFlags |= kAnimateFinished; 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 & kAnimateNotLoaded) return false; // If the animation has reached the last frame, then exit. if (_animationFlags & kAnimateFinished) return true; if (_animationFlags & kAnimateRandom) { // Select a random frame from the series. _currentPose = g_vm->_rnd->getRandomNumber(numPoses - 1); } else if (_animationFlags & kAnimateReverse) { // 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 & kAnimateRepeat)) { _animationFlags |= kAnimateFinished; } } else if (_animationFlags & kAnimateRepeat) { // 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 & kAnimateAlternate) { _animationFlags &= ~kAnimateReverse; _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 & (kAnimateAlternate | kAnimateRepeat))) _animationFlags |= kAnimateFinished; } else if (_animationFlags & kAnimateAlternate) { // At the end of the sequence, reverse direction _animationFlags |= kAnimateReverse; _currentPose = MAX(_currentPose - 1, 0); } else if (_animationFlags & kAnimateRepeat) { // Wrap back to beginning _currentPose = 0; } else //If Last Frame And Not Animate Repeat or Alternate _animationFlags |= kAnimateFinished; } return false; } //----------------------------------------------------------------------- // Drop the all of the actor's inventory void Actor::dropInventory() { GameObject *obj, *nextObj; for (obj = _data.childID != Nothing ? GameObject::objectAddress(_data.childID) : nullptr; obj != nullptr; obj = nextObj) { nextObj = obj->IDNext() != Nothing ? GameObject::objectAddress(obj->IDNext()) : nullptr; // Delete intangible objects and drop tangible objects if (obj->containmentSet() & ProtoObj::kIsIntangible) 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)) g_vm->_cnm->setUpdate(thisID()); evalActorEnchantments(this); } void Actor::holdInLeftHand(ObjectID objID) { assert(isObject(objID)); _leftHandObject = objID; if (isPlayerActor(this)) g_vm->_cnm->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::kIsArmor); } #endif _armorObjects[where] = objID; if (isPlayerActor(this)) g_vm->_cnm->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 & kAFLobotomized)) 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, kTerrainSurface)) ShowObjectSection(this); #endif if (_appearance) { if (animationFrames(kActionStand, _currentFacing) == 1) { if (_flags & kAFFightStance) { GameObject *weapon = offensiveObject(); if (weapon == this) weapon = nullptr; if (weapon != nullptr) { ProtoObj *weaponProto = weapon->proto(); setAction(weaponProto->fightStanceAction(thisID()), 0); } else { if (isActionAvailable(kActionSwingHigh)) setAction(kActionSwingHigh, 0); else setAction(kActionTwoHandSwingHigh, 0); } _cycleCount = 0; } else { if (_cycleCount > 0) { //If In Wait State Between Wait Animation _cycleCount--; setAction(kActionStand, 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(kActionWaitAgressive, kActionWaitImpatient, kActionWaitFriendly, kActionStand); // This is default break; case 1: setAvailableAction(kActionWaitImpatient, kActionWaitFriendly, kActionWaitAgressive, kActionStand); break; case 2: setAvailableAction(kActionWaitFriendly, kActionWaitImpatient, kActionWaitAgressive, kActionStand); } } else //Assume -1 if (nextAnimationFrame())//If Last Frame In Wait Animation _cycleCount = g_vm->_rnd->getRandomNumber(19); } } } else { if (_currentAnimation != kActionStand || (_animationFlags & kAnimateRepeat) == 0) setAction(kActionStand, kAnimateRepeat); else nextAnimationFrame(); } }// End if (appearance) } bool Actor::setAvailableAction(int16 action1, int16 action2, int16 action3, int16 actiondefault) { if (setAction(action1, 0)) return true; if (setAction(action2, 0)) return true; if (setAction(action3, 0)) return true; if (setAction(actiondefault, 0)) return true; return false; } //----------------------------------------------------------------------- // Set a new goal for this actor void Actor::setGoal(uint8 newGoal) { if (_currentGoal != newGoal) { if (_curTask != nullptr) { _curTask->abortTask(); delete _curTask; _curTask = nullptr; } _currentGoal = newGoal; } } //----------------------------------------------------------------------- // Reevaluate actor's built-in needs void Actor::evaluateNeeds() { if (!isDead() && isActivated() && !(_flags & kAFLobotomized)) { if (_disposition >= kDispositionPlayer) { if (g_vm->_act->_combatBehaviorEnabled) { SenseInfo info; if (canSenseActorProperty( info, kMaxSenseRange, kActorPropIDEnemy) || canSenseActorPropertyIndirectly( info, kMaxSenseRange, kActorPropIDEnemy)) { PlayerActorID playerID = _disposition - kDispositionPlayer; if (isAggressive(playerID)) setGoal(kActorGoalAttackEnemy); else { if (_leader != nullptr && inBandingRange()) setGoal(kActorGoalAvoidEnemies); else setGoal(kActorGoalPreserveSelf); } } else if (_leader != nullptr && inBandingRange()) { setGoal(kActorGoalFollowLeader); } else { setGoal(kActorGoalFollowAssignment); } } else if (_leader != nullptr && inBandingRange()) { setGoal(kActorGoalFollowLeader); } else { setGoal(kActorGoalFollowAssignment); } } else { if (_disposition == kDispositionEnemy && _appearance != nullptr && !hasEffect(kActorNotDefenseless)) { GameObject *obj; bool foundWeapon = false; ContainerIterator iter(this); while (iter.next(&obj) != Nothing) { ProtoObj *proto = obj->proto(); if ((proto->containmentSet() & ProtoObj::kIsWeapon) && isActionAvailable(proto->fightStanceAction(thisID()))) { foundWeapon = true; break; } } if (!foundWeapon && (isActionAvailable(kActionSwingHigh) || isActionAvailable(kActionTwoHandSwingHigh))) foundWeapon = true; if (!foundWeapon) _flags |= kAFAfraid; } if (_flags & kAFAfraid || hasEffect(kActorFear) || hasEffect(kActorRepelUndead)) { setGoal(kActorGoalPreserveSelf); } else if (_leader != nullptr && inBandingRange()) { setGoal(_leader->evaluateFollowerNeeds(this)); } else { SenseInfo info; if (_disposition == kDispositionEnemy && (getAssignment() == nullptr || canSenseProtaganist( info, kMaxSenseRange) || canSenseProtaganistIndirectly( info, kMaxSenseRange))) { setGoal(kActorGoalAttackEnemy); } else { setGoal(kActorGoalFollowAssignment); } } } } } void Actor::updateState() { // 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 & kAFLobotomized) return; // Update the action counter if (_actionCounter != 0 && _actionCounter != maxuint8) _actionCounter--; if (_appearance != nullptr && isDead() && isInterruptable() && (_moveTask == nullptr || _moveTask->_motionType != MotionTask::kMotionTypeDie)) { int16 deadState = isActionAvailable(kActionDead) ? kActionDead : isActionAvailable(kActionDie) ? kActionDie : kActionStand; if (_currentAnimation != deadState) MotionTask::die(*this); return; } if (!isDead()) { if (this == getCenterActor()) return; if (_flags & kAFSpecialAttack) { _flags &= ~kAFSpecialAttack; if (_currentTarget != nullptr) { 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 & kAFLobotomized)) return; } } switch (_currentGoal) { case kActorGoalFollowAssignment: { ActorAssignment *assign = getAssignment(); // Iterate until there is no assignment, or the current // assignment is valid while (assign != nullptr && !assign->isValid()) { g_vm->_act->_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 kAFLobotomized // return immediately if (isDead() || !isActivated() || (_flags & kAFLobotomized)) 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 == nullptr && _schedule != 0) { g_vm->_act->_updatesViaScript++; assert(_curTask == nullptr); 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 != nullptr && _curTask == nullptr) _curTask = assign->createTask(); } break; case kActorGoalPreserveSelf: if (_leader != nullptr || _followers != nullptr) disband(); if (_curTask == nullptr) { if ((_curTask = newTaskStack(this)) != nullptr) { Task *task = new GoAwayFromActorTask( _curTask, ActorPropertyTarget( _disposition == kDispositionEnemy ? kActorPropIDPlayerActor : kActorPropIDEnemy), true); if (task != nullptr) _curTask->setTask(task); else { delete _curTask; _curTask = nullptr; } } } break; case kActorGoalAttackEnemy: if (_curTask == nullptr) { if ((_curTask = newTaskStack(this)) != nullptr) { uint8 disp = _leader != nullptr ? _leader->_disposition : _disposition; Task *task = new HuntToKillTask( _curTask, ActorPropertyTarget( disp == kDispositionEnemy ? kActorPropIDPlayerActor : kActorPropIDEnemy)); if (task != nullptr) _curTask->setTask(task); else { delete _curTask; _curTask = nullptr; } } } break; case kActorGoalFollowLeader: assert(isActor(_leader)); assert(_followers == nullptr); if (_curTask == nullptr) _curTask = _leader->createFollowerTask(this); break; case kActorGoalAvoidEnemies: assert(isActor(_leader)); assert(_followers == nullptr); if (_curTask == nullptr) { if ((_curTask = newTaskStack(this)) != nullptr) { Task *task = new BandAndAvoidEnemiesTask(_curTask); if (task != nullptr) _curTask->setTask(task); else { delete _curTask; _curTask = nullptr; } } } } } } //----------------------------------------------------------------------- // 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 = nullptr; switch (_currentGoal) { case kActorGoalFollowAssignment: { ActorAssignment *assign = getAssignment(); // If we've gotten to this point, there had better be an // assignment, or something is amiss assert(assign != nullptr); // 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 == kDispositionFriendly) { if (attacker->_disposition >= kDispositionPlayer) { _disposition = kDispositionEnemy; 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 == kBehaviorHungry) return; if (offensiveObject() == this && !isActionAvailable(kActionSwingHigh) && !isActionAvailable(kActionTwoHandSwingHigh) && !hasEffect(kActorNotDefenseless)) { _flags |= kAFAfraid; return; } if (combatBehavior != kBehaviorHungry && (_flags & kAFTemporary) && !hasEffect(kActorFear) && !hasEffect(kActorRepelUndead)) { if (_flags & kAFAfraid) { // Let's give monsters a small chance of regaining their courage if ((uint16)g_vm->_rnd->getRandomNumber(0xffff) <= 0x3fff) _flags &= ~kAFAfraid; } 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 == kBehaviorCowardly) moraleBase += moraleBase / 2; else if (combatBehavior == kBehaviorBerserk) moraleBase -= moraleBase / 2; // Determine how many fellow band members this actor has. if (_leader != nullptr) fellowBandMembers = _leader->_followers->size(); else if (_followers != nullptr) 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)g_vm->_rnd->getRandomNumber(0xffff) <= moraleBase) _flags |= kAFAfraid; } } } //----------------------------------------------------------------------- // 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)) { 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])) == nullptr ? "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 const 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 != nullptr && canBlockWith(primary, relativeDir); if (canBlockWithPrimary) { bool canBlockWithSecondary; canBlockWithSecondary = secondary != nullptr && canBlockWith( secondary, relativeDir); if (canBlockWithSecondary) { // If we can block with either primary or secondary // there is a 25% chance of using the secondary defenseObj = (g_vm->_rnd->getRandomNumber(3) != 0) ? primary : secondary; } else { // The primary defensive object will be used defenseObj = primary; } } else defenseObj = nullptr; if (defenseObj != nullptr) { // Start a defensive motion defenseObj->proto()->initiateDefense( defenseObj->thisID(), thisID(), attacker->thisID()); } else { if (isActionAvailable(kActionJumpUp)) 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 == nullptr); // If the actor we're banding with is not the leader, then band // with his leader if (newLeader->_leader != nullptr) { newLeader = newLeader->_leader; assert(newLeader->_leader == nullptr); } // 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 == nullptr) { if (newLeader->addFollower(this)) _leader = newLeader; } else { int16 i, oldFollowerCount = _followers->size(); Actor **oldFollowers = new Actor * [oldFollowerCount]; if (oldFollowers != nullptr) { // 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 == nullptr); // 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() { if (_leader != nullptr) { _leader->removeFollower(this); _leader = nullptr; evaluateNeeds(); } else if (_followers != nullptr) { int16 i; for (i = 0; i < _followers->size(); i++) { Actor *follower = (*_followers)[i]; follower->_leader = nullptr; follower->evaluateNeeds(); } delete _followers; _followers = nullptr; } } //----------------------------------------------------------------------- // 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 == nullptr); assert(newBandMember->_followers == nullptr); // Allocate a new band, if needed if (_followers == nullptr && (_followers = new Band(this)) == nullptr) 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 != nullptr); int16 i; _followers->remove(bandMember); if (_followers->size() == 0) { delete _followers; _followers = nullptr; } 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 == kActorGoalAttackEnemy && combatBehavior != kBehaviorHungry) { uint32 moraleBase; moraleBase = combatBehavior == kBehaviorCowardly ? (1 << 16) / 4 : combatBehavior == kBehaviorSmart ? (1 << 16) / 8 : (1 << 16) / 16; moraleBase -= moraleBase * moraleBonus >> 16; if ((uint16)g_vm->_rnd->getRandomNumber(0xffff) <= moraleBase) follower->_flags |= kAFAfraid; } } } } //----------------------------------------------------------------------- // 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 = nullptr; if ((ts = newTaskStack(bandMember)) != nullptr) { Task *task = new BandTask(ts); if (task != nullptr) ts->setTask(task); else { delete ts; ts = nullptr; } } 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 == kDispositionEnemy && follower->canSenseProtaganist(info, kMaxSenseRange)) || (_disposition >= kDispositionPlayer && follower->canSenseActorProperty( info, kMaxSenseRange, kActorPropIDEnemy))) return kActorGoalAttackEnemy; return kActorGoalFollowLeader; } // Returns 0 if not moving, 1 if path being calculated, // 2 if path being followed. int Actor::pathFindState() { if (_moveTask == nullptr) 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() { 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], kBuiltinAbstract, 0, Method_KnowledgePackage_evalResponse, scf); // If script ran OK, then look at result if (res == kScriptResultFinished) { // 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 += g_vm->_rnd->getRandomNumber(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, kBuiltinAbstract, 0, Method_KnowledgePackage_executeResponse, scf); } else { scf.returnVal = kActionResultNotDone; } } //----------------------------------------------------------------------- // Polling function to determine if any of this actor's followers can // sense a protagonist within a specified range bool Actor::canSenseProtaganistIndirectly(SenseInfo &info, int16 range) { if (_followers != nullptr) { 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 != nullptr) { 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 != nullptr) { 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 != nullptr) { 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 != nullptr) { 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 >= kManaIDRed && i <= kManaIDViolet); 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 >= kManaIDRed && i <= kManaIDViolet); if ((&_effectiveStats.redMana)[i] < dMana) return false; return true; } //----------------------------------------------------------------------- // Saving throw funcion bool Actor::makeSavingThrow() { return false; } //------------------------------------------------------------------- // Determine if the actors are currently initialized bool areActorsInitialized() { return g_vm->_act->_actorList.size() > 0; } int16 GetRandomBetween(int start, int end) { return g_vm->_rnd->getRandomNumberRng(start, end - 1); } void updateActorStates() { // TODO: updateActorStates() for Dino if (g_vm->getGameId() == GID_DINO) return; if (g_vm->_act->_actorStatesPaused) return; int32 actorIndex = g_vm->_act->_baseActorIndex = (g_vm->_act->_baseActorIndex + 1) & ActorManager::kEvalRateMask; while (actorIndex < kActorCount) { Actor *a = g_vm->_act->_actorList[actorIndex]; if (isWorld(a->IDParent())) a->evaluateNeeds(); actorIndex += ActorManager::kEvalRate; } g_vm->_act->_updatesViaScript = 0; for (actorIndex = 0; actorIndex < kActorCount; actorIndex++) { Actor *a = g_vm->_act->_actorList[actorIndex]; if (isWorld(a->IDParent()) && a->isActivated()) a->updateState(); } } //------------------------------------------------------------------- void pauseActorStates() { g_vm->_act->_actorStatesPaused = true; } //------------------------------------------------------------------- void resumeActorStates() { g_vm->_act->_actorStatesPaused = false; } //------------------------------------------------------------------- void setCombatBehavior(bool enabled) { PlayerActor *player = nullptr; LivingPlayerActorIterator iter; g_vm->_act->_combatBehaviorEnabled = enabled; for (player = iter.first(); player != nullptr; 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 necessary? reserved[i] = stream->readByte(); } } void initActors() { // Load actors int i, resourceActorCount; Common::Array resourceActorList; Common::SeekableReadStream *stream; const int resourceActorSize = 91; // size of the packed struct resourceActorCount = listRes->size(kActorListID) / resourceActorSize; if (resourceActorCount < 1) error("Unable to load Actors"); if ((stream = loadResourceToStream(listRes, kActorListID, "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; if (g_vm->getGameId() == GID_DINO) { warning("TODO: initActors() for Dino"); return; } for (i = 0; i < resourceActorCount; i++) { // Initialize the actors with the resource data Actor *a = new Actor(resourceActorList[i]); a->_index = i + ActorBaseID; g_vm->_act->_actorList.push_back(a); } // Place all of the extra actors in actor limbo for (; i < kActorCount; i++) { Actor *a = new Actor; a->_index = i + ActorBaseID; g_vm->_act->_actorList.push_back(a); } g_vm->_act->_actorList[0]->_disposition = kDispositionPlayer + 0; g_vm->_act->_actorList[1]->_disposition = kDispositionPlayer + 1; g_vm->_act->_actorList[2]->_disposition = kDispositionPlayer + 2; } void saveActors(Common::OutSaveFile *outS) { debugC(2, kDebugSaveload, "Saving actors"); outS->write("ACTR", 4); CHUNK_BEGIN; out->writeSint16LE(kActorCount); debugC(3, kDebugSaveload, "... kActorCount = %d", kActorCount); for (int i = 0; i < kActorCount; ++i) g_vm->_act->_actorList[i]->write(out); CHUNK_END; } void loadActors(Common::InSaveFile *in) { debugC(2, kDebugSaveload, "Loading actors"); // Read in the actor count in->readSint16LE(); debugC(3, kDebugSaveload, "... kActorCount = %d", kActorCount); for (int i = 0; i < kActorCount; i++) { debugC(3, kDebugSaveload, "Loading actor %d", i + ActorBaseID); // Initilize actors with archive data Actor *a = new Actor(in); a->_index = i + ActorBaseID; g_vm->_act->_actorList.push_back(a); } for (int i = 0; i < kActorCount; ++i) { Actor *a = g_vm->_act->_actorList[i]; a->_leader = a->_leaderID != Nothing ? (Actor *)GameObject::objectAddress(a->_leaderID) : nullptr; a->_followers = a->_followersID != NoBand ? getBandAddress(a->_followersID) : nullptr; a->_currentTarget = a->_currentTargetID != Nothing ? GameObject::objectAddress(a->_currentTargetID) : nullptr; } } //------------------------------------------------------------------- // Cleanup the actor list void cleanupActors() { if (g_vm->_act->_actorList.size() > 0) { for (int i = 0; i < kActorCount; i++) delete g_vm->_act->_actorList[i]; g_vm->_act->_actorList.clear(); } } /* ============================================================================ * Actor faction tallies * ============================================================================ */ int16 AddFactionTally(int faction, enum factionTallyTypes act, int amt) { #if DEBUG if (faction >= kMaxFactions) error("Scripter: Tell Talin to increase kMaxFactions!\n"); assert(faction >= 0); assert(act >= 0); assert(act < kFactionNumColumns); #endif /* // If faction attitude counts get to big then down-scale all of them // in proportion. if ( g_vm->_act->_factionTable[faction][act] + amt > maxint16 ) { for (int i = 0; i < kFactionNumColumns; i++) g_vm->_act->_factionTable[faction][i] >>= 1; } // Otherwise, if it doesn;t underflow, then add it in. if ( g_vm->_act->_factionTable[faction][act] + amt > minint16 ) { g_vm->_act->_factionTable[faction][act] += amt; } */ g_vm->_act->_factionTable[faction][act] = clamp(minint16, g_vm->_act->_factionTable[faction][act] + amt, maxint16); return g_vm->_act->_factionTable[faction][act]; } // Get the attitude a particular faction has for a char. int16 GetFactionTally(int faction, enum factionTallyTypes act) { #if DEBUG if (faction >= kMaxFactions) error("Scripter: Tell Talin to increase kMaxFactions!\n"); assert(faction >= 0); assert(act >= 0); assert(act < kFactionNumColumns); #endif return g_vm->_act->_factionTable[faction][act]; } //------------------------------------------------------------------- // Initialize the faction tally table void initFactionTallies() { memset(&g_vm->_act->_factionTable, 0, sizeof(g_vm->_act->_factionTable)); } void saveFactionTallies(Common::OutSaveFile *outS) { debugC(2, kDebugSaveload, "Saving Faction Tallies"); outS->write("FACT", 4); CHUNK_BEGIN; for (int i = 0; i < kMaxFactions; ++i) { for (int j = 0; j < kFactionNumColumns; ++j) out->writeSint16LE(g_vm->_act->_factionTable[i][j]); } CHUNK_END; } void loadFactionTallies(Common::InSaveFile *in) { debugC(2, kDebugSaveload, "Loading Faction Tallies"); for (int i = 0; i < kMaxFactions; ++i) { for (int j = 0; j < kFactionNumColumns; ++j) g_vm->_act->_factionTable[i][j] = in->readSint16LE(); } } }