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:
NovaRain
2021-09-19 23:14:08 +08:00
parent c219ce183f
commit 858a6c5343
12 changed files with 604 additions and 69 deletions
+90 -32
View File
@@ -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--;
}
}
}
if (hitMode < fo::AttackType::ATKTYPE_LWEAPON_RELOAD) {
if (isCalled) cost++;
if (cost < 0) cost = 0;
long type = fo::func::item_w_subtype(weapon, hitMode);
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);
goto endReload;
}
// 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);
cost = item_w_mp_cost_sub(source, weapon, hitMode, isCalled, cost);
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
View File
@@ -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);
};
}
+3 -3
View File
@@ -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);
//}
+15 -3
View File
@@ -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);
}
+2
View File
@@ -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);
};
}
+18 -21
View File
@@ -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
View File
@@ -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);
}
+419
View File
@@ -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() {
//}
}
+35
View File
@@ -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);
};
}
+2
View File
@@ -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" />
+6
View File
@@ -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" />
+2
View File
@@ -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>();