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

1124 lines
31 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* aint32 with this program; if not, write to the Free Software
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "saga2/saga2.h"
#include "saga2/detection.h"
#include "saga2/intrface.h"
#include "saga2/contain.h"
#include "saga2/task.h"
#include "saga2/motion.h"
#include "saga2/transit.h"
#include "saga2/localize.h"
namespace Saga2 {
extern bool allPlayerActorsDead;
extern ObjectID viewCenterObject; // ID of object which the
// camera tracks
extern ReadyContainerView *TrioCviews[kNumViews];
extern ReadyContainerView *indivCviewTop, *indivCviewBot;
extern ContainerNode *indivReadyNode;
void updateMainDisplay();
TilePoint selectNearbySite(
ObjectID worldID,
const TilePoint &startingCoords,
int32 minDist,
int32 maxDist,
bool offScreenOnly);
bool checkPath(
ObjectID worldID,
uint8 height,
const TilePoint &startingPt,
const TilePoint &destPt);
/* ===================================================================== *
Globals
* ===================================================================== */
static PlayerActorID centerActor; // Index of the current center
// actor
bool brotherBandingEnabled;
/* ===================================================================== *
Methods
* ===================================================================== */
//-----------------------------------------------------------------------
// Resolve the banding state of this actor
void PlayerActor::resolveBanding() {
Actor *follower = getActor();
Actor *centerActor_ = getCenterActor();
// if already following, tell the actor to cease and desist
if (follower->_leader) {
follower->disband();
}
// do not allow actor to follow it's self
if (brotherBandingEnabled
&& isBanded()
&& follower != centerActor_) {
// create a new follow assignment
follower->bandWith(centerActor_);
}
}
//-----------------------------------------------------------------------
// Re-evaluate the portrait type for this player actor
void PlayerActor::recalcPortraitType() {
PortraitType pType;
Actor *a = getActor();
ActorAttributes &stats = getBaseStats();
if (a->isDead())
pType = kPortraitDead;
else if (a->_enchantmentFlags & (1 << actorAsleep))
pType = kPortraitAsleep;
else if (stats.vitality >= a->_effectiveStats.vitality * 3)
pType = kPortraitWounded;
else if (a->_enchantmentFlags & ((1 << actorDiseased) | (1 << actorPoisoned)))
pType = kPortraitSick;
else if (stats.vitality * 2 > a->_effectiveStats.vitality * 3)
pType = kPortraitOuch;
else if (a->_enchantmentFlags & ((1 << actorParalyzed) | (1 << actorFear) | (1 << actorBlind)))
pType = kPortraitConfused;
else if (isAggressive())
pType = kPortraitAngry;
else
pType = kPortraitNormal;
if (pType != portraitType)
updateBrotherPortrait(getPlayerActorID(this), portraitType = pType);
}
void PlayerActor::recoveryUpdate() { // change name to recovery update
manaUpdate();
AttribUpdate();
}
void PlayerActor::AttribUpdate() {
// get the actor pointer for this character
Actor *actor = getActor();
// get the effective stats for this player actor
ActorAttributes *effStats = &actor->_effectiveStats;
for (int16 i = 0; i < numSkills; i++) {
// go through each skill and update as needed
stdAttribUpdate(effStats->skill(i),
baseStats.skill(i),
i);
}
}
void PlayerActor::stdAttribUpdate(uint8 &stat, uint8 baseStat, int16 index) {
// first find out if this actor is wounded
if (stat < baseStat) {
// whole vitality number goes here
int16 recover;
int16 fractionRecover;
// get the whole number first
recover = attribPointsPerUpdate / attribPointsPerValue;
// get the fraction
fractionRecover = attribPointsPerUpdate % attribPointsPerValue;
// if there is an overrun
if (attribRecPools[index] + fractionRecover > attribPointsPerValue) {
// add the overrun to the whole number
recover++;
attribRecPools[index] = (attribRecPools[index] + fractionRecover) - attribPointsPerValue;
} else {
attribRecPools[index] += fractionRecover;
}
if (stat + recover >= baseStat) {
stat = baseStat;
} else {
stat += recover;
}
}
}
void PlayerActor::manaUpdate() {
const int numManas = 6;
const int minMana = 0;
// get the actor pointer for this character
Actor *actor = getActor();
// get indirections for each of the effective mana types
int16 *effectiveMana[numManas] = { &actor->_effectiveStats.redMana,
&actor->_effectiveStats.orangeMana,
&actor->_effectiveStats.yellowMana,
&actor->_effectiveStats.greenMana,
&actor->_effectiveStats.blueMana,
&actor->_effectiveStats.violetMana
};
// get indirections for each of the base mana types
int16 *baseMana[numManas] = { &baseStats.redMana,
&baseStats.orangeMana,
&baseStats.yellowMana,
&baseStats.greenMana,
&baseStats.blueMana,
&baseStats.violetMana
};
uint16 diff;
// do each mana type
for (int16 i = 0; i < numManas; i++) {
int levelBump;
int recRate;
// if baseMana has gone to zero, force it to 1
if (*baseMana[i] <= 0) *baseMana[i] = 1;
// Make mana harder to increase as it goes up.
if (*baseMana[i] >= 100) levelBump = 40;
else if (*baseMana[i] >= 40) levelBump = 20;
else levelBump = 10;
// is their current mana less then their maximum
if (*effectiveMana[i] < *baseMana[i]) {
diff = *effectiveMana[i];
recRate = 1;
if (*baseMana[i] >= 120) recRate = 3;
else if (*baseMana[i] >= 80) recRate = 2;
else if (*baseMana[i] >= 40) {
// This effectively causes recRate to be 1.5, i.e.
// hald of the time its 1 and the other half its 2.
if (*effectiveMana[i] % 3 == 0) recRate = 2;
}
// recover mana at specified rate
*effectiveMana[i] = clamp(minMana,
*effectiveMana[i] += recRate,
*baseMana[i]);
// get the difference between the manas
diff = *effectiveMana[i] - diff;
// find out if we're recovering from below one third
if (*effectiveMana[i] < *baseMana[i] / 3) {
// add the diff
// Deleted at request of Client.
// manaMemory[i] -= diff;
} else {
manaMemory[i] += diff;
}
// if we bumped passed the ( +/- ) levelBump mark
// decrement the base mana
*baseMana[i] += (manaMemory[i] / levelBump);
// get the fraction back to memory
manaMemory[i] = manaMemory[i] % levelBump;
//WriteStatusF( 4, " mana: %d", *effectiveMana[i] );
}
}
}
// get the skill advancement number ( could be zero )
void PlayerActor::skillAdvance(SkillProto *proto,
uint8 points,
uint8 useMult) { // useMult defaulted to 1
// get the skill level for the skill passed ( i.e. 1-100 )
uint8 skillLevel = getSkillLevel(proto, true); // true, use base stats
// get the stat index in question
uint8 stat = getStatIndex(proto);
// get the percentile chance of advancing
uint8 advanceChance = ActorAttributes::skillBasePercent - skillLevel;
// call the main body of code
skillAdvance(stat, advanceChance, points, useMult);
}
// get the skill advancement number ( could be zero )
void PlayerActor::skillAdvance(ActorSkillID stat,
uint8 points,
uint8 useMult) { // useMult defaulted to 1
// get the skill level for the skill passed ( i.e. 1-100 )
uint8 skillLevel = clamp(0, baseStats.skill(stat), ActorAttributes::skillMaxLevel);
// get the percentile chance of advancing
uint8 advanceChance = ActorAttributes::skillBasePercent - skillLevel;
// call the main body of code
skillAdvance(stat, advanceChance, points, useMult);
//WriteStatusF( 1, "adchan: %d, skillev: %d ", advanceChance, skillLevel );
}
void PlayerActor::skillAdvance(uint8 stat,
uint8 advanceChance,
uint8 points,
uint8 useMult) {
// roll percentile dice
if (g_vm->_rnd->getRandomNumber(99) < advanceChance) {
uint8 increase;
int16 oldValue = baseStats.skill(stat) / ActorAttributes::skillFracPointsPerLevel;
// success, now apply the multiplyer
attribMemPools[stat] += points * useMult;
// get the amout of whole increase points
increase = attribMemPools[stat] / ActorAttributes::skillFracPointsPerLevel;
// now set the pool with the fraction
attribMemPools[stat] =
attribMemPools[stat]
- increase
* ActorAttributes::skillFracPointsPerLevel;
// now apply changes to the baseStats
baseStats.skill(stat) = clamp(
0,
baseStats.skill(stat) += increase,
ActorAttributes::skillMaxLevel);
if (baseStats.skill(stat) / ActorAttributes::skillFracPointsPerLevel != oldValue) {
static const char *skillNames[] = {
ARCHERY_SKILL,
SWORD_SKILL,
SHIELD_SKILL,
BLUDGEON_SKILL,
DEAD_SKILL,
SPELL_SKILL,
DEAD2_SKILL,
AGILITY_SKILL,
BRAWN_SKILL
};
StatusMsg(SKILL_STATUS, getActor()->objName(), skillNames[stat]);
}
//WriteStatusF( 6, "frac: %d inc: %d, base: %d", attribMemPools[stat], increase, baseStats.allSkills[stat] );
}
}
void PlayerActor::vitalityAdvance(uint8 points) {
while (points-- > 0) {
if ((int16)g_vm->_rnd->getRandomNumber(ActorAttributes::vitalityLimit - 1) > baseStats.vitality) {
if (++vitalityMemory >= vitalityLevelBump) {
vitalityMemory -= vitalityLevelBump;
baseStats.vitality++;
StatusMsg(VITALITY_STATUS, getActor()->objName());
}
}
}
assert(baseStats.vitality < ActorAttributes::vitalityLimit);
}
// this function will return a value of 0 - 4 to indicate
// relative proficiency in requested skill
int8 PlayerActor::getSkillLevel(SkillProto *skill, bool base) { // basestats defaulted to false
// get the id for this skill
SpellID skillID = skill->getSpellID();
// index
uint16 stat;
// get the current stats for this player actor
ActorAttributes *effStats = getEffStats();
// check to see if this is a special case
if (skillID == skillVitality) {
return effStats->vitality / ActorAttributes::skillFracPointsPerLevel;
} else if (skillID == skillCartography) {
// cartography has no levels
return 0;
}
// get the index
stat = getStatIndex(skill);
// stat stored as skillLevel *
// skillFracPointsPerLevel +
// skillCurrentFracPoints
if (base) {
return clamp(0,
baseStats.skill(stat) / ActorAttributes::skillFracPointsPerLevel,
ActorAttributes::skillLevels - 1);
} else {
return clamp(0,
effStats->skill(stat) / ActorAttributes::skillFracPointsPerLevel,
ActorAttributes::skillLevels - 1);
}
}
uint8 PlayerActor::getStatIndex(SkillProto *proto) {
// get the id for this skill
SpellID skillID = proto->getSpellID();
uint16 stat;
// now map the id gotten from spellid to the
// attributeskilll enum for the allSkills array
switch (skillID) {
case skillPickpocket:
stat = skillIDPilfer;
break;
case skillSeeHidden:
stat = skillIDSpotHidden;
break;
case skillLockPick:
stat = skillIDLockpick;
break;
case skillFirstAid:
stat = skillIDFirstAid;
break;
case skillArchery:
stat = skillIDArchery;
break;
case skillSwordcraft:
stat = skillIDSwordcraft;
break;
case skillShieldcraft:
stat = skillIDShieldcraft;
break;
case skillBludgeon:
stat = skillIDBludgeon;
break;
case skillThrowing:
stat = skillIDThrowing;
break;
case skillSpellcraft:
stat = skillIDSpellcraft;
break;
case skillStealth:
stat = skillIDStealth;
break;
case skillAgility:
stat = skillIDAgility;
break;
case skillBrawn:
stat = skillIDBrawn;
break;
default:
error("Invalid case detected: Player.cpp-getSkillLevel() = %d", skillID);
}
// make sure we have a good index
if (stat >= numSkills) {
error("Invalid array index detected: Player.cpp-getSkillLevel()");
}
// return the index
return stat;
}
ActorAttributes *PlayerActor::getEffStats() {
// get the actor pointer for this character
Actor *actor = getActor();
// get the effective stats for this player actor
ActorAttributes *effStats = &actor->_effectiveStats;
// valid?
assert(effStats);
// return current stats for this player actor
return effStats;
}
//-----------------------------------------------------------------------
// Notify the user of attack if necessary
void PlayerActor::handleAttacked() {
if (!notifiedOfAttack) {
StatusMsg(ATTACK_STATUS, getActor()->objName());
notifiedOfAttack = true;
}
}
/* ===================================================================== *
Functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Return a pointer to a PlayerActor given it's ID
PlayerActor *getPlayerActorAddress(PlayerActorID id) {
assert(id >= 0 && id < (int)g_vm->_playerList.size());
return g_vm->_playerList[id];
}
//-----------------------------------------------------------------------
// Return a PlayerActor ID given it's address
PlayerActorID getPlayerActorID(PlayerActor *p) {
for (int i = 0; i < (int)g_vm->_playerList.size(); ++i) {
if (g_vm->_playerList[i] == p)
return i;
}
return -1;
}
//-----------------------------------------------------------------------
// Return a pointer the center actor's Actor structure
Actor *getCenterActor() {
return g_vm->_playerList[centerActor]->getActor();
}
//-----------------------------------------------------------------------
// Return the center actor's object ID
ObjectID getCenterActorID() {
return ActorBaseID + centerActor;
}
//-----------------------------------------------------------------------
// Return the center actor's player actor ID
PlayerActorID getCenterActorPlayerID() {
return centerActor;
}
//-----------------------------------------------------------------------
// Set a new center actor based upon a PlayerActor ID
void setCenterActor(PlayerActorID newCenter) {
extern void setEnchantmentDisplay();
assert(newCenter < kPlayerActors);
Actor *a = g_vm->_playerList[newCenter]->getActor();
PlayerActorIterator iter;
PlayerActor *player;
// If this actor is dead return immediately
if (a->isDead()) return;
// Take previous center actor out of fight stance
getCenterActor()->setFightStance(false);
// get rid of any following assignments the center actor might have
if (a->_leader) {
a->disband();
}
centerActor = newCenter;
viewCenterObject = g_vm->_playerList[centerActor]->getActorID();
indivReadyNode->changeOwner(newCenter);
g_vm->_cnm->setPlayerNum(newCenter);
setEnchantmentDisplay();
if (a->_curTask != nullptr) {
a->_curTask->abortTask();
delete a->_curTask;
a->_curTask = nullptr;
}
// Set the new centers fight stance based upon his aggression state
a->setFightStance(g_vm->_playerList[newCenter]->isAggressive());
// band actors to new center if banding button set
for (player = iter.first(); player != nullptr; player = iter.next()) {
player->resolveBanding();
}
// clear the last center actor's button state
updateBrotherRadioButtons(newCenter);
}
//-----------------------------------------------------------------------
// Set a new center actor based upon an Actor address
void setCenterActor(Actor *newCenter) {
assert(newCenter->_disposition >= dispositionPlayer);
setCenterActor(newCenter->_disposition - dispositionPlayer);
}
//-----------------------------------------------------------------------
// Set a new center actor based upon a PlayerActor address
void setCenterActor(PlayerActor *newCenter) {
assert(getPlayerActorID(newCenter) >= 0);
setCenterActor(getPlayerActorID(newCenter));
}
//-----------------------------------------------------------------------
// Return the coordinates of the current center actor
TilePoint centerActorCoords() {
Actor *a;
a = g_vm->_playerList[centerActor]->getActor();
return a->getLocation();
}
//-----------------------------------------------------------------------
// Set or clear a player's aggressive state
void setAggression(PlayerActorID player, bool aggression) {
assert(player >= 0 && player < kPlayerActors);
Actor *a = g_vm->_playerList[player]->getActor();
if (a->isDead()) return;
if (aggression)
g_vm->_playerList[player]->setAggression();
else
g_vm->_playerList[player]->clearAggression();
if (player == centerActor)
a->setFightStance(aggression);
a->evaluateNeeds();
updateBrotherAggressionButton(player, aggression);
}
//-----------------------------------------------------------------------
// Determine if player actor is in an aggressive state
bool isAggressive(PlayerActorID player) {
assert(player >= 0 && player < kPlayerActors);
return g_vm->_playerList[player]->isAggressive();
}
//-----------------------------------------------------------------------
// Adjust the player actors aggression setting based upon their
// proximity to enemies
void autoAdjustAggression() {
PlayerActorID i;
// Iterate through all player actors
for (i = 0; i < kPlayerActors; i++) {
if (i == centerActor || isBanded(i)) {
bool enemiesPresent = false;
Actor *actor = g_vm->_playerList[i]->getActor();
if (actor->getStats()->vitality >= kMinAutoAggressionVitality) {
GameObject *obj = nullptr;
ActiveRegion *activeReg = getActiveRegion(i);
TileRegion region = activeReg->getRegion();
GameWorld *world = activeReg->getWorld();
RegionalObjectIterator iter(world, region.min, region.max);
// Iterate through the objects in this player actor's
// active region to determine if their are enemy actor's
// in the vicinity.
for (iter.first(&obj); obj != nullptr; iter.next(&obj)) {
Actor *a;
if (!isActor(obj)) continue;
a = (Actor *)obj;
if (a->_disposition == dispositionEnemy) {
enemiesPresent = true;
break;
}
}
}
// Set this player actor's aggression
setAggression(i, enemiesPresent);
}
}
}
//-----------------------------------------------------------------------
// Set a player actor's banding
void setBanded(PlayerActorID player, bool banded) {
assert(player >= 0 && player < kPlayerActors);
if (g_vm->_playerList[player]->getActor()->isDead()) return;
if (banded)
g_vm->_playerList[player]->setBanded();
else
g_vm->_playerList[player]->clearBanded();
g_vm->_playerList[player]->resolveBanding();
updateBrotherBandingButton(player, banded);
}
//-----------------------------------------------------------------------
// Determine if a player actor is banded
bool isBanded(PlayerActorID player) {
assert(player >= 0 && player < kPlayerActors);
return g_vm->_playerList[player]->isBanded();
}
//-----------------------------------------------------------------------
// Globally enable or disable brother banding
void setBrotherBanding(bool enabled) {
if (brotherBandingEnabled != enabled) {
brotherBandingEnabled = enabled;
if (areActorsInitialized()) {
PlayerActorIterator iter;
PlayerActor *player;
// Update the state of the banding
for (player = iter.first(); player != nullptr; player = iter.next()) {
player->resolveBanding();
}
}
}
}
//-----------------------------------------------------------------------
// Set the portrait type for this actor based on the current state.
void recalcPortraitType(PlayerActorID brotherID) {
PlayerActor *pa = getPlayerActorAddress(brotherID);
pa->recalcPortraitType();
}
//-----------------------------------------------------------------------
// Returns an integer value representing this player actor's portrait
// state
int16 getPortraitType(PlayerActorID id) {
PlayerActor *pa = getPlayerActorAddress(id);
return pa->getPortraitType();
}
//-----------------------------------------------------------------------
// Given an actor, returns the corresponding player Actor ID
bool actorToPlayerID(Actor *a, PlayerActorID &result) {
if (a->_disposition >= dispositionPlayer) {
result = a->_disposition - dispositionPlayer;
return true;
}
return false;
}
bool actorIDToPlayerID(ObjectID id, PlayerActorID &result) {
if (!isActor(id)) return false;
Actor *a = (Actor *)GameObject::objectAddress(id);
if (a->_disposition >= dispositionPlayer) {
result = a->_disposition - dispositionPlayer;
return true;
}
return false;
}
void handlePlayerActorDeath(PlayerActorID id) {
assert(id >= 0 && id < kPlayerActors);
if (getCenterActor()->isDead()) {
PlayerActor *newCenter;
LivingPlayerActorIterator iter;
if ((newCenter = iter.first()) != nullptr)
setCenterActor(getPlayerActorID(newCenter));
else
allPlayerActorsDead = true;
}
PlayerActor *player = g_vm->_playerList[id];
player->clearAggression();
player->clearBanded();
updateBrotherAggressionButton(id, false);
updateBrotherBandingButton(id, false);
StatusMsg(DEATH_STATUS, player->getActor()->objName());
}
//-----------------------------------------------------------------------
// Transport the center actor and the banded brothers who have a path
// to the center actor
void transportCenterBand(const Location &loc) {
assert(isWorld(loc.context));
fadeDown();
Actor *center = getCenterActor();
TilePoint centerLoc = center->getLocation();
ObjectID centerWorldID = center->world()->thisID();
PlayerActor *player;
LivingPlayerActorIterator iter;
center->move(loc);
if (center->_moveTask != nullptr)
center->_moveTask->finishWalk();
for (player = iter.first();
player != nullptr;
player = iter.next()) {
Actor *a = player->getActor();
if (a != center
&& player->isBanded()
&& a->world()->thisID() == centerWorldID
&& checkPath(
centerWorldID,
a->proto()->height,
a->getLocation(),
centerLoc)) {
TilePoint dest;
dest = selectNearbySite(
loc.context,
loc,
1,
3,
false);
if (dest != Nowhere) {
a->move(Location(dest, loc.context));
if (a->_moveTask != nullptr)
a->_moveTask->finishWalk();
player->resolveBanding();
}
}
}
updateMainDisplay();
fadeUp();
}
//-----------------------------------------------------------------------
void handlePlayerActorAttacked(PlayerActorID id) {
PlayerActor *pa = getPlayerActorAddress(id);
pa->handleAttacked();
}
//-----------------------------------------------------------------------
void handleEndOfCombat() {
PlayerActorID i;
// Iterate through all player actors
for (i = 0; i < kPlayerActors; i++)
g_vm->_playerList[i]->resetAttackNotification();
}
/* ======================================================================= *
PlayerActor list management functions
* ======================================================================= */
// This structure is used in archiving the player actor list
struct PlayerActorArchive {
int16 portraitType;
uint16 flags;
ActorAttributes baseStats;
int16 manaMemory[numManas];
uint8 attribRecPools[numSkills];
uint8 attribMemPools[numSkills];
uint8 vitalityMemory;
bool notifiedOfAttack;
};
//-----------------------------------------------------------------------
// Initialize the player list
void initPlayerActors() {
if (g_vm->getGameId() == GID_DINO) {
warning("TODO: initPlayerActors() for Dino");
return;
}
g_vm->_playerList.push_back(new PlayerActor(ActorBaseID + 0)); // Julian
g_vm->_playerList.push_back(new PlayerActor(ActorBaseID + 1)); // Philip
g_vm->_playerList.push_back(new PlayerActor(ActorBaseID + 2)); // Kevin
for (int i = 0; i < kPlayerActors; i++) {
PlayerActor *p = g_vm->_playerList[i];
Actor *a = p->getActor();
ActorProto *proto = (ActorProto *)a->proto();
// Set the portrait type
p->portraitType = kPortraitNormal;
// Clear all flags
p->flags = 0;
// Copy the base stats from the actor's prototype
memcpy(&p->baseStats, &proto->baseStats, sizeof(p->baseStats));
// Clear out the accumulation arrays
memset(&p->manaMemory, 0, sizeof(p->manaMemory));
memset(&p->attribRecPools, 0, sizeof(p->attribRecPools));
memset(&p->attribMemPools, 0, sizeof(p->attribMemPools));
// Clear the vitalityMemory
p->vitalityMemory = 0;
// Clear the attack notification flag
p->notifiedOfAttack = false;
// Set the actor's disposition field to reflect that that
// actor is a player actor
a->_disposition = dispositionPlayer + i;
// Turn on banding for player actors
setBanded(i, true);
}
readyContainerSetup();
}
void savePlayerActors(Common::OutSaveFile *outS) {
debugC(2, kDebugSaveload, "Saving PlayerActors");
outS->write("PLYR", 4);
CHUNK_BEGIN;
for (int i = 0; i < kPlayerActors; i++) {
debugC(3, kDebugSaveload, "Saving PlayerActor %d", i);
PlayerActor *p = g_vm->_playerList[i];
// Store the portrait type
out->writeSint16LE(p->portraitType);
// Store the flags
out->writeUint16LE(p->flags);
// Store the base stats
p->baseStats.write(out);
// Store accumulation arrays
for (int j = 0; j < numManas; ++j)
out->writeSint16LE(p->manaMemory[j]);
for (int j = 0; j < numSkills; ++j)
out->writeByte(p->attribRecPools[j]);
for (int j = 0; j < numSkills; ++j)
out->writeByte(p->attribMemPools[j]);
// Store the vitality memory
out->writeByte(p->vitalityMemory);
// Store the attack notification flag
out->writeUint16LE(p->notifiedOfAttack);
debugC(4, kDebugSaveload, "... playerList[%d].portraitType = %d", i, p->portraitType);
debugC(4, kDebugSaveload, "... playerList[%d].flags = %d", i, p->flags);
debugC(4, kDebugSaveload, "... playerList[%d].vitalityMemory = %d", i, p->vitalityMemory);
debugC(4, kDebugSaveload, "... playerList[%d].notifiedOfAttack = %d", i, p->notifiedOfAttack);
}
CHUNK_END;
}
void loadPlayerActors(Common::InSaveFile *in) {
debugC(2, kDebugSaveload, "Loading PlayerActors");
for (int i = 0; i < kPlayerActors; i++) {
debugC(3, kDebugSaveload, "Loading PlayerActor %d", i);
PlayerActor *p = g_vm->_playerList[i];
// Restore the portrait type
p->portraitType = in->readSint16LE();
// Restore the flags
p->flags = in->readUint16LE();
// Restore the base stats
p->baseStats.read(in);
// Restore the accumulation arrays
for (int j = 0; j < numManas; ++j)
p->manaMemory[j] = in->readSint16LE();
for (int j = 0; j < numSkills; ++j)
p->attribRecPools[j] = in->readByte();
for (int j = 0; j < numSkills; ++j)
p->attribMemPools[j] = in->readByte();
// Restore the vitality memory
p->vitalityMemory = in->readByte();
// Restore the attack notification flag
p->notifiedOfAttack = in->readUint16LE();
debugC(4, kDebugSaveload, "... playerList[%d].portraitType = %d", i, p->portraitType);
debugC(4, kDebugSaveload, "... playerList[%d].flags = %d", i, p->flags);
debugC(4, kDebugSaveload, "... playerList[%d].vitalityMemory = %d", i, p->vitalityMemory);
debugC(4, kDebugSaveload, "... playerList[%d].notifiedOfAttack = %d", i, p->notifiedOfAttack);
}
readyContainerSetup();
}
//-----------------------------------------------------------------------
// Cleanup the player actor list
void cleanupPlayerActors() {
cleanupReadyContainers();
}
/* ======================================================================= *
CenterActor management function prototypes
* ======================================================================= */
// This structure is used in archiving the center actor ID and the view
// object ID
struct CenterActorArchive {
PlayerActorID centerActor;
ObjectID viewCenterObject;
};
//-----------------------------------------------------------------------
// Initialize the center actor ID and view object ID
void initCenterActor() {
if (g_vm->getGameId() == GID_DINO) {
warning("TODO: initCenterActor() for Dino");
return;
}
centerActor = FTA_JULIAN;
viewCenterObject = g_vm->_playerList[centerActor]->getActorID();
// clear the last center actor's button state
updateBrotherRadioButtons(FTA_JULIAN);
}
void saveCenterActor(Common::OutSaveFile *outS) {
debugC(2, kDebugSaveload, "Saving CenterActor");
outS->write("CNTR", 4);
CHUNK_BEGIN;
// Store the center actor and view object
out->writeSint16LE(centerActor);
out->writeUint16LE(viewCenterObject);
CHUNK_END;
debugC(3, kDebugSaveload, "... centerActor = %d", centerActor);
debugC(3, kDebugSaveload, "... viewCenterObject = %d", viewCenterObject);
}
void loadCenterActor(Common::InSaveFile *in) {
debugC(2, kDebugSaveload, "Loading CenterActor");
// Restore the center actor and view object
centerActor = in->readSint16LE();
viewCenterObject = in->readUint16LE();
debugC(3, kDebugSaveload, "... centerActor = %d", centerActor);
debugC(3, kDebugSaveload, "... viewCenterObject = %d", viewCenterObject);
}
//-----------------------------------------------------------------------
// Iterates through all player actors
PlayerActor *PlayerActorIterator::first() {
index = 0;
return g_vm->_playerList[index++];
}
PlayerActor *PlayerActorIterator::next() {
return (index < kPlayerActors) ? g_vm->_playerList[index++] : NULL;
}
//-----------------------------------------------------------------------
// Iterates through all player actors that are not dead.
PlayerActor *LivingPlayerActorIterator::first() {
index = 0;
return LivingPlayerActorIterator::next();
}
PlayerActor *LivingPlayerActorIterator::next() {
if (index >= kPlayerActors)
return nullptr;
Actor *a = g_vm->_playerList[index]->getActor();
while (a == nullptr || a->isDead()) {
if (++index >= kPlayerActors)
break;
a = g_vm->_playerList[index]->getActor();
}
return (index < kPlayerActors) ? g_vm->_playerList[index++] : nullptr;
}
} // end of namespace Saga2