fallout2-ce/src/perk.cc

691 lines
22 KiB
C++

#include "perk.h"
#include "debug.h"
#include "game.h"
#include "game_config.h"
#include "memory.h"
#include "object.h"
#include "party_member.h"
#include "platform_compat.h"
#include "skill.h"
#include "stat.h"
#include <stdio.h>
// 0x519DCC
PerkDescription gPerkDescriptions[PERK_COUNT] = {
{ NULL, NULL, 72, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 5, 0, 0, 0, 0, 0 },
{ NULL, NULL, 73, 1, 15, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 },
{ NULL, NULL, 74, 3, 3, 11, 2, -1, 0, 0, -1, 0, 6, 0, 0, 0, 0, 6, 0 },
{ NULL, NULL, 75, 2, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },
{ NULL, NULL, 76, 2, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 6 },
{ NULL, NULL, 77, 1, 15, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 6, 7, 0 },
{ NULL, NULL, 78, 3, 3, 13, 2, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },
{ NULL, NULL, 79, 3, 3, 14, 2, -1, 0, 0, -1, 0, 0, 0, 6, 0, 0, 0, 0 },
{ NULL, NULL, 80, 3, 6, 15, 5, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 6 },
{ NULL, NULL, 81, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },
{ NULL, NULL, 82, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 6, 0, 0, 0 },
{ NULL, NULL, 83, 2, 6, 31, 15, -1, 0, 0, -1, 0, 0, 0, 6, 0, 4, 0, 0 },
{ NULL, NULL, 84, 3, 3, 24, 10, -1, 0, 0, -1, 0, 0, 0, 6, 0, 0, 0, 6 },
{ NULL, NULL, 85, 3, 3, 12, 50, -1, 0, 0, -1, 0, 6, 0, 6, 0, 0, 0, 0 },
{ NULL, NULL, 86, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 6, 0, 0 },
{ NULL, NULL, 87, 1, 6, -1, 0, 8, 50, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 },
{ NULL, NULL, 88, 1, 3, -1, 0, 17, 40, 0, -1, 0, 0, 0, 6, 0, 6, 0, 0 },
{ NULL, NULL, 89, 1, 12, -1, 0, 15, 75, 0, -1, 0, 0, 0, 0, 7, 0, 0, 0 },
{ NULL, NULL, 90, 3, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 6, 0, 0 },
{ NULL, NULL, 91, 2, 3, -1, 0, 6, 40, 0, -1, 0, 0, 7, 0, 0, 5, 6, 0 },
{ NULL, NULL, 92, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 8 },
{ NULL, NULL, 93, 1, 9, 16, 20, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 4, 6 },
{ NULL, NULL, 94, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 5, 0, 0 },
{ NULL, NULL, 95, 1, 24, -1, 0, 3, 80, 0, -1, 0, 8, 0, 0, 0, 0, 8, 0 },
{ NULL, NULL, 96, 1, 24, -1, 0, 0, 80, 0, -1, 0, 0, 8, 0, 0, 0, 8, 0 },
{ NULL, NULL, 97, 1, 18, -1, 0, 8, 80, 2, 3, 80, 0, 0, 0, 0, 0, 10, 0 },
{ NULL, NULL, 98, 2, 12, 8, 1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },
{ NULL, NULL, 99, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 100, 2, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 4, 0, 0, 0, 0 },
{ NULL, NULL, 101, 1, 9, 9, 5, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 },
{ NULL, NULL, 102, 2, 6, 32, 25, -1, 0, 0, -1, 0, 0, 0, 3, 0, 0, 0, 0 },
{ NULL, NULL, 103, 1, 12, -1, 0, 13, 40, 1, 12, 40, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 104, 1, 12, -1, 0, 6, 40, 1, 7, 40, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 105, 1, 12, -1, 0, 10, 50, 2, 9, 50, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 106, 1, 9, -1, 0, 14, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 107, 3, 6, -1, 0, -1, 0, 0, -1, 0, -9, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 108, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 4, 0, 0, 0, 0, 0 },
{ NULL, NULL, 109, 1, 15, -1, 0, 10, 80, 0, -1, 0, 0, 0, 0, 0, 0, 8, 0 },
{ NULL, NULL, 110, 1, 6, -1, 0, 8, 60, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 111, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 10, 0, 0, 0 },
{ NULL, NULL, 112, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 8 },
{ NULL, NULL, 113, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 114, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 5, 0, 0, 0, 0 },
{ NULL, NULL, 115, 2, 6, -1, 0, 17, 40, 0, -1, 0, 0, 0, 6, 0, 0, 0, 0 },
{ NULL, NULL, 116, 1, 310, -1, 0, 17, 25, 0, -1, 0, 0, 0, 0, 0, 5, 0, 0 },
{ NULL, NULL, 117, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 0, 0, 0 },
{ NULL, NULL, 118, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 4 },
{ NULL, NULL, 119, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },
{ NULL, NULL, 120, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },
{ NULL, NULL, 121, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 4, 0, 0 },
{ NULL, NULL, 122, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 4, 0, 0 },
{ NULL, NULL, 123, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 124, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 125, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 126, -1, 1, -1, 0, -1, 0, 0, -1, 0, -2, 0, -2, 0, 0, -3, 0 },
{ NULL, NULL, 127, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -3, -2, 0 },
{ NULL, NULL, 128, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -2, 0, 0 },
{ NULL, NULL, 129, -1, 1, 31, -20, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 130, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 131, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 132, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 133, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 134, -1, 1, 31, 30, -1, 0, 0, -1, 0, 3, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 135, -1, 1, 31, 20, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 136, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 137, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 138, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 139, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 140, -1, 1, 31, 60, -1, 0, 0, -1, 0, 4, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 141, -1, 1, 31, 75, -1, 0, 0, -1, 0, 4, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 136, -1, 1, 8, -1, -1, 0, 0, -1, 0, -1, -1, 0, 0, 0, 0, 0 },
{ NULL, NULL, 149, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, -2, 0, 0, -1, 0, -1 },
{ NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 2, 0, 0, 0 },
{ NULL, NULL, 158, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 157, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 157, -1, 1, 3, -1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 168, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 168, -1, 1, 3, -1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 172, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 155, 1, 6, -1, 0, -1, 0, 0, -1, 0, -10, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 156, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },
{ NULL, NULL, 122, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 6, 0, 0 },
{ NULL, NULL, 39, 1, 9, -1, 0, 11, 75, 0, -1, 0, 0, 0, 0, 0, 0, 4, 0 },
{ NULL, NULL, 44, 1, 6, -1, 0, 16, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 0, 1, 12, -1, 0, -1, 0, 0, -1, 0, -10, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 1, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, -10, 0, 0, 0, 0, 0 },
{ NULL, NULL, 2, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, -10, 0, 0, 0, 0 },
{ NULL, NULL, 3, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, -10, 0, 0, 0 },
{ NULL, NULL, 4, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -10, 0, 0 },
{ NULL, NULL, 5, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, -10, 0 },
{ NULL, NULL, 6, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -10 },
{ NULL, NULL, 160, 1, 6, -1, 0, 10, 50, 2, 0x4000000, 50, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 161, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 159, 1, 12, -1, 0, 3, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 163, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 5, 0, 0, 5, 0 },
{ NULL, NULL, 162, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 6, 0, 0, 0 },
{ NULL, NULL, 164, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 5 },
{ NULL, NULL, 165, 1, 12, -1, 0, 7, 60, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 166, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, -10, 0, 0, 0 },
{ NULL, NULL, 43, 1, 6, -1, 0, 15, 50, 2, 14, 50, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 167, 1, 6, 12, 50, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 169, 1, 9, -1, 0, 1, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 170, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },
{ NULL, NULL, 121, 1, 6, -1, 0, 15, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 171, 1, 3, -1, 0, -1, 0, 0, -1, 0, 6, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 38, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 173, 1, 12, -1, 0, -1, 0, 0, -1, 0, -7, 0, 0, 0, 0, 5, 0 },
{ NULL, NULL, 104, -1, 1, -1, 0, 7, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 142, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 142, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 52, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 52, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 104, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 104, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 35, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 35, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ NULL, NULL, 64, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },
};
// An array of perk ranks for each party member.
//
// 0x51C120
PerkRankData* gPartyMemberPerkRanks = NULL;
// Amount of experience points granted when player selected "Here and now"
// perk.
//
// 0x51C124
int gHereAndNowBonusExperience = 0;
// perk.msg
//
// 0x6642D4
MessageList gPerksMessageList;
// 0x4965A0
int perksInit()
{
gPartyMemberPerkRanks = (PerkRankData*)internal_malloc(sizeof(*gPartyMemberPerkRanks) * gPartyMemberDescriptionsLength);
if (gPartyMemberPerkRanks == NULL) {
return -1;
}
perkResetRanks();
if (!messageListInit(&gPerksMessageList)) {
return -1;
}
char path[COMPAT_MAX_PATH];
sprintf(path, "%s%s", asc_5186C8, "perk.msg");
if (!messageListLoad(&gPerksMessageList, path)) {
return -1;
}
for (int perk = 0; perk < PERK_COUNT; perk++) {
MessageListItem messageListItem;
messageListItem.num = 101 + perk;
if (messageListGetItem(&gPerksMessageList, &messageListItem)) {
gPerkDescriptions[perk].name = messageListItem.text;
}
messageListItem.num = 1101 + perk;
if (messageListGetItem(&gPerksMessageList, &messageListItem)) {
gPerkDescriptions[perk].description = messageListItem.text;
}
}
return 0;
}
// 0x4966B0
void perksReset()
{
perkResetRanks();
}
// 0x4966B8
void perksExit()
{
messageListFree(&gPerksMessageList);
if (gPartyMemberPerkRanks != NULL) {
internal_free(gPartyMemberPerkRanks);
gPartyMemberPerkRanks = NULL;
}
}
// 0x4966E4
int perksLoad(File* stream)
{
for (int index = 0; index < gPartyMemberDescriptionsLength; index++) {
PerkRankData* ranksData = &(gPartyMemberPerkRanks[index]);
for (int perk = 0; perk < PERK_COUNT; perk++) {
if (fileReadInt32(stream, &(ranksData->ranks[perk])) == -1) {
return -1;
}
}
}
return 0;
}
// 0x496738
int perksSave(File* stream)
{
for (int index = 0; index < gPartyMemberDescriptionsLength; index++) {
PerkRankData* ranksData = &(gPartyMemberPerkRanks[index]);
for (int perk = 0; perk < PERK_COUNT; perk++) {
if (fileWriteInt32(stream, ranksData->ranks[perk]) == -1) {
return -1;
}
}
}
return 0;
}
// perkGetLevelData
// 0x49678C
PerkRankData* perkGetRankData(Object* critter)
{
if (critter == gDude) {
return gPartyMemberPerkRanks;
}
for (int index = 1; index < gPartyMemberDescriptionsLength; index++) {
if (critter->pid == gPartyMemberPids[index]) {
return gPartyMemberPerkRanks + index;
}
}
debugPrint("\nError: perkGetLevelData: Can't find party member match!");
return gPartyMemberPerkRanks;
}
// 0x49680C
bool perkCanAdd(Object* critter, int perk)
{
if (!perkIsValid(perk)) {
return false;
}
PerkDescription* perkDescription = &(gPerkDescriptions[perk]);
if (perkDescription->maxRank == -1) {
return false;
}
PerkRankData* ranksData = perkGetRankData(critter);
if (ranksData->ranks[perk] >= perkDescription->maxRank) {
return false;
}
if (critter == gDude) {
if (pcGetStat(PC_STAT_LEVEL) < perkDescription->minLevel) {
return false;
}
}
bool v1 = true;
int param1 = perkDescription->param1;
if (param1 != -1) {
bool isVariable = false;
if ((param1 & 0x4000000) != 0) {
isVariable = true;
param1 &= ~0x4000000;
}
int value1 = perkDescription->value1;
if (value1 < 0) {
if (isVariable) {
if (gameGetGlobalVar(param1) >= value1) {
v1 = false;
}
} else {
if (skillGetValue(critter, param1) >= -value1) {
v1 = false;
}
}
} else {
if (isVariable) {
if (gameGetGlobalVar(param1) < value1) {
v1 = false;
}
} else {
if (skillGetValue(critter, param1) < value1) {
v1 = false;
}
}
}
}
if (!v1 || perkDescription->field_24 == 2) {
if (perkDescription->field_24 == 0) {
return false;
}
if (!v1 && perkDescription->field_24 == 2) {
return false;
}
int param2 = perkDescription->param2;
bool isVariable = false;
if (param2 != -1) {
if ((param2 & 0x4000000) != 0) {
isVariable = true;
param2 &= ~0x4000000;
}
}
if (param2 == -1) {
return false;
}
int value2 = perkDescription->value2;
if (value2 < 0) {
if (isVariable) {
if (gameGetGlobalVar(param2) >= value2) {
return false;
}
} else {
if (skillGetValue(critter, param2) >= -value2) {
return false;
}
}
} else {
if (isVariable) {
if (gameGetGlobalVar(param2) < value2) {
return false;
}
} else {
if (skillGetValue(critter, param2) < value2) {
return false;
}
}
}
}
for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {
if (perkDescription->stats[stat] < 0) {
if (critterGetStat(critter, stat) >= -perkDescription->stats[stat]) {
return false;
}
} else {
if (critterGetStat(critter, stat) < perkDescription->stats[stat]) {
return false;
}
}
}
return true;
}
// Resets party member perks.
//
// 0x496A0C
void perkResetRanks()
{
for (int index = 0; index < gPartyMemberDescriptionsLength; index++) {
PerkRankData* ranksData = &(gPartyMemberPerkRanks[index]);
for (int perk = 0; perk < PERK_COUNT; perk++) {
ranksData->ranks[perk] = 0;
}
}
}
// 0x496A5C
int perkAdd(Object* critter, int perk)
{
if (!perkIsValid(perk)) {
return -1;
}
if (!perkCanAdd(critter, perk)) {
return -1;
}
PerkRankData* ranksData = perkGetRankData(critter);
ranksData->ranks[perk] += 1;
perkAddEffect(critter, perk);
return 0;
}
// perk_add_force
// 0x496A9C
int perkAddForce(Object* critter, int perk)
{
if (!perkIsValid(perk)) {
return -1;
}
PerkRankData* ranksData = perkGetRankData(critter);
int value = ranksData->ranks[perk];
int maxRank = gPerkDescriptions[perk].maxRank;
if (maxRank != -1 && value >= maxRank) {
return -1;
}
ranksData->ranks[perk] += 1;
perkAddEffect(critter, perk);
return 0;
}
// perk_sub
// 0x496AFC
int perkRemove(Object* critter, int perk)
{
if (!perkIsValid(perk)) {
return -1;
}
PerkRankData* ranksData = perkGetRankData(critter);
int value = ranksData->ranks[perk];
if (value < 1) {
return -1;
}
ranksData->ranks[perk] -= 1;
perkRemoveEffect(critter, perk);
return 0;
}
// Returns perks available to pick.
//
// 0x496B44
int perkGetAvailablePerks(Object* critter, int* perks)
{
int count = 0;
for (int perk = 0; perk < PERK_COUNT; perk++) {
if (perkCanAdd(critter, perk)) {
perks[count] = perk;
count++;
}
}
return count;
}
// has_perk
// 0x496B78
int perkGetRank(Object* critter, int perk)
{
if (!perkIsValid(perk)) {
return 0;
}
PerkRankData* ranksData = perkGetRankData(critter);
return ranksData->ranks[perk];
}
// 0x496B90
char* perkGetName(int perk)
{
if (!perkIsValid(perk)) {
return NULL;
}
return gPerkDescriptions[perk].name;
}
// 0x496BB4
char* perkGetDescription(int perk)
{
if (!perkIsValid(perk)) {
return NULL;
}
return gPerkDescriptions[perk].description;
}
// 0x496BD8
int perkGetFrmId(int perk)
{
if (!perkIsValid(perk)) {
return 0;
}
return gPerkDescriptions[perk].frmId;
}
// perk_add_effect
// 0x496BFC
void perkAddEffect(Object* critter, int perk)
{
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
debugPrint("\nERROR: perk_add_effect: Was called on non-critter!");
return;
}
if (!perkIsValid(perk)) {
return;
}
PerkDescription* perkDescription = &(gPerkDescriptions[perk]);
if (perkDescription->stat != -1) {
int value = critterGetBonusStat(critter, perkDescription->stat);
critterSetBonusStat(critter, perkDescription->stat, value + perkDescription->statModifier);
}
if (perk == PERK_HERE_AND_NOW) {
PerkRankData* ranksData = perkGetRankData(critter);
ranksData->ranks[PERK_HERE_AND_NOW] -= 1;
int level = pcGetStat(PC_STAT_LEVEL);
gHereAndNowBonusExperience = pcGetExperienceForLevel(level + 1) - pcGetStat(PC_STAT_EXPERIENCE);
pcAddExperienceWithOptions(gHereAndNowBonusExperience, false);
ranksData->ranks[PERK_HERE_AND_NOW] += 1;
}
if (perkDescription->maxRank == -1) {
for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {
int value = critterGetBonusStat(critter, stat);
critterSetBonusStat(critter, stat, value + perkDescription->stats[stat]);
}
}
}
// perk_remove_effect
// 0x496CE0
void perkRemoveEffect(Object* critter, int perk)
{
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
debugPrint("\nERROR: perk_remove_effect: Was called on non-critter!");
return;
}
if (!perkIsValid(perk)) {
return;
}
PerkDescription* perkDescription = &(gPerkDescriptions[perk]);
if (perkDescription->stat != -1) {
int value = critterGetBonusStat(critter, perkDescription->stat);
critterSetBonusStat(critter, perkDescription->stat, value - perkDescription->statModifier);
}
if (perk == PERK_HERE_AND_NOW) {
int xp = pcGetStat(PC_STAT_EXPERIENCE);
pcSetStat(PC_STAT_EXPERIENCE, xp - gHereAndNowBonusExperience);
}
if (perkDescription->maxRank == -1) {
for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {
int value = critterGetBonusStat(critter, stat);
critterSetBonusStat(critter, stat, value - perkDescription->stats[stat]);
}
}
}
// Returns modifier to specified skill accounting for perks.
//
// 0x496DD0
int perkGetSkillModifier(Object* critter, int skill)
{
int modifier = 0;
switch (skill) {
case SKILL_FIRST_AID:
if (perkHasRank(critter, PERK_MEDIC)) {
modifier += 10;
}
if (perkHasRank(critter, PERK_VAULT_CITY_TRAINING)) {
modifier += 5;
}
break;
case SKILL_DOCTOR:
if (perkHasRank(critter, PERK_MEDIC)) {
modifier += 10;
}
if (perkHasRank(critter, PERK_LIVING_ANATOMY)) {
modifier += 10;
}
if (perkHasRank(critter, PERK_VAULT_CITY_TRAINING)) {
modifier += 5;
}
break;
case SKILL_SNEAK:
if (perkHasRank(critter, PERK_GHOST)) {
int lightIntensity = objectGetLightIntensity(gDude);
if (lightIntensity > 45875) {
modifier += 20;
}
}
// FALLTHROUGH
case SKILL_LOCKPICK:
case SKILL_STEAL:
case SKILL_TRAPS:
if (perkHasRank(critter, PERK_THIEF)) {
modifier += 10;
}
if (skill == SKILL_LOCKPICK || skill == SKILL_STEAL) {
if (perkHasRank(critter, PERK_MASTER_THIEF)) {
modifier += 15;
}
}
if (skill == SKILL_STEAL) {
if (perkHasRank(critter, PERK_HARMLESS)) {
modifier += 20;
}
}
break;
case SKILL_SCIENCE:
case SKILL_REPAIR:
if (perkHasRank(critter, PERK_MR_FIXIT)) {
modifier += 10;
}
break;
case SKILL_SPEECH:
if (perkHasRank(critter, PERK_SPEAKER)) {
modifier += 20;
}
if (perkHasRank(critter, PERK_EXPERT_EXCREMENT_EXPEDITOR)) {
modifier += 5;
}
// FALLTHROUGH
case SKILL_BARTER:
if (perkHasRank(critter, PERK_NEGOTIATOR)) {
modifier += 10;
}
if (skill == SKILL_BARTER) {
if (perkHasRank(critter, PERK_SALESMAN)) {
modifier += 20;
}
}
break;
case SKILL_GAMBLING:
if (perkHasRank(critter, PERK_GAMBLER)) {
modifier += 20;
}
break;
case SKILL_OUTDOORSMAN:
if (perkHasRank(critter, PERK_RANGER)) {
modifier += 15;
}
if (perkHasRank(critter, PERK_SURVIVALIST)) {
modifier += 25;
}
break;
}
return modifier;
}