Files
sfall/sfall/Modules/Explosions.cpp
T
2020-10-12 11:01:25 +08:00

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();
};
}
}