2022-05-19 01:51:26 -07:00
|
|
|
#include "critter.h"
|
|
|
|
|
2022-09-15 02:38:23 -07:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "animation.h"
|
2022-06-19 03:32:42 -07:00
|
|
|
#include "art.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "character_editor.h"
|
|
|
|
#include "combat.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "display_monitor.h"
|
|
|
|
#include "endgame.h"
|
|
|
|
#include "game.h"
|
|
|
|
#include "geometry.h"
|
|
|
|
#include "interface.h"
|
|
|
|
#include "item.h"
|
|
|
|
#include "map.h"
|
|
|
|
#include "memory.h"
|
2022-06-19 02:57:46 -07:00
|
|
|
#include "message.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "object.h"
|
|
|
|
#include "party_member.h"
|
2022-05-28 02:34:49 -07:00
|
|
|
#include "platform_compat.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "proto.h"
|
|
|
|
#include "queue.h"
|
|
|
|
#include "random.h"
|
|
|
|
#include "reaction.h"
|
|
|
|
#include "scripts.h"
|
|
|
|
#include "skill.h"
|
|
|
|
#include "stat.h"
|
|
|
|
#include "tile.h"
|
|
|
|
#include "trait.h"
|
2022-09-14 23:00:11 -07:00
|
|
|
#include "worldmap.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-09-23 05:43:44 -07:00
|
|
|
namespace fallout {
|
|
|
|
|
2022-06-19 02:57:46 -07:00
|
|
|
// Maximum length of dude's name length.
|
|
|
|
#define DUDE_NAME_MAX_LENGTH (32)
|
|
|
|
|
|
|
|
// The number of effects caused by radiation.
|
|
|
|
//
|
|
|
|
// A radiation effect is an identifier and does not have it's own name. It's
|
|
|
|
// stat is specified in [gRadiationEffectStats], and it's amount is specified
|
|
|
|
// in [gRadiationEffectPenalties] for every [RadiationLevel].
|
|
|
|
#define RADIATION_EFFECT_COUNT 8
|
|
|
|
|
|
|
|
// Radiation levels.
|
|
|
|
//
|
|
|
|
// The names of levels are taken from Fallout 3, comments from Fallout 2.
|
|
|
|
typedef enum RadiationLevel {
|
|
|
|
// Very nauseous.
|
|
|
|
RADIATION_LEVEL_NONE,
|
|
|
|
|
|
|
|
// Slightly fatigued.
|
|
|
|
RADIATION_LEVEL_MINOR,
|
|
|
|
|
|
|
|
// Vomiting does not stop.
|
|
|
|
RADIATION_LEVEL_ADVANCED,
|
|
|
|
|
|
|
|
// Hair is falling out.
|
|
|
|
RADIATION_LEVEL_CRITICAL,
|
|
|
|
|
|
|
|
// Skin is falling off.
|
|
|
|
RADIATION_LEVEL_DEADLY,
|
|
|
|
|
|
|
|
// Intense agony.
|
|
|
|
RADIATION_LEVEL_FATAL,
|
|
|
|
|
|
|
|
// The number of radiation levels.
|
|
|
|
RADIATION_LEVEL_COUNT,
|
|
|
|
} RadiationLevel;
|
|
|
|
|
|
|
|
static int _get_rad_damage_level(Object* obj, void* data);
|
2022-09-01 08:41:37 -07:00
|
|
|
static int critter_kill_count_clear();
|
2022-06-19 02:57:46 -07:00
|
|
|
static int _critterClearObjDrugs(Object* obj, void* data);
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
// 0x50141C
|
2022-06-19 02:57:46 -07:00
|
|
|
static char _aCorpse[] = "corpse";
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x501494
|
2022-06-19 02:57:46 -07:00
|
|
|
static char byte_501494[] = "";
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x51833C
|
2022-06-19 02:57:46 -07:00
|
|
|
static char* _name_critter = _aCorpse;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// Modifiers to endurance for performing radiation damage check.
|
|
|
|
//
|
|
|
|
// 0x518340
|
2022-06-19 02:57:46 -07:00
|
|
|
static const int gRadiationEnduranceModifiers[RADIATION_LEVEL_COUNT] = {
|
2022-05-19 01:51:26 -07:00
|
|
|
2,
|
|
|
|
0,
|
|
|
|
-2,
|
|
|
|
-4,
|
|
|
|
-6,
|
|
|
|
-8,
|
|
|
|
};
|
|
|
|
|
|
|
|
// List of stats affected by radiation.
|
|
|
|
//
|
|
|
|
// The values of this list specify stats that can be affected by radiation.
|
|
|
|
// The amount of penalty to every stat (identified by index) is stored
|
|
|
|
// separately in [gRadiationEffectPenalties] per radiation level.
|
|
|
|
//
|
|
|
|
// The order of stats is important - primary stats must be at the top. See
|
|
|
|
// [RADIATION_EFFECT_PRIMARY_STAT_COUNT] for more info.
|
|
|
|
//
|
|
|
|
// 0x518358
|
2022-06-19 02:57:46 -07:00
|
|
|
static const int gRadiationEffectStats[RADIATION_EFFECT_COUNT] = {
|
2022-05-19 01:51:26 -07:00
|
|
|
STAT_STRENGTH,
|
|
|
|
STAT_PERCEPTION,
|
|
|
|
STAT_ENDURANCE,
|
|
|
|
STAT_CHARISMA,
|
|
|
|
STAT_INTELLIGENCE,
|
|
|
|
STAT_AGILITY,
|
|
|
|
STAT_CURRENT_HIT_POINTS,
|
|
|
|
STAT_HEALING_RATE,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Denotes how many primary stats at the top of [gRadiationEffectStats] array.
|
|
|
|
// These stats are used to determine if critter is alive after applying
|
|
|
|
// radiation effects.
|
|
|
|
#define RADIATION_EFFECT_PRIMARY_STAT_COUNT 6
|
|
|
|
|
|
|
|
// List of stat modifiers caused by radiation at different radiation levels.
|
|
|
|
//
|
|
|
|
// 0x518378
|
2022-06-19 02:57:46 -07:00
|
|
|
static const int gRadiationEffectPenalties[RADIATION_LEVEL_COUNT][RADIATION_EFFECT_COUNT] = {
|
2022-05-19 01:51:26 -07:00
|
|
|
// clang-format off
|
|
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
|
|
{ -1, 0, 0, 0, 0, 0, 0, 0 },
|
|
|
|
{ -1, 0, 0, 0, 0, -1, 0, -3 },
|
|
|
|
{ -2, 0, -1, 0, 0, -2, -5, -5 },
|
|
|
|
{ -4, -3, -3, -3, -1, -5, -15, -10 },
|
|
|
|
{ -6, -5, -5, -5, -3, -6, -20, -10 },
|
|
|
|
// clang-format on
|
|
|
|
};
|
|
|
|
|
|
|
|
// 0x518438
|
2022-06-19 02:57:46 -07:00
|
|
|
static Object* _critterClearObj = NULL;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// scrname.msg
|
|
|
|
//
|
|
|
|
// 0x56D754
|
2022-06-19 02:57:46 -07:00
|
|
|
static MessageList gCritterMessageList;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x56D75C
|
2022-06-19 02:57:46 -07:00
|
|
|
static char gDudeName[DUDE_NAME_MAX_LENGTH];
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x56D77C
|
2022-06-19 02:57:46 -07:00
|
|
|
static int _sneak_working;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x56D780
|
2022-06-19 02:57:46 -07:00
|
|
|
static int gKillsByType[KILL_TYPE_COUNT];
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// Something with radiation.
|
|
|
|
//
|
|
|
|
// 0x56D7CC
|
2022-06-19 02:57:46 -07:00
|
|
|
static int _old_rad_level;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// scrname_init
|
|
|
|
// 0x42CF50
|
|
|
|
int critterInit()
|
|
|
|
{
|
|
|
|
dudeResetName();
|
|
|
|
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Uninline;
|
|
|
|
critter_kill_count_clear();
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (!messageListInit(&gCritterMessageList)) {
|
|
|
|
debugPrint("\nError: Initing critter name message file!");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-05-28 02:34:49 -07:00
|
|
|
char path[COMPAT_MAX_PATH];
|
2022-12-08 12:05:50 -08:00
|
|
|
snprintf(path, sizeof(path), "%sscrname.msg", asc_5186C8);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (!messageListLoad(&gCritterMessageList, path)) {
|
|
|
|
debugPrint("\nError: Loading critter name message file!");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-11-10 07:07:23 -08:00
|
|
|
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_SCRNAME, &gCritterMessageList);
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42CFE4
|
|
|
|
void critterReset()
|
|
|
|
{
|
|
|
|
dudeResetName();
|
2022-09-01 08:41:37 -07:00
|
|
|
|
|
|
|
// NOTE: Uninline;
|
|
|
|
critter_kill_count_clear();
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D004
|
|
|
|
void critterExit()
|
|
|
|
{
|
2022-11-10 07:07:23 -08:00
|
|
|
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_SCRNAME, nullptr);
|
2022-05-19 01:51:26 -07:00
|
|
|
messageListFree(&gCritterMessageList);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D01C
|
|
|
|
int critterLoad(File* stream)
|
|
|
|
{
|
|
|
|
if (fileReadInt32(stream, &_sneak_working) == -1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
|
|
|
|
return protoCritterDataRead(stream, &(proto->critter.data));
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D058
|
|
|
|
int critterSave(File* stream)
|
|
|
|
{
|
|
|
|
if (fileWriteInt32(stream, _sneak_working) == -1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
|
|
|
|
return protoCritterDataWrite(stream, &(proto->critter.data));
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D094
|
|
|
|
void critterProtoDataCopy(CritterProtoData* dest, CritterProtoData* src)
|
|
|
|
{
|
|
|
|
memcpy(dest, src, sizeof(CritterProtoData));
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D0A8
|
|
|
|
char* critterGetName(Object* obj)
|
|
|
|
{
|
|
|
|
if (obj == gDude) {
|
|
|
|
return gDudeName;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (obj->field_80 == -1) {
|
|
|
|
if (obj->sid != -1) {
|
|
|
|
Script* script;
|
|
|
|
if (scriptGetScript(obj->sid, &script) != -1) {
|
|
|
|
obj->field_80 = script->field_14;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
char* name = NULL;
|
|
|
|
if (obj->field_80 != -1) {
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
messageListItem.num = 101 + obj->field_80;
|
|
|
|
if (messageListGetItem(&gCritterMessageList, &messageListItem)) {
|
|
|
|
name = messageListItem.text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name == NULL || *name == '\0') {
|
|
|
|
name = protoGetName(obj->pid);
|
|
|
|
}
|
|
|
|
|
|
|
|
_name_critter = name;
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D138
|
|
|
|
int dudeSetName(const char* name)
|
|
|
|
{
|
|
|
|
if (strlen(name) <= DUDE_NAME_MAX_LENGTH) {
|
|
|
|
strncpy(gDudeName, name, DUDE_NAME_MAX_LENGTH);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D170
|
|
|
|
void dudeResetName()
|
|
|
|
{
|
|
|
|
strncpy(gDudeName, "None", DUDE_NAME_MAX_LENGTH);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D18C
|
|
|
|
int critterGetHitPoints(Object* critter)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.hp : 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D1A4
|
2022-07-29 06:04:05 -07:00
|
|
|
int critterAdjustHitPoints(Object* critter, int hp)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int maximumHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS);
|
2022-07-29 06:04:05 -07:00
|
|
|
int newHp = critter->data.critter.hp + hp;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
critter->data.critter.hp = newHp;
|
|
|
|
if (maximumHp >= newHp) {
|
|
|
|
if (newHp <= 0 && (critter->data.critter.combat.results & DAM_DEAD) == 0) {
|
|
|
|
critterKill(critter, -1, true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
critter->data.critter.hp = maximumHp;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D1F8
|
|
|
|
int critterGetPoison(Object* critter)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.poison : 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Adjust critter's current poison by specified amount.
|
|
|
|
//
|
|
|
|
// For unknown reason this function only works on dude.
|
|
|
|
//
|
|
|
|
// The [amount] can either be positive (adds poison) or negative (removes
|
|
|
|
// poison).
|
|
|
|
//
|
|
|
|
// 0x42D210
|
|
|
|
int critterAdjustPoison(Object* critter, int amount)
|
|
|
|
{
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
|
|
|
|
if (critter != gDude) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (amount > 0) {
|
|
|
|
// Take poison resistance into account.
|
|
|
|
amount -= amount * critterGetStat(critter, STAT_POISON_RESISTANCE) / 100;
|
|
|
|
} else {
|
|
|
|
if (gDude->data.critter.poison <= 0) {
|
|
|
|
// Critter is not poisoned and we're want to decrease it even
|
|
|
|
// further, which makes no sense.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int newPoison = critter->data.critter.poison + amount;
|
|
|
|
if (newPoison > 0) {
|
|
|
|
critter->data.critter.poison = newPoison;
|
|
|
|
|
|
|
|
_queue_clear_type(EVENT_TYPE_POISON, NULL);
|
|
|
|
queueAddEvent(10 * (505 - 5 * newPoison), gDude, NULL, EVENT_TYPE_POISON);
|
|
|
|
|
|
|
|
// You have been poisoned!
|
|
|
|
messageListItem.num = 3000;
|
|
|
|
if (amount < 0) {
|
|
|
|
// You feel a little better.
|
|
|
|
messageListItem.num = 3002;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
critter->data.critter.poison = 0;
|
|
|
|
|
|
|
|
// You feel better.
|
|
|
|
messageListItem.num = 3003;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critter == gDude) {
|
|
|
|
indicatorBarRefresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D318
|
|
|
|
int poisonEventProcess(Object* obj, void* data)
|
|
|
|
{
|
|
|
|
if (obj != gDude) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
critterAdjustPoison(obj, -2);
|
|
|
|
critterAdjustHitPoints(obj, -1);
|
|
|
|
|
|
|
|
interfaceRenderHitPoints(false);
|
|
|
|
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
// You take damage from poison.
|
|
|
|
messageListItem.num = 3001;
|
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: Uninline.
|
|
|
|
int hitPoints = critterGetHitPoints(obj);
|
|
|
|
if (hitPoints > 5) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D38C
|
|
|
|
int critterGetRadiation(Object* obj)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
return PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER ? obj->data.critter.radiation : 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D3A4
|
|
|
|
int critterAdjustRadiation(Object* obj, int amount)
|
|
|
|
{
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
|
|
|
|
if (obj != gDude) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
|
|
|
|
if (amount > 0) {
|
|
|
|
amount -= critterGetStat(obj, STAT_RADIATION_RESISTANCE) * amount / 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (amount > 0) {
|
2022-10-05 03:06:49 -07:00
|
|
|
proto->critter.data.flags |= CRITTER_RADIATED;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (amount > 0) {
|
|
|
|
Object* geigerCounter = NULL;
|
|
|
|
|
|
|
|
Object* item1 = critterGetItem1(gDude);
|
|
|
|
if (item1 != NULL) {
|
|
|
|
if (item1->pid == PROTO_ID_GEIGER_COUNTER_I || item1->pid == PROTO_ID_GEIGER_COUNTER_II) {
|
|
|
|
geigerCounter = item1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Object* item2 = critterGetItem2(gDude);
|
|
|
|
if (item2 != NULL) {
|
|
|
|
if (item2->pid == PROTO_ID_GEIGER_COUNTER_I || item2->pid == PROTO_ID_GEIGER_COUNTER_II) {
|
|
|
|
geigerCounter = item2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (geigerCounter != NULL) {
|
|
|
|
if (miscItemIsOn(geigerCounter)) {
|
|
|
|
if (amount > 5) {
|
|
|
|
// The geiger counter is clicking wildly.
|
|
|
|
messageListItem.num = 1009;
|
|
|
|
} else {
|
|
|
|
// The geiger counter is clicking.
|
|
|
|
messageListItem.num = 1008;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (amount >= 10) {
|
|
|
|
// You have received a large dose of radiation.
|
|
|
|
messageListItem.num = 1007;
|
|
|
|
|
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
obj->data.critter.radiation += amount;
|
|
|
|
if (obj->data.critter.radiation < 0) {
|
|
|
|
obj->data.critter.radiation = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (obj == gDude) {
|
|
|
|
indicatorBarRefresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D4F4
|
|
|
|
int _critter_check_rads(Object* obj)
|
|
|
|
{
|
|
|
|
if (obj != gDude) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(obj->pid, &proto);
|
2022-10-05 03:06:49 -07:00
|
|
|
if ((proto->critter.data.flags & CRITTER_RADIATED) == 0) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
_old_rad_level = 0;
|
|
|
|
|
|
|
|
_queue_clear_type(EVENT_TYPE_RADIATION, _get_rad_damage_level);
|
|
|
|
|
|
|
|
// NOTE: Uninline
|
|
|
|
int radiation = critterGetRadiation(obj);
|
|
|
|
|
|
|
|
int radiationLevel;
|
|
|
|
if (radiation > 999)
|
|
|
|
radiationLevel = RADIATION_LEVEL_FATAL;
|
|
|
|
else if (radiation > 599)
|
|
|
|
radiationLevel = RADIATION_LEVEL_DEADLY;
|
|
|
|
else if (radiation > 399)
|
|
|
|
radiationLevel = RADIATION_LEVEL_CRITICAL;
|
|
|
|
else if (radiation > 199)
|
|
|
|
radiationLevel = RADIATION_LEVEL_ADVANCED;
|
|
|
|
else if (radiation > 99)
|
|
|
|
radiationLevel = RADIATION_LEVEL_MINOR;
|
|
|
|
else
|
|
|
|
radiationLevel = RADIATION_LEVEL_NONE;
|
|
|
|
|
|
|
|
if (statRoll(obj, STAT_ENDURANCE, gRadiationEnduranceModifiers[radiationLevel], NULL) <= ROLL_FAILURE) {
|
|
|
|
radiationLevel++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (radiationLevel > _old_rad_level) {
|
|
|
|
// Create timer event for applying radiation damage.
|
2022-05-21 08:22:03 -07:00
|
|
|
RadiationEvent* radiationEvent = (RadiationEvent*)internal_malloc(sizeof(*radiationEvent));
|
2022-05-19 01:51:26 -07:00
|
|
|
if (radiationEvent == NULL) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
radiationEvent->radiationLevel = radiationLevel;
|
|
|
|
radiationEvent->isHealing = 0;
|
2022-07-29 06:04:05 -07:00
|
|
|
queueAddEvent(GAME_TIME_TICKS_PER_HOUR * randomBetween(4, 18), obj, radiationEvent, EVENT_TYPE_RADIATION);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-10-05 03:06:49 -07:00
|
|
|
proto->critter.data.flags &= ~CRITTER_RADIATED;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D618
|
2022-06-19 02:57:46 -07:00
|
|
|
static int _get_rad_damage_level(Object* obj, void* data)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
2022-05-21 08:22:03 -07:00
|
|
|
RadiationEvent* radiationEvent = (RadiationEvent*)data;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
_old_rad_level = radiationEvent->radiationLevel;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D624
|
|
|
|
int _clear_rad_damage(Object* obj, void* data)
|
|
|
|
{
|
2022-05-21 08:22:03 -07:00
|
|
|
RadiationEvent* radiationEvent = (RadiationEvent*)data;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (radiationEvent->isHealing) {
|
|
|
|
_process_rads(obj, radiationEvent->radiationLevel, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Applies radiation.
|
|
|
|
//
|
|
|
|
// 0x42D63C
|
|
|
|
void _process_rads(Object* obj, int radiationLevel, bool isHealing)
|
|
|
|
{
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
|
|
|
|
if (radiationLevel == RADIATION_LEVEL_NONE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int radiationLevelIndex = radiationLevel - 1;
|
|
|
|
int modifier = isHealing ? -1 : 1;
|
|
|
|
|
|
|
|
if (obj == gDude) {
|
|
|
|
// Radiation level message, higher is worse.
|
|
|
|
messageListItem.num = 1000 + radiationLevelIndex;
|
2022-08-14 07:30:45 -07:00
|
|
|
|
|
|
|
// SFALL: Fix radiation message when removing radiation effects.
|
|
|
|
if (isHealing) {
|
|
|
|
// You feel better.
|
|
|
|
messageListItem.num = 3003;
|
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int effect = 0; effect < RADIATION_EFFECT_COUNT; effect++) {
|
|
|
|
int value = critterGetBonusStat(obj, gRadiationEffectStats[effect]);
|
|
|
|
value += modifier * gRadiationEffectPenalties[radiationLevelIndex][effect];
|
|
|
|
critterSetBonusStat(obj, gRadiationEffectStats[effect], value);
|
|
|
|
}
|
|
|
|
|
2022-08-14 07:30:45 -07:00
|
|
|
// SFALL: Prevent death when removing radiation effects.
|
|
|
|
if (!isHealing) {
|
|
|
|
if ((obj->data.critter.combat.results & DAM_DEAD) == 0) {
|
|
|
|
// Loop thru effects affecting primary stats. If any of the primary stat
|
|
|
|
// dropped below minimal value, kill it.
|
|
|
|
for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) {
|
|
|
|
int base = critterGetBaseStatWithTraitModifier(obj, gRadiationEffectStats[effect]);
|
|
|
|
int bonus = critterGetBonusStat(obj, gRadiationEffectStats[effect]);
|
|
|
|
if (base + bonus < PRIMARY_STAT_MIN) {
|
|
|
|
critterKill(obj, -1, 1);
|
|
|
|
break;
|
|
|
|
}
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((obj->data.critter.combat.results & DAM_DEAD) != 0) {
|
|
|
|
if (obj == gDude) {
|
|
|
|
// You have died from radiation sickness.
|
|
|
|
messageListItem.num = 1006;
|
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
2022-08-19 10:25:14 -07:00
|
|
|
// SFALL: Display a pop-up message box about death from radiation.
|
|
|
|
gameShowDeathDialog(messageListItem.text);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D740
|
|
|
|
int radiationEventProcess(Object* obj, void* data)
|
|
|
|
{
|
2022-05-21 08:22:03 -07:00
|
|
|
RadiationEvent* radiationEvent = (RadiationEvent*)data;
|
2022-05-19 01:51:26 -07:00
|
|
|
if (!radiationEvent->isHealing) {
|
|
|
|
// Schedule healing stats event in 7 days.
|
2022-05-21 08:22:03 -07:00
|
|
|
RadiationEvent* newRadiationEvent = (RadiationEvent*)internal_malloc(sizeof(*newRadiationEvent));
|
2022-05-19 01:51:26 -07:00
|
|
|
if (newRadiationEvent != NULL) {
|
|
|
|
_queue_clear_type(EVENT_TYPE_RADIATION, _clear_rad_damage);
|
|
|
|
newRadiationEvent->radiationLevel = radiationEvent->radiationLevel;
|
|
|
|
newRadiationEvent->isHealing = 1;
|
|
|
|
queueAddEvent(GAME_TIME_TICKS_PER_DAY * 7, obj, newRadiationEvent, EVENT_TYPE_RADIATION);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_process_rads(obj, radiationEvent->radiationLevel, radiationEvent->isHealing);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D7A0
|
|
|
|
int radiationEventRead(File* stream, void** dataPtr)
|
|
|
|
{
|
2022-05-21 08:22:03 -07:00
|
|
|
RadiationEvent* radiationEvent = (RadiationEvent*)internal_malloc(sizeof(*radiationEvent));
|
2022-05-19 01:51:26 -07:00
|
|
|
if (radiationEvent == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fileReadInt32(stream, &(radiationEvent->radiationLevel)) == -1) goto err;
|
|
|
|
if (fileReadInt32(stream, &(radiationEvent->isHealing)) == -1) goto err;
|
|
|
|
|
|
|
|
*dataPtr = radiationEvent;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
|
|
|
internal_free(radiationEvent);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D7FC
|
|
|
|
int radiationEventWrite(File* stream, void* data)
|
|
|
|
{
|
2022-05-21 08:22:03 -07:00
|
|
|
RadiationEvent* radiationEvent = (RadiationEvent*)data;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (fileWriteInt32(stream, radiationEvent->radiationLevel) == -1) return -1;
|
|
|
|
if (fileWriteInt32(stream, radiationEvent->isHealing) == -1) return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D82C
|
|
|
|
int critterGetDamageType(Object* obj)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
if (protoGetProto(obj->pid, &proto) == -1) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return proto->critter.data.damageType;
|
|
|
|
}
|
|
|
|
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Inlined.
|
|
|
|
//
|
|
|
|
// 0x42D860
|
|
|
|
static int critter_kill_count_clear()
|
|
|
|
{
|
|
|
|
memset(gKillsByType, 0, sizeof(gKillsByType));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
// 0x42D878
|
|
|
|
int killsIncByType(int killType)
|
|
|
|
{
|
|
|
|
if (killType != -1 && killType < KILL_TYPE_COUNT) {
|
|
|
|
gKillsByType[killType]++;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D8A8
|
|
|
|
int killsGetByType(int killType)
|
|
|
|
{
|
|
|
|
if (killType != -1 && killType < KILL_TYPE_COUNT) {
|
|
|
|
return gKillsByType[killType];
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D8C0
|
|
|
|
int killsLoad(File* stream)
|
|
|
|
{
|
|
|
|
if (fileReadInt32List(stream, gKillsByType, KILL_TYPE_COUNT) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D8F0
|
|
|
|
int killsSave(File* stream)
|
|
|
|
{
|
|
|
|
if (fileWriteInt32List(stream, gKillsByType, KILL_TYPE_COUNT) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D920
|
|
|
|
int critterGetKillType(Object* obj)
|
|
|
|
{
|
|
|
|
if (obj == gDude) {
|
|
|
|
int gender = critterGetStat(obj, STAT_GENDER);
|
|
|
|
if (gender == GENDER_FEMALE) {
|
|
|
|
return KILL_TYPE_WOMAN;
|
|
|
|
}
|
|
|
|
return KILL_TYPE_MAN;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(obj->pid, &proto);
|
|
|
|
|
|
|
|
return proto->critter.data.killType;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D974
|
|
|
|
char* killTypeGetName(int killType)
|
|
|
|
{
|
|
|
|
if (killType != -1 && killType < KILL_TYPE_COUNT) {
|
|
|
|
if (killType >= 0 && killType < KILL_TYPE_COUNT) {
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
return getmsg(&gProtoMessageList, &messageListItem, 1450 + killType);
|
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return byte_501494;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D9B4
|
|
|
|
char* killTypeGetDescription(int killType)
|
|
|
|
{
|
|
|
|
if (killType != -1 && killType < KILL_TYPE_COUNT) {
|
|
|
|
if (killType >= 0 && killType < KILL_TYPE_COUNT) {
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
return getmsg(&gProtoMessageList, &messageListItem, 1469 + killType);
|
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return byte_501494;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42D9F4
|
|
|
|
int _critter_heal_hours(Object* critter, int a2)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critter->data.critter.hp < critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS)) {
|
|
|
|
critterAdjustHitPoints(critter, 14 * (a2 / 3));
|
|
|
|
}
|
|
|
|
|
|
|
|
critter->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DA54
|
2022-06-19 02:57:46 -07:00
|
|
|
static int _critterClearObjDrugs(Object* obj, void* data)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
|
|
|
return obj == _critterClearObj;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DA64
|
|
|
|
void critterKill(Object* critter, int anim, bool a3)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int elevation = critter->elevation;
|
|
|
|
|
|
|
|
partyMemberRemove(critter);
|
|
|
|
|
|
|
|
// NOTE: Original code uses goto to jump out from nested conditions below.
|
|
|
|
bool shouldChangeFid = false;
|
|
|
|
int fid;
|
|
|
|
if (_critter_is_prone(critter)) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int current = FID_ANIM_TYPE(critter->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (current == ANIM_FALL_BACK || current == ANIM_FALL_FRONT) {
|
|
|
|
bool back = false;
|
|
|
|
if (current == ANIM_FALL_BACK) {
|
|
|
|
back = true;
|
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_FRONT_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (!artExists(fid)) {
|
|
|
|
back = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (back) {
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
shouldChangeFid = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (anim < 0) {
|
|
|
|
anim = LAST_SF_DEATH_ANIM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (anim > LAST_SF_DEATH_ANIM) {
|
|
|
|
debugPrint("\nError: Critter Kill: death_frame out of range!");
|
|
|
|
anim = LAST_SF_DEATH_ANIM;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
_obj_fix_violence_settings(&fid);
|
|
|
|
if (!artExists(fid)) {
|
|
|
|
debugPrint("\nError: Critter Kill: Can't match fid!");
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_BLOOD_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
_obj_fix_violence_settings(&fid);
|
|
|
|
}
|
|
|
|
|
|
|
|
shouldChangeFid = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Rect updatedRect;
|
|
|
|
Rect tempRect;
|
|
|
|
|
|
|
|
if (shouldChangeFid) {
|
|
|
|
objectSetFrame(critter, 0, &updatedRect);
|
|
|
|
|
|
|
|
objectSetFid(critter, fid, &tempRect);
|
|
|
|
rectUnion(&updatedRect, &tempRect, &updatedRect);
|
|
|
|
}
|
|
|
|
|
2022-10-05 03:06:49 -07:00
|
|
|
if (!_critter_flag_check(critter->pid, CRITTER_FLAT)) {
|
2022-05-19 01:51:26 -07:00
|
|
|
critter->flags |= OBJECT_NO_BLOCK;
|
|
|
|
_obj_toggle_flat(critter, &tempRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: using uninitialized updatedRect/tempRect if fid was not set.
|
|
|
|
|
|
|
|
rectUnion(&updatedRect, &tempRect, &updatedRect);
|
|
|
|
|
|
|
|
_obj_turn_off_light(critter, &tempRect);
|
|
|
|
rectUnion(&updatedRect, &tempRect, &updatedRect);
|
|
|
|
|
|
|
|
critter->data.critter.hp = 0;
|
|
|
|
critter->data.critter.combat.results |= DAM_DEAD;
|
|
|
|
|
|
|
|
if (critter->sid != -1) {
|
|
|
|
scriptRemove(critter->sid);
|
|
|
|
critter->sid = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_critterClearObj = critter;
|
|
|
|
_queue_clear_type(EVENT_TYPE_DRUG, _critterClearObjDrugs);
|
|
|
|
|
2022-08-17 22:41:15 -07:00
|
|
|
itemDestroyAllHidden(critter);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (a3) {
|
|
|
|
tileWindowRefreshRect(&updatedRect, elevation);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critter == gDude) {
|
|
|
|
endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH);
|
|
|
|
_game_user_wants_to_quit = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns experience for killing [critter].
|
|
|
|
//
|
|
|
|
// 0x42DCB8
|
|
|
|
int critterGetExp(Object* critter)
|
|
|
|
{
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(critter->pid, &proto);
|
|
|
|
return proto->critter.data.experience;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DCDC
|
|
|
|
bool critterIsActive(Object* critter)
|
|
|
|
{
|
|
|
|
if (critter == NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD)) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (critter->data.critter.combat.results & DAM_DEAD) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DD18
|
|
|
|
bool critterIsDead(Object* critter)
|
|
|
|
{
|
|
|
|
if (critter == NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) <= 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((critter->data.critter.combat.results & DAM_DEAD) != 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DD58
|
|
|
|
bool critterIsCrippled(Object* critter)
|
|
|
|
{
|
|
|
|
if (critter == NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (critter->data.critter.combat.results & DAM_CRIP) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DD80
|
|
|
|
bool _critter_is_prone(Object* critter)
|
|
|
|
{
|
|
|
|
if (critter == NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = FID_ANIM_TYPE(critter->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
return (critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0
|
2023-04-26 05:35:02 -07:00
|
|
|
|| (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM)
|
|
|
|
|| (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// critter_body_type
|
|
|
|
// 0x42DDC4
|
|
|
|
int critterGetBodyType(Object* critter)
|
|
|
|
{
|
|
|
|
if (critter == NULL) {
|
|
|
|
debugPrint("\nError: critter_body_type: pobj was NULL!");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(critter->pid, &proto);
|
|
|
|
return proto->critter.data.bodyType;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DE58
|
|
|
|
int gcdLoad(const char* path)
|
|
|
|
{
|
|
|
|
File* stream = fileOpen(path, "rb");
|
|
|
|
if (stream == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
|
|
|
|
if (protoCritterDataRead(stream, &(proto->critter.data)) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileRead(gDudeName, DUDE_NAME_MAX_LENGTH, 1, stream);
|
|
|
|
|
|
|
|
if (skillsLoad(stream) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (traitsLoad(stream) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-06-10 14:32:57 -07:00
|
|
|
if (fileReadInt32(stream, &gCharacterEditorRemainingCharacterPoints) == -1) {
|
2022-05-19 01:51:26 -07:00
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
proto->critter.data.baseStats[STAT_DAMAGE_RESISTANCE_EMP] = 100;
|
|
|
|
proto->critter.data.bodyType = 0;
|
|
|
|
proto->critter.data.experience = 0;
|
|
|
|
proto->critter.data.killType = 0;
|
|
|
|
|
|
|
|
fileClose(stream);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42DF70
|
|
|
|
int protoCritterDataRead(File* stream, CritterProtoData* critterData)
|
|
|
|
{
|
|
|
|
if (fileReadInt32(stream, &(critterData->flags)) == -1) return -1;
|
|
|
|
if (fileReadInt32List(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1;
|
|
|
|
if (fileReadInt32List(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1;
|
|
|
|
if (fileReadInt32List(stream, critterData->skills, SKILL_COUNT) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(critterData->bodyType)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(critterData->experience)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(critterData->killType)) == -1) return -1;
|
|
|
|
|
|
|
|
// NOTE: For unknown reason damage type is not present in two protos: Sentry
|
|
|
|
// Bot and Weak Brahmin. These two protos are 412 bytes, not 416.
|
|
|
|
//
|
|
|
|
// Given that only Floating Eye Bot, Floater, and Nasty Floater have
|
|
|
|
// natural damage type other than normal, I think addition of natural
|
|
|
|
// damage type as a feature was a last minute design decision. Most protos
|
|
|
|
// were updated, but not all. Another suggestion is that some team member
|
|
|
|
// used outdated toolset to build those two protos (mapper or whatever
|
|
|
|
// they used to create protos in the first place).
|
|
|
|
//
|
|
|
|
// Regardless of the reason, damage type is considered optional by original
|
|
|
|
// code as seen at 0x42E01B.
|
|
|
|
if (fileReadInt32(stream, &(critterData->damageType)) == -1) {
|
|
|
|
critterData->damageType = DAMAGE_TYPE_NORMAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E08C
|
|
|
|
int gcdSave(const char* path)
|
|
|
|
{
|
|
|
|
File* stream = fileOpen(path, "wb");
|
|
|
|
if (stream == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
|
|
|
|
if (protoCritterDataWrite(stream, &(proto->critter.data)) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileWrite(gDudeName, DUDE_NAME_MAX_LENGTH, 1, stream);
|
|
|
|
|
|
|
|
if (skillsSave(stream) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (traitsSave(stream) == -1) {
|
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-06-10 14:32:57 -07:00
|
|
|
if (fileWriteInt32(stream, gCharacterEditorRemainingCharacterPoints) == -1) {
|
2022-05-19 01:51:26 -07:00
|
|
|
fileClose(stream);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileClose(stream);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E174
|
|
|
|
int protoCritterDataWrite(File* stream, CritterProtoData* critterData)
|
|
|
|
{
|
|
|
|
if (fileWriteInt32(stream, critterData->flags) == -1) return -1;
|
|
|
|
if (fileWriteInt32List(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1;
|
|
|
|
if (fileWriteInt32List(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1;
|
|
|
|
if (fileWriteInt32List(stream, critterData->skills, SKILL_COUNT) == -1) return -1;
|
|
|
|
if (fileWriteInt32(stream, critterData->bodyType) == -1) return -1;
|
|
|
|
if (fileWriteInt32(stream, critterData->experience) == -1) return -1;
|
|
|
|
if (fileWriteInt32(stream, critterData->killType) == -1) return -1;
|
|
|
|
if (fileWriteInt32(stream, critterData->damageType) == -1) return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E220
|
|
|
|
void dudeDisableState(int state)
|
|
|
|
{
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
|
|
|
|
proto->critter.data.flags &= ~(1 << state);
|
|
|
|
|
|
|
|
if (state == DUDE_STATE_SNEAKING) {
|
|
|
|
queueRemoveEventsByType(gDude, EVENT_TYPE_SNEAK);
|
|
|
|
}
|
|
|
|
|
|
|
|
indicatorBarRefresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E26C
|
|
|
|
void dudeEnableState(int state)
|
|
|
|
{
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
|
|
|
|
proto->critter.data.flags |= (1 << state);
|
|
|
|
|
|
|
|
if (state == DUDE_STATE_SNEAKING) {
|
|
|
|
sneakEventProcess(NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
indicatorBarRefresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E2B0
|
|
|
|
void dudeToggleState(int state)
|
|
|
|
{
|
|
|
|
// NOTE: Uninline.
|
|
|
|
if (dudeHasState(state)) {
|
|
|
|
dudeDisableState(state);
|
|
|
|
} else {
|
|
|
|
dudeEnableState(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E2F8
|
|
|
|
bool dudeHasState(int state)
|
|
|
|
{
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(gDude->pid, &proto);
|
|
|
|
return (proto->critter.data.flags & (1 << state)) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E32C
|
|
|
|
int sneakEventProcess(Object* obj, void* data)
|
|
|
|
{
|
|
|
|
int time;
|
|
|
|
|
|
|
|
int sneak = skillGetValue(gDude, SKILL_SNEAK);
|
|
|
|
if (skillRoll(gDude, SKILL_SNEAK, 0, NULL) < ROLL_SUCCESS) {
|
|
|
|
time = 600;
|
|
|
|
_sneak_working = false;
|
|
|
|
|
|
|
|
if (sneak > 250)
|
|
|
|
time = 100;
|
|
|
|
else if (sneak > 200)
|
|
|
|
time = 120;
|
|
|
|
else if (sneak > 170)
|
|
|
|
time = 150;
|
|
|
|
else if (sneak > 135)
|
|
|
|
time = 200;
|
|
|
|
else if (sneak > 100)
|
|
|
|
time = 300;
|
|
|
|
else if (sneak > 80)
|
|
|
|
time = 400;
|
|
|
|
} else {
|
|
|
|
time = 600;
|
|
|
|
_sneak_working = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
queueAddEvent(time, gDude, NULL, EVENT_TYPE_SNEAK);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E3E4
|
|
|
|
int _critter_sneak_clear(Object* obj, void* data)
|
|
|
|
{
|
|
|
|
dudeDisableState(DUDE_STATE_SNEAKING);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if dude is really sneaking.
|
|
|
|
//
|
|
|
|
// 0x42E3F4
|
|
|
|
bool dudeIsSneaking()
|
|
|
|
{
|
|
|
|
// NOTE: Uninline.
|
|
|
|
if (dudeHasState(DUDE_STATE_SNEAKING)) {
|
|
|
|
return _sneak_working;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E424
|
|
|
|
int knockoutEventProcess(Object* obj, void* data)
|
|
|
|
{
|
|
|
|
if ((obj->data.critter.combat.results & DAM_DEAD) != 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
obj->data.critter.combat.results &= ~(DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN);
|
|
|
|
obj->data.critter.combat.results |= DAM_KNOCKED_DOWN;
|
|
|
|
|
|
|
|
if (isInCombat()) {
|
2023-01-16 05:49:50 -08:00
|
|
|
obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_ENGAGING;
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
|
|
|
_dude_standup(obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E460
|
|
|
|
int _critter_wake_clear(Object* obj, void* data)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((obj->data.critter.combat.results & DAM_DEAD) != 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
obj->data.critter.combat.results &= ~(DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN);
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_STAND, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
objectSetFid(obj, fid, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E4C0
|
|
|
|
int _critter_set_who_hit_me(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
if (a1 == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (a2 != NULL && FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(a1->pid) == OBJ_TYPE_CRITTER) {
|
2022-08-01 09:47:09 -07:00
|
|
|
if (a2 == NULL || a1->data.critter.combat.team != a2->data.critter.combat.team || (statRoll(a1, STAT_INTELLIGENCE, -1, NULL) < 2 && (!objectIsPartyMember(a1) || !objectIsPartyMember(a2)))) {
|
2022-05-19 01:51:26 -07:00
|
|
|
a1->data.critter.combat.whoHitMe = a2;
|
|
|
|
if (a2 == gDude) {
|
|
|
|
reactionSetValue(a1, -3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E564
|
|
|
|
bool _critter_can_obj_dude_rest()
|
|
|
|
{
|
|
|
|
bool v1 = false;
|
2022-09-15 01:42:02 -07:00
|
|
|
if (!wmMapCanRestHere(gElevation)) {
|
2022-05-19 01:51:26 -07:00
|
|
|
v1 = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
Object** critterList;
|
|
|
|
int critterListLength = objectListCreate(-1, gElevation, OBJ_TYPE_CRITTER, &critterList);
|
|
|
|
|
|
|
|
// TODO: Check conditions in this loop.
|
|
|
|
for (int index = 0; index < critterListLength; index++) {
|
|
|
|
Object* critter = critterList[index];
|
|
|
|
if ((critter->data.critter.combat.results & DAM_DEAD) != 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critter == gDude) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critter->data.critter.combat.whoHitMe != gDude) {
|
|
|
|
if (!v1 || critter->data.critter.combat.team == gDude->data.critter.combat.team) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critterListLength != 0) {
|
|
|
|
objectListFree(critterList);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E62C
|
|
|
|
int critterGetMovementPointCostAdjustedForCrippledLegs(Object* critter, int actionPoints)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int flags = critter->data.critter.combat.results;
|
|
|
|
if ((flags & DAM_CRIP_LEG_LEFT) != 0 && (flags & DAM_CRIP_LEG_RIGHT) != 0) {
|
|
|
|
return 8 * actionPoints;
|
2022-07-06 04:23:06 -07:00
|
|
|
} else if ((flags & DAM_CRIP_LEG_ANY) != 0) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return 4 * actionPoints;
|
|
|
|
} else {
|
|
|
|
return actionPoints;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E66C
|
|
|
|
bool critterIsEncumbered(Object* critter)
|
|
|
|
{
|
|
|
|
int maxWeight = critterGetStat(critter, STAT_CARRY_WEIGHT);
|
|
|
|
int currentWeight = objectGetInventoryWeight(critter);
|
|
|
|
return maxWeight < currentWeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x42E690
|
|
|
|
bool critterIsFleeing(Object* critter)
|
|
|
|
{
|
|
|
|
return critter != NULL
|
|
|
|
? (critter->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0
|
|
|
|
: false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks proto critter flag.
|
|
|
|
//
|
|
|
|
// 0x42E6AC
|
|
|
|
bool _critter_flag_check(int pid, int flag)
|
|
|
|
{
|
|
|
|
if (pid == -1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
protoGetProto(pid, &proto);
|
|
|
|
return (proto->critter.data.flags & flag) != 0;
|
|
|
|
}
|
2022-09-23 05:43:44 -07:00
|
|
|
|
|
|
|
} // namespace fallout
|