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

3637 lines
103 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* 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() | isWeapon;
}
//-----------------------------------------------------------------------
// Determine if the specified object can be contained by this object
bool ActorProto::canContain(ObjectID dObj, ObjectID item) {
assert(isActor(dObj));
assert(isObject(item) || isActor(item));
GameObject *itemPtr = GameObject::objectAddress(item);
// Actors can contain any object, except worlds and other actors
return isObject(item)
&& ((itemPtr->containmentSet() & ProtoObj::isIntangible) == 0
|| itemPtr->possessor() == dObj);
}
//-----------------------------------------------------------------------
// Determine if the specified object can be contained by this object at
// the specified slot
bool ActorProto::canContainAt(
ObjectID dObj,
ObjectID item,
const TilePoint &) {
assert(isActor(dObj));
assert(isObject(item) || isActor(item));
GameObject *itemPtr = GameObject::objectAddress(item);
// Actors can contain any object, except worlds and other actors
// REM: must add test to determine if specified slot is valid.
return isObject(item)
&& ((itemPtr->containmentSet() & ProtoObj::isIntangible) == 0
|| itemPtr->possessor() == dObj);
}
weaponID ActorProto::getWeaponID() {
return weaponDamage;
}
//-----------------------------------------------------------------------
// use this actor
bool ActorProto::useAction(ObjectID dObj, ObjectID enactor) {
assert(isActor(dObj));
Actor *a = (Actor *)GameObject::objectAddress(dObj);
if (a->isDead())
return ((PhysicalContainerProto *)this)->PhysicalContainerProto::useAction(dObj, enactor);
return false;
}
//-----------------------------------------------------------------------
// Determine if this actor can be opened
bool ActorProto::canOpen(ObjectID dObj, ObjectID) {
assert(isActor(dObj));
return ((Actor *)GameObject::objectAddress(dObj))->isDead();
}
//-----------------------------------------------------------------------
// open this actor
// Kludge!
extern int16 openMindType;
bool ActorProto::openAction(ObjectID dObj, ObjectID) {
assert(isActor(dObj));
ContainerNode *cn;
GameObject *dObjPtr = GameObject::objectAddress(dObj);
assert(!dObjPtr->isOpen() && !dObjPtr->isLocked());
cn = CreateContainerNode(dObj, false, openMindType);
cn->markForShow(); // Deferred open
dObjPtr->_data.objectFlags |= objectOpen; // Set open bit;
return true;
}
//-----------------------------------------------------------------------
// close this actor
bool ActorProto::closeAction(ObjectID dObj, ObjectID) {
assert(isActor(dObj));
GameObject *dObjPtr = GameObject::objectAddress(dObj);
ContainerNode *cn = g_vm->_cnm->find(dObj, ContainerNode::deadType);
assert(dObjPtr->isOpen());
assert(cn);
// Delete the container (lazy delete)
cn->markForDelete();
// Clear open bit
dObjPtr->_data.objectFlags &= ~objectOpen;
return true;
}
//-----------------------------------------------------------------------
bool ActorProto::strikeAction(
ObjectID dObj,
ObjectID enactor,
ObjectID item) {
assert(isActor(dObj));
assert(isActor(enactor));
assert(isObject(item) || isActor(item));
Actor *a = (Actor *)GameObject::objectAddress(enactor);
ActorAttributes *effStats = a->getStats();
GameObject *itemPtr = GameObject::objectAddress(item);
ObjectSoundFXs *soundFXs;
Location al = Location(a->getLocation(), a->IDParent());
if (itemPtr->acceptStrike(enactor, dObj, effStats->getSkillLevel(skillIDBludgeon)))
return true;
soundFXs = &objectSoundFXTable[soundFXClass];
makeCombatSound(soundFXs->soundFXMissed, al);
return false;
}
bool ActorProto::damageAction(
ObjectID dObj,
ObjectID enactor,
ObjectID target) {
assert(isActor(dObj));
assert(isActor(enactor));
assert(isObject(target) || isActor(target));
Actor *a = (Actor *)GameObject::objectAddress(enactor);
ActorAttributes *effStats = a->getStats();
WeaponStuff *ws = &getWeapon(getWeaponID());
GameObject *targetPtr = GameObject::objectAddress(target);
uint8 damageSoundID;
Location al = Location(a->getLocation(), a->IDParent());
damageSoundID = targetPtr->proto()->getDamageSound(
objectSoundFXTable[soundFXClass]);
if (damageSoundID != 0)
makeCombatSound(damageSoundID, al);
ws->implement(
a,
GameObject::objectAddress(target),
GameObject::objectAddress(dObj),
effStats->getSkillLevel(skillIDBrawn));
return true;
}
//-----------------------------------------------------------------------
// Routine that is called when an object is dragged & dropped
// onto an actor.
bool ActorProto::acceptDropAction(
ObjectID dObj, // object dropped on
ObjectID enactor, // person doing dropping
ObjectID droppedID, // ID of dropped object
int count) {
assert(isActor(dObj));
Actor *a = (Actor *)GameObject::objectAddress(dObj);
GameObject *droppedObj = GameObject::objectAddress(droppedID);
if (a->isDead()) {
a->dropInventoryObject(droppedObj, count);
return true;
}
Location newLoc;
uint16 dropType;
// For now, we'll just drop the object into the actor's
// inventory.
//
// REM: We might want to arrange the inventory item in a
// semi-sensible way, like putting the new item at the
// head of the list. (Do this by changing the newLoc coord
// fields...)
//
// REM: We might want to ask the object how it feels about
// being given to an actor...
// NOTE: Added check so that dropping an object on an actor who
// already has the object will do nothing.
if (droppedObj->IDParent() == dObj) return true;
dropType = droppedObj->containmentSet();
scriptResult result;
scriptCallFrame scf;
scf.invokedObject = dObj;
scf.enactor = enactor;
scf.directObject = droppedID;
scf.indirectObject = dObj;
if (dropType & isIntangible) {
// Set up the arguments we want to pass to the script
scf.value = droppedObj->proto()->lockType + senseIdeaGreeting;
// Invoke the script...
if (dropType & isConcept) {
runObjectMethod(dObj, Method_Actor_onTalkTo, scf);
} else if (dropType & isPsych) {
// What to do???
} else if (dropType & (isSpell | isSkill)) {
// What to do???
// Cast the spell on the actor?
}
} else {
scf.value = count;
result = runObjectMethod(dObj, Method_Actor_onReceive, scf);
if (result == scriptResultFinished
&& scf.returnVal != actionResultNotDone)
return scf.returnVal == actionResultSuccess;
// Place the object in the actor's inventory (if possible)
if (!a->placeObject(enactor, droppedID, true, count))
a->dropInventoryObject(droppedObj, count);
}
return true;
}
//-----------------------------------------------------------------------
// Call the actor's "greet" script.
bool ActorProto::greetActor(
ObjectID dObj, // object dropped on
ObjectID enactor) { // person doing dropping
assert(isActor(dObj));
scriptCallFrame scf;
scf.invokedObject = dObj;
scf.enactor = enactor;
scf.directObject = Nothing;
scf.indirectObject = Nothing;
scf.value = senseIdeaGreeting;
return runObjectMethod(dObj, Method_Actor_onTalkTo, scf);
}
//-----------------------------------------------------------------------
// cause damage directly
bool ActorProto::acceptDamageAction(
ObjectID dObj,
ObjectID enactor,
int8 absDamage,
effectDamageTypes dType,
int8 dice,
uint8 sides,
int8) {
assert(isActor(dObj));
assert(isObject(enactor) || isActor(enactor));
int8 pdm = 0; //=perDieMod+(resistant ? -2 : 0);
int16 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::objPropNoSurface)
|| (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, factionNumKills, 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(actorDiseased)) {
// If we've just lost all vitality, we're dead, else make a
// morale check
vitality += healing;
vitality = clamp(0, vitality, maxVitality);
if (actorToPlayerID(a, pID))
updateBrotherControls(pID);
WriteStatusF(5, "Healing: %d", healing);
} else
return false;
return true;
}
//-----------------------------------------------------------------------
// Accept strike from an object (allows this actor to cause damage to
// the striking object).
bool ActorProto::acceptStrikeAction(
ObjectID dObj,
ObjectID enactor,
ObjectID strikingObj,
uint8 skillIndex) {
assert(isActor(dObj));
assert(isActor(enactor));
const int toHitBase = 100;
const int avgHitChance = toHitBase / 2;
const int skillScalingFactor =
(avgHitChance
+ ActorAttributes::skillLevels - 1)
/ ActorAttributes::skillLevels;
const int dodgingBonus = 10;
Actor *a = (Actor *)GameObject::objectAddress(dObj);
ActorAttributes *effStats = a->getStats();
GameObject *weapon = GameObject::objectAddress(strikingObj);
assert(weapon->proto()->containmentSet() & ProtoObj::isWeapon);
Actor *enactorPtr = (Actor *)GameObject::objectAddress(enactor);
ArmorAttributes armorAttribs;
uint8 hitChance;
if (a->isDead())
return weapon->damage(enactor, dObj);
a->handleOffensiveAct((Actor *)GameObject::objectAddress(enactor));
// Sum up the armor attributes
a->totalArmorAttributes(armorAttribs);
// Determine "to hit" percentage
hitChance = avgHitChance
+ ((int)skillIndex
- (int)effStats->getSkillLevel(skillIDAgility))
* skillScalingFactor;
// Factor in armor bonus
hitChance -= armorAttribs.defenseBonus;
// Factor in dodging bonus if any
if (a->_moveTask != nullptr && a->_moveTask->isDodging(enactorPtr))
hitChance -= dodgingBonus;
hitChance = MAX<uint8>(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(skillIDAgility, 1);
}
}
return false;
}
//-----------------------------------------------------------------------
// Insert another object into this object at the specified slot
bool ActorProto::acceptInsertionAtAction(
ObjectID dObj,
ObjectID,
ObjectID item,
const TilePoint &where,
int16 num) {
enum {
notInUse,
heldInLeftHand,
heldInRightHand,
worn
} inUseType;
int wornWhere = 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(actionSwingHigh))
MotionTask::oneHandedSwing(*attackerPtr, *targetPtr);
else if (attackerPtr->isActionAvailable(actionTwoHandSwingHigh))
MotionTask::twoHandedSwing(*attackerPtr, *targetPtr);
} else
MotionTask::oneHandedSwing(*attackerPtr, *targetPtr);
}
//-----------------------------------------------------------------------
// Given an object sound effect record, which sound should be made
// when this object is damaged
uint8 ActorProto::getDamageSound(const ObjectSoundFXs &soundFXs) {
return !(flags & ResourceObjectPrototype::objPropNoSurface)
? !(flags & ResourceObjectPrototype::objPropHardSurface)
? soundFXs.soundFXHitFlesh
: soundFXs.soundFXHitHard
: 0;
}
//-----------------------------------------------------------------------
// Do the background processing, if needed, for this object.
void ActorProto::doBackgroundUpdate(GameObject *obj) {
// get the ID for this object
ObjectID actorID = obj->thisID();
// find out if this object is an actor
if (isActor(actorID)) {
// get a pointer to that actor
GameObject *actorObj = GameObject::objectAddress(actorID);
Actor *a = (Actor *)actorObj;
if (!a->isActivated()) {
// If this is a temporary actor waiting for expiration,
// then decrement the expiration counter and possibly
// delete the actor
if ((a->_flags & Actor::temporary) || a->isDead()) {
if (a->_deactivationCounter <= 0) {
a->deleteObjectRecursive();
return;
} else a->_deactivationCounter--;
} else {
// If the actor has failed morale there is a random
// chance of him regaining his courage
if ((a->_flags & Actor::afraid) && g_vm->_rnd->getRandomNumber(127) == 0)
a->_flags &= ~Actor::afraid;
}
}
// execute that actor's vitality update function
((Actor *)actorObj)->vitalityUpdate();
// do any updates directly related only to the brothers
if (isPlayerActor(actorID)) {
switch (actorID) {
case ActorBaseID + FTA_JULIAN:
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(skillIDBludgeon, points);
if (g_vm->_rnd->getRandomNumber(1))
player->skillAdvance(skillIDBrawn, points);
}
}
// ------------------------------------------------------------------------
bool ActorProto::canFitBulkwise(GameObject *container, GameObject *obj) {
#if DEBUG
if (massAndBulkCount)
#endif
{
uint16 maxBulk = container->bulkCapacity();
uint16 totalBulk = container->totalContainedBulk();
return totalBulk + obj->totalBulk() <= maxBulk;
}
#if DEBUG
return true;
#endif
}
// ------------------------------------------------------------------------
bool ActorProto::canFitMasswise(GameObject *container, GameObject *obj) {
assert(isActor(container));
#if DEBUG
if (massAndBulkCount)
#endif
{
Actor *a = (Actor *)container;
// get the maxium amount of weight this character should be able to carry
uint16 cmaxCapacity = container->massCapacity();
uint16 totalMass = a->totalContainedMass();
return totalMass + obj->totalMass() <= cmaxCapacity;
}
#if DEBUG
return true;
#endif
}
// ------------------------------------------------------------------------
// Return the maximum mass capacity for the specified container
uint16 ActorProto::massCapacity(GameObject *container) {
assert(isActor(container));
Actor *a = (Actor *)container;
ActorAttributes *effStats = a->getStats();
return baseCarryingCapacity
+ effStats->getSkillLevel(skillIDBrawn)
* carryingCapacityBonusPerBrawn;
}
// ------------------------------------------------------------------------
// Return the maximum bulk capacity for the specified container
uint16 ActorProto::bulkCapacity(GameObject *) {
return bulk * 4;
}
/* ===================================================================== *
ActorArchive struct
* ===================================================================== */
// This data structure is used in the creation of an actor archive. It
// includes all of the fixed size data fields which must be preserved in
// a save file without any of the overhead such as a base class or virtual
// member functions. Some of the Actor data members, such as moveTask
// currentTask and currentTransaction, are omitted because the links
// to these other objects will archived with their respective 'Task' object.
// Also, the assignment member was not included because it is a complex
// variable sized data structure which will be asked to archive itself.
struct ActorArchive {
uint8 faction;
uint8 colorScheme;
int32 appearanceID;
int8 attitude,
mood;
uint8 disposition;
Direction currentFacing;
int16 tetherLocU;
int16 tetherLocV;
int16 tetherDist;
ObjectID leftHandObject,
rightHandObject;
uint16 knowledge[16];
uint16 schedule;
uint8 conversationMemory[4];
uint8 currentAnimation,
currentPose,
animationFlags;
uint8 flags;
ActorPose poseInfo;
int16 cycleCount;
int16 kludgeCount;
uint32 enchantmentFlags;
uint8 currentGoal,
deactivationCounter;
ActorAttributes effectiveStats;
uint8 actionCounter;
uint16 effectiveResistance;
uint16 effectiveImmunity;
int16 recPointsPerUpdate; // fractional vitality recovery
int16 currentRecoveryPoints;
ObjectID leaderID;
BandID followersID;
ObjectID _armorObjects[ARMOR_COUNT];
ObjectID currentTargetID;
int16 scriptVar[actorScriptVars];
};
/* ===================================================================== *
Actor member functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize all fields in the actor structure to neutral values.
void Actor::init(
int16 protoIndex,
uint16 nameIndex,
uint16 scriptIndex,
int32 appearanceNum,
uint8 colorSchemeIndex,
uint8 factionNum,
uint8 initFlags) {
debugC(1, kDebugActors, "Actor init flags: %d, permanent: %d", initFlags, initFlags & actorPermanent);
// 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 = dirDown;
_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 = actionStand;
_currentPose = 0;
_animationFlags = 0;
_flags = 0;
if (!(initFlags & actorPermanent))
_flags |= temporary;
_poseInfo.flags = 0;
_poseInfo.actorFrameIndex = 0;
_poseInfo.actorFrameBank = 0;
_poseInfo.leftObjectIndex = 0;
_poseInfo.rightObjectIndex = 0;
_poseInfo.leftObjectOffset.x = _poseInfo.leftObjectOffset.y = 0;
_poseInfo.rightObjectOffset.x = _poseInfo.rightObjectOffset.y = 0;
_appearance = nullptr;
_cycleCount = 0;
_kludgeCount = 0;
_moveTask = nullptr;
_enchantmentFlags = 0L;
_curTask = nullptr;
_currentGoal = actorGoalFollowAssignment;
_deactivationCounter = 0;
_assignment = nullptr;
memcpy(
&_effectiveStats,
&((ActorProto *)prototype)->baseStats,
sizeof(_effectiveStats));
_effectiveStats.vitality = MAX<int16>(_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 < actorScriptVars; i++)
_scriptVar[i] = 0;
evalActorEnchantments(this);
}
//-----------------------------------------------------------------------
// Actor constructor -- copies the resource fields and simply NULL's most
// of the rest of the data members
Actor::Actor() {
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 = actionStand;
_currentPose = 0;
_animationFlags = 0;
_flags = 0;
_poseInfo.flags = 0;
_poseInfo.actorFrameIndex = 0;
_poseInfo.actorFrameBank = 0;
_poseInfo.leftObjectIndex = 0;
_poseInfo.rightObjectIndex = 0;
_poseInfo.leftObjectOffset.x = _poseInfo.leftObjectOffset.y = 0;
_poseInfo.rightObjectOffset.x = _poseInfo.rightObjectOffset.y = 0;
_appearance = nullptr;
_cycleCount = 0;
_kludgeCount = 0;
_moveTask = nullptr;
_enchantmentFlags = 0L;
_curTask = nullptr;
_currentGoal = actorGoalFollowAssignment;
_deactivationCounter = 0;
_assignment = nullptr;
memset(&_effectiveStats, 0, sizeof(_effectiveStats));
_effectiveStats.vitality = MAX<uint16>(_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 < actorScriptVars; 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 = actionStand;
_currentPose = 0;
_animationFlags = 0;
_flags = 0;
_poseInfo.flags = 0;
_poseInfo.actorFrameIndex = 0;
_poseInfo.actorFrameBank = 0;
_poseInfo.leftObjectIndex = 0;
_poseInfo.rightObjectIndex = 0;
_poseInfo.leftObjectOffset.x = _poseInfo.leftObjectOffset.y = 0;
_poseInfo.rightObjectOffset.x = _poseInfo.rightObjectOffset.y = 0;
_appearance = nullptr;
_cycleCount = 0;
_kludgeCount = 0;
_moveTask = nullptr;
_enchantmentFlags = 0L;
_curTask = nullptr;
_currentGoal = actorGoalFollowAssignment;
_deactivationCounter = 0;
_assignment = nullptr;
if (prototype)
memcpy(&_effectiveStats, &((ActorProto *)prototype)->baseStats, sizeof(_effectiveStats));
_effectiveStats.vitality = MAX<uint16>(_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 < actorScriptVars; 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 & hasAssignment) {
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 & hasAssignment)
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 & hasAssignment)
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 & temporary)
&& !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 & temporary) {
incTempActorCount(protoNum);
debugC(1, kDebugActors, "Actors: Created temp actor %d (%s) new count:%d", a->thisID() - 32768, a->objName(), getTempActorCount(protoNum));
}
return a;
}
//-----------------------------------------------------------------------
// Delete this actor
void Actor::deleteActor() {
if (_flags & temporary) {
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 & objectNoRecycle)) {
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 / recPointsPerVitality;
// get the fraction
fractionRecover = _recPointsPerUpdate % recPointsPerVitality;
// if there is an overrun
if (_currentRecoveryPoints + fractionRecover > recPointsPerVitality) {
// add the overrun to the whole number
recover++;
_currentRecoveryPoints = (_currentRecoveryPoints + fractionRecover) - recPointsPerVitality;
} else {
_currentRecoveryPoints += fractionRecover;
}
if (_effectiveStats.vitality + recover >=
baseStats->vitality) {
_effectiveStats.vitality = baseStats->vitality;
} else {
_effectiveStats.vitality += recover;
//WriteStatusF( 5, " Healed: %d, rec: %d, part: %d ", effectiveStats.vitality,
// recover, currentRecoveryPoints );
}
}
}
//-----------------------------------------------------------------------
// Perform actor specific activation tasks
void Actor::activateActor() {
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 & temporary) || isDead()) {
_deactivationCounter = 10; // actor lasts for 50 seconds
}
}
//-----------------------------------------------------------------------
// Delobotomize this actor
void Actor::delobotomize() {
if (!(_flags & lobotomized)) return;
ObjectID dObj = thisID();
scriptCallFrame scf;
_flags &= ~lobotomized;
scf.invokedObject = dObj;
scf.enactor = dObj;
scf.directObject = dObj;
scf.indirectObject = Nothing;
scf.value = 0;
runObjectMethod(dObj, Method_Actor_onDelobotomize, scf);
evaluateNeeds();
}
//-----------------------------------------------------------------------
// Lobotomize this actor
void Actor::lobotomize() {
if (_flags & lobotomized) 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 |= lobotomized;
scf.invokedObject = dObj;
scf.enactor = dObj;
scf.directObject = dObj;
scf.indirectObject = Nothing;
scf.value = 0;
runObjectMethod(dObj, Method_Actor_onLobotomize, scf);
}
//-----------------------------------------------------------------------
// Return a pointer to the base stats for this actor. If this actor
// is a non-player actor, the base stats are in the prototype. If this
// actor is a player actor, the base stats are in the PlayerActor
// structure.
ActorAttributes *Actor::getBaseStats() {
if (_disposition < dispositionPlayer)
return &((ActorProto *)prototype)->baseStats;
else
return &g_vm->_playerList[_disposition - dispositionPlayer]->baseStats;
}
//-----------------------------------------------------------------------
// Return the racial base enchantment flags. If this actor
// is a non-player actor, the base stats are in the prototype.
uint32 Actor::getBaseEnchantmentEffects() {
//if ( disposition < dispositionPlayer )
return ((ActorProto *)prototype)->baseEffectFlags;
}
//-----------------------------------------------------------------------
// Return the object base resistance flags. If this actor
// is a non-player actor, the base stats are in the prototype.
uint16 Actor::getBaseResistance() {
//if ( disposition < dispositionPlayer )
return ((ActorProto *)prototype)->resistance;
}
//-----------------------------------------------------------------------
// Return the object base immunity flags. If this actor
// is a non-player actor, the base stats are in the prototype.
uint16 Actor::getBaseImmunity() {
//if ( disposition < dispositionPlayer )
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(actorImmobile)
|| hasEffect(actorAsleep)
|| hasEffect(actorParalyzed);
}
//-----------------------------------------------------------------------
// 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::isWeapon);
return obj;
}
if (_leftHandObject != Nothing) {
assert(isObject(_leftHandObject));
GameObject *obj = GameObject::objectAddress(_leftHandObject);
if (obj->containmentSet() & ProtoObj::isWeapon)
return obj;
}
// If not carrying a weapon attack with self
return this;
}
//-----------------------------------------------------------------------
// Returns pointers to this actor's readied primary defensive object
// and optionally their scondary defensive object
void Actor::defensiveObject(GameObject **priPtr, GameObject **secPtr) {
assert(priPtr != 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<uint8>(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 & animateNoRestart)) {
if (flags & animateReverse) _currentPose = numPoses - 1;
else _currentPose = 0;
} else {
_currentPose = clamp(0, _currentPose, numPoses - 1);
}
return numPoses;
}
//-----------------------------------------------------------------------
// returns true if the action is available in the current direction.
//
bool Actor::isActionAvailable(int16 newState, bool anyDir) {
ActorAnimation *anim;
// Refresh the handles
// RLockHandle( appearance->animations );
// RUnlockHandle( appearance->animations );
if (_appearance == nullptr)
return false;
// If this animation has no frames, then return false
anim = _appearance->animation(newState);
if (anim == nullptr)
return false;
if (anyDir) {
for (int i = 0; i < numPoseFacings; i++) {
if (anim->count[i] > 0) return true;
}
} else {
if (anim->count[_currentFacing] > 0) return true;
}
return false;
}
//-----------------------------------------------------------------------
// Return the number of animation frames in the specified action for the
// specified direction
int16 Actor::animationFrames(int16 actionType, Direction dir) {
if (_appearance == nullptr)
return 0;
ActorAnimation *anim;
anim = _appearance->animation(actionType);
if (!anim)
return 0;
return anim->count[dir];
}
//-----------------------------------------------------------------------
// Update the current animation sequence to the next frame.
// Returns true if the animation sequence has finished.
bool Actor::nextAnimationFrame() {
ActorAnimation *anim;
int16 numPoses;
// Refresh the handles
// RLockHandle( appearance->animations );
// RUnlockHandle( appearance->animations );
if (_appearance == nullptr) {
if (_animationFlags & animateOnHold) {
return false;
} else if (_animationFlags & animateRepeat) {
_animationFlags |= animateOnHold;
return false;
} else {
_animationFlags |= animateFinished;
return true;
}
} else _animationFlags &= ~animateOnHold;
// Get the number of frames in the animation
anim = _appearance->animation(_currentAnimation);
numPoses = anim->count[_currentFacing];
if (numPoses <= 0) {
_animationFlags |= animateFinished;
return true; // no poses, return DONE
}
// If the sprite could not be displayed because it has not
// been loaded, then don't update the animation state --
// wait until the sprite gets loaded, and then continue
// with the action.
if (_animationFlags & animateNotLoaded) return false;
// If the animation has reached the last frame, then exit.
if (_animationFlags & animateFinished) return true;
if (_animationFlags & animateRandom) {
// Select a random frame from the series.
_currentPose = g_vm->_rnd->getRandomNumber(numPoses - 1);
} else if (_animationFlags & animateReverse) {
// Note that the logic for forward repeats is slightly
// different for reverse repeats. Specifically, the
// "alternate" flag is always checked when going forward,
// but it's only checked when going backwards if the repeat
// flag is also set. This means that an "alternate" with
// no "repeat" will ping-pong exactly once.
if (_currentPose > 0) {
_currentPose--;
// Check if this is the last frame
if (_currentPose <= 0 && !(_animationFlags & animateRepeat)) {
_animationFlags |= animateFinished;
}
} else if (_animationFlags & animateRepeat) {
// If we're repeating, check for a back & forth,
// or for a wraparound. Also checks for case of
// a degenerate series (1 frame only)
if (_animationFlags & animateAlternate) {
_animationFlags &= ~animateReverse;
_currentPose = MIN(1, numPoses - 1);
} else {
_currentPose = numPoses - 1;
}
}
} else {
if (_currentPose < numPoses - 1) {
// Increment the pose number
_currentPose++;
// Check if this is the last frame
if (_currentPose >= numPoses - 1 &&
!(_animationFlags & (animateAlternate | animateRepeat)))
_animationFlags |= animateFinished;
} else if (_animationFlags & animateAlternate) {
// At the end of the sequence, reverse direction
_animationFlags |= animateReverse;
_currentPose = MAX(_currentPose - 1, 0);
} else if (_animationFlags & animateRepeat) {
// Wrap back to beginning
_currentPose = 0;
} else //If Last Frame And Not Animate Repeat or Alternate
_animationFlags |= animateFinished;
}
return false;
}
//-----------------------------------------------------------------------
// Drop the all of the actor's inventory
void Actor::dropInventory() {
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::isIntangible)
obj->deleteObjectRecursive();
else
dropInventoryObject(obj, obj->isMergeable() ? obj->getExtra() : 1);
}
}
//-----------------------------------------------------------------------
// Place an object into this actor's right or left hand
void Actor::holdInRightHand(ObjectID objID) {
assert(isObject(objID));
_rightHandObject = objID;
if (isPlayerActor(this))
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::isArmor);
}
#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 & lobotomized)) return;
#if DEBUG*0
WriteStatusF(4, "Wait Count %d Attitude %d", cycleCount, attitude);
#endif
#if DEBUG*0
extern void ShowObjectSection(GameObject * obj);
if (this != getCenterActor())
if (lineOfSight(getCenterActor(), this, terrainSurface))
ShowObjectSection(this);
#endif
if (_appearance) {
if (animationFrames(actionStand, _currentFacing) == 1) {
if (_flags & fightStance) {
GameObject *weapon = offensiveObject();
if (weapon == this) weapon = nullptr;
if (weapon != nullptr) {
ProtoObj *weaponProto = weapon->proto();
setAction(weaponProto->fightStanceAction(thisID()), 0);
} else {
if (isActionAvailable(actionSwingHigh))
setAction(actionSwingHigh, 0);
else
setAction(actionTwoHandSwingHigh, 0);
}
_cycleCount = 0;
} else {
if (_cycleCount > 0) { //If In Wait State Between Wait Animation
_cycleCount--;
setAction(actionStand, 0); //Just stand still
} else { // Wait Animation
if (_cycleCount == 0) { //If Just Starting Wait Animation
_cycleCount--;
switch (_attitude) { //Emotion And Character Type
//Currently Attitude Not Set So Always Hits Zero
case 0:
//Returns True If Successful No Checking Yet
setAvailableAction(actionWaitAgressive,
actionWaitImpatient,
actionWaitFriendly,
actionStand); // This is default
break;
case 1:
setAvailableAction(actionWaitImpatient,
actionWaitFriendly,
actionWaitAgressive,
actionStand);
break;
case 2:
setAvailableAction(actionWaitFriendly,
actionWaitImpatient,
actionWaitAgressive,
actionStand);
}
} else //Assume -1
if (nextAnimationFrame())//If Last Frame In Wait Animation
_cycleCount = g_vm->_rnd->getRandomNumber(19);
}
}
} else {
if (_currentAnimation != actionStand
|| (_animationFlags & animateRepeat) == 0)
setAction(actionStand, animateRepeat);
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 & lobotomized)) {
if (_disposition >= dispositionPlayer) {
if (g_vm->_act->_combatBehaviorEnabled) {
SenseInfo info;
if (canSenseActorProperty(
info,
maxSenseRange,
actorPropIDEnemy)
|| canSenseActorPropertyIndirectly(
info,
maxSenseRange,
actorPropIDEnemy)) {
PlayerActorID playerID = _disposition - dispositionPlayer;
if (isAggressive(playerID))
setGoal(actorGoalAttackEnemy);
else {
if (_leader != nullptr && inBandingRange())
setGoal(actorGoalAvoidEnemies);
else
setGoal(actorGoalPreserveSelf);
}
} else if (_leader != nullptr && inBandingRange()) {
setGoal(actorGoalFollowLeader);
} else {
setGoal(actorGoalFollowAssignment);
}
} else if (_leader != nullptr && inBandingRange()) {
setGoal(actorGoalFollowLeader);
} else {
setGoal(actorGoalFollowAssignment);
}
} else {
if (_disposition == dispositionEnemy
&& _appearance != nullptr
&& !hasEffect(actorNotDefenseless)) {
GameObject *obj;
bool foundWeapon = false;
ContainerIterator iter(this);
while (iter.next(&obj) != Nothing) {
ProtoObj *proto = obj->proto();
if ((proto->containmentSet() & ProtoObj::isWeapon)
&& isActionAvailable(proto->fightStanceAction(thisID()))) {
foundWeapon = true;
break;
}
}
if (!foundWeapon
&& (isActionAvailable(actionSwingHigh)
|| isActionAvailable(actionTwoHandSwingHigh)))
foundWeapon = true;
if (!foundWeapon)
_flags |= afraid;
}
if (_flags & afraid || hasEffect(actorFear) || hasEffect(actorRepelUndead)) {
setGoal(actorGoalPreserveSelf);
} else if (_leader != nullptr && inBandingRange()) {
setGoal(_leader->evaluateFollowerNeeds(this));
} else {
SenseInfo info;
if (_disposition == dispositionEnemy
&& (getAssignment() == nullptr
|| canSenseProtaganist(
info,
maxSenseRange)
|| canSenseProtaganistIndirectly(
info,
maxSenseRange))) {
setGoal(actorGoalAttackEnemy);
} else {
setGoal(actorGoalFollowAssignment);
}
}
}
}
}
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 & lobotomized)
return;
// Update the action counter
if (_actionCounter != 0 && _actionCounter != maxuint8)
_actionCounter--;
if (_appearance != nullptr
&& isDead()
&& isInterruptable()
&& (_moveTask == nullptr
|| _moveTask->motionType != MotionTask::motionTypeDie)) {
int16 deadState = isActionAvailable(actionDead)
? actionDead
: isActionAvailable(actionDie)
? actionDie
: actionStand;
if (_currentAnimation != deadState)
MotionTask::die(*this);
return;
}
if (!isDead()) {
if (this == getCenterActor()) return;
if (_flags & specialAttack) {
_flags &= ~specialAttack;
if (_currentTarget != 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 & lobotomized))
return;
}
}
switch (_currentGoal) {
case actorGoalFollowAssignment: {
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 lobotomized
// return immediately
if (isDead() || !isActivated() || (_flags & lobotomized))
return;
// Re-get the assignment
assign = getAssignment();
}
// If there is no assignment at this point, call the
// schedule to setup a new assignment.
if (assign == 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 actorGoalPreserveSelf:
if (_leader != nullptr || _followers != nullptr)
disband();
if (_curTask == nullptr) {
if ((_curTask = newTaskStack(this)) != nullptr) {
Task *task = new GoAwayFromActorTask(
_curTask,
ActorPropertyTarget(
_disposition == dispositionEnemy
? actorPropIDPlayerActor
: actorPropIDEnemy),
true);
if (task != nullptr)
_curTask->setTask(task);
else {
delete _curTask;
_curTask = nullptr;
}
}
}
break;
case actorGoalAttackEnemy:
if (_curTask == nullptr) {
if ((_curTask = newTaskStack(this)) != nullptr) {
uint8 disp = _leader != nullptr
? _leader->_disposition
: _disposition;
Task *task = new HuntToKillTask(
_curTask,
ActorPropertyTarget(
disp == dispositionEnemy
? actorPropIDPlayerActor
: actorPropIDEnemy));
if (task != nullptr)
_curTask->setTask(task);
else {
delete _curTask;
_curTask = nullptr;
}
}
}
break;
case actorGoalFollowLeader:
assert(isActor(_leader));
assert(_followers == nullptr);
if (_curTask == nullptr)
_curTask = _leader->createFollowerTask(this);
break;
case actorGoalAvoidEnemies:
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 actorGoalFollowAssignment: {
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 == dispositionFriendly) {
if (attacker->_disposition >= dispositionPlayer) {
_disposition = dispositionEnemy;
evaluateNeeds();
}
}
}
//-----------------------------------------------------------------------
// This function will cause the actor to react appropriately to taking
// damage.
void Actor::handleDamageTaken(uint8 damage) {
uint8 combatBehavior = ((ActorProto *)prototype)->combatBehavior;
if (combatBehavior == behaviorHungry) return;
if (offensiveObject() == this
&& !isActionAvailable(actionSwingHigh)
&& !isActionAvailable(actionTwoHandSwingHigh)
&& !hasEffect(actorNotDefenseless)) {
_flags |= afraid;
return;
}
if (combatBehavior != behaviorHungry
&& (_flags & temporary)
&& !hasEffect(actorFear)
&& !hasEffect(actorRepelUndead)) {
if (_flags & afraid) {
// Let's give monsters a small chance of regaining their courage
if ((uint16)g_vm->_rnd->getRandomNumber(0xffff) <= 0x3fff)
_flags &= ~afraid;
} else {
int16 i,
fellowBandMembers,
vitality = _effectiveStats.vitality;
uint32 moraleBase = ((int32)damage << 16) / vitality,
bonus = 0;
// Adjustment added by Talin to globally reduce the amount of cowardice
// in the game. I may reduce it further depending on playtesting.
moraleBase /= 3;
// Adjust morale base according to the combat behavior
if (combatBehavior == behaviorCowardly)
moraleBase += moraleBase / 2;
else if (combatBehavior == behaviorBerserk)
moraleBase -= moraleBase / 2;
// Determine how many fellow band members this actor has.
if (_leader != 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 |= afraid;
}
}
}
//-----------------------------------------------------------------------
// This function is called when this actor successfully causes damage
// to another actor.
void Actor::handleSuccessfulStrike(Actor *target, int8 damage) {
PlayerActorID playerID;
if (actorToPlayerID(this, playerID)) {
PlayerActor *player = getPlayerActorAddress(playerID);
int16 ratio;
// If it's a weak monster, then reduce amount of vitality advanced.
// If we are twice as vital, then get half the exp's. If we are three times
// as vital, get 1/3 the exp. etc.
ratio = clamp(1, getBaseStats()->vitality / target->getBaseStats()->vitality, 4);
player->vitalityAdvance(damage / ratio);
}
}
//-----------------------------------------------------------------------
// This function is called when this actor successfully kills another
// actor.
void Actor::handleSuccessfulKill(Actor *target) {
PlayerActorID playerID;
if (this != target && actorToPlayerID(this, playerID)) {
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(actionJumpUp))
MotionTask::dodge(*this, *attacker);
}
}
}
//-----------------------------------------------------------------------
// Cause this actor to accept another actor as his leader. If the actor
// has followers, this will band those followers to the new leader as
// well.
void Actor::bandWith(Actor *newLeader) {
assert(_leader == 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 == actorGoalAttackEnemy
&& combatBehavior != behaviorHungry) {
uint32 moraleBase;
moraleBase = combatBehavior == behaviorCowardly
? (1 << 16) / 4
: combatBehavior == behaviorSmart
? (1 << 16) / 8
: (1 << 16) / 16;
moraleBase -= moraleBase * moraleBonus >> 16;
if ((uint16)g_vm->_rnd->getRandomNumber(0xffff) <= moraleBase)
follower->_flags |= afraid;
}
}
}
}
//-----------------------------------------------------------------------
// Create a task for a follower of this actor. This is called when a
// follower has no task.
TaskStack *Actor::createFollowerTask(Actor *bandMember) {
assert(bandMember->_leader == this);
TaskStack *ts = 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 == dispositionEnemy
&& follower->canSenseProtaganist(info, maxSenseRange))
|| (_disposition >= dispositionPlayer
&& follower->canSenseActorProperty(
info,
maxSenseRange,
actorPropIDEnemy)))
return actorGoalAttackEnemy;
return actorGoalFollowLeader;
}
// Returns 0 if not moving, 1 if path being calculated,
// 2 if path being followed.
bool 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],
builtinAbstract,
0,
Method_KnowledgePackage_evalResponse,
scf);
// If script ran OK, then look at result
if (res == scriptResultFinished) {
// break up return code into priority and
// response code
int16 pri = scf.returnVal >> 8,
response = scf.returnVal & 0xff;
if (pri > 0) {
// Add a bit of jitter to response
pri += 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,
builtinAbstract,
0,
Method_KnowledgePackage_executeResponse,
scf);
} else {
scf.returnVal = actionResultNotDone;
}
}
//-----------------------------------------------------------------------
// Polling function to determine if any of this actor's followers can
// sense a protaganist within a specified range
bool Actor::canSenseProtaganistIndirectly(SenseInfo &info, int16 range) {
if (_followers != 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 >= manaIDRed && i <= manaIDViolet);
if ((&_effectiveStats.redMana)[i] < dMana)
return false;
(&_effectiveStats.redMana)[i] -= dMana;
updateIndicators();
return true;
}
bool Actor::hasMana(ActorManaID i, int8 dMana) {
#if NO_MONSTER_MANA
if (!isPlayerActor(this))
return true;
#endif
assert(i >= manaIDRed && i <= manaIDViolet);
if ((&_effectiveStats.redMana)[i] < dMana)
return false;
return true;
}
//-----------------------------------------------------------------------
// Saving throw funcion
bool Actor::makeSavingThrow() {
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 neccessary?
reserved[i] = stream->readByte();
}
}
void initActors() {
// Load actors
int i, resourceActorCount;
Common::Array<ResourceActor> 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 = dispositionPlayer + 0;
g_vm->_act->_actorList[1]->_disposition = dispositionPlayer + 1;
g_vm->_act->_actorList[2]->_disposition = dispositionPlayer + 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 < factionNumColumns);
#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 < factionNumColumns; 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 < factionNumColumns);
#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 < factionNumColumns; ++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 < factionNumColumns; ++j)
g_vm->_act->_factionTable[i][j] = in->readSint16LE();
}
}
}