mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
509 lines
14 KiB
C++
509 lines
14 KiB
C++
/*
|
|
* sfall
|
|
* Copyright (C) 2008-2014 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\Fallout2.h"
|
|
#include "..\Logging.h"
|
|
#include "..\SimplePatch.h"
|
|
#include "Scripting\Arrays.h"
|
|
#include "LoadGameHook.h"
|
|
#include "MainLoopHook.h"
|
|
#include "ScriptExtender.h"
|
|
|
|
#include "Explosions.h"
|
|
|
|
namespace sfall
|
|
{
|
|
|
|
using namespace fo::Fields;
|
|
|
|
struct explosiveInfo {
|
|
DWORD pid;
|
|
DWORD pidActive;
|
|
DWORD minDamage;
|
|
DWORD maxDamage;
|
|
};
|
|
|
|
std::vector<explosiveInfo> explosives;
|
|
|
|
static bool lightingEnabled = false;
|
|
static bool explosionsMetaruleReset = false;
|
|
static bool explosionsDamageReset = false;
|
|
static bool explosionMaxTargetReset = false;
|
|
|
|
// enable lighting for flying projectile, based on projectile PID data (light intensity & radius)
|
|
static void __declspec(naked) ranged_attack_lighting_fix() {
|
|
static const DWORD ranged_attack_lighting_fix_back = 0x4118F8;
|
|
__asm {
|
|
mov eax, [esp + 40]; // source projectile ptr - 1st arg
|
|
mov ecx, [eax + lightIntensity]; // check existing light intensity
|
|
test ecx, ecx; // if non-zero, skip obj_set_light call
|
|
jnz skip;
|
|
mov ecx, [esp + 0x1C]; // protoPtr of projectile
|
|
mov edx, [ecx + 0x0C]; // light radius - 2nd arg
|
|
mov ebx, [ecx + 0x10]; // light intensity - 4th arg
|
|
xor ecx, ecx; // unknown(0) - 3rd argument
|
|
call fo::funcoffs::obj_set_light_;
|
|
skip:
|
|
jmp ranged_attack_lighting_fix_back; // jump back
|
|
}
|
|
}
|
|
|
|
static DWORD explosion_effect_starting_dir = 0;
|
|
|
|
static void __declspec(naked) explosion_effect_hook() {
|
|
static const DWORD explosion_effect_hook_back = 0x411AB9;
|
|
__asm {
|
|
mov bl, lightingEnabled;
|
|
test bl, bl;
|
|
jz usevanilla;
|
|
xor ebx, ebx;
|
|
call fo::funcoffs::register_object_animate_;
|
|
jmp next;
|
|
usevanilla:
|
|
xor ebx, ebx;
|
|
call fo::funcoffs::register_object_animate_and_hide_;
|
|
next:
|
|
mov al, lightingEnabled;
|
|
test al, al;
|
|
jz skiplight;
|
|
mov eax, [esp + 40]; // projectile ptr - 1st arg
|
|
mov edx, 0xFFFF0008; // maximum radius + intensity (see anim_set_check_light_fix)
|
|
xor ebx, ebx;
|
|
call fo::funcoffs::register_object_light_;
|
|
skiplight:
|
|
mov edi, explosion_effect_starting_dir; // starting direction
|
|
jmp explosion_effect_hook_back; // jump back
|
|
}
|
|
}
|
|
|
|
// enable lighting for central explosion object (action_explode)
|
|
static void __declspec(naked) explosion_lighting_fix2() {
|
|
static const DWORD explosion_lighting_fix2_back = 0x412FC7;
|
|
__asm {
|
|
mov eax, [esp + 24];
|
|
mov edx, 1; // turn on
|
|
xor ebx, ebx;
|
|
call fo::funcoffs::register_object_funset_;
|
|
|
|
mov eax, [esp + 24]; // explosion obj ptr
|
|
mov edx, 0xFFFF0008; // maximum radius + intensity (see anim_set_check_light_fix)
|
|
xor ebx, ebx;
|
|
call fo::funcoffs::register_object_light_;
|
|
|
|
mov eax, [esp + 24];
|
|
xor edx, edx;
|
|
xor ebx, ebx;
|
|
call fo::funcoffs::register_object_animate_;
|
|
jmp explosion_lighting_fix2_back; // jump back
|
|
}
|
|
}
|
|
|
|
//DWORD __stdcall LogThis(DWORD value1, DWORD value2, DWORD value3) {
|
|
// dlog_f("anim_set_check_light_fix: object 0x%X, something 0x%X, radius 0x%X", DL_MAIN, value1, value2, value3);
|
|
// return value1;
|
|
//}
|
|
|
|
static void __declspec(naked) anim_set_check_light_fix() {
|
|
static const DWORD anim_set_check_light_back = 0x415A4C;
|
|
__asm {
|
|
mov eax, [esi + 4]; // object
|
|
lea ecx, [esp + 16]; // unknown.. something related to next "tile_refresh_rect" call?
|
|
mov edx, [esi + 12]; // radius set in "reg_anim_light"
|
|
mov ebx, edx;
|
|
shr ebx, 16; // take highest 2 bytes
|
|
test ebx, ebx;
|
|
jz nothingspecial;
|
|
// special case
|
|
and edx, 0xFF; // use lowest byte as radius, highest 2 bytes as intensity
|
|
inc ebx;
|
|
jmp end;
|
|
nothingspecial:
|
|
mov ebx, [eax + 112]; // object current light intensity (original behavior)
|
|
end:
|
|
jmp anim_set_check_light_back; // jump back right to the "obj_set_light" call
|
|
}
|
|
}
|
|
|
|
// enable lighting for burning poor guy
|
|
static void __declspec(naked) fire_dance_lighting_fix1() {
|
|
static const DWORD fire_dance_lighting_back = 0x410A4F;
|
|
__asm {
|
|
push edx;
|
|
push ebx;
|
|
mov eax, esi; // projectile ptr - 1st arg
|
|
mov edx, 0xFFFF0002; // maximum radius + intensity (see anim_set_check_light_fix)
|
|
xor ebx, ebx;
|
|
call fo::funcoffs::register_object_light_;
|
|
mov eax, esi;
|
|
pop ebx;
|
|
pop edx;
|
|
call fo::funcoffs::register_object_animate_; // overwritten call
|
|
mov eax, esi; // projectile ptr - 1st arg
|
|
mov edx, 0x00010000; // maximum radius + intensity (see anim_set_check_light_fix)
|
|
mov ebx, -1;
|
|
call fo::funcoffs::register_object_light_;
|
|
jmp fire_dance_lighting_back; // jump back
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
|
|
static DWORD __fastcall CheckExplosives(DWORD register pid) {
|
|
for (const auto &item: explosives) {
|
|
if (item.pid == pid) return item.pidActive;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static DWORD __fastcall CheckActiveExplosives(DWORD register pid) {
|
|
for (const auto &item: explosives) {
|
|
if (item.pidActive == pid) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static DWORD __fastcall GetDamage(DWORD register pid, DWORD &min, DWORD &max) {
|
|
DWORD result = 0;
|
|
for (const auto &item: explosives) {
|
|
if (item.pidActive == pid) {
|
|
min = item.minDamage;
|
|
max = item.maxDamage;
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DWORD __fastcall SetQueueExplosionDamage(DWORD register pid) {
|
|
DWORD min, max;
|
|
DWORD result = GetDamage(pid, min, max);
|
|
|
|
if (result) {
|
|
__asm mov edx, min;
|
|
__asm mov ecx, max;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void __declspec(naked) obj_use_explosive_hack() {
|
|
using namespace fo;
|
|
__asm {
|
|
cmp edx, PID_PLASTIC_EXPLOSIVES;
|
|
jz end;
|
|
// end engine code
|
|
push edx;
|
|
mov ecx, edx;
|
|
call CheckExplosives;
|
|
pop edx;
|
|
test eax, eax;
|
|
jnz end;
|
|
retn;
|
|
end:
|
|
mov dword ptr [esp], 0x49BCE0;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) obj_use_explosive_active_hack() {
|
|
using namespace fo;
|
|
__asm {
|
|
cmp eax, PID_PLASTIC_EXPLOSIVES;
|
|
jz end;
|
|
// end engine code
|
|
mov ecx, eax;
|
|
call CheckExplosives;
|
|
test eax, eax;
|
|
jz skipSet;
|
|
mov dword ptr [esi + protoId], eax; // change item pid to active;
|
|
skipSet:
|
|
mov dword ptr [esp], 0x49BD62;
|
|
end:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) queue_do_explosion_hack() {
|
|
using namespace fo;
|
|
__asm {
|
|
cmp edx, PID_ACTIVE_DYNAMITE;
|
|
jz dynamite;
|
|
cmp edx, PID_ACTIVE_PLASTIC_EXPLOSIVE;
|
|
jz end;
|
|
push edx;
|
|
mov ecx, edx;
|
|
call SetQueueExplosionDamage;
|
|
test eax, eax;
|
|
mov ebx, edx;
|
|
pop edx;
|
|
jz end;
|
|
mov dword ptr [esp], 0x4A2888;
|
|
end:
|
|
retn;
|
|
dynamite:
|
|
mov dword ptr [esp], 0x4A2872;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) inven_action_cursor_drop_hack() {
|
|
using namespace fo;
|
|
__asm {
|
|
cmp ebx, PID_ACTIVE_PLASTIC_EXPLOSIVE;
|
|
jz end;
|
|
// end engine code
|
|
push eax;
|
|
mov ecx, ebx;
|
|
call CheckActiveExplosives;
|
|
test eax, eax; // check in engine
|
|
pop eax;
|
|
end:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) protinstTestDroppedExplosive_hack() {
|
|
__asm {
|
|
jz end;
|
|
mov ecx, edx;
|
|
call CheckActiveExplosives;
|
|
test eax, eax;
|
|
jz end;
|
|
mov dword ptr [esp], 0x49C112; // exit, no active item explosive
|
|
end:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void apply_expl_hack() {
|
|
MakeCall(0x49BCC7, obj_use_explosive_hack); // check explosives
|
|
MakeCall(0x49BD56, obj_use_explosive_active_hack); // set active explosive
|
|
MakeCall(0x4A2865, queue_do_explosion_hack); // set damage explosive
|
|
MakeCall(0x4737F2, inven_action_cursor_drop_hack, 1); // check drop explosives
|
|
MakeCall(0x49C005, protinstTestDroppedExplosive_hack, 1); // check drop explosives
|
|
}
|
|
|
|
void Explosions::AddToExplosives(DWORD pid, DWORD activePid, DWORD minDmg, DWORD maxDmg) {
|
|
static bool onlyOnce = false;
|
|
for (unsigned int i = 0; i < explosives.size(); i++) {
|
|
if (explosives[i].pid == pid) {
|
|
explosives.erase(explosives.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
explosives.push_back({ pid, activePid, minDmg, maxDmg });
|
|
|
|
if (!onlyOnce) {
|
|
apply_expl_hack();
|
|
onlyOnce = true;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
|
|
static const DWORD explosion_dmg_check_adr[] = {0x411709, 0x4119FC, 0x411C08, 0x4517C1, 0x423BC8, 0x42381A};
|
|
static const DWORD explosion_art_adr[] = {0x411A19, 0x411A29, 0x411A35, 0x411A3C};
|
|
static const DWORD explosion_art_defaults[] = {10, 2, 31, 29};
|
|
static const DWORD explosion_radius_grenade = 0x479183;
|
|
static const DWORD explosion_radius_rocket = 0x47918B;
|
|
|
|
static const DWORD dynamite_min_dmg_addr = 0x4A2878;
|
|
static const DWORD dynamite_max_dmg_addr = 0x4A2873;
|
|
static const DWORD plastic_min_dmg_addr = 0x4A2884;
|
|
static const DWORD plastic_max_dmg_addr = 0x4A287F;
|
|
|
|
// default values
|
|
static DWORD dynamite_minDmg;
|
|
static DWORD dynamite_maxDmg;
|
|
static DWORD plastic_minDmg;
|
|
static DWORD plastic_maxDmg;
|
|
static DWORD set_expl_radius_grenade = 2;
|
|
static DWORD set_expl_radius_rocket = 3;
|
|
|
|
static const size_t numArtChecks = sizeof(explosion_art_adr) / sizeof(explosion_art_adr[0]);
|
|
|
|
static void SetExplosionRadius(int arg1, int arg2) {
|
|
SafeWrite32(explosion_radius_grenade, arg1);
|
|
SafeWrite32(explosion_radius_rocket, arg2);
|
|
}
|
|
|
|
static void SetExplosionDamage(int pid, int min, int max) {
|
|
explosionsDamageReset = true;
|
|
switch (pid) {
|
|
case fo::ProtoId::PID_DYNAMITE:
|
|
SafeWrite32(dynamite_min_dmg_addr, min);
|
|
SafeWrite32(dynamite_max_dmg_addr, max);
|
|
break;
|
|
case fo::ProtoId::PID_PLASTIC_EXPLOSIVES:
|
|
SafeWrite32(plastic_min_dmg_addr, min);
|
|
SafeWrite32(plastic_max_dmg_addr, max);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int GetExplosionDamage(int pid) {
|
|
DWORD min = 0, max = 0;
|
|
switch (pid) {
|
|
case fo::ProtoId::PID_DYNAMITE:
|
|
min = *(DWORD*)dynamite_min_dmg_addr;
|
|
max = *(DWORD*)dynamite_max_dmg_addr;
|
|
break;
|
|
case fo::ProtoId::PID_PLASTIC_EXPLOSIVES:
|
|
min = *(DWORD*)plastic_min_dmg_addr;
|
|
max = *(DWORD*)plastic_max_dmg_addr;
|
|
break;
|
|
default:
|
|
GetDamage(pid, min, max);
|
|
}
|
|
|
|
DWORD arrayId = script::CreateTempArray(2, 0);
|
|
script::arrays[arrayId].val[0] = min;
|
|
script::arrays[arrayId].val[1] = max;
|
|
|
|
return arrayId;
|
|
}
|
|
|
|
enum MetaruleExplosionsMode {
|
|
EXPL_FORCE_EXPLOSION_PATTERN = 1,
|
|
EXPL_FORCE_EXPLOSION_ART = 2,
|
|
EXPL_FORCE_EXPLOSION_RADIUS = 3,
|
|
EXPL_FORCE_EXPLOSION_DMGTYPE = 4,
|
|
EXPL_STATIC_EXPLOSION_RADIUS = 5,
|
|
EXPL_GET_EXPLOSION_DAMAGE = 6,
|
|
EXPL_SET_DYNAMITE_EXPLOSION_DAMAGE = 7,
|
|
EXPL_SET_PLASTIC_EXPLOSION_DAMAGE = 8,
|
|
EXPL_SET_EXPLOSION_MAX_TARGET = 9,
|
|
};
|
|
|
|
int __stdcall ExplosionsMetaruleFunc(int mode, int arg1, int arg2) {
|
|
switch (mode) {
|
|
case EXPL_FORCE_EXPLOSION_PATTERN:
|
|
if (arg1) {
|
|
explosion_effect_starting_dir = 2; // bottom-right
|
|
SafeWrite8(0x411B54, 4); // bottom-left + 1
|
|
} else {
|
|
explosion_effect_starting_dir = 0;
|
|
SafeWrite8(0x411B54, 6); // last direction
|
|
}
|
|
break;
|
|
case EXPL_FORCE_EXPLOSION_ART:
|
|
SafeWriteBatch<DWORD>((BYTE)arg1, explosion_art_adr);
|
|
break;
|
|
case EXPL_FORCE_EXPLOSION_RADIUS:
|
|
SetExplosionRadius(arg1, arg1);
|
|
break;
|
|
case EXPL_FORCE_EXPLOSION_DMGTYPE:
|
|
SafeWriteBatch<BYTE>((BYTE)arg1, explosion_dmg_check_adr);
|
|
break;
|
|
case EXPL_STATIC_EXPLOSION_RADIUS:
|
|
if (arg1 > 0) set_expl_radius_grenade = arg1;
|
|
if (arg2 > 0) set_expl_radius_rocket = arg2;
|
|
SetExplosionRadius(set_expl_radius_grenade, set_expl_radius_rocket);
|
|
break;
|
|
case EXPL_GET_EXPLOSION_DAMAGE:
|
|
return GetExplosionDamage(arg1);
|
|
case EXPL_SET_DYNAMITE_EXPLOSION_DAMAGE:
|
|
SetExplosionDamage(fo::ProtoId::PID_DYNAMITE, arg1, arg2);
|
|
return 0;
|
|
case EXPL_SET_PLASTIC_EXPLOSION_DAMAGE:
|
|
SetExplosionDamage(fo::ProtoId::PID_PLASTIC_EXPLOSIVES, arg1, arg2);
|
|
return 0;
|
|
case EXPL_SET_EXPLOSION_MAX_TARGET:
|
|
if (arg1 > 0 && arg1 < 7) {
|
|
SafeWrite8(0x423C93, arg1);
|
|
explosionMaxTargetReset = true;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
if (mode != EXPL_STATIC_EXPLOSION_RADIUS) explosionsMetaruleReset = true;
|
|
return 0;
|
|
}
|
|
|
|
void ResetExplosionSettings() {
|
|
if (!explosionsMetaruleReset) return;
|
|
// explosion pattern
|
|
explosion_effect_starting_dir = 0;
|
|
SafeWrite8(0x411B54, 6); // last direction
|
|
// explosion art
|
|
for (int i = 0; i < numArtChecks; i++) {
|
|
SafeWrite32(explosion_art_adr[i], explosion_art_defaults[i]);
|
|
}
|
|
// explosion radiuses
|
|
SetExplosionRadius(set_expl_radius_grenade, set_expl_radius_rocket);
|
|
// explosion dmgtype
|
|
SafeWriteBatch<BYTE>(fo::DamageType::DMG_explosion, explosion_dmg_check_adr);
|
|
// explosion max target count
|
|
if (explosionMaxTargetReset) {
|
|
SafeWrite8(0x423C93, 6);
|
|
explosionMaxTargetReset = false;
|
|
}
|
|
explosionsMetaruleReset = false;
|
|
}
|
|
|
|
void ResetExplosionRadius() {
|
|
if (set_expl_radius_grenade != 2 || set_expl_radius_rocket != 3) SetExplosionRadius(2, 3);
|
|
}
|
|
|
|
static void ResetExplosionDamage() {
|
|
if (!explosives.empty()) explosives.clear();
|
|
|
|
if (!explosionsDamageReset) return;
|
|
SafeWrite32(dynamite_min_dmg_addr, dynamite_minDmg);
|
|
SafeWrite32(dynamite_max_dmg_addr, dynamite_maxDmg);
|
|
SafeWrite32(plastic_min_dmg_addr, plastic_minDmg);
|
|
SafeWrite32(plastic_max_dmg_addr, plastic_maxDmg);
|
|
explosionsDamageReset = false;
|
|
}
|
|
|
|
void Explosions::init() {
|
|
MakeJump(0x411AB4, explosion_effect_hook); // required for explosions_metarule
|
|
|
|
lightingEnabled = GetConfigInt("Misc", "ExplosionsEmitLight", 0) != 0;
|
|
if (lightingEnabled) {
|
|
dlog("Applying Explosion changes.", DL_INIT);
|
|
MakeJump(0x4118E1, ranged_attack_lighting_fix);
|
|
MakeJump(0x410A4A, fire_dance_lighting_fix1);
|
|
MakeJump(0x415A3F, anim_set_check_light_fix); // this allows to change light intensity
|
|
dlogr(" Done", DL_INIT);
|
|
}
|
|
|
|
// initialize explosives
|
|
dynamite_maxDmg = SimplePatch<DWORD>(dynamite_max_dmg_addr, "Misc", "Dynamite_DmgMax", 50, 0, 9999);
|
|
dynamite_minDmg = SimplePatch<DWORD>(dynamite_min_dmg_addr, "Misc", "Dynamite_DmgMin", 30, 0, dynamite_maxDmg);
|
|
plastic_maxDmg = SimplePatch<DWORD>(plastic_max_dmg_addr, "Misc", "PlasticExplosive_DmgMax", 80, 0, 9999);
|
|
plastic_minDmg = SimplePatch<DWORD>(plastic_min_dmg_addr, "Misc", "PlasticExplosive_DmgMin", 40, 0, plastic_maxDmg);
|
|
|
|
// after each combat attack, reset metarule_explosions settings
|
|
MainLoopHook::OnAfterCombatAttack() += ResetExplosionSettings;
|
|
|
|
LoadGameHook::OnGameReset() += []() {
|
|
ResetExplosionRadius();
|
|
ResetExplosionDamage();
|
|
};
|
|
}
|
|
|
|
}
|