diff --git a/addons/sourcemod/gamedata/l4d_pellet_spread_customizer.txt b/addons/sourcemod/gamedata/l4d_pellet_spread_customizer.txt new file mode 100644 index 0000000000..24bfa3d91f --- /dev/null +++ b/addons/sourcemod/gamedata/l4d_pellet_spread_customizer.txt @@ -0,0 +1,148 @@ +"Games" +{ + "#default" + { + "Functions" + { + "CTerrorGun::FireBullet" + { + "signature" "CTerrorGun::FireBullet" + "callconv" "thiscall" + "return" "void" + "this" "entity" + } + + "SharedRandomFloat" + { + "signature" "SharedRandomFloat" + "callconv" "cdecl" + "return" "float" + "arguments" + { + "name" + { + "type" "charptr" + } + "min" + { + "type" "float" + } + "max" + { + "type" "float" + } + "seed" + { + "type" "int" + } + } + } + } + } + + "left4dead" + { + "MemPatches" + { + "center_pellet" + { + "signature" "CTerrorPlayer::FireBullet" + "linux" + { + "offset" "44Ch" + "verify" "\xB8\x01" + "patch" "\xB8\x00" + } + "windows" + { + "offset" "262h" + "verify" "\xB1\x01" + "patch" "\xB1\x00" + } + } + } + + "Signatures" + { + "CTerrorGun::FireBullet" + { + "library" "server" + "linux" "@_ZN10CTerrorGun10FireBulletEv" + "windows" "\x83\xEC\x40\x53\x56\x8B\xF1\xE8" + /* 83 EC 40 53 56 8B F1 E8 */ + } + + /* @A1m`: + * How to find in windows: + * Can be found by string "CTerrorPlayer::FireBullet Spread" or "CTerrorPlayer::FireBullet SpreadDir" + */ + "CTerrorPlayer::FireBullet" + { + "library" "server" + "linux" "@_ZN13CTerrorPlayer10FireBulletE6VectorRK6QAngle10CSWeaponIDi" + "windows" "\x81\xEC\x90\x00\x00\x00\x8B\x84\x24\xA4\x00\x00\x00" + } + + "SharedRandomFloat" + { + "library" "server" + "linux" "@_Z17SharedRandomFloatPKcffi" + "windows" "\x83\xEC\x08\xA1\x2A\x2A\x2A\x2A\x53\x56\x57\x8B\x7C\x24\x24\x8D\x4C\x24\x24\x51\x89\x7C\x24\x14\x89\x44\x24\x10\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x54\x24\x14\x52\x8D\x44\x24\x30\x50\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x4C\x24\x24\x51\x8D\x54\x24\x3C\x52\xE8\x2A\x2A\x2A\x2A\x8B\x74\x24\x34\x56\xE8\x2A\x2A\x2A\x2A\x50\x8D\x44\x24\x48\x56\x50\xE8\x2A\x2A\x2A\x2A\x8D\x4C\x24\x50\x51\xE8\x2A\x2A\x2A\x2A\x8B\x15\x2A\x2A\x2A\x2A\x8B\x5C\x24\x54\x83\xC4\x30\x83\x7A\x30\x00\x74\x2A\x57\x53\x56\xB0\xFF\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x88\x44\x24\x38\x88\x44\x24\x39\x88\x44\x24\x3B\x8D\x44\x24\x38\x68\x2A\x2A\x2A\x2A\x50\xC6\x44\x24\x42\x00\xFF\x15\x2A\x2A\x2A\x2A\x83\xC4\x1C\x53\xB9\x2A\x2A\x2A\x2A\xFF\x15\x2A\x2A\x2A\x2A\xD9\x44\x24\x20" + // 83 EC 08 A1 ? ? ? ? 53 56 57 8B 7C 24 24 8D 4C 24 24 51 89 7C 24 14 89 44 24 10 E8 ? ? ? ? 6A 04 8D 54 24 14 52 8D 44 24 30 50 E8 ? ? ? ? 6A 04 8D 4C 24 24 51 8D 54 24 3C 52 E8 ? ? ? ? 8B 74 24 34 56 E8 ? ? ? ? 50 8D 44 24 48 56 50 E8 ? ? ? ? 8D 4C 24 50 51 E8 ? ? ? ? 8B 15 ? ? ? ? 8B 5C 24 54 83 C4 30 83 7A 30 00 74 ? 57 53 56 B0 FF 68 ? ? ? ? 68 ? ? ? ? 88 44 24 38 88 44 24 39 88 44 24 3B 8D 44 24 38 68 ? ? ? ? 50 C6 44 24 42 00 FF 15 ? ? ? ? 83 C4 1C 53 B9 ? ? ? ? FF 15 ? ? ? ? D9 44 24 20 + } + } + } + + "left4dead2" + { + "MemPatches" + { + "center_pellet" + { + "signature" "CTerrorPlayer::FireBullet" + "linux" + { + "offset" "3FAh" + "verify" "\xB8\x01" + "patch" "\xB8\x00" + } + "windows" + { + "offset" "2EBh" + "verify" "\xB1\x01" + "patch" "\xB1\x00" + } + } + } + + "Signatures" + { + "CTerrorGun::FireBullet" + { + "library" "server" + "linux" "@_ZN10CTerrorGun10FireBulletEv" + "windows" "\x55\x8B\xEC\x83\xEC\x34\x53\x56\x8B\xF1\xE8" + /* 55 8B EC 83 EC 34 53 56 8B F1 E8 */ + } + + /* @A1m`: + * How to find in windows: + * Can be found by string "CTerrorPlayer::FireBullet Spread" or "CTerrorPlayer::FireBullet SpreadDir" + */ + "CTerrorPlayer::FireBullet" + { + "library" "server" + "linux" "@_ZN13CTerrorPlayer10FireBulletE6VectorRK6QAngle10CSWeaponIDi" + "windows" "\x55\x8b\xec\x81\xec\x2a\x2a\x2a\x2a\x8b\x45\x18\x57\x8b\xf9" + } + + "SharedRandomFloat" + { + "library" "server" + "linux" "@_Z17SharedRandomFloatPKcffi" + "windows" "\x55\x8B\xEC\x83\xEC\x08\xA1\x2A\x2A\x2A\x2A\x53\x56\x57\x8B\x7D\x14\x8D\x4D\x14\x51\x89\x7D\xF8\x89\x45\xFC\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x55\xFC\x52\x8D\x45\x14\x50\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x4D\xF8\x51\x8D\x55\x14\x52\xE8\x2A\x2A\x2A\x2A\x8B\x75\x08\x56\xE8\x2A\x2A\x2A\x2A\x50\x8D\x45\x14\x56\x50\xE8\x2A\x2A\x2A\x2A\x8D\x4D\x14\x51\xE8\x2A\x2A\x2A\x2A\x8B\x15\x2A\x2A\x2A\x2A\x8B\x5D\x14\x83\xC4\x30\x83\x7A\x30\x00\x74\x2A\x57\x53\x56\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x8D\x45\x14\x68\x2A\x2A\x2A\x2A\x50\xC7\x45\x14\xFF\xFF\x00\xFF\xFF\x15\x2A\x2A\x2A\x2A\x83\xC4\x1C\x53\xB9\x2A\x2A\x2A\x2A\xFF\x15\x2A\x2A\x2A\x2A\xD9\x45\x10" + // 55 8B EC 83 EC 08 A1 ? ? ? ? 53 56 57 8B 7D 14 8D 4D 14 51 89 7D F8 89 45 FC E8 ? ? ? ? 6A 04 8D 55 FC 52 8D 45 14 50 E8 ? ? ? ? 6A 04 8D 4D F8 51 8D 55 14 52 E8 ? ? ? ? 8B 75 08 56 E8 ? ? ? ? 50 8D 45 14 56 50 E8 ? ? ? ? 8D 4D 14 51 E8 ? ? ? ? 8B 15 ? ? ? ? 8B 5D 14 83 C4 30 83 7A 30 00 74 ? 57 53 56 68 ? ? ? ? 68 ? ? ? ? 8D 45 14 68 ? ? ? ? 50 C7 45 14 FF FF 00 FF FF 15 ? ? ? ? 83 C4 1C 53 B9 ? ? ? ? FF 15 ? ? ? ? D9 45 10 + } + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/gamedata/code_patcher.txt b/addons/sourcemod/scripting/archive/code_patcher.txt similarity index 100% rename from addons/sourcemod/gamedata/code_patcher.txt rename to addons/sourcemod/scripting/archive/code_patcher.txt diff --git a/addons/sourcemod/scripting/archive/l4d2_static_shotgun_spread.sp b/addons/sourcemod/scripting/archive/l4d2_static_shotgun_spread.sp new file mode 100644 index 0000000000..6ef644f8a8 --- /dev/null +++ b/addons/sourcemod/scripting/archive/l4d2_static_shotgun_spread.sp @@ -0,0 +1,197 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include + +#define BULLET_MAX_SIZE 3 +#define GAMEDATA_FILE "code_patcher" +#define KEY_SGSPREAD "sgspread" + +// Original code & Notes: https://github.com/Jahze/l4d2_plugins/tree/master/spread_patch +// Static Shotgun Spread leverages code_patcher (code_patcher.txt gamedata) +// to replace RNG in pellet spread with static factors. +// This plugin allows you to adjust the spread characteristics, by live patching operands in the custom ASM. + +// You can use the visualise_impacts.smx plugin to test the resulting spread. +// It will render small purple boxes where the server-side pellets land. + +enum +{ + eWindows = 0, + eLinux, + /* eMac, joke:) */ + + ePlatform_Size +}; + +int + g_ePlatform = eLinux; + +static const int + g_iBulletOffsets[ePlatform_Size][BULLET_MAX_SIZE] = { + { 0xf, 0x21, 0x30 }, // Windows + { 0x11, 0x22, 0x2f } // Linux + }, + g_iDegreeOffsets[ePlatform_Size] = { + 0x3f, // Windows + 0x43 // Linux + }, + g_iFactorOffset[ePlatform_Size] = { + 0x36, // Windows + 0x34 // Linux + }, + g_iCenterPelletOffset[ePlatform_Size] = { + -0x36, // Windows + -0x1c // Linux + }; + +Address + g_pBullets[BULLET_MAX_SIZE] = {Address_Null, ...}, + g_pDegree = Address_Null, + g_pFactor = Address_Null, + g_pCenterPellet = Address_Null; + +MemoryPatch + g_hPatchSgSpread = null; + +ConVar + g_hCvarRing1Bullets = null, + g_hCvarRing1Factor = null, + g_hCvarCenterPellet = null; + +public Plugin myinfo = +{ + name = "L4D2 Static Shotgun Spread", + author = "Jahze, Visor, A1m`, Rena", + version = "1.6.5", + description = "Changes the values in the sgspread patch", + url = "https://github.com/SirPlease/L4D2-Competitive-Rework" +}; + +public void OnPluginStart() +{ + InitGameData(); + + g_hCvarRing1Bullets = CreateConVar("sgspread_ring1_bullets", "3", "Number of bullets for the first ring, the remaining bullets will be in the second ring."); + g_hCvarRing1Factor = CreateConVar("sgspread_ring1_factor", "2", "Determines how far or closer the bullets will be from the center for the first ring."); + g_hCvarCenterPellet = CreateConVar("sgspread_center_pellet", "1", "Center pellet: 0 - off, 1 - on.", _, true, 0.0, true, 1.0); + + InitPlugin(); +} + +void InitGameData() +{ + Handle hConf = LoadGameConfigFile(GAMEDATA_FILE); + if (hConf == null) { + SetFailState("Missing gamedata \"" ... GAMEDATA_FILE ... "\""); + } + + g_ePlatform = GameConfGetOffset(hConf, "OS"); + if (g_ePlatform == -1) { + SetFailState("Failed to retrieve offset \"OS\""); + } + + g_hPatchSgSpread = MemoryPatch.CreateFromConf(hConf, KEY_SGSPREAD); + if (g_hPatchSgSpread == null || !g_hPatchSgSpread.Validate()) { + SetFailState("Failed to validate MemoryPatch \"" ... KEY_SGSPREAD ... "\""); + } + + Address pFinalAddr = g_hPatchSgSpread.Address + view_as
(g_iCenterPelletOffset[g_ePlatform]); + int iCurrentValue = LoadFromAddress(pFinalAddr, NumberType_Int8); + if (iCurrentValue != 1) { + SetFailState("Center pellet offset is uncorrect! CheckByte: %x", iCurrentValue); + } + + if (!g_hPatchSgSpread.Enable()) { + SetFailState("Failed to enable MemoryPatch \"" ... KEY_SGSPREAD ... "\""); + } + + delete hConf; +} + +void InitPlugin() +{ + for (int i = 0; i < BULLET_MAX_SIZE; i++) { + g_pBullets[i] = g_hPatchSgSpread.Address + view_as
(g_iBulletOffsets[g_ePlatform][i]); + } + + g_pDegree = g_hPatchSgSpread.Address + view_as
(g_iDegreeOffsets[g_ePlatform]); + g_pFactor = g_hPatchSgSpread.Address + view_as
(g_iFactorOffset[g_ePlatform]); + g_pCenterPellet = g_hPatchSgSpread.Address + view_as
(g_iCenterPelletOffset[g_ePlatform]); + + HotPatchBullets(g_hCvarRing1Bullets.IntValue); + HotPatchFactor(g_hCvarRing1Factor.IntValue); + HotPatchCenterPellet(g_hCvarCenterPellet.BoolValue); + + g_hCvarRing1Bullets.AddChangeHook(OnRing1BulletsChange); + g_hCvarRing1Factor.AddChangeHook(OnRing1FactorChange); + g_hCvarCenterPellet.AddChangeHook(OnCenterPelletChange); +} + +public void OnPluginEnd() +{ + if (g_pCenterPellet != Address_Null) { + int iCurrentValue = LoadFromAddress(g_pCenterPellet, NumberType_Int8); + + if (iCurrentValue != 1) { + StoreToAddress(g_pCenterPellet, 1, NumberType_Int8); + } + } +} + +void OnRing1BulletsChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + HotPatchBullets(hConVar.IntValue); +} + +void OnRing1FactorChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + HotPatchFactor(hConVar.IntValue); +} + +void OnCenterPelletChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + HotPatchCenterPellet(hConVar.BoolValue); +} + +void HotPatchBullets(int iBullets) +{ + PatchBullets(iBullets, g_hCvarCenterPellet.BoolValue); + + float fBullets = float(iBullets); + if (g_ePlatform == eLinux) { + fBullets *= 2.0; + } + + StoreToAddress(g_pDegree, view_as(360.0 / fBullets), NumberType_Int32); +} + +void HotPatchFactor(int iFactor) +{ + // Asm patch on windows need the float type ! + int iSetFactor = (g_ePlatform == eWindows) ? view_as(float(iFactor)) : iFactor; + StoreToAddress(g_pFactor, iSetFactor, NumberType_Int32); +} + +void HotPatchCenterPellet(bool bNewValue) +{ + bool iCurrentValue = LoadFromAddress(g_pCenterPellet, NumberType_Int8); + if (iCurrentValue == bNewValue) { + return; + } + + StoreToAddress(g_pCenterPellet, view_as(bNewValue), NumberType_Int8); + + PatchBullets(g_hCvarRing1Bullets.IntValue, bNewValue); +} + +void PatchBullets(int iBullets, bool bCenterPellet) +{ + int iOffset1 = iBullets + (bCenterPellet ? 1 : 0); + int iOffset2 = iBullets + (bCenterPellet ? 2 : 1); + + StoreToAddress(g_pBullets[0], iOffset1, NumberType_Int8); + StoreToAddress(g_pBullets[1], iOffset1, NumberType_Int8); + StoreToAddress(g_pBullets[2], iOffset2, NumberType_Int8); +} diff --git a/addons/sourcemod/scripting/include/l4d_pellet_spread_customizer.inc b/addons/sourcemod/scripting/include/l4d_pellet_spread_customizer.inc new file mode 100644 index 0000000000..9db2ca7281 --- /dev/null +++ b/addons/sourcemod/scripting/include/l4d_pellet_spread_customizer.inc @@ -0,0 +1,50 @@ +#if defined _l4d_pellet_spread_customizer_included_ + #endinput +#endif +#define _l4d_pellet_spread_customizer_included_ + +/** + * @brief Called whenever a weapon is about to fire bullets. + * @remarks Determine whether the first bullet should be affected by ScatterYaw/Pitch. + * @remarks Bullet that's affected by ScatterYaw/Pitch is called pellet hereinafter. + * @note Called in pre-detour of function "CTerrorGun::FireBullet". + * @note Has no effect if either ScatterYaw/Pitch is not configured for this weapon. + * + * @param weapon Weapon entity index + * + * @return Plugin_Handled to force the first bullet acting as a pellet, + * Plugin_Continue otherwise. + */ +forward Action L4D_OnPelletFirstBullet(int weapon); + +/** + * @brief Called whenever generating spread values for a pellet. + * @remarks Spread -> how far from the center, SpreadDir -> circular angle. + * @remarks By default the actual output for spread is ranged in [-x, x], for angle it is [0, 180]. + * @note Negative values are accepted, for the opposite direction. + * @note "nMaxPellets" adds 1 if "L4D_OnPelletFirstBullet" returns Plugin_Handled. + * @note Called in post-detour of function "SharedRandomFloat", the same frame later "L4D_OnPelletFirstBullet". + * + * @param weapon Weapon entity index + * @param spread/angle Spread value + * @param nPellet Current pellet number (starting from 1) + * @param nMaxPellets Max pellet number + * + * @return Plugin_Changed to change to the exact value, + * Plugin_Continue to leave it randomly generated. + */ +forward Action L4D_OnPelletSpread(int weapon, float &spread, int nPellet, int nMaxPellets); +forward Action L4D_OnPelletSpreadDir(int weapon, float &angle, int nPellet, int nMaxPellets); + + + +public SharedPlugin __pl_l4d_pellet_spread_customizer = +{ + name = "l4d_pellet_spread_customizer", + file = "l4d_pellet_spread_customizer.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; diff --git a/addons/sourcemod/scripting/l4d2_static_shotgun_spread.sp b/addons/sourcemod/scripting/l4d2_static_shotgun_spread.sp index 6ef644f8a8..10f813c101 100644 --- a/addons/sourcemod/scripting/l4d2_static_shotgun_spread.sp +++ b/addons/sourcemod/scripting/l4d2_static_shotgun_spread.sp @@ -2,196 +2,64 @@ #pragma newdecls required #include -#include - -#define BULLET_MAX_SIZE 3 -#define GAMEDATA_FILE "code_patcher" -#define KEY_SGSPREAD "sgspread" - -// Original code & Notes: https://github.com/Jahze/l4d2_plugins/tree/master/spread_patch -// Static Shotgun Spread leverages code_patcher (code_patcher.txt gamedata) -// to replace RNG in pellet spread with static factors. -// This plugin allows you to adjust the spread characteristics, by live patching operands in the custom ASM. +#include "include/l4d_pellet_spread_customizer" // You can use the visualise_impacts.smx plugin to test the resulting spread. // It will render small purple boxes where the server-side pellets land. -enum -{ - eWindows = 0, - eLinux, - /* eMac, joke:) */ - - ePlatform_Size -}; - -int - g_ePlatform = eLinux; - -static const int - g_iBulletOffsets[ePlatform_Size][BULLET_MAX_SIZE] = { - { 0xf, 0x21, 0x30 }, // Windows - { 0x11, 0x22, 0x2f } // Linux - }, - g_iDegreeOffsets[ePlatform_Size] = { - 0x3f, // Windows - 0x43 // Linux - }, - g_iFactorOffset[ePlatform_Size] = { - 0x36, // Windows - 0x34 // Linux - }, - g_iCenterPelletOffset[ePlatform_Size] = { - -0x36, // Windows - -0x1c // Linux - }; - -Address - g_pBullets[BULLET_MAX_SIZE] = {Address_Null, ...}, - g_pDegree = Address_Null, - g_pFactor = Address_Null, - g_pCenterPellet = Address_Null; - -MemoryPatch - g_hPatchSgSpread = null; - -ConVar - g_hCvarRing1Bullets = null, - g_hCvarRing1Factor = null, - g_hCvarCenterPellet = null; +ConVar g_hCvarRing1Bullets = null; +ConVar g_hCvarRing1Factor = null; +ConVar g_hCvarCenterPellet = null; public Plugin myinfo = { name = "L4D2 Static Shotgun Spread", - author = "Jahze, Visor, A1m`, Rena", - version = "1.6.5", - description = "Changes the values in the sgspread patch", - url = "https://github.com/SirPlease/L4D2-Competitive-Rework" + author = "Jahze, Visor, A1m`, Rena, Forgetest", + version = "2.0", + description = "Apply circular pattern to shotgun spreads", + url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins" }; public void OnPluginStart() { - InitGameData(); - g_hCvarRing1Bullets = CreateConVar("sgspread_ring1_bullets", "3", "Number of bullets for the first ring, the remaining bullets will be in the second ring."); g_hCvarRing1Factor = CreateConVar("sgspread_ring1_factor", "2", "Determines how far or closer the bullets will be from the center for the first ring."); g_hCvarCenterPellet = CreateConVar("sgspread_center_pellet", "1", "Center pellet: 0 - off, 1 - on.", _, true, 0.0, true, 1.0); - - InitPlugin(); } -void InitGameData() +public Action L4D_OnPelletFirstBullet(int weapon) { - Handle hConf = LoadGameConfigFile(GAMEDATA_FILE); - if (hConf == null) { - SetFailState("Missing gamedata \"" ... GAMEDATA_FILE ... "\""); - } - - g_ePlatform = GameConfGetOffset(hConf, "OS"); - if (g_ePlatform == -1) { - SetFailState("Failed to retrieve offset \"OS\""); - } - - g_hPatchSgSpread = MemoryPatch.CreateFromConf(hConf, KEY_SGSPREAD); - if (g_hPatchSgSpread == null || !g_hPatchSgSpread.Validate()) { - SetFailState("Failed to validate MemoryPatch \"" ... KEY_SGSPREAD ... "\""); - } - - Address pFinalAddr = g_hPatchSgSpread.Address + view_as
(g_iCenterPelletOffset[g_ePlatform]); - int iCurrentValue = LoadFromAddress(pFinalAddr, NumberType_Int8); - if (iCurrentValue != 1) { - SetFailState("Center pellet offset is uncorrect! CheckByte: %x", iCurrentValue); - } - - if (!g_hPatchSgSpread.Enable()) { - SetFailState("Failed to enable MemoryPatch \"" ... KEY_SGSPREAD ... "\""); - } - - delete hConf; + return g_hCvarCenterPellet.BoolValue ? Plugin_Continue : Plugin_Handled; } -void InitPlugin() +public Action L4D_OnPelletSpread(int weapon, float &spread, int nPellet, int nMaxPellets) { - for (int i = 0; i < BULLET_MAX_SIZE; i++) { - g_pBullets[i] = g_hPatchSgSpread.Address + view_as
(g_iBulletOffsets[g_ePlatform][i]); + // For bullets on the first ring, divides the spread value by the factor. + if (nPellet <= g_hCvarRing1Bullets.IntValue) + { + spread /= g_hCvarRing1Factor.FloatValue; } - g_pDegree = g_hPatchSgSpread.Address + view_as
(g_iDegreeOffsets[g_ePlatform]); - g_pFactor = g_hPatchSgSpread.Address + view_as
(g_iFactorOffset[g_ePlatform]); - g_pCenterPellet = g_hPatchSgSpread.Address + view_as
(g_iCenterPelletOffset[g_ePlatform]); - - HotPatchBullets(g_hCvarRing1Bullets.IntValue); - HotPatchFactor(g_hCvarRing1Factor.IntValue); - HotPatchCenterPellet(g_hCvarCenterPellet.BoolValue); + // For bullets on the second ring, sticks to the max spread value. - g_hCvarRing1Bullets.AddChangeHook(OnRing1BulletsChange); - g_hCvarRing1Factor.AddChangeHook(OnRing1FactorChange); - g_hCvarCenterPellet.AddChangeHook(OnCenterPelletChange); + return Plugin_Changed; } -public void OnPluginEnd() +public Action L4D_OnPelletSpreadDir(int weapon, float &angle, int nPellet, int nMaxPellets) { - if (g_pCenterPellet != Address_Null) { - int iCurrentValue = LoadFromAddress(g_pCenterPellet, NumberType_Int8); - - if (iCurrentValue != 1) { - StoreToAddress(g_pCenterPellet, 1, NumberType_Int8); - } + int numSlices = 0; + if (nPellet <= g_hCvarRing1Bullets.IntValue) + { + numSlices = g_hCvarRing1Bullets.IntValue; } -} - -void OnRing1BulletsChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) -{ - HotPatchBullets(hConVar.IntValue); -} - -void OnRing1FactorChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) -{ - HotPatchFactor(hConVar.IntValue); -} - -void OnCenterPelletChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) -{ - HotPatchCenterPellet(hConVar.BoolValue); -} - -void HotPatchBullets(int iBullets) -{ - PatchBullets(iBullets, g_hCvarCenterPellet.BoolValue); - - float fBullets = float(iBullets); - if (g_ePlatform == eLinux) { - fBullets *= 2.0; + else + { + numSlices = nMaxPellets - g_hCvarRing1Bullets.IntValue; } - - StoreToAddress(g_pDegree, view_as(360.0 / fBullets), NumberType_Int32); -} - -void HotPatchFactor(int iFactor) -{ - // Asm patch on windows need the float type ! - int iSetFactor = (g_ePlatform == eWindows) ? view_as(float(iFactor)) : iFactor; - StoreToAddress(g_pFactor, iSetFactor, NumberType_Int32); -} - -void HotPatchCenterPellet(bool bNewValue) -{ - bool iCurrentValue = LoadFromAddress(g_pCenterPellet, NumberType_Int8); - if (iCurrentValue == bNewValue) { - return; - } - - StoreToAddress(g_pCenterPellet, view_as(bNewValue), NumberType_Int8); - - PatchBullets(g_hCvarRing1Bullets.IntValue, bNewValue); -} - -void PatchBullets(int iBullets, bool bCenterPellet) -{ - int iOffset1 = iBullets + (bCenterPellet ? 1 : 0); - int iOffset2 = iBullets + (bCenterPellet ? 2 : 1); - - StoreToAddress(g_pBullets[0], iOffset1, NumberType_Int8); - StoreToAddress(g_pBullets[1], iOffset1, NumberType_Int8); - StoreToAddress(g_pBullets[2], iOffset2, NumberType_Int8); + + // @Forgetest: + // Not sure where the first pellet of ring should be. Actually no full understanding of the original ASM codes. + // Keep it simple here, starting at 0. + angle = 180.0 / numSlices * ((nPellet - 1) % numSlices); // "% numSlices" should be unnecessary, whatever. + return Plugin_Changed; } diff --git a/addons/sourcemod/scripting/l4d_pellet_spread_customizer.sp b/addons/sourcemod/scripting/l4d_pellet_spread_customizer.sp new file mode 100644 index 0000000000..646da55461 --- /dev/null +++ b/addons/sourcemod/scripting/l4d_pellet_spread_customizer.sp @@ -0,0 +1,265 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include +#include + +#define DEBUG 0 +#define PLUGIN_VERSION "1.0" + +public Plugin myinfo = +{ + name = "[L4D & 2] Pellet Spread Customizer", + author = "Forgetest", + description = "Customize spread pattern for pellet-shooting guns.", + version = PLUGIN_VERSION, + url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins", +} + +methodmap GameDataWrapper < GameData { + public GameDataWrapper(const char[] file) { + GameData gd = new GameData(file); + if (!gd) SetFailState("Missing gamedata \"%s\"", file); + return view_as(gd); + } + public MemoryPatch CreatePatchOrFail(const char[] name, bool enable = false) { + MemoryPatch hPatch = MemoryPatch.CreateFromConf(this, name); + if (!(enable ? hPatch.Enable() : hPatch.Validate())) + SetFailState("Failed to patch \"%s\"", name); + return hPatch; + } + public DynamicDetour CreateDetourOrFail( + const char[] name, + DHookCallback preHook = INVALID_FUNCTION, + DHookCallback postHook = INVALID_FUNCTION) { + DynamicDetour hSetup = DynamicDetour.FromConf(this, name); + if (!hSetup) + SetFailState("Missing detour setup \"%s\"", name); + if (preHook != INVALID_FUNCTION && !hSetup.Enable(Hook_Pre, preHook)) + SetFailState("Failed to pre-detour \"%s\"", name); + if (postHook != INVALID_FUNCTION && !hSetup.Enable(Hook_Post, postHook)) + SetFailState("Failed to post-detour \"%s\"", name); + return hSetup; + } +} + +MemoryPatch g_patch_CenterPellet; + +GlobalForward g_fwd_CenterPellet; +GlobalForward g_fwd_SpreadMod; +GlobalForward g_fwd_SpreadDIrMod; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + g_fwd_CenterPellet = new GlobalForward("L4D_OnPelletFirstBullet", ET_Event, Param_Cell); + g_fwd_SpreadMod = new GlobalForward("L4D_OnPelletSpread", ET_Event, Param_Cell, Param_FloatByRef, Param_Cell, Param_Cell); + g_fwd_SpreadDIrMod = new GlobalForward("L4D_OnPelletSpreadDir", ET_Event, Param_Cell, Param_FloatByRef, Param_Cell, Param_Cell); + + RegPluginLibrary("l4d_pellet_spread_customizer"); + return APLRes_Success; +} + +public void OnPluginStart() +{ + GameDataWrapper gd = new GameDataWrapper("l4d_pellet_spread_customizer"); + g_patch_CenterPellet = gd.CreatePatchOrFail("center_pellet", false); + delete gd.CreateDetourOrFail("CTerrorGun::FireBullet", DTR_CTerrorGun_FireBullet, DTR_CTerrorGun_FireBullet_Post); + delete gd.CreateDetourOrFail("SharedRandomFloat", _, DTR_SharedRandomFloat_Post); + delete gd; +} + +void ToggleCenterPelletPatch(bool enable) +{ + static bool status = false; + + if (status == enable) + return; + + status = enable; + if (status) + g_patch_CenterPellet.Enable(); + else + g_patch_CenterPellet.Disable(); +} + +bool ShouldUseCenterPellet(int weapon) +{ + if (g_fwd_CenterPellet.FunctionCount == 0) + return true; + + Action result = Plugin_Continue; + Call_StartForward(g_fwd_CenterPellet); + Call_PushCell(weapon); + Call_Finish(result); + + return result == Plugin_Continue; +} + +static int g_nMaxBullets; +static bool g_bCenterPellet; +static int g_iFireBulletWeapon = -1; +MRESReturn DTR_CTerrorGun_FireBullet(int weapon) +{ + g_nMaxBullets = GetWeaponMaxBullets(weapon); + g_iFireBulletWeapon = weapon; + + if (ShouldUseCenterPellet(weapon)) + { + g_bCenterPellet = true; + ToggleCenterPelletPatch(false); + } + else + { + g_bCenterPellet = false; + ToggleCenterPelletPatch(true); + } + + return MRES_Ignored; +} + +MRESReturn DTR_CTerrorGun_FireBullet_Post(int weapon) +{ + g_iFireBulletWeapon = -1; + ToggleCenterPelletPatch(false); + return MRES_Ignored; +} + +bool ApplyPelletSpreadMod(int weapon, float &spread, int nPellet, int nMaxPellets) +{ + if (g_fwd_SpreadMod.FunctionCount == 0) + return false; + + Action result = Plugin_Continue; + Call_StartForward(g_fwd_SpreadMod); + Call_PushCell(weapon); + Call_PushFloatRef(spread); + Call_PushCell(nPellet); + Call_PushCell(nMaxPellets); + Call_Finish(result); + + return result == Plugin_Changed; +} + +bool ApplyPelletSpreadDirMod(int weapon, float &angle, int nPellet, int nMaxPellets) +{ + if (g_fwd_SpreadDIrMod.FunctionCount == 0) + return false; + + Action result = Plugin_Continue; + Call_StartForward(g_fwd_SpreadDIrMod); + Call_PushCell(weapon); + Call_PushFloatRef(angle); + Call_PushCell(nPellet); + Call_PushCell(nMaxPellets); + Call_Finish(result); + + return result == Plugin_Changed; +} + +MRESReturn DTR_SharedRandomFloat_Post(DHookReturn hReturn, DHookParam hParams) +{ + if (g_iFireBulletWeapon == -1) + return MRES_Ignored; + + Assert(g_nMaxBullets > 0); + + char name[64]; + hParams.GetString(1, name, sizeof(name)); + + if (strncmp(name, "CTerrorPlayer::FireBullet Spread", 32)) + return MRES_Ignored; + + int nPellet = hParams.Get(4); + int nMaxPellets = g_nMaxBullets; + + if (g_bCenterPellet) + { + nPellet -= 1; + nMaxPellets -= 1; + } + + if (!strcmp(name[32], "Dir")) + { + // SpreadDir + float angle = hReturn.Value; + if (ApplyPelletSpreadDirMod(g_iFireBulletWeapon, angle, nPellet, nMaxPellets)) + { + DebugChatMsg("#%d angle %.1f", nPellet, angle); + hReturn.Value = angle; + return MRES_Override; + } + } + else + { + // Spread + float flSpread = hParams.Get(3); + if (ApplyPelletSpreadMod(g_iFireBulletWeapon, flSpread, nPellet, nMaxPellets)) + { + DebugChatMsg("#%d spread %.1f", nPellet, flSpread); + hReturn.Value = flSpread; + return MRES_Override; + } + } + + return MRES_Ignored; +} + +int GetWeaponMaxBullets(int weapon) +{ + char weaponname[64]; + GetEdictClassname(weapon, weaponname, sizeof(weaponname)); + return L4D2_GetIntWeaponAttribute(weaponname, L4D2IWA_Bullets); +} + +void DebugChatMsg(const char[] format, any ...) +{ +#if DEBUG + char buffer[512]; + VFormat(buffer, sizeof(buffer), format, 2); + PrintToChatAll("[DEBUG] %s", buffer); +#else + #pragma unused format +#endif +} + +void Assert(bool expr, const char[] msg = "") +{ +#if DEBUG + if (!expr) + { + if (msg[0]) + ThrowError("Assertion failed! (%s)", msg); + else + ThrowError("Assertion failed!"); + } +#else + #pragma unused expr + #pragma unused msg +#endif +} + +stock ConVar CreateConVarHook(const char[] name, + const char[] defaultValue, + const char[] description="", + int flags=0, + bool hasMin=false, float min=0.0, + bool hasMax=false, float max=0.0, + ConVarChanged callback) +{ + ConVar cv = CreateConVar(name, defaultValue, description, flags, hasMin, min, hasMax, max); + + char value[512]; + cv.GetString(value, sizeof(value)); + + Call_StartFunction(INVALID_HANDLE, callback); + Call_PushCell(cv); + Call_PushString(value); + Call_PushString(value); + Call_Finish(); + + cv.AddChangeHook(callback); + + return cv; +}