mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-08 10:51:11 +00:00
3653 lines
103 KiB
C++
3653 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 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*
|
|
* Based on the original sources
|
|
* Faery Tale II -- The Halls of the Dead
|
|
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
|
|
*/
|
|
|
|
#include "common/debug.h"
|
|
|
|
#include "saga2/saga2.h"
|
|
#include "saga2/dispnode.h"
|
|
#include "saga2/tile.h"
|
|
#include "saga2/motion.h"
|
|
#include "saga2/task.h"
|
|
#include "saga2/assign.h"
|
|
#include "saga2/setup.h"
|
|
#include "saga2/stimtype.h"
|
|
#include "saga2/band.h"
|
|
#include "saga2/sensor.h"
|
|
#include "saga2/weapons.h"
|
|
#include "saga2/localize.h"
|
|
#include "saga2/intrface.h"
|
|
#include "saga2/contain.h"
|
|
#include "saga2/combat.h"
|
|
|
|
// Include files needed for SAGA script dispatch
|
|
#include "saga2/script.h"
|
|
#include "saga2/methods.r" // generated by SAGA
|
|
|
|
namespace Saga2 {
|
|
|
|
/* ===================================================================== *
|
|
Constants
|
|
* ===================================================================== */
|
|
|
|
// this is currently set to an arbitrary value for testing purposes.
|
|
const uint16 defaultReach = 24;
|
|
|
|
const uint32 actorListID = MKTAG('A', 'C', 'T', 'O');
|
|
|
|
/* ===================================================================== *
|
|
Externals
|
|
* ===================================================================== */
|
|
|
|
extern uint8 identityColors[256];
|
|
|
|
extern hResContext *listRes; // object list resource handle
|
|
|
|
extern int16 actorLimboCount;
|
|
|
|
bool unstickObject(GameObject *obj);
|
|
|
|
extern ObjectSoundFXs *objectSoundFXTable; // the global object sound effects table
|
|
|
|
#if DEBUG
|
|
extern bool massAndBulkCount;
|
|
#endif
|
|
|
|
/* ===================================================================== *
|
|
Globals -- might as well stick it here as anywhere.
|
|
* ===================================================================== */
|
|
|
|
int16 factionTable[maxFactions][factionNumColumns];
|
|
|
|
// Indicates wether actor states should be paused
|
|
bool actorStatesPaused;
|
|
|
|
// Indicates wether player actors should have combat behavior
|
|
bool combatBehaviorEnabled;
|
|
|
|
/* ===================================================================== *
|
|
ActorProto member functions
|
|
* ===================================================================== */
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return a bit mask indicating the properties of this object type
|
|
|
|
uint16 ActorProto::containmentSet(void) {
|
|
// All actors may also be weapons (indicating natural attacks)
|
|
return ProtoObj::containmentSet() | isWeapon;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if the specified object can be contained by this object
|
|
|
|
bool ActorProto::canContain(ObjectID dObj, ObjectID item) {
|
|
assert(isActor(dObj));
|
|
assert(isObject(item) || isActor(item));
|
|
|
|
GameObject *itemPtr = GameObject::objectAddress(item);
|
|
|
|
// Actors can contain any object, except worlds and other actors
|
|
return isObject(item)
|
|
&& ((itemPtr->containmentSet() & ProtoObj::isIntangible) == 0
|
|
|| itemPtr->possessor() == dObj);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if the specified object can be contained by this object at
|
|
// the specified slot
|
|
|
|
bool ActorProto::canContainAt(
|
|
ObjectID dObj,
|
|
ObjectID item,
|
|
const TilePoint &) {
|
|
assert(isActor(dObj));
|
|
assert(isObject(item) || isActor(item));
|
|
|
|
GameObject *itemPtr = GameObject::objectAddress(item);
|
|
|
|
// Actors can contain any object, except worlds and other actors
|
|
// REM: must add test to determine if specified slot is valid.
|
|
return isObject(item)
|
|
&& ((itemPtr->containmentSet() & ProtoObj::isIntangible) == 0
|
|
|| itemPtr->possessor() == dObj);
|
|
}
|
|
|
|
weaponID ActorProto::getWeaponID(void) {
|
|
return weaponDamage;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// use this actor
|
|
|
|
bool ActorProto::useAction(ObjectID dObj, ObjectID enactor) {
|
|
assert(isActor(dObj));
|
|
|
|
Actor *a = (Actor *)GameObject::objectAddress(dObj);
|
|
|
|
if (a->isDead())
|
|
return ((PhysicalContainerProto *)this)->PhysicalContainerProto::useAction(dObj, enactor);
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if this actor can be opened
|
|
|
|
bool ActorProto::canOpen(ObjectID dObj, ObjectID) {
|
|
assert(isActor(dObj));
|
|
|
|
return ((Actor *)GameObject::objectAddress(dObj))->isDead();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// open this actor
|
|
|
|
// Kludge!
|
|
extern int16 openMindType;
|
|
|
|
bool ActorProto::openAction(ObjectID dObj, ObjectID) {
|
|
assert(isActor(dObj));
|
|
|
|
ContainerNode *cn;
|
|
|
|
GameObject *dObjPtr = GameObject::objectAddress(dObj);
|
|
|
|
assert(!dObjPtr->isOpen() && !dObjPtr->isLocked());
|
|
|
|
cn = CreateContainerNode(dObj, false, openMindType);
|
|
cn->markForShow(); // Deferred open
|
|
dObjPtr->_data.objectFlags |= objectOpen; // Set open bit;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// close this actor
|
|
|
|
bool ActorProto::closeAction(ObjectID dObj, ObjectID) {
|
|
assert(isActor(dObj));
|
|
|
|
GameObject *dObjPtr = GameObject::objectAddress(dObj);
|
|
ContainerNode *cn = g_vm->_containerList->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)
|
|
: NULL;
|
|
}
|
|
|
|
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 != NULL) {
|
|
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 != NULL)
|
|
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 != NULL && 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 != NULL) {
|
|
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(154) >= 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 = NULL;
|
|
Location oldLoc(itemPtr->getLocation(), itemPtr->IDParent());
|
|
|
|
bool result;
|
|
|
|
// Split the merged object if needed.
|
|
if (itemPtr->isMergeable() // If mergeable
|
|
&& num < itemPtr->getExtra()) { // And not dropping whole pile
|
|
if (num == 0) return false; // If mergeing zero, then do nothing
|
|
|
|
extractedObj = itemPtr->extractMerged(itemPtr->getExtra() - num);
|
|
if (extractedObj == NULL)
|
|
return false;
|
|
|
|
extractedObj->move(oldLoc);
|
|
}
|
|
|
|
// Determine if this object is simply being moved within this actor
|
|
if (oldLoc.context == dObj) {
|
|
// Determine if and where the object is in use by this actor
|
|
if (a->leftHandObject == item)
|
|
inUseType = heldInLeftHand;
|
|
else if (a->rightHandObject == item)
|
|
inUseType = heldInRightHand;
|
|
else {
|
|
int i;
|
|
|
|
inUseType = notInUse;
|
|
|
|
for (i = 0; i < ARMOR_COUNT; i++) {
|
|
if (a->armorObjects[i] == item) {
|
|
inUseType = worn;
|
|
wornWhere = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
inUseType = notInUse;
|
|
|
|
// Do the deed
|
|
itemPtr->move(Location(0, 0, 0, ImportantLimbo));
|
|
if (dObjPtr->canFitBulkwise(itemPtr)
|
|
&& dObjPtr->canFitMasswise(itemPtr)) {
|
|
itemPtr->move(Location(where, dObj));
|
|
result = true;
|
|
} else {
|
|
itemPtr->move(oldLoc);
|
|
if (extractedObj != NULL)
|
|
GameObject::mergeWith(extractedObj, itemPtr, extractedObj->getExtra());
|
|
result = false;
|
|
}
|
|
|
|
// Re-equip the item if necessary
|
|
if (inUseType != notInUse) {
|
|
switch (inUseType) {
|
|
case heldInLeftHand:
|
|
a->holdInLeftHand(item);
|
|
break;
|
|
|
|
case heldInRightHand:
|
|
a->holdInRightHand(item);
|
|
break;
|
|
|
|
case worn:
|
|
a->wear(item, wornWhere);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Initiate a natural attack motion
|
|
|
|
void ActorProto::initiateAttack(ObjectID attacker, ObjectID target) {
|
|
assert(isActor(attacker));
|
|
assert(isObject(target) || isActor(target));
|
|
|
|
Actor *attackerPtr = (Actor *)GameObject::objectAddress(attacker);
|
|
GameObject *targetPtr = GameObject::objectAddress(target);
|
|
|
|
// Start the attack motion
|
|
if (attackerPtr->appearance != NULL) {
|
|
if (attackerPtr->isActionAvailable(actionSwingHigh))
|
|
MotionTask::oneHandedSwing(*attackerPtr, *targetPtr);
|
|
else if (attackerPtr->isActionAvailable(actionTwoHandSwingHigh))
|
|
MotionTask::twoHandedSwing(*attackerPtr, *targetPtr);
|
|
} else
|
|
MotionTask::oneHandedSwing(*attackerPtr, *targetPtr);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Given an object sound effect record, which sound should be made
|
|
// when this object is damaged
|
|
|
|
uint8 ActorProto::getDamageSound(const ObjectSoundFXs &soundFXs) {
|
|
return !(flags & ResourceObjectPrototype::objPropNoSurface)
|
|
? !(flags & ResourceObjectPrototype::objPropHardSurface)
|
|
? soundFXs.soundFXHitFlesh
|
|
: soundFXs.soundFXHitHard
|
|
: 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Do the background processing, if needed, for this object.
|
|
|
|
void ActorProto::doBackgroundUpdate(GameObject *obj) {
|
|
// get the ID for this object
|
|
ObjectID actorID = obj->thisID();
|
|
|
|
// find out if this object is an actor
|
|
if (isActor(actorID)) {
|
|
// get a pointer to that actor
|
|
GameObject *actorObj = GameObject::objectAddress(actorID);
|
|
Actor *a = (Actor *)actorObj;
|
|
|
|
if (!a->isActivated()) {
|
|
// If this is a temporary actor waiting for expiration,
|
|
// then decrement the expiration counter and possibly
|
|
// delete the actor
|
|
if ((a->flags & Actor::temporary) || a->isDead()) {
|
|
if (a->deactivationCounter <= 0) {
|
|
a->deleteObjectRecursive();
|
|
return;
|
|
} else a->deactivationCounter--;
|
|
} else {
|
|
// If the actor has failed morale there is a random
|
|
// chance of him regaining his courage
|
|
if ((a->flags & Actor::afraid) && 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) {
|
|
int i;
|
|
|
|
debugC(1, kDebugActors, "Actor init flags: %d, permanent: %d", initFlags, initFlags & actorPermanent);
|
|
|
|
// Fixup the prototype pointer to point to an actor prototype
|
|
prototype = (ProtoObj *)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;
|
|
memset(&knowledge, 0, sizeof(knowledge));
|
|
|
|
// Initialize the rest of the data members
|
|
*((uint32 *)conversationMemory) = 0L;
|
|
currentAnimation = actionStand;
|
|
currentPose = 0;
|
|
animationFlags = 0;
|
|
flags = 0;
|
|
if (!(initFlags & actorPermanent))
|
|
flags |= temporary;
|
|
|
|
poseInfo.flags = 0;
|
|
poseInfo.actorFrameIndex = 0;
|
|
poseInfo.actorFrameBank = 0;
|
|
poseInfo.leftObjectIndex = 0;
|
|
poseInfo.rightObjectIndex = 0;
|
|
poseInfo.leftObjectOffset.x = poseInfo.leftObjectOffset.y = 0;
|
|
poseInfo.rightObjectOffset.x = poseInfo.rightObjectOffset.y = 0;
|
|
|
|
appearance = NULL;
|
|
cycleCount = 0;
|
|
kludgeCount = 0;
|
|
moveTask = NULL;
|
|
enchantmentFlags = 0L;
|
|
curTask = NULL;
|
|
currentGoal = actorGoalFollowAssignment;
|
|
deactivationCounter = 0;
|
|
_assignment = nullptr;
|
|
|
|
memcpy(
|
|
&effectiveStats,
|
|
&((ActorProto *)prototype)->baseStats,
|
|
sizeof(effectiveStats));
|
|
effectiveStats.vitality = MAX<int16>(effectiveStats.vitality, 1);
|
|
|
|
actionCounter = 0;
|
|
effectiveResistance = 0;
|
|
effectiveImmunity = 0;
|
|
recPointsPerUpdate = BASE_REC_RATE;
|
|
currentRecoveryPoints = 0;
|
|
leader = NULL;
|
|
followers = NULL;
|
|
_followersID = NoBand;
|
|
for (i = 0; i < ARMOR_COUNT; i++)
|
|
armorObjects[i] = Nothing;
|
|
currentTarget = NULL;
|
|
for (i = 0; i < actorScriptVars; i++)
|
|
scriptVar[i] = 0;
|
|
|
|
evalActorEnchantments(this);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Actor constructor -- copies the resource fields and simply NULL's most
|
|
// of the rest of the data members
|
|
Actor::Actor(void) {
|
|
prototype = nullptr;
|
|
faction = 0;
|
|
colorScheme = 0;
|
|
appearanceID = 0;
|
|
attitude = 0;
|
|
mood = 0;
|
|
disposition = 0;
|
|
currentFacing = 0;
|
|
tetherLocU = 0;
|
|
tetherLocV = 0;
|
|
tetherDist = 0;
|
|
leftHandObject = 0;
|
|
rightHandObject = 0;
|
|
schedule = 0;
|
|
for (int i = 0; i < 16; ++i)
|
|
knowledge[i] = 0;
|
|
|
|
// Initialize the rest of the data members
|
|
for (int i = 0; i < 4; ++i)
|
|
conversationMemory[i] = 0;
|
|
|
|
currentAnimation = actionStand;
|
|
currentPose = 0;
|
|
animationFlags = 0;
|
|
flags = 0;
|
|
|
|
poseInfo.flags = 0;
|
|
poseInfo.actorFrameIndex = 0;
|
|
poseInfo.actorFrameBank = 0;
|
|
poseInfo.leftObjectIndex = 0;
|
|
poseInfo.rightObjectIndex = 0;
|
|
poseInfo.leftObjectOffset.x = poseInfo.leftObjectOffset.y = 0;
|
|
poseInfo.rightObjectOffset.x = poseInfo.rightObjectOffset.y = 0;
|
|
|
|
appearance = nullptr;
|
|
cycleCount = 0;
|
|
kludgeCount = 0;
|
|
moveTask = nullptr;
|
|
enchantmentFlags = 0L;
|
|
curTask = nullptr;
|
|
currentGoal = actorGoalFollowAssignment;
|
|
deactivationCounter = 0;
|
|
_assignment = nullptr;
|
|
|
|
memset(&effectiveStats, 0, sizeof(effectiveStats));
|
|
effectiveStats.vitality = MAX<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) {
|
|
int i;
|
|
|
|
// Fixup the prototype pointer to point to an actor prototype
|
|
prototype = prototype != NULL
|
|
? (ProtoObj *)g_vm->_actorProtos[getProtoNum()]
|
|
: NULL;
|
|
|
|
// Copy the resource fields
|
|
faction = res.faction;
|
|
colorScheme = res.colorScheme;
|
|
appearanceID = res.appearanceID;
|
|
attitude = res.attitude;
|
|
mood = res.mood;
|
|
disposition = res.disposition;
|
|
currentFacing = res.currentFacing;
|
|
tetherLocU = res.tetherLocU;
|
|
tetherLocV = res.tetherLocV;
|
|
tetherDist = res.tetherDist;
|
|
leftHandObject = res.leftHandObject;
|
|
rightHandObject = res.rightHandObject;
|
|
schedule = res.schedule;
|
|
memcpy(&knowledge, &res.knowledge, sizeof(knowledge));
|
|
|
|
// Initialize the rest of the data members
|
|
*((uint32 *)conversationMemory) = 0L;
|
|
currentAnimation = actionStand;
|
|
currentPose = 0;
|
|
animationFlags = 0;
|
|
flags = 0;
|
|
|
|
poseInfo.flags = 0;
|
|
poseInfo.actorFrameIndex = 0;
|
|
poseInfo.actorFrameBank = 0;
|
|
poseInfo.leftObjectIndex = 0;
|
|
poseInfo.rightObjectIndex = 0;
|
|
poseInfo.leftObjectOffset.x = poseInfo.leftObjectOffset.y = 0;
|
|
poseInfo.rightObjectOffset.x = poseInfo.rightObjectOffset.y = 0;
|
|
|
|
appearance = NULL;
|
|
cycleCount = 0;
|
|
kludgeCount = 0;
|
|
moveTask = NULL;
|
|
enchantmentFlags = 0L;
|
|
curTask = NULL;
|
|
currentGoal = actorGoalFollowAssignment;
|
|
deactivationCounter = 0;
|
|
_assignment = nullptr;
|
|
|
|
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 = NULL;
|
|
_leaderID = Nothing;
|
|
followers = NULL;
|
|
_followersID = NoBand;
|
|
for (i = 0; i < ARMOR_COUNT; i++)
|
|
armorObjects[i] = Nothing;
|
|
currentTarget = NULL;
|
|
_currentTargetID = Nothing;
|
|
for (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();
|
|
|
|
_followersID = in->readSint16LE();
|
|
|
|
for (int i = 0; i < ARRAYSIZE(armorObjects); ++i)
|
|
armorObjects[i] = in->readUint16LE();
|
|
|
|
_currentTargetID = in->readUint16LE();
|
|
|
|
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(void) {
|
|
if (appearance != NULL) ReleaseActorAppearance(appearance);
|
|
|
|
if (getAssignment())
|
|
delete getAssignment();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the number of bytes needed to archive this actor
|
|
|
|
int32 Actor::archiveSize(void) {
|
|
int32 size = GameObject::archiveSize();
|
|
|
|
size += sizeof(ActorArchive);
|
|
if (flags & hasAssignment)
|
|
size += assignmentArchiveSize(this);
|
|
|
|
return size;
|
|
}
|
|
|
|
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 != NULL)
|
|
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);
|
|
|
|
int leaderID = (leader != NULL) ? leader->thisID() : Nothing;
|
|
|
|
out->writeUint16LE(leaderID);
|
|
|
|
int followersID = (followers != NULL) ? getBandID(followers) : NoBand;
|
|
|
|
out->writeSint16LE(followersID);
|
|
out->write(armorObjects, ARMOR_COUNT * 2);
|
|
|
|
int currentTargetID = currentTarget != NULL ? 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 != NULL ? leader->thisID() : Nothing);
|
|
debugC(4, kDebugSaveload, "... followersID = %d", followers != NULL ? getBandID(followers) : NoBand);
|
|
// debugC(4, kDebugSaveload, "... armorObjects = %d", armorObjects);
|
|
debugC(4, kDebugSaveload, "... currentTargetID = %d", currentTarget != NULL ? 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->_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(void) {
|
|
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 != NULL) {
|
|
curTask->abortTask();
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
|
|
// Kill motion task
|
|
if (moveTask != NULL) moveTask->remove();
|
|
|
|
// If banded, remove from band
|
|
if (leader != NULL) {
|
|
assert(isActor(leader));
|
|
|
|
leader->removeFollower(this);
|
|
leader = NULL;
|
|
} else if (followers != NULL) {
|
|
int16 i;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
Actor *follower = (*followers)[i];
|
|
|
|
follower->leader = NULL;
|
|
follower->evaluateNeeds();
|
|
}
|
|
|
|
delete followers;
|
|
followers = NULL;
|
|
}
|
|
|
|
// Place in limbo
|
|
if (!(_data.objectFlags & objectNoRecycle)) {
|
|
append(ActorLimbo);
|
|
actorLimboCount++;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Cause the actor to stop his current motion task is he is interruptable
|
|
|
|
void Actor::stopMoving(void) {
|
|
if (moveTask != NULL && isInterruptable())
|
|
moveTask->remove();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Cause this actor to die
|
|
|
|
void Actor::die(void) {
|
|
if (!isDead()) return;
|
|
|
|
ObjectID dObj = thisID();
|
|
scriptCallFrame scf;
|
|
PlayerActorID playerID;
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_Actor_onDie, scf);
|
|
|
|
// Kill task
|
|
if (curTask != NULL) {
|
|
curTask->abortTask();
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
|
|
// Kill motion task
|
|
if (moveTask != NULL) moveTask->remove();
|
|
|
|
// If banded, remove from band
|
|
if (leader != NULL) {
|
|
assert(isActor(leader));
|
|
|
|
leader->removeFollower(this);
|
|
leader = NULL;
|
|
}
|
|
|
|
if (actorToPlayerID(this, playerID))
|
|
handlePlayerActorDeath(playerID);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Cause this actor to come back to life
|
|
|
|
void Actor::imNotQuiteDead(void) {
|
|
if (isDead()) {
|
|
PlayerActorID pID;
|
|
|
|
effectiveStats.vitality = 1;
|
|
if (actorToPlayerID(this, pID))
|
|
updateBrotherControls(pID);
|
|
|
|
evaluateNeeds();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Cuase the actor to re-assess his/her vitality
|
|
|
|
void Actor::vitalityUpdate(void) {
|
|
// If we're dead, don't heal
|
|
if (isDead()) return;
|
|
|
|
// get the base stats for this actor
|
|
ActorAttributes *baseStats = getBaseStats();
|
|
|
|
// first find out if this actor is wounded
|
|
if (effectiveStats.vitality < baseStats->vitality) {
|
|
// whole vitality number goes here
|
|
int16 recover;
|
|
int16 fractionRecover;
|
|
|
|
// get the whole number first
|
|
recover = recPointsPerUpdate / recPointsPerVitality;
|
|
|
|
// get the fraction
|
|
fractionRecover = recPointsPerUpdate % recPointsPerVitality;
|
|
|
|
// if there is an overrun
|
|
if (currentRecoveryPoints + fractionRecover > recPointsPerVitality) {
|
|
// add the overrun to the whole number
|
|
recover++;
|
|
currentRecoveryPoints = (currentRecoveryPoints + fractionRecover) - recPointsPerVitality;
|
|
} else {
|
|
currentRecoveryPoints += fractionRecover;
|
|
}
|
|
|
|
|
|
if (effectiveStats.vitality + recover >=
|
|
baseStats->vitality) {
|
|
effectiveStats.vitality = baseStats->vitality;
|
|
} else {
|
|
effectiveStats.vitality += recover;
|
|
|
|
//WriteStatusF( 5, " Healed: %d, rec: %d, part: %d ", effectiveStats.vitality,
|
|
// recover, currentRecoveryPoints );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Perform actor specific activation tasks
|
|
|
|
void Actor::activateActor(void) {
|
|
debugC(1, kDebugActors, "Actors: Activated %d (%s)", thisID() - 32768, objName());
|
|
|
|
evaluateNeeds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Perfrom actor specific deactivation tasks
|
|
|
|
void Actor::deactivateActor(void) {
|
|
debugC(1, kDebugActors, "Actors: De-activated %d (%s)", thisID() - 32768, objName());
|
|
|
|
// Kill task
|
|
if (curTask != NULL) {
|
|
curTask->abortTask();
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
|
|
// Kill motion task
|
|
if (moveTask != NULL) moveTask->remove();
|
|
|
|
// If banded, remove from band
|
|
if (leader != NULL) {
|
|
assert(isActor(leader));
|
|
|
|
leader->removeFollower(this);
|
|
leader = NULL;
|
|
}
|
|
|
|
// Temporary actors get deleted upon deactivation
|
|
if ((flags & temporary) || isDead()) {
|
|
deactivationCounter = 10; // actor lasts for 50 seconds
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Delobotomize this actor
|
|
|
|
void Actor::delobotomize(void) {
|
|
if (!(flags & lobotomized)) return;
|
|
|
|
ObjectID dObj = thisID();
|
|
scriptCallFrame scf;
|
|
|
|
flags &= ~lobotomized;
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_Actor_onDelobotomize, scf);
|
|
|
|
evaluateNeeds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Lobotomize this actor
|
|
|
|
void Actor::lobotomize(void) {
|
|
if (flags & lobotomized) return;
|
|
|
|
ObjectID dObj = thisID();
|
|
scriptCallFrame scf;
|
|
|
|
// Kill task
|
|
if (curTask != NULL) {
|
|
curTask->abortTask();
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
|
|
// Kill motion task
|
|
if (moveTask != NULL) moveTask->remove();
|
|
|
|
flags |= lobotomized;
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_Actor_onLobotomize, scf);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return a pointer to the base stats for this actor. If this actor
|
|
// is a non-player actor, the base stats are in the prototype. If this
|
|
// actor is a player actor, the base stats are in the PlayerActor
|
|
// structure.
|
|
|
|
ActorAttributes *Actor::getBaseStats(void) {
|
|
if (disposition < dispositionPlayer)
|
|
return &((ActorProto *)prototype)->baseStats;
|
|
else
|
|
return &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(void) {
|
|
//if ( disposition < dispositionPlayer )
|
|
return ((ActorProto *)prototype)->baseEffectFlags;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the object base resistance flags. If this actor
|
|
// is a non-player actor, the base stats are in the prototype.
|
|
|
|
uint16 Actor::getBaseResistance(void) {
|
|
//if ( disposition < dispositionPlayer )
|
|
return ((ActorProto *)prototype)->resistance;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the object base immunity flags. If this actor
|
|
// is a non-player actor, the base stats are in the prototype.
|
|
|
|
uint16 Actor::getBaseImmunity(void) {
|
|
//if ( disposition < dispositionPlayer )
|
|
return ((ActorProto *)prototype)->immunity;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the base recovery rate
|
|
|
|
uint16 Actor::getBaseRecovery(void) {
|
|
return BASE_REC_RATE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if specified point is within actor's reach
|
|
|
|
bool Actor::inReach(const TilePoint &tp) {
|
|
return inRange(tp, defaultReach);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if specified point is within an objects use range
|
|
|
|
bool Actor::inUseRange(const TilePoint &tp, GameObject *obj) {
|
|
uint16 range = obj->proto()->maximumRange;
|
|
|
|
return inRange(tp, MAX(range, defaultReach));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if actor is immobile (i.e. can't walk)
|
|
|
|
bool Actor::isImmobile(void) {
|
|
return isDead()
|
|
|| hasEffect(actorImmobile)
|
|
|| hasEffect(actorAsleep)
|
|
|| hasEffect(actorParalyzed);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return a pointer to this actor's currently readied offensive object
|
|
|
|
GameObject *Actor::offensiveObject(void) {
|
|
if (rightHandObject != Nothing) {
|
|
assert(isObject(rightHandObject));
|
|
|
|
GameObject *obj = GameObject::objectAddress(rightHandObject);
|
|
|
|
// Any object in an actor's right hand should be a weapon
|
|
assert(obj->containmentSet() & ProtoObj::isWeapon);
|
|
|
|
return obj;
|
|
}
|
|
|
|
if (leftHandObject != Nothing) {
|
|
assert(isObject(leftHandObject));
|
|
|
|
GameObject *obj = GameObject::objectAddress(leftHandObject);
|
|
|
|
if (obj->containmentSet() & ProtoObj::isWeapon)
|
|
return obj;
|
|
}
|
|
|
|
// If not carrying a weapon attack with self
|
|
return this;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Returns pointers to this actor's readied primary defensive object
|
|
// and optionally their scondary defensive object
|
|
|
|
void Actor::defensiveObject(GameObject **priPtr, GameObject **secPtr) {
|
|
assert(priPtr != NULL);
|
|
|
|
GameObject *leftHandObjPtr,
|
|
*rightHandObjPtr,
|
|
*primary = NULL,
|
|
*secondary = NULL;
|
|
|
|
// Get a pointer to the left hand object
|
|
leftHandObjPtr = leftHandObject != Nothing
|
|
? (assert(isObject(leftHandObject))
|
|
, GameObject::objectAddress(leftHandObject))
|
|
: NULL;
|
|
|
|
// Get a pointer to the right hand object
|
|
rightHandObjPtr = rightHandObject != Nothing
|
|
? (assert(isObject(rightHandObject))
|
|
, GameObject::objectAddress(rightHandObject))
|
|
: NULL;
|
|
|
|
if (leftHandObjPtr != NULL) {
|
|
GameObject **rightHandObjDest;
|
|
|
|
if (leftHandObjPtr->proto()->canBlock()) {
|
|
// Left hand object is primary. Right hand object may be
|
|
// secondary
|
|
primary = leftHandObjPtr;
|
|
rightHandObjDest = &secondary;
|
|
} else
|
|
// Right hand object may be primary
|
|
rightHandObjDest = &primary;
|
|
|
|
if (rightHandObjPtr != NULL && rightHandObjPtr->proto()->canBlock())
|
|
// Right hand object is defensive
|
|
*rightHandObjDest = rightHandObjPtr;
|
|
} else {
|
|
if (rightHandObjPtr != NULL && rightHandObjPtr->proto()->canBlock())
|
|
// Right hand object is primary defensive object
|
|
primary = rightHandObjPtr;
|
|
}
|
|
|
|
// Return the primary pointer
|
|
*priPtr = primary;
|
|
// Return the secondary pointer
|
|
if (secPtr != NULL) *secPtr = secondary;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Returns a pointer to the object with which this actor is currently
|
|
// blocking, if any
|
|
|
|
GameObject *Actor::blockingObject(Actor *attacker) {
|
|
return moveTask != NULL
|
|
? moveTask->blockingObject(attacker)
|
|
: NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the total used armor attributes
|
|
|
|
void Actor::totalArmorAttributes(ArmorAttributes &armorAttribs) {
|
|
int i;
|
|
ProtoObj *thisProto = proto();
|
|
|
|
// Plug in actor's natural values
|
|
armorAttribs.damageAbsorbtion = thisProto->damageAbsorbtion;
|
|
armorAttribs.damageDivider = MAX<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 != NULL);
|
|
|
|
armorAttribs.damageAbsorbtion += armorProto->damageAbsorbtion;
|
|
if (armorProto->damageDivider != 0)
|
|
armorAttribs.damageDivider *= armorProto->damageDivider;
|
|
armorAttribs.defenseBonus += armorProto->defenseBonus;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if specified point is within actor's attack range
|
|
|
|
bool Actor::inAttackRange(const TilePoint &tp) {
|
|
GameObject *weapon = offensiveObject();
|
|
uint16 range = weapon != NULL ? weapon->proto()->maximumRange : 0;
|
|
|
|
return inRange(tp, MAX(range, defaultReach));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Initiate an attack upon a specified target
|
|
|
|
void Actor::attack(GameObject *target) {
|
|
GameObject *weapon = offensiveObject();
|
|
|
|
if (weapon != NULL)
|
|
weapon->proto()->initiateAttack(thisID(), target->thisID());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Stop all attacks on a specified target
|
|
|
|
void Actor::stopAttack(GameObject *target) {
|
|
if (moveTask && moveTask->isAttack() && moveTask->targetObj == target)
|
|
moveTask->finishAttack();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if this actor can block an attack
|
|
|
|
bool Actor::canDefend(void) {
|
|
if (isDead()) return false;
|
|
|
|
// Look at left hand object, generally the defensive object
|
|
if (leftHandObject != Nothing) {
|
|
GameObject *obj = GameObject::objectAddress(leftHandObject);
|
|
|
|
if (obj->proto()->canBlock()) return true;
|
|
}
|
|
|
|
// Look at right hand object, generally the offensive object
|
|
if (rightHandObject != Nothing) {
|
|
GameObject *obj = GameObject::objectAddress(rightHandObject);
|
|
|
|
if (obj->proto()->canBlock()) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return a numeric value which roughly estimates this actor's
|
|
// offensive strength
|
|
|
|
int16 Actor::offenseScore(void) {
|
|
// REM: at this time this calculation is somewhat arbitrary
|
|
|
|
int16 score = 0;
|
|
GameObject *weapon = offensiveObject();
|
|
|
|
if (weapon != NULL) {
|
|
ProtoObj *proto = weapon->proto();
|
|
|
|
score += proto->weaponDamage + (proto->maximumRange / kTileUVSize);
|
|
}
|
|
|
|
// Add average mana
|
|
score += (effectiveStats.redMana
|
|
+ effectiveStats.orangeMana
|
|
+ effectiveStats.yellowMana
|
|
+ effectiveStats.greenMana
|
|
+ effectiveStats.blueMana
|
|
+ effectiveStats.violetMana)
|
|
/ 6;
|
|
|
|
score += effectiveStats.spellcraft + effectiveStats.brawn;
|
|
|
|
return score;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return a numeric value which roughly estimates this actor's
|
|
// defensive strength
|
|
|
|
int16 Actor::defenseScore(void) {
|
|
// REM: at this time this calculation is somewhat arbitrary
|
|
|
|
int16 score = 0;
|
|
GameObject *shield;
|
|
ArmorAttributes armorAttribs;
|
|
|
|
defensiveObject(&shield);
|
|
|
|
if (shield != NULL) {
|
|
ProtoObj *proto = shield->proto();
|
|
|
|
score += proto->defenseBonus;
|
|
}
|
|
|
|
totalArmorAttributes(armorAttribs);
|
|
|
|
score += (armorAttribs.defenseBonus + armorAttribs.damageAbsorbtion)
|
|
* armorAttribs.damageDivider;
|
|
|
|
score += effectiveStats.agility + effectiveStats.vitality;
|
|
|
|
return score;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the sprite color translation table based upon the actor's
|
|
// color scheme
|
|
|
|
void Actor::getColorTranslation(ColorTable map) {
|
|
// If actor has color table loaded, then calculate the
|
|
// translation table.
|
|
if (appearance
|
|
&& appearance->schemeList) {
|
|
buildColorTable(map,
|
|
appearance->schemeList->_schemes[colorScheme]->bank,
|
|
11);
|
|
} else memcpy(map, identityColors, 256);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Set the current animation sequence for the actor.
|
|
//
|
|
// Each time the nextAnimationFrame() is called, it will increment
|
|
// to the next frame in the sequence.
|
|
|
|
int16 Actor::setAction(int16 newState, int16 flags_) {
|
|
ActorAnimation *anim;
|
|
int16 numPoses = 0;
|
|
|
|
// Refresh the handles
|
|
// RLockHandle( appearance->animations );
|
|
// RUnlockHandle( appearance->animations );
|
|
|
|
if (appearance == NULL) return 0;
|
|
|
|
// If this animation has no frames, then return false
|
|
anim = appearance->animation(newState);
|
|
if (anim)
|
|
numPoses = anim->count[currentFacing];
|
|
if (numPoses <= 0) return 0;
|
|
|
|
// Set up the animation
|
|
currentAnimation = newState;
|
|
animationFlags = flags_;
|
|
|
|
// If they haven't set the "no reset" flag, then
|
|
if (!(flags_ & animateNoRestart)) {
|
|
if (flags_ & animateReverse) currentPose = numPoses - 1;
|
|
else currentPose = 0;
|
|
} else {
|
|
currentPose = clamp(0, currentPose, numPoses - 1);
|
|
}
|
|
|
|
return numPoses;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// returns true if the action is available in the current direction.
|
|
//
|
|
|
|
bool Actor::isActionAvailable(int16 newState, bool anyDir) {
|
|
ActorAnimation *anim;
|
|
|
|
// Refresh the handles
|
|
// RLockHandle( appearance->animations );
|
|
// RUnlockHandle( appearance->animations );
|
|
|
|
if (appearance == nullptr)
|
|
return false;
|
|
|
|
// If this animation has no frames, then return false
|
|
anim = appearance->animation(newState);
|
|
if (anim == nullptr)
|
|
return false;
|
|
|
|
if (anyDir) {
|
|
for (int i = 0; i < numPoseFacings; i++) {
|
|
if (anim->count[i] > 0) return true;
|
|
}
|
|
} else {
|
|
if (anim->count[currentFacing] > 0) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Return the number of animation frames in the specified action for the
|
|
// specified direction
|
|
|
|
int16 Actor::animationFrames(int16 actionType, Direction dir) {
|
|
if (appearance == nullptr)
|
|
return 0;
|
|
|
|
ActorAnimation *anim;
|
|
|
|
anim = appearance->animation(actionType);
|
|
|
|
if (!anim)
|
|
return 0;
|
|
|
|
return anim->count[dir];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Update the current animation sequence to the next frame.
|
|
// Returns true if the animation sequence has finished.
|
|
|
|
bool Actor::nextAnimationFrame(void) {
|
|
ActorAnimation *anim;
|
|
int16 numPoses;
|
|
|
|
// Refresh the handles
|
|
// RLockHandle( appearance->animations );
|
|
// RUnlockHandle( appearance->animations );
|
|
|
|
if (appearance == NULL) {
|
|
if (animationFlags & animateOnHold) {
|
|
return false;
|
|
} else if (animationFlags & animateRepeat) {
|
|
animationFlags |= animateOnHold;
|
|
return false;
|
|
} else {
|
|
animationFlags |= animateFinished;
|
|
return true;
|
|
}
|
|
} else animationFlags &= ~animateOnHold;
|
|
|
|
// Get the number of frames in the animation
|
|
anim = appearance->animation(currentAnimation);
|
|
numPoses = anim->count[currentFacing];
|
|
if (numPoses <= 0) {
|
|
animationFlags |= animateFinished;
|
|
return true; // no poses, return DONE
|
|
}
|
|
|
|
// If the sprite could not be displayed because it has not
|
|
// been loaded, then don't update the animation state --
|
|
// wait until the sprite gets loaded, and then continue
|
|
// with the action.
|
|
if (animationFlags & animateNotLoaded) return false;
|
|
|
|
// If the animation has reached the last frame, then exit.
|
|
if (animationFlags & animateFinished) return true;
|
|
|
|
if (animationFlags & animateRandom) {
|
|
// Select a random frame from the series.
|
|
currentPose = 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(void) {
|
|
GameObject *obj,
|
|
*nextObj;
|
|
|
|
for (obj = _data.childID != Nothing
|
|
? GameObject::objectAddress(_data.childID)
|
|
: NULL;
|
|
obj != NULL;
|
|
obj = nextObj) {
|
|
nextObj = obj->IDNext() != Nothing
|
|
? GameObject::objectAddress(obj->IDNext())
|
|
: NULL;
|
|
|
|
// Delete intangible objects and drop tangible objects
|
|
if (obj->containmentSet() & ProtoObj::isIntangible)
|
|
obj->deleteObjectRecursive();
|
|
else
|
|
dropInventoryObject(obj, obj->isMergeable() ? obj->getExtra() : 1);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Place an object into this actor's right or left hand
|
|
|
|
void Actor::holdInRightHand(ObjectID objID) {
|
|
assert(isObject(objID));
|
|
rightHandObject = objID;
|
|
|
|
if (isPlayerActor(this))
|
|
g_vm->_containerList->setUpdate(thisID());
|
|
|
|
evalActorEnchantments(this);
|
|
}
|
|
|
|
void Actor::holdInLeftHand(ObjectID objID) {
|
|
assert(isObject(objID));
|
|
leftHandObject = objID;
|
|
|
|
if (isPlayerActor(this))
|
|
g_vm->_containerList->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->_containerList->setUpdate(thisID());
|
|
|
|
evalActorEnchantments(this);
|
|
|
|
if (actorToPlayerID(this, playerID)) {
|
|
updateBrotherArmor(playerID);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Called when the actor is on the display list and has no motion task.
|
|
|
|
void Actor::updateAppearance(int32) {
|
|
// static uint16 count;
|
|
// count++;
|
|
|
|
if (isDead() || !isActivated() || (flags & lobotomized)) return;
|
|
|
|
#if DEBUG*0
|
|
WriteStatusF(4, "Wait Count %d Attitude %d", cycleCount, attitude);
|
|
#endif
|
|
|
|
#if DEBUG*0
|
|
extern void ShowObjectSection(GameObject * obj);
|
|
if (this != getCenterActor())
|
|
if (lineOfSight(getCenterActor(), this, terrainSurface))
|
|
ShowObjectSection(this);
|
|
#endif
|
|
|
|
if (appearance) {
|
|
if (animationFrames(actionStand, currentFacing) == 1) {
|
|
if (flags & fightStance) {
|
|
GameObject *weapon = offensiveObject();
|
|
|
|
if (weapon == this) weapon = NULL;
|
|
|
|
if (weapon != NULL) {
|
|
ProtoObj *weaponProto = weapon->proto();
|
|
|
|
setAction(weaponProto->fightStanceAction(thisID()), 0);
|
|
} else {
|
|
if (isActionAvailable(actionSwingHigh))
|
|
setAction(actionSwingHigh, 0);
|
|
else
|
|
setAction(actionTwoHandSwingHigh, 0);
|
|
}
|
|
|
|
cycleCount = 0;
|
|
} else {
|
|
if (cycleCount > 0) { //If In Wait State Between Wait Animation
|
|
cycleCount--;
|
|
|
|
setAction(actionStand, 0); //Just stand still
|
|
} else { // Wait Animation
|
|
if (cycleCount == 0) { //If Just Starting Wait Animation
|
|
cycleCount--;
|
|
switch (attitude) { //Emotion And Character Type
|
|
//Currently Attitude Not Set So Always Hits Zero
|
|
case 0:
|
|
//Returns True If Successful No Checking Yet
|
|
setAvailableAction(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 != NULL) {
|
|
curTask->abortTask();
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
|
|
currentGoal = newGoal;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Reevaluate actor's built-in needs
|
|
|
|
void Actor::evaluateNeeds(void) {
|
|
if (!isDead()
|
|
&& isActivated()
|
|
&& !(flags & lobotomized)) {
|
|
if (disposition >= dispositionPlayer) {
|
|
if (combatBehaviorEnabled) {
|
|
SenseInfo info;
|
|
|
|
if (canSenseActorProperty(
|
|
info,
|
|
maxSenseRange,
|
|
actorPropIDEnemy)
|
|
|| canSenseActorPropertyIndirectly(
|
|
info,
|
|
maxSenseRange,
|
|
actorPropIDEnemy)) {
|
|
PlayerActorID playerID = disposition - dispositionPlayer;
|
|
|
|
if (isAggressive(playerID))
|
|
setGoal(actorGoalAttackEnemy);
|
|
else {
|
|
if (leader != NULL && inBandingRange())
|
|
setGoal(actorGoalAvoidEnemies);
|
|
else
|
|
setGoal(actorGoalPreserveSelf);
|
|
}
|
|
} else if (leader != NULL && inBandingRange()) {
|
|
setGoal(actorGoalFollowLeader);
|
|
} else {
|
|
setGoal(actorGoalFollowAssignment);
|
|
}
|
|
} else if (leader != NULL && inBandingRange()) {
|
|
setGoal(actorGoalFollowLeader);
|
|
} else {
|
|
setGoal(actorGoalFollowAssignment);
|
|
}
|
|
} else {
|
|
if (disposition == dispositionEnemy
|
|
&& appearance != NULL
|
|
&& !hasEffect(actorNotDefenseless)) {
|
|
GameObject *obj;
|
|
bool foundWeapon = false;
|
|
ContainerIterator iter(this);
|
|
|
|
while (iter.next(&obj) != Nothing) {
|
|
ProtoObj *proto = obj->proto();
|
|
|
|
if ((proto->containmentSet() & ProtoObj::isWeapon)
|
|
&& isActionAvailable(proto->fightStanceAction(thisID()))) {
|
|
foundWeapon = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundWeapon
|
|
&& (isActionAvailable(actionSwingHigh)
|
|
|| isActionAvailable(actionTwoHandSwingHigh)))
|
|
foundWeapon = true;
|
|
|
|
if (!foundWeapon)
|
|
flags |= afraid;
|
|
}
|
|
|
|
if (flags & afraid || hasEffect(actorFear) || hasEffect(actorRepelUndead)) {
|
|
setGoal(actorGoalPreserveSelf);
|
|
} else if (leader != NULL && inBandingRange()) {
|
|
setGoal(leader->evaluateFollowerNeeds(this));
|
|
} else {
|
|
SenseInfo info;
|
|
|
|
if (disposition == dispositionEnemy
|
|
&& (getAssignment() == NULL
|
|
|| canSenseProtaganist(
|
|
info,
|
|
maxSenseRange)
|
|
|| canSenseProtaganistIndirectly(
|
|
info,
|
|
maxSenseRange))) {
|
|
setGoal(actorGoalAttackEnemy);
|
|
} else {
|
|
setGoal(actorGoalFollowAssignment);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Update the state of this actor.
|
|
|
|
static int32 updatesViaScript = 0;
|
|
|
|
void Actor::updateState(void) {
|
|
// The actor should not be set permanently uninterruptable when
|
|
// the actor does not have a motion task
|
|
assert(isMoving() || actionCounter != maxuint8);
|
|
|
|
GameObject::updateState();
|
|
|
|
if (flags & lobotomized)
|
|
return;
|
|
|
|
// Update the action counter
|
|
if (actionCounter != 0 && actionCounter != maxuint8)
|
|
actionCounter--;
|
|
|
|
if (appearance != NULL
|
|
&& isDead()
|
|
&& isInterruptable()
|
|
&& (moveTask == NULL
|
|
|| moveTask->motionType != MotionTask::motionTypeDie)) {
|
|
int16 deadState = isActionAvailable(actionDead)
|
|
? actionDead
|
|
: isActionAvailable(actionDie)
|
|
? actionDie
|
|
: actionStand;
|
|
|
|
if (currentAnimation != deadState)
|
|
MotionTask::die(*this);
|
|
return;
|
|
}
|
|
|
|
if (!isDead()) {
|
|
if (this == getCenterActor()) return;
|
|
|
|
if (flags & specialAttack) {
|
|
flags &= ~specialAttack;
|
|
|
|
if (currentTarget != NULL) {
|
|
scriptCallFrame scf;
|
|
ObjectID dObj = thisID();
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = currentTarget->thisID();
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_Actor_onSpecialAttack, scf);
|
|
|
|
// If this actor is now deactivated or lobotomized
|
|
// return immediately
|
|
if (isDead() || !isActivated() || (flags & lobotomized))
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (currentGoal) {
|
|
case actorGoalFollowAssignment: {
|
|
ActorAssignment *assign = getAssignment();
|
|
|
|
// Iterate until there is no assignment, or the current
|
|
// assignment is valid
|
|
while (assign != NULL && !assign->isValid()) {
|
|
updatesViaScript++;
|
|
scriptCallFrame scf;
|
|
ObjectID dObj = thisID();
|
|
|
|
delete assign;
|
|
|
|
// Notify the scripts that the assignment has ended
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_Actor_onEndAssignment, scf);
|
|
|
|
// If this actor is now deactivated or lobotomized
|
|
// return immediately
|
|
if (isDead() || !isActivated() || (flags & lobotomized))
|
|
return;
|
|
|
|
// Re-get the assignment
|
|
assign = getAssignment();
|
|
}
|
|
|
|
// If there is no assignment at this point, call the
|
|
// schedule to setup a new assignment.
|
|
if (assign == NULL && schedule != 0) {
|
|
updatesViaScript++;
|
|
assert(curTask == NULL);
|
|
|
|
scriptCallFrame scf;
|
|
|
|
scf.invokedObject = Nothing;
|
|
scf.enactor = Nothing;
|
|
scf.directObject = thisID();
|
|
scf.indirectObject = Nothing;
|
|
scf.value = 0;
|
|
|
|
runScript(schedule, scf);
|
|
|
|
// Re-get the assignment
|
|
assign = getAssignment();
|
|
}
|
|
|
|
// Have the assignment create a new task
|
|
if (assign != NULL && curTask == NULL)
|
|
curTask = assign->createTask();
|
|
}
|
|
break;
|
|
|
|
case actorGoalPreserveSelf:
|
|
|
|
if (leader != NULL || followers != NULL)
|
|
disband();
|
|
|
|
if (curTask == NULL) {
|
|
if ((curTask = newTaskStack(this)) != NULL) {
|
|
Task *task = new GoAwayFromActorTask(
|
|
curTask,
|
|
ActorPropertyTarget(
|
|
disposition == dispositionEnemy
|
|
? actorPropIDPlayerActor
|
|
: actorPropIDEnemy),
|
|
true);
|
|
|
|
if (task != NULL)
|
|
curTask->setTask(task);
|
|
else {
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case actorGoalAttackEnemy:
|
|
|
|
if (curTask == NULL) {
|
|
if ((curTask = newTaskStack(this)) != NULL) {
|
|
uint8 disp = leader != NULL
|
|
? leader->disposition
|
|
: disposition;
|
|
|
|
Task *task = new HuntToKillTask(
|
|
curTask,
|
|
ActorPropertyTarget(
|
|
disp == dispositionEnemy
|
|
? actorPropIDPlayerActor
|
|
: actorPropIDEnemy));
|
|
|
|
if (task != NULL)
|
|
curTask->setTask(task);
|
|
else {
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case actorGoalFollowLeader:
|
|
|
|
assert(isActor(leader));
|
|
assert(followers == NULL);
|
|
|
|
if (curTask == NULL)
|
|
curTask = leader->createFollowerTask(this);
|
|
|
|
break;
|
|
|
|
case actorGoalAvoidEnemies:
|
|
|
|
assert(isActor(leader));
|
|
assert(followers == NULL);
|
|
|
|
if (curTask == NULL) {
|
|
if ((curTask = newTaskStack(this)) != NULL) {
|
|
Task *task = new BandAndAvoidEnemiesTask(curTask);
|
|
|
|
if (task != NULL)
|
|
curTask->setTask(task);
|
|
else {
|
|
delete curTask;
|
|
curTask = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// This routine is used to notify the actor that a task has ended. The
|
|
// actor should handle the situation appropriately
|
|
|
|
void Actor::handleTaskCompletion(TaskResult result) {
|
|
// The task is done, get rid of it
|
|
delete curTask;
|
|
curTask = NULL;
|
|
|
|
switch (currentGoal) {
|
|
case actorGoalFollowAssignment: {
|
|
ActorAssignment *assign = getAssignment();
|
|
|
|
// If we've gotten to this point, there had better be an
|
|
// assignment, or something is amiss
|
|
assert(assign != NULL);
|
|
|
|
// Notify the assignment
|
|
assign->handleTaskCompletion(result);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// This function will cause the actor to react to an offensive act
|
|
|
|
void Actor::handleOffensiveAct(Actor *attacker) {
|
|
ObjectID dObj = thisID();
|
|
scriptCallFrame scf;
|
|
|
|
scf.invokedObject = dObj;
|
|
scf.enactor = dObj;
|
|
scf.directObject = dObj;
|
|
scf.indirectObject = attacker->thisID();
|
|
scf.value = 0;
|
|
|
|
runObjectMethod(dObj, Method_Actor_onAttacked, scf);
|
|
|
|
if (disposition == dispositionFriendly) {
|
|
if (attacker->disposition >= dispositionPlayer) {
|
|
disposition = dispositionEnemy;
|
|
evaluateNeeds();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// This function will cause the actor to react appropriately to taking
|
|
// damage.
|
|
|
|
void Actor::handleDamageTaken(uint8 damage) {
|
|
uint8 combatBehavior = ((ActorProto *)prototype)->combatBehavior;
|
|
|
|
if (combatBehavior == behaviorHungry) return;
|
|
|
|
if (offensiveObject() == this
|
|
&& !isActionAvailable(actionSwingHigh)
|
|
&& !isActionAvailable(actionTwoHandSwingHigh)
|
|
&& !hasEffect(actorNotDefenseless)) {
|
|
flags |= afraid;
|
|
return;
|
|
}
|
|
|
|
if (combatBehavior != behaviorHungry
|
|
&& (flags & temporary)
|
|
&& !hasEffect(actorFear)
|
|
&& !hasEffect(actorRepelUndead)) {
|
|
if (flags & afraid) {
|
|
// Let's give monsters a small chance of regaining their courage
|
|
if ((uint16)g_vm->_rnd->getRandomNumber(65534) <= 0x3fff)
|
|
flags &= ~afraid;
|
|
} else {
|
|
int16 i,
|
|
fellowBandMembers,
|
|
vitality = effectiveStats.vitality;
|
|
uint32 moraleBase = ((int32)damage << 16) / vitality,
|
|
bonus = 0;
|
|
|
|
// Adjustment added by Talin to globally reduce the amount of cowardice
|
|
// in the game. I may reduce it further depending on playtesting.
|
|
moraleBase /= 3;
|
|
|
|
// Adjust morale base according to the combat behavior
|
|
if (combatBehavior == behaviorCowardly)
|
|
moraleBase += moraleBase / 2;
|
|
else if (combatBehavior == behaviorBerserk)
|
|
moraleBase -= moraleBase / 2;
|
|
|
|
// Determine how many fellow band members this actor has.
|
|
if (leader != NULL)
|
|
fellowBandMembers = leader->followers->size();
|
|
else if (followers != NULL)
|
|
fellowBandMembers = followers->size();
|
|
else
|
|
fellowBandMembers = 0;
|
|
|
|
// REM: this calculation can be done via a lookup table
|
|
for (i = 0; i < fellowBandMembers; i++)
|
|
bonus += ((1 << 16) - bonus) >> 4;
|
|
|
|
// Adjust the morale base to acount for the number of fellow band
|
|
// members
|
|
moraleBase -= bonus * moraleBase >> 16;
|
|
|
|
// Test this actor's morale
|
|
if ((uint16)g_vm->_rnd->getRandomNumber(65534) <= moraleBase)
|
|
flags |= afraid;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// This function is called when this actor successfully causes damage
|
|
// to another actor.
|
|
|
|
void Actor::handleSuccessfulStrike(Actor *target, int8 damage) {
|
|
PlayerActorID playerID;
|
|
|
|
if (actorToPlayerID(this, playerID)) {
|
|
PlayerActor *player = getPlayerActorAddress(playerID);
|
|
int16 ratio;
|
|
|
|
// If it's a weak monster, then reduce amount of vitality advanced.
|
|
// If we are twice as vital, then get half the exp's. If we are three times
|
|
// as vital, get 1/3 the exp. etc.
|
|
ratio = clamp(1, getBaseStats()->vitality / target->getBaseStats()->vitality, 4);
|
|
|
|
player->vitalityAdvance(damage / ratio);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// This function is called when this actor successfully kills another
|
|
// actor.
|
|
|
|
void Actor::handleSuccessfulKill(Actor *target) {
|
|
PlayerActorID playerID;
|
|
|
|
if (this != target && actorToPlayerID(this, playerID)) {
|
|
static const char vowels[] = "AEIOU";
|
|
|
|
PlayerActor *player = getPlayerActorAddress(playerID);
|
|
int16 ratio;
|
|
int16 points = target->getBaseStats()->vitality;
|
|
const char *monsterName = target->objName();
|
|
const char *aStr;
|
|
|
|
// If it's a weak monster, then reduce amount of vitality advanced.
|
|
// If we are twice as vital, then get half the exp's. If we are three times
|
|
// as vital, get 1/3 the exp. etc.
|
|
ratio = clamp(1, getBaseStats()->vitality / points, 4);
|
|
|
|
player->vitalityAdvance(points / ratio);
|
|
|
|
aStr = target->getNameIndex() == 0
|
|
? strchr(vowels, toupper(monsterName[0])) == NULL
|
|
? "a "
|
|
: "an "
|
|
: "";
|
|
StatusMsg("%s has killed %s%s.", objName(), aStr, monsterName);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Determine if this actor can block a blow from the specified relative
|
|
// direction with the specified defensive object.
|
|
|
|
bool Actor::canBlockWith(GameObject *defenseObj, Direction relativeDir) {
|
|
assert(defenseObj->proto()->canBlock());
|
|
assert(relativeDir < 8);
|
|
|
|
// Assuming that the actor may increment or decrement their facing
|
|
// to block, these masks represent the possible relative facings
|
|
// based upon the current relative facing
|
|
static uint8 dirMaskArray[8] = {
|
|
0x83, // 10000011
|
|
0x07, // 00000111
|
|
0x0E, // 00001110
|
|
0x1C, // 00011100
|
|
0x38, // 00111000
|
|
0x70, // 01110000
|
|
0xE0, // 11100000
|
|
0xC1 // 11000001
|
|
};
|
|
|
|
return (defenseObj->proto()->defenseDirMask()
|
|
& dirMaskArray[relativeDir])
|
|
!= 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// This function is called to notify this actor of an impending attack
|
|
|
|
void Actor::evaluateMeleeAttack(Actor *attacker) {
|
|
if (isInterruptable() && !isDead()) {
|
|
Direction relativeDir;
|
|
GameObject *defenseObj,
|
|
*primary,
|
|
*secondary;
|
|
bool canBlockWithPrimary;
|
|
|
|
// Compute the attacker's direction relative to this actor's
|
|
// facing
|
|
relativeDir = ((attacker->_data.location - _data.location).quickDir()
|
|
- currentFacing) & 0x7;
|
|
|
|
// Get pointers to this actors primary and secondary defensive
|
|
// objects
|
|
defensiveObject(&primary, &secondary);
|
|
|
|
canBlockWithPrimary = primary != NULL
|
|
&& canBlockWith(primary, relativeDir);
|
|
|
|
if (canBlockWithPrimary) {
|
|
bool canBlockWithSecondary;
|
|
|
|
canBlockWithSecondary = secondary != NULL
|
|
&& canBlockWith(
|
|
secondary,
|
|
relativeDir);
|
|
|
|
if (canBlockWithSecondary) {
|
|
// If we can block with either primary or secondary
|
|
// there is a 25% chance of using the secondary
|
|
defenseObj = (g_vm->_rnd->getRandomNumber(3) != 0) ? primary : secondary;
|
|
} else {
|
|
// The primary defensive object will be used
|
|
defenseObj = primary;
|
|
}
|
|
} else
|
|
defenseObj = NULL;
|
|
|
|
if (defenseObj != NULL) {
|
|
// Start a defensive motion
|
|
defenseObj->proto()->initiateDefense(
|
|
defenseObj->thisID(),
|
|
thisID(),
|
|
attacker->thisID());
|
|
} else {
|
|
if (isActionAvailable(actionJumpUp))
|
|
MotionTask::dodge(*this, *attacker);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Cause this actor to accept another actor as his leader. If the actor
|
|
// has followers, this will band those followers to the new leader as
|
|
// well.
|
|
|
|
void Actor::bandWith(Actor *newLeader) {
|
|
assert(leader == NULL);
|
|
|
|
// If the actor we're banding with is not the leader, then band
|
|
// with his leader
|
|
if (newLeader->leader != NULL) {
|
|
newLeader = newLeader->leader;
|
|
assert(newLeader->leader == NULL);
|
|
}
|
|
|
|
// If this actor himself does not have followers then its really
|
|
// simple, otherwise we need to band all of this actor's followers
|
|
// with the new leader.
|
|
if (followers == NULL) {
|
|
if (newLeader->addFollower(this)) leader = newLeader;
|
|
} else {
|
|
int16 i,
|
|
oldFollowerCount = followers->size();
|
|
Actor **oldFollowers = new Actor * [oldFollowerCount];
|
|
|
|
if (oldFollowers != NULL) {
|
|
// Copy the list followers
|
|
for (i = 0; i < oldFollowerCount; i++) {
|
|
oldFollowers[i] = (*followers)[i];
|
|
assert(oldFollowers[i]->leader == this);
|
|
}
|
|
|
|
// Disband all of the old followers
|
|
for (i = 0; i < oldFollowerCount; i++)
|
|
oldFollowers[i]->disband();
|
|
|
|
assert(followers == NULL);
|
|
|
|
// Add this actor and all of the old followers to the new
|
|
// leader's followers.
|
|
if (newLeader->addFollower(this)) {
|
|
leader = newLeader;
|
|
|
|
for (i = 0; i < oldFollowerCount; i++)
|
|
oldFollowers[i]->bandWith(newLeader);
|
|
}
|
|
|
|
delete [] oldFollowers;
|
|
}
|
|
}
|
|
|
|
evaluateNeeds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Simply causes this actor to be removed from his current band.
|
|
|
|
void Actor::disband(void) {
|
|
if (leader != NULL) {
|
|
leader->removeFollower(this);
|
|
leader = NULL;
|
|
|
|
evaluateNeeds();
|
|
} else if (followers != NULL) {
|
|
int16 i;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
Actor *follower = (*followers)[i];
|
|
|
|
follower->leader = NULL;
|
|
follower->evaluateNeeds();
|
|
}
|
|
|
|
delete followers;
|
|
followers = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add the specified actor to the list of this actor's followers.
|
|
|
|
bool Actor::addFollower(Actor *newBandMember) {
|
|
// The new band member should not be a leader of another band or
|
|
// a follower of another leader
|
|
assert(newBandMember->leader == NULL);
|
|
assert(newBandMember->followers == NULL);
|
|
|
|
// Allocate a new band, if needed
|
|
if (followers == NULL && (followers = new Band(this)) == NULL)
|
|
return false;
|
|
|
|
return followers->add(newBandMember);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Remove the specified actor from this actor's list of followers.
|
|
|
|
void Actor::removeFollower(Actor *bandMember) {
|
|
assert(bandMember->leader == this);
|
|
assert(followers != NULL);
|
|
|
|
int16 i;
|
|
|
|
followers->remove(bandMember);
|
|
if (followers->size() == 0) {
|
|
delete followers;
|
|
followers = NULL;
|
|
} else {
|
|
uint16 moraleBonus = 0;
|
|
|
|
for (i = 0; i < followers->size(); i++)
|
|
moraleBonus += ((1 << 16) - moraleBonus) >> 4;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
Actor *follower = (*followers)[i];
|
|
ActorProto *proto = (ActorProto *)follower->prototype;
|
|
uint8 combatBehavior = proto->combatBehavior;
|
|
|
|
if (follower->currentGoal == actorGoalAttackEnemy
|
|
&& combatBehavior != behaviorHungry) {
|
|
uint32 moraleBase;
|
|
|
|
moraleBase = combatBehavior == behaviorCowardly
|
|
? (1 << 16) / 4
|
|
: combatBehavior == behaviorSmart
|
|
? (1 << 16) / 8
|
|
: (1 << 16) / 16;
|
|
|
|
moraleBase -= moraleBase * moraleBonus >> 16;
|
|
|
|
if ((uint16)g_vm->_rnd->getRandomNumber(65534) <= moraleBase)
|
|
follower->flags |= afraid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Create a task for a follower of this actor. This is called when a
|
|
// follower has no task.
|
|
|
|
TaskStack *Actor::createFollowerTask(Actor *bandMember) {
|
|
assert(bandMember->leader == this);
|
|
|
|
TaskStack *ts = NULL;
|
|
|
|
if ((ts = newTaskStack(bandMember)) != NULL) {
|
|
Task *task = new BandTask(ts);
|
|
|
|
if (task != NULL)
|
|
ts->setTask(task);
|
|
else {
|
|
delete ts;
|
|
ts = NULL;
|
|
}
|
|
}
|
|
|
|
return ts;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Evaluate a follower's needs and give him an approriate goal.
|
|
|
|
uint8 Actor::evaluateFollowerNeeds(Actor *follower) {
|
|
assert(follower->leader == this);
|
|
|
|
SenseInfo info;
|
|
|
|
if ((disposition == dispositionEnemy
|
|
&& follower->canSenseProtaganist(info, maxSenseRange))
|
|
|| (disposition >= dispositionPlayer
|
|
&& follower->canSenseActorProperty(
|
|
info,
|
|
maxSenseRange,
|
|
actorPropIDEnemy)))
|
|
return actorGoalAttackEnemy;
|
|
|
|
return actorGoalFollowLeader;
|
|
}
|
|
|
|
// Returns 0 if not moving, 1 if path being calculated,
|
|
// 2 if path being followed.
|
|
bool Actor::pathFindState(void) {
|
|
if (moveTask == NULL) return 0;
|
|
if (moveTask->pathFindTask) return 1;
|
|
return 2;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Add knowledge package to actor
|
|
|
|
bool Actor::addKnowledge(uint16 kID) {
|
|
for (int i = 0; i < ARRAYSIZE(knowledge); i++) {
|
|
if (knowledge[i] == 0) {
|
|
knowledge[i] = kID;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Remove knowledge package from actor
|
|
|
|
bool Actor::removeKnowledge(uint16 kID) {
|
|
for (int i = 0; i < ARRAYSIZE(knowledge); i++) {
|
|
if (knowledge[i] == kID) {
|
|
knowledge[i] = 0;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Remove all knowledge package from actor
|
|
|
|
void Actor::clearKnowledge(void) {
|
|
for (int i = 0; i < ARRAYSIZE(knowledge); i++) {
|
|
knowledge[i] = 0;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Called to evaluate actor knowledge
|
|
|
|
void Actor::useKnowledge(scriptCallFrame &scf) {
|
|
uint16 bestResponsePri = 0,
|
|
bestResponseClass = 0,
|
|
bestResponseCode = 0;
|
|
|
|
// First, search for the class with the best response
|
|
|
|
for (int i = 0; i < ARRAYSIZE(knowledge); i++) {
|
|
if (knowledge[i]) {
|
|
scriptResult res;
|
|
|
|
// Run the script to eval the response of this
|
|
// knowledge package
|
|
|
|
res = runMethod(knowledge[i],
|
|
builtinAbstract,
|
|
0,
|
|
Method_KnowledgePackage_evalResponse,
|
|
scf);
|
|
|
|
// If script ran OK, then look at result
|
|
|
|
if (res == scriptResultFinished) {
|
|
// break up return code into priority and
|
|
// response code
|
|
|
|
int16 pri = scf.returnVal >> 8,
|
|
response = scf.returnVal & 0xff;
|
|
|
|
if (pri > 0) {
|
|
// Add a bit of jitter to response
|
|
|
|
pri += 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 != NULL) {
|
|
int i;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
if ((*followers)[i]->canSenseProtaganist(info, range))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if any of this actor's followers can
|
|
// sense a specific actor within a specified range
|
|
|
|
bool Actor::canSenseSpecificActorIndirectly(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
Actor *a) {
|
|
if (followers != NULL) {
|
|
int i;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
if ((*followers)[i]->canSenseSpecificActor(info, range, a))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if any of this actor's followers can
|
|
// sense a specific object within a specified range
|
|
|
|
bool Actor::canSenseSpecificObjectIndirectly(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
ObjectID obj) {
|
|
if (followers != NULL) {
|
|
int i;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
if ((*followers)[i]->canSenseSpecificObject(info, range, obj))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if any of this actor's followers can
|
|
// sense an actor with a specified property within a specified range
|
|
|
|
bool Actor::canSenseActorPropertyIndirectly(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
ActorPropertyID prop) {
|
|
if (followers != NULL) {
|
|
int i;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
if ((*followers)[i]->canSenseActorProperty(info, range, prop))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Polling function to determine if any of this actor's followers can
|
|
// sense an object with a specified property within a specified range
|
|
|
|
bool Actor::canSenseObjectPropertyIndirectly(
|
|
SenseInfo &info,
|
|
int16 range,
|
|
ObjectPropertyID prop) {
|
|
if (followers != NULL) {
|
|
int i;
|
|
|
|
for (i = 0; i < followers->size(); i++) {
|
|
if ((*followers)[i]->canSenseObjectProperty(info, range, prop))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Mana check - spell casting uses this to check whether an actor
|
|
// has enough mana to cast a spell & to remove that mana if
|
|
// it's there
|
|
|
|
#define NO_MONSTER_MANA 1
|
|
|
|
bool Actor::takeMana(ActorManaID i, int8 dMana) {
|
|
#if NO_MONSTER_MANA
|
|
if (!isPlayerActor(this))
|
|
return true;
|
|
#endif
|
|
assert(i >= manaIDRed && i <= manaIDViolet);
|
|
if ((&effectiveStats.redMana)[i] < dMana)
|
|
return false;
|
|
(&effectiveStats.redMana)[i] -= dMana;
|
|
updateIndicators();
|
|
return true;
|
|
}
|
|
|
|
bool Actor::hasMana(ActorManaID i, int8 dMana) {
|
|
#if NO_MONSTER_MANA
|
|
if (!isPlayerActor(this))
|
|
return true;
|
|
#endif
|
|
assert(i >= manaIDRed && i <= manaIDViolet);
|
|
if ((&effectiveStats.redMana)[i] < dMana)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Saving throw funcion
|
|
|
|
bool Actor::makeSavingThrow(void) {
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Determine if the actors are currently initialized
|
|
|
|
bool areActorsInitialized(void) {
|
|
return g_vm->_actorList.size() > 0;
|
|
}
|
|
|
|
int16 GetRandomBetween(int start, int end) {
|
|
return g_vm->_rnd->getRandomNumberRng(start, end);
|
|
}
|
|
|
|
void updateActorStates(void) {
|
|
if (actorStatesPaused) return;
|
|
|
|
static const int32 evalRate = 8;
|
|
static const int32 evalRateMask = evalRate - 1;
|
|
static int32 baseActorIndex = evalRateMask;
|
|
|
|
int32 actorIndex;
|
|
|
|
actorIndex = baseActorIndex = (baseActorIndex + 1) & evalRateMask;
|
|
while (actorIndex < kActorCount) {
|
|
Actor *a = g_vm->_actorList[actorIndex];
|
|
|
|
if (isWorld(a->IDParent()))
|
|
a->evaluateNeeds();
|
|
|
|
actorIndex += evalRate;
|
|
}
|
|
|
|
updatesViaScript = 0;
|
|
for (actorIndex = 0; actorIndex < kActorCount; actorIndex++) {
|
|
Actor *a = g_vm->_actorList[actorIndex];
|
|
|
|
if (isWorld(a->IDParent()) && a->isActivated())
|
|
a->updateState();
|
|
}
|
|
|
|
|
|
//WriteStatusF((useLine%10)+10,"%d actor updates by script",updatesViaScript);
|
|
//WriteStatusF(((useLine+1)%10)+10," ");
|
|
//useLine++;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
|
|
void pauseActorStates(void) {
|
|
actorStatesPaused = true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
|
|
void resumeActorStates(void) {
|
|
actorStatesPaused = false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
|
|
void setCombatBehavior(bool enabled) {
|
|
PlayerActor *player;
|
|
LivingPlayerActorIterator iter;
|
|
|
|
combatBehaviorEnabled = enabled;
|
|
|
|
for (player = iter.first(); player != NULL; player = iter.next())
|
|
player->getActor()->evaluateNeeds();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Initialize the actor list
|
|
|
|
ResourceActor::ResourceActor(Common::SeekableReadStream *stream) : ResourceGameObject(stream) {
|
|
faction = stream->readByte();
|
|
colorScheme = stream->readByte();
|
|
appearanceID = stream->readSint32BE();
|
|
attitude = stream->readSByte();
|
|
mood = stream->readSByte();
|
|
disposition = stream->readByte();
|
|
currentFacing = stream->readByte();
|
|
tetherLocU = stream->readSint16LE();
|
|
tetherLocV = stream->readSint16LE();
|
|
tetherDist = stream->readSint16LE();
|
|
leftHandObject = stream->readUint16LE();
|
|
rightHandObject = stream->readUint16LE();
|
|
for (int i = 0; i < 16; ++i) {
|
|
knowledge[i] = stream->readUint16LE();
|
|
}
|
|
schedule = stream->readUint16LE();
|
|
for (int i = 0; i < 18; ++i) { // padding bytes = not neccessary?
|
|
reserved[i] = stream->readByte();
|
|
}
|
|
}
|
|
|
|
void initActors(void) {
|
|
// Load actors
|
|
int i, resourceActorCount;
|
|
Common::Array<ResourceActor> resourceActorList;
|
|
Common::SeekableReadStream *stream;
|
|
const int resourceActorSize = 91; // size of the packed struct
|
|
|
|
resourceActorCount = listRes->size(actorListID)
|
|
/ resourceActorSize;
|
|
|
|
if (resourceActorCount < 1)
|
|
error("Unable to load Actors");
|
|
|
|
if ((stream = loadResourceToStream(listRes, actorListID, "res actor list")) == nullptr)
|
|
error("Unable to load Actors");
|
|
|
|
// Read the resource actors
|
|
for (int k = 0; k < resourceActorCount; ++k) {
|
|
ResourceActor res(stream);
|
|
resourceActorList.push_back(res);
|
|
}
|
|
|
|
delete stream;
|
|
|
|
for (i = 0; i < resourceActorCount; i++) {
|
|
// Initialize the actors with the resource data
|
|
Actor *a = new Actor(resourceActorList[i]);
|
|
|
|
a->_index = i + ActorBaseID;
|
|
|
|
g_vm->_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->_actorList.push_back(a);
|
|
}
|
|
|
|
g_vm->_actorList[0]->disposition = dispositionPlayer + 0;
|
|
g_vm->_actorList[1]->disposition = dispositionPlayer + 1;
|
|
g_vm->_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->_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->_actorList.push_back(a);
|
|
}
|
|
|
|
for (int i = 0; i < kActorCount; ++i) {
|
|
Actor *a = g_vm->_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(void) {
|
|
if (g_vm->_actorList.size() > 0) {
|
|
for (int i = 0; i < kActorCount; i++)
|
|
delete g_vm->_actorList[i];
|
|
|
|
g_vm->_actorList.clear();
|
|
}
|
|
}
|
|
|
|
/* ============================================================================ *
|
|
Actor faction tallies
|
|
* ============================================================================ */
|
|
|
|
int16 AddFactionTally(int faction, enum factionTallyTypes act, int amt) {
|
|
#if DEBUG
|
|
if (faction >= maxFactions)
|
|
error("Scripter: Tell Talin to increase maxFactions!\n");
|
|
assert(faction >= 0);
|
|
assert(act >= 0);
|
|
assert(act < factionNumColumns);
|
|
#endif
|
|
/*
|
|
// If faction attitude counts get to big then down-scale all of them
|
|
// in proportion.
|
|
if ( factionTable[faction][act] + amt > maxint16 )
|
|
{
|
|
for (int i = 0; i < factionNumColumns; i++)
|
|
factionTable[faction][i] >>= 1;
|
|
}
|
|
|
|
// Otherwise, if it doesn;t underflow, then add it in.
|
|
if ( factionTable[faction][act] + amt > minint16 )
|
|
{
|
|
factionTable[faction][act] += amt;
|
|
}
|
|
*/
|
|
factionTable[faction][act] = clamp(minint16,
|
|
factionTable[faction][act] + amt,
|
|
maxint16);
|
|
|
|
return factionTable[faction][act];
|
|
}
|
|
|
|
// Get the attitude a particular faction has for a char.
|
|
int16 GetFactionTally(int faction, enum factionTallyTypes act) {
|
|
#if DEBUG
|
|
if (faction >= maxFactions)
|
|
error("Scripter: Tell Talin to increase maxFactions!\n");
|
|
assert(faction >= 0);
|
|
assert(act >= 0);
|
|
assert(act < factionNumColumns);
|
|
#endif
|
|
|
|
return factionTable[faction][act];
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Initialize the faction tally table
|
|
|
|
void initFactionTallies(void) {
|
|
memset(&factionTable, 0, sizeof(factionTable));
|
|
}
|
|
|
|
void saveFactionTallies(Common::OutSaveFile *outS) {
|
|
debugC(2, kDebugSaveload, "Saving Faction Tallies");
|
|
|
|
outS->write("FACT", 4);
|
|
CHUNK_BEGIN;
|
|
for (int i = 0; i < maxFactions; ++i) {
|
|
for (int j = 0; j < factionNumColumns; ++j)
|
|
out->writeSint16LE(factionTable[i][j]);
|
|
}
|
|
CHUNK_END;
|
|
}
|
|
|
|
void loadFactionTallies(Common::InSaveFile *in) {
|
|
debugC(2, kDebugSaveload, "Loading Faction Tallies");
|
|
|
|
for (int i = 0; i < maxFactions; ++i) {
|
|
for (int j = 0; j < factionNumColumns; ++j)
|
|
factionTable[i][j] = in->readSint16LE();
|
|
}
|
|
}
|
|
|
|
}
|