mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
Added new Unarmed module and UnarmedFile option
* example INI file not yet. Replaced item_w_mp_cost_ function with the sfall implementation. Disabled some code in various modules due to new implementations.
This commit is contained in:
+89
-31
@@ -13,6 +13,7 @@
|
||||
#include "..\Modules\HookScripts\InventoryHs.h"
|
||||
#include "..\Modules\HookScripts\ObjectHs.h"
|
||||
#include "..\Modules\Perks.h"
|
||||
#include "..\Modules\Unarmed.h"
|
||||
|
||||
#include "..\Game\stats.h"
|
||||
|
||||
@@ -23,7 +24,8 @@ namespace game
|
||||
|
||||
namespace sf = sfall;
|
||||
|
||||
constexpr int reloadCostAP = 2; // engine default reload AP cost
|
||||
constexpr int reloadAPCost = 2; // engine default reload AP cost
|
||||
constexpr int unarmedAPCost = 3; // engine default use item AP cost
|
||||
|
||||
static std::array<long, 3> healingItemPids = {fo::PID_STIMPAK, fo::PID_SUPER_STIMPAK, fo::PID_HEALING_POWDER};
|
||||
|
||||
@@ -121,7 +123,34 @@ long Items::item_weapon_range(fo::GameObject* source, fo::GameObject* weapon, lo
|
||||
// return item_weapon_range(source, fo::func::item_hit_with(source, hitMode), hitMode);
|
||||
//}
|
||||
|
||||
static bool fastShotTweak = false;
|
||||
static int fastShotTweak;
|
||||
|
||||
static long item_w_mp_cost_sub(fo::GameObject* source, fo::GameObject* item, long hitMode, long isCalled, long cost) {
|
||||
if (isCalled) cost++;
|
||||
if (cost < 0) cost = 0;
|
||||
|
||||
long type = fo::func::item_w_subtype(item, hitMode);
|
||||
|
||||
if (source->protoId == fo::ProtoID::PID_Player && sf::Perks::DudeHasTrait(fo::Trait::TRAIT_fast_shot)) {
|
||||
// Alternative behaviors of the Fast Shot trait
|
||||
if (item && fastShotTweak > 2) { // Fallout 1 behavior (allowed for all weapons)
|
||||
cost--;
|
||||
} else if (fastShotTweak == 2) { // Alternative behavior (allowed for all attacks)
|
||||
cost--;
|
||||
} else if (fastShotTweak < 2 && type > fo::AttackSubType::MELEE && fo::func::item_w_range(source, hitMode) >= 2) { // Fallout 2 behavior (with Haenlomal's fix)
|
||||
cost--;
|
||||
}
|
||||
}
|
||||
if ((type == fo::AttackSubType::MELEE || type == fo::AttackSubType::UNARMED) && Stats::perk_level(source, fo::Perk::PERK_bonus_hth_attacks)) {
|
||||
cost--;
|
||||
}
|
||||
if (type == fo::AttackSubType::GUNS && Stats::perk_level(source, fo::Perk::PERK_bonus_rate_of_fire)) {
|
||||
cost--;
|
||||
}
|
||||
if (cost < 1) cost = 1;
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
// Implementation of item_w_primary_mp_cost_ and item_w_secondary_mp_cost_ engine functions in a single function with the HOOK_CALCAPCOST hook
|
||||
long __fastcall Items::item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, long hitMode, long isCalled) {
|
||||
@@ -139,40 +168,18 @@ long __fastcall Items::item_weapon_mp_cost(fo::GameObject* source, fo::GameObjec
|
||||
case fo::AttackType::ATKTYPE_LWEAPON_RELOAD:
|
||||
case fo::AttackType::ATKTYPE_RWEAPON_RELOAD:
|
||||
if (weapon && weapon->protoId != fo::ProtoID::PID_SOLAR_SCORCHER) { // Solar Scorcher has no reload AP cost
|
||||
cost = reloadCostAP;
|
||||
cost = reloadAPCost;
|
||||
if (fo::util::GetProto(weapon->protoId)->item.weapon.perk == fo::Perk::PERK_weapon_fast_reload) {
|
||||
cost--;
|
||||
}
|
||||
}
|
||||
goto endReload;
|
||||
}
|
||||
if (hitMode < fo::AttackType::ATKTYPE_LWEAPON_RELOAD) {
|
||||
if (isCalled) cost++;
|
||||
if (cost < 0) cost = 0;
|
||||
|
||||
long type = fo::func::item_w_subtype(weapon, hitMode);
|
||||
cost = item_w_mp_cost_sub(source, weapon, hitMode, isCalled, cost);
|
||||
|
||||
if (source->protoId == fo::ProtoID::PID_Player && sf::Perks::DudeHasTrait(fo::Trait::TRAIT_fast_shot)) {
|
||||
if (fastShotTweak || // Fallout 1 behavior and Alternative behavior (allowed for all weapons)
|
||||
(fo::func::item_w_range(source, hitMode) >= 2 && type > fo::AttackSubType::MELEE)) // Fallout 2 behavior (with fix) and Haenlomal's tweak
|
||||
{
|
||||
cost--;
|
||||
}
|
||||
}
|
||||
if ((type == fo::AttackSubType::MELEE || type == fo::AttackSubType::UNARMED) && Stats::perk_level(source, fo::Perk::PERK_bonus_hth_attacks)) {
|
||||
cost--;
|
||||
}
|
||||
if (type == fo::AttackSubType::GUNS && Stats::perk_level(source, fo::Perk::PERK_bonus_rate_of_fire)) {
|
||||
cost--;
|
||||
}
|
||||
if (cost < 1) cost = 1;
|
||||
}
|
||||
return sf::CalcApCostHook_Invoke(source, hitMode, isCalled, cost, weapon);
|
||||
}
|
||||
|
||||
// Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook
|
||||
long Items::item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled) {
|
||||
long cost = fo::func::item_w_mp_cost(source, hitMode, isCalled);
|
||||
return sf::CalcApCostHook_Invoke(source, hitMode, isCalled, cost, nullptr);
|
||||
endReload:
|
||||
return sf::CalcApCostHook_Invoke(source, hitMode, isCalled, cost, weapon); // return cost
|
||||
}
|
||||
|
||||
static void __declspec(naked) ai_search_inven_weap_hook() {
|
||||
@@ -187,12 +194,63 @@ static void __declspec(naked) ai_search_inven_weap_hook() {
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook
|
||||
long __fastcall Items::item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled) {
|
||||
fo::GameObject* handItem = nullptr;
|
||||
|
||||
switch (hitMode) {
|
||||
case fo::AttackType::ATKTYPE_LWEAPON_PRIMARY:
|
||||
case fo::AttackType::ATKTYPE_LWEAPON_SECONDARY:
|
||||
case fo::AttackType::ATKTYPE_LWEAPON_RELOAD:
|
||||
handItem = fo::func::inven_left_hand(source);
|
||||
break;
|
||||
case fo::AttackType::ATKTYPE_RWEAPON_PRIMARY:
|
||||
case fo::AttackType::ATKTYPE_RWEAPON_SECONDARY:
|
||||
case fo::AttackType::ATKTYPE_RWEAPON_RELOAD:
|
||||
handItem = fo::func::inven_right_hand(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (handItem) {
|
||||
return Items::item_weapon_mp_cost(source, handItem, hitMode, isCalled);
|
||||
}
|
||||
|
||||
// unarmed hits
|
||||
long cost = unarmedAPCost;
|
||||
if (hitMode == fo::AttackType::ATKTYPE_PUNCH || hitMode == fo::AttackType::ATKTYPE_KICK || hitMode >= fo::AttackType::ATKTYPE_STRONGPUNCH) {
|
||||
cost = sf::Unarmed::GetHitAPCost((fo::AttackType)hitMode);
|
||||
}
|
||||
|
||||
// return cost
|
||||
return sf::CalcApCostHook_Invoke(
|
||||
source,
|
||||
hitMode,
|
||||
isCalled,
|
||||
item_w_mp_cost_sub(source, nullptr, hitMode, isCalled, cost),
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
static void __declspec(naked) item_w_mp_cost_hack() {
|
||||
using namespace fo;
|
||||
__asm {
|
||||
push ebx; // isCalled
|
||||
mov ecx, eax; // source
|
||||
call Items::item_w_mp_cost;
|
||||
pop ecx;
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
void Items::init() {
|
||||
// Replace the item_w_primary_mp_cost_ function with the sfall implementation
|
||||
sf::HookCall(0x429A08, ai_search_inven_weap_hook);
|
||||
|
||||
int fastShotFix = sf::IniReader::GetConfigInt("Misc", "FastShotFix", 0);
|
||||
fastShotTweak = (fastShotFix > 0 && fastShotFix <= 3);
|
||||
// Replace the item_w_mp_cost_ function with the sfall implementation
|
||||
sf::MakeJump(fo::funcoffs::item_w_mp_cost_ + 1, item_w_mp_cost_hack); // 0x478B25
|
||||
|
||||
fastShotTweak = sf::IniReader::GetConfigInt("Misc", "FastShotFix", 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -37,8 +37,8 @@ public:
|
||||
static long __fastcall item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, long hitMode, long isCalled);
|
||||
|
||||
// Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook
|
||||
// Note: Use the generic item_mp_cost function which has a hook call
|
||||
static long item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled);
|
||||
// Note: Can use the generic item_mp_cost_ function which has a hook call
|
||||
static long __fastcall item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -120,7 +120,7 @@ isFloat:
|
||||
jmp NegateFixHack_Back;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static void __declspec(naked) compute_attack_hack() {
|
||||
static const DWORD UnarmedAttacksFixEnd = 0x423A0D;
|
||||
__asm {
|
||||
@@ -157,7 +157,7 @@ end:
|
||||
jmp UnarmedAttacksFixEnd;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
static void __declspec(naked) SharpShooterFix() {
|
||||
__asm {
|
||||
call fo::funcoffs::stat_level_ // Perception
|
||||
@@ -3186,7 +3186,7 @@ void BugFixes::init()
|
||||
|
||||
//if (IniReader::GetConfigInt("Misc", "SpecialUnarmedAttacksFix", 1)) {
|
||||
dlog("Applying Special Unarmed Attacks fix.", DL_FIX);
|
||||
MakeJump(0x42394D, compute_attack_hack);
|
||||
//MakeJump(0x42394D, compute_attack_hack); - implementation moved to Unarmed module
|
||||
dlogr(" Done", DL_FIX);
|
||||
//}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "..\Logging.h"
|
||||
#include "HookScripts.h"
|
||||
|
||||
#include "..\Game\stats.h"
|
||||
|
||||
#include "DamageMod.h"
|
||||
|
||||
namespace sfall
|
||||
@@ -320,7 +322,7 @@ static __declspec(naked) void CommonDmgRngDispFix_hook() {
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static __declspec(naked) void HtHDamageFix1a_hack() {
|
||||
using namespace fo;
|
||||
__asm {
|
||||
@@ -337,7 +339,7 @@ fix:
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
static __declspec(naked) void HtHDamageFix1b_hook() {
|
||||
using namespace fo;
|
||||
__asm {
|
||||
@@ -399,6 +401,14 @@ static void __declspec(naked) DisplayBonusHtHDmg2_hack() {
|
||||
}
|
||||
}
|
||||
|
||||
static bool bonusHtHDamageFix = false;
|
||||
|
||||
long DamageMod::GetHtHMinDamageBonus(fo::GameObject* source) {
|
||||
return (bonusHtHDamageFix)
|
||||
? game::Stats::perk_level(source, fo::Perk::PERK_bonus_hth_damage) << 1 // Rank_of_Bonus_HtH_Damage_perk *= 2
|
||||
: 0;
|
||||
}
|
||||
|
||||
void DamageMod::init() {
|
||||
if (formula = IniReader::GetConfigInt("Misc", "DamageFormula", 0)) {
|
||||
switch (formula) {
|
||||
@@ -414,7 +424,9 @@ void DamageMod::init() {
|
||||
|
||||
int BonusHtHDmgFix = IniReader::GetConfigInt("Misc", "BonusHtHDamageFix", 1);
|
||||
int DisplayBonusDmg = IniReader::GetConfigInt("Misc", "DisplayBonusDamage", 0);
|
||||
|
||||
if (BonusHtHDmgFix) {
|
||||
bonusHtHDamageFix = true;
|
||||
dlog("Applying Bonus HtH Damage Perk fix.", DL_INIT);
|
||||
if (DisplayBonusDmg == 0) { // Subtract damage from perk bonus (vanilla displaying)
|
||||
HookCalls(MeleeDmgDisplayPrintFix_hook, {
|
||||
@@ -426,7 +438,7 @@ void DamageMod::init() {
|
||||
0x472546 // Unarmed (display_stats_)
|
||||
});
|
||||
}
|
||||
MakeCall(0x478492, HtHDamageFix1a_hack); // Unarmed (item_w_damage_)
|
||||
//MakeCall(0x478492, HtHDamageFix1a_hack); // Unarmed (item_w_damage_)
|
||||
HookCall(0x47854C, HtHDamageFix1b_hook); // MeleeWeap (item_w_damage_)
|
||||
dlogr(" Done", DL_INIT);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "Module.h"
|
||||
|
||||
namespace sfall
|
||||
namespace sfall
|
||||
{
|
||||
|
||||
class DamageMod : public Module {
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
static int formula;
|
||||
static void DamageGlovz(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty);
|
||||
static void DamageYAAM(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty);
|
||||
|
||||
static long GetHtHMinDamageBonus(fo::GameObject* source);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ long CalcApCostHook_Invoke(fo::GameObject* source, long hitMode, long isCalled,
|
||||
? CalcApCostHook_Script(source, hitMode, isCalled, cost, weapon)
|
||||
: cost;
|
||||
}
|
||||
|
||||
/*
|
||||
static void __declspec(naked) CalcApCostHook() {
|
||||
__asm {
|
||||
HookBegin;
|
||||
@@ -129,7 +129,7 @@ static void __declspec(naked) CalcApCostHook() {
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
// this is for using non-weapon items, always 2 AP in vanilla
|
||||
static void __declspec(naked) CalcApCostHook2() {
|
||||
__asm {
|
||||
@@ -281,13 +281,11 @@ static void __fastcall FindTargetHook_Script(DWORD* target, fo::GameObject* atta
|
||||
}
|
||||
EndHook();
|
||||
}
|
||||
|
||||
/*
|
||||
void FindTargetHook_Invoke(fo::GameObject* targets[], fo::GameObject* attacker) {
|
||||
if (HookScripts::HookHasScript(HOOK_FINDTARGET)) FindTargetHook_Script((DWORD*)targets, attacker);
|
||||
}
|
||||
*/
|
||||
|
||||
static void __declspec(naked) FindTargetHook() {
|
||||
__asm {
|
||||
push eax;
|
||||
@@ -311,9 +309,10 @@ static void __declspec(naked) ItemDamageHook() {
|
||||
}
|
||||
argCount = 6;
|
||||
|
||||
if (args[2] == 0) { // weapon arg
|
||||
args[4] += 8; // type arg
|
||||
}
|
||||
// tweak for 0x4784AA (obsolete)
|
||||
//if (args[2] == 0) { // weapon arg
|
||||
// args[4] += 8; // type arg
|
||||
//}
|
||||
|
||||
RunHookScript(HOOK_ITEMDAMAGE);
|
||||
|
||||
@@ -610,7 +609,6 @@ static void __declspec(naked) ai_search_inven_weap_hook() {
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fo::GameObject* BestWeaponHook_Invoke(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target) {
|
||||
return (HookScripts::HookHasScript(HOOK_BESTWEAPON))
|
||||
@@ -618,7 +616,6 @@ fo::GameObject* BestWeaponHook_Invoke(fo::GameObject* bestWeapon, fo::GameObject
|
||||
: bestWeapon;
|
||||
}
|
||||
*/
|
||||
|
||||
static bool __stdcall CanUseWeaponHook_Script(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode) {
|
||||
BeginHook();
|
||||
argCount = 4;
|
||||
@@ -695,18 +692,18 @@ void Inject_AfterHitRollHook() {
|
||||
}
|
||||
|
||||
void Inject_CalcApCostHook() {
|
||||
HookCalls(CalcApCostHook, {
|
||||
0x42307A,
|
||||
0x42669F,
|
||||
0x42687B,
|
||||
0x42A625,
|
||||
0x42A655,
|
||||
0x42A686,
|
||||
0x42AE32,
|
||||
0x42AE71,
|
||||
0x460048,
|
||||
0x47807B
|
||||
});
|
||||
//HookCalls(CalcApCostHook, {
|
||||
// 0x42307A,
|
||||
// 0x42669F,
|
||||
// 0x42687B,
|
||||
// 0x42A625,
|
||||
// 0x42A655,
|
||||
// 0x42A686,
|
||||
// 0x42AE32,
|
||||
// 0x42AE71,
|
||||
// 0x460048,
|
||||
// 0x47807B
|
||||
//});
|
||||
MakeCall(0x478083, CalcApCostHook2);
|
||||
}
|
||||
|
||||
|
||||
+10
-8
@@ -1073,7 +1073,7 @@ static void PerkAndTraitSetup() {
|
||||
HookCall(0x4248F9, BlockedTrait); // compute_damage_
|
||||
break;
|
||||
case fo::Trait::TRAIT_fast_shot:
|
||||
HookCall(0x478C8A, BlockedTrait); // item_w_mp_cost_
|
||||
//HookCall(0x478C8A, BlockedTrait); // item_w_mp_cost_ (obsolete)
|
||||
HookCall(0x478E70, BlockedTrait); // item_w_called_shot_
|
||||
break;
|
||||
case fo::Trait::TRAIT_bloody_mess:
|
||||
@@ -1121,7 +1121,7 @@ dlgExit:
|
||||
jmp perks_dialog_Ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static void __declspec(naked) item_w_mp_cost_hook() {
|
||||
__asm {
|
||||
call fo::funcoffs::item_w_range_;
|
||||
@@ -1136,7 +1136,7 @@ checkType:
|
||||
jmp fo::funcoffs::item_w_subtype_; // eax - item
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
// Haenlomal's tweak
|
||||
static void __declspec(naked) item_w_called_shot_hack() {
|
||||
static const DWORD FastShotTraitFix_End = 0x478E7F;
|
||||
@@ -1174,18 +1174,20 @@ static void FastShotTraitFix() {
|
||||
goto fix;
|
||||
case 2:
|
||||
dlog("Applying Fast Shot trait patch (Alternative behavior).", DL_INIT);
|
||||
SafeWrite16(0x478C9F, 0x9090);
|
||||
HookCalls((void*)0x478C7D, {0x478BB8, 0x478BC7, 0x478BD6, 0x478BEA, 0x478BF9, 0x478C08, 0x478C2F});
|
||||
/* Implemented in sfall item_w_mp_cost function */
|
||||
//SafeWrite16(0x478C9F, 0x9090); // item_w_mp_cost_
|
||||
//HookCalls((void*)0x478C7D, {0x478BB8, 0x478BC7, 0x478BD6, 0x478BEA, 0x478BF9, 0x478C08, 0x478C2F}); // jmp 0x478C7D
|
||||
goto done;
|
||||
case 3:
|
||||
dlog("Applying Fast Shot trait patch (Fallout 1 behavior).", DL_INIT);
|
||||
HookCall(0x478C97, (void*)fo::funcoffs::item_hit_with_);
|
||||
SafeWrite16(0x478C9E, CodeType::JumpZ << 8); // ignore all unarmed attacks (cmp eax, 0; jz)
|
||||
/* Implemented in sfall item_w_mp_cost function */
|
||||
//HookCall(0x478C97, (void*)fo::funcoffs::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);
|
||||
//HookCall(0x478C97, item_w_mp_cost_hook); - Fix implemented in sfall item_w_mp_cost function
|
||||
done:
|
||||
dlogr(" Done", DL_INIT);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* sfall
|
||||
* Copyright (C) 2008-2021 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 <algorithm>
|
||||
|
||||
#include "..\main.h"
|
||||
#include "..\FalloutEngine\Fallout2.h"
|
||||
|
||||
#include "DamageMod.h"
|
||||
|
||||
#include "Unarmed.h"
|
||||
|
||||
namespace sfall
|
||||
{
|
||||
|
||||
class Hits {
|
||||
public:
|
||||
static const long count = 20;
|
||||
|
||||
private:
|
||||
struct HitsData {
|
||||
long reqLevel = 0; // intface_update_items_
|
||||
long reqSkill = 0;
|
||||
long reqStat[7] = {0};
|
||||
long minDamage = 1; // default for all
|
||||
long maxDamage = 2; // default for all (maxDamage + STAT_melee_dmg)
|
||||
long bonusDamage = 0; // item_w_damage_
|
||||
long bonusCrit = 0; // compute_attack_
|
||||
long apCost = 3; // item_w_mp_cost_
|
||||
bool isPenetrate = false; // compute_damage_
|
||||
bool isSecondary = false;
|
||||
};
|
||||
|
||||
struct SortHits {
|
||||
char level;
|
||||
char hit; // index in Hits class
|
||||
};
|
||||
|
||||
// sorted in descending order of the required stats in group hits
|
||||
SortHits sortHits[Hits::count - fo::AttackType::ATKTYPE_STRONGPUNCH];
|
||||
|
||||
HitsData hit[Hits::count];
|
||||
|
||||
long skillLevel; // SKILL_UNARMED_COMBAT
|
||||
long psStat[fo::Stat::STAT_base_count];
|
||||
|
||||
public:
|
||||
class Types{
|
||||
public: enum : long {
|
||||
punch = fo::AttackType::ATKTYPE_PUNCH,
|
||||
kick = fo::AttackType::ATKTYPE_KICK,
|
||||
|
||||
strong_punch = fo::AttackType::ATKTYPE_STRONGPUNCH,
|
||||
hammer_punch,
|
||||
haymaker,
|
||||
jab,
|
||||
palm_strike,
|
||||
piercing_strike,
|
||||
|
||||
strong_kick,
|
||||
snap_kick,
|
||||
power_kick,
|
||||
hip_kick,
|
||||
hook_kick,
|
||||
piercing_kick
|
||||
};
|
||||
};
|
||||
|
||||
Hits() {
|
||||
// punch primary 1
|
||||
hit[Types::strong_punch].reqLevel = 1;
|
||||
hit[Types::strong_punch].reqSkill = 55;
|
||||
hit[Types::strong_punch].reqStat[fo::Stat::STAT_ag] = 6;
|
||||
hit[Types::strong_punch].bonusDamage = 3;
|
||||
// secondary 1
|
||||
hit[Types::jab].reqLevel = 5;
|
||||
hit[Types::jab].reqSkill = 75;
|
||||
hit[Types::jab].reqStat[fo::Stat::STAT_st] = 4;
|
||||
hit[Types::jab].reqStat[fo::Stat::STAT_ag] = 6;
|
||||
hit[Types::jab].bonusDamage = 3;
|
||||
hit[Types::jab].bonusCrit = 10;
|
||||
hit[Types::jab].isSecondary = true;
|
||||
|
||||
// primary 2
|
||||
hit[Types::hammer_punch].reqLevel = 6;
|
||||
hit[Types::hammer_punch].reqSkill = 75;
|
||||
hit[Types::hammer_punch].reqStat[fo::Stat::STAT_st] = 5;
|
||||
hit[Types::hammer_punch].reqStat[fo::Stat::STAT_ag] = 6;
|
||||
hit[Types::hammer_punch].bonusDamage = 5;
|
||||
hit[Types::hammer_punch].bonusCrit = 5;
|
||||
// secondary 2
|
||||
hit[Types::palm_strike].reqLevel = 12;
|
||||
hit[Types::palm_strike].reqSkill = 115;
|
||||
hit[Types::palm_strike].reqStat[fo::Stat::STAT_st] = 5;
|
||||
hit[Types::palm_strike].reqStat[fo::Stat::STAT_ag] = 7;
|
||||
hit[Types::palm_strike].bonusDamage = 7;
|
||||
hit[Types::palm_strike].bonusCrit = 20;
|
||||
hit[Types::palm_strike].apCost = 6;
|
||||
hit[Types::palm_strike].isPenetrate = true;
|
||||
hit[Types::palm_strike].isSecondary = true;
|
||||
|
||||
// primary 3
|
||||
hit[Types::haymaker].reqLevel = 9;
|
||||
hit[Types::haymaker].reqSkill = 100;
|
||||
hit[Types::haymaker].reqStat[fo::Stat::STAT_st] = 5;
|
||||
hit[Types::haymaker].reqStat[fo::Stat::STAT_ag] = 7;
|
||||
hit[Types::haymaker].bonusDamage = 7;
|
||||
hit[Types::haymaker].bonusCrit = 15;
|
||||
// secondary 3
|
||||
hit[Types::piercing_strike].reqLevel = 16;
|
||||
hit[Types::piercing_strike].reqSkill = 130;
|
||||
hit[Types::piercing_strike].reqStat[fo::Stat::STAT_st] = 5;
|
||||
hit[Types::piercing_strike].reqStat[fo::Stat::STAT_ag] = 7;
|
||||
hit[Types::piercing_strike].bonusDamage = 10;
|
||||
hit[Types::piercing_strike].bonusCrit = 40;
|
||||
hit[Types::piercing_strike].apCost = 8;
|
||||
hit[Types::piercing_strike].isPenetrate = true;
|
||||
hit[Types::piercing_strike].isSecondary = true;
|
||||
|
||||
// kick primary 1
|
||||
hit[Types::strong_kick].reqLevel = 1;
|
||||
hit[Types::strong_kick].reqSkill = 40;
|
||||
hit[Types::strong_kick].reqStat[fo::Stat::STAT_ag] = 6;
|
||||
hit[Types::strong_kick].bonusDamage = 5;
|
||||
hit[Types::strong_kick].apCost = 4;
|
||||
// secondary 1
|
||||
hit[Types::hip_kick].reqLevel = 6;
|
||||
hit[Types::hip_kick].reqSkill = 60;
|
||||
hit[Types::hip_kick].reqStat[fo::Stat::STAT_st] = 6;
|
||||
hit[Types::hip_kick].reqStat[fo::Stat::STAT_ag] = 7;
|
||||
hit[Types::hip_kick].bonusDamage = 7;
|
||||
hit[Types::hip_kick].apCost = 7;
|
||||
hit[Types::hip_kick].isSecondary = true;
|
||||
|
||||
// primary 2
|
||||
hit[Types::snap_kick].reqLevel = 6;
|
||||
hit[Types::snap_kick].reqSkill = 60;
|
||||
hit[Types::snap_kick].reqStat[fo::Stat::STAT_ag] = 6;
|
||||
hit[Types::snap_kick].bonusDamage = 7;
|
||||
hit[Types::snap_kick].apCost = 4;
|
||||
// secondary 2
|
||||
hit[Types::hook_kick].reqLevel = 12;
|
||||
hit[Types::hook_kick].reqSkill = 100;
|
||||
hit[Types::hook_kick].reqStat[fo::Stat::STAT_st] = 6;
|
||||
hit[Types::hook_kick].reqStat[fo::Stat::STAT_ag] = 7;
|
||||
hit[Types::hook_kick].bonusDamage = 9;
|
||||
hit[Types::hook_kick].bonusCrit = 10;
|
||||
hit[Types::hook_kick].apCost = 7;
|
||||
hit[Types::hook_kick].isPenetrate = true;
|
||||
hit[Types::hook_kick].isSecondary = true;
|
||||
|
||||
// primary 3
|
||||
hit[Types::power_kick].reqLevel = 9;
|
||||
hit[Types::power_kick].reqSkill = 80;
|
||||
hit[Types::power_kick].reqStat[fo::Stat::STAT_st] = 6;
|
||||
hit[Types::power_kick].reqStat[fo::Stat::STAT_ag] = 6;
|
||||
hit[Types::power_kick].bonusDamage = 9;
|
||||
hit[Types::power_kick].bonusCrit = 5;
|
||||
hit[Types::power_kick].apCost = 4;
|
||||
// secondary 3
|
||||
hit[Types::piercing_kick].reqLevel = 15;
|
||||
hit[Types::piercing_kick].reqSkill = 125;
|
||||
hit[Types::piercing_kick].reqStat[fo::Stat::STAT_st] = 6;
|
||||
hit[Types::piercing_kick].reqStat[fo::Stat::STAT_ag] = 8;
|
||||
hit[Types::piercing_kick].bonusDamage = 12;
|
||||
hit[Types::piercing_kick].bonusCrit = 50;
|
||||
hit[Types::piercing_kick].apCost = 9;
|
||||
hit[Types::piercing_kick].isPenetrate = true;
|
||||
hit[Types::piercing_kick].isSecondary = true;
|
||||
}
|
||||
|
||||
HitsData& Hit(fo::AttackType i) { return hit[i]; }
|
||||
|
||||
// Get hit by index
|
||||
HitsData& Hit(size_t index) { return hit[index]; }
|
||||
|
||||
fo::AttackType GetSortHit(size_t index) { return (fo::AttackType)sortHits[index].hit; }
|
||||
|
||||
void Sort() {
|
||||
for (char i = fo::AttackType::ATKTYPE_STRONGPUNCH, j = 0; i < Hits::count; i++, j++) {
|
||||
sortHits[j].level = (char)hit[i].reqLevel;
|
||||
sortHits[j].hit = i;
|
||||
}
|
||||
|
||||
auto greater_comp = [&](const SortHits &a, const SortHits &b) {
|
||||
if (a.level == b.level) {
|
||||
return hit[a.hit].reqSkill > hit[b.hit].reqSkill;
|
||||
}
|
||||
return a.level > b.level;
|
||||
};
|
||||
|
||||
std::sort(&sortHits[0], &sortHits[6], greater_comp);
|
||||
std::sort(&sortHits[6], &sortHits[12], greater_comp);
|
||||
}
|
||||
|
||||
void GetDudeStats(long sLevel) {
|
||||
skillLevel = sLevel;
|
||||
for (size_t stat = 0; stat < fo::Stat::STAT_base_count; stat++) {
|
||||
psStat[stat] = fo::func::stat_level(fo::var::obj_dude, stat);
|
||||
}
|
||||
}
|
||||
|
||||
long SkillLevel() { return skillLevel; }
|
||||
long DudeStat(long stat) { return psStat[stat]; }
|
||||
};
|
||||
|
||||
Hits unarmed;
|
||||
|
||||
static bool UnarmedReqStats(fo::AttackType hit) {
|
||||
if (unarmed.SkillLevel() >= unarmed.Hit(hit).reqSkill && (long)fo::var::Level_pc >= unarmed.Hit(hit).reqLevel) {
|
||||
for (size_t stat = 0; stat < fo::Stat::STAT_base_count; stat++) {
|
||||
if (unarmed.Hit(hit).reqStat[stat] == 0) continue;
|
||||
if (unarmed.Hit(hit).reqStat[stat] > unarmed.DudeStat(stat)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool Punching(bool isPrimary) {
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
fo::AttackType hit = unarmed.GetSortHit(i);
|
||||
if (unarmed.Hit(hit).isSecondary == isPrimary) continue;
|
||||
|
||||
if (UnarmedReqStats(hit)) {
|
||||
if (unarmed.Hit(hit).isSecondary) {
|
||||
fo::var::itemButtonItems[fo::HandSlot::Left].secondaryAttack = hit;
|
||||
} else {
|
||||
fo::var::itemButtonItems[fo::HandSlot::Left].primaryAttack = hit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Punch hits
|
||||
static void __fastcall check_unarmed_left_slot(long skillLevel) {
|
||||
unarmed.GetDudeStats(skillLevel);
|
||||
|
||||
if (!Punching(true)) fo::var::itemButtonItems[fo::HandSlot::Left].primaryAttack = fo::AttackType::ATKTYPE_PUNCH;
|
||||
if (!Punching(false)) fo::var::itemButtonItems[fo::HandSlot::Left].secondaryAttack = fo::AttackType::ATKTYPE_PUNCH;
|
||||
}
|
||||
|
||||
static void __declspec(naked) intface_update_items_hack_punch() {
|
||||
__asm {
|
||||
push 0x45F1D7;
|
||||
jmp check_unarmed_left_slot;
|
||||
}
|
||||
}
|
||||
|
||||
static bool Kicking(bool isPrimary) {
|
||||
for (size_t i = 6; i < 12; i++) {
|
||||
fo::AttackType hit = unarmed.GetSortHit(i);
|
||||
if (unarmed.Hit(hit).isSecondary == isPrimary) continue;
|
||||
|
||||
if (UnarmedReqStats(hit)) {
|
||||
if (unarmed.Hit(hit).isSecondary) {
|
||||
fo::var::itemButtonItems[fo::HandSlot::Right].secondaryAttack = hit;
|
||||
} else {
|
||||
fo::var::itemButtonItems[fo::HandSlot::Right].primaryAttack = hit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kick hits
|
||||
static void check_unarmed_right_slot() {
|
||||
if (!Kicking(true)) fo::var::itemButtonItems[fo::HandSlot::Right].primaryAttack = fo::AttackType::ATKTYPE_KICK;
|
||||
if (!Kicking(false)) fo::var::itemButtonItems[fo::HandSlot::Right].secondaryAttack = fo::AttackType::ATKTYPE_KICK;
|
||||
}
|
||||
|
||||
static void __declspec(naked) intface_update_items_hack_kick() {
|
||||
__asm {
|
||||
push 0x45F380;
|
||||
jmp check_unarmed_right_slot;
|
||||
}
|
||||
}
|
||||
|
||||
static long __fastcall get_unarmed_crit_chance(long &chanceOut, fo::AttackType hit) {
|
||||
chanceOut = unarmed.Hit(hit).bonusCrit;
|
||||
return (chanceOut > 0)
|
||||
? 0x4239F4 //
|
||||
: 0x423A0D; // skip random roll
|
||||
}
|
||||
|
||||
static void __declspec(naked) compute_attack_hack() {
|
||||
__asm {
|
||||
sub esp, 4;
|
||||
mov ecx, esp; // chanceOut ref
|
||||
call get_unarmed_crit_chance; // edx - hit
|
||||
pop ecx; // chance
|
||||
jmp eax;
|
||||
}
|
||||
}
|
||||
|
||||
static long __fastcall get_unarmed_damage(fo::GameObject* source, fo::AttackType hit, long &minOut, long &maxOut) {
|
||||
minOut = unarmed.Hit(hit).minDamage + DamageMod::GetHtHMinDamageBonus(source);
|
||||
maxOut = unarmed.Hit(hit).maxDamage + fo::func::stat_level(source, fo::Stat::STAT_melee_dmg);
|
||||
return unarmed.Hit(hit).bonusDamage;
|
||||
}
|
||||
|
||||
static void __declspec(naked) item_w_damage_hack() {
|
||||
static DWORD item_w_damage_hack_ret = 0x478553;
|
||||
__asm {
|
||||
lea eax, [esp + 4]; // min_DMG
|
||||
lea edx, [esp + 0]; // max_DMG
|
||||
push ecx;
|
||||
push edx; // maxOut ref
|
||||
push eax; // minOut ref
|
||||
mov edx, esi; // hit
|
||||
call get_unarmed_damage;
|
||||
mov ebx, eax; // bonus
|
||||
pop ecx;
|
||||
jmp item_w_damage_hack_ret;
|
||||
}
|
||||
}
|
||||
|
||||
static long __fastcall check_unarmed_penetrate(fo::AttackType hit) {
|
||||
return static_cast<long>(unarmed.Hit(hit).isPenetrate);
|
||||
}
|
||||
|
||||
long Unarmed::GetHitAPCost(fo::AttackType hit) {
|
||||
return unarmed.Hit(hit).apCost;
|
||||
}
|
||||
|
||||
void Unarmed::init() {
|
||||
unarmed = Hits();
|
||||
|
||||
auto unarmedFile = IniReader::GetConfigString("Misc", "UnarmedFile", "", MAX_PATH);
|
||||
if (!unarmedFile.empty()) {
|
||||
const char* file = unarmedFile.insert(0, ".\\").c_str();
|
||||
if (GetFileAttributes(file) != INVALID_FILE_ATTRIBUTES) { // check if file exists
|
||||
char stat[6] = "Stat0";
|
||||
char sHit[4] = "0";
|
||||
for (size_t i = fo::AttackType::ATKTYPE_PUNCH; i < Hits::count; _itoa(++i, sHit, 10)) {
|
||||
if (i < fo::ATKTYPE_STRONGPUNCH && i != fo::ATKTYPE_PUNCH && i != fo::ATKTYPE_KICK) continue;
|
||||
|
||||
auto& hit = unarmed.Hit(i);
|
||||
|
||||
int val = IniReader::GetInt(sHit, "ReqLevel", -1, file);
|
||||
if (val >= 0) hit.reqLevel = val;
|
||||
|
||||
val = IniReader::GetInt(sHit, "SkillLevel", -1, file);
|
||||
if (val >= 0) hit.reqSkill = val;
|
||||
|
||||
val = IniReader::GetInt(sHit, "MinDamage", -1, file);
|
||||
if (val >= 0) hit.minDamage = val;
|
||||
|
||||
val = IniReader::GetInt(sHit, "MaxDamage", -1, file);
|
||||
if (val >= 0) hit.maxDamage = val;
|
||||
|
||||
val = IniReader::GetInt(sHit, "BonusDamage", -1, file);
|
||||
if (val >= 0) hit.bonusDamage = val;
|
||||
|
||||
val = IniReader::GetInt(sHit, "BonusCrit", -1, file);
|
||||
if (val >= 0) hit.bonusCrit = val;
|
||||
|
||||
val = IniReader::GetInt(sHit, "APCost", -1, file);
|
||||
if (val >= 0) hit.apCost = val;
|
||||
|
||||
val = IniReader::GetInt(sHit, "Penetrate", -1, file);
|
||||
if (val >= 0) hit.isPenetrate = (val != 0);
|
||||
|
||||
val = IniReader::GetInt(sHit, "Second", -1, file);
|
||||
if (val >= 0) hit.isSecondary = (val != 0);
|
||||
|
||||
for (size_t s = 0; s < fo::Stat::STAT_base_count; _itoa(++s, &stat[4], 10)) {
|
||||
val = IniReader::GetInt(sHit, stat, -1, file);
|
||||
if (val >= 0) hit.reqStat[s] = val;
|
||||
}
|
||||
stat[4] = '0';
|
||||
}
|
||||
|
||||
// Only if a custom file is used, otherwise use the engine function code
|
||||
MakeJump(0x45F0DF, intface_update_items_hack_punch);
|
||||
MakeJump(0x45F278, intface_update_items_hack_kick);
|
||||
}
|
||||
}
|
||||
unarmed.Sort();
|
||||
|
||||
// Get critical chance hack
|
||||
MakeJump(0x42394D, compute_attack_hack);
|
||||
SafeWrite16(0x423A03, 0xC839); // cmp eax, 50 -> cmp eax, ecx
|
||||
SafeWrite8(0x423A05, CodeType::Nop);
|
||||
|
||||
// Get damage hack
|
||||
MakeJump(0x478492, item_w_damage_hack);
|
||||
|
||||
// compute_damage_ penetrate hack
|
||||
SafeWrite8(0x4248B4, 0x4E); // mov ecx, [hit]
|
||||
MakeCall(0x4248B6, check_unarmed_penetrate, 5);
|
||||
SafeWrite16(0x4248C1, 0x01F8); // cmp eax, 1
|
||||
SafeWrite8(0x4248C8, CodeType::JumpShort);
|
||||
}
|
||||
|
||||
//void Unarmed::exit() {
|
||||
//}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* sfall
|
||||
* Copyright (C) 2008-2021 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Module.h"
|
||||
|
||||
namespace sfall
|
||||
{
|
||||
|
||||
class Unarmed : public Module {
|
||||
public:
|
||||
const char* name() { return "Unarmed"; }
|
||||
void init();
|
||||
//void exit() override;
|
||||
|
||||
static long GetHitAPCost(fo::AttackType hit);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -335,6 +335,7 @@
|
||||
<ClInclude Include="Modules\SubModules\CombatBlock.h" />
|
||||
<ClInclude Include="Modules\SubModules\EnginePerks.h" />
|
||||
<ClInclude Include="Modules\SubModules\WindowRender.h" />
|
||||
<ClInclude Include="Modules\Unarmed.h" />
|
||||
<ClInclude Include="Modules\Worldmap.h" />
|
||||
<ClInclude Include="Modules\ScriptExtender.h" />
|
||||
<ClInclude Include="Modules\BarBoxes.h" />
|
||||
@@ -449,6 +450,7 @@
|
||||
<ClCompile Include="Modules\SubModules\CombatBlock.cpp" />
|
||||
<ClCompile Include="Modules\SubModules\EnginePerks.cpp" />
|
||||
<ClCompile Include="Modules\SubModules\WindowRender.cpp" />
|
||||
<ClCompile Include="Modules\Unarmed.cpp" />
|
||||
<ClCompile Include="Modules\Worldmap.cpp" />
|
||||
<ClCompile Include="Modules\ScriptExtender.cpp" />
|
||||
<ClCompile Include="Modules\BarBoxes.cpp" />
|
||||
|
||||
@@ -364,6 +364,9 @@
|
||||
<ClInclude Include="Game\AI\AIHelpers.h">
|
||||
<Filter>Game\AI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Modules\Unarmed.h">
|
||||
<Filter>Modules</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
@@ -669,6 +672,9 @@
|
||||
<ClCompile Include="Game\AI\AIHelpers.cpp">
|
||||
<Filter>Game\AI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Modules\Unarmed.cpp">
|
||||
<Filter>Modules</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="version.rc" />
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
#include "Modules\Stats.h"
|
||||
#include "Modules\TalkingHeads.h"
|
||||
#include "Modules\Tiles.h"
|
||||
#include "Modules\Unarmed.h"
|
||||
#include "Modules\Worldmap.h"
|
||||
|
||||
#include "CRC.h"
|
||||
@@ -121,6 +122,7 @@ static void InitModules() {
|
||||
manager.add<Books>();
|
||||
manager.add<Criticals>();
|
||||
manager.add<Elevators>();
|
||||
manager.add<Unarmed>();
|
||||
|
||||
manager.add<Animations>();
|
||||
manager.add<BarBoxes>();
|
||||
|
||||
Reference in New Issue
Block a user