mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
Added hook type 3 to HOOK_WITHINPERCEPTION (when AI selects a target) Extended the upper limit of set_pickpocket_max and set_hit_chance_max script functions to 999. Added a fix for combat_is_shot_blocked_ not taking the flags of critters in the line of fire into account. Minor edits to ASM code of some hooks.
527 lines
12 KiB
C++
527 lines
12 KiB
C++
/*
|
|
* sfall
|
|
* Copyright (C) 2008-2016 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 "..\..\..\FalloutEngine\AsmMacros.h"
|
|
#include "..\..\..\FalloutEngine\Fallout2.h"
|
|
|
|
#include "..\..\Combat.h"
|
|
#include "..\..\Criticals.h"
|
|
#include "..\..\CritterStats.h"
|
|
#include "..\..\ScriptExtender.h"
|
|
#include "..\..\Skills.h"
|
|
#include "..\..\Stats.h"
|
|
#include "..\OpcodeContext.h"
|
|
|
|
#include "Stats.h"
|
|
|
|
namespace sfall
|
|
{
|
|
namespace script
|
|
{
|
|
|
|
const char* invalidStat = "%s() - stat number out of range.";
|
|
const char* objNotCritter = "%s() - the object is not a critter.";
|
|
|
|
void op_set_pc_base_stat(OpcodeContext& ctx) {
|
|
int stat = ctx.arg(0).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
((long*)FO_VAR_pc_proto)[9 + stat] = ctx.arg(1).rawValue();
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void op_set_pc_extra_stat(OpcodeContext& ctx) {
|
|
int stat = ctx.arg(0).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
((long*)FO_VAR_pc_proto)[44 + stat] = ctx.arg(1).rawValue();
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void op_get_pc_base_stat(OpcodeContext& ctx) {
|
|
int value = 0,
|
|
stat = ctx.arg(0).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
value = ((long*)FO_VAR_pc_proto)[9 + stat];
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
ctx.setReturn(value);
|
|
}
|
|
|
|
void op_get_pc_extra_stat(OpcodeContext& ctx) {
|
|
int value = 0,
|
|
stat = ctx.arg(0).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
value = ((long*)FO_VAR_pc_proto)[44 + stat];
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
ctx.setReturn(value);
|
|
}
|
|
|
|
void op_set_critter_base_stat(OpcodeContext& ctx) {
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
int stat = ctx.arg(1).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
CritterStats::SetStat(obj, stat, ctx.arg(2).rawValue(), 9);
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void op_set_critter_extra_stat(OpcodeContext& ctx) {
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
int stat = ctx.arg(1).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
CritterStats::SetStat(obj, stat, ctx.arg(2).rawValue(), 44);
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void op_get_critter_base_stat(OpcodeContext& ctx) {
|
|
int result = 0;
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
int stat = ctx.arg(1).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
result = CritterStats::GetStat(obj, stat, 9);
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
ctx.setReturn(result);
|
|
}
|
|
|
|
void op_get_critter_extra_stat(OpcodeContext& ctx) {
|
|
int result = 0;
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
int stat = ctx.arg(1).rawValue();
|
|
if (stat >= 0 && stat < fo::STAT_max_stat) {
|
|
result = CritterStats::GetStat(obj, stat, 44);
|
|
} else {
|
|
ctx.printOpcodeError(invalidStat, ctx.getOpcodeName());
|
|
}
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
ctx.setReturn(result);
|
|
}
|
|
|
|
void op_set_critter_skill_points(OpcodeContext& ctx) {
|
|
unsigned long skill = ctx.arg(1).rawValue();
|
|
if (skill > 17) {
|
|
ctx.printOpcodeError("%s() - incorrect skill number.", ctx.getOpcodeName());
|
|
return;
|
|
}
|
|
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
fo::Proto* proto = fo::GetProto(obj->protoId);
|
|
if (proto) proto->critter.skills[skill] = ctx.arg(2).rawValue();
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void op_get_critter_skill_points(OpcodeContext& ctx) {
|
|
unsigned long skill = ctx.arg(1).rawValue();
|
|
if (skill > 17) {
|
|
ctx.printOpcodeError("%s() - incorrect skill number.", ctx.getOpcodeName());
|
|
return;
|
|
}
|
|
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
fo::Proto* proto = fo::GetProto(obj->protoId);
|
|
if (proto) ctx.setReturn(proto->critter.skills[skill]);
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_available_skill_points() {
|
|
__asm {
|
|
mov esi, ecx;
|
|
_GET_ARG_INT(end);
|
|
mov edx, eax;
|
|
xor eax, eax;
|
|
call fo::funcoffs::stat_pc_set_;
|
|
end:
|
|
mov ecx, esi;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_get_available_skill_points() {
|
|
__asm {
|
|
mov edx, dword ptr ds:[FO_VAR_curr_pc_stat];
|
|
_J_RET_VAL_TYPE(VAR_TYPE_INT);
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_mod_skill_points_per_level() {
|
|
__asm {
|
|
mov esi, ecx;
|
|
_GET_ARG_INT(end);
|
|
mov ecx, 100;
|
|
cmp eax, ecx;
|
|
cmovg eax, ecx;
|
|
neg ecx; // -100
|
|
cmp eax, ecx;
|
|
cmovl eax, ecx;
|
|
add eax, 5; // add fallout default points
|
|
push eax;
|
|
push 0x43C27A;
|
|
call SafeWrite8;
|
|
end:
|
|
mov ecx, esi;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_get_critter_current_ap() {
|
|
using namespace fo;
|
|
using namespace Fields;
|
|
__asm {
|
|
_GET_ARG_INT(fail); // Get function arg and check if valid
|
|
test eax, eax;
|
|
jz fail;
|
|
mov edx, [eax + protoId];
|
|
shr edx, 24;
|
|
cmp edx, OBJ_TYPE_CRITTER;
|
|
jnz fail;
|
|
mov edx, [eax + movePoints];
|
|
end:
|
|
mov eax, ebx;
|
|
_J_RET_VAL_TYPE(VAR_TYPE_INT); // Pass back the result
|
|
fail:
|
|
xor edx, edx;
|
|
jmp end;
|
|
}
|
|
}
|
|
|
|
void op_set_critter_current_ap(OpcodeContext& ctx) {
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
long ap = ctx.arg(1).rawValue();
|
|
if (ap < 0) ap = 0;
|
|
obj->critter.movePoints = ap;
|
|
|
|
if (obj == fo::var::obj_dude) fo::func::intface_update_move_points(ap, fo::var::combat_free_move);
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_pickpocket_max() {
|
|
__asm {
|
|
mov esi, ecx;
|
|
_GET_ARG_INT(end);
|
|
xor edx, edx;
|
|
test eax, eax;
|
|
cmovs eax, edx; // max < 0
|
|
mov ecx, 999;
|
|
cmp eax, ecx;
|
|
cmova eax, ecx; // 0 - 999 (maximum)
|
|
push edx; // mod (0)
|
|
push eax; // maximum
|
|
push 0xFFFFFFFF; // critter
|
|
call SetPickpocketMax;
|
|
end:
|
|
mov ecx, esi;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_hit_chance_max() {
|
|
__asm {
|
|
mov esi, ecx;
|
|
_GET_ARG_INT(end);
|
|
xor edx, edx;
|
|
test eax, eax;
|
|
cmovs eax, edx; // max < 0
|
|
mov ecx, 999;
|
|
cmp eax, ecx;
|
|
cmova eax, ecx; // 0 - 999 (maximum)
|
|
push edx; // mod (0)
|
|
push eax; // maximum
|
|
push 0xFFFFFFFF; // critter
|
|
call SetHitChanceMax;
|
|
end:
|
|
mov ecx, esi;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void op_set_critter_hit_chance_mod(OpcodeContext& ctx) {
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
SetHitChanceMax(obj, ctx.arg(1).rawValue(), ctx.arg(2).rawValue());
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_base_hit_chance_mod() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
push 0xFFFFFFFF;
|
|
call SetHitChanceMax;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void op_set_critter_pickpocket_mod(OpcodeContext& ctx) {
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
SetPickpocketMax(obj, ctx.arg(1).rawValue(), ctx.arg(2).rawValue());
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_base_pickpocket_mod() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
push 0xFFFFFFFF;
|
|
call SetPickpocketMax;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void op_set_critter_skill_mod(OpcodeContext& ctx) {
|
|
fo::GameObject* obj = ctx.arg(0).object();
|
|
if (obj->Type() == fo::OBJ_TYPE_CRITTER) {
|
|
SetSkillMax(obj, ctx.arg(1).rawValue());
|
|
} else {
|
|
ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName());
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_base_skill_mod() { // same as set_skill_max
|
|
__asm {
|
|
mov esi, ecx;
|
|
_GET_ARG_INT(end);
|
|
push eax;
|
|
push 0xFFFFFFFF;
|
|
call SetSkillMax;
|
|
end:
|
|
mov ecx, esi;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_skill_max() {
|
|
__asm {
|
|
mov esi, ecx;
|
|
_GET_ARG_INT(end);
|
|
mov ecx, 300;
|
|
cmp eax, ecx;
|
|
cmova eax, ecx; // 0 - 300
|
|
push eax;
|
|
push 0xFFFFFFFF;
|
|
call SetSkillMax;
|
|
end:
|
|
mov ecx, esi;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_stat_max() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
push ecx;
|
|
push eax;
|
|
call SetPCStatMax;
|
|
call SetNPCStatMax;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_stat_min() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
push ecx;
|
|
push eax;
|
|
call SetPCStatMin;
|
|
call SetNPCStatMin;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_pc_stat_max() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
call SetPCStatMax;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_pc_stat_min() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
call SetPCStatMin;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_npc_stat_max() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
call SetNPCStatMax;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void __declspec(naked) op_set_npc_stat_min() {
|
|
__asm {
|
|
push ecx;
|
|
_GET_ARG(ecx, esi);
|
|
mov eax, ebx;
|
|
_GET_ARG_INT(end);
|
|
cmp si, VAR_TYPE_INT;
|
|
jne end;
|
|
push ecx;
|
|
push eax;
|
|
call SetNPCStatMin;
|
|
end:
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void mf_get_stat_max(OpcodeContext& ctx) {
|
|
int who = (ctx.numArgs() > 1) ? ctx.arg(1).rawValue() : 0;
|
|
ctx.setReturn(
|
|
Stats::GetStatMax(ctx.arg(0).rawValue(), who)
|
|
);
|
|
}
|
|
|
|
void mf_get_stat_min(OpcodeContext& ctx) {
|
|
int who = (ctx.numArgs() > 1) ? ctx.arg(1).rawValue() : 0;
|
|
ctx.setReturn(
|
|
Stats::GetStatMin(ctx.arg(0).rawValue(), who)
|
|
);
|
|
}
|
|
|
|
static void __declspec(naked) statPCAddExperienceCheckPMs_hack() {
|
|
static DWORD xpTemp;
|
|
__asm {
|
|
mov ebp, [esp]; // return addr
|
|
mov xpTemp, eax; // experience
|
|
fild xpTemp;
|
|
fmul Stats::experienceMod;
|
|
fistp xpTemp;
|
|
mov eax, xpTemp;
|
|
sub esp, 0xC; // instead of 0x10
|
|
mov edi, eax;
|
|
jmp ebp;
|
|
}
|
|
}
|
|
|
|
void op_set_xp_mod(OpcodeContext& ctx) {
|
|
static bool xpModPatch = false;
|
|
long percent = ctx.arg(0).rawValue() & 0xFFFF;
|
|
Stats::experienceMod = percent / 100.0f;
|
|
|
|
if (xpModPatch) return;
|
|
xpModPatch = true;
|
|
MakeCall(0x4AFABD, statPCAddExperienceCheckPMs_hack);
|
|
}
|
|
|
|
}
|
|
}
|