fallout2-ce/src/party_member.cc

1674 lines
45 KiB
C++

#include "party_member.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "animation.h"
#include "color.h"
#include "combat_ai.h"
#include "combat_ai_defs.h"
#include "config.h"
#include "critter.h"
#include "debug.h"
#include "display_monitor.h"
#include "game.h"
#include "game_dialog.h"
#include "item.h"
#include "loadsave.h"
#include "map.h"
#include "memory.h"
#include "message.h"
#include "object.h"
#include "proto.h"
#include "proto_instance.h"
#include "queue.h"
#include "random.h"
#include "scripts.h"
#include "skill.h"
#include "stat.h"
#include "string_parsers.h"
#include "text_object.h"
#include "tile.h"
#include "window_manager.h"
namespace fallout {
typedef struct PartyMemberDescription {
bool areaAttackMode[AREA_ATTACK_MODE_COUNT];
bool runAwayMode[RUN_AWAY_MODE_COUNT];
bool bestWeapon[BEST_WEAPON_COUNT];
bool distanceMode[DISTANCE_COUNT];
bool attackWho[ATTACK_WHO_COUNT];
bool chemUse[CHEM_USE_COUNT];
bool disposition[DISPOSITION_COUNT];
int level_minimum;
int level_up_every;
int level_pids_num;
int level_pids[5];
} PartyMemberDescription;
typedef struct STRU_519DBC {
int field_0;
int field_4; // party member level
int field_8; // early what?
} STRU_519DBC;
typedef struct STRUCT_519DA8 {
Object* object;
Script* script;
int* vars;
struct STRUCT_519DA8* next;
} STRUCT_519DA8;
static int partyMemberGetDescription(Object* object, PartyMemberDescription** partyMemberDescriptionPtr);
static void partyMemberDescriptionInit(PartyMemberDescription* partyMemberDescription);
static int _partyMemberPrepLoadInstance(STRUCT_519DA8* a1);
static int _partyMemberRecoverLoadInstance(STRUCT_519DA8* a1);
static int _partyMemberNewObjID();
static int _partyMemberNewObjIDRecurseFind(Object* object, int objectId);
static int _partyMemberPrepItemSave(Object* object);
static int _partyMemberItemSave(Object* object);
static int _partyMemberItemRecover(STRUCT_519DA8* a1);
static int _partyMemberClearItemList();
static int _partyFixMultipleMembers();
static int _partyMemberCopyLevelInfo(Object* object, int a2);
// 0x519D9C
int gPartyMemberDescriptionsLength = 0;
// 0x519DA0
int* gPartyMemberPids = NULL;
//
static STRUCT_519DA8* _itemSaveListHead = NULL;
// List of party members, it's length is [gPartyMemberDescriptionsLength] + 20.
//
// 0x519DA8
static STRUCT_519DA8* gPartyMembers = NULL;
// Number of critters added to party.
//
// 0x519DAC
static int gPartyMembersLength = 0;
// 0x519DB0
static int _partyMemberItemCount = 20000;
// 0x519DB4
static int _partyStatePrepped = 0;
// 0x519DB8
static PartyMemberDescription* gPartyMemberDescriptions = NULL;
// 0x519DBC
static STRU_519DBC* _partyMemberLevelUpInfoList = NULL;
// 0x519DC0
static int _curID = 20000;
// partyMember_init
// 0x493BC0
int partyMembersInit()
{
Config config;
gPartyMemberDescriptionsLength = 0;
if (!configInit(&config)) {
return -1;
}
if (!configRead(&config, "data\\party.txt", true)) {
goto err;
}
char section[50];
sprintf(section, "Party Member %d", gPartyMemberDescriptionsLength);
int partyMemberPid;
while (configGetInt(&config, section, "party_member_pid", &partyMemberPid)) {
gPartyMemberDescriptionsLength++;
sprintf(section, "Party Member %d", gPartyMemberDescriptionsLength);
}
gPartyMemberPids = (int*)internal_malloc(sizeof(*gPartyMemberPids) * gPartyMemberDescriptionsLength);
if (gPartyMemberPids == NULL) {
goto err;
}
memset(gPartyMemberPids, 0, sizeof(*gPartyMemberPids) * gPartyMemberDescriptionsLength);
gPartyMembers = (STRUCT_519DA8*)internal_malloc(sizeof(*gPartyMembers) * (gPartyMemberDescriptionsLength + 20));
if (gPartyMembers == NULL) {
goto err;
}
memset(gPartyMembers, 0, sizeof(*gPartyMembers) * (gPartyMemberDescriptionsLength + 20));
gPartyMemberDescriptions = (PartyMemberDescription*)internal_malloc(sizeof(*gPartyMemberDescriptions) * gPartyMemberDescriptionsLength);
if (gPartyMemberDescriptions == NULL) {
goto err;
}
memset(gPartyMemberDescriptions, 0, sizeof(*gPartyMemberDescriptions) * gPartyMemberDescriptionsLength);
_partyMemberLevelUpInfoList = (STRU_519DBC*)internal_malloc(sizeof(*_partyMemberLevelUpInfoList) * gPartyMemberDescriptionsLength);
if (_partyMemberLevelUpInfoList == NULL) goto err;
memset(_partyMemberLevelUpInfoList, 0, sizeof(*_partyMemberLevelUpInfoList) * gPartyMemberDescriptionsLength);
for (int index = 0; index < gPartyMemberDescriptionsLength; index++) {
sprintf(section, "Party Member %d", index);
if (!configGetInt(&config, section, "party_member_pid", &partyMemberPid)) {
break;
}
PartyMemberDescription* partyMemberDescription = &(gPartyMemberDescriptions[index]);
gPartyMemberPids[index] = partyMemberPid;
partyMemberDescriptionInit(partyMemberDescription);
char* string;
if (configGetString(&config, section, "area_attack_mode", &string)) {
while (*string != '\0') {
int areaAttackMode;
strParseStrFromList(&string, &areaAttackMode, gAreaAttackModeKeys, AREA_ATTACK_MODE_COUNT);
partyMemberDescription->areaAttackMode[areaAttackMode] = true;
}
}
if (configGetString(&config, section, "attack_who", &string)) {
while (*string != '\0') {
int attachWho;
strParseStrFromList(&string, &attachWho, gAttackWhoKeys, ATTACK_WHO_COUNT);
partyMemberDescription->attackWho[attachWho] = true;
}
}
if (configGetString(&config, section, "best_weapon", &string)) {
while (*string != '\0') {
int bestWeapon;
strParseStrFromList(&string, &bestWeapon, gBestWeaponKeys, BEST_WEAPON_COUNT);
partyMemberDescription->bestWeapon[bestWeapon] = true;
}
}
if (configGetString(&config, section, "chem_use", &string)) {
while (*string != '\0') {
int chemUse;
strParseStrFromList(&string, &chemUse, gChemUseKeys, CHEM_USE_COUNT);
partyMemberDescription->chemUse[chemUse] = true;
}
}
if (configGetString(&config, section, "distance", &string)) {
while (*string != '\0') {
int distanceMode;
strParseStrFromList(&string, &distanceMode, gDistanceModeKeys, DISTANCE_COUNT);
partyMemberDescription->distanceMode[distanceMode] = true;
}
}
if (configGetString(&config, section, "run_away_mode", &string)) {
while (*string != '\0') {
int runAwayMode;
strParseStrFromList(&string, &runAwayMode, gRunAwayModeKeys, RUN_AWAY_MODE_COUNT);
partyMemberDescription->runAwayMode[runAwayMode] = true;
}
}
if (configGetString(&config, section, "disposition", &string)) {
while (*string != '\0') {
int disposition;
strParseStrFromList(&string, &disposition, gDispositionKeys, DISPOSITION_COUNT);
partyMemberDescription->disposition[disposition] = true;
}
}
int levelUpEvery;
if (configGetInt(&config, section, "level_up_every", &levelUpEvery)) {
partyMemberDescription->level_up_every = levelUpEvery;
int levelMinimum;
if (configGetInt(&config, section, "level_minimum", &levelMinimum)) {
partyMemberDescription->level_minimum = levelMinimum;
}
if (configGetString(&config, section, "level_pids", &string)) {
while (*string != '\0' && partyMemberDescription->level_pids_num < 5) {
int levelPid;
strParseInt(&string, &levelPid);
partyMemberDescription->level_pids[partyMemberDescription->level_pids_num] = levelPid;
partyMemberDescription->level_pids_num++;
}
}
}
}
configFree(&config);
return 0;
err:
configFree(&config);
return -1;
}
// 0x4940E4
void partyMembersReset()
{
for (int index = 0; index < gPartyMemberDescriptionsLength; index++) {
_partyMemberLevelUpInfoList[index].field_0 = 0;
_partyMemberLevelUpInfoList[index].field_4 = 0;
_partyMemberLevelUpInfoList[index].field_8 = 0;
}
}
// 0x494134
void partyMembersExit()
{
for (int index = 0; index < gPartyMemberDescriptionsLength; index++) {
_partyMemberLevelUpInfoList[index].field_0 = 0;
_partyMemberLevelUpInfoList[index].field_4 = 0;
_partyMemberLevelUpInfoList[index].field_8 = 0;
}
gPartyMemberDescriptionsLength = 0;
if (gPartyMemberPids != NULL) {
internal_free(gPartyMemberPids);
gPartyMemberPids = NULL;
}
if (gPartyMembers != NULL) {
internal_free(gPartyMembers);
gPartyMembers = NULL;
}
if (gPartyMemberDescriptions != NULL) {
internal_free(gPartyMemberDescriptions);
gPartyMemberDescriptions = NULL;
}
if (_partyMemberLevelUpInfoList != NULL) {
internal_free(_partyMemberLevelUpInfoList);
_partyMemberLevelUpInfoList = NULL;
}
}
// 0x4941F0
static int partyMemberGetDescription(Object* object, PartyMemberDescription** partyMemberDescriptionPtr)
{
for (int index = 1; index < gPartyMemberDescriptionsLength; index++) {
if (gPartyMemberPids[index] == object->pid) {
*partyMemberDescriptionPtr = &(gPartyMemberDescriptions[index]);
return 0;
}
}
return -1;
}
// 0x49425C
static void partyMemberDescriptionInit(PartyMemberDescription* partyMemberDescription)
{
for (int index = 0; index < AREA_ATTACK_MODE_COUNT; index++) {
partyMemberDescription->areaAttackMode[index] = 0;
}
for (int index = 0; index < RUN_AWAY_MODE_COUNT; index++) {
partyMemberDescription->runAwayMode[index] = 0;
}
for (int index = 0; index < BEST_WEAPON_COUNT; index++) {
partyMemberDescription->bestWeapon[index] = 0;
}
for (int index = 0; index < DISTANCE_COUNT; index++) {
partyMemberDescription->distanceMode[index] = 0;
}
for (int index = 0; index < ATTACK_WHO_COUNT; index++) {
partyMemberDescription->attackWho[index] = 0;
}
for (int index = 0; index < CHEM_USE_COUNT; index++) {
partyMemberDescription->chemUse[index] = 0;
}
for (int index = 0; index < DISPOSITION_COUNT; index++) {
partyMemberDescription->disposition[index] = 0;
}
partyMemberDescription->level_minimum = 0;
partyMemberDescription->level_up_every = 0;
partyMemberDescription->level_pids_num = 0;
partyMemberDescription->level_pids[0] = -1;
for (int index = 0; index < gPartyMemberDescriptionsLength; index++) {
_partyMemberLevelUpInfoList[index].field_0 = 0;
_partyMemberLevelUpInfoList[index].field_4 = 0;
_partyMemberLevelUpInfoList[index].field_8 = 0;
}
}
// partyMemberAdd
// 0x494378
int partyMemberAdd(Object* object)
{
if (gPartyMembersLength >= gPartyMemberDescriptionsLength + 20) {
return -1;
}
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
if (partyMember->object == object || partyMember->object->pid == object->pid) {
return 0;
}
}
if (_partyStatePrepped) {
debugPrint("\npartyMemberAdd DENIED: %s\n", critterGetName(object));
return -1;
}
STRUCT_519DA8* partyMember = &(gPartyMembers[gPartyMembersLength]);
partyMember->object = object;
partyMember->script = NULL;
partyMember->vars = NULL;
object->id = (object->pid & 0xFFFFFF) + 18000;
object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);
gPartyMembersLength++;
Script* script;
if (scriptGetScript(object->sid, &script) != -1) {
script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
script->field_1C = object->id;
object->sid = ((object->pid & 0xFFFFFF) + 18000) | (object->sid & 0xFF000000);
script->sid = object->sid;
}
critterSetTeam(object, 0);
queueRemoveEventsByType(object, EVENT_TYPE_SCRIPT);
if (_gdialogActive()) {
if (object == gGameDialogSpeaker) {
_gdialogUpdatePartyStatus();
}
}
return 0;
}
// partyMemberRemove
// 0x4944DC
int partyMemberRemove(Object* object)
{
if (gPartyMembersLength == 0) {
return -1;
}
if (object == NULL) {
return -1;
}
int index;
for (index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
if (partyMember->object == object) {
break;
}
}
if (index == gPartyMembersLength) {
return -1;
}
if (_partyStatePrepped) {
debugPrint("\npartyMemberRemove DENIED: %s\n", critterGetName(object));
return -1;
}
if (index < gPartyMembersLength - 1) {
gPartyMembers[index].object = gPartyMembers[gPartyMembersLength - 1].object;
}
object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);
gPartyMembersLength--;
Script* script;
if (scriptGetScript(object->sid, &script) != -1) {
script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
}
queueRemoveEventsByType(object, EVENT_TYPE_SCRIPT);
if (_gdialogActive()) {
if (object == gGameDialogSpeaker) {
_gdialogUpdatePartyStatus();
}
}
return 0;
}
// 0x49460C
int _partyMemberPrepSave()
{
_partyStatePrepped = 1;
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
if (index > 0) {
ptr->object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);
}
Script* script;
if (scriptGetScript(ptr->object->sid, &script) != -1) {
script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
}
}
return 0;
}
// 0x49466C
int _partyMemberUnPrepSave()
{
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
if (index > 0) {
ptr->object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);
}
Script* script;
if (scriptGetScript(ptr->object->sid, &script) != -1) {
script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
}
}
_partyStatePrepped = 0;
return 0;
}
// 0x4946CC
int partyMembersSave(File* stream)
{
if (fileWriteInt32(stream, gPartyMembersLength) == -1) return -1;
if (fileWriteInt32(stream, _partyMemberItemCount) == -1) return -1;
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
if (fileWriteInt32(stream, partyMember->object->id) == -1) return -1;
}
for (int index = 1; index < gPartyMemberDescriptionsLength; index++) {
STRU_519DBC* ptr = &(_partyMemberLevelUpInfoList[index]);
if (fileWriteInt32(stream, ptr->field_0) == -1) return -1;
if (fileWriteInt32(stream, ptr->field_4) == -1) return -1;
if (fileWriteInt32(stream, ptr->field_8) == -1) return -1;
}
return 0;
}
// 0x4947AC
int _partyMemberPrepLoad()
{
if (_partyStatePrepped) {
return -1;
}
_partyStatePrepped = 1;
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr_519DA8 = &(gPartyMembers[index]);
if (_partyMemberPrepLoadInstance(ptr_519DA8) != 0) {
return -1;
}
}
return 0;
}
// partyMemberPrepLoadInstance
// 0x49480C
static int _partyMemberPrepLoadInstance(STRUCT_519DA8* a1)
{
Object* obj = a1->object;
if (obj == NULL) {
debugPrint("\n Error!: partyMemberPrepLoadInstance: No Critter Object!");
a1->script = NULL;
a1->vars = NULL;
a1->next = NULL;
return 0;
}
if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {
obj->data.critter.combat.whoHitMe = NULL;
}
Script* script;
if (scriptGetScript(obj->sid, &script) == -1) {
debugPrint("\n Error!: partyMemberPrepLoadInstance: Can't find script!");
debugPrint("\n partyMemberPrepLoadInstance: script was: (%s)", critterGetName(obj));
a1->script = NULL;
a1->vars = NULL;
a1->next = NULL;
return 0;
}
a1->script = (Script*)internal_malloc(sizeof(*script));
if (a1->script == NULL) {
showMesageBox("\n Error!: partyMemberPrepLoad: Out of memory!");
exit(1);
}
memcpy(a1->script, script, sizeof(*script));
if (script->localVarsCount != 0 && script->localVarsOffset != -1) {
a1->vars = (int*)internal_malloc(sizeof(*a1->vars) * script->localVarsCount);
if (a1->vars == NULL) {
showMesageBox("\n Error!: partyMemberPrepLoad: Out of memory!");
exit(1);
}
if (gMapLocalVars != NULL) {
memcpy(a1->vars, gMapLocalVars + script->localVarsOffset, sizeof(int) * script->localVarsCount);
} else {
debugPrint("\nWarning: partyMemberPrepLoadInstance: No map_local_vars found, but script references them!");
memset(a1->vars, 0, sizeof(int) * script->localVarsCount);
}
}
Inventory* inventory = &(obj->data.inventory);
for (int index = 0; index < inventory->length; index++) {
InventoryItem* inventoryItem = &(inventory->items[index]);
_partyMemberItemSave(inventoryItem->item);
}
script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
scriptRemove(script->sid);
if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {
_dude_stand(obj, obj->rotation, -1);
}
return 0;
}
// partyMemberRecoverLoad
// 0x4949C4
int _partyMemberRecoverLoad()
{
if (_partyStatePrepped != 1) {
debugPrint("\npartyMemberRecoverLoad DENIED");
return -1;
}
debugPrint("\n");
for (int index = 0; index < gPartyMembersLength; index++) {
if (_partyMemberRecoverLoadInstance(&(gPartyMembers[index])) != 0) {
return -1;
}
debugPrint("[Party Member %d]: %s\n", index, critterGetName(gPartyMembers[index].object));
}
STRUCT_519DA8* v6 = _itemSaveListHead;
while (v6 != NULL) {
_itemSaveListHead = v6->next;
_partyMemberItemRecover(v6);
internal_free(v6);
v6 = _itemSaveListHead;
}
_partyStatePrepped = 0;
if (!_isLoadingGame()) {
_partyFixMultipleMembers();
}
return 0;
}
// partyMemberRecoverLoadInstance
// 0x494A88
static int _partyMemberRecoverLoadInstance(STRUCT_519DA8* a1)
{
if (a1->script == NULL) {
showMesageBox("\n Error!: partyMemberRecoverLoadInstance: No script!");
return 0;
}
int scriptType = SCRIPT_TYPE_CRITTER;
if (PID_TYPE(a1->object->pid) != OBJ_TYPE_CRITTER) {
scriptType = SCRIPT_TYPE_ITEM;
}
int v1 = -1;
if (scriptAdd(&v1, scriptType) == -1) {
showMesageBox("\n Error!: partyMemberRecoverLoad: Can't create script!");
exit(1);
}
Script* script;
if (scriptGetScript(v1, &script) == -1) {
showMesageBox("\n Error!: partyMemberRecoverLoad: Can't find script!");
exit(1);
}
memcpy(script, a1->script, sizeof(*script));
int sid = (scriptType << 24) | ((a1->object->pid & 0xFFFFFF) + 18000);
a1->object->sid = sid;
script->sid = sid;
script->flags &= ~(SCRIPT_FLAG_0x01 | SCRIPT_FLAG_0x04);
internal_free(a1->script);
a1->script = NULL;
script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
if (a1->vars != NULL) {
script->localVarsOffset = _map_malloc_local_var(script->localVarsCount);
memcpy(gMapLocalVars + script->localVarsOffset, a1->vars, sizeof(int) * script->localVarsCount);
}
return 0;
}
// 0x494BBC
int partyMembersLoad(File* stream)
{
int* partyMemberObjectIds = (int*)internal_malloc(sizeof(*partyMemberObjectIds) * (gPartyMemberDescriptionsLength + 20));
if (partyMemberObjectIds == NULL) {
return -1;
}
// FIXME: partyMemberObjectIds is never free'd in this function, obviously memory leak.
if (fileReadInt32(stream, &gPartyMembersLength) == -1) return -1;
if (fileReadInt32(stream, &_partyMemberItemCount) == -1) return -1;
gPartyMembers->object = gDude;
if (gPartyMembersLength != 0) {
for (int index = 1; index < gPartyMembersLength; index++) {
if (fileReadInt32(stream, &(partyMemberObjectIds[index])) == -1) return -1;
}
for (int index = 1; index < gPartyMembersLength; index++) {
int objectId = partyMemberObjectIds[index];
Object* object = objectFindFirst();
while (object != NULL) {
if (object->id == objectId) {
break;
}
object = objectFindNext();
}
if (object != NULL) {
gPartyMembers[index].object = object;
} else {
debugPrint("Couldn't find party member on map...trying to load anyway.\n");
if (index + 1 >= gPartyMembersLength) {
partyMemberObjectIds[index] = 0;
} else {
memcpy(&(partyMemberObjectIds[index]), &(partyMemberObjectIds[index + 1]), sizeof(*partyMemberObjectIds) * (gPartyMembersLength - (index + 1)));
}
index--;
gPartyMembersLength--;
}
}
if (_partyMemberUnPrepSave() == -1) {
return -1;
}
}
_partyFixMultipleMembers();
for (int index = 1; index < gPartyMemberDescriptionsLength; index++) {
STRU_519DBC* ptr_519DBC = &(_partyMemberLevelUpInfoList[index]);
if (fileReadInt32(stream, &(ptr_519DBC->field_0)) == -1) return -1;
if (fileReadInt32(stream, &(ptr_519DBC->field_4)) == -1) return -1;
if (fileReadInt32(stream, &(ptr_519DBC->field_8)) == -1) return -1;
}
return 0;
}
// 0x494D7C
void _partyMemberClear()
{
if (_partyStatePrepped) {
_partyMemberUnPrepSave();
}
for (int index = gPartyMembersLength; index > 1; index--) {
partyMemberRemove(gPartyMembers[1].object);
}
gPartyMembersLength = 1;
_scr_remove_all();
_partyMemberClearItemList();
_partyStatePrepped = 0;
}
// 0x494DD0
int _partyMemberSyncPosition()
{
int clockwiseRotation = (gDude->rotation + 2) % ROTATION_COUNT;
int counterClockwiseRotation = (gDude->rotation + 4) % ROTATION_COUNT;
int n = 0;
int distance = 2;
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
Object* partyMemberObj = partyMember->object;
if ((partyMemberObj->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(partyMemberObj->pid) == OBJ_TYPE_CRITTER) {
int rotation;
if ((n % 2) != 0) {
rotation = clockwiseRotation;
} else {
rotation = counterClockwiseRotation;
}
int tile = tileGetTileInDirection(gDude->tile, rotation, distance / 2);
_objPMAttemptPlacement(partyMemberObj, tile, gDude->elevation);
distance++;
n++;
}
}
return 0;
}
// Heals party members according to their healing rate.
//
// 0x494EB8
int _partyMemberRestingHeal(int a1)
{
int v1 = a1 / 3;
if (v1 == 0) {
return 0;
}
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
if (PID_TYPE(partyMember->object->pid) == OBJ_TYPE_CRITTER) {
int healingRate = critterGetStat(partyMember->object, STAT_HEALING_RATE);
critterAdjustHitPoints(partyMember->object, v1 * healingRate);
}
}
return 1;
}
// 0x494F24
Object* partyMemberFindByPid(int pid)
{
for (int index = 0; index < gPartyMembersLength; index++) {
Object* object = gPartyMembers[index].object;
if (object->pid == pid) {
return object;
}
}
return NULL;
}
// 0x494F64
bool _isPotentialPartyMember(Object* object)
{
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
if (partyMember->object->pid == gPartyMemberPids[index]) {
return true;
}
}
return false;
}
// Returns `true` if specified object is a party member.
//
// 0x494FC4
bool objectIsPartyMember(Object* object)
{
if (object == NULL) {
return false;
}
if (object->id < 18000) {
return false;
}
bool isPartyMember = false;
for (int index = 0; index < gPartyMembersLength; index++) {
if (gPartyMembers[index].object == object) {
isPartyMember = true;
break;
}
}
return isPartyMember;
}
// Returns number of active critters in the party.
//
// 0x495010
int _getPartyMemberCount()
{
int count = gPartyMembersLength;
for (int index = 1; index < gPartyMembersLength; index++) {
Object* object = gPartyMembers[index].object;
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER || critterIsDead(object) || (object->flags & OBJECT_HIDDEN) != 0) {
count--;
}
}
return count;
}
// 0x495070
static int _partyMemberNewObjID()
{
Object* object;
do {
_curID++;
object = objectFindFirst();
while (object != NULL) {
if (object->id == _curID) {
break;
}
Inventory* inventory = &(object->data.inventory);
int index;
for (index = 0; index < inventory->length; index++) {
InventoryItem* inventoryItem = &(inventory->items[index]);
Object* item = inventoryItem->item;
if (item->id == _curID) {
break;
}
if (_partyMemberNewObjIDRecurseFind(item, _curID)) {
break;
}
}
if (index < inventory->length) {
break;
}
object = objectFindNext();
}
} while (object != NULL);
_curID++;
return _curID;
}
// 0x4950F4
static int _partyMemberNewObjIDRecurseFind(Object* obj, int objectId)
{
Inventory* inventory = &(obj->data.inventory);
for (int index = 0; index < inventory->length; index++) {
InventoryItem* inventoryItem = &(inventory->items[index]);
if (inventoryItem->item->id == objectId) {
return 1;
}
if (_partyMemberNewObjIDRecurseFind(inventoryItem->item, objectId)) {
return 1;
}
}
return 0;
}
// 0x495140
int _partyMemberPrepItemSaveAll()
{
for (int partyMemberIndex = 0; partyMemberIndex < gPartyMembersLength; partyMemberIndex++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[partyMemberIndex]);
Inventory* inventory = &(partyMember->object->data.inventory);
for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) {
InventoryItem* inventoryItem = &(inventory->items[inventoryItemIndex]);
_partyMemberPrepItemSave(inventoryItem->item);
}
}
return 0;
}
// partyMemberPrepItemSaveAll
static int _partyMemberPrepItemSave(Object* object)
{
if (object->sid != -1) {
Script* script;
if (scriptGetScript(object->sid, &script) == -1) {
showMesageBox("\n Error!: partyMemberPrepItemSaveAll: Can't find script!");
exit(1);
}
script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
}
Inventory* inventory = &(object->data.inventory);
for (int index = 0; index < inventory->length; index++) {
InventoryItem* inventoryItem = &(inventory->items[index]);
_partyMemberPrepItemSave(inventoryItem->item);
}
return 0;
}
// 0x495234
static int _partyMemberItemSave(Object* object)
{
if (object->sid != -1) {
Script* script;
if (scriptGetScript(object->sid, &script) == -1) {
showMesageBox("\n Error!: partyMemberItemSave: Can't find script!");
exit(1);
}
if (object->id < 20000) {
script->field_1C = _partyMemberNewObjID();
object->id = script->field_1C;
}
STRUCT_519DA8* node = (STRUCT_519DA8*)internal_malloc(sizeof(*node));
if (node == NULL) {
showMesageBox("\n Error!: partyMemberItemSave: Out of memory!");
exit(1);
}
node->object = object;
node->script = (Script*)internal_malloc(sizeof(*script));
if (node->script == NULL) {
showMesageBox("\n Error!: partyMemberItemSave: Out of memory!");
exit(1);
}
memcpy(node->script, script, sizeof(*script));
if (script->localVarsCount != 0 && script->localVarsOffset != -1) {
node->vars = (int*)internal_malloc(sizeof(*node->vars) * script->localVarsCount);
if (node->vars == NULL) {
showMesageBox("\n Error!: partyMemberItemSave: Out of memory!");
exit(1);
}
memcpy(node->vars, gMapLocalVars + script->localVarsOffset, sizeof(int) * script->localVarsCount);
} else {
node->vars = NULL;
}
STRUCT_519DA8* temp = _itemSaveListHead;
_itemSaveListHead = node;
node->next = temp;
}
Inventory* inventory = &(object->data.inventory);
for (int index = 0; index < inventory->length; index++) {
InventoryItem* inventoryItem = &(inventory->items[index]);
_partyMemberItemSave(inventoryItem->item);
}
return 0;
}
// partyMemberItemRecover
// 0x495388
static int _partyMemberItemRecover(STRUCT_519DA8* a1)
{
int sid = -1;
if (scriptAdd(&sid, SCRIPT_TYPE_ITEM) == -1) {
showMesageBox("\n Error!: partyMemberItemRecover: Can't create script!");
exit(1);
}
Script* script;
if (scriptGetScript(sid, &script) == -1) {
showMesageBox("\n Error!: partyMemberItemRecover: Can't find script!");
exit(1);
}
memcpy(script, a1->script, sizeof(*script));
a1->object->sid = _partyMemberItemCount | (SCRIPT_TYPE_ITEM << 24);
script->sid = _partyMemberItemCount | (SCRIPT_TYPE_ITEM << 24);
script->program = NULL;
script->flags &= ~(SCRIPT_FLAG_0x01 | SCRIPT_FLAG_0x04 | SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);
_partyMemberItemCount++;
internal_free(a1->script);
a1->script = NULL;
if (a1->vars != NULL) {
script->localVarsOffset = _map_malloc_local_var(script->localVarsCount);
memcpy(gMapLocalVars + script->localVarsOffset, a1->vars, sizeof(int) * script->localVarsCount);
}
return 0;
}
// 0x4954C4
static int _partyMemberClearItemList()
{
while (_itemSaveListHead != NULL) {
STRUCT_519DA8* node = _itemSaveListHead;
_itemSaveListHead = _itemSaveListHead->next;
if (node->script != NULL) {
internal_free(node->script);
}
if (node->vars != NULL) {
internal_free(node->vars);
}
internal_free(node);
}
_partyMemberItemCount = 20000;
return 0;
}
// Returns best skill of the specified party member.
//
// 0x495520
int partyMemberGetBestSkill(Object* object)
{
int bestSkill = SKILL_SMALL_GUNS;
if (object == NULL) {
return bestSkill;
}
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {
return bestSkill;
}
int bestValue = 0;
for (int skill = 0; skill < SKILL_COUNT; skill++) {
int value = skillGetValue(object, skill);
if (value > bestValue) {
bestSkill = skill;
bestValue = value;
}
}
return bestSkill;
}
// Returns party member with highest skill level.
//
// 0x495560
Object* partyMemberGetBestInSkill(int skill)
{
int bestValue = 0;
Object* bestPartyMember = NULL;
for (int index = 0; index < gPartyMembersLength; index++) {
Object* object = gPartyMembers[index].object;
if ((object->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {
int value = skillGetValue(object, skill);
if (value > bestValue) {
bestValue = value;
bestPartyMember = object;
}
}
}
return bestPartyMember;
}
// Returns highest skill level in party.
//
// 0x4955C8
int partyGetBestSkillValue(int skill)
{
int bestValue = 0;
for (int index = 0; index < gPartyMembersLength; index++) {
Object* object = gPartyMembers[index].object;
if ((object->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {
int value = skillGetValue(object, skill);
if (value > bestValue) {
bestValue = value;
}
}
}
return bestValue;
}
// 0x495620
static int _partyFixMultipleMembers()
{
debugPrint("\n\n\n[Party Members]:");
int critterCount = 0;
for (Object* obj = objectFindFirst(); obj != NULL; obj = objectFindNext()) {
if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {
critterCount++;
}
bool isPartyMember = false;
for (int index = 1; index < gPartyMemberDescriptionsLength; index++) {
if (obj->pid == gPartyMemberPids[index]) {
isPartyMember = true;
break;
}
}
if (!isPartyMember) {
continue;
}
debugPrint("\n PM: %s", critterGetName(obj));
bool v19 = false;
if (obj->sid == -1) {
v19 = true;
} else {
Object* v7 = NULL;
for (int i = 0; i < gPartyMembersLength; i++) {
if (obj->pid == gPartyMembers[i].object->pid) {
v7 = gPartyMembers[i].object;
break;
}
}
if (v7 != NULL && obj != v7) {
if (v7->sid == obj->sid) {
obj->sid = -1;
}
v19 = true;
}
}
if (!v19) {
continue;
}
Object* v10 = NULL;
for (int i = 0; i < gPartyMembersLength; i++) {
if (obj->pid == gPartyMembers[i].object->pid) {
v10 = gPartyMembers[i].object;
}
}
// TODO: Probably wrong.
if (obj == v10) {
debugPrint("\nError: Attempting to destroy evil critter doppleganger FAILED!");
continue;
}
debugPrint("\nDestroying evil critter doppleganger!");
if (obj->sid != -1) {
scriptRemove(obj->sid);
obj->sid = -1;
} else {
if (queueRemoveEventsByType(obj, EVENT_TYPE_SCRIPT) == -1) {
debugPrint("\nERROR Removing Timed Events on FIX remove!!\n");
}
}
objectDestroy(obj, NULL);
}
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
Script* script;
if (scriptGetScript(partyMember->object->sid, &script) != -1) {
script->owner = partyMember->object;
} else {
debugPrint("\nError: Failed to fix party member critter scripts!");
}
}
debugPrint("\nTotal Critter Count: %d\n\n", critterCount);
return 0;
}
// 0x495870
void _partyMemberSaveProtos()
{
for (int index = 1; index < gPartyMemberDescriptionsLength; index++) {
int pid = gPartyMemberPids[index];
if (pid != -1) {
_proto_save_pid(pid);
}
}
}
// 0x4958B0
bool partyMemberSupportsDisposition(Object* critter, int disposition)
{
if (critter == NULL) {
return false;
}
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
return false;
}
if (disposition == -1 || disposition > 5) {
return false;
}
PartyMemberDescription* partyMemberDescription;
if (partyMemberGetDescription(critter, &partyMemberDescription) == -1) {
return false;
}
return partyMemberDescription->disposition[disposition + 1];
}
// 0x495920
bool partyMemberSupportsAreaAttackMode(Object* object, int areaAttackMode)
{
if (object == NULL) {
return false;
}
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {
return false;
}
if (areaAttackMode >= AREA_ATTACK_MODE_COUNT) {
return false;
}
PartyMemberDescription* partyMemberDescription;
if (partyMemberGetDescription(object, &partyMemberDescription) == -1) {
return false;
}
return partyMemberDescription->areaAttackMode[areaAttackMode];
}
// 0x495980
bool partyMemberSupportsRunAwayMode(Object* object, int runAwayMode)
{
if (object == NULL) {
return false;
}
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {
return false;
}
if (runAwayMode >= RUN_AWAY_MODE_COUNT) {
return false;
}
PartyMemberDescription* partyMemberDescription;
if (partyMemberGetDescription(object, &partyMemberDescription) == -1) {
return false;
}
return partyMemberDescription->runAwayMode[runAwayMode + 1];
}
// 0x4959E0
bool partyMemberSupportsBestWeapon(Object* object, int bestWeapon)
{
if (object == NULL) {
return false;
}
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {
return false;
}
if (bestWeapon >= BEST_WEAPON_COUNT) {
return false;
}
PartyMemberDescription* partyMemberDescription;
if (partyMemberGetDescription(object, &partyMemberDescription) == -1) {
return false;
}
return partyMemberDescription->bestWeapon[bestWeapon];
}
// 0x495A40
bool partyMemberSupportsDistance(Object* object, int distanceMode)
{
if (object == NULL) {
return false;
}
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {
return false;
}
if (distanceMode >= DISTANCE_COUNT) {
return false;
}
PartyMemberDescription* partyMemberDescription;
if (partyMemberGetDescription(object, &partyMemberDescription) == -1) {
return false;
}
return partyMemberDescription->distanceMode[distanceMode];
}
// 0x495AA0
bool partyMemberSupportsAttackWho(Object* object, int attackWho)
{
if (object == NULL) {
return false;
}
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {
return false;
}
if (attackWho >= ATTACK_WHO_COUNT) {
return false;
}
PartyMemberDescription* partyMemberDescription;
if (partyMemberGetDescription(object, &partyMemberDescription) == -1) {
return false;
}
return partyMemberDescription->attackWho[attackWho];
}
// 0x495B00
bool partyMemberSupportsChemUse(Object* object, int chemUse)
{
if (object == NULL) {
return false;
}
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {
return false;
}
if (chemUse >= CHEM_USE_COUNT) {
return false;
}
PartyMemberDescription* partyMemberDescription;
if (partyMemberGetDescription(object, &partyMemberDescription) == -1) {
return false;
}
return partyMemberDescription->chemUse[chemUse];
}
// partyMemberIncLevels
// 0x495B60
int _partyMemberIncLevels()
{
int i;
STRUCT_519DA8* ptr;
Object* obj;
PartyMemberDescription* party_member;
const char* name;
int j;
int v0;
STRU_519DBC* ptr_519DBC;
int v24;
char* text;
MessageListItem msg;
char str[260];
Rect v19;
v0 = -1;
for (i = 1; i < gPartyMembersLength; i++) {
ptr = &(gPartyMembers[i]);
obj = ptr->object;
if (partyMemberGetDescription(obj, &party_member) == -1) {
break;
}
if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {
continue;
}
name = critterGetName(obj);
debugPrint("\npartyMemberIncLevels: %s", name);
if (party_member->level_up_every == 0) {
continue;
}
for (j = 1; j < gPartyMemberDescriptionsLength; j++) {
if (gPartyMemberPids[j] == obj->pid) {
v0 = j;
}
}
if (v0 == -1) {
continue;
}
if (pcGetStat(PC_STAT_LEVEL) < party_member->level_minimum) {
continue;
}
ptr_519DBC = &(_partyMemberLevelUpInfoList[v0]);
if (ptr_519DBC->field_0 >= party_member->level_pids_num) {
continue;
}
ptr_519DBC->field_4++;
v24 = ptr_519DBC->field_4 % party_member->level_pids_num;
debugPrint("pm: levelMod: %d, Lvl: %d, Early: %d, Every: %d", v24, ptr_519DBC->field_4, ptr_519DBC->field_8, party_member->level_up_every);
if (v24 != 0 || ptr_519DBC->field_8 == 0) {
if (ptr_519DBC->field_8 == 0) {
if (v24 == 0 || randomBetween(0, 100) <= 100 * v24 / party_member->level_up_every) {
ptr_519DBC->field_0++;
if (v24 != 0) {
ptr_519DBC->field_8 = 1;
}
if (_partyMemberCopyLevelInfo(obj, party_member->level_pids[ptr_519DBC->field_0]) == -1) {
return -1;
}
name = critterGetName(obj);
// %s has gained in some abilities.
text = getmsg(&gMiscMessageList, &msg, 9000);
sprintf(str, text, name);
displayMonitorAddMessage(str);
debugPrint(str);
// Individual message
msg.num = 9000 + 10 * v0 + ptr_519DBC->field_0 - 1;
if (messageListGetItem(&gMiscMessageList, &msg)) {
name = critterGetName(obj);
sprintf(str, msg.text, name);
textObjectAdd(obj, str, 101, _colorTable[0x7FFF], _colorTable[0], &v19);
tileWindowRefreshRect(&v19, obj->elevation);
}
}
}
} else {
ptr_519DBC->field_8 = 0;
}
}
return 0;
}
// 0x495EA8
static int _partyMemberCopyLevelInfo(Object* critter, int a2)
{
if (critter == NULL) {
return -1;
}
if (a2 == -1) {
return -1;
}
Proto* proto1;
if (protoGetProto(critter->pid, &proto1) == -1) {
return -1;
}
Proto* proto2;
if (protoGetProto(a2, &proto2) == -1) {
return -1;
}
Object* item2 = critterGetItem2(critter);
_invenUnwieldFunc(critter, 1, 0);
Object* armor = critterGetArmor(critter);
_adjust_ac(critter, armor, NULL);
itemRemove(critter, armor, 1);
int maxHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS);
critterAdjustHitPoints(critter, maxHp);
for (int stat = 0; stat < SPECIAL_STAT_COUNT; stat++) {
proto1->critter.data.baseStats[stat] = proto2->critter.data.baseStats[stat];
}
for (int stat = 0; stat < SPECIAL_STAT_COUNT; stat++) {
proto1->critter.data.bonusStats[stat] = proto2->critter.data.bonusStats[stat];
}
for (int skill = 0; skill < SKILL_COUNT; skill++) {
proto1->critter.data.skills[skill] = proto2->critter.data.skills[skill];
}
critter->data.critter.hp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS);
if (armor != NULL) {
itemAdd(critter, armor, 1);
_inven_wield(critter, armor, 0);
}
if (item2 != NULL) {
// SFALL: Fix for party member's equipped weapon being placed in the
// incorrect item slot after leveling up.
_invenWieldFunc(critter, item2, 1, false);
}
return 0;
}
// Returns `true` if any party member that can be healed thru the rest is
// wounded.
//
// This function is used to determine if any party member needs healing thru
// the "Rest until party healed", therefore it excludes robots in the party
// (they cannot be healed by resting) and dude (he/she has it's own "Rest
// until healed" option).
//
// 0x496058
bool partyIsAnyoneCanBeHealedByRest()
{
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
Object* object = ptr->object;
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue;
if (critterIsDead(object)) continue;
if ((object->flags & OBJECT_HIDDEN) != 0) continue;
if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue;
int currentHp = critterGetHitPoints(object);
int maximumHp = critterGetStat(object, STAT_MAXIMUM_HIT_POINTS);
if (currentHp < maximumHp) {
return true;
}
}
return false;
}
// Returns maximum amount of damage of any party member that can be healed thru
// the rest.
//
// 0x4960DC
int partyGetMaxWoundToHealByRest()
{
int maxWound = 0;
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
Object* object = ptr->object;
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue;
if (critterIsDead(object)) continue;
if ((object->flags & OBJECT_HIDDEN) != 0) continue;
if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue;
int currentHp = critterGetHitPoints(object);
int maximumHp = critterGetStat(object, STAT_MAXIMUM_HIT_POINTS);
int wound = maximumHp - currentHp;
if (wound > 0) {
if (wound > maxWound) {
maxWound = wound;
}
}
}
return maxWound;
}
} // namespace fallout