mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
1446 lines
39 KiB
C++
1446 lines
39 KiB
C++
/*
|
|
* sfall
|
|
* Copyright (C) 2008, 2009, 2010, 2012 The sfall team
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <vector>
|
|
|
|
#include "main.h"
|
|
#include "FalloutEngine.h"
|
|
|
|
#include "Perks.h"
|
|
|
|
long PerkLevelMod = 0;
|
|
|
|
static const int maxNameLen = 64; // don't change size
|
|
static const int maxDescLen = 512; // don't change size
|
|
static const int descLen = 256; // maximum text length for interface
|
|
|
|
static char perksFile[MAX_PATH] = {0};
|
|
|
|
static char Name[maxNameLen * PERK_count] = {0};
|
|
static char Desc[descLen * PERK_count] = {0};
|
|
static char tName[maxNameLen * TRAIT_count] = {0};
|
|
static char tDesc[descLen * TRAIT_count] = {0};
|
|
static char PerkBoxTitle[33];
|
|
|
|
static DWORD addPerkMode = 2;
|
|
|
|
static bool perksReInit = false;
|
|
static int perksEnable = 0;
|
|
static int traitsEnable = 0;
|
|
|
|
static PerkInfo perks[PERK_count];
|
|
static TraitInfo traits[TRAIT_count];
|
|
|
|
#pragma pack(push, 1)
|
|
struct FakePerk {
|
|
int Level; // current level (max 100)
|
|
int Image;
|
|
char Name[maxNameLen];
|
|
char Desc[maxDescLen];
|
|
char reserve[512]; // empty block
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
std::vector<FakePerk> fakeTraits;
|
|
std::vector<FakePerk> fakePerks;
|
|
std::vector<FakePerk> fakeSelectablePerks; // available perks for selection in the perk selection list
|
|
|
|
static long RemoveTraitID = -1;
|
|
static std::list<int> RemovePerkID;
|
|
static std::list<int> RemoveSelectableID;
|
|
|
|
static DWORD traitSkillBonuses[TRAIT_count * 18] = {0};
|
|
static DWORD traitStatBonuses[TRAIT_count * (STAT_max_derived + 1)] = {0};
|
|
|
|
static bool disableTraits[TRAIT_count];
|
|
static DWORD IgnoringDefaultPerks = 0;
|
|
|
|
static DWORD PerkFreqOverride = 0;
|
|
|
|
static const DWORD GainStatPerks[7][2] = {
|
|
{0x4AF122, 0xC9}, // Strength // mov ecx, ecx
|
|
{0x4AF184, 0xC9}, // Perception
|
|
{0x4AF19F, 0x90}, // Endurance // nop
|
|
{0x4AF1C0, 0xC9}, // Charisma
|
|
{0x4AF217, 0xC9}, // Intelligance
|
|
{0x4AF232, 0x90}, // Agility
|
|
{0x4AF24D, 0x90}, // Luck
|
|
};
|
|
|
|
void __stdcall SetPerkFreq(int i) {
|
|
PerkFreqOverride = i;
|
|
}
|
|
|
|
static bool __stdcall IsTraitDisabled(int traitID) {
|
|
return disableTraits[traitID];
|
|
}
|
|
|
|
static void __declspec(naked) LevelUpHack() {
|
|
__asm {
|
|
push ecx;
|
|
mov ecx, PerkFreqOverride;
|
|
test ecx, ecx;
|
|
jnz afterSkilled;
|
|
push TRAIT_skilled;
|
|
call IsTraitDisabled;
|
|
test al, al;
|
|
jnz notSkilled;
|
|
mov eax, TRAIT_skilled;
|
|
call trait_level_; // Check if the player has the skilled trait
|
|
test eax, eax;
|
|
jz notSkilled;
|
|
mov ecx, 4;
|
|
jmp afterSkilled;
|
|
notSkilled:
|
|
mov ecx, 3;
|
|
afterSkilled:
|
|
mov eax, ds:[FO_VAR_Level_]; // Get player's level
|
|
inc eax;
|
|
xor edx, edx;
|
|
div ecx;
|
|
test edx, edx;
|
|
jnz end;
|
|
inc byte ptr ds:[FO_VAR_free_perk]; // Increment the number of perks owed
|
|
end:
|
|
pop ecx;
|
|
mov edx, ds:[FO_VAR_Level_];
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkBoxTitleHook() {
|
|
__asm {
|
|
lea eax, PerkBoxTitle;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __stdcall IgnoreDefaultPerks() {
|
|
IgnoringDefaultPerks = 1;
|
|
}
|
|
|
|
void __stdcall RestoreDefaultPerks() {
|
|
IgnoringDefaultPerks = 0;
|
|
}
|
|
|
|
void __fastcall SetPerkboxTitle(const char* name) {
|
|
if (name[0] == '\0') {
|
|
PerkBoxTitle[0] = 0;
|
|
SafeWrite32(0x43C77D, 0x488CB);
|
|
} else {
|
|
strncpy_s(PerkBoxTitle, name, _TRUNCATE);
|
|
HookCall(0x43C77C, GetPerkBoxTitleHook);
|
|
}
|
|
}
|
|
|
|
void __stdcall SetSelectablePerk(const char* name, int active, int image, const char* desc) {
|
|
if (active < 0) return;
|
|
if (active > 1) active = 1;
|
|
size_t size = fakeSelectablePerks.size();
|
|
if (active == 0) {
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!strcmp(name, fakeSelectablePerks[i].Name)) {
|
|
fakeSelectablePerks.erase(fakeSelectablePerks.begin() + i);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!strcmp(name, fakeSelectablePerks[i].Name)) {
|
|
fakeSelectablePerks[i].Level = active;
|
|
fakeSelectablePerks[i].Image = image;
|
|
strncpy(fakeSelectablePerks[i].Desc, desc, descLen - 1);
|
|
fakeSelectablePerks[i].Desc[descLen - 1] = 0;
|
|
return;
|
|
}
|
|
}
|
|
FakePerk fp;
|
|
memset(&fp, 0, sizeof(FakePerk));
|
|
fp.Level = active;
|
|
fp.Image = image;
|
|
strncpy_s(fp.Name, name, _TRUNCATE);
|
|
strncpy_s(fp.Desc, desc, _TRUNCATE);
|
|
fakeSelectablePerks.push_back(fp);
|
|
}
|
|
}
|
|
|
|
void __stdcall SetFakePerk(const char* name, int level, int image, const char* desc) {
|
|
if (level < 0) return;
|
|
if (level > 100) level = 100;
|
|
size_t size = fakePerks.size();
|
|
if (level == 0) { // remove perk from fakePerks
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!strcmp(name, fakePerks[i].Name)) {
|
|
fakePerks.erase(fakePerks.begin() + i);
|
|
return;
|
|
}
|
|
}
|
|
} else { // add or change the existing fake perk in fakePerks
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!strcmp(name, fakePerks[i].Name)) {
|
|
fakePerks[i].Level = level;
|
|
fakePerks[i].Image = image;
|
|
strncpy(fakePerks[i].Desc, desc, descLen - 1);
|
|
fakePerks[i].Desc[descLen - 1] = 0;
|
|
return;
|
|
}
|
|
}
|
|
FakePerk fp;
|
|
memset(&fp, 0, sizeof(FakePerk));
|
|
fp.Level = level;
|
|
fp.Image = image;
|
|
strncpy_s(fp.Name, name, _TRUNCATE);
|
|
strncpy_s(fp.Desc, desc, _TRUNCATE);
|
|
fakePerks.push_back(fp);
|
|
}
|
|
}
|
|
|
|
void __stdcall SetFakeTrait(const char* name, int active, int image, const char* desc) {
|
|
if (active < 0) return;
|
|
if (active > 1) active = 1;
|
|
size_t size = fakeTraits.size();
|
|
if (active == 0) {
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!strcmp(name, fakeTraits[i].Name)) {
|
|
fakeTraits.erase(fakeTraits.begin() + i);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!strcmp(name, fakeTraits[i].Name)) {
|
|
fakeTraits[i].Level = active;
|
|
fakeTraits[i].Image = image;
|
|
strncpy(fakeTraits[i].Desc, desc, descLen - 1);
|
|
fakeTraits[i].Desc[descLen - 1] = 0;
|
|
return;
|
|
}
|
|
}
|
|
FakePerk fp;
|
|
memset(&fp, 0, sizeof(FakePerk));
|
|
fp.Level = active;
|
|
fp.Image = image;
|
|
strncpy_s(fp.Name, name, _TRUNCATE);
|
|
strncpy_s(fp.Desc, desc, _TRUNCATE);
|
|
fakeTraits.push_back(fp);
|
|
}
|
|
}
|
|
|
|
static DWORD __stdcall HaveFakeTraits2() {
|
|
return fakeTraits.size();
|
|
}
|
|
|
|
static void __declspec(naked) HaveFakeTraits() {
|
|
__asm {
|
|
push ecx;
|
|
push edx;
|
|
call HaveFakeTraits2;
|
|
pop edx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static DWORD __stdcall HaveFakePerks2() {
|
|
return fakePerks.size();
|
|
}
|
|
|
|
static void __declspec(naked) HaveFakePerks() {
|
|
__asm {
|
|
push ecx;
|
|
push edx;
|
|
call HaveFakePerks2;
|
|
pop edx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static FakePerk* __stdcall GetFakePerk2(int id) {
|
|
return &fakePerks[id - PERK_count];
|
|
}
|
|
|
|
static void __declspec(naked) GetFakePerk() {
|
|
__asm {
|
|
mov eax, [esp + 4];
|
|
push ecx;
|
|
push edx;
|
|
push eax;
|
|
call GetFakePerk2;
|
|
pop edx;
|
|
pop ecx;
|
|
retn 4;
|
|
}
|
|
}
|
|
|
|
static FakePerk* __stdcall GetFakeSPerk2(int id) {
|
|
return &fakeSelectablePerks[id - PERK_count];
|
|
}
|
|
|
|
static void __declspec(naked) GetFakeSPerk() {
|
|
__asm {
|
|
mov eax, [esp + 4];
|
|
push ecx;
|
|
push edx;
|
|
push eax;
|
|
call GetFakeSPerk2;
|
|
pop edx;
|
|
pop ecx;
|
|
retn 4;
|
|
}
|
|
}
|
|
|
|
static DWORD __stdcall GetFakeSPerkLevel2(int id) {
|
|
char* name = fakeSelectablePerks[id - PERK_count].Name;
|
|
for (DWORD i = 0; i < fakePerks.size(); i++) {
|
|
if (!strcmp(name, fakePerks[i].Name)) return fakePerks[i].Level;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void __declspec(naked) GetFakeSPerkLevel() {
|
|
__asm {
|
|
mov eax, [esp + 4];
|
|
push ecx;
|
|
push edx;
|
|
push eax;
|
|
call GetFakeSPerkLevel2;
|
|
pop edx;
|
|
pop ecx;
|
|
retn 4;
|
|
}
|
|
}
|
|
|
|
static DWORD __stdcall HandleFakeTraits(int isSelect) {
|
|
for (DWORD i = 0; i < fakeTraits.size(); i++) {
|
|
if (fo_folder_print_line(fakeTraits[i].Name) && !isSelect) {
|
|
isSelect = 1;
|
|
*ptr_folder_card_fid = fakeTraits[i].Image;
|
|
*ptr_folder_card_title = (DWORD)fakeTraits[i].Name;
|
|
*ptr_folder_card_title2 = 0;
|
|
*ptr_folder_card_desc = (DWORD)fakeTraits[i].Desc;
|
|
}
|
|
}
|
|
return isSelect;
|
|
}
|
|
|
|
static void __declspec(naked) PlayerHasPerkHack() {
|
|
__asm {
|
|
push ecx;
|
|
call HandleFakeTraits;
|
|
mov ecx, eax;
|
|
xor ebx, ebx;
|
|
oloop:
|
|
mov eax, ds:[FO_VAR_obj_dude];
|
|
mov edx, ebx;
|
|
call perk_level_;
|
|
test eax, eax;
|
|
jnz win;
|
|
inc ebx;
|
|
cmp ebx, PERK_count;
|
|
jl oloop;
|
|
call HaveFakePerks;
|
|
test eax, eax;
|
|
jnz win;
|
|
mov eax, 0x434446; // skip print perks
|
|
jmp eax;
|
|
win:
|
|
mov eax, 0x43438A; // print perks
|
|
jmp eax;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) PlayerHasTraitHook() {
|
|
__asm {
|
|
call HaveFakeTraits;
|
|
test eax, eax;
|
|
jz end;
|
|
mov eax, 0x43425B; // print traits
|
|
jmp eax;
|
|
end:
|
|
jmp PlayerHasPerkHack;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkLevelHook() {
|
|
__asm {
|
|
cmp edx, PERK_count;
|
|
jl end;
|
|
push edx;
|
|
call GetFakePerk;
|
|
mov eax, ds:[eax];
|
|
retn;
|
|
end:
|
|
jmp perk_level_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkImageHook() {
|
|
__asm {
|
|
cmp eax, PERK_count;
|
|
jl end;
|
|
push eax;
|
|
call GetFakePerk;
|
|
mov eax, ds:[eax + 4];
|
|
retn;
|
|
end:
|
|
jmp perk_skilldex_fid_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkNameHook() {
|
|
__asm {
|
|
cmp eax, PERK_count;
|
|
jl end;
|
|
push eax;
|
|
call GetFakePerk;
|
|
lea eax, ds:[eax + 8];
|
|
retn;
|
|
end:
|
|
jmp perk_name_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkDescHook() {
|
|
__asm {
|
|
cmp eax, PERK_count;
|
|
jl end;
|
|
push eax;
|
|
call GetFakePerk;
|
|
lea eax, ds:[eax + 72];
|
|
retn;
|
|
end:
|
|
jmp perk_description_
|
|
}
|
|
}
|
|
|
|
// Search all available perks for the player to display them in the character screen
|
|
static void __declspec(naked) EndPerkLoopHack() {
|
|
static const DWORD EndPerkLoopExit = 0x434446;
|
|
static const DWORD EndPerkLoopCont = 0x4343A5;
|
|
__asm {
|
|
jl cLoop; // if ebx < 119
|
|
call HaveFakePerks; // return perks count
|
|
add eax, PERK_count; // total = perks count + vanilla count
|
|
cmp ebx, eax; // if perkId < total then continue
|
|
jl cLoop;
|
|
jmp EndPerkLoopExit; // exit loop
|
|
cLoop:
|
|
jmp EndPerkLoopCont; // continue loop
|
|
}
|
|
}
|
|
|
|
// Build a table of perks ID numbers available for selection, data buffer has limited size for 119 perks
|
|
static DWORD __stdcall HandleExtraSelectablePerks(DWORD available, DWORD* data) {
|
|
for (DWORD i = 0; i < fakeSelectablePerks.size(); i++) {
|
|
if (available >= 119) break; // exit if the buffer is overfull
|
|
data[available++] = PERK_count + i;
|
|
}
|
|
return available; // total number of perks available for selection
|
|
}
|
|
|
|
static void __declspec(naked) GetAvailablePerksHook() {
|
|
__asm {
|
|
push ecx;
|
|
push edx; // arg data
|
|
cmp IgnoringDefaultPerks, 0;
|
|
jnz skipDefaults;
|
|
call perk_make_list_; // return available count
|
|
jmp next;
|
|
skipDefaults:
|
|
xor eax, eax;
|
|
next:
|
|
push eax; // arg available
|
|
call HandleExtraSelectablePerks;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkSLevelHook() {
|
|
__asm {
|
|
cmp edx, PERK_count;
|
|
jl end;
|
|
push edx;
|
|
call GetFakeSPerkLevel;
|
|
retn;
|
|
end:
|
|
jmp perk_level_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkSImageHook() {
|
|
__asm {
|
|
cmp eax, PERK_count;
|
|
jl end;
|
|
push eax;
|
|
call GetFakeSPerk;
|
|
mov eax, ds:[eax + 4];
|
|
retn;
|
|
end:
|
|
jmp perk_skilldex_fid_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkSNameHook() {
|
|
__asm {
|
|
cmp eax, PERK_count;
|
|
jl end;
|
|
push eax;
|
|
call GetFakeSPerk;
|
|
lea eax, ds:[eax + 8]; // Name
|
|
retn;
|
|
end:
|
|
jmp perk_name_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) GetPerkSDescHook() {
|
|
__asm {
|
|
cmp eax, PERK_count;
|
|
jl end;
|
|
push eax;
|
|
call GetFakeSPerk;
|
|
lea eax, ds:[eax + 72]; // Desc
|
|
retn;
|
|
end:
|
|
jmp perk_description_;
|
|
}
|
|
}
|
|
|
|
// Adds the selected perk to the player
|
|
static long __stdcall AddFakePerk(DWORD perkID) {
|
|
size_t count;
|
|
bool matched = false;
|
|
// behavior for fake perk/trait
|
|
perkID -= PERK_count;
|
|
if (addPerkMode & 1) { // add perk to trait
|
|
count = fakeTraits.size();
|
|
for (size_t d = 0; d < count; d++) {
|
|
if (!strcmp(fakeTraits[d].Name, fakeSelectablePerks[perkID].Name)) {
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!matched) {
|
|
if (RemoveTraitID == -1) RemoveTraitID = count; // index of the added trait
|
|
fakeTraits.push_back(fakeSelectablePerks[perkID]);
|
|
}
|
|
}
|
|
if (addPerkMode & 2) { // default mode
|
|
matched = false;
|
|
count = fakePerks.size();
|
|
for (size_t d = 0; d < count; d++) {
|
|
if (!strcmp(fakePerks[d].Name, fakeSelectablePerks[perkID].Name)) {
|
|
RemovePerkID.push_back(d);
|
|
fakePerks[d].Level++;
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!matched) { // add to fakePerks
|
|
RemovePerkID.push_back(count); // index of the added perk
|
|
fakePerks.push_back(fakeSelectablePerks[perkID]);
|
|
}
|
|
}
|
|
if (addPerkMode & 4) { // delete from selectable perks
|
|
RemoveSelectableID.push_back(perkID); //fakeSelectablePerks.remove_at(perkID);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Adds perk from selection window to player
|
|
static void __declspec(naked) AddPerkHook() {
|
|
__asm {
|
|
cmp edx, PERK_count;
|
|
jl normalPerk;
|
|
push ecx;
|
|
push edx;
|
|
call AddFakePerk;
|
|
pop ecx;
|
|
retn;
|
|
normalPerk:
|
|
push edx;
|
|
call perk_add_;
|
|
pop edx;
|
|
test eax, eax;
|
|
jnz end;
|
|
// fix gain perks (add to base stats instead of bonus stats)
|
|
cmp edx, PERK_gain_strength_perk;
|
|
jl end;
|
|
cmp edx, PERK_gain_luck_perk;
|
|
jg end;
|
|
inc ds:[edx * 4 + (FO_VAR_pc_proto + 0x24 - PERK_gain_strength_perk * 4)]; // base_stat_srength
|
|
end:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HeaveHoHook() {
|
|
__asm {
|
|
xor edx, edx;
|
|
mov eax, ecx;
|
|
call stat_level_;
|
|
lea ebx, [0 + eax * 4];
|
|
sub ebx, eax;
|
|
cmp ebx, esi; // ebx = dist (3*ST), esi = max dist weapon
|
|
jle lower; // jump if dist <= max
|
|
mov ebx, esi; // dist = max
|
|
lower:
|
|
mov eax, ecx;
|
|
mov edx, PERK_heave_ho;
|
|
call perk_level_;
|
|
lea ecx, [0 + eax * 8];
|
|
sub ecx, eax;
|
|
sub ecx, eax;
|
|
mov eax, ecx;
|
|
add eax, ebx; // distance = dist + (PERK_heave_ho * 6)
|
|
push 0x478AFC;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static bool perkHeaveHoModFix = false;
|
|
void __stdcall ApplyHeaveHoFix() { // not really a fix
|
|
MakeJump(0x478AC4, HeaveHoHook);
|
|
perks[PERK_heave_ho].strengthMin = 0;
|
|
perkHeaveHoModFix = true;
|
|
}
|
|
|
|
//////////////////////////////// ENGINE PERKS /////////////////////////////////
|
|
|
|
static class EnginePerkBonus {
|
|
public:
|
|
long WeaponScopeRangePenalty;
|
|
long WeaponScopeRangeBonus;
|
|
long WeaponLongRangeBonus;
|
|
long WeaponAccurateBonus;
|
|
long WeaponHandlingBonus;
|
|
|
|
float MasterTraderBonus;
|
|
long SalesmanBonus;
|
|
|
|
long LivingAnatomyBonus;
|
|
long PyromaniacBonus;
|
|
|
|
long StonewallPercent;
|
|
|
|
long DemolitionExpertBonus;
|
|
|
|
long VaultCityInoculationsPoisonBonus;
|
|
long VaultCityInoculationsRadBonus;
|
|
|
|
EnginePerkBonus() {
|
|
WeaponScopeRangePenalty = 8;
|
|
WeaponScopeRangeBonus = 5;
|
|
WeaponLongRangeBonus = 4;
|
|
WeaponAccurateBonus = 20;
|
|
WeaponHandlingBonus = 3;
|
|
|
|
MasterTraderBonus = 25;
|
|
SalesmanBonus = 20;
|
|
|
|
LivingAnatomyBonus = 5;
|
|
PyromaniacBonus = 5;
|
|
|
|
StonewallPercent = 50;
|
|
|
|
DemolitionExpertBonus = 10;
|
|
|
|
VaultCityInoculationsPoisonBonus = 10;
|
|
VaultCityInoculationsRadBonus = 10;
|
|
}
|
|
|
|
///////////////////////////////////////////
|
|
|
|
void setWeaponScopeRangePenalty(long value) {
|
|
if (value < 0) return;
|
|
WeaponScopeRangePenalty = value;
|
|
SafeWrite32(0x42448E, value);
|
|
}
|
|
|
|
void setWeaponScopeRangeBonus(long value) {
|
|
if (value < 2) return;
|
|
WeaponScopeRangeBonus = value;
|
|
SafeWrite32(0x424489, value);
|
|
}
|
|
|
|
void setWeaponLongRangeBonus(long value) {
|
|
if (value < 2) return;
|
|
WeaponLongRangeBonus = value;
|
|
SafeWrite32(0x424474, value);
|
|
}
|
|
|
|
void setWeaponAccurateBonus(long value) {
|
|
if (value < 0) return;
|
|
WeaponAccurateBonus = value;
|
|
if (WeaponAccurateBonus > 125) WeaponAccurateBonus = 125;
|
|
SafeWrite8(0x42465D, static_cast<BYTE>(WeaponAccurateBonus));
|
|
}
|
|
|
|
void setWeaponHandlingBonus(long value) {
|
|
if (value < 0) return;
|
|
WeaponHandlingBonus = value;
|
|
if (WeaponHandlingBonus > 10) WeaponHandlingBonus = 10;
|
|
SafeWrite8(0x424636, static_cast<char>(WeaponHandlingBonus));
|
|
SafeWrite8(0x4251CE, static_cast<signed char>(-WeaponHandlingBonus));
|
|
}
|
|
|
|
void setMasterTraderBonus(long value) {
|
|
if (value < 0) return;
|
|
MasterTraderBonus = static_cast<float>(value);
|
|
SafeWrite32(0x474BB3, *(DWORD*)&MasterTraderBonus); // write float data
|
|
}
|
|
|
|
void setSalesmanBonus(long value) {
|
|
if (value < 0) return;
|
|
SalesmanBonus = value;
|
|
if (SalesmanBonus > 999) SalesmanBonus = 999;
|
|
}
|
|
|
|
void setLivingAnatomyBonus(long value) {
|
|
if (value < 0) return;
|
|
LivingAnatomyBonus = value;
|
|
if (LivingAnatomyBonus > 125) LivingAnatomyBonus = 125;
|
|
SafeWrite8(0x424A91, static_cast<BYTE>(LivingAnatomyBonus));
|
|
}
|
|
|
|
void setPyromaniacBonus(long value) {
|
|
if (value < 0) return;
|
|
PyromaniacBonus = value;
|
|
if (PyromaniacBonus > 125) PyromaniacBonus = 125;
|
|
SafeWrite8(0x424AB6, static_cast<BYTE>(PyromaniacBonus));
|
|
}
|
|
|
|
void setStonewallPercent(long value) {
|
|
if (value < 0) return;
|
|
StonewallPercent = value;
|
|
if (StonewallPercent > 100) StonewallPercent = 100;
|
|
SafeWrite8(0x424B50, static_cast<BYTE>(StonewallPercent));
|
|
}
|
|
|
|
void setDemolitionExpertBonus(long value) {
|
|
if (value < 0) return;
|
|
DemolitionExpertBonus = value;
|
|
if (DemolitionExpertBonus > 999) DemolitionExpertBonus = 999;
|
|
}
|
|
|
|
void setVaultCityInoculationsPoisonBonus(long value) {
|
|
if (value < -100) value = -100;
|
|
if (value > 100) value = 100;
|
|
VaultCityInoculationsPoisonBonus = value;
|
|
SafeWrite8(0x4AF26A, static_cast<signed char>(VaultCityInoculationsPoisonBonus));
|
|
}
|
|
|
|
void setVaultCityInoculationsRadBonus(long value) {
|
|
if (value < -100) value = -100;
|
|
if (value > 100) value = 100;
|
|
VaultCityInoculationsRadBonus = value;
|
|
SafeWrite8(0x4AF287, static_cast<signed char>(VaultCityInoculationsRadBonus));
|
|
}
|
|
|
|
} enginePerks;
|
|
|
|
static void __declspec(naked) perk_adjust_skill_hack_salesman() {
|
|
__asm {
|
|
imul eax, [enginePerks.SalesmanBonus];
|
|
add ecx, eax; // barter_skill + (perkLevel * SalesmanBonus)
|
|
mov eax, ecx
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) queue_explode_exit_hack_demolition_expert() {
|
|
__asm {
|
|
imul eax, [enginePerks.DemolitionExpertBonus];
|
|
add ecx, eax; // maxBaseDmg + (perkLevel * DemolitionExpertBonus)
|
|
add ebx, eax // minBaseDmg + (perkLevel * DemolitionExpertBonus)
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void EnginePerkBonusInit() {
|
|
// Allows the current perk level to affect the calculation of its bonus value
|
|
MakeCall(0x496F5E, perk_adjust_skill_hack_salesman);
|
|
MakeCall(0x4A289C, queue_explode_exit_hack_demolition_expert, 1);
|
|
}
|
|
|
|
static void ReadPerksBonuses(const char* perksFile) {
|
|
int wScopeRangeMod = iniGetInt("PerksTweak", "WeaponScopeRangePenalty", 8, perksFile);
|
|
if (wScopeRangeMod != 8) enginePerks.setWeaponScopeRangePenalty(wScopeRangeMod);
|
|
wScopeRangeMod = iniGetInt("PerksTweak", "WeaponScopeRangeBonus", 5, perksFile);
|
|
if (wScopeRangeMod != 5) enginePerks.setWeaponScopeRangeBonus(wScopeRangeMod);
|
|
|
|
int wLongRangeBonus = iniGetInt("PerksTweak", "WeaponLongRangeBonus", 4, perksFile);
|
|
if (wLongRangeBonus != 4) enginePerks.setWeaponLongRangeBonus(wLongRangeBonus);
|
|
|
|
int wAccurateBonus = iniGetInt("PerksTweak", "WeaponAccurateBonus", 20, perksFile);
|
|
if (wAccurateBonus != 20) enginePerks.setWeaponAccurateBonus(wAccurateBonus);
|
|
|
|
int wHandlingBonus = iniGetInt("PerksTweak", "WeaponHandlingBonus", 3, perksFile);
|
|
if (wHandlingBonus != 3) enginePerks.setWeaponHandlingBonus(wHandlingBonus);
|
|
|
|
int masterTraderBonus = iniGetInt("PerksTweak", "MasterTraderBonus", 25, perksFile);
|
|
if (masterTraderBonus != 25) enginePerks.setMasterTraderBonus(masterTraderBonus);
|
|
|
|
int salesmanBonus = iniGetInt("PerksTweak", "SalesmanBonus", 20, perksFile);
|
|
if (salesmanBonus != 20) enginePerks.setSalesmanBonus(salesmanBonus);
|
|
|
|
int livingAnatomyBonus = iniGetInt("PerksTweak", "LivingAnatomyBonus", 5, perksFile);
|
|
if (livingAnatomyBonus != 5) enginePerks.setLivingAnatomyBonus(livingAnatomyBonus);
|
|
|
|
int pyromaniacBonus = iniGetInt("PerksTweak", "PyromaniacBonus", 5, perksFile);
|
|
if (pyromaniacBonus != 5) enginePerks.setPyromaniacBonus(pyromaniacBonus);
|
|
|
|
int stonewallPercent = iniGetInt("PerksTweak", "StonewallPercent", 50, perksFile);
|
|
if (stonewallPercent != 50) enginePerks.setStonewallPercent(stonewallPercent);
|
|
|
|
int demolitionExpertBonus = iniGetInt("PerksTweak", "DemolitionExpertBonus", 10, perksFile);
|
|
if (demolitionExpertBonus != 10) enginePerks.setDemolitionExpertBonus(demolitionExpertBonus);
|
|
|
|
int vaultCityInoculationsBonus = iniGetInt("PerksTweak", "VaultCityInoculationsPoisonBonus", 10, perksFile);
|
|
if (vaultCityInoculationsBonus != 10) enginePerks.setVaultCityInoculationsPoisonBonus(vaultCityInoculationsBonus);
|
|
vaultCityInoculationsBonus = iniGetInt("PerksTweak", "VaultCityInoculationsRadBonus", 10, perksFile);
|
|
if (vaultCityInoculationsBonus != 10) enginePerks.setVaultCityInoculationsRadBonus(vaultCityInoculationsBonus);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void PerkEngineInit() {
|
|
EnginePerkBonusInit();
|
|
|
|
// Character screen (list_perks_)
|
|
HookCall(0x434256, PlayerHasTraitHook); // jz func
|
|
MakeJump(0x43436B, PlayerHasPerkHack);
|
|
HookCall(0x4343AC, GetPerkLevelHook);
|
|
HookCall(0x43440D, GetPerkImageHook);
|
|
HookCall(0x434432, GetPerkDescHook);
|
|
MakeJump(0x434440, EndPerkLoopHack, 1);
|
|
const DWORD getPerkNameAddr[] = {0x4343C1, 0x4343DF, 0x43441B};
|
|
HookCalls(GetPerkNameHook, getPerkNameAddr);
|
|
|
|
// GetPlayerAvailablePerks (ListDPerks_)
|
|
HookCall(0x43D127, GetAvailablePerksHook);
|
|
|
|
// ShowPerkBox (perks_dialog_)
|
|
HookCall(0x43C952, AddPerkHook);
|
|
|
|
// PerkboxSwitchPerk (RedrwDPrks_)
|
|
|
|
// GetPerkS hooks
|
|
const DWORD getPerkSLevelAddr[] = {
|
|
0x43D25E, 0x43D275, // ListDPerks_
|
|
0x43C82E, 0x43C85B, // perks_dialog_
|
|
0x43C3F1, 0x43C41E // RedrwDPrks_
|
|
};
|
|
HookCalls(GetPerkSLevelHook, getPerkSLevelAddr);
|
|
const DWORD getPerkSNameAddr[] = {
|
|
0x43D17D, // ListDPerks_
|
|
0x43C8A6, 0x43C8EF, // perks_dialog_
|
|
0x43C469, 0x43C4B2 // RedrwDPrks_
|
|
};
|
|
HookCalls(GetPerkSNameHook, getPerkSNameAddr);
|
|
const DWORD getPerkSDescAddr[] = {
|
|
0x43C888, 0x43C8D1, // perks_dialog_
|
|
0x43C44B, 0x43C494 // RedrwDPrks_
|
|
};
|
|
HookCalls(GetPerkSDescHook, getPerkSDescAddr);
|
|
const DWORD getPerkSImageAddr[] = {
|
|
0x43C90F, // perks_dialog_
|
|
0x43C4D2 // RedrwDPrks_
|
|
};
|
|
HookCalls(GetPerkSImageHook, getPerkSImageAddr);
|
|
|
|
// perk_owed hooks
|
|
MakeCall(0x4AFB2F, LevelUpHack, 1); // replaces 'mov edx, ds:[PlayerLevel]'
|
|
SafeWrite8(0x43C2EC, CODETYPE_JumpShort); // skip the block of code which checks if the player has gained a perk (now handled in level up code)
|
|
}
|
|
|
|
static void PerkSetup() {
|
|
if (!perksReInit) {
|
|
// _perk_data
|
|
const DWORD perkDataAddr[] = {0x496669, 0x496837, 0x496BAD, 0x496C41, 0x496D25};
|
|
SafeWriteBatch<DWORD>((DWORD)perks, perkDataAddr);
|
|
const DWORD perkDataDescAddr[] = {0x496696, 0x496BD1};
|
|
SafeWriteBatch<DWORD>((DWORD)&perks[0].description, perkDataDescAddr);
|
|
SafeWrite32(0x496BF5, (DWORD)&perks[0].image);
|
|
SafeWrite32(0x496AD4, (DWORD)&perks[0].ranks);
|
|
}
|
|
std::memcpy(perks, (void*)FO_VAR_perk_data, sizeof(PerkInfo) * PERK_count); // copy vanilla data
|
|
|
|
if (perksEnable) {
|
|
char num[4];
|
|
for (int i = 0; i < PERK_count; i++) {
|
|
_itoa(i, num, 10);
|
|
if (iniGetString(num, "Name", "", &Name[i * maxNameLen], maxNameLen - 1, perksFile)) {
|
|
perks[i].name = &Name[i * maxNameLen];
|
|
}
|
|
if (iniGetString(num, "Desc", "", &Desc[i * descLen], descLen - 1, perksFile)) {
|
|
perks[i].description = &Desc[i * descLen];
|
|
}
|
|
int value;
|
|
value = iniGetInt(num, "Image", -99999, perksFile);
|
|
if (value != -99999) perks[i].image = value;
|
|
value = iniGetInt(num, "Ranks", -99999, perksFile);
|
|
if (value != -99999) perks[i].ranks = value;
|
|
value = iniGetInt(num, "Level", -99999, perksFile);
|
|
if (value != -99999) perks[i].levelMin = value;
|
|
value = iniGetInt(num, "Stat", -99999, perksFile);
|
|
if (value != -99999) perks[i].stat = value;
|
|
value = iniGetInt(num, "StatMag", -99999, perksFile);
|
|
if (value != -99999) perks[i].statMod = value;
|
|
value = iniGetInt(num, "Skill1", -99999, perksFile);
|
|
if (value != -99999) perks[i].skill1 = value;
|
|
value = iniGetInt(num, "Skill1Mag", -99999, perksFile);
|
|
if (value != -99999) perks[i].skill1Min = value;
|
|
value = iniGetInt(num, "Type", -99999, perksFile);
|
|
if (value != -99999) perks[i].skillOperator = value;
|
|
value = iniGetInt(num, "Skill2", -99999, perksFile);
|
|
if (value != -99999) perks[i].skill2 = value;
|
|
value = iniGetInt(num, "Skill2Mag", -99999, perksFile);
|
|
if (value != -99999) perks[i].skill2Min = value;
|
|
value = iniGetInt(num, "STR", -99999, perksFile);
|
|
if (value != -99999) perks[i].strengthMin = value;
|
|
value = iniGetInt(num, "PER", -99999, perksFile);
|
|
if (value != -99999) perks[i].perceptionMin = value;
|
|
value = iniGetInt(num, "END", -99999, perksFile);
|
|
if (value != -99999) perks[i].enduranceMin = value;
|
|
value = iniGetInt(num, "CHR", -99999, perksFile);
|
|
if (value != -99999) perks[i].charismaMin = value;
|
|
value = iniGetInt(num, "INT", -99999, perksFile);
|
|
if (value != -99999) perks[i].intelligenceMin = value;
|
|
value = iniGetInt(num, "AGL", -99999, perksFile);
|
|
if (value != -99999) perks[i].agilityMin = value;
|
|
value = iniGetInt(num, "LCK", -99999, perksFile);
|
|
if (value != -99999) perks[i].luckMin = value;
|
|
}
|
|
}
|
|
perksReInit = false;
|
|
}
|
|
|
|
/////////////////////////// TRAIT FUNCTIONS ///////////////////////////////////
|
|
|
|
static bool smallFrameTraitFix = false;
|
|
|
|
static int __stdcall DudeGetBaseStat(DWORD statID) {
|
|
return fo_stat_get_base_direct(*ptr_obj_dude, statID);
|
|
}
|
|
|
|
static __forceinline bool __stdcall CheckTrait(DWORD traitID) {
|
|
return (!disableTraits[traitID] && (ptr_pc_trait[0] == traitID || ptr_pc_trait[1] == traitID));
|
|
}
|
|
|
|
static int __stdcall trait_adjust_stat_override(DWORD statID) {
|
|
if (statID > STAT_max_derived) return 0;
|
|
|
|
int result = 0;
|
|
if (traitsEnable) {
|
|
if (ptr_pc_trait[0] != -1) result += traitStatBonuses[statID * TRAIT_count + ptr_pc_trait[0]];
|
|
if (ptr_pc_trait[1] != -1) result += traitStatBonuses[statID * TRAIT_count + ptr_pc_trait[1]];
|
|
}
|
|
|
|
switch (statID) {
|
|
case STAT_st:
|
|
if (CheckTrait(TRAIT_gifted)) result++;
|
|
if (CheckTrait(TRAIT_bruiser)) result += 2;
|
|
break;
|
|
case STAT_pe:
|
|
if (CheckTrait(TRAIT_gifted)) result++;
|
|
break;
|
|
case STAT_en:
|
|
if (CheckTrait(TRAIT_gifted)) result++;
|
|
break;
|
|
case STAT_ch:
|
|
if (CheckTrait(TRAIT_gifted)) result++;
|
|
break;
|
|
case STAT_iq:
|
|
if (CheckTrait(TRAIT_gifted)) result++;
|
|
break;
|
|
case STAT_ag:
|
|
if (CheckTrait(TRAIT_gifted)) result++;
|
|
if (CheckTrait(TRAIT_small_frame)) result++;
|
|
break;
|
|
case STAT_lu:
|
|
if (CheckTrait(TRAIT_gifted)) result++;
|
|
break;
|
|
case STAT_max_move_points:
|
|
if (CheckTrait(TRAIT_bruiser)) result -= 2;
|
|
break;
|
|
case STAT_ac:
|
|
if (CheckTrait(TRAIT_kamikaze)) return -DudeGetBaseStat(STAT_ac);
|
|
break;
|
|
case STAT_melee_dmg:
|
|
if (CheckTrait(TRAIT_heavy_handed)) result += 4;
|
|
break;
|
|
case STAT_carry_amt:
|
|
if (CheckTrait(TRAIT_small_frame)) {
|
|
int st;
|
|
if (smallFrameTraitFix) {
|
|
st = fo_stat_level(*ptr_obj_dude, STAT_st);
|
|
} else {
|
|
st = DudeGetBaseStat(STAT_st);
|
|
}
|
|
result -= st * 10;
|
|
}
|
|
break;
|
|
case STAT_sequence:
|
|
if (CheckTrait(TRAIT_kamikaze)) result += 5;
|
|
break;
|
|
case STAT_heal_rate:
|
|
if (CheckTrait(TRAIT_fast_metabolism)) result += 2;
|
|
break;
|
|
case STAT_crit_chance:
|
|
if (CheckTrait(TRAIT_finesse)) result += 10;
|
|
break;
|
|
case STAT_better_crit:
|
|
if (CheckTrait(TRAIT_heavy_handed)) result -= 30;
|
|
break;
|
|
case STAT_rad_resist:
|
|
if (CheckTrait(TRAIT_fast_metabolism)) return -DudeGetBaseStat(STAT_rad_resist);
|
|
break;
|
|
case STAT_poison_resist:
|
|
if (CheckTrait(TRAIT_fast_metabolism)) return -DudeGetBaseStat(STAT_poison_resist);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void __declspec(naked) trait_adjust_stat_hack() {
|
|
__asm {
|
|
push edx;
|
|
push ecx;
|
|
push eax; // statID
|
|
call trait_adjust_stat_override;
|
|
pop ecx;
|
|
pop edx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static int __stdcall trait_adjust_skill_override(DWORD skillID) {
|
|
int result = 0;
|
|
if (traitsEnable) {
|
|
if (ptr_pc_trait[0] != -1) result += traitSkillBonuses[skillID * TRAIT_count + ptr_pc_trait[0]];
|
|
if (ptr_pc_trait[1] != -1) result += traitSkillBonuses[skillID * TRAIT_count + ptr_pc_trait[1]];
|
|
}
|
|
|
|
if (CheckTrait(TRAIT_gifted)) result -= 10;
|
|
|
|
if (CheckTrait(TRAIT_good_natured)) {
|
|
if (skillID <= SKILL_THROWING) {
|
|
result -= 10;
|
|
} else if (skillID == SKILL_FIRST_AID || skillID == SKILL_DOCTOR || skillID == SKILL_CONVERSANT || skillID == SKILL_BARTER) {
|
|
result += 15;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void __declspec(naked) trait_adjust_skill_hack() {
|
|
__asm {
|
|
push edx;
|
|
push ecx;
|
|
push eax; // skillID
|
|
call trait_adjust_skill_override;
|
|
pop ecx;
|
|
pop edx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) BlockedTrait() {
|
|
__asm {
|
|
xor eax, eax;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void PerkAndTraitSetup() {
|
|
PerkSetup();
|
|
|
|
if (!traitsEnable) return;
|
|
|
|
std::memcpy(traits, (void*)FO_VAR_trait_data, sizeof(TraitInfo) * TRAIT_count);
|
|
|
|
// _trait_data
|
|
const DWORD traitDataAddr[] = {0x4B3A81, 0x4B3B80};
|
|
SafeWriteBatch<DWORD>((DWORD)traits, traitDataAddr);
|
|
const DWORD traitDataDescAddr[] = {0x4B3AAE, 0x4B3BA0};
|
|
SafeWriteBatch<DWORD>((DWORD)&traits[0].description, traitDataDescAddr);
|
|
SafeWrite32(0x4B3BC0, (DWORD)&traits[0].image);
|
|
|
|
char buf[512], num[5] = {'t'};
|
|
char* num2 = &num[1];
|
|
for (int i = 0; i < TRAIT_count; i++) {
|
|
_itoa_s(i, num2, 4, 10);
|
|
if (iniGetString(num, "Name", "", &tName[i * maxNameLen], maxNameLen - 1, perksFile)) {
|
|
traits[i].name = &tName[i * maxNameLen];
|
|
}
|
|
if (iniGetString(num, "Desc", "", &tDesc[i * descLen], descLen - 1, perksFile)) {
|
|
traits[i].description = &tDesc[i * descLen];
|
|
}
|
|
int value;
|
|
value = iniGetInt(num, "Image", -99999, perksFile);
|
|
if (value != -99999) traits[i].image = value;
|
|
|
|
if (iniGetString(num, "StatMod", "", buf, 512, perksFile) > 0) {
|
|
char *stat, *mod;
|
|
stat = strtok(buf, "|");
|
|
mod = strtok(0, "|");
|
|
while (stat&&mod) {
|
|
int _stat = atoi(stat), _mod = atoi(mod);
|
|
if (_stat >= 0 && _stat <= STAT_max_derived) traitStatBonuses[_stat * TRAIT_count + i] = _mod;
|
|
stat = strtok(0, "|");
|
|
mod = strtok(0, "|");
|
|
}
|
|
}
|
|
|
|
if (iniGetString(num, "SkillMod", "", buf, 512, perksFile) > 0) {
|
|
char *stat, *mod;
|
|
stat = strtok(buf, "|");
|
|
mod = strtok(0, "|");
|
|
while (stat&&mod) {
|
|
int _stat = atoi(stat), _mod = atoi(mod);
|
|
if (_stat >= 0 && _stat < 18) traitSkillBonuses[_stat * TRAIT_count + i] = _mod;
|
|
stat = strtok(0, "|");
|
|
mod = strtok(0, "|");
|
|
}
|
|
}
|
|
|
|
if (iniGetInt(num, "NoHardcode", 0, perksFile)) {
|
|
disableTraits[i] = true;
|
|
switch (i) {
|
|
case TRAIT_one_hander:
|
|
HookCall(0x4245E0, BlockedTrait); // determine_to_hit_func_
|
|
break;
|
|
case TRAIT_finesse:
|
|
HookCall(0x4248F9, BlockedTrait); // compute_damage_
|
|
break;
|
|
case TRAIT_fast_shot:
|
|
HookCall(0x478C8A, BlockedTrait); // item_w_mp_cost_
|
|
HookCall(0x478E70, BlockedTrait); // item_w_called_shot_
|
|
break;
|
|
case TRAIT_bloody_mess:
|
|
HookCall(0x410707, BlockedTrait); // pick_death_
|
|
break;
|
|
case TRAIT_jinxed:
|
|
HookCall(0x42389F, BlockedTrait); // compute_attack_
|
|
break;
|
|
case TRAIT_drug_addict:
|
|
HookCall(0x47A0CD, BlockedTrait); // item_d_take_drug_
|
|
HookCall(0x47A51A, BlockedTrait); // perform_withdrawal_start_
|
|
break;
|
|
case TRAIT_drug_resistant:
|
|
HookCall(0x479BE1, BlockedTrait); // insert_drug_effect_
|
|
HookCall(0x47A0DD, BlockedTrait); // item_d_take_drug_
|
|
break;
|
|
case TRAIT_skilled:
|
|
HookCall(0x43C295, BlockedTrait); // UpdateLevel_
|
|
HookCall(0x43C2F3, BlockedTrait); // UpdateLevel_
|
|
break;
|
|
case TRAIT_gifted:
|
|
HookCall(0x43C2A4, BlockedTrait); // UpdateLevel_
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static __declspec(naked) void game_init_hook() {
|
|
__asm {
|
|
call trait_init_;
|
|
jmp PerkAndTraitSetup;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) perks_dialog_hook() {
|
|
static const DWORD perks_dialog_Ret = 0x43C92F;
|
|
__asm {
|
|
call ListDPerks_;
|
|
test eax, eax;
|
|
jz dlgExit;
|
|
retn;
|
|
dlgExit:
|
|
add esp, 4;
|
|
jmp perks_dialog_Ret;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) item_w_mp_cost_hook() {
|
|
__asm {
|
|
call item_w_range_;
|
|
cmp eax, 2;
|
|
jge checkType; // is weapon range less than 2?
|
|
retn; // yes, skip -1 AP cost (0x478CA2)
|
|
checkType:
|
|
mov eax, edi; // source
|
|
mov edx, ecx; // hit_mode
|
|
call item_hit_with_; // get pointer to weapon
|
|
mov edx, ecx; // hit_mode
|
|
jmp item_w_subtype_; // eax - item
|
|
}
|
|
}
|
|
|
|
// Haenlomal's fix
|
|
static void __declspec(naked) item_w_called_shot_hack() {
|
|
static const DWORD FastShotTraitFix_End = 0x478E7F;
|
|
__asm {
|
|
mov edx, ecx; // argument for item_hit_with_: hit_mode
|
|
mov eax, ebx; // argument for item_hit_with_: pointer to source_obj (always dude_obj due to code path)
|
|
call item_hit_with_; // get pointer to weapon
|
|
mov edx, ecx;
|
|
call item_w_subtype_;
|
|
cmp eax, ATKSUBTYPE_THROWING; // is weapon type GUNS or THROWING?
|
|
jge checkRange; // yes
|
|
jmp FastShotTraitFix_End; // continue processing called shot attempt
|
|
checkRange:
|
|
mov edx, ecx; // argument for item_w_range_: hit_mode
|
|
mov eax, ebx; // argument for item_w_range_: pointer to source_obj (always dude_obj due to code path)
|
|
call item_w_range_; // get weapon's range
|
|
cmp eax, 2; // is weapon range greater than or equal to 2 (i.e. ranged attack)?
|
|
jge cantUse; // yes, disallow called shot attempt
|
|
jmp FastShotTraitFix_End; // continue processing called shot attempt
|
|
cantUse:
|
|
xor eax, eax; // clean up and exit function item_w_called_shot
|
|
pop esi;
|
|
pop ecx;
|
|
pop ebx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void FastShotTraitFix() {
|
|
switch (GetConfigInt("Misc", "FastShotFix", 1)) {
|
|
case 1:
|
|
dlog("Applying Fast Shot trait patch. (Haenlomal's fix)", DL_INIT);
|
|
MakeJump(0x478E79, item_w_called_shot_hack);
|
|
goto fix;
|
|
case 2: {
|
|
dlog("Applying Fast Shot trait patch. (Alternative behavior)", DL_INIT);
|
|
SafeWrite16(0x478C9F, 0x9090);
|
|
const DWORD fastShotFixF1[] = {0x478BB8, 0x478BC7, 0x478BD6, 0x478BEA, 0x478BF9, 0x478C08, 0x478C2F};
|
|
HookCalls((void*)0x478C7D, fastShotFixF1);
|
|
goto done;
|
|
}
|
|
case 3:
|
|
dlog("Applying Fast Shot trait patch. (Fallout 1 behavior)", DL_INIT);
|
|
HookCall(0x478C97, (void*)item_hit_with_);
|
|
SafeWrite16(0x478C9E, CODETYPE_JumpZ << 8); // ignore all unarmed attacks (cmp eax, 0; jz)
|
|
goto done;
|
|
default:
|
|
dlog("Applying Fast Shot trait fix.", DL_INIT);
|
|
fix:
|
|
HookCall(0x478C97, item_w_mp_cost_hook);
|
|
done:
|
|
dlogr(" Done", DL_INIT);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void __fastcall SetPerkValue(int id, int param, int value) {
|
|
if (id < 0 || id >= PERK_count) return;
|
|
*(DWORD*)((DWORD)(&perks[id]) + param) = value;
|
|
perksReInit = true;
|
|
}
|
|
|
|
void __stdcall SetPerkName(int id, const char* value) {
|
|
if (id < 0 || id >= PERK_count) return;
|
|
strncpy_s(&Name[id * maxNameLen], maxNameLen, value, _TRUNCATE);
|
|
perks[id].name = &Name[maxNameLen * id];
|
|
perksReInit = true;
|
|
}
|
|
|
|
void __stdcall SetPerkDesc(int id, const char* value) {
|
|
if (id < 0 || id >= PERK_count) return;
|
|
strncpy_s(&Desc[id * descLen], descLen, value, _TRUNCATE);
|
|
perks[id].description = &Desc[descLen * id];
|
|
perksReInit = true;
|
|
}
|
|
|
|
void PerksReset() {
|
|
fakeTraits.clear();
|
|
fakePerks.clear();
|
|
fakeSelectablePerks.clear();
|
|
IgnoringDefaultPerks = 0;
|
|
addPerkMode = 2;
|
|
PerkFreqOverride = 0;
|
|
|
|
if (PerkBoxTitle[0] != 0) {
|
|
PerkBoxTitle[0] = 0;
|
|
SafeWrite32(0x43C77D, 0x488CB);
|
|
}
|
|
|
|
// Reset some settable game values back to the defaults
|
|
// Perk level mod
|
|
PerkLevelMod = 0;
|
|
// Pyromaniac bonus
|
|
SafeWrite8(0x424AB6, 5);
|
|
// Swift Learner bonus
|
|
SafeWrite32(0x4AFAE2, 100);
|
|
// Restore 'Heave Ho' modify fix
|
|
if (perkHeaveHoModFix) {
|
|
SafeWrite8(0x478AC4, 0xBA);
|
|
SafeWrite32(0x478AC5, 0x23);
|
|
perkHeaveHoModFix = false;
|
|
}
|
|
if (perksReInit) PerkSetup(); // restore perk data
|
|
}
|
|
|
|
void PerksSave(HANDLE file) {
|
|
DWORD unused, count = fakeTraits.size();
|
|
WriteFile(file, &count, 4, &unused, 0);
|
|
for (DWORD i = 0; i < count; i++) {
|
|
WriteFile(file, &fakeTraits[i], sizeof(FakePerk), &unused, 0);
|
|
}
|
|
count = fakePerks.size();
|
|
WriteFile(file, &count, 4, &unused, 0);
|
|
for (DWORD i = 0; i < count; i++) {
|
|
WriteFile(file, &fakePerks[i], sizeof(FakePerk), &unused, 0);
|
|
}
|
|
count = fakeSelectablePerks.size();
|
|
WriteFile(file, &count, 4, &unused, 0);
|
|
for (DWORD i = 0; i < count; i++) {
|
|
WriteFile(file, &fakeSelectablePerks[i], sizeof(FakePerk), &unused, 0);
|
|
}
|
|
}
|
|
|
|
bool PerksLoad(HANDLE file) {
|
|
DWORD count, size;
|
|
ReadFile(file, &count, 4, &size, 0);
|
|
if (size != 4) return false;
|
|
for (DWORD i = 0; i < count; i++) {
|
|
FakePerk fp;
|
|
ReadFile(file, &fp, sizeof(FakePerk), &size, 0);
|
|
fakeTraits.push_back(fp);
|
|
}
|
|
ReadFile(file, &count, 4, &size, 0);
|
|
if (size != 4) return false;
|
|
for (DWORD i = 0; i < count; i++) {
|
|
FakePerk fp;
|
|
ReadFile(file, &fp, sizeof(FakePerk), &size, 0);
|
|
fakePerks.push_back(fp);
|
|
}
|
|
ReadFile(file, &count, 4, &size, 0);
|
|
if (size != 4) return false;
|
|
for (DWORD i = 0; i < count; i++) {
|
|
FakePerk fp;
|
|
ReadFile(file, &fp, sizeof(FakePerk), &size, 0);
|
|
fakeSelectablePerks.push_back(fp);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void __stdcall AddPerkMode(DWORD mode) {
|
|
addPerkMode = mode;
|
|
}
|
|
|
|
DWORD __stdcall HasFakePerk(const char* name) {
|
|
if (name[0] == 0) return 0;
|
|
for (DWORD i = 0; i < fakePerks.size(); i++) {
|
|
if (!strcmp(name, fakePerks[i].Name)) {
|
|
return fakePerks[i].Level;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
DWORD __stdcall HasFakeTrait(const char* name) {
|
|
if (name[0] == 0) return 0;
|
|
for (DWORD i = 0; i < fakeTraits.size(); i++) {
|
|
if (!strcmp(name, fakeTraits[i].Name)) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void __stdcall ClearSelectablePerks() {
|
|
fakeSelectablePerks.clear();
|
|
addPerkMode = 2;
|
|
IgnoringDefaultPerks = 0;
|
|
SafeWrite32(0x43C77D, 0x488CB);
|
|
}
|
|
|
|
void PerksEnterCharScreen() {
|
|
RemoveTraitID = -1;
|
|
RemovePerkID.clear();
|
|
RemoveSelectableID.clear();
|
|
}
|
|
|
|
void PerksCancelCharScreen() {
|
|
if (RemoveTraitID != -1) {
|
|
fakeTraits.erase(fakeTraits.begin() + RemoveTraitID, fakeTraits.end());
|
|
}
|
|
if (RemovePerkID.size() > 1) RemovePerkID.sort(); // sorting to correctly remove from the end
|
|
while (!RemovePerkID.empty()) {
|
|
int index = RemovePerkID.back();
|
|
if (!--fakePerks[index].Level) fakePerks.erase(fakePerks.begin() + index);
|
|
RemovePerkID.pop_back();
|
|
}
|
|
}
|
|
|
|
void PerksAcceptCharScreen() {
|
|
if (RemoveSelectableID.size() > 1) {
|
|
RemoveSelectableID.sort();
|
|
RemoveSelectableID.unique();
|
|
}
|
|
while (!RemoveSelectableID.empty()) {
|
|
fakeSelectablePerks.erase(fakeSelectablePerks.begin() + RemoveSelectableID.back());
|
|
RemoveSelectableID.pop_back();
|
|
}
|
|
}
|
|
|
|
void Perks_Init() {
|
|
// Replace functions
|
|
MakeJump(trait_adjust_stat_, trait_adjust_stat_hack); // 0x4B3C7C
|
|
MakeJump(trait_adjust_skill_, trait_adjust_skill_hack); // 0x4B40FC
|
|
|
|
// Fix the carry weight penalty of the Small Frame trait not being applied to bonus Strength points
|
|
smallFrameTraitFix = (GetConfigInt("Misc", "SmallFrameFix", 0) != 0);
|
|
|
|
FastShotTraitFix();
|
|
|
|
// Disable losing unused perks
|
|
SafeWrite16(0x43C369, 0x0DFE); // mov ds:[_free_perk], dh > dec ds:[_free_perk]
|
|
// If there are unused perks, then call the perk selection window
|
|
SafeWrite8(0x43C370, 0xB1); // jmp 0x43C322
|
|
|
|
// Don't show an empty perk selection window
|
|
HookCall(0x43C80B, perks_dialog_hook);
|
|
|
|
// Disable gain perks for bonus stats
|
|
for (int i = STAT_st; i <= STAT_lu; i++) SafeWrite8(GainStatPerks[i][0], (BYTE)GainStatPerks[i][1]);
|
|
|
|
PerkEngineInit();
|
|
// Perk and Trait init
|
|
HookCall(0x44272E, game_init_hook);
|
|
|
|
if (GetConfigString("Misc", "PerksFile", "", &perksFile[2], MAX_PATH - 3)) {
|
|
perksFile[0] = '.';
|
|
perksFile[1] = '\\';
|
|
if (GetFileAttributes(perksFile) == INVALID_FILE_ATTRIBUTES) return;
|
|
|
|
perksEnable = iniGetInt("Perks", "Enable", 1, perksFile);
|
|
traitsEnable = iniGetInt("Traits", "Enable", 1, perksFile);
|
|
|
|
// Engine perks settings
|
|
ReadPerksBonuses(perksFile);
|
|
}
|
|
}
|