mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-16 22:58:09 +00:00
3637 lines
103 KiB
C++
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();
|
|
}
|
|
}
|
|
|
|
}
|