From 10164e49a22f944eefdbcdabd489bc9bec6e5cd4 Mon Sep 17 00:00:00 2001 From: A1mDev <33463136+A1mDev@users.noreply.github.com> Date: Mon, 24 Nov 2025 04:59:54 +0700 Subject: [PATCH 1/5] [l4d2_sharedrandom_overrides] Upload --- .../gamedata/l4d2_sharedrandom_overrides.txt | 184 +++ .../scripting/l4d2_sharedrandom_overrides.sp | 1090 +++++++++++++++++ 2 files changed, 1274 insertions(+) create mode 100644 addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt create mode 100644 addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp diff --git a/addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt b/addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt new file mode 100644 index 0000000000..bc3d756f68 --- /dev/null +++ b/addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt @@ -0,0 +1,184 @@ +/** + * Author: A1m` + * Work summary: + * - Documented parameters, return values, and calling conventions for use with DHooks + * - Described how to locate these functions on Windows + * - Found function signatures on both Linux and Windows + * - Created a memory patch for the central pellet +**/ +"Games" +{ + "#default" + { + "Functions" + { + /** + * https://github.com/ValveSoftware/source-sdk-2013/blob/master/src/game/shared/util_shared.cpp#L157-L164 + * float SharedRandomFloat( const char *sharedname, float flMinVal, float flMaxVal, int additionalSeed = 0 ); + **/ + "DH::SharedRandomFloat" + { + "signature" "SharedRandomFloat" + "callconv" "cdecl" + "return" "float" + + "arguments" + { + "sharedname" + { + "type" "charptr" + } + "flMinVal" + { + "type" "float" + } + "flMaxVal" + { + "type" "float" + } + "additionalSeed" + { + "type" "int" + } + } + } + + /** + * Not used + * void CTerrorPlayer::FireBullet( Vector vecSrc, const QAngle &angShoot, CSWeaponID weaponID, int additionalSeed = 0 ); + **/ + "DH::CTerrorPlayer::FireBullets" + { + "signature" "CTerrorPlayer::FireBullets" + "callconv" "thiscall" + "return" "void" + "this" "entity" + + "arguments" + { + // why vectorptr??? + "vecSrc" + { + "type" "vectorptr" + } + "angShoot" + { + "type" "vectorptr" + } + "weaponId" + { + "type" "int" + } + "additionalSeed" + { + "type" "int" + } + } + } + + /** + * void CTerrorGun::FireBullet( void ); + **/ + "DH::CTerrorGun::FireBullet" + { + "signature" "CTerrorGun::FireBullet" + "callconv" "thiscall" + "return" "void" + "this" "entity" + } + } + } + + "left4dead2" + { + "MemPatches" + { + /** + * We write 0 or 1 to the i variable when initializing the loop, + * on Windows it's a little different, although I think it's also a loop variable. + **/ + "spread_center_pellet_patch" + { + "signature" "CTerrorPlayer::FireBullet" + + "linux" + { + "offset" "1018" + + "patch" "\xB8\x00\x00\x00\x00" /* B8 00 00 00 00 - mov eax, 0 */ + "verify" "\xB8\x01\x00\x00\x00" /* B8 01 00 00 00 - mov eax, 1 */ + } + + "windows" + { + "offset" "747" + + "patch" "\xB1\x00" /* B1 00 - mov cl, 0 */ + "verify" "\xB1\x01" /* B1 01 - mov cl, 1 */ + } + } + } + + "Addresses" + { + "CBaseEntity::m_nPredictionRandomSeed" + { + "windows" + { + "signature" "SharedRandomFloat" + "read" "7" // 55 8B EC 83 EC 08 A1 (? ? ? ?), we read addr (? ? ? ?) + } + "linux" + { + "signature" "CBaseEntity::m_nPredictionRandomSeed" + } + } + } + + "Signatures" + { + /** + * [Windows] Can be found by string "SharedRandomFloat" + **/ + "SharedRandomFloat" + { + "library" "server" + + "linux" "@_Z17SharedRandomFloatPKcffi" + /* 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 */ + "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" + } + + /** + * [Windows] Can be found by string "CTerrorPlayer::FireBullet Spread" or "CTerrorPlayer::FireBullet SpreadDir" + **/ + "CTerrorPlayer::FireBullet" + { + "library" "server" + + "linux" "@_ZN13CTerrorPlayer10FireBulletE6VectorRK6QAngle10CSWeaponIDi" + /* 55 8B EC 81 EC A0 00 00 00 8B 45 18 */ + "windows" "\x55\x8B\xEC\x81\xEC\xA0\x00\x00\x00\x8B\x45\x18" + } + + /** + * [Windows] Can be found by string "CTerrorGun::FireBullet HorizSpread" or "CTerrorGun::FireBullet VertSpread" + **/ + "CTerrorGun::FireBullet" + { + "library" "server" + + "linux" "@_ZN10CTerrorGun10FireBulletEv" + /* 55 8B EC 83 EC 34 53 56 8B F1 E8 ? ? ? ? */ + "windows" "\x55\x8B\xEC\x83\xEC\x34\x53\x56\x8B\xF1\xE8\x2A\x2A\x2A\x2A" + } + + "CBaseEntity::m_nPredictionRandomSeed" + { + "library" "server" + + "linux" "@_ZN11CBaseEntity23m_nPredictionRandomSeedE" + } + } + } +} diff --git a/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp b/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp new file mode 100644 index 0000000000..2668fff6b8 --- /dev/null +++ b/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp @@ -0,0 +1,1090 @@ +/** + * Plugin: Shared Random Pattern + * Original author: A1m` + * + * Description: + * Implements a configurable, non-random shotgun pellet pattern: + * - Custom rings + * - Radius factors + * - Hybrid spread (Maybe in the future. :D) + * Replaces the old ASM patch method with a detour hook. + * Fixed the spread direction calculation: it now uses the full 0 – < 180 degree range, unlike the old ASM patch. + * The old ASM patch used a spread direction from -22° to ~154°, if the center pellet was activated, or from 0° to ~134° if not. + * Disable spread offset, up-down or left-right relative to the shot. This is not needed in static spread. The old assembler patch did not fix this. + * Otherwise the shot will not be accurate. (Thanks Alan for the report). + * + * Credits: + * Jahze - Wrote the first ASM patch for static spread and wrote a plugin to customize this pattern `l4d2_static_shotgun_spread`, + * along with several related helper plugins for spread manipulation and testing. + * ProdigySim — Proposed replacing ASM patch with function hook `SharedRandomFloat` and offered the initial version of the plugin. + * ProdigySim, Visor, A1m`, Rena - Old plugin maintainers — maintained l4d2_static_shotgun_spread and old ASM patch. + * Alan - Provides feedback, tests the initial version of the plugin proposed by ProdigySim, and checks its functionality. + * + * Notes: + * This plugin supports the old logic entirely and is aimed at further changing the spread pattern. + * The old method with ASM patch was difficult to understand, support, and was limited. + * + * Research & Implementation: + * Test code has been written in C++; see the plugin below. + * A detailed analysis of the ASM patch from the file `code_patcher.txt` (gamedata file) was performed. + * Reverse engineering and analysis of the game code, function `CTerrorPlayer::FireBullets`and accompanying code + * Investigating the behavior of the `l4d2_static_shotgun_spread` plugin. + * All research and implementation were performed by A1m`. + * + * Bugs & Todo: + * Fix: Large values of the 'sgspread_prediction_seed' cvar do not match the specified ones because the cvar value is stored as a float and breaks when retrieved due to rounding. + * Add: Gaussian distribution of the pattern. + * Improve: Remove the dynamic patch of the central pellet for an individual pattern for each shotgun. + * Fix: The saved seed is sometimes equal to INT_MAX (overflow?). + * Add: l4d1 support? + * + * The game stack trace look like this: + * + * CTerrorGun::FireBullet + * │ + * ▼ + * SharedRandomFloat + * │ + * ▼ + * FireTerrorBullets + * │ + * ▼ + * CTerrorPlayer::FireBullet + * │ + * ▼ + * SharedRandomFloat +**/ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include // #include +#include + +#define DEBUG 0 + +#define GAMEDATA_FILE "l4d2_sharedrandom_overrides" +#define DHOOK_SHAREDRANDFLOAT_NAME "DH::SharedRandomFloat" +#define CTERRORGUN_FIREBULLET "DH::CTerrorGun::FireBullet" +#define CENTER_PELLET_PATCH "spread_center_pellet_patch" +#define SHAREDRANDFLOAT_NAME "SharedRandomFloat" +#define CBASEENTITY_PREDICTIONSEED "CBaseEntity::m_nPredictionRandomSeed" + +// How many bits to use to encode an edict. +#define MAX_EDICT_BITS 11 // # of bits needed to represent max edicts +// Max # of edicts in a level +#define MAX_EDICTS (1 << MAX_EDICT_BITS) + +#define INT_MAX 0x7fffffff + +enum /*eSpreadTypes*/ +{ + eSGSDisable = 0, + eSGSStaticRings, + eSGSDynamicRings, + eSGSVanillaConsistent, + + eSGSpread_Size +}; + +enum /*eSurvivorUpgradeType*/ +{ + UPGRADE_INCENDIARY_AMMO = 0, + UPGRADE_EXPLOSIVE_AMMO, + UPGRADE_LASER_SIGHT +}; + +enum /*eFireBulletsInfo*/ +{ + eiCurrentFireId = 0, + eiTotalPellets, + eiWeaponRef, + eiWeaponOwnerRef, + eiFrameCallNum, + eiPredictionRandomSeedOld, + + eTotalSize +}; + +#if DEBUG +enum /*eDebugType*/ +{ + eDataTypeNone = -1, + efScatterOffset = 0, + efAngleDeg, + efHorizontalSpread, + efVerticalSpread, + + eDebugDataSize +}; + +float + g_fDebugDataStorage[eDebugDataSize] = {0.0, ...}; + +bool + g_bDebugSetData[eDebugDataSize] = {false, ...}; +#endif + +int + g_iCurrentFireID = 0, + g_iSavedPredictionRandomSeed = -1, + g_iFireBulletInfo[eTotalSize] = {0, ...}; + +Address + g_iPredictionRandomSeedAddr = Address_Null; + +MemoryPatch + g_hCenterPelletPatch = null; + +DynamicDetour + g_hDetourTerrorGunFireBullet = null, + g_hDetourSharedRandomFloat = null; + +bool + g_bCenterPelletPatchEnabled = false, + g_bDetourHooksEnabled = false; + +ConVar + g_hCvarPatternType = null, + g_hCvarRing1Pellets = null, + g_hCvarRing1Factor = null, + g_hCvarCenterPellet = null, + g_hCvarRing1MaxOffsetPercent = null, + g_hCvarRing2MaxOffsetPercent = null, + g_hCvarAllPelletsSpreadOffset = null, + g_hCvarPredictionSeed = null, + g_hCvarPerfFireBulletSingle = null; + +public Plugin myinfo = +{ + name = "[L4D] Shared Random Pattern", + author = "A1m`", + description = "Replaces random spread for shotgun pellets with configurable pattern (no ASM patch)", + version = "0.2.8", + url = "https://github.com/SirPlease/L4D2-Competitive-Rework" +}; + +public void OnPluginStart() +{ + InitGameData(); + + g_hCvarPatternType = CreateConVar( + "sgspread_pattern_type", + "1", + "Specifies the pattern type: \ + 0 - disable(vanilla), \ + 1 - static shotgun spread pattern, \ + 2 - two-circle random deviation pattern (first circle: (Max spread / sgspread_ring1_factor) minus 'sgspread_ring1_offset', second circle: Max spread minus 'sgspread_ring2_offset'), \ + 3 - repeatable vanilla spread.", + _, true, float(eSGSDisable), true, float(eSGSpread_Size) + ); + + // sgspread_ring1_bullets old cvar name + g_hCvarRing1Pellets = CreateConVar( + "sgspread_ring1_pellets", + "3", + "Number of pellets in the first ring, the remaining pellets will go to the second ring.", + _, true, 0.0 + ); + + g_hCvarRing1Factor = CreateConVar( + "sgspread_ring1_factor", + "2", + "Determines how far pellets in the first ring are from the center (Max spread / factor).", + _, true, 1.0 + ); + + g_hCvarCenterPellet = CreateConVar( + "sgspread_center_pellet", + "1", + "Center pellet: 0 - off, 1 - on (default game behavior).", + _, true, 0.0, true, 1.0 + ); + + g_hCvarRing1MaxOffsetPercent = CreateConVar( + "sgspread_ring1_offset", + "60.0", + "Maximum random deviation for the first circle (in percent of its radius). Used in pattern type 2 ('eSGSDynamicRings').", + _, true, 1.0, true, 100.0 + ); + + g_hCvarRing2MaxOffsetPercent = CreateConVar( + "sgspread_ring2_offset", + "30.0", + "Maximum random deviation for the second circle (in percent of its radius). Used in pattern type 2 ('eSGSDynamicRings').", + _, true, 1.0, true, 100.0 + ); + + g_hCvarAllPelletsSpreadOffset = CreateConVar( + "sgspread_all_pellets_offset", + "0", + "Enable or disable spread offset (vertical/horizontal) relative to the shot. \ + If enabled, the entire pellet spread shifts slightly, reducing accuracy. \ + Typically not required for static spread.", + _, true, 0.0, true, 1.0 + ); + + // ConVar is stored as a float internally? So when setting a large number (1234567890), + // it's rounded to the nearest representable float. + g_hCvarPredictionSeed = CreateConVar( + "sgspread_prediction_seed", + "-1", + "Set a fixed seed for predictable pellet spread. If set to -1, the plugin will remember any random value for shots. Used in pattern type 4 ('eSGSVanillaConsistent').", + _, false, 0.0, true, float(INT_MAX) + ); + +#if DEBUG + RegConsoleCmd("sm_center_pellet_val", Cmd_CheckCenterPelletVal); + RegConsoleCmd("sm_get_random_seed", Cmd_GetPredictionRandomSeed); +#endif + + RegAdminCmd("sm_reset_seed", Cmd_ResetSavedPredictionSeed, ADMFLAG_RCON); + + InitPlugin(); +} + +void InitPlugin() +{ + g_hCvarPerfFireBulletSingle = FindConVar("perf_fire_bullet_single"); + + TogglePatch(!g_hCvarCenterPellet.BoolValue); + ToggleDetourHooks(g_hCvarPatternType.IntValue > eSGSDisable); + + g_hCvarPatternType.AddChangeHook(Hook_OnPatternChanged); + g_hCvarCenterPellet.AddChangeHook(Hook_OnCenterPelletChanged); +} + +void InitGameData() +{ + GameData hGamedata = new GameData(GAMEDATA_FILE); + if (hGamedata == null) { + SetFailState("Missing gamedata '%s'", GAMEDATA_FILE); + } + + g_hDetourSharedRandomFloat = DynamicDetour.FromConf(hGamedata, DHOOK_SHAREDRANDFLOAT_NAME); + if (g_hDetourSharedRandomFloat == null) { + SetFailState("Missing detour setup '%s'", DHOOK_SHAREDRANDFLOAT_NAME); + } + + g_hDetourTerrorGunFireBullet = DynamicDetour.FromConf(hGamedata, CTERRORGUN_FIREBULLET); + if (g_hDetourTerrorGunFireBullet == null) { + SetFailState("Missing detour setup '%s'", CTERRORGUN_FIREBULLET); + } + + g_hCenterPelletPatch = MemoryPatch.CreateFromConf(hGamedata, CENTER_PELLET_PATCH); + if (g_hCenterPelletPatch == null || !g_hCenterPelletPatch.Validate()) { + SetFailState("Failed to validate MemoryPatch '%s'", CENTER_PELLET_PATCH); + } + + g_iPredictionRandomSeedAddr = GameConfGetAddress(hGamedata, CBASEENTITY_PREDICTIONSEED); + if (g_iPredictionRandomSeedAddr == Address_Null) { + SetFailState("Unable to get '%s' address", CBASEENTITY_PREDICTIONSEED); + } + + delete hGamedata; +} + +void Hook_OnPatternChanged(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + ToggleDetourHooks(hConVar.IntValue > eSGSDisable); +} + +void Hook_OnCenterPelletChanged(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + TogglePatch(!hConVar.BoolValue); +} + +void TogglePatch(bool bPatchEnable, bool bDeleteHandles = false) +{ + if (g_hCenterPelletPatch == null) { + SetFailState("Patch handle '%s' is null", CENTER_PELLET_PATCH); + return; + } + + if (bPatchEnable && !g_bCenterPelletPatchEnabled) { + if (!g_hCenterPelletPatch.Enable()) { + SetFailState("Failed to enable MemoryPatch '%s'", CENTER_PELLET_PATCH); + } + + g_bCenterPelletPatchEnabled = true; + return; + } + + if (!bPatchEnable && g_bCenterPelletPatchEnabled) { + g_hCenterPelletPatch.Disable(); // void + + g_bCenterPelletPatchEnabled = false; + } + + if (!bPatchEnable && bDeleteHandles) { + delete g_hCenterPelletPatch; + g_hCenterPelletPatch = null; + } +} + +void ToggleDetourHooks(bool bDetourEnable, bool bDeleteHandles = false) +{ + if (g_hDetourSharedRandomFloat == null) { + SetFailState("Detour handle '%s' is null", DHOOK_SHAREDRANDFLOAT_NAME); + return; + } + + if (bDetourEnable && !g_bDetourHooksEnabled) { + if (!g_hDetourTerrorGunFireBullet.Enable(Hook_Pre, Handler_CTerrorGun__FireBullet_Pre)) { + SetFailState("Failed to enable detour '%s'", CTERRORGUN_FIREBULLET); + } + + if (!g_hDetourTerrorGunFireBullet.Enable(Hook_Post, Handler_CTerrorGun__FireBullet_Post)) { + SetFailState("Failed to enable detour '%s'", CTERRORGUN_FIREBULLET); + } + + if (!g_hDetourSharedRandomFloat.Enable(Hook_Pre, Handler_SharedRandomFloat_Pre)) { + SetFailState("Failed to enable detour '%s'", DHOOK_SHAREDRANDFLOAT_NAME); + } + + #if DEBUG + if (!g_hDetourSharedRandomFloat.Enable(Hook_Post, Handler_SharedRandomFloat_Post)) { + SetFailState("Failed to enable detour '%s'", DHOOK_SHAREDRANDFLOAT_NAME); + } + #endif + + ClearFireBulletsInfo(); + g_bDetourHooksEnabled = true; + return; + } + + if (!bDetourEnable && g_bDetourHooksEnabled) { + if (!g_hDetourTerrorGunFireBullet.Disable(Hook_Pre, Handler_CTerrorGun__FireBullet_Pre)) { + SetFailState("Failed to enable detour '%s'", CTERRORGUN_FIREBULLET); + } + + if (!g_hDetourTerrorGunFireBullet.Disable(Hook_Post, Handler_CTerrorGun__FireBullet_Post)) { + SetFailState("Failed to enable detour '%s'", CTERRORGUN_FIREBULLET); + } + + if (!g_hDetourSharedRandomFloat.Disable(Hook_Pre, Handler_SharedRandomFloat_Pre)) { + SetFailState("Failed to disable detour '%s'", DHOOK_SHAREDRANDFLOAT_NAME); + } + + #if DEBUG + if (!g_hDetourSharedRandomFloat.Disable(Hook_Post, Handler_SharedRandomFloat_Post)) { + SetFailState("Failed to disable detour '%s'", DHOOK_SHAREDRANDFLOAT_NAME); + } + #endif + + ClearFireBulletsInfo(); + g_bDetourHooksEnabled = false; + } + + if (!bDetourEnable && bDeleteHandles) { + delete g_hDetourSharedRandomFloat; + g_hDetourSharedRandomFloat = null; + } +} + +public void OnPluginEnd() +{ + g_hCvarPatternType.RemoveChangeHook(Hook_OnPatternChanged); + g_hCvarCenterPellet.RemoveChangeHook(Hook_OnCenterPelletChanged); + + ToggleDetourHooks(false, true); + TogglePatch(false, true); +} + +// Pre-hook +// void CTerrorGun::FireBullet( void ) +// We save additional information that is not in `SharedRandomFloat` function +MRESReturn Handler_CTerrorGun__FireBullet_Pre(int iThisWeapon) +{ + ClearFireBulletsInfo(); + + char sWeaponName[32]; + GetEdictClassname(iThisWeapon, sWeaponName, sizeof(sWeaponName)); + + g_iFireBulletInfo[eiTotalPellets] = GetTotalPellets(iThisWeapon, sWeaponName); + g_iFireBulletInfo[eiWeaponRef] = EntIndexToEntRef(iThisWeapon); + + int iOwner = GetEntPropEnt(iThisWeapon, Prop_Send, "m_hOwner"); // CTerrorWeapon::GetPlayerOwner() + if (iOwner > 0 && iOwner <= MaxClients) { // Replacement for CBasePlayer::IsPlayer check + g_iFireBulletInfo[eiWeaponOwnerRef] = EntIndexToEntRef(iOwner); + } + + IncrementFireId(); + g_iFireBulletInfo[eiCurrentFireId] = g_iCurrentFireID; + g_iFireBulletInfo[eiFrameCallNum] = GetGameTickCount(); + + TryOverrideRandomSeed(); + +#if DEBUG + // CTerrorWeaponInfo::m_fPelletScatterYaw, offset 3120 + g_fDebugDataStorage[efHorizontalSpread] = L4D2_GetFloatWeaponAttribute(sWeaponName, L4D2FWA_PelletScatterYaw); + g_bDebugSetData[efHorizontalSpread] = true; + + // CTerrorWeaponInfo::m_fPelletScatterPitch, offset 3116 + g_fDebugDataStorage[efVerticalSpread] = L4D2_GetFloatWeaponAttribute(sWeaponName, L4D2FWA_PelletScatterPitch); + g_bDebugSetData[efVerticalSpread] = true; + + PrintToChatAll("[CTerrorGun::FireBullet (Pre)] Weapon ref: %d", EntIndexToEntRef(iThisWeapon)); + ParseAdditionInfo(); +#endif + + return MRES_Ignored; +} + +// Post-hook +// void CTerrorGun::FireBullet( void ) +// Clearing additional information +MRESReturn Handler_CTerrorGun__FireBullet_Post(int iThisWeapon) +{ + if (g_iFireBulletInfo[eiPredictionRandomSeedOld] != -1) { + StoreToAddress(g_iPredictionRandomSeedAddr, g_iFireBulletInfo[eiPredictionRandomSeedOld], NumberType_Int32, false); + } + + ClearFireBulletsInfo(); + +#if DEBUG + PrintToChatAll("[CTerrorGun::FireBullet (Post)] Weapon ref: %d, frame: %d", EntIndexToEntRef(iThisWeapon), GetGameTickCount()); +#endif + + return MRES_Ignored; +} + +// Pre-hook +// float SharedRandomFloat( const char *sharedname, float flMinVal, float flMaxVal, int additionalSeed /*=0*/ ) +// Our code only works if CTerrorWeaponInfo::m_fPelletScatterYaw offset 3120 (flHorizontalSpread) is greater than zero, +// and the code only works for the 2nd pellet if the central pellet is not activated with the patch, +// because the function is called only in this case, +// with parameters "CTerrorPlayer::FireBullet Spread" or "CTerrorPlayer::FireBullet SpreadDir". +MRESReturn Handler_SharedRandomFloat_Pre(DHookReturn hReturn, DHookParam hParams) +{ + // Check if additional information is received, our code works, + // otherwise our spread pattern will be incorrect. + if (!ShouldOverrideSharedRandom()) { + return MRES_Ignored; + } + + char sSharedName[128]; + hParams.GetString(1, sSharedName, sizeof(sSharedName)); + + float flMin = hParams.Get(2); + float flMax = hParams.Get(3); + int iAdditionSeed = hParams.Get(4); + + float flOverrideValue = 0.0; + MRESReturn iReturnOverride = OverrideSharedRandom(sSharedName, flMin, flMax, iAdditionSeed, g_iFireBulletInfo[eiTotalPellets], flOverrideValue); + + if (iReturnOverride != MRES_Ignored) { + #if DEBUG + PrintToChatAll("[OverrideSharedRandom] Name: %s, max: %.6f, seed: %d, total pellets: %d, override value: %.6f", + sSharedName, flMax, iAdditionSeed, g_iFireBulletInfo[eiTotalPellets], flOverrideValue); + #endif + + if (iReturnOverride == MRES_ChangedHandled) { + //hParams.SetString(1, sSharedName); + hParams.Set(2, flMin); + hParams.Set(3, flMax); + //hParams.Set(4, iAdditionSeed); + } else { + DHookSetReturn(hReturn, flOverrideValue); + } + + return iReturnOverride; + } + + return MRES_Ignored; +} + +// Post-hook for logging, can be disabled in release +// float SharedRandomFloat( const char *sharedname, float flMinVal, float flMaxVal, int additionalSeed /*=0*/ ) +#if DEBUG +MRESReturn Handler_SharedRandomFloat_Post(DHookReturn hReturn, DHookParam hParams) +{ + char sSharedName[128]; + hParams.GetString(1, sSharedName, sizeof(sSharedName)); + + float flMin = hParams.Get(2); + float flMax = hParams.Get(3); + int iAdditionSeed = hParams.Get(4); + + float flReturned = hReturn.Value; + + CalculatePoints(sSharedName, flReturned, iAdditionSeed); + int iPredictionRandomSeedNow = LoadFromAddress(g_iPredictionRandomSeedAddr, NumberType_Int32); + PrintToChatAll("[SharedRandomFloat (Post)] Name: %s, Min: %.6f, Max: %.6f, Additional Seed: %d, Seed: %d, Return: %.6f", + sSharedName, flMin, flMax, iAdditionSeed, iPredictionRandomSeedNow, flReturned); + return MRES_Ignored; +} +#endif + +MRESReturn OverrideSharedRandom(const char[] sSharedName, float &flMin, float &flMax, int iAdditionSeed, int iTotalPellets, float& flOverrideValue) +{ + if (strcmp(sSharedName, "CTerrorGun::FireBullet HorizSpread") == 0 + || strcmp(sSharedName, "CTerrorGun::FireBullet VertSpread") == 0 + ) { + // Shotguns Only: Disable spread offset (vertical/horizontal) relative to the shot. + // This is not needed in static spread. + // The old assembler patch did not fix this. + if (!g_hCvarAllPelletsSpreadOffset.BoolValue && iTotalPellets > 1 && flMax > 0.0) { + flOverrideValue = 0.0; + return MRES_Override; + } + + return MRES_Ignored; + } + + // Call original function with a constant seed. + if (g_hCvarPatternType.IntValue == eSGSVanillaConsistent) { + return MRES_Ignored; + } + + int iPelletsInRing1 = g_hCvarRing1Pellets.IntValue + (g_hCvarCenterPellet.BoolValue ? 1 : 0); + + if (strcmp(sSharedName, "CTerrorPlayer::FireBullet Spread") == 0) { + if (g_hCvarPatternType.IntValue == eSGSStaticRings) { + if (iAdditionSeed > iPelletsInRing1) { + // Ring 2 (full spread) + // flHorizontalSpread == flMax, look at the comments below the plugin to see where this came from. + flOverrideValue = flMax; + return MRES_Override; + } + + // Ring 1 (reduced spread) + // flHorizontalSpread == flMax + flOverrideValue = (flMax / g_hCvarRing1Factor.FloatValue); + return MRES_Override; + } + + // eSGSDynamicRings + if (iAdditionSeed > iPelletsInRing1) { + // Ring 2 - random radius from flMax, minus configurable offset (g_hCvarRing2MaxOffsetPercent). + float fOffsetPercent = flMax * (g_hCvarRing2MaxOffsetPercent.FloatValue / 100.0); + + flMin = flMax - fOffsetPercent; + flMax = flMax; + + return MRES_ChangedHandled; // The function will return a random value itself + } + + // Ring 1 - random radius from (flMax / ring factor) minus configurable offset (g_hCvarRing1MaxOffsetPercent). + float fRingFactor = flMax / g_hCvarRing1Factor.FloatValue; + float fOffsetPercent = fRingFactor * (g_hCvarRing1MaxOffsetPercent.FloatValue / 100.0); + + flMin = fRingFactor - fOffsetPercent; + flMax = fRingFactor; + + return MRES_ChangedHandled; // The function will return a random value itself. + } + + if (strcmp(sSharedName, "CTerrorPlayer::FireBullet SpreadDir") == 0) { + if (iAdditionSeed > iPelletsInRing1) { + // Ring 2 + // + // In fact, `iRemainingShots` doesn't exist, it's all optimization on Linux, it doesn't exist on Windows, + // it all comes down to using iTotalPellets. + // + // We don't have access to this thing `iRemainingShots` (linux only?). + // We now count the number of pellets in the second ring differently + // + // Old formula from `l4d2_static_shotgun_spread` (linux only?): + // int iPelletsInRing1 = g_hCvarRing1Pellets.IntValue + (1 - !g_hCvarCenterPellet.BoolValue); + // int iPelletsCount2 = g_hCvarRing1Pellets.IntValue + (2 - !g_hCvarCenterPellet.BoolValue); + // flOverrideValue = (180.0f / float(iRemainingShots + iAdditionSeed - iPelletsInRing1) * float(iAdditionSeed - iPelletsCount2)); + // + // New formula, now we use total pellets var. + // FirstSeed = 1 if center pellet enabled; FirstSeed = 2 if not (to shift the indexing). + // + // Center pellet disabled + // int iPelletsInRing = iTotalPellets - g_hCvarRing1Pellets.IntValue; + // int iIndexInRing = iAdditionSeed - g_hCvarRing1Pellets.IntValue + 1; + // flOverrideValue = (180.0f / float(iPelletsInRing) * float(iIndexInRing)); + // + // Center pellet enabled + // int iPelletsInRing = iTotalPellets - g_hCvarRing1Pellets.IntValue + 1; // + 1 for center pellet + // int iIndexInRing = iAdditionSeed - g_hCvarRing1Pellets.IntValue + 2; + // flOverrideValue = (180.0f / float(iPelletsInRing) * float(iIndexInRing)); + + int iPelletsInRing2 = iTotalPellets - iPelletsInRing1; + int iFirstSeed = (g_hCvarCenterPellet.BoolValue) ? 2 : 1; // We add the pellet offset due to the seed + int iIndexInRing = iAdditionSeed - (g_hCvarRing1Pellets.IntValue + iFirstSeed); + float fAngleStep = 180.0 / float(iPelletsInRing2); + + flOverrideValue = (fAngleStep * float(iIndexInRing)); + return MRES_Override; + } + + // Ring 1: + // + // Old formula from `l4d2_static_shotgun_spread` for linux (degree patch), for windows (360 / pellets): + // float flDegree = 360.0f / (2.0f * float(g_hCvarRing1Pellets.IntValue)); + // flOverrideValue = (flDegree * float(iAdditionSeed - 2)); + // + // New formula, we use an angle from 0 to < 180 degrees, the old ASM patch created a negative angle. + // FirstSeed = 1 if center pellet enabled; FirstSeed = 2 if not (to shift the indexing). + // + // Center pellet disabled + // flOverrideValue = (180.0f / float(g_hCvarRing1Pellets.IntValue) * float(iAdditionSeed - 2)); + // + // Center pellet enabled + // flOverrideValue = (180.0f / float(g_hCvarRing1Pellets.IntValue) * float(iAdditionSeed - 1)); + + int iFirstSeed = (g_hCvarCenterPellet.BoolValue) ? 2 : 1; + int iIndexInRing = iAdditionSeed - iFirstSeed; + float fAngleStep = 180.0 / float(g_hCvarRing1Pellets.IntValue); + + flOverrideValue = (fAngleStep * float(iIndexInRing)); + return MRES_Override; + } + + return MRES_Ignored; +} + +bool ShouldOverrideSharedRandom() +{ + if (g_iFireBulletInfo[eiWeaponRef] == INVALID_ENT_REFERENCE) { + return false; + } + + int iWeapon = EntRefToEntIndex(g_iFireBulletInfo[eiWeaponRef]); + if (iWeapon == INVALID_ENT_REFERENCE) { + return false; + } + + // Check if additional data was saved in another call, i.e. the data is outdated + // I don't think it's necessary, but the checks are too simple and don't affect performance, + // so I'll leave it, because if we get the wrong data, our spread pattern will be wrong. + // The asm patch took the data directly, but we can't do that inside function `SharedRandomFloat` + if (g_iFireBulletInfo[eiFrameCallNum] != GetGameTickCount() || g_iFireBulletInfo[eiCurrentFireId] != g_iCurrentFireID) { + return false; + } + + return true; +} + +void IncrementFireId() +{ + // int overflow check + if (g_iCurrentFireID > 1000000) { + g_iCurrentFireID = 0; + } + + g_iCurrentFireID++; +} + +int GetTotalPellets(int iWeapon, const char[] sWeaponName) +{ + // From game function `CTerrorPlayer::FireBullet` + int iWeaponInfoPellets = L4D2_GetIntWeaponAttribute(sWeaponName, L4D2IWA_Bullets); + + int iTotalPellets = iWeaponInfoPellets; + int iBulletsPerFire = g_hCvarPerfFireBulletSingle.IntValue; + if (iBulletsPerFire == 1) { + iTotalPellets = 1; + } else if (iBulletsPerFire > 1) { + iTotalPellets = iWeaponInfoPellets * iBulletsPerFire; + } + + // In the game, this is not a simple variable, it is a CBitVec<3> m_upgradeBitVec + if (HasEntProp(iWeapon, Prop_Send, "m_upgradeBitVec")) { + if (GetEntProp(iWeapon, Prop_Send, "m_upgradeBitVec") & (1 << UPGRADE_EXPLOSIVE_AMMO)) { + iTotalPellets = 1; + } + } + + return iTotalPellets; +} + +void TryOverrideRandomSeed() +{ + if (g_hCvarPatternType.IntValue != eSGSVanillaConsistent) { + return; + } + + int iPredictionRandomSeedNow = LoadFromAddress(g_iPredictionRandomSeedAddr, NumberType_Int32) & INT_MAX; + + if (g_hCvarPredictionSeed.IntValue != -1) { + g_iSavedPredictionRandomSeed = -1; + + g_iFireBulletInfo[eiPredictionRandomSeedOld] = iPredictionRandomSeedNow; + StoreToAddress(g_iPredictionRandomSeedAddr, g_hCvarPredictionSeed.IntValue & INT_MAX, NumberType_Int32, false); + return; + } + + if (g_iSavedPredictionRandomSeed == -1) { + g_iSavedPredictionRandomSeed = iPredictionRandomSeedNow; + } else { + g_iFireBulletInfo[eiPredictionRandomSeedOld] = iPredictionRandomSeedNow; + StoreToAddress(g_iPredictionRandomSeedAddr, g_iSavedPredictionRandomSeed & INT_MAX, NumberType_Int32, false); + } +} + +void ClearFireBulletsInfo() +{ + for (int i = 0; i < eTotalSize; i++) { + g_iFireBulletInfo[i] = 0; + } + + g_iFireBulletInfo[eiPredictionRandomSeedOld] = -1; + g_iFireBulletInfo[eiWeaponRef] = INVALID_ENT_REFERENCE; + g_iFireBulletInfo[eiWeaponOwnerRef] = INVALID_ENT_REFERENCE; + +#if DEBUG + ClearDebugData(); +#endif +} + +Action Cmd_ResetSavedPredictionSeed(int iClient, int iArgs) +{ + g_iSavedPredictionRandomSeed = -1; + ReplyToCommand(iClient, "[SM] Reset saved prediction seed."); + + return Plugin_Handled; +} + +stock int GenerateTimeSeed() +{ + float fEngineTime = GetEngineTime() * 1000.0; + int iSeed = RoundToNearest(fEngineTime); + return iSeed & INT_MAX; +} + +#if DEBUG +void CalculatePoints(const char[] sSharedName, float fSetData, int iAdditionSeed) +{ + // From game function `CTerrorPlayer::FireBullet` + // + // This function is used to debug. + // It collects the values ​​generated by the SharedRandomFloat function and outputs the results to the chat when both required values ​​are obtained: + // ** efScatterOffset — random deviation of pellets from the center (scatter). + // ** efAngleDeg — the direction of this deviation in degrees. + + if (strcmp(sSharedName, "CTerrorPlayer::FireBullet Spread") == 0) { + g_bDebugSetData[efScatterOffset] = true; + g_fDebugDataStorage[efScatterOffset] = fSetData; + } else if (strcmp(sSharedName, "CTerrorPlayer::FireBullet SpreadDir") == 0) { + g_bDebugSetData[efAngleDeg] = true; + g_fDebugDataStorage[efAngleDeg] = fSetData; + } + + if (!g_bDebugSetData[efScatterOffset] || !g_bDebugSetData[efAngleDeg]) { + return; + } + + if (!g_bDebugSetData[efHorizontalSpread] || !g_bDebugSetData[efVerticalSpread]) { + return; + } + + float fVerticalSpreadScale = 1.0; + if (g_fDebugDataStorage[efHorizontalSpread] != 0.0 && g_fDebugDataStorage[efVerticalSpread] != 0.0) { + fVerticalSpreadScale = g_fDebugDataStorage[efVerticalSpread] / g_fDebugDataStorage[efHorizontalSpread]; + } + + // Game code, variables have been changed to make the code easier to understand. + // + // g_fDebugDataStorage[efHorizontalSpread] = CTerrorWeaponInfo::m_fPelletScatterYaw, offset 3120 + // g_fDebugDataStorage[efVerticalSpread] = CTerrorWeaponInfo::m_fPelletScatterPitch, offset 3116 + // + //g_fDebugDataStorage[efScatterOffset] = SharedRandomFloat("CTerrorPlayer::FireBullet Spread", -g_fDebugDataStorage[efHorizontalSpread], g_fDebugDataStorage[efHorizontalSpread], iAdditionSeed); + //g_fDebugDataStorage[efAngleDeg] = SharedRandomFloat("CTerrorPlayer::FireBullet SpreadDir", 0.0, 180.0, iAdditionSeed); + + float fAngleRad = (g_fDebugDataStorage[efAngleDeg] * 2.0) * (FLOAT_PI / 180.0); // Scale to 2.0f and convert to radians + float fOffsetX = (fVerticalSpreadScale * g_fDebugDataStorage[efScatterOffset]) * Sine(fAngleRad); + float fOffsetY = g_fDebugDataStorage[efScatterOffset] * Cosine(fAngleRad); + + PrintToChatAll("%d) x: %f, y: %f", iAdditionSeed, fOffsetX, fOffsetY); + + g_bDebugSetData[efScatterOffset] = false; + g_fDebugDataStorage[efScatterOffset] = 0.0; + + g_bDebugSetData[efAngleDeg] = false; + g_fDebugDataStorage[efAngleDeg] = 0.0; +} + +void ClearDebugData() +{ + for (int i = 0; i < eDebugDataSize; i++) { + g_bDebugSetData[i] = false; + g_fDebugDataStorage[i] = 0.0; + } +} + +void ParseAdditionInfo() +{ + char sWeaponName[64]; + int iWeaponIndex = GetEntityIndexAndName(g_iFireBulletInfo[eiWeaponRef], sWeaponName, sizeof(sWeaponName)); + + char sOwnerName[64]; + int iOwnerIndex = GetEntityIndexAndName(g_iFireBulletInfo[eiWeaponOwnerRef], sOwnerName, sizeof(sOwnerName)); + + PrintToChatAll("[Weapon Info] Ref: %d, index: %d, name %s", g_iFireBulletInfo[eiWeaponRef], iWeaponIndex, sWeaponName); + PrintToChatAll("[Owner Info] Ref: %d, index: %d, name: %s", g_iFireBulletInfo[eiWeaponOwnerRef], iOwnerIndex, sOwnerName); + PrintToChatAll("[Another Info] Total pellets: %d, frame: %d", g_iFireBulletInfo[eiTotalPellets], g_iFireBulletInfo[eiFrameCallNum]); +} + +int GetEntityIndexAndName(int iRef, char[] sEntityName, int iMaxLength) +{ + FormatEx(sEntityName, iMaxLength, "Unknown"); + + if (iRef == INVALID_ENT_REFERENCE) { + return INVALID_ENT_REFERENCE; + } + + int iIndex = EntRefToEntIndex(iRef); + if (iIndex == INVALID_ENT_REFERENCE) { + return INVALID_ENT_REFERENCE; + } + + if (iIndex < 0 || iIndex > MAX_EDICTS) { + return INVALID_ENT_REFERENCE; + } + + if (iIndex >= 1 && iIndex <= MaxClients) { + GetClientName(iIndex, sEntityName, iMaxLength); + } else { + GetEdictClassname(iIndex, sEntityName, iMaxLength); + } + + return iIndex; +} + +Action Cmd_CheckCenterPelletVal(int iClient, int iArgs) +{ + Address hPatchAggr = g_hCenterPelletPatch.Address; + + int iValue = LoadFromAddress(hPatchAggr + view_as
(1), NumberType_Int8); + ReplyToCommand(iClient, "[Debug] Center pellet: %s, patch value: %d.", (iValue == 0) ? "disable" : "enable", iValue); + + return Plugin_Handled; +} + +Action Cmd_GetPredictionRandomSeed(int iClient, int iArgs) +{ + int iValue = LoadFromAddress(g_iPredictionRandomSeedAddr, NumberType_Int32); + ReplyToCommand(iClient, "[Debug] %s: %d.", CBASEENTITY_PREDICTIONSEED, iValue); + + return Plugin_Handled; +} +#endif + +#if 0 +// C++ test code written by A1m` +// +// Can be run in any C++ compiler, +// you need to compare the points with the assembler patch and the function hook, +// changing the value of the `bUseOurAsmPatch` variable. +// +// Some settings have been changed. +// Command `sm_weapon_attributes shotgun_chrome` from `l4d2_weapon_attributes` plugin +/* + Weapon stats for weapon_shotgun_chrome: + Damage: 17. + Pellets: 16. + Clip Size: 8. + Max player speed: 220.00. + Spread per shot: 1.00. + Max spread: 5.00. + Spread decay: 5.00. + Min ducking spread: 0.00. + Min standing spread: 0.80. + Min in air spread: 2.50. + Max movement spread: 1.50. + Penetration num layers: 0.00. + Penetration power: 30.00. + Penetration max dist: 500.00. + Char penetration max dist: 300.00. + Range: 3000.00. + Range modifier: 0.69. + Cycle time: 0.50. + Pellet scatter pitch: 3.25. + Pellet scatter yaw: 3.25. +*/ +#include +#include +#include // setprecision, fixed +#include + +using namespace std; +float SharedRandomFloat(const char* sSharedName, float flMin, float flMax, int iAdditionSeed); + +// Zonemod settings for chorme shotgun +int iTotalPellets = 16; // CTerrorWeaponInfo::m_iBullets, offset 2520 (Maybe m_iPellets?) +int iNumPelletsRing1 = 8; // Cvar `sgspread_ring1_pellets` from `l4d2_static_shotgun_spread` plugin +float flRing1Factor = 3.0f; // Cvar `sgspread_ring1_factor` from `l4d2_static_shotgun_spread` plugin +bool bCenterPellet = false; // Cvar `sgspread_center_pellet` from `l4d2_static_shotgun_spread` plugin +bool bUseOurAsmPatch = false; // Use asm or hook SharedRandomFloat with our code + +float flHorizontalSpread = 3.25f; // default value, CTerrorWeaponInfo::m_fPelletScatterYaw, offset 3120 +float flVerticalSpread = 3.25f; // default value, CTerrorWeaponInfo::m_fPelletScatterPitch, offset 3116 + +int main() +{ + // iAdditionSeed - `CTerrorPlayer::FireBullets` function arg + // + // We ignore start vector (vector[3] = {0.0f, 0.0f, 0.0f}), + // it is also one of the arguments of the function + // + // We ignore the initial vector of the shot, + // we set it to zero, so it does not participate in our calculations, + // it is also one of the arguments of the function + // + // In fact, `iRemainingShots` doesn't exist, it's all optimization on Linux, it doesn't exist on Windows, + // it all comes down to using iTotalPellets. + // + // Init vars + int iAdditionSeed = 0; + float flAngleDeg = 0.0f; + float flNewSpread = 0.0f; + int iRemainingShots = iTotalPellets - 1; + + float flVerticalSpreadScale = 1.0f; + if (flHorizontalSpread != 0.0f && flVerticalSpread != 0.0f) { + // 3.25 / 3.25 = 1.0f for chrome anyway, omg :D + flVerticalSpreadScale = flVerticalSpread / flHorizontalSpread; + } + + if (iTotalPellets > 0) { + for (int i = (bCenterPellet) ? 1 : 0; ; i = 0) { // `l4d2_static_shotgun_spread` center pellet (center pellet patch) in start loop + ++iAdditionSeed; + + if (flHorizontalSpread != 0.0f && i == 0) { + if (bUseOurAsmPatch) { + // Start our asm patch + int iPelletsCountRing1 = iNumPelletsRing1 + (1 - !bCenterPellet); // `l4d2_static_shotgun_spread` formula + + if (iAdditionSeed > iPelletsCountRing1) { // `l4d2_static_shotgun_spread` pellet 0 offset (pellet0 patch) + // Ring 2 + flNewSpread = flHorizontalSpread; + + // `l4d2_static_shotgun_spread` pellet 1 and pellet 2 offsets (pellet1, pellet2 patch) ↓ + int iPelletsCountRing12 = iNumPelletsRing1 + (2 - !bCenterPellet); // `l4d2_static_shotgun_spread` formula + flAngleDeg = 180.0f / float(iRemainingShots + iAdditionSeed - iPelletsCountRing1) * float(iAdditionSeed - iPelletsCountRing12); + } else { + // Ring 1 + float flDegree = 360.0f / (2.0f * float(iNumPelletsRing1)); // formula from `l4d2_static_shotgun_spread` for linux (degree patch), for windows (360 / pellets) + + flNewSpread = flHorizontalSpread / flRing1Factor; // `l4d2_static_shotgun_spread` factor (factor patch), for linux store int type, windows float type + flAngleDeg = flDegree * float(iAdditionSeed - 2); + } + // End our asm patch + } else { + // Use hook SharedRandomFloat with our code + // The goal is to get the same values as in the assembler patch + flNewSpread = SharedRandomFloat("CTerrorPlayer::FireBullet Spread", -flHorizontalSpread, flHorizontalSpread, iAdditionSeed); + flAngleDeg = SharedRandomFloat("CTerrorPlayer::FireBullet SpreadDir", 0.0f, 180.0f, iAdditionSeed); + } + + float flAngleRad = (flAngleDeg * 2.0f) * (M_PI / 180.0f); // scale to 2.0f and convert to radians + float flOffsetX = (flVerticalSpreadScale * flNewSpread) * sin(flAngleRad); + float flOffsetY = flNewSpread * cos(flAngleRad); + + //cout << "flNewSpread: " << fixed << setprecision(6) << flNewSpread + // << ", flAngleDeg: " << fixed << setprecision(6) << flAngleDeg + // << ", flAngleRad: " << fixed << setprecision(6) << flAngleRad + // << endl; + + // You can draw the obtained result somewhere on the website, + // or, for example, ask to draw chatgpt + cout << "x: " << fixed << setprecision(6) << flOffsetX + << ", y: " << fixed << setprecision(6) << flOffsetY + << endl; + } + + if (iAdditionSeed == 1 && i == 1) { + // Center pellet code + float flOffsetX = 0.0f; + float flOffsetY = 0.0f; + cout << "x: " << fixed << setprecision(6) << flOffsetX + << ", y: " << fixed << setprecision(6) << flOffsetY + << endl; + } + + if (iRemainingShots <= 0) { + cout << "Total shots: "<< iAdditionSeed; + break; + } + + --iRemainingShots; + } + } + + return 0; +} + +// Try to make your code =) +float SharedRandomFloat(const char* sSharedName, float flMin, float flMax, int iAdditionSeed) +{ + int iPelletsInRing1 = iNumPelletsRing1 + (bCenterPellet ? 1 : 0); + + if (strcmp(sSharedName, "CTerrorPlayer::FireBullet Spread") == 0) { + if (iAdditionSeed > iPelletsInRing1) { + // Ring 2 (full spread) + // flHorizontalSpread == flMax + return flMax; + } + + // Ring 1 (reduced spread) + // flHorizontalSpread == flMax + return (flMax / flRing1Factor); + } + + if (strcmp(sSharedName, "CTerrorPlayer::FireBullet SpreadDir") == 0) { + if (iAdditionSeed > iPelletsInRing1) { + // Ring 2 + // + // In fact, `iRemainingShots` doesn't exist, it's all optimization on Linux, it doesn't exist on Windows, + // it all comes down to using iTotalPellets. + // + // We don't have access to this thing `iRemainingShots` (linux only?). + // We now count the number of pellets in the second ring differently + // + // Old formula from `l4d2_static_shotgun_spread` (linux only?): + // int iPelletsInRing1 = iNumPelletsRing1 + (1 - !bCenterPellet); + // int iPelletsCountRing12 = iNumPelletsRing1 + (2 - !bCenterPellet); + // return (180.0f / float(iRemainingShots + iAdditionSeed - iPelletsInRing1) * float(iAdditionSeed - iPelletsCountRing12)); + // + // New formula, now we use total pellets var. + // FirstSeed = 1 if center pellet enabled; FirstSeed = 2 if not (to shift the indexing). + // + // Center pellet disabled + // int iPelletsInRing = iTotalPellets - iNumPelletsRing1; + // int iIndexInRing = iAdditionSeed - iNumPelletsRing1 + 1; + // return (180.0f / float(iPelletsInRing) * float(iIndexInRing)); + // + // Center pellet enabled + // int iPelletsInRing = iTotalPellets - iNumPelletsRing1 + 1; // + 1 for center pellet + // int iIndexInRing = iAdditionSeed - iNumPelletsRing1 + 2; + // return (180.0f / float(iPelletsInRing) * float(iIndexInRing)); + + int iPelletsInRing2 = iTotalPellets - iPelletsInRing1; + int iFirstSeed = (bCenterPellet) ? 2 : 1; // We add the pellet offset due to the seed + int iIndexInRing = iAdditionSeed - (iNumPelletsRing1 + iFirstSeed); + float fAngleStep = 180.0f / float(iPelletsInRing2); + + return (fAngleStep * float(iIndexInRing)); + } + + // Ring 1: + // + // Old formula from `l4d2_static_shotgun_spread` for linux (degree patch), for windows (360 / pellets): + // float flDegree = 360.0f / (2.0f * float(iNumPelletsRing1)); + // return (flDegree * float(iAdditionSeed - 2)); + // + // New formula, we use an angle from 0 to < 180 degrees, the old ASM patch created a negative angle. + // FirstSeed = 1 if center pellet enabled; FirstSeed = 2 if not (to shift the indexing). + // + // Center pellet disabled + // return (180.0f / float(iNumPelletsRing1) * float(iAdditionSeed - 2)); + // + // Center pellet enabled + // return (180.0f / float(iNumPelletsRing1) * float(iAdditionSeed - 1)); + + int iFirstSeed = (bCenterPellet) ? 2 : 1; + int iIndexInRing = iAdditionSeed - iFirstSeed; + float fAngleStep = 180.0f / float(iNumPelletsRing1); + + return (fAngleStep * float(iIndexInRing)); + } + + return 0.0f; // The game returns a random value, but we return 0.0f =) +} +#endif From a93eabb45fd433100107be2c0117d41cb3df9b2a Mon Sep 17 00:00:00 2001 From: A1mDev <33463136+A1mDev@users.noreply.github.com> Date: Mon, 24 Nov 2025 05:05:29 +0700 Subject: [PATCH 2/5] Delete or move old files to the archive --- .../sourcemod/plugins/optional/code_patcher.smx | Bin 8292 -> 0 bytes .../optional/l4d2_static_shotgun_spread.smx | Bin 4842 -> 0 bytes .../scripting/{ => archive}/code_patcher.sp | 0 .../archive}/gamedata/code_patcher.txt | 0 .../{ => archive}/include/code_patcher.inc | 0 .../{ => archive}/l4d2_static_shotgun_spread.sp | 0 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 addons/sourcemod/plugins/optional/code_patcher.smx delete mode 100644 addons/sourcemod/plugins/optional/l4d2_static_shotgun_spread.smx rename addons/sourcemod/scripting/{ => archive}/code_patcher.sp (100%) rename addons/sourcemod/{ => scripting/archive}/gamedata/code_patcher.txt (100%) rename addons/sourcemod/scripting/{ => archive}/include/code_patcher.inc (100%) rename addons/sourcemod/scripting/{ => archive}/l4d2_static_shotgun_spread.sp (100%) diff --git a/addons/sourcemod/plugins/optional/code_patcher.smx b/addons/sourcemod/plugins/optional/code_patcher.smx deleted file mode 100644 index f9676a3ca834820bc1aa6f9e12cafd6e6dace59f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8292 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 ONOt%E=2mGdA_jS2|FaRdT^ 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 BCUpP+ diff --git a/addons/sourcemod/scripting/code_patcher.sp b/addons/sourcemod/scripting/archive/code_patcher.sp similarity index 100% rename from addons/sourcemod/scripting/code_patcher.sp rename to addons/sourcemod/scripting/archive/code_patcher.sp diff --git a/addons/sourcemod/gamedata/code_patcher.txt b/addons/sourcemod/scripting/archive/gamedata/code_patcher.txt similarity index 100% rename from addons/sourcemod/gamedata/code_patcher.txt rename to addons/sourcemod/scripting/archive/gamedata/code_patcher.txt diff --git a/addons/sourcemod/scripting/include/code_patcher.inc b/addons/sourcemod/scripting/archive/include/code_patcher.inc similarity index 100% rename from addons/sourcemod/scripting/include/code_patcher.inc rename to addons/sourcemod/scripting/archive/include/code_patcher.inc diff --git a/addons/sourcemod/scripting/l4d2_static_shotgun_spread.sp b/addons/sourcemod/scripting/archive/l4d2_static_shotgun_spread.sp similarity index 100% rename from addons/sourcemod/scripting/l4d2_static_shotgun_spread.sp rename to addons/sourcemod/scripting/archive/l4d2_static_shotgun_spread.sp From 4d14628db322855d8c3ca42db90a4a00c3a7ec60 Mon Sep 17 00:00:00 2001 From: A1mDev <33463136+A1mDev@users.noreply.github.com> Date: Mon, 24 Nov 2025 05:33:06 +0700 Subject: [PATCH 3/5] [l4d2_sharedrandom_overrides] Add new settings to configs --- cfg/cfgogl/acemodrv/shared_plugins.cfg | 2 +- cfg/cfgogl/acemodrv/shared_settings.cfg | 12 +++++++----- cfg/cfgogl/apex/shared_plugins.cfg | 2 +- cfg/cfgogl/apex/shared_settings.cfg | 12 +++++++----- cfg/cfgogl/deadman/confogl_plugins.cfg | 2 +- cfg/cfgogl/deadman/deadman.cfg | 9 +++++++-- cfg/cfgogl/eq/shared_plugins.cfg | 2 +- cfg/cfgogl/eq/shared_settings.cfg | 8 +++++--- cfg/cfgogl/neomod/shared_plugins.cfg | 2 +- cfg/cfgogl/neomod/shared_settings.cfg | 7 +++++-- cfg/cfgogl/nextmod/shared_plugins.cfg | 2 +- cfg/cfgogl/nextmod/shared_settings.cfg | 12 +++++++----- cfg/cfgogl/pmelite/confogl_plugins.cfg | 2 +- cfg/cfgogl/pmelite/pmelite.cfg | 9 +++++++-- cfg/cfgogl/zonehunters/shared_plugins.cfg | 2 +- cfg/cfgogl/zonehunters/shared_settings.cfg | 12 +++++++----- cfg/cfgogl/zonemod/shared_plugins.cfg | 2 +- cfg/cfgogl/zonemod/shared_settings.cfg | 12 +++++++----- cfg/cfgogl/zoneretro/shared_plugins.cfg | 2 +- cfg/cfgogl/zoneretro/shared_settings.cfg | 16 +++++++++------- 20 files changed, 78 insertions(+), 51 deletions(-) diff --git a/cfg/cfgogl/acemodrv/shared_plugins.cfg b/cfg/cfgogl/acemodrv/shared_plugins.cfg index 29cbcadad3..aa81b31a11 100644 --- a/cfg/cfgogl/acemodrv/shared_plugins.cfg +++ b/cfg/cfgogl/acemodrv/shared_plugins.cfg @@ -104,7 +104,7 @@ sm plugins load optional/l4d2_ladder_rambos.smx // Static shotgun spread //---------------------- sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx //--------------------------------------------- // Merged FF Plugins, needs to be loaded here diff --git a/cfg/cfgogl/acemodrv/shared_settings.cfg b/cfg/cfgogl/acemodrv/shared_settings.cfg index becc226da8..8b889fbeff 100644 --- a/cfg/cfgogl/acemodrv/shared_settings.cfg +++ b/cfg/cfgogl/acemodrv/shared_settings.cfg @@ -268,9 +268,9 @@ confogl_addcvar sm2_permament_health_proportion 0.75 confogl_addcvar sm2_pills_hp_factor 6.0 confogl_addcvar sm2_pills_max_bonus 30 -///////////////////////////// -// [Static shotgun spread] // -///////////////////////////// +////////////////////////////// +///// [Shotgun settings] ///// +////////////////////////////// // Pump settings sm_weapon pumpshotgun damage 14 @@ -284,10 +284,12 @@ sm_weapon shotgun_chrome bullets 16 sm_weapon shotgun_chrome scatterpitch 3.25 sm_weapon shotgun_chrome scatteryaw 3.25 -// First ring settings -confogl_addcvar sgspread_ring1_bullets 8 +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 3 confogl_addcvar sgspread_center_pellet 0 +confogl_addcvar sgspread_all_pellets_offset 0 // Common damage settings confogl_addcvar z_shotgun_bonus_damage_range 75 diff --git a/cfg/cfgogl/apex/shared_plugins.cfg b/cfg/cfgogl/apex/shared_plugins.cfg index a1ab438c51..0a75d8ac33 100644 --- a/cfg/cfgogl/apex/shared_plugins.cfg +++ b/cfg/cfgogl/apex/shared_plugins.cfg @@ -102,7 +102,7 @@ sm plugins load optional/l4d2_ladder_rambos.smx // Static shotgun spread //---------------------- sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx //--------------------------------------------- // Merged FF Plugins, needs to be loaded here diff --git a/cfg/cfgogl/apex/shared_settings.cfg b/cfg/cfgogl/apex/shared_settings.cfg index a825aa9cac..85a9626aa2 100644 --- a/cfg/cfgogl/apex/shared_settings.cfg +++ b/cfg/cfgogl/apex/shared_settings.cfg @@ -237,9 +237,9 @@ confogl_addcvar SM_adrenaline_health_buffer 25 confogl_addcvar SM_mapmulti 1 confogl_addcvar SM_custommaxdistance 1 -///////////////////////////// -// [Static shotgun spread] // -///////////////////////////// +////////////////////////////// +///// [Shotgun settings] ///// +////////////////////////////// // Pump settings sm_weapon pumpshotgun damage 15 @@ -254,10 +254,12 @@ sm_weapon shotgun_chrome scatterpitch 3.25 sm_weapon shotgun_chrome scatteryaw 3.25 sm_weapon shotgun_chrome rangemod 0.75 -// First ring settings -confogl_addcvar sgspread_ring1_bullets 8 +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 3 confogl_addcvar sgspread_center_pellet 0 +confogl_addcvar sgspread_all_pellets_offset 0 // Common damage settings confogl_addcvar z_shotgun_bonus_damage_range 75 diff --git a/cfg/cfgogl/deadman/confogl_plugins.cfg b/cfg/cfgogl/deadman/confogl_plugins.cfg index 4c85616468..841d6d0021 100644 --- a/cfg/cfgogl/deadman/confogl_plugins.cfg +++ b/cfg/cfgogl/deadman/confogl_plugins.cfg @@ -90,7 +90,7 @@ sm plugins load optional/caster_system.smx // Static shotgun spread sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx // Confogl sm plugins load optional/readyup.smx diff --git a/cfg/cfgogl/deadman/deadman.cfg b/cfg/cfgogl/deadman/deadman.cfg index 9bf5f89bf5..c73a6a5f00 100644 --- a/cfg/cfgogl/deadman/deadman.cfg +++ b/cfg/cfgogl/deadman/deadman.cfg @@ -137,7 +137,7 @@ l4d2_addweaponrule sniper_awp hunting_rifle l4d2_addweaponrule pumpshotgun shotgun_chrome l4d2_addweaponrule smg smg_silenced -// [Static shotgun spread] +// [Shotgun settings] sm_weapon pumpshotgun damage 14 sm_weapon pumpshotgun bullets 20 sm_weapon pumpshotgun scatterpitch 4.5 @@ -146,9 +146,14 @@ sm_weapon shotgun_chrome damage 16 sm_weapon shotgun_chrome bullets 16 sm_weapon shotgun_chrome scatterpitch 3.5 sm_weapon shotgun_chrome scatteryaw 3.5 + +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 confogl_addcvar sgspread_center_pellet 0 -confogl_addcvar sgspread_ring1_bullets 8 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 3 +confogl_addcvar sgspread_all_pellets_offset 0 + confogl_addcvar z_shotgun_bonus_damage_range 75 // [SMG Spread Improvement] diff --git a/cfg/cfgogl/eq/shared_plugins.cfg b/cfg/cfgogl/eq/shared_plugins.cfg index 938e438c03..460460661f 100644 --- a/cfg/cfgogl/eq/shared_plugins.cfg +++ b/cfg/cfgogl/eq/shared_plugins.cfg @@ -107,7 +107,7 @@ sm plugins load optional/l4d2_ladder_rambos.smx // Static shotgun spread //---------------------- sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx //--------------------------------------------- // Merged FF Plugins, needs to be loaded here diff --git a/cfg/cfgogl/eq/shared_settings.cfg b/cfg/cfgogl/eq/shared_settings.cfg index 6366812338..eaf64507a1 100644 --- a/cfg/cfgogl/eq/shared_settings.cfg +++ b/cfg/cfgogl/eq/shared_settings.cfg @@ -250,7 +250,7 @@ confogl_addcvar sm2_permament_health_proportion 0.75 confogl_addcvar sm2_pills_hp_factor 6.0 confogl_addcvar sm2_pills_max_bonus 30 -// [Static shotgun spread] +// [Shotgun settings] // Pump settings sm_weapon pumpshotgun damage 14 sm_weapon pumpshotgun bullets 20 @@ -263,10 +263,12 @@ sm_weapon shotgun_chrome bullets 16 sm_weapon shotgun_chrome scatterpitch 3.0 sm_weapon shotgun_chrome scatteryaw 3.0 -// First ring settings -confogl_addcvar sgspread_ring1_bullets 8 +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 3 confogl_addcvar sgspread_center_pellet 0 +confogl_addcvar sgspread_all_pellets_offset 0 // Common damage settings confogl_addcvar z_shotgun_bonus_damage_range 75 diff --git a/cfg/cfgogl/neomod/shared_plugins.cfg b/cfg/cfgogl/neomod/shared_plugins.cfg index 789ba7ac6d..c46fa30a10 100644 --- a/cfg/cfgogl/neomod/shared_plugins.cfg +++ b/cfg/cfgogl/neomod/shared_plugins.cfg @@ -111,7 +111,7 @@ sm plugins load optional/l4d2_melee_spawn_control.smx // Static shotgun spread //---------------------- sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx //--------------------------------------------- // Merged FF Plugins, needs to be loaded here diff --git a/cfg/cfgogl/neomod/shared_settings.cfg b/cfg/cfgogl/neomod/shared_settings.cfg index f91bb67f2a..1d2a3ca8a1 100644 --- a/cfg/cfgogl/neomod/shared_settings.cfg +++ b/cfg/cfgogl/neomod/shared_settings.cfg @@ -295,10 +295,13 @@ sm_weapon smg_silenced reloadduration 1.88 ///////////////////////////////// // [Shotgun Tweaks 'n Stuff] // ///////////////////////////////// -// Static shotgun spread -confogl_addcvar sgspread_ring1_bullets 8 + +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 2 // Does not affect the actual first ring, just the distance between ring 1 and 2 (Only affects pump) confogl_addcvar sgspread_center_pellet 0 +confogl_addcvar sgspread_all_pellets_offset 0 sm_weapon pumpshotgun damage 17 sm_weapon pumpshotgun bullets 16 diff --git a/cfg/cfgogl/nextmod/shared_plugins.cfg b/cfg/cfgogl/nextmod/shared_plugins.cfg index c2f9e3e523..f0411947ea 100644 --- a/cfg/cfgogl/nextmod/shared_plugins.cfg +++ b/cfg/cfgogl/nextmod/shared_plugins.cfg @@ -82,7 +82,7 @@ sm plugins load optional/l4d2_fix_spawn_order.smx sm plugins load optional/l4d2_sounds_blocker.smx sm plugins load optional/autopause.smx sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx sm plugins load optional/l4d2_godframes_control_merge.smx sm plugins load optional/l4d2_getup_fixes.smx sm plugins load optional/l4d2_hittable_control.smx diff --git a/cfg/cfgogl/nextmod/shared_settings.cfg b/cfg/cfgogl/nextmod/shared_settings.cfg index 5631338280..bda2da5a9e 100644 --- a/cfg/cfgogl/nextmod/shared_settings.cfg +++ b/cfg/cfgogl/nextmod/shared_settings.cfg @@ -292,9 +292,9 @@ confogl_addcvar l4d2_MITSR_RiotShield 0 confogl_addcvar l4d2_MITSR_Tonfa 0 confogl_addcvar l4d2_MITSR_FryingPan 0 -///////////////////////////// -// [Static shotgun spread] // -///////////////////////////// +////////////////////////////// +///// [Shotgun settings] ///// +////////////////////////////// // Pump settings sm_weapon pumpshotgun damage 15 @@ -308,10 +308,12 @@ sm_weapon shotgun_chrome bullets 16 sm_weapon shotgun_chrome scatterpitch 3.25 sm_weapon shotgun_chrome scatteryaw 3.25 -// First ring settings -confogl_addcvar sgspread_ring1_bullets 8 +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 3 confogl_addcvar sgspread_center_pellet 0 +confogl_addcvar sgspread_all_pellets_offset 0 // Common damage settings confogl_addcvar z_shotgun_bonus_damage_range 60 diff --git a/cfg/cfgogl/pmelite/confogl_plugins.cfg b/cfg/cfgogl/pmelite/confogl_plugins.cfg index 42ca010eea..e2bfdd59c4 100644 --- a/cfg/cfgogl/pmelite/confogl_plugins.cfg +++ b/cfg/cfgogl/pmelite/confogl_plugins.cfg @@ -97,7 +97,7 @@ sm plugins load optional/l4d2_slowdown_control.smx // Static shotgun spread sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx sm plugins load optional/l4d2_melee_shenanigans.smx sm plugins load optional/fix_engine.smx sm plugins load optional/l4d2_profitless_ai_tank.smx diff --git a/cfg/cfgogl/pmelite/pmelite.cfg b/cfg/cfgogl/pmelite/pmelite.cfg index 617a8afa9b..80e7bcead1 100644 --- a/cfg/cfgogl/pmelite/pmelite.cfg +++ b/cfg/cfgogl/pmelite/pmelite.cfg @@ -228,7 +228,7 @@ l4d2_addweaponrule sniper_awp hunting_rifle l4d2_addweaponrule grenade_launcher pistol l4d2_addweaponrule rifle_m60 pistol_magnum -// [Static shotgun spread] +// [Shotgun settings] sm_weapon pumpshotgun damage 14 sm_weapon pumpshotgun bullets 20 sm_weapon pumpshotgun scatterpitch 4.5 @@ -237,9 +237,14 @@ sm_weapon shotgun_chrome damage 16 sm_weapon shotgun_chrome bullets 16 sm_weapon shotgun_chrome scatterpitch 3.5 sm_weapon shotgun_chrome scatteryaw 3.5 + +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 confogl_addcvar sgspread_center_pellet 0 -confogl_addcvar sgspread_ring1_bullets 8 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 3 +confogl_addcvar sgspread_all_pellets_offset 0 + confogl_addcvar z_shotgun_bonus_damage_range 75 // [SMG Spread Improvement] diff --git a/cfg/cfgogl/zonehunters/shared_plugins.cfg b/cfg/cfgogl/zonehunters/shared_plugins.cfg index 49f58a7ce6..e5a32b68d6 100644 --- a/cfg/cfgogl/zonehunters/shared_plugins.cfg +++ b/cfg/cfgogl/zonehunters/shared_plugins.cfg @@ -105,7 +105,7 @@ sm plugins load optional/weapon_loadout_vote.smx // Static shotgun spread //---------------------- sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx //--------------------------------------------- // Merged FF Plugins, needs to be loaded here diff --git a/cfg/cfgogl/zonehunters/shared_settings.cfg b/cfg/cfgogl/zonehunters/shared_settings.cfg index 0613266550..ba09ca9579 100644 --- a/cfg/cfgogl/zonehunters/shared_settings.cfg +++ b/cfg/cfgogl/zonehunters/shared_settings.cfg @@ -233,14 +233,16 @@ confogl_addcvar sm2_pills_max_bonus 100 // [l4d2_tank_horde_monitor.smx] confogl_addcvar l4d2_tank_bypass_extra_flow 1500 -///////////////////////////// -// [Static shotgun spread] // -///////////////////////////// +////////////////////////////// +///// [Shotgun settings] ///// +////////////////////////////// -// First ring settings -confogl_addcvar sgspread_ring1_bullets 8 +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 2 // Does not affect the actual first ring, just the distance between ring 1 and 2 (Only affects pump) confogl_addcvar sgspread_center_pellet 0 +confogl_addcvar sgspread_all_pellets_offset 0 ///////////////////////////// // [SMG Tweaks 'n Stuff] // diff --git a/cfg/cfgogl/zonemod/shared_plugins.cfg b/cfg/cfgogl/zonemod/shared_plugins.cfg index 62a9f4d76f..fd44b2ea37 100644 --- a/cfg/cfgogl/zonemod/shared_plugins.cfg +++ b/cfg/cfgogl/zonemod/shared_plugins.cfg @@ -111,7 +111,7 @@ sm plugins load optional/l4d2_ledgeblock.smx // Static shotgun spread //---------------------- sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx //--------------------------------------------- // Merged FF Plugins, needs to be loaded here diff --git a/cfg/cfgogl/zonemod/shared_settings.cfg b/cfg/cfgogl/zonemod/shared_settings.cfg index eea14f96bc..570ec9d1f6 100644 --- a/cfg/cfgogl/zonemod/shared_settings.cfg +++ b/cfg/cfgogl/zonemod/shared_settings.cfg @@ -295,14 +295,16 @@ confogl_addcvar sm2_pills_max_bonus 100 // [l4d2_tank_horde_monitor.smx] confogl_addcvar l4d2_tank_bypass_extra_flow 1500 -///////////////////////////// -// [Static shotgun spread] // -///////////////////////////// +////////////////////////////// +///// [Shotgun settings] ///// +////////////////////////////// -// First ring settings -confogl_addcvar sgspread_ring1_bullets 8 +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 confogl_addcvar sgspread_ring1_factor 2 // Does not affect the actual first ring, just the distance between ring 1 and 2 (If there is a second ring) confogl_addcvar sgspread_center_pellet 1 +confogl_addcvar sgspread_all_pellets_offset 0 ///////////////////////////// diff --git a/cfg/cfgogl/zoneretro/shared_plugins.cfg b/cfg/cfgogl/zoneretro/shared_plugins.cfg index cac2601f31..9c9726409f 100644 --- a/cfg/cfgogl/zoneretro/shared_plugins.cfg +++ b/cfg/cfgogl/zoneretro/shared_plugins.cfg @@ -102,7 +102,7 @@ sm plugins load optional/l4d2_tankrage.smx // Static shotgun spread //---------------------- sm plugins load optional/l4d2_weapon_attributes.smx -sm plugins load optional/l4d2_static_shotgun_spread.smx +sm plugins load optional/l4d2_sharedrandom_overrides.smx //--------------------------------------------- // Merged FF Plugins, needs to be loaded here diff --git a/cfg/cfgogl/zoneretro/shared_settings.cfg b/cfg/cfgogl/zoneretro/shared_settings.cfg index bb3f6ab938..ad36bfc526 100644 --- a/cfg/cfgogl/zoneretro/shared_settings.cfg +++ b/cfg/cfgogl/zoneretro/shared_settings.cfg @@ -227,14 +227,16 @@ confogl_addcvar sm2_pills_max_bonus 60 // [l4d2_tank_horde_monitor.smx] confogl_addcvar l4d2_tank_bypass_extra_flow 1500 -///////////////////////////// -// [Static shotgun spread] // -///////////////////////////// - -// First ring settings -confogl_addcvar sgspread_ring1_bullets 8 -confogl_addcvar sgspread_ring1_factor 2 // Does not affect the actual first ring, just the distance between ring 1 and 2 (Only affects pump) +////////////////////////////// +///// [Shotgun settings] ///// +////////////////////////////// + +// [l4d2_sharedrandom_overrides] Shotgun spread settings +confogl_addcvar sgspread_pattern_type 1 +confogl_addcvar sgspread_ring1_pellets 8 +confogl_addcvar sgspread_ring1_factor 2 // Does not affect the actual first ring, just the distance between ring 1 and 2 (Only affects pump) confogl_addcvar sgspread_center_pellet 0 +confogl_addcvar sgspread_all_pellets_offset 0 ///////////////////////////// // [SMG Tweaks 'n Stuff] // From dc8bf86fc7eebf341c67f72dccac7855a0ddf5fb Mon Sep 17 00:00:00 2001 From: A1mDev <33463136+A1mDev@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:31:59 +0700 Subject: [PATCH 4/5] Minor fixes --- .../sourcemod/gamedata/l4d2_sharedrandom_overrides.txt | 8 -------- .../sourcemod/scripting/l4d2_sharedrandom_overrides.sp | 10 ++++------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt b/addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt index bc3d756f68..5ea83fd4ff 100644 --- a/addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt +++ b/addons/sourcemod/gamedata/l4d2_sharedrandom_overrides.txt @@ -1,11 +1,3 @@ -/** - * Author: A1m` - * Work summary: - * - Documented parameters, return values, and calling conventions for use with DHooks - * - Described how to locate these functions on Windows - * - Found function signatures on both Linux and Windows - * - Created a memory patch for the central pellet -**/ "Games" { "#default" diff --git a/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp b/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp index 2668fff6b8..dac4368844 100644 --- a/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp +++ b/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp @@ -6,7 +6,7 @@ * Implements a configurable, non-random shotgun pellet pattern: * - Custom rings * - Radius factors - * - Hybrid spread (Maybe in the future. :D) + * - Hybrid spread (Maybe in the future) * Replaces the old ASM patch method with a detour hook. * Fixed the spread direction calculation: it now uses the full 0 – < 180 degree range, unlike the old ASM patch. * The old ASM patch used a spread direction from -22° to ~154°, if the center pellet was activated, or from 0° to ~134° if not. @@ -29,7 +29,6 @@ * A detailed analysis of the ASM patch from the file `code_patcher.txt` (gamedata file) was performed. * Reverse engineering and analysis of the game code, function `CTerrorPlayer::FireBullets`and accompanying code * Investigating the behavior of the `l4d2_static_shotgun_spread` plugin. - * All research and implementation were performed by A1m`. * * Bugs & Todo: * Fix: Large values of the 'sgspread_prediction_seed' cvar do not match the specified ones because the cvar value is stored as a float and breaks when retrieved due to rounding. @@ -59,7 +58,7 @@ #include #include -#include // #include +#include //#include #include #define DEBUG 0 @@ -938,7 +937,7 @@ int main() float flVerticalSpreadScale = 1.0f; if (flHorizontalSpread != 0.0f && flVerticalSpread != 0.0f) { - // 3.25 / 3.25 = 1.0f for chrome anyway, omg :D + // 3.25 / 3.25 = 1.0f for chrome anyway, omg flVerticalSpreadScale = flVerticalSpread / flHorizontalSpread; } @@ -1010,7 +1009,6 @@ int main() return 0; } -// Try to make your code =) float SharedRandomFloat(const char* sSharedName, float flMin, float flMax, int iAdditionSeed) { int iPelletsInRing1 = iNumPelletsRing1 + (bCenterPellet ? 1 : 0); @@ -1085,6 +1083,6 @@ float SharedRandomFloat(const char* sSharedName, float flMin, float flMax, int i return (fAngleStep * float(iIndexInRing)); } - return 0.0f; // The game returns a random value, but we return 0.0f =) + return 0.0f; // The game returns a random value, but we return 0.0f } #endif From 006030e51bbbd8dd9b9789df7c5886b1247c86e9 Mon Sep 17 00:00:00 2001 From: A1mDev <33463136+A1mDev@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:37:21 +0700 Subject: [PATCH 5/5] Fix build --- addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp b/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp index dac4368844..53c7160a27 100644 --- a/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp +++ b/addons/sourcemod/scripting/l4d2_sharedrandom_overrides.sp @@ -560,7 +560,6 @@ MRESReturn OverrideSharedRandom(const char[] sSharedName, float &flMin, float &f float fOffsetPercent = flMax * (g_hCvarRing2MaxOffsetPercent.FloatValue / 100.0); flMin = flMax - fOffsetPercent; - flMax = flMax; return MRES_ChangedHandled; // The function will return a random value itself }