diff --git a/addons/sourcemod/gamedata/si_pool.txt b/addons/sourcemod/gamedata/si_pool.txt new file mode 100644 index 000000000..7aa7d5d31 --- /dev/null +++ b/addons/sourcemod/gamedata/si_pool.txt @@ -0,0 +1,114 @@ +/* + * @Author: 我是派蒙啊 + * @Last Modified by: 我是派蒙啊 + * @Create Date: 2024-02-17 16:48:01 + * @Last Modified time: 2024-03-25 19:03:25 + * @Github: https://github.com/Paimon-Kawaii + */ +"Games" +{ + "left4dead2" + { + "Addresses" + { + "NextBotCreatePlayerBot.jumptable" + { + "windows" + { + "signature" "CTerrorPlayer::ReplaceWithBot.jumptable" + "offset" "7" + } + } + } + + "Signatures" + { + "CTerrorPlayer::ReplaceWithBot.jumptable" + { + "library" "server" + // Switch jump with a bunch of cases matching... + // PUSH rel32 + // CALL rel32 + // JUMP rel8 + // There are acutally 2 matches of this in the windows binary, + // but they appear to be the same functionality--so it doesn't matter which we get. + /* FF 24 85 ? ? ? ? 68 ? ? ? ? E8 ? ? ? ? EB ? 68 ? ? ? ? E8 ? ? ? ? EB ? 68 ? ? ? ? E8 ? ? ? ? EB ? 68 ? ? ? ? E8 ? ? ? ? EB ? 68 ? ? ? ? E8 ? ? ? ? EB ? 68 ? ? ? ? E8 ? ? ? ? EB ? 68 ? ? ? ? E8 */ + "windows" "\xFF\x24\x85\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xEB\x2A\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xEB\x2A\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xEB\x2A\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xEB\x2A\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xEB\x2A\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xEB\x2A\x68\x2A\x2A\x2A\x2A\xE8" + } + + "NextBotCreatePlayerBot" + { + "library" "server" + "linux" "@_Z22NextBotCreatePlayerBotI6HunterEPT_PKc" + } + + "NextBotCreatePlayerBot" + { + "library" "server" + "linux" "@_Z22NextBotCreatePlayerBotI6JockeyEPT_PKc" + } + "NextBotCreatePlayerBot" + { + "library" "server" + "linux" "@_Z22NextBotCreatePlayerBotI7SpitterEPT_PKc" + } + "NextBotCreatePlayerBot" + { + "library" "server" + "linux" "@_Z22NextBotCreatePlayerBotI7ChargerEPT_PKc" + } + "NextBotCreatePlayerBot" + { + "library" "server" + "linux" "@_Z22NextBotCreatePlayerBotI6SmokerEPT_PKc" + } + "NextBotCreatePlayerBot" + { + "library" "server" + "linux" "@_Z22NextBotCreatePlayerBotI6BoomerEPT_PKc" + } + + /* + * CTerrorPlayer::SetClass(CBaseEntity *, int) + */ + "CTerrorPlayer::SetClass" + { + "library" "server" + "linux" "@_ZN13CTerrorPlayer8SetClassE15ZombieClassType" + "windows" "\x55\x8B\x2A\x56\x8B\x2A\xE8\x2A\x2A\x2A\x2A\x83\x2A\x2A\x0F\x85\x2A\x2A\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x40\xA3" + /* 55 8B ? 56 8B ? E8 ? ? ? ? 83 ? ? 0F 85 ? ? ? ? A1 ? ? ? ? 40 A3 */ // Updated by SilverShot. + /* Search "weapon_smoker_claw" */ + } + + /* + * CBaseAbility::CreateForPlayer(CBaseAbility *this, CTerrorPlayer *) + */ + "CBaseAbility::CreateForPlayer" + { + "library" "server" + "linux" "@_ZN12CBaseAbility15CreateForPlayerEP13CTerrorPlayer" + "windows" "\x55\x8B\x2A\x83\x2A\x2A\x56\x8B\x2A\x2A\x85\x2A\x0F\x84\x2A\x2A\x2A\x2A\x8B\x2A\xE8\x2A\x2A\x2A\x2A\x83" + /* 55 8B ? 83 ? ? 56 8B ? ? 85 ? 0F 84 ? ? ? ? 8B ? E8 ? ? ? ? 83 */ // Updated by SilverShot. + /* Search "ability_tongue" */ + } + + /* CCSPlayer::State_Transition(CSPlayerState) */ + "CCSPlayer::State_Transition" + { + "library" "server" + "linux" "@_ZN9CCSPlayer16State_TransitionE13CSPlayerState" + "windows" "\x55\x8B\xEC\x56\x8B\xF1\x8B\x86\x2A\x2A\x2A\x2A\x57\x8B\x7D\x2A\x85\xC0\x74\x2A\x83" + /* 55 8B EC 56 8B F1 8B 86 ? ? ? ? 57 8B 7D ? 85 C0 74 ? 83 */ + } + + /* CTerrorPlayer::RoundRespawn */ + "CTerrorPlayer::RoundRespawn" + { + "library" "server" + "linux" "@_ZN13CTerrorPlayer12RoundRespawnEv" + "windows" "\x56\x8B\xF1\xE8\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x84\xC0\x75" + /* 56 8B F1 E8 ? ? ? ? E8 ? ? ? ? 84 C0 75 */ + } + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/plugins/optional/AnneHappy/infected_control.smx b/addons/sourcemod/plugins/optional/AnneHappy/infected_control.smx index 11357a9d3..7b304ad5a 100644 Binary files a/addons/sourcemod/plugins/optional/AnneHappy/infected_control.smx and b/addons/sourcemod/plugins/optional/AnneHappy/infected_control.smx differ diff --git a/addons/sourcemod/plugins/optional/AnneHappy/si_pool.smx b/addons/sourcemod/plugins/optional/AnneHappy/si_pool.smx new file mode 100644 index 000000000..4052f7d47 Binary files /dev/null and b/addons/sourcemod/plugins/optional/AnneHappy/si_pool.smx differ diff --git a/addons/sourcemod/scripting/AnneHappy/infected_control.sp b/addons/sourcemod/scripting/AnneHappy/infected_control.sp index 338e8bfa8..6e47624c4 100644 --- a/addons/sourcemod/scripting/AnneHappy/infected_control.sp +++ b/addons/sourcemod/scripting/AnneHappy/infected_control.sp @@ -10,249 +10,280 @@ #include #include #include +#include -#define CVAR_FLAG FCVAR_NOTIFY -#define TEAM_SURVIVOR 2 -#define TEAM_INFECTED 3 +#define CVAR_FLAG FCVAR_NOTIFY +#define TEAM_SURVIVOR 2 +#define TEAM_INFECTED 3 // 数据 -#define NAV_MESH_HEIGHT 20.0 -#define PLAYER_HEIGHT 72.0 -#define PLAYER_CHEST 45.0 -#define HIGHERPOS 300.0 -#define HIGHERPOSADDDISTANCE 300.0 +#define NAV_MESH_HEIGHT 20.0 +#define PLAYER_HEIGHT 72.0 +#define PLAYER_CHEST 45.0 +#define HIGHERPOS 300.0 +#define HIGHERPOSADDDISTANCE 300.0 #define INCAPSURVIVORCHECKDIS 500.0 -#define NORMALPOSMULT 1.4 +#define NORMALPOSMULT 1.4 // 启用特感类型 -#define ENABLE_SMOKER (1 << 0) -#define ENABLE_BOOMER (1 << 1) -#define ENABLE_HUNTER (1 << 2) -#define ENABLE_SPITTER (1 << 3) -#define ENABLE_JOCKEY (1 << 4) -#define ENABLE_CHARGER (1 << 5) +#define ENABLE_SMOKER (1 << 0) +#define ENABLE_BOOMER (1 << 1) +#define ENABLE_HUNTER (1 << 2) +#define ENABLE_SPITTER (1 << 3) +#define ENABLE_JOCKEY (1 << 4) +#define ENABLE_CHARGER (1 << 5) // Spitter吐口水之后能传送的时间 -#define SPIT_INTERVAL 2.0 +#define SPIT_INTERVAL 2.0 //确认为跑男的距离 -#define RushManDistance 1200.0 - -stock const char InfectedName[10][] = -{ - "common", - "smoker", - "boomer", - "hunter", - "spitter", - "jockey", - "charger", - "witch", - "tank", - "survivor" +#define RushManDistance 1200.0 + +stock const char InfectedName[10][] = { + "common", + "smoker", + "boomer", + "hunter", + "spitter", + "jockey", + "charger", + "witch", + "tank", + "survivor" }; - #if (DEBUG) char sLogFile[PLATFORM_MAX_PATH] = "addons/sourcemod/logs/infected_control.txt"; #endif + // 插件基本信息,根据 GPL 许可证条款,需要修改插件请勿修改此信息! -public Plugin myinfo = +public Plugin myinfo = { - name = "Direct InfectedSpawn", - author = "Caibiii, 夜羽真白,东", - description = "特感刷新控制,传送落后特感", - version = "2023.09.04", - url = "https://github.com/fantasylidong/CompetitiveWithAnne" + name = "Direct InfectedSpawn", + author = "Caibiii, 夜羽真白,东", + description = "特感刷新控制,传送落后特感", + version = "2023.09.04", + url = "https://github.com/fantasylidong/CompetitiveWithAnne", + + } // Cvars -ConVar - g_hSpawnDistanceMin, //特感最低生成距离 - g_hSpawnDistanceMax, //特感最大生成距离 - g_hTeleportSi, //是否打开特感传送 -// g_hTeleportDistance, - g_hSiLimit, //一波特感生成数量上限 - g_hSiInterval, //每波特感生成基础间隔 - g_hMaxPlayerZombies, //设置导演系统的特感数量上限 - g_hTeleportCheckTime, //几秒不被看到后可以传送 - g_hEnableSIoption, //设置生成哪几种特感 - g_hAllChargerMode, //是否为全牛模式 - g_hAutoSpawnTimeControl, //自动设置增加时间,加到基础间隔之上,这项不打开,增加时间默认为g_hSiInterval/2.打开为特感数量小于g_hSiLimit/3 + 1后再过基准时间开始刷特。 - //但是这个值大于g_hSiInterval/2也会开始强制刷特 - g_hAddDamageToSmoker, //被smoker拉的时候是否对smoker是否进行增伤 - g_hIgnoreIncappedSurvivorSight, //是否忽视掉倒地生还者视线 - g_hVsBossFlowBuffer, - g_hAllHunterMode; +ConVar g_hSpawnDistanceMin, // 特感最低生成距离 + g_hSpawnDistanceMax, // 特感最大生成距离 + g_hTeleportSi, // 是否打开特感传送 + // g_hTeleportDistance, + g_hSiLimit, // 一波特感生成数量上限 + g_hSiInterval, // 每波特感生成基础间隔 + g_hMaxPlayerZombies, // 设置导演系统的特感数量上限 + g_hTeleportCheckTime, // 几秒不被看到后可以传送 + g_hEnableSIoption, // 设置生成哪几种特感 + g_hAllChargerMode, // 是否为全牛模式 + g_hAutoSpawnTimeControl, // 自动设置增加时间,加到基础间隔之上,这项不打开,增加时间默认为g_hSiInterval/2.打开为特感数量小于g_hSiLimit/3 + 1后再过基准时间开始刷特。 + // 但是这个值大于g_hSiInterval/2也会开始强制刷特 + g_hAddDamageToSmoker, // 被smoker拉的时候是否对smoker是否进行增伤 + g_hIgnoreIncappedSurvivorSight, // 是否忽视掉倒地生还者视线 + g_hVsBossFlowBuffer, g_hAllHunterMode; // Ints -int - g_iSiLimit, //特感数量 - g_iRushManIndex, //跑男id - g_iWaveTime, //Debug时输出这是第几波刷特 - g_iLastSpawnTime, //离上次刷特过去了多久 - g_iTotalSINum = 0, //总共还活着的特感 - g_iEnableSIoption = 63, //可生成的特感种类 - g_iTeleportCheckTime = 5, //特感传送要求的不被看到的次数(1s检查一次) - g_iSINum[6] = {0}, //记录当前还存活的特感数量 - g_ArraySIlimit[6] = {0}, //记录去除队列里特感数量后还能生成的特感 - g_iTeleCount[MAXPLAYERS + 1] = {0}, //每个特感传送的不被看到次数 - g_iTargetSurvivor = -1, //OnGameFrame参数里,以该目标生成生成网络,寻找生成目标 - g_iQueueIndex = 0, //当前生成队列长度 - g_iTeleportIndex = 0, //当前传送队列长度 - g_iSpawnMaxCount = 0, //当前可生成特感数量 - g_iSurvivorNum = 0, //活着的生还者数量 - g_iSurvivors[MAXPLAYERS + 1] = {0}; //活着生还者的索引 +int + g_iSiLimit, //特感数量 + g_iRushManIndex, //跑男id + g_iWaveTime, // Debug时输出这是第几波刷特 + g_iLastSpawnTime, //离上次刷特过去了多久 + g_iTotalSINum = 0, //总共还活着的特感 + g_iEnableSIoption = 63, //可生成的特感种类 + g_iTeleportCheckTime = 5, //特感传送要求的不被看到的次数(1s检查一次) + g_iSINum[6] = { 0 }, //记录当前还存活的特感数量 + g_ArraySIlimit[6] = { 0 }, //记录去除队列里特感数量后还能生成的特感 + g_iTeleCount[MAXPLAYERS + 1] = { 0 }, //每个特感传送的不被看到次数 + g_iTargetSurvivor = -1, // OnGameFrame参数里,以该目标生成生成网络,寻找生成目标 + g_iQueueIndex = 0, //当前生成队列长度 + g_iTeleportIndex = 0, //当前传送队列长度 + g_iSpawnMaxCount = 0, //当前可生成特感数量 + g_iSurvivorNum = 0, //活着的生还者数量 + g_iSurvivors[MAXPLAYERS + 1] = { 0 }; //活着生还者的索引 // Floats -float - g_fSpitterSpitTime[MAXPLAYERS + 1], //Spitter吐口水时间 - g_fSpawnDistanceMin, //特感的最小生成距离 - g_fSpawnDistanceMax, //特感的最大生成距离 - g_fSpawnDistance, //特感的当前生成距离 -// g_fTeleportDistanceMin, //特感传送距离生还的最小距离 - g_fTeleportDistance, //特感当前传送生成距离 - g_fLastSISpawnStartTime, //上一波特感生成时间 - g_fUnpauseNextSpawnTime, //因为暂停记录下下一波特感的时间,方便解除暂停时创建处理线程 - g_fSiInterval; //特感的生成时间间隔 +float + g_fSpitterSpitTime[MAXPLAYERS + 1], // Spitter吐口水时间 + g_fSpawnDistanceMin, //特感的最小生成距离 + g_fSpawnDistanceMax, //特感的最大生成距离 + g_fSpawnDistance, //特感的当前生成距离 + // g_fTeleportDistanceMin, //特感传送距离生还的最小距离 + g_fTeleportDistance, //特感当前传送生成距离 + g_fLastSISpawnStartTime, //上一波特感生成时间 + g_fUnpauseNextSpawnTime, //因为暂停记录下下一波特感的时间,方便解除暂停时创建处理线程 + g_fSiInterval; //特感的生成时间间隔 // Bools -bool - g_bTeleportSi, //是否开启特感传送检测 - g_bPickRushMan, //是否针对跑男 - g_bShouldCheck, //是否开启时间检测 - g_bAutoSpawnTimeControl, //是否开启自动增加时间 - g_bAddDamageToSmoker, //是否对smoker增伤(一般alone模式开启) - g_bIgnoreIncappedSurvivorSight, //是否忽略倒地生还者的视线 - g_bIsLate = false, //text插件是否发送开启刷特命令 - g_bSmokerAvailable = false, //ai_smoker_new是否存在 - g_bTargetSystemAvailable = false; //目标选择插件是否存在 +bool + g_bTeleportSi, //是否开启特感传送检测 + g_bPickRushMan, //是否针对跑男 + g_bShouldCheck, //是否开启时间检测 + g_bAutoSpawnTimeControl, //是否开启自动增加时间 + g_bAddDamageToSmoker, //是否对smoker增伤(一般alone模式开启) + g_bIgnoreIncappedSurvivorSight, //是否忽略倒地生还者的视线 + g_bIsLate = false, // text插件是否发送开启刷特命令 + g_bSmokerAvailable = false, // ai_smoker_new是否存在 + g_bSIPoolAvailable = false, //特感池是否存在 + g_bTargetSystemAvailable = false; //目标选择插件是否存在 + // Handle -Handle - g_hCheckShouldSpawnOrNot = INVALID_HANDLE, //1s检测一次是否开启刷特进程的维护进程 - g_hSpawnProcess = INVALID_HANDLE, //刷特 handle - g_hTeleHandle = INVALID_HANDLE, //传送sdk Handle - g_hRushManNotifyForward = INVALID_HANDLE; //检测到跑男提醒Target_limit插件放开单人目标限制 +Handle + g_hCheckShouldSpawnOrNot = INVALID_HANDLE, // 1s检测一次是否开启刷特进程的维护进程 + g_hSpawnProcess = INVALID_HANDLE, //刷特 handle + g_hTeleHandle = INVALID_HANDLE, //传送sdk Handle + g_hRushManNotifyForward = INVALID_HANDLE; //检测到跑男提醒Target_limit插件放开单人目标限制 + // ArrayList -ArrayList - aTeleportQueue, //传送队列 - //aSpawnNavList, //储存特感生成的navid,用来限制特感不能生成在同一块Navid上 - aSpawnQueue; //刷特队列 +ArrayList + aTeleportQueue, //传送队列 + // aSpawnNavList, //储存特感生成的navid,用来限制特感不能生成在同一块Navid上 + aSpawnQueue; //刷特队列 + +SIPool + g_hSIPool; public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max) { - RegPluginLibrary("infected_control"); - g_hRushManNotifyForward = CreateGlobalForward("OnDetectRushman", ET_Ignore, Param_Cell); - CreateNative("GetNextSpawnTime", Native_GetNextSpawnTime); - return APLRes_Success; + RegPluginLibrary("infected_control"); + g_hRushManNotifyForward = CreateGlobalForward("OnDetectRushman", ET_Ignore, Param_Cell); + CreateNative("GetNextSpawnTime", Native_GetNextSpawnTime); + return APLRes_Success; } - -public any Native_GetNextSpawnTime(Handle plugin, int numParams) +any Native_GetNextSpawnTime(Handle plugin, int numParams) { - float time = 0.0; - //如果刷特进程还不开始,直接返回刷特间隔 - if (g_hSpawnProcess == null) - { - time = g_fSiInterval; - } - else - { - time = g_fSiInterval - (GetGameTime() - g_fLastSISpawnStartTime); - } - Debug_Print("下一波特感生成时间是%.2f秒后", time); - return time; + float time = 0.0; + //如果刷特进程还不开始,直接返回刷特间隔 + if (g_hSpawnProcess == null) + time = g_fSiInterval; + else time = g_fSiInterval - (GetGameTime() - g_fLastSISpawnStartTime); + + Debug_Print("下一波特感生成时间是%.2f秒后", time); + return time; } -public void OnAllPluginsLoaded(){ - g_bTargetSystemAvailable = LibraryExists("si_target_limit"); - g_bSmokerAvailable = LibraryExists("ai_smoker_new"); +public void OnAllPluginsLoaded() +{ + g_bTargetSystemAvailable = LibraryExists("si_target_limit"); + g_bSmokerAvailable = LibraryExists("ai_smoker_new"); + g_bSIPoolAvailable = LibraryExists("si_pool"); } + public void OnLibraryAdded(const char[] name) { - if ( StrEqual(name, "si_target_limit") ) { g_bTargetSystemAvailable = true; } - else if( StrEqual(name, "ai_smoker_new") ) { g_bSmokerAvailable = true; } + if (StrEqual(name, "si_target_limit")) + g_bTargetSystemAvailable = true; + else if (StrEqual(name, "ai_smoker_new")) + g_bSmokerAvailable = true; + else if (StrEqual(name, "si_pool")) + g_bSIPoolAvailable = true; } + public void OnLibraryRemoved(const char[] name) { - if ( StrEqual(name, "si_target_limit") ) { g_bTargetSystemAvailable = false; } - else if( StrEqual(name, "ai_smoker_new") ) { g_bSmokerAvailable = false; } + if (StrEqual(name, "si_target_limit")) + g_bTargetSystemAvailable = false; + else if (StrEqual(name, "ai_smoker_new")) + g_bSmokerAvailable = false; + else if (StrEqual(name, "si_pool")) + g_bSIPoolAvailable = false; } + public void OnPluginStart() { - // CreateConVar - g_hSpawnDistanceMin = CreateConVar("inf_SpawnDistanceMin", "250.0", "特感复活离生还者最近的距离限制", CVAR_FLAG, true, 0.0); - g_hSpawnDistanceMax = CreateConVar("inf_SpawnDistanceMax", "1500.0", "特感复活离生还者最远的距离限制", CVAR_FLAG, true, g_hSpawnDistanceMin.FloatValue); - g_hTeleportSi = CreateConVar("inf_TeleportSi", "1", "是否开启特感距离生还者一定距离将其传送至生还者周围", CVAR_FLAG, true, 0.0, true, 1.0); - g_hTeleportCheckTime = CreateConVar("inf_TeleportCheckTime", "5", "特感几秒后没被看到开始传送", CVAR_FLAG, true, 0.0); - g_hEnableSIoption = CreateConVar("inf_EnableSIoption", "63", "启用生成的特感类型,1 smoker 2 boomer 4 hunter 8 spitter 16 jockey 32 charger,把你想要生成的特感值加起来", CVAR_FLAG, true, 0.0, true, 63.0); - g_hAllChargerMode = CreateConVar("inf_AllChargerMode", "0", "是否是全牛模式", CVAR_FLAG, true, 0.0, true, 1.0); - g_hAllHunterMode = CreateConVar("inf_AllHunterMode", "0", "是否是全猎人模式", CVAR_FLAG, true, 0.0, true, 1.0); - g_hAutoSpawnTimeControl = CreateConVar("inf_EnableAutoSpawnTime", "1", "是否开启自动设置增加时间", CVAR_FLAG, true, 0.0, true, 1.0); - g_hIgnoreIncappedSurvivorSight = CreateConVar("inf_IgnoreIncappedSurvivorSight", "1", "特感传送检测是否被看到的时候是否忽略倒地生还者视线", CVAR_FLAG, true, 0.0, true, 1.0); - g_hAddDamageToSmoker= CreateConVar("inf_AddDamageToSmoker", "0", "单人模式smoker拉人时是否5倍伤害", CVAR_FLAG, true, 0.0, true, 1.0); - //传送会根据这个数值画一个以选定生还者为核心,两边各长inf_TeleportDistance单位距离,高inf_TeleportDistance距离的长方形区域内找复活位置,PS传送最好近一点 - //g_hTeleportDistance = CreateConVar("inf_TeleportDistance", "600.0", "特感传送区域的最小复活大小", CVAR_FLAG, true, g_hSpawnDistanceMin.FloatValue); - g_hSiLimit = CreateConVar("l4d_infected_limit", "6", "一次刷出多少特感", CVAR_FLAG, true, 0.0); - g_hSiInterval = CreateConVar("versus_special_respawn_interval", "16.0", "对抗模式下刷特时间控制", CVAR_FLAG, true, 0.0); - g_hMaxPlayerZombies = FindConVar("z_max_player_zombies"); - g_hVsBossFlowBuffer = FindConVar("versus_boss_buffer"); - SetConVarInt(FindConVar("director_no_specials"), 1); - // HookEvents - HookEvent("player_death", evt_PlayerDeath, EventHookMode_PostNoCopy); - HookEvent("round_start", evt_RoundStart, EventHookMode_PostNoCopy); - HookEvent("finale_win", evt_RoundEnd, EventHookMode_PostNoCopy); - HookEvent("map_transition", evt_RoundEnd, EventHookMode_PostNoCopy); - HookEvent("mission_lost", evt_RoundEnd, EventHookMode_PostNoCopy); - HookEvent("player_hurt", evt_PlayerHurt, EventHookMode_PostNoCopy); - HookEvent("ability_use", evt_GetSpitTime, EventHookMode_PostNoCopy); - HookEvent("player_spawn", evt_PlayerSpawn, EventHookMode_PostNoCopy); - // AddChangeHook - g_hSpawnDistanceMax.AddChangeHook(ConVarChanged_Cvars); - g_hSpawnDistanceMin.AddChangeHook(ConVarChanged_Cvars); - g_hTeleportSi.AddChangeHook(ConVarChanged_Cvars); - g_hTeleportCheckTime.AddChangeHook(ConVarChanged_Cvars); - //g_hTeleportDistance.AddChangeHook(ConVarChanged_Cvars); - g_hSiInterval.AddChangeHook(ConVarChanged_Cvars); - g_hIgnoreIncappedSurvivorSight.AddChangeHook(ConVarChanged_Cvars); - g_hEnableSIoption.AddChangeHook(ConVarChanged_Cvars); - g_hAllChargerMode.AddChangeHook(ConVarChanged_Cvars); - g_hAllHunterMode.AddChangeHook(ConVarChanged_Cvars); - g_hAutoSpawnTimeControl.AddChangeHook(ConVarChanged_Cvars); - g_hAddDamageToSmoker.AddChangeHook(ConVarChanged_Cvars); - g_hSiLimit.AddChangeHook(MaxPlayerZombiesChanged_Cvars); - - // ArrayList - aSpawnQueue = new ArrayList(); - aTeleportQueue = new ArrayList(); - //aSpawnNavList = new ArrayList(); - // GetCvars - GetCvars(); - GetSiLimit(); - // SetConVarBonus - SetConVarBounds(g_hMaxPlayerZombies, ConVarBound_Upper, true, g_hSiLimit.FloatValue); - // Debug - RegAdminCmd("sm_startspawn", Cmd_StartSpawn, ADMFLAG_ROOT, "管理员重置刷特时钟"); - RegAdminCmd("sm_stopspawn", Cmd_StopSpawn, ADMFLAG_ROOT, "管理员重置刷特时钟"); -} - -public void OnPluginEnd() { - if(g_hAllChargerMode.BoolValue){ - FindConVar("z_charger_health").RestoreDefault(); - FindConVar("z_charge_max_speed").RestoreDefault(); - FindConVar("z_charge_start_speed").RestoreDefault(); - FindConVar("z_charger_pound_dmg").RestoreDefault(); - FindConVar("z_charge_max_damage").RestoreDefault(); - FindConVar("z_charge_interval").RestoreDefault(); - } -} - -void TweakSettings() { - if(g_hAllChargerMode.BoolValue){ - FindConVar("z_charger_health").SetFloat(500.0); - FindConVar("z_charge_max_speed").SetFloat(750.0); - FindConVar("z_charge_start_speed").SetFloat(350.0); - FindConVar("z_charger_pound_dmg").SetFloat(10.0); - FindConVar("z_charge_max_damage").SetFloat(6.0); - FindConVar("z_charge_interval").SetFloat(2.0); - } + // CreateConVar + g_hSpawnDistanceMin = CreateConVar("inf_SpawnDistanceMin", "250.0", "特感复活离生还者最近的距离限制", CVAR_FLAG, true, 0.0); + g_hSpawnDistanceMax = CreateConVar("inf_SpawnDistanceMax", "1500.0", "特感复活离生还者最远的距离限制", CVAR_FLAG, true, g_hSpawnDistanceMin.FloatValue); + g_hTeleportSi = CreateConVar("inf_TeleportSi", "1", "是否开启特感距离生还者一定距离将其传送至生还者周围", CVAR_FLAG, true, 0.0, true, 1.0); + g_hTeleportCheckTime = CreateConVar("inf_TeleportCheckTime", "5", "特感几秒后没被看到开始传送", CVAR_FLAG, true, 0.0); + g_hEnableSIoption = CreateConVar("inf_EnableSIoption", "63", "启用生成的特感类型,1 smoker 2 boomer 4 hunter 8 spitter 16 jockey 32 charger,把你想要生成的特感值加起来", CVAR_FLAG, true, 0.0, true, 63.0); + g_hAllChargerMode = CreateConVar("inf_AllChargerMode", "0", "是否是全牛模式", CVAR_FLAG, true, 0.0, true, 1.0); + g_hAllHunterMode = CreateConVar("inf_AllHunterMode", "0", "是否是全猎人模式", CVAR_FLAG, true, 0.0, true, 1.0); + g_hAutoSpawnTimeControl = CreateConVar("inf_EnableAutoSpawnTime", "1", "是否开启自动设置增加时间", CVAR_FLAG, true, 0.0, true, 1.0); + g_hIgnoreIncappedSurvivorSight = CreateConVar("inf_IgnoreIncappedSurvivorSight", "1", "特感传送检测是否被看到的时候是否忽略倒地生还者视线", CVAR_FLAG, true, 0.0, true, 1.0); + g_hAddDamageToSmoker = CreateConVar("inf_AddDamageToSmoker", "0", "单人模式smoker拉人时是否5倍伤害", CVAR_FLAG, true, 0.0, true, 1.0); + // 传送会根据这个数值画一个以选定生还者为核心,两边各长inf_TeleportDistance单位距离,高inf_TeleportDistance距离的长方形区域内找复活位置,PS传送最好近一点 + // g_hTeleportDistance = CreateConVar("inf_TeleportDistance", "600.0", "特感传送区域的最小复活大小", CVAR_FLAG, true, g_hSpawnDistanceMin.FloatValue); + g_hSiLimit = CreateConVar("l4d_infected_limit", "6", "一次刷出多少特感", CVAR_FLAG, true, 0.0); + g_hSiInterval = CreateConVar("versus_special_respawn_interval", "16.0", "对抗模式下刷特时间控制", CVAR_FLAG, true, 0.0); + g_hMaxPlayerZombies = FindConVar("z_max_player_zombies"); + g_hVsBossFlowBuffer = FindConVar("versus_boss_buffer"); + SetConVarInt(FindConVar("director_no_specials"), 1); + + // HookEvents + // PostNoCopy是绝对不正确的,NoCopy 会导致 event 丢弃所有的数据("Use 'PostNoCopy' if your action is Post and ONLY requires the event name.") + // 丢弃的数据包括 userid,attackerid,weaponid 等等,对于求生来说这几个字节的内存没必要省略 + // 详见:https://wiki.alliedmods.net/Events_(SourceMod_Scripting) + HookEvent("finale_win", evt_RoundEnd); + HookEvent("mission_lost", evt_RoundEnd); + HookEvent("player_hurt", evt_PlayerHurt); + HookEvent("ability_use", evt_GetSpitTime); + HookEvent("round_start", evt_RoundStart); + HookEvent("map_transition", evt_RoundEnd); + HookEvent("player_death", evt_PlayerDeath); + HookEvent("player_spawn", evt_PlayerSpawn); + + // AddChangeHook + g_hSpawnDistanceMax.AddChangeHook(ConVarChanged_Cvars); + g_hSpawnDistanceMin.AddChangeHook(ConVarChanged_Cvars); + g_hTeleportSi.AddChangeHook(ConVarChanged_Cvars); + g_hTeleportCheckTime.AddChangeHook(ConVarChanged_Cvars); + // g_hTeleportDistance.AddChangeHook(ConVarChanged_Cvars); + g_hSiInterval.AddChangeHook(ConVarChanged_Cvars); + g_hIgnoreIncappedSurvivorSight.AddChangeHook(ConVarChanged_Cvars); + g_hEnableSIoption.AddChangeHook(ConVarChanged_Cvars); + g_hAllChargerMode.AddChangeHook(ConVarChanged_Cvars); + g_hAllHunterMode.AddChangeHook(ConVarChanged_Cvars); + g_hAutoSpawnTimeControl.AddChangeHook(ConVarChanged_Cvars); + g_hAddDamageToSmoker.AddChangeHook(ConVarChanged_Cvars); + g_hSiLimit.AddChangeHook(MaxPlayerZombiesChanged_Cvars); + + // ArrayList + aSpawnQueue = new ArrayList(); + aTeleportQueue = new ArrayList(); + // aSpawnNavList = new ArrayList(); + + // GetCvars + GetCvars(); + GetSiLimit(); + + // SetConVarBonus + SetConVarBounds(g_hMaxPlayerZombies, ConVarBound_Upper, true, g_hSiLimit.FloatValue); + + // Debug + RegAdminCmd("sm_startspawn", Cmd_StartSpawn, ADMFLAG_ROOT, "管理员重置刷特时钟"); + RegAdminCmd("sm_stopspawn", Cmd_StopSpawn, ADMFLAG_ROOT, "管理员重置刷特时钟"); +} + +public void OnMapStart() +{ + if (g_bSIPoolAvailable && !g_hSIPool) g_hSIPool = SIPool.Instance(); +} + +public void OnPluginEnd() +{ + if (g_hAllChargerMode.BoolValue) + { + FindConVar("z_charger_health").RestoreDefault(); + FindConVar("z_charge_max_speed").RestoreDefault(); + FindConVar("z_charge_start_speed").RestoreDefault(); + FindConVar("z_charger_pound_dmg").RestoreDefault(); + FindConVar("z_charge_max_damage").RestoreDefault(); + FindConVar("z_charge_interval").RestoreDefault(); + } +} + +void TweakSettings() +{ + if (g_hAllChargerMode.BoolValue) + { + FindConVar("z_charger_health").SetFloat(500.0); + FindConVar("z_charge_max_speed").SetFloat(750.0); + FindConVar("z_charge_start_speed").SetFloat(350.0); + FindConVar("z_charger_pound_dmg").SetFloat(10.0); + FindConVar("z_charge_max_damage").SetFloat(6.0); + FindConVar("z_charge_interval").SetFloat(2.0); + } } // 向量绘制 @@ -260,20 +291,20 @@ void TweakSettings() { stock Action Cmd_StartSpawn(int client, int args) { - if (L4D_HasAnySurvivorLeftSafeArea()) - { - ResetStatus(); - CreateTimer(0.1, SpawnFirstInfected); - GetSiLimit(); - TweakSettings(); - } - return Plugin_Handled; + if (L4D_HasAnySurvivorLeftSafeArea()) + { + ResetStatus(); + CreateTimer(0.1, SpawnFirstInfected); + GetSiLimit(); + TweakSettings(); + } + return Plugin_Handled; } stock Action Cmd_StopSpawn(int client, int args) { - StopSpawn(); - return Plugin_Handled; + StopSpawn(); + return Plugin_Handled; } // ********************* @@ -281,195 +312,181 @@ stock Action Cmd_StopSpawn(int client, int args) // ********************* void ConVarChanged_Cvars(ConVar convar, const char[] oldValue, const char[] newValue) { - GetCvars(); + GetCvars(); } void MaxPlayerZombiesChanged_Cvars(ConVar convar, const char[] oldValue, const char[] newValue) { - g_iSiLimit = g_hSiLimit.IntValue; - CreateTimer(0.1, MaxSpecialsSet); + g_iSiLimit = g_hSiLimit.IntValue; + CreateTimer(0.1, MaxSpecialsSet); } void GetCvars() { - g_fSpawnDistanceMax = g_hSpawnDistanceMax.FloatValue; - g_fSpawnDistanceMin = g_hSpawnDistanceMin.FloatValue; - g_bTeleportSi = g_hTeleportSi.BoolValue; - //g_fTeleportDistanceMin = g_hTeleportDistance.FloatValue; - g_fSiInterval = g_hSiInterval.FloatValue; - g_iSiLimit = g_hSiLimit.IntValue; - g_iTeleportCheckTime = g_hTeleportCheckTime.IntValue; - g_iEnableSIoption = g_hEnableSIoption.IntValue; - g_bAddDamageToSmoker = g_hAddDamageToSmoker.BoolValue; - g_bAutoSpawnTimeControl = g_hAutoSpawnTimeControl.BoolValue; - g_bIgnoreIncappedSurvivorSight = g_hIgnoreIncappedSurvivorSight.BoolValue; - if(g_hAllChargerMode.BoolValue){ - TweakSettings(); - } + g_fSpawnDistanceMax = g_hSpawnDistanceMax.FloatValue; + g_fSpawnDistanceMin = g_hSpawnDistanceMin.FloatValue; + g_bTeleportSi = g_hTeleportSi.BoolValue; + // g_fTeleportDistanceMin = g_hTeleportDistance.FloatValue; + g_fSiInterval = g_hSiInterval.FloatValue; + g_iSiLimit = g_hSiLimit.IntValue; + g_iTeleportCheckTime = g_hTeleportCheckTime.IntValue; + g_iEnableSIoption = g_hEnableSIoption.IntValue; + g_bAddDamageToSmoker = g_hAddDamageToSmoker.BoolValue; + g_bAutoSpawnTimeControl = g_hAutoSpawnTimeControl.BoolValue; + g_bIgnoreIncappedSurvivorSight = g_hIgnoreIncappedSurvivorSight.BoolValue; + if (g_hAllChargerMode.BoolValue) + TweakSettings(); } public Action MaxSpecialsSet(Handle timer) { - SetConVarBounds(g_hMaxPlayerZombies, ConVarBound_Upper, true, g_hSiLimit.FloatValue); - g_hMaxPlayerZombies.IntValue = g_iSiLimit; - return Plugin_Continue; + SetConVarBounds(g_hMaxPlayerZombies, ConVarBound_Upper, true, g_hSiLimit.FloatValue); + g_hMaxPlayerZombies.IntValue = g_iSiLimit; + return Plugin_Continue; } // ********************* // 事件 // ********************* -//Spitter出生重置能力 -public void evt_PlayerSpawn(Event event, const char[] name, bool dont_broadcast) +// Spitter出生重置能力 +void evt_PlayerSpawn(Event event, const char[] name, bool dont_broadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - if(IsSpitter(client)) - { - g_fSpitterSpitTime[client] = GetGameTime(); - } - if(IsAiTank(client)) - { - Debug_Print("系统生成一只tank,特感总数量 %d, 真实特感数量:%d", g_iTotalSINum, GetCurrentSINum()); - } + int client = GetClientOfUserId(event.GetInt("userid")); + if (IsSpitter(client)) + g_fSpitterSpitTime[client] = GetGameTime(); + else if (IsAiTank(client)) + Debug_Print("系统生成一只tank,特感总数量 %d, 真实特感数量:%d", g_iTotalSINum, GetCurrentSINum()); } //获取spitter口水时间 -public void evt_GetSpitTime(Event event, const char[] name, bool dont_broadcast) +void evt_GetSpitTime(Event event, const char[] name, bool dont_broadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - if (!client || !IsClientInGame(client) || !IsFakeClient(client)) - return; + int client = GetClientOfUserId(event.GetInt("userid")); + if (!client || !IsClientInGame(client) || !IsFakeClient(client)) + return; - static char ability[16]; - event.GetString("ability", ability, sizeof ability); - if (strcmp(ability, "ability_spit") == 0) - { - g_fSpitterSpitTime[client] = GetGameTime(); - } + static char ability[16]; + event.GetString("ability", ability, sizeof ability); + if (strcmp(ability, "ability_spit") == 0) + g_fSpitterSpitTime[client] = GetGameTime(); } /* 玩家受伤,增加对smoker得伤害 */ -public void evt_PlayerHurt(Event event, const char[] name, bool dont_broadcast) -{ - if(g_bAddDamageToSmoker){ - int victim = GetClientOfUserId(GetEventInt(event, "userid")); - int attacker = GetClientOfUserId(GetEventInt(event, "attacker")); - int damage = GetEventInt(event, "dmg_health"); - int eventhealth = GetEventInt(event, "health"); - int AddDamage = 0; - if (IsValidSurvivor(attacker) && IsInfectedBot(victim) && GetEntProp(victim, Prop_Send, "m_zombieClass") == 1) - { - if( GetEntPropEnt(victim, Prop_Send, "m_tongueVictim") > 0 ) - { - AddDamage = damage * 5; - } - int health = eventhealth - AddDamage; - if (health < 1) - { - health = 0; - } - SetEntityHealth(victim, health); - SetEventInt(event, "health", health); - } - } -} - -public void InitStatus(){ - if (g_hTeleHandle != INVALID_HANDLE) - { - delete g_hTeleHandle; - g_hTeleHandle = INVALID_HANDLE; - } - if (g_hCheckShouldSpawnOrNot != INVALID_HANDLE) - { - delete g_hCheckShouldSpawnOrNot; - g_hCheckShouldSpawnOrNot = INVALID_HANDLE; - } - if (g_hSpawnProcess != INVALID_HANDLE) - { - KillTimer(g_hSpawnProcess); - Debug_Print("刷特进程终止"); - g_hSpawnProcess = INVALID_HANDLE; - } - - g_bPickRushMan = false; - g_bShouldCheck = false; - g_bIsLate = false; - g_iSpawnMaxCount = 0; - g_fLastSISpawnStartTime = 0.0; - g_fUnpauseNextSpawnTime = 0.0; - aSpawnQueue.Clear(); - aTeleportQueue.Clear(); - //aSpawnNavList.Clear(); - g_iQueueIndex = 0; - g_iTeleportIndex = 0; - g_iWaveTime=0; - for(int i = 0; i <= MAXPLAYERS; i++) - { - g_fSpitterSpitTime[i] = 0.0; - } - for(int i = 0; i < 6; i++){ - g_iSINum[i] =0; - } -} - -public void StopSpawn(){ - InitStatus(); -} - -public void evt_RoundStart(Event event, const char[] name, bool dontBroadcast) -{ - InitStatus(); - CreateTimer(0.1, MaxSpecialsSet); - CreateTimer(1.0, SafeRoomReset, _, TIMER_FLAG_NO_MAPCHANGE); -} - -public void evt_RoundEnd(Event event, const char[] name, bool dontBroadcast) -{ - InitStatus(); -} - -public void evt_PlayerDeath(Event event, const char[] name, bool dontBroadcast) -{ - int client = GetClientOfUserId(event.GetInt("userid")); - if (IsInfectedBot(client)) - { - int type = GetEntProp(client, Prop_Send, "m_zombieClass"); - //防止无声口水 - if (type != ZC_SPITTER) - { - CreateTimer(0.5, Timer_KickBot, client); - } - if(type >= 1 && type <=6){ - if(g_iSINum[type - 1] > 0) - { - g_iSINum[type - 1] --; - } - else - { - g_iSINum[type - 1] = 0; - } - if(g_iTotalSINum > 0) - { - g_iTotalSINum --; - } - else - { - g_iTotalSINum = 0; - } - Debug_Print("杀死%N,特感总数和该种类特感数量减1分别为%d %d", client, g_iTotalSINum, g_iSINum[type - 1]); - } - g_iTeleCount[client] = 0; - } -} - -public Action Timer_KickBot(Handle timer, int client) -{ - if (IsClientInGame(client) && !IsClientInKickQueue(client) && IsFakeClient(client)) - { - Debug_Print("踢出特感%N",client); - KickClient(client, "You are worthless and was kicked by console"); - } - return Plugin_Continue; +void evt_PlayerHurt(Event event, const char[] name, bool dont_broadcast) +{ + if (!g_bAddDamageToSmoker) return; + + int victim = GetClientOfUserId(GetEventInt(event, "userid")); + int attacker = GetClientOfUserId(GetEventInt(event, "attacker")); + int damage = GetEventInt(event, "dmg_health"); + int eventhealth = GetEventInt(event, "health"); + int AddDamage = 0; + if (IsValidSurvivor(attacker) && IsInfectedBot(victim) && GetEntProp(victim, Prop_Send, "m_zombieClass") == 1) + { + if (GetEntPropEnt(victim, Prop_Send, "m_tongueVictim") > 0) + AddDamage = damage * 5; + + int health = eventhealth - AddDamage; + if (health < 1) health = 0; + + SetEntityHealth(victim, health); + SetEventInt(event, "health", health); + } +} + +void InitStatus() +{ + if (g_hTeleHandle != INVALID_HANDLE) + { + delete g_hTeleHandle; + g_hTeleHandle = INVALID_HANDLE; + //这里其实可以不用赋值,delete 后变量会被分配为 null,可以使用 if(g_hTeleHandle != null) 进行判断 + } + + if (g_hCheckShouldSpawnOrNot != INVALID_HANDLE) + { + delete g_hCheckShouldSpawnOrNot; + g_hCheckShouldSpawnOrNot = INVALID_HANDLE; + } + + if (g_hSpawnProcess != INVALID_HANDLE) + { + KillTimer(g_hSpawnProcess); + Debug_Print("刷特进程终止"); + g_hSpawnProcess = INVALID_HANDLE; + } + + g_bPickRushMan = false; + g_bShouldCheck = false; + g_bIsLate = false; + g_iSpawnMaxCount = 0; + g_fLastSISpawnStartTime = 0.0; + g_fUnpauseNextSpawnTime = 0.0; + aSpawnQueue.Clear(); + aTeleportQueue.Clear(); + // aSpawnNavList.Clear(); + g_iQueueIndex = 0; + g_iTeleportIndex = 0; + g_iWaveTime = 0; + for (int i = 0; i <= MAXPLAYERS; i++) + g_fSpitterSpitTime[i] = 0.0; + + for (int i = 0; i < 6; i++) + g_iSINum[i] = 0; +} + +void StopSpawn() +{ + InitStatus(); +} + +void evt_RoundStart(Event event, const char[] name, bool dontBroadcast) +{ + InitStatus(); + CreateTimer(0.1, MaxSpecialsSet); + CreateTimer(1.0, SafeRoomReset, _, TIMER_FLAG_NO_MAPCHANGE); +} + +void evt_RoundEnd(Event event, const char[] name, bool dontBroadcast) +{ + InitStatus(); +} + +void evt_PlayerDeath(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + if (!IsInfectedBot(client)) return; + + int type = GetEntProp(client, Prop_Send, "m_zombieClass"); + //防止无声口水 + if (type != ZC_SPITTER || g_bSIPoolAvailable) // 使用SIPool后不会出现无声口水 + CreateTimer(0.5, Timer_KickBot, client); + + if (type >= 1 && type <= 6) + { + if (g_iSINum[type - 1] > 0) g_iSINum[type - 1]--; + else g_iSINum[type - 1] = 0; + + if (g_iTotalSINum > 0) g_iTotalSINum--; + else g_iTotalSINum = 0; + + Debug_Print("杀死%N,特感总数和该种类特感数量减1分别为%d %d", client, g_iTotalSINum, g_iSINum[type - 1]); + } + g_iTeleCount[client] = 0; +} + +Action Timer_KickBot(Handle timer, int client) +{ + if (IsClientInGame(client) && !IsClientInKickQueue(client) && IsFakeClient(client)) + { + Debug_Print("踢出特感%N", client); + if (g_bSIPoolAvailable) + g_hSIPool.ReturnSIBot(client); + else KickClient(client, "You are worthless and was kicked by console"); + + return Plugin_Stop; + } + return Plugin_Continue; } // ********************* @@ -477,265 +494,256 @@ public Action Timer_KickBot(Handle timer, int client) // ********************* public void OnGameFrame() { - // 根据情况动态调整 z_maxplayers_zombie 数值 - if (g_iSiLimit > g_hMaxPlayerZombies.IntValue) - { - CreateTimer(0.1, MaxSpecialsSet); - } - if (g_iTeleportIndex <= 0 && g_iQueueIndex < g_iSiLimit) - { - int zombieclass = 0; - if(g_hAllChargerMode.BoolValue){ - zombieclass = 6; - } - else if(g_hAllHunterMode.BoolValue){ - zombieclass = 3; - }else{ - zombieclass = GetRandomInt(1,6); - } - if (zombieclass != 0 && MeetRequire(zombieclass) && !HasReachedLimit(zombieclass) && g_iQueueIndex < g_iSiLimit) - { - //这里增加一些boomer和spitter生成的判定,让bommer和spitter比较晚生成 - aSpawnQueue.Push(g_iQueueIndex); - aSpawnQueue.Set(g_iQueueIndex, zombieclass, 0, false); - g_ArraySIlimit[zombieclass - 1] -= 1; - g_iQueueIndex += 1; - //Debug_Print("<刷特队列> 当前入队特感:%s,当前队列长度:%d,当前队列索引位置:%d", InfectedName[zombieclass], aSpawnQueue.Length, g_iQueueIndex); - } - } - if (g_bIsLate) - { - /* - // 当nav存储长度超过特感生成上限时,删去第一个 - if (aSpawnNavList.Length > g_iSiLimit) - { - //Debug_Print(" 当前队列长度:%d, 超过特感上限,清除队列第一个元素", aSpawnNavList.Length); - aSpawnNavList.Erase(0); - } - */ - if (g_iTotalSINum < g_iSiLimit) - { - if(g_iTeleportIndex > 0) - { - g_iTargetSurvivor = GetTargetSurvivor(); - if(g_fTeleportDistance < g_fSpawnDistanceMax) - { - g_fTeleportDistance += 20.0; - } - float fSpawnPos[3] = {0.0}; - if(GetSpawnPos(fSpawnPos, g_iTargetSurvivor, g_fTeleportDistance, true)) - { - int iZombieClass = aTeleportQueue.Get(0); - if(!(iZombieClass >= 1 && iZombieClass <= 6)) - { - Debug_Print("特感类型读取错误,读取的特感类型为:%d", iZombieClass); - aTeleportQueue.Erase(0); - g_iTeleportIndex -= 1; - return; - } - if(SpawnInfected(fSpawnPos, g_fTeleportDistance, iZombieClass, true)){ - g_iSINum[iZombieClass - 1] += 1; - g_iTotalSINum += 1; - if (aTeleportQueue.Length > 0 && g_iTeleportIndex > 0) - { - aTeleportQueue.Erase(0); - g_iTeleportIndex -= 1; - } - print_type(iZombieClass, g_fSpawnDistance, true); - } - else - { - if (g_iTeleportIndex <= 0) - { - aTeleportQueue.Clear(); - g_iTeleportIndex = 0; - } - } - } - } - //传送队列优先处理,防止普通刷特刷出来把特感数量刷满了 - if(g_iSpawnMaxCount > 0 && g_iTeleportIndex <= 0 && g_iQueueIndex > 0) - { - g_iTargetSurvivor = GetTargetSurvivor(); - if(g_fSpawnDistance < g_fSpawnDistanceMax) - { - g_fSpawnDistance += 5.0; - } - float fSpawnPos[3] = {0.0}; - if(GetSpawnPos(fSpawnPos, g_iTargetSurvivor, g_fSpawnDistance)) { - int iZombieClass = aSpawnQueue.Get(0); - if(SpawnInfected(fSpawnPos, g_fSpawnDistance, iZombieClass)){ - g_iSpawnMaxCount -= 1; - g_iSINum[iZombieClass - 1] += 1; - g_iTotalSINum += 1; - if (aSpawnQueue.Length > 0 && g_iQueueIndex > 0) - { - aSpawnQueue.Erase(0); - g_iQueueIndex -= 1; - //刷出来之后要求特感激进进攻 - BypassAndExecuteCommand("nb_assault"); - } - print_type(iZombieClass, g_fSpawnDistance); - } - else - { - if (HasReachedLimit(iZombieClass)) - { - ReachedLimit(iZombieClass); - } - if (g_iQueueIndex <= 0) - { - aSpawnQueue.Clear(); - g_iQueueIndex = 0; - } - } - } - } - } - } -} - -stock bool GetSpawnPos(float fSpawnPos[3], int TargetSurvivor, float SpawnDistance, bool IsTeleport = false){ - if(IsValidClient(TargetSurvivor)){ - float fSurvivorPos[3] = {0.0}, fDirection[3] = {0.0}, fEndPos[3] = {0.0}, fMins[3] = {0.0}, fMaxs[3] = {0.0}; - // 根据指定生还者坐标,拓展刷新范围 - GetClientEyePosition(TargetSurvivor, fSurvivorPos); - //增加高度,增加刷房顶的几率 - if(SpawnDistance < 500.0) - { - fMaxs[2] = fSurvivorPos[2] + 800.0; - } - else - { - fMaxs[2] = fSurvivorPos[2] + SpawnDistance + 300.0; - } - fMins[0] = fSurvivorPos[0] - SpawnDistance; - fMaxs[0] = fSurvivorPos[0] + SpawnDistance; - fMins[1] = fSurvivorPos[1] - SpawnDistance; - fMaxs[1] = fSurvivorPos[1] + SpawnDistance; - fMaxs[2] = fSurvivorPos[2] + SpawnDistance; - // 规定射线方向 - fDirection[0] = 90.0; - fDirection[1] = fDirection[2] = 0.0; - // 随机刷新位置 - fSpawnPos[0] = GetRandomFloat(fMins[0], fMaxs[0]); - fSpawnPos[1] = GetRandomFloat(fMins[1], fMaxs[1]); - fSpawnPos[2] = GetRandomFloat(fSurvivorPos[2], fMaxs[2]); - // 找位条件,可视,是否在有效 NavMesh,是否卡住,否则先会判断是否在有效 Mesh 与是否卡住导致某些位置刷不出特感 - int count2=0; - //生成的时候只能在有跑男情况下才特意生成到幸存者前方 - while (PlayerVisibleToSDK(fSpawnPos, IsTeleport) || !IsOnValidMesh(fSpawnPos) || IsPlayerStuck(fSpawnPos) || ((g_bPickRushMan || IsTeleport) && !Is_Pos_Ahead(fSpawnPos, g_iTargetSurvivor))) - { - count2++; - if(count2 > 20) - { - return false; - } - fSpawnPos[0] = GetRandomFloat(fMins[0], fMaxs[0]); - fSpawnPos[1] = GetRandomFloat(fMins[1], fMaxs[1]); - fSpawnPos[2] = GetRandomFloat(fSurvivorPos[2], fMaxs[2]); - TR_TraceRay(fSpawnPos, fDirection, MASK_PLAYERSOLID, RayType_Infinite); - if(TR_DidHit()) - { - TR_GetEndPosition(fEndPos); - fSpawnPos = fEndPos; - fSpawnPos[2] += NAV_MESH_HEIGHT; - } - } - return true; - } - return false; + // 根据情况动态调整 z_maxplayers_zombie 数值 + if (g_iSiLimit > g_hMaxPlayerZombies.IntValue) + CreateTimer(0.1, MaxSpecialsSet); + + if (g_iTeleportIndex <= 0 && g_iQueueIndex < g_iSiLimit) + { + int zombieclass = 0; + if (g_hAllChargerMode.BoolValue) + zombieclass = 6; + else if (g_hAllHunterMode.BoolValue) + zombieclass = 3; + else zombieclass = GetRandomInt(1, 6); + + if (zombieclass != 0 && MeetRequire(zombieclass) && !HasReachedLimit(zombieclass) && g_iQueueIndex < g_iSiLimit) + { + //这里增加一些boomer和spitter生成的判定,让boomer和spitter比较晚生成 + aSpawnQueue.Push(g_iQueueIndex); + aSpawnQueue.Set(g_iQueueIndex, zombieclass, 0, false); + g_ArraySIlimit[zombieclass - 1] -= 1; + g_iQueueIndex += 1; + Debug_Print("<刷特队列> 当前入队特感:%s,当前队列长度:%d,当前队列索引位置:%d", InfectedName[zombieclass], aSpawnQueue.Length, g_iQueueIndex); + } + } + + if (g_bIsLate) + { + /* + // 当nav存储长度超过特感生成上限时,删去第一个 + if (aSpawnNavList.Length > g_iSiLimit) + { + //Debug_Print(" 当前队列长度:%d, 超过特感上限,清除队列第一个元素", aSpawnNavList.Length); + aSpawnNavList.Erase(0); + } + */ + if (g_iTotalSINum < g_iSiLimit) + { + if (g_iTeleportIndex > 0) + { + g_iTargetSurvivor = GetTargetSurvivor(); + if (g_fTeleportDistance < g_fSpawnDistanceMax) + g_fTeleportDistance += 20.0; + + float fSpawnPos[3] = { 0.0 }; + if (GetSpawnPos(fSpawnPos, g_iTargetSurvivor, g_fTeleportDistance, true)) + { + int iZombieClass = aTeleportQueue.Get(0); + if (!(iZombieClass >= 1 && iZombieClass <= 6)) + { + Debug_Print("特感类型读取错误,读取的特感类型为:%d", iZombieClass); + aTeleportQueue.Erase(0); + g_iTeleportIndex -= 1; + return; + } + + if (SpawnInfected(fSpawnPos, g_fTeleportDistance, iZombieClass, true)) + { + g_iSINum[iZombieClass - 1] += 1; + g_iTotalSINum += 1; + if (aTeleportQueue.Length > 0 && g_iTeleportIndex > 0) + { + aTeleportQueue.Erase(0); + g_iTeleportIndex -= 1; + } + print_type(iZombieClass, g_fSpawnDistance, true); + } + else if (g_iTeleportIndex <= 0) + { + aTeleportQueue.Clear(); + g_iTeleportIndex = 0; + } + } + } + + Debug_Print("spawn_max:%d, tpidx:%d, queue_idx:%d", g_iSpawnMaxCount, g_iTeleportIndex, g_iQueueIndex); + //传送队列优先处理,防止普通刷特刷出来把特感数量刷满了 + if (g_iSpawnMaxCount > 0 && g_iTeleportIndex <= 0 && g_iQueueIndex > 0) + { + g_iTargetSurvivor = GetTargetSurvivor(); + if (g_fSpawnDistance < g_fSpawnDistanceMax) + g_fSpawnDistance += 5.0; + + float fSpawnPos[3] = { 0.0 }; + if (GetSpawnPos(fSpawnPos, g_iTargetSurvivor, g_fSpawnDistance)) + { + int iZombieClass = aSpawnQueue.Get(0); + if (SpawnInfected(fSpawnPos, g_fSpawnDistance, iZombieClass)) + { + g_iSpawnMaxCount -= 1; + g_iSINum[iZombieClass - 1] += 1; + g_iTotalSINum += 1; + if (aSpawnQueue.Length > 0 && g_iQueueIndex > 0) + { + aSpawnQueue.Erase(0); + g_iQueueIndex -= 1; + //刷出来之后要求特感激进进攻 + BypassAndExecuteCommand("nb_assault"); + } + print_type(iZombieClass, g_fSpawnDistance); + } + else + { + if (HasReachedLimit(iZombieClass)) + ReachedLimit(iZombieClass); + + if (g_iQueueIndex <= 0) + { + aSpawnQueue.Clear(); + g_iQueueIndex = 0; + } + } + } + } + } + } +} + +stock bool GetSpawnPos(float fSpawnPos[3], int TargetSurvivor, float SpawnDistance, bool IsTeleport = false) +{ + if (!IsValidClient(TargetSurvivor)) return false; + + float fSurvivorPos[3], fDirection[3], fEndPos[3], fMins[3], fMaxs[3]; + // 根据指定生还者坐标,拓展刷新范围 + GetClientEyePosition(TargetSurvivor, fSurvivorPos); + //增加高度,增加刷房顶的几率 + if (SpawnDistance < 500.0) + fMaxs[2] = fSurvivorPos[2] + 800.0; + else fMaxs[2] = fSurvivorPos[2] + SpawnDistance + 300.0; + + fMins[0] = fSurvivorPos[0] - SpawnDistance; + fMaxs[0] = fSurvivorPos[0] + SpawnDistance; + fMins[1] = fSurvivorPos[1] - SpawnDistance; + fMaxs[1] = fSurvivorPos[1] + SpawnDistance; + fMaxs[2] = fSurvivorPos[2] + SpawnDistance; + // 规定射线方向 + fDirection[0] = 90.0; + fDirection[1] = fDirection[2] = 0.0; + // 随机刷新位置 + fSpawnPos[0] = GetRandomFloat(fMins[0], fMaxs[0]); + fSpawnPos[1] = GetRandomFloat(fMins[1], fMaxs[1]); + fSpawnPos[2] = GetRandomFloat(fSurvivorPos[2], fMaxs[2]); + // 找位条件,可视,是否在有效 NavMesh,是否卡住,否则先会判断是否在有效 Mesh 与是否卡住导致某些位置刷不出特感 + int count2 = 0; + //生成的时候只能在有跑男情况下才特意生成到幸存者前方 + while (PlayerVisibleToSDK(fSpawnPos, IsTeleport) || !IsOnValidMesh(fSpawnPos) || IsPlayerStuck(fSpawnPos) || ((g_bPickRushMan || IsTeleport) && !Is_Pos_Ahead(fSpawnPos, g_iTargetSurvivor))) + { + count2++; + if (count2 > 20) + { + return false; + } + fSpawnPos[0] = GetRandomFloat(fMins[0], fMaxs[0]); + fSpawnPos[1] = GetRandomFloat(fMins[1], fMaxs[1]); + fSpawnPos[2] = GetRandomFloat(fSurvivorPos[2], fMaxs[2]); + TR_TraceRay(fSpawnPos, fDirection, MASK_PLAYERSOLID, RayType_Infinite); + if (TR_DidHit()) + { + TR_GetEndPosition(fEndPos); + fSpawnPos = fEndPos; + fSpawnPos[2] += NAV_MESH_HEIGHT; + } + } + return true; } /* stock bool Is_Nav_already_token(Address nav) { - for(int i = 0; i < aSpawnNavList.Length; i++) - { - if(nav == aSpawnNavList.Get(i)) - return true; - } - return false; + for(int i = 0; i < aSpawnNavList.Length; i++) + { + if(nav == aSpawnNavList.Get(i)) + return true; + } + return false; } */ stock bool SpawnInfected(float fSpawnPos[3], float SpawnDistance, int iZombieClass, bool IsTeleport = false) { - - float fSurvivorPos[3]; - //Debug_Print("生还者看不到"); - // 生还数量为 4,循环 4 次,检测此位置到生还的距离是否小于 750 是则刷特,此处可以刷新 1 ~ g_iSiLimit 只特感,如果此处刷完,则上面的 SpawnSpecial 将不再刷特 - for (int count = 0; count < g_iSurvivorNum; count++) - { - int index = g_iSurvivors[count]; - //不是有效生还者不生成 - if(!IsValidSurvivor(index)) - continue; - - //生还者倒地或者挂边,也不生成 - if(IsClientIncapped(index)){ - continue; - } - //非跑男模式目标已满,跳过 - if(g_bTargetSystemAvailable && !g_bPickRushMan && IsClientReachLimit(index)) - { - continue; - } - GetClientEyePosition(index, fSurvivorPos); - fSurvivorPos[2] -= 60.0; - //获取nav地址 - Address nav1 = L4D_GetNearestNavArea(fSpawnPos, 120.0, false, false, false, TEAM_INFECTED); - Address nav2 = L4D_GetNearestNavArea(fSurvivorPos, 120.0, false, false, false, TEAM_INFECTED); - - //这一段是对高处生成位置进行的补偿 - float distance; - if(IsTeleport) - { - distance = g_fTeleportDistance; - }else - { - distance = g_fSpawnDistance; - } - if(distance * (NORMALPOSMULT - 1) <= 250.0) - { - distance += 250.0; - } - else - { - distance *= NORMALPOSMULT; - } - if(fSpawnPos[2] - fSurvivorPos[2] > HIGHERPOS) - { - distance += HIGHERPOSADDDISTANCE; - } - //nav1 和 nav2 必须有网格相连的路,并且生成距离大于distance,增加不能是同nav网格的要求 - if (L4D2_NavAreaBuildPath(nav1, nav2, distance, TEAM_INFECTED, false) && GetVectorDistance(fSurvivorPos, fSpawnPos, true) >= Pow(g_fSpawnDistanceMin, 2.0) && nav1 != nav2) - { - if (iZombieClass > 0 && !HasReachedLimit(iZombieClass) && CheckSIOption(iZombieClass)) - { - if(IsTeleport && g_iTeleportIndex <= 0){ - return false; - } - if(!IsTeleport && g_iSpawnMaxCount <= 0){ - return false; - } - int entityindex = L4D2_SpawnSpecial(iZombieClass, fSpawnPos, view_as({0.0, 0.0, 0.0})); - if (IsValidEntity(entityindex) && IsValidEdict(entityindex)) - { - //aSpawnNavList.Push(nav1); - //Debug_Print(" 当前入队nav:%d,当前队列长度:%d", nav1, aSpawnNavList.Length); - if(IsInfectedBot(entityindex) && IsPlayerAlive(entityindex)) - return true; - else - { - Debug_Print("生成错误"); - RemoveEntity(entityindex); - return false; - } - } - } - } - } - return false; + float fSurvivorPos[3]; + Debug_Print("生还者看不到"); + // 生还数量为 4,循环 4 次,检测此位置到生还的距离是否小于 750 是则刷特,此处可以刷新 1 ~ g_iSiLimit 只特感,如果此处刷完,则上面的 SpawnSpecial 将不再刷特 + for (int count = 0; count < g_iSurvivorNum; count++) + { + int index = g_iSurvivors[count]; + //不是有效生还者不生成 + if (!IsValidSurvivor(index)) + continue; + + //生还者倒地或者挂边,也不生成 + if (IsClientIncapped(index)) + continue; + + //非跑男模式目标已满,跳过 + if (g_bTargetSystemAvailable && !g_bPickRushMan && IsClientReachLimit(index)) + continue; + + GetClientEyePosition(index, fSurvivorPos); + fSurvivorPos[2] -= 60.0; + //获取nav地址 + Address nav1 = L4D_GetNearestNavArea(fSpawnPos, 120.0, false, false, false, TEAM_INFECTED); + Address nav2 = L4D_GetNearestNavArea(fSurvivorPos, 120.0, false, false, false, TEAM_INFECTED); + + //这一段是对高处生成位置进行的补偿 + float distance; + if (IsTeleport) + distance = g_fTeleportDistance; + else + distance = g_fSpawnDistance; + + if (distance * (NORMALPOSMULT - 1) <= 250.0) + distance += 250.0; + else + distance *= NORMALPOSMULT; + + if (fSpawnPos[2] - fSurvivorPos[2] > HIGHERPOS) + distance += HIGHERPOSADDDISTANCE; + + // nav1 和 nav2 必须有网格相连的路,并且生成距离大于distance,增加不能是同nav网格的要求 + if (L4D2_NavAreaBuildPath(nav1, nav2, distance, TEAM_INFECTED, false) && GetVectorDistance(fSurvivorPos, fSpawnPos, true) >= Pow(g_fSpawnDistanceMin, 2.0) && nav1 != nav2) + { + if (iZombieClass > 0 && !HasReachedLimit(iZombieClass) && CheckSIOption(iZombieClass)) + { + if (IsTeleport && g_iTeleportIndex <= 0) + return false; + + if (!IsTeleport && g_iSpawnMaxCount <= 0) + return false; + + int entityindex; + if (g_bSIPoolAvailable) + entityindex = g_hSIPool.RequestSIBot(iZombieClass, fSpawnPos); + else entityindex = L4D2_SpawnSpecial(iZombieClass, fSpawnPos, view_as({ 0.0, 0.0, 0.0 })); + + Debug_Print("请求%d特感,生成:%d", iZombieClass, entityindex); + if (IsValidEntity(entityindex) && IsValidEdict(entityindex)) + { + // aSpawnNavList.Push(nav1); + // Debug_Print(" 当前入队nav:%d,当前队列长度:%d", nav1, aSpawnNavList.Length); + if (IsInfectedBot(entityindex) && IsPlayerAlive(entityindex)) + return true; + else + { + Debug_Print("生成错误"); + RemoveEntity(entityindex); + return false; + } + } + } + } + } + return false; } // 当前在场的某种特感种类数量达到 Cvar 限制,但因为刷新一个特感,出队此元素,之后再入队相同特感元素,则会刷不出来,需要处理重复情况,如果队列长度大于 1 且索引大于 0,说明队列存在 @@ -743,255 +751,238 @@ stock bool SpawnInfected(float fSpawnPos[3], float SpawnDistance, int iZombieCla // 如:当前存在 2 个 Smoker 未死亡,Smoker 的 Cvar 限制为 2 ,这时入队一个 Smoker 元素,则会导致无法刷出特感 void ReachedLimit(int type) { - if (aSpawnQueue.Length > 1 && g_iQueueIndex > 0) - { - Debug_Print("%s上限已到,无法生成,且队列不为空,删除第一个队列元素", InfectedName[type]); - aSpawnQueue.Erase(0); - g_iQueueIndex -= 1; - } - else - { - for (int i = 1; i <= 6; i++) - { - if (CheckSIOption(i) && !HasReachedLimit(i)) - { - Debug_Print("%s上限已到,无法生成,当前队列为空,遍历1-6类型发现%s类型未满", InfectedName[type], InfectedName[i]); - aSpawnQueue.Set(0, i, 0, false); - } - } - } -} - -public int CheckSIOption(int type){ + if (aSpawnQueue.Length > 1 && g_iQueueIndex > 0) + { + Debug_Print("%s上限已到,无法生成,且队列不为空,删除第一个队列元素", InfectedName[type]); + aSpawnQueue.Erase(0); + g_iQueueIndex -= 1; + } + else + for (int i = 1; i <= 6; i++) + if (CheckSIOption(i) && !HasReachedLimit(i)) + { + Debug_Print("%s上限已到,无法生成,当前队列为空,遍历1-6类型发现%s类型未满", InfectedName[type], InfectedName[i]); + aSpawnQueue.Set(0, i, 0, false); + } +} + +int CheckSIOption(int type) +{ switch (type) { case 1: - { return ENABLE_SMOKER & g_iEnableSIoption; - } case 2: - { return ENABLE_BOOMER & g_iEnableSIoption; - } case 3: - { return ENABLE_HUNTER & g_iEnableSIoption; - } case 4: - { return ENABLE_SPITTER & g_iEnableSIoption; - } case 5: - { return ENABLE_JOCKEY & g_iEnableSIoption; - } case 6: - { return ENABLE_CHARGER & g_iEnableSIoption; - } } return 0; } - // 当前某种特感数量是否达到 Convar 值限制 bool HasReachedLimit(int zombieclass) { - int count = 0; char convar[16] = {'\0'}; - for (int infected = 1; infected <= MaxClients; infected++) - { - if (IsClientConnected(infected) && IsClientInGame(infected) && GetEntProp(infected, Prop_Send, "m_zombieClass") == zombieclass) - { - count += 1; - } - } - if((g_hAllChargerMode.BoolValue || g_hAllHunterMode.BoolValue) && count == g_iSiLimit){ - return true; - } - else if((g_hAllChargerMode.BoolValue || g_hAllHunterMode.BoolValue) && count < g_iSiLimit){ - return false; - } - FormatEx(convar, sizeof(convar), "z_%s_limit", InfectedName[zombieclass]); - if (count == GetConVarInt(FindConVar(convar))) - { - return true; - } - else - { - return false; - } -} - -stock void print_type(int iType, float SpawnDistance, bool Isteleport = false){ - if (iType >= 1 && iType <=6) - { - Debug_Print(" %s生成一只%s,当前%s数量:%d,特感总数量 %d, 真实特感数量:%d, 找位最大单位距离:%f", Isteleport?"传送":"", InfectedName[iType], InfectedName[iType], g_iSINum[iType -1], g_iTotalSINum, GetCurrentSINum(), SpawnDistance); - } + int count = 0; + static char convar[16]; + for (int infected = 1; infected <= MaxClients; infected++) + if (IsClientConnected(infected) && IsClientInGame(infected) && !IsPlayerAlive(infected) + && GetEntProp(infected, Prop_Send, "m_zombieClass") == zombieclass) + count += 1; + + if ((g_hAllChargerMode.BoolValue || g_hAllHunterMode.BoolValue) && count == g_iSiLimit) + return true; + else if ((g_hAllChargerMode.BoolValue || g_hAllHunterMode.BoolValue) && count < g_iSiLimit) + return false; + + FormatEx(convar, sizeof(convar), "z_%s_limit\0", InfectedName[zombieclass]); + // if (count == GetConVarInt(FindConVar(convar))) + // { + // return true; + // } + // else + // { + // return false; + // } + return count == GetConVarInt(FindConVar(convar)); +} + +void print_type(int iType, float SpawnDistance, bool Isteleport = false) +{ + if (iType >= 1 && iType <= 6) + { + Debug_Print(" %s生成一只%s,当前%s数量:%d,特感总数量 %d, 真实特感数量:%d, 找位最大单位距离:%f", Isteleport ? "传送" : "", InfectedName[iType], InfectedName[iType], g_iSINum[iType - 1], g_iTotalSINum, GetCurrentSINum(), SpawnDistance); + } } // 初始 & 动态刷特时钟 -public Action SpawnFirstInfected(Handle timer) -{ - if (!g_bIsLate) - { - g_bIsLate = true; - //首先触发一次刷特,然后每1s检测 - g_hCheckShouldSpawnOrNot = CreateTimer(1.0, CheckShouldSpawnOrNot, _, TIMER_REPEAT); - SpawnInfectedSettings(); - if (g_bTeleportSi) - { - g_hTeleHandle = CreateTimer(1.0, Timer_PositionSi, _, TIMER_REPEAT); - } - } - return Plugin_Stop; -} - -public Action SpawnNewInfected(Handle timer) -{ - SpawnInfectedSettings(); - g_hSpawnProcess = INVALID_HANDLE; - return Plugin_Stop; -} - -public void SpawnInfectedSettings() -{ - if (g_bIsLate) - { - g_iSurvivorNum = 0; - g_iLastSpawnTime = 0; - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidSurvivor(client) && IsPlayerAlive(client)) - { - g_iSurvivors[g_iSurvivorNum] = client; - g_iSurvivorNum += 1; - } - } - g_fSpawnDistance = g_fSpawnDistanceMin; - /* - //优化性能,每波刷新前清除aSpawnNavList队列中的值,但是如果刷特时间很短,这个优化估计起的作用不大 - if(g_iSpawnMaxCount == 0) - { - aSpawnNavList.Clear(); - } - */ - - g_iSpawnMaxCount += g_iSiLimit; - g_bShouldCheck = true; - g_iWaveTime++; - Debug_Print("开始第%d波刷特", g_iWaveTime); - - // 当一定时间内刷不出特感,触发时钟使 g_iSpawnMaxCount 超过 g_iSiLimit 值时,最多允许刷出 g_iSiLimit + 2 只特感,防止连续刷 2-3 波的情况 - if (g_iSiLimit < g_iSpawnMaxCount) - { - - g_iSpawnMaxCount = g_iSiLimit; - Debug_Print("当前特感数量达到上限"); - } - - } +Action SpawnFirstInfected(Handle timer) +{ + if (!g_bIsLate) + { + g_bIsLate = true; + //首先触发一次刷特,然后每1s检测 + g_hCheckShouldSpawnOrNot = CreateTimer(1.0, CheckShouldSpawnOrNot, _, TIMER_REPEAT); + SpawnInfectedSettings(); + if (g_bTeleportSi) + g_hTeleHandle = CreateTimer(1.0, Timer_PositionSi, _, TIMER_REPEAT); + } + return Plugin_Stop; +} + +Action SpawnNewInfected(Handle timer) +{ + SpawnInfectedSettings(); + g_hSpawnProcess = INVALID_HANDLE; + return Plugin_Stop; +} + +void SpawnInfectedSettings() +{ + if (g_bIsLate) + { + g_iSurvivorNum = 0; + g_iLastSpawnTime = 0; + for (int client = 1; client <= MaxClients; client++) + if (IsValidSurvivor(client) && IsPlayerAlive(client)) + { + g_iSurvivors[g_iSurvivorNum] = client; + g_iSurvivorNum += 1; + } + + g_fSpawnDistance = g_fSpawnDistanceMin; + /* + //优化性能,每波刷新前清除aSpawnNavList队列中的值,但是如果刷特时间很短,这个优化估计起的作用不大 + if(g_iSpawnMaxCount == 0) + { + aSpawnNavList.Clear(); + } + */ + + g_iSpawnMaxCount += g_iSiLimit; + g_bShouldCheck = true; + g_iWaveTime++; + Debug_Print("开始第%d波刷特", g_iWaveTime); + + // 当一定时间内刷不出特感,触发时钟使 g_iSpawnMaxCount 超过 g_iSiLimit 值时,最多允许刷出 g_iSiLimit + 2 只特感,防止连续刷 2-3 波的情况 + if (g_iSiLimit < g_iSpawnMaxCount) + { + g_iSpawnMaxCount = g_iSiLimit; + Debug_Print("当前特感数量达到上限"); + } + } } public void OnUnpause() { - if(g_hSpawnProcess == INVALID_HANDLE) - { - Debug_Print("解除暂停,原先一波刷特进程已经在处理,下一波刷特是%.2f秒后", g_fUnpauseNextSpawnTime); - g_hSpawnProcess = CreateTimer(g_fUnpauseNextSpawnTime, SpawnNewInfected, _, TIMER_REPEAT); - } -} - -public Action CheckShouldSpawnOrNot(Handle timer) -{ - if(IsInPause()) - { - Debug_Print("处于暂停状态,停止刷特"); - if(g_hSpawnProcess != INVALID_HANDLE) - { - g_fUnpauseNextSpawnTime = g_fSiInterval - (GetGameTime() - g_fLastSISpawnStartTime); - KillTimer(g_hSpawnProcess); - g_hSpawnProcess = INVALID_HANDLE; - } - return Plugin_Continue; - } - g_iLastSpawnTime ++; - if(!g_bIsLate) return Plugin_Stop; - if(!g_bShouldCheck && g_hSpawnProcess != INVALID_HANDLE) return Plugin_Continue; - if(FindConVar("survivor_limit").IntValue >= 2 && IsAnyTankOrAboveHalfSurvivorDownOrDied() && g_iLastSpawnTime < RoundToFloor(g_fSiInterval / 2)) return Plugin_Continue; - //防止0s情况下spitter无法快速踢出导致的特感越刷越少问题 - if(g_iEnableSIoption & ENABLE_SPITTER && g_iLastSpawnTime < 4) {Debug_Print("因为可以刷spitter,所以最低4秒起刷,不然容易造成特感数量统计错误,特感生成不出来");return Plugin_Continue;} - if(!g_bAutoSpawnTimeControl) - { - g_bShouldCheck = false; - if(g_iSpawnMaxCount == g_iSiLimit) - { - Debug_Print("固定增时系统因为等待刷特数量达到上限,暂停刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); - g_iLastSpawnTime = 0; - } - else - { - Debug_Print("固定增时系统开始新一波刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); - g_hSpawnProcess = CreateTimer(g_fSiInterval * 1.5, SpawnNewInfected, _, TIMER_REPEAT); - } - } - else - { - if((IsAllKillersDown() && g_iSpawnMaxCount == 0) || (g_iTotalSINum <= (RoundToFloor(g_iSiLimit / 4.0) + 1) && g_iSpawnMaxCount == 0) || (g_iLastSpawnTime >= g_fSiInterval * 0.5)) - { - g_bShouldCheck = false; - if(g_iSpawnMaxCount == g_iSiLimit) - { - Debug_Print("自动增时系统因为等待刷特数量达到上限,暂停刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); - g_iLastSpawnTime = 0; - } - else - { - Debug_Print("自动增时系统开始新一波刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); - g_hSpawnProcess = CreateTimer(g_fSiInterval, SpawnNewInfected, _, TIMER_REPEAT); - } - } - } - g_fLastSISpawnStartTime = GetGameTime(); - return Plugin_Continue; + if (g_hSpawnProcess == INVALID_HANDLE) + { + Debug_Print("解除暂停,原先一波刷特进程已经在处理,下一波刷特是%.2f秒后", g_fUnpauseNextSpawnTime); + g_hSpawnProcess = CreateTimer(g_fUnpauseNextSpawnTime, SpawnNewInfected, _, TIMER_REPEAT); + } +} + +Action CheckShouldSpawnOrNot(Handle timer) +{ + if (IsInPause()) + { + Debug_Print("处于暂停状态,停止刷特"); + if (g_hSpawnProcess != INVALID_HANDLE) + { + g_fUnpauseNextSpawnTime = g_fSiInterval - (GetGameTime() - g_fLastSISpawnStartTime); + KillTimer(g_hSpawnProcess); + g_hSpawnProcess = INVALID_HANDLE; + } + return Plugin_Continue; + } + + g_iLastSpawnTime++; + if (!g_bIsLate) return Plugin_Stop; + if (!g_bShouldCheck && g_hSpawnProcess != INVALID_HANDLE) return Plugin_Continue; + if (FindConVar("survivor_limit").IntValue >= 2 && IsAnyTankOrAboveHalfSurvivorDownOrDied() && g_iLastSpawnTime < RoundToFloor(g_fSiInterval / 2)) return Plugin_Continue; + //防止0s情况下spitter无法快速踢出导致的特感越刷越少问题 + if (g_iEnableSIoption & ENABLE_SPITTER && g_iLastSpawnTime < 4 && !g_bSIPoolAvailable) // 使用 SIPool 后无此问题 + { + Debug_Print("因为可以刷spitter,所以最低4秒起刷,不然容易造成特感数量统计错误,特感生成不出来"); + return Plugin_Continue; + } + if (!g_bAutoSpawnTimeControl) + { + g_bShouldCheck = false; + if (g_iSpawnMaxCount == g_iSiLimit) + { + Debug_Print("固定增时系统因为等待刷特数量达到上限,暂停刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); + g_iLastSpawnTime = 0; + } + else + { + Debug_Print("固定增时系统开始新一波刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); + g_hSpawnProcess = CreateTimer(g_fSiInterval * 1.5, SpawnNewInfected, _, TIMER_REPEAT); + } + } + else if ((IsAllKillersDown() && g_iSpawnMaxCount == 0) || (g_iTotalSINum <= (RoundToFloor(g_iSiLimit / 4.0) + 1) && g_iSpawnMaxCount == 0) || (g_iLastSpawnTime >= g_fSiInterval * 0.5)) + { + g_bShouldCheck = false; + if (g_iSpawnMaxCount == g_iSiLimit) + { + Debug_Print("自动增时系统因为等待刷特数量达到上限,暂停刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); + g_iLastSpawnTime = 0; + } + else + { + Debug_Print("自动增时系统开始新一波刷特, 总用时:%.1f秒", g_iLastSpawnTime + g_fSiInterval); + g_hSpawnProcess = CreateTimer(g_fSiInterval, SpawnNewInfected, _, TIMER_REPEAT); + } + } + g_fLastSISpawnStartTime = GetGameTime(); + return Plugin_Continue; } //是否存在非克、舌头、口水、胖子存活 bool IsAllKillersDown() { - return (g_iSINum[view_as(ZC_CHARGER) - 1] + g_iSINum[view_as(ZC_HUNTER) - 1] + g_iSINum[view_as(ZC_JOCKEY)] - 1) == 0; + return (g_iSINum[view_as(ZC_CHARGER) - 1] + + g_iSINum[view_as(ZC_HUNTER) - 1] + + g_iSINum[view_as(ZC_JOCKEY)] - 1) + == 0; } -stock void BypassAndExecuteCommand(char []strCommand) +stock void BypassAndExecuteCommand(char[] strCommand) { - int flags = GetCommandFlags(strCommand); - SetCommandFlags(strCommand, flags & ~ FCVAR_CHEAT); - FakeClientCommand(GetRandomSurvivor(), "%s", strCommand); - SetCommandFlags(strCommand, flags); + int flags = GetCommandFlags(strCommand); + SetCommandFlags(strCommand, flags & ~FCVAR_CHEAT); + FakeClientCommand(GetRandomSurvivor(), "%s", strCommand); + SetCommandFlags(strCommand, flags); } // 开局重置特感状态 -public Action SafeRoomReset(Handle timer) -{ - ResetStatus(); - return Plugin_Continue; -} - -public void ResetStatus(){ - g_iTotalSINum = 0; - for (int client = 1; client <= MaxClients; client++) - { - if (IsInfectedBot(client) && IsPlayerAlive(client)) - { - g_iTeleCount[client] = 0; - int type = GetEntProp(client, Prop_Send, "m_zombieClass"); - g_iSINum[type - 1] += 1; - g_iTotalSINum += 1; - } - if (IsValidSurvivor(client) && !IsPlayerAlive(client)) - { - L4D_RespawnPlayer(client); - } - } +Action SafeRoomReset(Handle timer) +{ + ResetStatus(); + return Plugin_Continue; +} + +void ResetStatus() +{ + g_iTotalSINum = 0; + for (int client = 1; client <= MaxClients; client++) + { + if (IsInfectedBot(client) && !IsPlayerAlive(client)) + { + g_iTeleCount[client] = 0; + int type = GetEntProp(client, Prop_Send, "m_zombieClass"); + g_iSINum[type - 1] += 1; + g_iTotalSINum += 1; + } + if (IsValidSurvivor(client) && !IsPlayerAlive(client)) + L4D_RespawnPlayer(client); + } } // ********************* @@ -999,549 +990,514 @@ public void ResetStatus(){ // ********************* bool IsInfectedBot(int client) { - if (client > 0 && client <= MaxClients && IsClientInGame(client) && IsFakeClient(client) && GetClientTeam(client) == TEAM_INFECTED && GetEntProp(client, Prop_Send, "m_zombieClass") <= 6 && GetEntProp(client, Prop_Send, "m_zombieClass") >=1) - { - return true; - } - else - { - return false; - } + // if (client > 0 && client <= MaxClients && IsClientInGame(client) && IsFakeClient(client) && GetClientTeam(client) == TEAM_INFECTED && GetEntProp(client, Prop_Send, "m_zombieClass") <= 6 && GetEntProp(client, Prop_Send, "m_zombieClass") >= 1) + // { + // return true; + // } + // else + // { + // return false; + // } + + return client > 0 && client <= MaxClients && IsClientInGame(client) && IsFakeClient(client) + && GetClientTeam(client) == TEAM_INFECTED && GetEntProp(client, Prop_Send, "m_zombieClass") <= 6 + && GetEntProp(client, Prop_Send, "m_zombieClass") >= 1; } bool IsOnValidMesh(float fReferencePos[3]) { - Address pNavArea = L4D2Direct_GetTerrorNavArea(fReferencePos); - if (pNavArea != Address_Null && !(L4D_GetNavArea_SpawnAttributes(pNavArea) & CHECKPOINT)) - { - return true; - } - else - { - return false; - } + Address pNavArea = L4D2Direct_GetTerrorNavArea(fReferencePos); + // if (pNavArea != Address_Null && !(L4D_GetNavArea_SpawnAttributes(pNavArea) & CHECKPOINT)) + // { + // return true; + // } + // else + // { + // return false; + // } + + // 我真心建议这样写,可读性不比用if分支差,一个方法太长看着会很乱的 + return pNavArea != Address_Null && !(L4D_GetNavArea_SpawnAttributes(pNavArea) & CHECKPOINT); } //判断该坐标是否可以看到生还或者距离小于g_fSpawnDistanceMin码,减少一层栈函数,增加实时性,单人模式增加2条射线模仿左右眼 stock bool PlayerVisibleTo(float targetposition[3], bool IsTeleport = false) { - float position[3], vAngles[3], vLookAt[3], spawnPos[3]; - for (int client = 1; client <= MaxClients; ++client) - { - if (IsClientConnected(client) && IsClientInGame(client) && IsValidSurvivor(client) && IsPlayerAlive(client)) - { - //传送的时候无视倒地或者挂边生还者的视线,检测到跑男时,也不关注被控生还者的视线 - if(IsTeleport && (IsClientIncapped(client) || (g_bPickRushMan && IsPinned(client)))){ - if(!g_bIgnoreIncappedSurvivorSight){ - int sum = 0; - float temp[3]; - for(int i = 0; i < MaxClients; i++){ - if(i != client && IsValidSurvivor(i) && !IsClientIncapped(i)){ - GetClientAbsOrigin(i, temp); - //倒地生还者INCAPSURVIVORCHECKDIS范围内已经没有正常生还者,掠过这个人的视线判断 - if(GetVectorDistance(temp, position, true) < Pow(INCAPSURVIVORCHECKDIS, 2.0)){ - sum ++; - } - } - } - if(sum == 0){ - Debug_Print("Teleport方法,目标位置已经不能被正常生还者所看到"); - continue; - }else{ - Debug_Print("Teleport方法,目标位置依旧能被正常生还者看到,sum为:%d", sum); - } - } - else{ - continue; - } - - } - GetClientEyePosition(client, position); - //position[0] += 20; - if(GetVectorDistance(targetposition, position, true) < Pow(g_fSpawnDistanceMin, 2.0)) - { - return true; - } - MakeVectorFromPoints(targetposition, position, vLookAt); - GetVectorAngles(vLookAt, vAngles); - Handle trace = TR_TraceRayFilterEx(targetposition, vAngles, MASK_VISIBLE, RayType_Infinite, TraceFilter, client); - if(TR_DidHit(trace)) - { - static float vStart[3]; - TR_GetEndPosition(vStart, trace); - if((GetVectorDistance(targetposition, vStart, false) + 75.0) >= GetVectorDistance(position, targetposition)) - { - return true; - } - else - { - spawnPos = targetposition; - spawnPos[2] += 40.0; - MakeVectorFromPoints(spawnPos, position, vLookAt); - GetVectorAngles(vLookAt, vAngles); - Handle trace2 = TR_TraceRayFilterEx(spawnPos, vAngles, MASK_VISIBLE, RayType_Infinite, TraceFilter, client); - if(TR_DidHit(trace2)) - { - TR_GetEndPosition(vStart, trace2); - if((GetVectorDistance(spawnPos, vStart, false) + 75.0) >= GetVectorDistance(position, spawnPos)) - return true; - } - else - { - return true; - } - delete trace2; - } - } - else - { - return true; - } - delete trace; - } - } - return false; -} - -//thanks fdxx https://github.com/fdxx/l4d2_plugins/blob/main/l4d2_si_spawn_control.sp -stock bool PlayerVisibleToSDK(float targetposition[3], bool IsTeleport = false){ - static float fTargetPos[3]; - - float position[3]; - fTargetPos = targetposition; - fTargetPos[2] += 62.0; //眼睛位置 - - //计算该位置是不是和所有人都相隔大于g_fSpawnDistanceMax - int count = 0, skipcount = 0; - - for (int client = 1; client <= MaxClients; client++) - { - if (IsClientInGame(client) && GetClientTeam(client) == 2 && IsPlayerAlive(client)) - { - GetClientEyePosition(client, position); - //传送的时候无视倒地或者挂边生还者的视线,检测到跑男时,也不关注被控生还者的视线 - if(IsTeleport && (IsClientIncapped(client) || (g_bPickRushMan && IsPinned(client)))){ - if(!g_bIgnoreIncappedSurvivorSight){ - int sum = 0; - float temp[3]; - for(int i = 1; i <= MaxClients; i++){ - if(i != client && IsValidSurvivor(i) && !IsClientIncapped(i)){ - GetClientAbsOrigin(i, temp); - //倒地生还者500范围内已经没有正常生还者,掠过这个人的视线判断 - if(GetVectorDistance(temp, position, true) < Pow(INCAPSURVIVORCHECKDIS, 2.0)){ - sum ++; - } - } - } - if(sum == 0){ - Debug_Print("Teleport方法,目标位置已经不能被正常生还者所看到"); - skipcount++; - continue; - }else{ - Debug_Print("Teleport方法,目标位置依旧能被正常生还者看到,sum为:%d", sum); - } - }else{ - skipcount++; - continue; - } - } - //太近直接返回看见 - if(GetVectorDistance(targetposition, position, true) < Pow(g_fSpawnDistanceMin, 2.0)) - { - return true; - } - //太远直接返回没看见 - if(GetVectorDistance(targetposition, position, true) >= Pow(g_fSpawnDistanceMax, 2.0)) - { - count++; - if(count >= (g_iSurvivorNum - skipcount)){ - return false; - } - - } - if (L4D2_IsVisibleToPlayer(client, 2, 3, 0, targetposition)) - { - return true; - } - if (L4D2_IsVisibleToPlayer(client, 2, 3, 0, fTargetPos)) - { - return true; - } - } - } - - return false; + float position[3], vAngles[3], vLookAt[3], spawnPos[3]; + for (int client = 1; client <= MaxClients; ++client) + { + if (IsClientConnected(client) && IsClientInGame(client) && IsValidSurvivor(client) && IsPlayerAlive(client)) + { + //传送的时候无视倒地或者挂边生还者的视线,检测到跑男时,也不关注被控生还者的视线 + if (IsTeleport && (IsClientIncapped(client) || (g_bPickRushMan && IsPinned(client)))) + if (!g_bIgnoreIncappedSurvivorSight) + { + int sum = 0; + float temp[3]; + for (int i = 0; i < MaxClients; i++) + if (i != client && IsValidSurvivor(i) && !IsClientIncapped(i)) + { + GetClientAbsOrigin(i, temp); + //倒地生还者INCAPSURVIVORCHECKDIS范围内已经没有正常生还者,掠过这个人的视线判断 + if (GetVectorDistance(temp, position, true) < Pow(INCAPSURVIVORCHECKDIS, 2.0)) + sum++; + } + + if (sum == 0) + { + Debug_Print("Teleport方法,目标位置已经不能被正常生还者所看到"); + continue; + } + else Debug_Print("Teleport方法,目标位置依旧能被正常生还者看到,sum为:%d", sum); + } + else continue; + + GetClientEyePosition(client, position); + // position[0] += 20; + if (GetVectorDistance(targetposition, position, true) < Pow(g_fSpawnDistanceMin, 2.0)) + return true; + + MakeVectorFromPoints(targetposition, position, vLookAt); + GetVectorAngles(vLookAt, vAngles); + Handle trace = TR_TraceRayFilterEx(targetposition, vAngles, MASK_VISIBLE, RayType_Infinite, TraceFilter, client); + if (TR_DidHit(trace)) + { + static float vStart[3]; + TR_GetEndPosition(vStart, trace); + delete trace; // 用完就 delete,不然迟早会忘 + if ((GetVectorDistance(targetposition, vStart, false) + 75.0) >= GetVectorDistance(position, targetposition)) + return true; + + // else // 都 return 了就别 else 了,一堆 大括号 + 缩进 看着眼疼 + // { + spawnPos = targetposition; + spawnPos[2] += 40.0; + MakeVectorFromPoints(spawnPos, position, vLookAt); + GetVectorAngles(vLookAt, vAngles); + Handle trace2 = TR_TraceRayFilterEx(spawnPos, vAngles, MASK_VISIBLE, RayType_Infinite, TraceFilter, client); + if (TR_DidHit(trace2)) + { + TR_GetEndPosition(vStart, trace2); + delete trace2; + if ((GetVectorDistance(spawnPos, vStart, false) + 75.0) >= GetVectorDistance(position, spawnPos)) + return true; + } + else delete trace2; + + return true; + // delete trace2; // 你都 return 了,还怎么delete??? + // } + } + else delete trace; + + return true; + // delete trace; // 你都 return 了,还怎么delete??? + } + } + return false; +} + +// thanks fdxx https://github.com/fdxx/l4d2_plugins/blob/main/l4d2_si_spawn_control.sp +stock bool PlayerVisibleToSDK(float targetposition[3], bool IsTeleport = false) +{ + static float fTargetPos[3]; + + float position[3]; + fTargetPos = targetposition; + fTargetPos[2] += 62.0; //眼睛位置 + + //计算该位置是不是和所有人都相隔大于g_fSpawnDistanceMax + int count = 0, skipcount = 0; + + for (int client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client) && GetClientTeam(client) == 2 && IsPlayerAlive(client)) + { + GetClientEyePosition(client, position); + //传送的时候无视倒地或者挂边生还者的视线,检测到跑男时,也不关注被控生还者的视线 + if (IsTeleport && (IsClientIncapped(client) || (g_bPickRushMan && IsPinned(client)))) + { + if (!g_bIgnoreIncappedSurvivorSight) + { + int sum = 0; + float temp[3]; + for (int i = 1; i <= MaxClients; i++) + if (i != client && IsValidSurvivor(i) && !IsClientIncapped(i)) + { + GetClientAbsOrigin(i, temp); + //倒地生还者500范围内已经没有正常生还者,掠过这个人的视线判断 + if (GetVectorDistance(temp, position, true) < Pow(INCAPSURVIVORCHECKDIS, 2.0)) + sum++; + } + + if (sum == 0) + { + Debug_Print("Teleport方法,目标位置已经不能被正常生还者所看到"); + skipcount++; + continue; + } + else Debug_Print("Teleport方法,目标位置依旧能被正常生还者看到,sum为:%d", sum); + } + else + { + skipcount++; + continue; + } + } + //太近直接返回看见 + if (GetVectorDistance(targetposition, position, true) < Pow(g_fSpawnDistanceMin, 2.0)) + return true; + + //太远直接返回没看见 + if (GetVectorDistance(targetposition, position, true) >= Pow(g_fSpawnDistanceMax, 2.0)) + { + count++; + if (count >= (g_iSurvivorNum - skipcount)) + return false; + } + if (L4D2_IsVisibleToPlayer(client, 2, 3, 0, targetposition)) + return true; + if (L4D2_IsVisibleToPlayer(client, 2, 3, 0, fTargetPos)) + return true; + } + } + + return false; } bool IsPlayerStuck(float fSpawnPos[3]) { - //似乎所有客户端的尺寸都一样 - static const float fClientMinSize[3] = {-16.0, -16.0, 0.0}; - static const float fClientMaxSize[3] = {16.0, 16.0, 72.0}; + //似乎所有客户端的尺寸都一样 + static const float fClientMinSize[3] = { -16.0, -16.0, 0.0 }; + static const float fClientMaxSize[3] = { 16.0, 16.0, 72.0 }; - static bool bHit; - static Handle hTrace; + static bool bHit; + static Handle hTrace; - hTrace = TR_TraceHullFilterEx(fSpawnPos, fSpawnPos, fClientMinSize, fClientMaxSize, MASK_PLAYERSOLID, TraceFilter_Stuck); - bHit = TR_DidHit(hTrace); + hTrace = TR_TraceHullFilterEx(fSpawnPos, fSpawnPos, fClientMinSize, fClientMaxSize, MASK_PLAYERSOLID, TraceFilter_Stuck); + bHit = TR_DidHit(hTrace); - delete hTrace; - return bHit; + delete hTrace; + return bHit; } stock bool TraceFilter_Stuck(int entity, int contentsMask) { - if (entity <= MaxClients || !IsValidEntity(entity)) - { - return false; - } - else{ - static char sClassName[20]; - GetEntityClassname(entity, sClassName, sizeof(sClassName)); - if (strcmp(sClassName, "env_physics_blocker") == 0 && !EnvBlockType(entity)){ - return false; - } - } - return true; -} - -stock bool EnvBlockType(int entity){ - int BlockType = GetEntProp(entity, Prop_Data, "m_nBlockType"); - //阻拦ai infected - if(BlockType == 1 || BlockType == 2){ - return false; - } - else{ - return true; - } + if (entity <= MaxClients || !IsValidEntity(entity)) + return false; + + static char sClassName[20]; + GetEntityClassname(entity, sClassName, sizeof(sClassName)); + if (strcmp(sClassName, "env_physics_blocker") == 0 && !EnvBlockType(entity)) + return false; + + return true; +} + +stock bool EnvBlockType(int entity) +{ + int BlockType = GetEntProp(entity, Prop_Data, "m_nBlockType"); + //阻拦ai infected + // if (BlockType == 1 || BlockType == 2) + // return false; + // else + // return true; + return !(BlockType == 1 || BlockType == 2); } stock bool TraceFilter(int entity, int contentsMask) { - if (entity <= MaxClients || !IsValidEntity(entity)) - { - return false; - } - else - { - static char sClassName[9]; - GetEntityClassname(entity, sClassName, sizeof(sClassName)); - if (strcmp(sClassName, "infected") == 0 || strcmp(sClassName, "witch") == 0) - { - return false; - } - } - return true; + if (entity <= MaxClients || !IsValidEntity(entity)) + return false; + + static char sClassName[9]; + GetEntityClassname(entity, sClassName, sizeof(sClassName)); + if (strcmp(sClassName, "infected") == 0 || strcmp(sClassName, "witch") == 0) + return false; + + return true; } bool IsPinned(int client) { - bool bIsPinned = false; - if (IsValidSurvivor(client) && IsPlayerAlive(client)) - { - if(GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) bIsPinned = true; - if(GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0) bIsPinned = true; - if(GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0) bIsPinned = true; - if(GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0) bIsPinned = true; - if(GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0) bIsPinned = true; - } - return bIsPinned; + if (!(IsValidSurvivor(client) && IsPlayerAlive(client))) return false; + + return GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0 + || GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 + || GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 + || GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 + || GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0; } bool IsPinningSomeone(int client) { - bool bIsPinning = false; - if (IsInfectedBot(client)) - { - if (GetEntPropEnt(client, Prop_Send, "m_tongueVictim") > 0) bIsPinning = true; - if (GetEntPropEnt(client, Prop_Send, "m_jockeyVictim") > 0) bIsPinning = true; - if (GetEntPropEnt(client, Prop_Send, "m_pounceVictim") > 0) bIsPinning = true; - if (GetEntPropEnt(client, Prop_Send, "m_pummelVictim") > 0) bIsPinning = true; - if (GetEntPropEnt(client, Prop_Send, "m_carryVictim") > 0) bIsPinning = true; - } - return bIsPinning; + bool bIsPinning = false; + if (IsInfectedBot(client)) + { + if (GetEntPropEnt(client, Prop_Send, "m_tongueVictim") > 0) bIsPinning = true; + if (GetEntPropEnt(client, Prop_Send, "m_jockeyVictim") > 0) bIsPinning = true; + if (GetEntPropEnt(client, Prop_Send, "m_pounceVictim") > 0) bIsPinning = true; + if (GetEntPropEnt(client, Prop_Send, "m_pummelVictim") > 0) bIsPinning = true; + if (GetEntPropEnt(client, Prop_Send, "m_carryVictim") > 0) bIsPinning = true; + } + return bIsPinning; } bool CanBeTeleport(int client) { - if (IsInfectedBot(client) && IsClientInGame(client)&& IsPlayerAlive(client) && GetEntProp(client, Prop_Send, "m_zombieClass") != ZC_TANK && !IsPinningSomeone(client)) - { - // 防止无声口水 - if(IsSpitter(client) && GetGameTime() - g_fSpitterSpitTime[client] < SPIT_INTERVAL) - { - return false; - } - - if(GetClosetSurvivorDistance(client) < g_fSpawnDistanceMin) - { - return false; - } - - //舌头能力检查 - if(IsAiSmoker(client) && g_bSmokerAvailable && !IsSmokerCanUseAbility(client)) - { - return false; - } - float fPos[3]; - GetClientAbsOrigin(client, fPos); - if(Is_Pos_Ahead(fPos)) - { - return false; - } - return true; - } - else - { - return false; - } -} - -//5秒内以1s检测一次,5次没被看到,就可以踢出并加入传送队列 -public Action Timer_PositionSi(Handle timer) -{ - if(IsInPause()) - { - Debug_Print("处于暂停状态,停止传送检测"); - return Plugin_Continue; - } - //每1s找一次跑男或者是否所有全被控 - if(CheckRushManAndAllPinned()) - { - return Plugin_Continue; - } - for (int client = 1; client <= MaxClients; client++) - { - if(CanBeTeleport(client)){ - float fSelfPos[3] = {0.0}; - GetClientEyePosition(client, fSelfPos); - if (!PlayerVisibleToSDK(fSelfPos, true)) - { - // 如果是跑男状态,只要1s没被看到后就能传送 - if ((g_iTeleCount[client] > g_iTeleportCheckTime || (g_bPickRushMan && g_iTeleCount[client] > 0))) - { - int type = GetInfectedClass(client); - if(type >= 1 && type <= 6){ - if(g_iTeleportIndex == 0) - { - g_fTeleportDistance = g_fSpawnDistanceMin; - } - aTeleportQueue.Push(type); - g_iTeleportIndex += 1; - Debug_Print("<传送队列> %N踢出,进入传送队列,当前 <传送队列> 队列长度:%d 队列索引:%d 当前记录特感总数为:%d , 真实数量为:%d", client, aTeleportQueue.Length, g_iTeleportIndex, g_iTotalSINum, GetCurrentSINum()); - //不再单独处理spitter防止无声口水,已经在canbeteleport处理 - if(g_iSINum[type - 1] > 0) - { - g_iSINum[type - 1] --; - } - else - { - g_iSINum[type - 1] = 0; - } - if(g_iTotalSINum > 0) - { - g_iTotalSINum --; - } - else - { - g_iTotalSINum = 0; - } - KickClient(client, "传送刷特,踢出"); - //Debug_Print("当前 <传送队列> 队列长度:%d 队列索引:%d 当前记录特感总数为:%d , 真实数量为:%d", aTeleportQueue.Length, g_iTeleportIndex, g_iTotalSINum, GetCurrentSINum()); - g_iTeleCount[client] = 0; - } - } - g_iTeleCount[client] += 1; - } - else{ - g_iTeleCount[client] = 0; - } - } - - } - //每1s找一次攻击目标,主要用于检测跑男,正常情况ongameframe会调用找攻击目标 - g_iTargetSurvivor = GetTargetSurvivor(); - return Plugin_Continue; + if (IsInfectedBot(client) && IsClientInGame(client) && IsPlayerAlive(client) && GetEntProp(client, Prop_Send, "m_zombieClass") != ZC_TANK && !IsPinningSomeone(client)) + { + // 防止无声口水 (使用 SIPool 后无此问题) + if (!g_bSIPoolAvailable && IsSpitter(client) && GetGameTime() - g_fSpitterSpitTime[client] < SPIT_INTERVAL) + return false; + + if (GetClosetSurvivorDistance(client) < g_fSpawnDistanceMin) + return false; + + //舌头能力检查 + if (IsAiSmoker(client) && g_bSmokerAvailable && !IsSmokerCanUseAbility(client)) + return false; + + float fPos[3]; + GetClientAbsOrigin(client, fPos); + if (Is_Pos_Ahead(fPos)) + return false; + + return true; + } + + return false; +} + +// 5秒内以1s检测一次,5次没被看到,就可以踢出并加入传送队列 +Action Timer_PositionSi(Handle timer) +{ + if (IsInPause()) + { + Debug_Print("处于暂停状态,停止传送检测"); + return Plugin_Continue; + } + + //每1s找一次跑男或者是否所有全被控 + if (CheckRushManAndAllPinned()) + return Plugin_Continue; + + for (int client = 1; client <= MaxClients; client++) + { + if (CanBeTeleport(client)) + { + float fSelfPos[3] = { 0.0 }; + GetClientEyePosition(client, fSelfPos); + if (!PlayerVisibleToSDK(fSelfPos, true)) + { + // 如果是跑男状态,只要1s没被看到后就能传送 + if ((g_iTeleCount[client] > g_iTeleportCheckTime || (g_bPickRushMan && g_iTeleCount[client] > 0))) + { + int type = GetInfectedClass(client); + if (type >= 1 && type <= 6) + { + if (g_iTeleportIndex == 0) + g_fTeleportDistance = g_fSpawnDistanceMin; + + aTeleportQueue.Push(type); + g_iTeleportIndex += 1; + Debug_Print("<传送队列> %N踢出,进入传送队列,当前 <传送队列> 队列长度:%d 队列索引:%d 当前记录特感总数为:%d , 真实数量为:%d", client, aTeleportQueue.Length, g_iTeleportIndex, g_iTotalSINum, GetCurrentSINum()); + //不再单独处理spitter防止无声口水,已经在canbeteleport处理 + if (g_iSINum[type - 1] > 0) g_iSINum[type - 1]--; + else g_iSINum[type - 1] = 0; + + if (g_iTotalSINum > 0) g_iTotalSINum--; + else g_iTotalSINum = 0; + + // KickClient(client, "传送刷特,踢出"); + if (g_bSIPoolAvailable) g_hSIPool.ReturnSIBot(client); + else KickClient(client, "传送刷特,踢出"); + + Debug_Print("当前 <传送队列> 队列长度:%d 队列索引:%d 当前记录特感总数为:%d , 真实数量为:%d", aTeleportQueue.Length, g_iTeleportIndex, g_iTotalSINum, GetCurrentSINum()); + g_iTeleCount[client] = 0; + } + } + g_iTeleCount[client] += 1; + } + else g_iTeleCount[client] = 0; + } + } + //每1s找一次攻击目标,主要用于检测跑男,正常情况ongameframe会调用找攻击目标 + g_iTargetSurvivor = GetTargetSurvivor(); + return Plugin_Continue; } stock int GetCurrentSINum() { - int sum = 0; - for(int i = 0; i < MaxClients; i++){ - if(IsInfectedBot(i) && IsPlayerAlive(i)) - { - sum ++; - } - } - return sum; + int sum = 0; + for (int i = 0; i < MaxClients; i++) + if (IsInfectedBot(i) && !IsPlayerAlive(i)) + sum++; + + return sum; } stock bool IsSpitter(int client) { - if (IsInfectedBot(client) && IsPlayerAlive(client) && GetEntProp(client, Prop_Send, "m_zombieClass") == ZC_SPITTER) - { - return true; - } - else - { - return false; - } + // if (IsInfectedBot(client) && IsPlayerAlive(client) && GetEntProp(client, Prop_Send, "m_zombieClass") == ZC_SPITTER) + // { + // return true; + // } + // else + // { + // return false; + // } + return IsInfectedBot(client) && IsPlayerAlive(client) && (GetEntProp(client, Prop_Send, "m_zombieClass") == ZC_SPITTER); } // 跑男定义为距离所有生还者或者特感超过RushManDistance距离 bool CheckRushManAndAllPinned() { - bool TempRushMan = g_bPickRushMan; - int iSurvivors[8] = {0}, iSurvivorIndex = 0, PinnedNumber = 0; - int iInfecteds[MAXPLAYERS] = {0}, iInfectedIndex = 0; - float fInfectedssOrigin[MAXPLAYERS][3], fSurvivorsOrigin[8][3], OriginTemp[3]; - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidSurvivor(client) && IsPlayerAlive(client)) - { - if(IsPinned(client) || IsClientIncapped(client)){ - PinnedNumber++; - } - GetClientAbsOrigin(client, OriginTemp); - if(iSurvivorIndex < 8) - { - fSurvivorsOrigin[iSurvivorIndex] = OriginTemp; - iSurvivors[iSurvivorIndex++] = client; - } - } - else if(IsInfectedBot(client) && IsPlayerAlive(client)) - { - iInfecteds[iInfectedIndex] = client; - GetClientAbsOrigin(client, OriginTemp); - fInfectedssOrigin[iInfectedIndex++] = OriginTemp; - } - } - if(iSurvivorIndex == 1) - { - //一个人有什么跑男 - return false; - } - int target = L4D_GetHighestFlowSurvivor(); - if (iSurvivorIndex >= 1 && IsValidClient(target)) - { - GetClientAbsOrigin(target, OriginTemp); - bool testSurvior = false; - if(iSurvivorIndex == 1) - { - testSurvior = true; - } - for(int i =0; i < iSurvivorIndex && !testSurvior; i++){ - if(IsPinned(target) || IsClientIncapped(target) || (iSurvivors[i] != target && GetVectorDistance(fSurvivorsOrigin[i], OriginTemp, true) <= Pow(RushManDistance, 2.0))) - { - testSurvior = true; - break; - } - } - if(!testSurvior || g_iTotalSINum < (g_iSiLimit / 2 + 1)) - { - g_bPickRushMan = false; - g_iRushManIndex = -1; - if(TempRushMan != g_bPickRushMan){ - StartForward(g_bPickRushMan); - } - return PinnedNumber == iSurvivorIndex; - } - else - { - for(int i =0; i < iInfectedIndex; i++) - { - if(IsPinned(target) || IsClientIncapped(target) || (GetVectorDistance(fInfectedssOrigin[i], OriginTemp, true) <= Pow(RushManDistance, 2.0) * 1.3)) - { - g_bPickRushMan = false; - g_iRushManIndex = -1; - if(TempRushMan != g_bPickRushMan){ - StartForward(g_bPickRushMan); - } - return PinnedNumber == iSurvivorIndex; - } - } - } - if(!testSurvior) - { - Debug_Print("跑男由于和其他正常生还者过远触发") ; - } - else - { - Debug_Print("跑男由于和特感过远触发") ; - } - g_bPickRushMan = true; - g_iRushManIndex = target; - if(TempRushMan != g_bPickRushMan){ - StartForward(g_bPickRushMan); - } - } - return PinnedNumber == iSurvivorIndex; + bool TempRushMan = g_bPickRushMan; + int iSurvivors[8] = { 0 }, iSurvivorIndex = 0, PinnedNumber = 0; + int iInfecteds[MAXPLAYERS] = { 0 }, iInfectedIndex = 0; + float fInfectedssOrigin[MAXPLAYERS][3], fSurvivorsOrigin[8][3], OriginTemp[3]; + for (int client = 1; client <= MaxClients; client++) + { + if (IsValidSurvivor(client) && IsPlayerAlive(client)) + { + if (IsPinned(client) || IsClientIncapped(client)) + PinnedNumber++; + + GetClientAbsOrigin(client, OriginTemp); + if (iSurvivorIndex < 8) + { + fSurvivorsOrigin[iSurvivorIndex] = OriginTemp; + iSurvivors[iSurvivorIndex++] = client; + } + } + else if (IsInfectedBot(client) && IsPlayerAlive(client)) + { + iInfecteds[iInfectedIndex] = client; + GetClientAbsOrigin(client, OriginTemp); + fInfectedssOrigin[iInfectedIndex++] = OriginTemp; + } + } + + //一个人有什么跑男 + if (iSurvivorIndex == 1) + return false; + + int target = L4D_GetHighestFlowSurvivor(); + if (iSurvivorIndex >= 1 && IsValidClient(target)) + { + GetClientAbsOrigin(target, OriginTemp); + bool testSurvior = false; + if (iSurvivorIndex == 1) + testSurvior = true; + + for (int i = 0; i < iSurvivorIndex && !testSurvior; i++) + if (IsPinned(target) || IsClientIncapped(target) || (iSurvivors[i] != target && GetVectorDistance(fSurvivorsOrigin[i], OriginTemp, true) <= Pow(RushManDistance, 2.0))) + { + testSurvior = true; + break; + } + + if (!testSurvior || g_iTotalSINum < (g_iSiLimit / 2 + 1)) + { + g_bPickRushMan = false; + g_iRushManIndex = -1; + if (TempRushMan != g_bPickRushMan) + StartForward(g_bPickRushMan); + + return PinnedNumber == iSurvivorIndex; + } + else + for (int i = 0; i < iInfectedIndex; i++) + if (IsPinned(target) || IsClientIncapped(target) || (GetVectorDistance(fInfectedssOrigin[i], OriginTemp, true) <= Pow(RushManDistance, 2.0) * 1.3)) + { + g_bPickRushMan = false; + g_iRushManIndex = -1; + if (TempRushMan != g_bPickRushMan) + StartForward(g_bPickRushMan); + + return PinnedNumber == iSurvivorIndex; + } + + if (!testSurvior) + Debug_Print("跑男由于和其他正常生还者过远触发"); + else Debug_Print("跑男由于和特感过远触发"); + + g_bPickRushMan = true; + g_iRushManIndex = target; + if (TempRushMan != g_bPickRushMan) + StartForward(g_bPickRushMan); + } + return PinnedNumber == iSurvivorIndex; } int GetTargetSurvivor() { - //如果有跑男,抓跑男 - if(g_bPickRushMan && IsValidSurvivor(g_iRushManIndex) && IsPlayerAlive(g_iRushManIndex) && !IsPinned(g_iRushManIndex)){ - return g_iRushManIndex; - } - //没有跑男,抓目标未满的生还者 - else - { - int iSurvivors[8] = {0} , iSurvivorIndex = 0; - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidSurvivor(client) && IsPlayerAlive(client) && (!IsPinned(client) || !IsClientIncapped(client))) - { - g_bIsLate = true; - if(g_bTargetSystemAvailable && IsClientReachLimit(client)) - { - continue; - } - if (iSurvivorIndex < 8) - { - iSurvivors[iSurvivorIndex] = client; - iSurvivorIndex += 1; - } - } - } - if (iSurvivorIndex > 0) - { - return iSurvivors[GetRandomInt(0, iSurvivorIndex - 1)]; - } - else{ - return L4D_GetHighestFlowSurvivor(); - } - } -} - -void StartForward(bool IsRush){ - Debug_Print("跑男检测状态变化,发送forward"); - Call_StartForward(g_hRushManNotifyForward);//转发触发 - Call_PushCell(IsRush);//按顺序将参数push进forward传参列表里 - Call_Finish();//转发结束 + //如果有跑男,抓跑男 + if (g_bPickRushMan && IsValidSurvivor(g_iRushManIndex) && IsPlayerAlive(g_iRushManIndex) && !IsPinned(g_iRushManIndex)) + return g_iRushManIndex; + + //没有跑男,抓目标未满的生还者 + int iSurvivors[8] = { 0 }, iSurvivorIndex = 0; + for (int client = 1; client <= MaxClients; client++) + { + if (IsValidSurvivor(client) && IsPlayerAlive(client) && (!IsPinned(client) || !IsClientIncapped(client))) + { + g_bIsLate = true; + if (g_bTargetSystemAvailable && IsClientReachLimit(client)) + continue; + + if (iSurvivorIndex < 8) + { + iSurvivors[iSurvivorIndex] = client; + iSurvivorIndex += 1; + } + } + } + if (iSurvivorIndex > 0) + return iSurvivors[GetRandomInt(0, iSurvivorIndex - 1)]; + + return L4D_GetHighestFlowSurvivor(); +} + +void StartForward(bool IsRush) +{ + Debug_Print("跑男检测状态变化,发送forward"); + Call_StartForward(g_hRushManNotifyForward); //转发触发 + Call_PushCell(IsRush); //按顺序将参数push进forward传参列表里 + Call_Finish(); //转发结束 } stock bool IsAiSmoker(int client) { - if (client && client <= MaxClients && IsClientInGame(client) && IsPlayerAlive(client) && IsFakeClient(client) && GetClientTeam(client) == TEAM_INFECTED && GetEntProp(client, Prop_Send, "m_zombieClass") == 1 && GetEntProp(client, Prop_Send, "m_isGhost") != 1) - { - return true; - } - else - { - return false; - } + // if (client && client <= MaxClients && IsClientInGame(client) && IsPlayerAlive(client) && IsFakeClient(client) && GetClientTeam(client) == TEAM_INFECTED && GetEntProp(client, Prop_Send, "m_zombieClass") == 1 && GetEntProp(client, Prop_Send, "m_isGhost") != 1) + // { + // return true; + // } + // else + // { + // return false; + // } + + return client && client <= MaxClients && IsClientInGame(client) && IsPlayerAlive(client) + && IsFakeClient(client) && (GetClientTeam(client) == TEAM_INFECTED) + && (GetEntProp(client, Prop_Send, "m_zombieClass") == 1) && (GetEntProp(client, Prop_Send, "m_isGhost") != 1); } stock bool IsAiTank(int client) { - if (client && client <= MaxClients && IsClientInGame(client) && IsPlayerAlive(client) && IsFakeClient(client) && GetClientTeam(client) == TEAM_INFECTED && GetEntProp(client, Prop_Send, "m_zombieClass") == 8 && GetEntProp(client, Prop_Send, "m_isGhost") != 1) - { - return true; - } - else - { - return false; - } + // if (client && client <= MaxClients && IsClientInGame(client) && IsPlayerAlive(client) && IsFakeClient(client) && GetClientTeam(client) == TEAM_INFECTED && GetEntProp(client, Prop_Send, "m_zombieClass") == 8 && GetEntProp(client, Prop_Send, "m_isGhost") != 1) + // { + // return true; + // } + // else + // { + // return false; + // } + + return client && client <= MaxClients && IsClientInGame(client) && IsPlayerAlive(client) + && IsFakeClient(client) && (GetClientTeam(client) == TEAM_INFECTED) + && (GetEntProp(client, Prop_Send, "m_zombieClass") == 8) && (GetEntProp(client, Prop_Send, "m_isGhost") != 1); } stock bool IsGhost(int client) @@ -1550,186 +1506,182 @@ stock bool IsGhost(int client) } //获取队列里Hunter和Charger数量 -stock int getArrayHunterAndChargetNum(){ - int count = 0; - for(int i = 0; i < aSpawnQueue.Length; i++){ - int type = aSpawnQueue.Get(i); - if(type == 3 || type == 6){ - count++; - } - } - return count; -} -stock int getArrayDominateSINum(){ - int count = 0; - for(int i = 0; i < aSpawnQueue.Length; i++){ - int type = aSpawnQueue.Get(i); - if(type != 2 || type == 4){ - count++; - } - } - return count; +stock int getArrayHunterAndChargetNum() +{ + int count = 0; + for (int i = 0; i < aSpawnQueue.Length; i++) + { + int type = aSpawnQueue.Get(i); + if (type == 3 || type == 6) + count++; + } + return count; +} + +stock int getArrayDominateSINum() +{ + int count = 0; + for (int i = 0; i < aSpawnQueue.Length; i++) + { + int type = aSpawnQueue.Get(i); + if (type != 2 || type == 4) + count++; + } + return count; } // 返回在场特感数量,根据 z_%s_limit 限制每种特感上限 bool MeetRequire(int iType) { - if(g_hAllChargerMode.BoolValue || g_hAllHunterMode.BoolValue){ - return true; - } - GetSiLimit(); - if (iType == 1) - { - if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) - { - return true; - } - } - else if (iType == 2) - { - if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0) && ((getArrayDominateSINum() > (g_iSiLimit/4 +1)) || (g_iQueueIndex >= g_iSiLimit -2))) - { - return true; - } - } - else if (iType == 3) - { - if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) - { - return true; - } - } - else if (iType == 4) - { - if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0) && ((getArrayHunterAndChargetNum() > (g_iSiLimit/5 +1) || (g_iQueueIndex >= g_iSiLimit -2)))) - { - return true; - } - } - else if (iType == 5) - { - if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) - { - return true; - } - } - else if (iType == 6) - { - if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) - { - return true; - } - } - return false; + if (g_hAllChargerMode.BoolValue || g_hAllHunterMode.BoolValue) + return true; + + GetSiLimit(); + if (iType < 1 || iType > 6) return false; + switch (iType) + { + case 2: + if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0) && ((getArrayDominateSINum() > (g_iSiLimit / 4 + 1)) || (g_iQueueIndex >= g_iSiLimit - 2))) + return true; + case 4: + if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0) && ((getArrayHunterAndChargetNum() > (g_iSiLimit / 5 + 1) || (g_iQueueIndex >= g_iSiLimit - 2)))) + return true; + default: + if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) + return true; + } + return false; + // if (iType == 1) + // { + // if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) + // return true; + // } + // else if (iType == 2) + // { + // if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0) && ((getArrayDominateSINum() > (g_iSiLimit / 4 + 1)) || (g_iQueueIndex >= g_iSiLimit - 2))) + // return true; + // } + // else if (iType == 3) + // { + // if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) + // return true; + // } + // else if (iType == 4) + // { + // if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0) && ((getArrayHunterAndChargetNum() > (g_iSiLimit / 5 + 1) || (g_iQueueIndex >= g_iSiLimit - 2)))) + // return true; + // } + // else if (iType == 5) + // { + // if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) + // return true; + // } + // else if (iType == 6) + // { + // if (CheckSIOption(iType) && (g_ArraySIlimit[iType - 1] > 0)) + // return true; + // } + // return false; } // 特感种类限制数组,刷完一波特感时重新读取 Cvar 数值,重置特感种类限制数量 void GetSiLimit() { - g_ArraySIlimit[0] = GetConVarInt(FindConVar("z_smoker_limit")); - g_ArraySIlimit[1] = GetConVarInt(FindConVar("z_boomer_limit")); - g_ArraySIlimit[2] = GetConVarInt(FindConVar("z_hunter_limit")); - g_ArraySIlimit[3] = GetConVarInt(FindConVar("z_spitter_limit")); - g_ArraySIlimit[4] = GetConVarInt(FindConVar("z_jockey_limit")); - g_ArraySIlimit[5] = GetConVarInt(FindConVar("z_charger_limit")); - //删除队列里已有元素 - for(int i = 0; i < aSpawnQueue.Length; i++){ - int type = aSpawnQueue.Get(i); - if(type > 0 && type < 7){ - if(g_ArraySIlimit[type - 1] > 0){ - g_ArraySIlimit[type - 1]--; - } - else - { - g_ArraySIlimit[type - 1] = 0; - } - } - } + g_ArraySIlimit[0] = GetConVarInt(FindConVar("z_smoker_limit")); + g_ArraySIlimit[1] = GetConVarInt(FindConVar("z_boomer_limit")); + g_ArraySIlimit[2] = GetConVarInt(FindConVar("z_hunter_limit")); + g_ArraySIlimit[3] = GetConVarInt(FindConVar("z_spitter_limit")); + g_ArraySIlimit[4] = GetConVarInt(FindConVar("z_jockey_limit")); + g_ArraySIlimit[5] = GetConVarInt(FindConVar("z_charger_limit")); + //删除队列里已有元素 + for (int i = 0; i < aSpawnQueue.Length; i++) + { + int type = aSpawnQueue.Get(i); + if (type > 0 && type < 7) + { + if (g_ArraySIlimit[type - 1] > 0) + g_ArraySIlimit[type - 1]--; + else g_ArraySIlimit[type - 1] = 0; + } + } } // 判断一个坐标是否在当前最高路程的生还者前面 bool Is_Pos_Ahead(float refpos[3], int target = -1) { - int pos_flow = 0, target_flow = 0; - Address pNowNav = L4D2Direct_GetTerrorNavArea(refpos); - if (pNowNav == Address_Null) - { - pNowNav = view_as
(L4D_GetNearestNavArea(refpos, 300.0)); - } - pos_flow = Calculate_Flow(pNowNav); - if(target == -1) - { - target = L4D_GetHighestFlowSurvivor(); - } - if (IsValidSurvivor(target)) - { - float targetpos[3] = {0.0}; - GetClientAbsOrigin(target, targetpos); - Address pTargetNav = L4D2Direct_GetTerrorNavArea(targetpos); - if (pTargetNav == Address_Null) - { - pTargetNav = view_as
(L4D_GetNearestNavArea(refpos, 300.0)); - } - target_flow = Calculate_Flow(pTargetNav); - } - return view_as(pos_flow >= target_flow); + int pos_flow = 0, target_flow = 0; + Address pNowNav = L4D2Direct_GetTerrorNavArea(refpos); + if (pNowNav == Address_Null) + pNowNav = view_as
(L4D_GetNearestNavArea(refpos, 300.0)); + + pos_flow = Calculate_Flow(pNowNav); + if (target == -1) + target = L4D_GetHighestFlowSurvivor(); + + if (IsValidSurvivor(target)) + { + float targetpos[3] = { 0.0 }; + GetClientAbsOrigin(target, targetpos); + Address pTargetNav = L4D2Direct_GetTerrorNavArea(targetpos); + if (pTargetNav == Address_Null) + pTargetNav = view_as
(L4D_GetNearestNavArea(refpos, 300.0)); + + target_flow = Calculate_Flow(pTargetNav); + } + return view_as(pos_flow >= target_flow); } + int Calculate_Flow(Address pNavArea) { - float now_nav_flow = L4D2Direct_GetTerrorNavAreaFlow(pNavArea) / L4D2Direct_GetMapMaxFlowDistance(); - float now_nav_promixity = now_nav_flow + g_hVsBossFlowBuffer.FloatValue / L4D2Direct_GetMapMaxFlowDistance(); - if (now_nav_promixity > 1.0) - { - now_nav_promixity = 1.0; - } - return RoundToNearest(now_nav_promixity * 100.0); + float now_nav_flow = L4D2Direct_GetTerrorNavAreaFlow(pNavArea) / L4D2Direct_GetMapMaxFlowDistance(); + float now_nav_promixity = now_nav_flow + g_hVsBossFlowBuffer.FloatValue / L4D2Direct_GetMapMaxFlowDistance(); + if (now_nav_promixity > 1.0) + now_nav_promixity = 1.0; + + return RoundToNearest(now_nav_promixity * 100.0); } // @key:需要调整的 key 值 // @retVal:原 value 值,使用 return Plugin_Handled 覆盖 public Action L4D_OnGetScriptValueInt(const char[] key, int &retVal) { - if ((strcmp(key, "cm_ShouldHurry", false) == 0) || (strcmp(key, "cm_AggressiveSpecials", false) == 0) && retVal != 1) - { - retVal = 1; - return Plugin_Handled; - } - return Plugin_Continue; + if ((strcmp(key, "cm_ShouldHurry", false) == 0) || (strcmp(key, "cm_AggressiveSpecials", false) == 0) && retVal != 1) + { + retVal = 1; + return Plugin_Handled; + } + return Plugin_Continue; } -stock void Debug_Print(char[] format, any ...) +stock void Debug_Print(char[] format, any...) { - #if (DEBUG) - { - char sTime[32]; - FormatTime(sTime, sizeof(sTime), "%I-%M-%S", GetTime()); - char sBuffer[512]; - VFormat(sBuffer, sizeof(sBuffer), format, 2); - Format(sBuffer, sizeof(sBuffer), "[%s] %s: %s", "DEBUG", sTime, sBuffer); - // PrintToChatAll(sBuffer); - PrintToConsoleAll(sBuffer); - PrintToServer(sBuffer); - LogToFile(sLogFile, sBuffer); - } - #endif +#if (DEBUG) + { + char sTime[32]; + FormatTime(sTime, sizeof(sTime), "%I-%M-%S", GetTime()); + char sBuffer[512]; + VFormat(sBuffer, sizeof(sBuffer), format, 2); + Format(sBuffer, sizeof(sBuffer), "[%s] %s: %s", "DEBUG", sTime, sBuffer); + // PrintToChatAll(sBuffer); + PrintToConsoleAll(sBuffer); + PrintToServer(sBuffer); + LogToFile(sLogFile, sBuffer); + } +#endif } stock bool IsAnyTankOrAboveHalfSurvivorDownOrDied() { - int count = 0; - for(int i = 1; i <= MaxClients; i ++) - { - if(IsAiTank(i)) - return true; - if(IsValidSurvivor(i) && (L4D_IsPlayerIncapacitated(i) || !IsPlayerAlive(i))) - { - count ++; - } - } - if(count >= RoundToCeil(FindConVar("survivor_limit").IntValue / 2.0)) - { - return true; - } - return false; -} + int count = 0; + for (int i = 1; i <= MaxClients; i++) + { + if (IsAiTank(i)) + return true; + if (IsValidSurvivor(i) && (L4D_IsPlayerIncapacitated(i) || !IsPlayerAlive(i))) + count++; + } + if (count >= RoundToCeil(FindConVar("survivor_limit").IntValue / 2.0)) + return true; + return false; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/AnneHappy/si_pool.sp b/addons/sourcemod/scripting/AnneHappy/si_pool.sp new file mode 100644 index 000000000..39b18d1f5 --- /dev/null +++ b/addons/sourcemod/scripting/AnneHappy/si_pool.sp @@ -0,0 +1,486 @@ +/* + * @Author: 我是派蒙啊 + * @Last Modified by: 我是派蒙啊 + * @Create Date: 2024-02-17 11:15:10 + * @Last Modified time: 2024-03-26 13:39:14 + * @Github: https://github.com/Paimon-Kawaii + */ + +#pragma semicolon 1 +#pragma newdecls required + +#define DEBUG 0 + +#if DEBUG + #define LOGFILE "addons/sourcemod/logs/si_pool_log.txt" +#endif + +#define VERSION "2024.03.26#121" + +#define LIBRARY_NAME "si_pool" +#define GAMEDATA_FILE "si_pool" + +#include + +#include +#include + +#include + +public Plugin myinfo = +{ + name = "Special Infected Bot Client Pool", + author = "我是派蒙啊", + description = "A Client Pool for SI Bots, used to avoid lots of CreateFakeClient() operation", + version = VERSION, + url = "http://github.com/Paimon-Kawaii/L4D2-Plugins" +}; + +#define MAXSIZE MAXPLAYERS + 1 + +static char g_sZombieClass[][] = { + "Smoker", + "Boomer", + "Hunter", + "Spitter", + "Jockey", + "Charger", + "Witch", + "Tank", +}; + +#define DEAD 1 +void ResetDeadZombie(int client) +{ + SetStateTransition(client, STATE_ACTIVE); + SetEntProp(client, Prop_Send, "m_isGhost", true); + SetEntProp(client, Prop_Send, "deadflag", DEAD); + SetEntProp(client, Prop_Send, "m_lifeState", DEAD); + SetEntProp(client, Prop_Send, "m_iPlayerState", DEAD); + SetEntProp(client, Prop_Send, "m_zombieState", DEAD); + SetEntProp(client, Prop_Send, "m_iObserverMode", DEAD); + SetEntProp(client, Prop_Send, "movetype", MOVETYPE_NOCLIP); +} + +#define ALIVE 0 +#define FSOLID_NOT_STANDABLE 0x10 +void InitializeSpecial(int ent, const float vPos[3] = NULL_VECTOR, const float vAng[3] = NULL_VECTOR, bool bSpawn = false) +{ + if (bSpawn) DispatchSpawn(ent); + else RespawnPlayer(ent); + + if (GetClientTeam(ent) != TEAM_INFECTED) ChangeClientTeam(ent, TEAM_INFECTED); + SetEntProp(ent, Prop_Send, "m_usSolidFlags", FSOLID_NOT_STANDABLE); + SetEntProp(ent, Prop_Send, "movetype", MOVETYPE_WALK); + SetEntProp(ent, Prop_Send, "deadflag", ALIVE); + SetEntProp(ent, Prop_Send, "m_lifeState", ALIVE); + SetEntProp(ent, Prop_Send, "m_iObserverMode", ALIVE); + SetEntProp(ent, Prop_Send, "m_iPlayerState", ALIVE); + SetEntProp(ent, Prop_Send, "m_zombieState", ALIVE); + SetEntProp(ent, Prop_Send, "m_isGhost", false); + TeleportEntity(ent, vPos, vAng, NULL_VECTOR); +} + +#define ZC_COUNT 6 +Handle + // g_hSDK_CTerrorPlayer_SetClass, + g_hSDK_CBaseAbility_CreateForPlayer, + g_hSDK_CCSPlayer_State_Transition, + g_hSDK_CTerrorPlayer_RoundRespawn, + g_hSDK_NextBotCreatePlayerBot[ZC_COUNT]; + +static SIPool g_hSIPool; +static int g_iLastDeadTypeIdx = -1; +static int g_iPoolSize[ZC_COUNT] = { 0, ... }; +static int g_iPoolArray[ZC_COUNT][MAXSIZE] = { + {-1, ...}, + { -1, ...}, + { -1, ...}, + { -1, ...}, + { -1, ...}, + { -1, ...}, +}; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + RegPluginLibrary(LIBRARY_NAME); + g_hSIPool = view_as(myself); + + return APLRes_Success; +} + +public void OnPluginStart() +{ + CreateNatives(); + + HookEvents(); + PrepareSDKCalls(); +} + +public bool OnClientConnect(int client) +{ + if (IsFakeClient(client)) return true; + if (g_iLastDeadTypeIdx == -1) return true; + + int size = g_iPoolSize[g_iLastDeadTypeIdx]; + if (size > 0) + { + KickClient(g_iPoolArray[g_iLastDeadTypeIdx][size - 1]); + OnPoolSizeChanged(size, size - 1, g_iLastDeadTypeIdx); + g_iPoolSize[g_iLastDeadTypeIdx]--; + } + + return true; +} + +void CreateNatives() +{ + CreateNative("SIPool.Instance", Native_SIPool_Instance_get); + + CreateNative("SIPool.RequestSIBot", Native_SIPool_RequestSIBot); + CreateNative("SIPool.ReturnSIBot", Native_SIPool_ReturnSIBot); +} + +any Native_SIPool_Instance_get(Handle plugin, int numParams) +{ + return g_hSIPool; +} + +any Native_SIPool_RequestSIBot(Handle plugin, int numParams) +{ + int zclass_idx = GetNativeCell(2) - 1; + int size = g_iPoolSize[zclass_idx]; + if (size < 1) + { +#if DEBUG + static bool log = false; + if (!log) + { + LogToFile(LOGFILE, "[SIPool] Pool empty or not sized !"); + LogToFile(LOGFILE, "[SIPool] SIPool will auto set size to 1 !"); + LogToFile(LOGFILE, "[SIPool] This log only showed once."); + log = true; + } +#endif + OnPoolSizeChanged(0, 1, zclass_idx); + g_iPoolSize[zclass_idx] = 1; + } + + int index = 1; + size = g_iPoolSize[zclass_idx]; + int bot = g_iPoolArray[zclass_idx][size - index]; +#if DEBUG + LogToFile(LOGFILE, "[SIPool] Start request (%s)pool, current size: %d", g_sZombieClass[zclass_idx], size); + LogToFile(LOGFILE, "[SIPool] %d is ghost:(%d), isalive:(%d)?", bot, IsGhost(bot), IsValidClient(bot) && IsPlayerAlive(bot)); +#endif + while (!(IsValidClient(bot) && IsFakeClient(bot) && !IsPlayerAlive(bot)) && ++index <= size) + { + bot = g_iPoolArray[zclass_idx][size - index]; +#if DEBUG + LogToFile(LOGFILE, "[SIPool] %d is ghost:(%d), isalive:(%d)?", bot, IsGhost(bot), IsValidClient(bot) && IsPlayerAlive(bot)); +#endif + } + if (index > size && !(IsValidClient(bot) && IsFakeClient(bot) && !IsPlayerAlive(bot))) + { +#if DEBUG + LogToFile(LOGFILE, "[SIPool] No SI available !"); +#endif + OnPoolSizeChanged(size, 0, zclass_idx); + g_iPoolSize[zclass_idx] = 0; + + return -1; + } + + static float origin[3], angle[3]; + bool bPos = !IsNativeParamNullVector(3), bAngle = !IsNativeParamNullVector(4); + if (bPos) GetNativeArray(3, origin, 3); + if (bAngle) GetNativeArray(4, angle, 3); + + if (bPos && bAngle) InitializeSpecial(bot, origin, angle); + else if (bPos) InitializeSpecial(bot, origin); + else if (bAngle) InitializeSpecial(bot, _, angle); + else InitializeSpecial(bot); + // SetClass(bot, zclass_idx + 1); + SetClientName(bot, g_sZombieClass[zclass_idx]); + + Event event = CreateEvent("player_spawn", true); + if (event != INVALID_HANDLE) + { + event.SetInt("userid", GetClientUserId(bot)); + event.Fire(); + } + + OnPoolSizeChanged(size, size - index, zclass_idx); + g_iPoolSize[zclass_idx] -= index; + +#if DEBUG + LogToFile(LOGFILE, "[SIPool] SI request: %d, type: %s", bot, g_sZombieClass[zclass_idx]); + LogToFile(LOGFILE, "[SIPool] Finish request (%s)pool, current size: %d", g_sZombieClass[zclass_idx], g_iPoolSize[zclass_idx]); +#endif + + return bot; +} + +any Native_SIPool_ReturnSIBot(Handle plugin, int numParams) +{ + int bot = GetNativeCell(2); + if (!(IsInfected(bot) && IsFakeClient(bot) && IsPlayerAlive(bot))) + { +#if DEBUG + LogToFile(LOGFILE, "[SIPool] SI is not available!"); +#endif + return false; + } + ForcePlayerSuicide(bot); + + return true; +} + +void OnPoolSizeChanged(int iOldPoolSize, int iNewPoolSize, int zclass_idx) +{ + if (GetClientCount(false) >= MaxClients && iOldPoolSize < iNewPoolSize) return; + +#if DEBUG + LogToFile(LOGFILE, "[SIPool] (%s)pool sized(%d -> %d)", g_sZombieClass[zclass_idx], iOldPoolSize, iNewPoolSize); +#endif + + bool add; + int idx_min, idx_max; + if (iOldPoolSize < iNewPoolSize) + { + idx_min = iOldPoolSize; + idx_max = iNewPoolSize; + add = true; + } + if (!add) return; + + for (int i = idx_min; i < idx_max; i++) + { + int bot = CreateSIBot(zclass_idx); + if (bot == -1) + { + int max_count_class = 0; + for (int v = 0, count = 0; v < ZC_COUNT; v++) + if (count < g_iPoolSize[v]) + { + count = g_iPoolSize[v]; + max_count_class = v; + } + KickClient(g_iPoolArray[max_count_class][g_iPoolSize[max_count_class]--], "Kicked because client full."); + + bot = CreateSIBot(zclass_idx); + if (bot == -1) + { + LogError("[SIPool] SI create failed for the unknow reason ?!"); + break; + } + } + g_iPoolArray[zclass_idx][i] = bot; + InitializeSpecial(bot, _, _, true); + ResetDeadZombie(bot); +#if DEBUG + LogToFile(LOGFILE, "[SIPool] SI create: %d, (%s)pool sized(%d -> %d)", bot, g_sZombieClass[zclass_idx], iOldPoolSize, iNewPoolSize); +#endif + } +} + +void HookEvents() +{ + HookEvent("player_death", Event_PlayerDeath); + HookEvent("round_start", Event_RoundStart); +} + +void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + if (!(IsInfected(client) && IsFakeClient(client)) || IsTank(client)) return; + + // Return bot; + ResetDeadZombie(client); + g_iLastDeadTypeIdx = GetZombieClass(client) - 1; + g_iPoolArray[g_iLastDeadTypeIdx][g_iPoolSize[g_iLastDeadTypeIdx]++] = client; + +#if DEBUG + LogToFile(LOGFILE, "[SIPool] SI dead: %d, (%s)pool sized(%d -> %d)", client, g_sZombieClass[g_iLastDeadTypeIdx], g_iPoolSize[g_iLastDeadTypeIdx] - 1, g_iPoolSize[g_iLastDeadTypeIdx]); + LogToFile(LOGFILE, "[SIPool] %d is ghost:(%d), isalive:(%d)?", client, IsGhost(client), IsPlayerAlive(client)); +#endif +} + +void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) +{ + for (int i = 0; i < ZC_COUNT; i++) + { + g_iPoolSize[i] = 0; + for (int v = 0; v < MAXSIZE; v++) + g_iPoolArray[i][v] = -1; + } + g_iLastDeadTypeIdx = -1; +} + +void PrepareSDKCalls() +{ + GameData hGameData = new GameData(GAMEDATA_FILE); + Address pReplaceWithBot = hGameData.GetAddress("NextBotCreatePlayerBot.jumptable"); + if (pReplaceWithBot != Address_Null && LoadFromAddress(pReplaceWithBot, NumberType_Int8) == 0x68) + PrepWindowsCreateBotCalls(pReplaceWithBot); + else + PrepLinuxCreateBotCalls(hGameData); + + // StartPrepSDKCall(SDKCall_Player); + // if (PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, "CTerrorPlayer::SetClass")) + // { + // PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + // g_hSDK_CTerrorPlayer_SetClass = EndPrepSDKCall(); + // if (g_hSDK_CTerrorPlayer_SetClass == null) + // LogError("Failed to create SDKCall: \"CTerrorPlayer::SetClass\""); + // } + // else LogError("Failed to find signature: \"CTerrorPlayer::SetClass\""); + + StartPrepSDKCall(SDKCall_Static); + if (PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, "CBaseAbility::CreateForPlayer")) + { + PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer); + g_hSDK_CBaseAbility_CreateForPlayer = EndPrepSDKCall(); + if (g_hSDK_CBaseAbility_CreateForPlayer == null) + LogError("Failed to create SDKCall: \"CBaseAbility::CreateForPlayer\""); + } + else LogError("Failed to find signature: \"CBaseAbility::CreateForPlayer\""); + + StartPrepSDKCall(SDKCall_Player); + if (PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, "CCSPlayer::State_Transition")) + { + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + g_hSDK_CCSPlayer_State_Transition = EndPrepSDKCall(); + if (g_hSDK_CCSPlayer_State_Transition == null) + LogError("Failed to create SDKCall: \"CCSPlayer::State_Transition\""); + } + else LogError("Failed to find signature: \"CCSPlayer::State_Transition\""); + + StartPrepSDKCall(SDKCall_Player); + if (PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, "CTerrorPlayer::RoundRespawn")) + { + g_hSDK_CTerrorPlayer_RoundRespawn = EndPrepSDKCall(); + if (g_hSDK_CTerrorPlayer_RoundRespawn == null) + LogError("Failed to create SDKCall: \"CTerrorPlayer::RoundRespawn\""); + } + else LogError("Failed to find signature: \"CTerrorPlayer::RoundRespawn\""); + + delete hGameData; +} + +// #define HUNTER_ADDR 0 +// #define JOCKEY_ADDR 12 +// #define SPITTER_ADDR 24 +// #define CHARGER_ADDR 36 +// #define SMOKER_ADDR 48 +// #define BOOMER_ADDR 60 +// #define TANK_ADDR 72 +static int g_iZombieAddr[ZC_COUNT] = { + 48, 60, 0, 24, 12, 36 +}; +void PrepWindowsCreateBotCalls(Address pBaseAddr) +{ +#if DEBUG + TestName(pBaseAddr); +#endif + for (int i = 0; i < ZC_COUNT; i++) + { + Address pJumpAddr = pBaseAddr + view_as
(g_iZombieAddr[i]); + Address pFuncRefAddr = pJumpAddr + view_as
(6); + int funcRelOffset = LoadFromAddress(pFuncRefAddr, NumberType_Int32); + Address pCallOffsetBase = pJumpAddr + view_as
(10); + Address pNextBotCreatePlayerBotTAddr = pCallOffsetBase + view_as
(funcRelOffset); + + StartPrepSDKCall(SDKCall_Static); + if (!PrepSDKCall_SetAddress(pNextBotCreatePlayerBotTAddr)) + SetFailState("Unable to find NextBotCreatePlayer address in memory."); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_CBasePlayer, SDKPass_Pointer); + g_hSDK_NextBotCreatePlayerBot[i] = EndPrepSDKCall(); + } +} + +void PrepLinuxCreateBotCalls(GameData hGameData = null) +{ + static char signature_name[32]; + for (int i = 0; i < ZC_COUNT; i++) + { + Format(signature_name, sizeof(signature_name), "NextBotCreatePlayerBot<%s>", g_sZombieClass[i]); + StartPrepSDKCall(SDKCall_Static); + if (!PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, signature_name)) + SetFailState("Failed to find signature: %s", signature_name); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_CBasePlayer, SDKPass_Pointer); + g_hSDK_NextBotCreatePlayerBot[i] = EndPrepSDKCall(); + } +} + +#if DEBUG +void TestName(Address pBaseAddr) +{ + for (int i = 0; i < 7; i++) + { + Address pCaseBase = pBaseAddr + view_as
(i * 12); + Address pSIStringAddr = view_as
(LoadFromAddress(pCaseBase + view_as
(1), NumberType_Int32)); + static char SIName[32]; + LoadStringFromAddress(pSIStringAddr, SIName, sizeof(SIName)); + LogToFile(LOGFILE, "[SIPool] Found \"%s\"(%d) in memory.", SIName, i); + } +} + +void LoadStringFromAddress(Address pAddr, char[] buffer, int maxlength) +{ + int i; + char val; + while (i < maxlength) + { + val = LoadFromAddress(pAddr + view_as
(i), NumberType_Int8); + if (val == 0) + { + buffer[i] = '\0'; + break; + } + buffer[i++] = val; + } + buffer[maxlength - 1] = '\0'; +} +#endif + +// #define ABILITY_TRYTIMES 3 +// void SetClass(int client, int zombieClass) +// { +// int weapon = GetPlayerWeaponSlot(client, 0); +// if (weapon != -1) +// { +// RemovePlayerItem(client, weapon); +// RemoveEntity(weapon); +// } + +// int ability = GetEntPropEnt(client, Prop_Send, "m_customAbility"); +// if (ability != -1) RemoveEntity(ability); +// ability = -1; + +// SDKCall(g_hSDK_CTerrorPlayer_SetClass, client, zombieClass); + +// for (int count = 0; count < ABILITY_TRYTIMES && ability == -1; count++) +// ability = SDKCall(g_hSDK_CBaseAbility_CreateForPlayer, client); + +// if (ability != -1) SetEntPropEnt(client, Prop_Send, "m_customAbility", ability); +// else LogToFile(LOGFILE, "[SIPool] Failed to create ability for %N after %d times tried.", client, ABILITY_TRYTIMES); +// } + +void SetStateTransition(int client, int state) +{ + SDKCall(g_hSDK_CCSPlayer_State_Transition, client, state); +} + +void RespawnPlayer(int client) +{ + SDKCall(g_hSDK_CTerrorPlayer_RoundRespawn, client); +} + +int CreateSIBot(int zclass_idx) +{ + return SDKCall(g_hSDK_NextBotCreatePlayerBot[zclass_idx], g_sZombieClass[zclass_idx]); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/paiutils.inc b/addons/sourcemod/scripting/include/paiutils.inc new file mode 100644 index 000000000..54f50eccb --- /dev/null +++ b/addons/sourcemod/scripting/include/paiutils.inc @@ -0,0 +1,889 @@ +/* + * @Author: 派蒙 + * @Last Modified by: 我是派蒙啊 + * @Create Date: 2022-01-14 11:54:43 + * @Last Modified time: 2024-02-04 12:19:46 + * @Github: http://github.com/PaimonQwQ + */ + +/* #################################################################### * + Paimon Utils + Author: 我是派蒙啊 + Version: 2023.07.15 + * #################################################################### */ + +#if defined _paiutils_included + #endinput +#endif +#define _paiutils_included + +// #define int(%1) view_as(%1) +// #define float(%1) view_as(%1) +// #define bool(%1) view_as(%1) +// #define char(%1) view_as(%1) + +#define TEAM_SPECTATOR 1 +#define TEAM_SURVIVOR 2 +#define TEAM_INFECTED 3 + +#define ZC_SMOKER 1 +#define ZC_BOOMER 2 +#define ZC_HUNTER 3 +#define ZC_SPITTER 4 +#define ZC_JOCKEY 5 +#define ZC_CHARGER 6 +#define ZC_WITCH 7 +#define ZC_TANK 8 + +#define CMDBOT_ATTACK 0 +#define CMDBOT_MOVE 1 +#define CMDBOT_RETREAT 2 +#define CMDBOT_RESET 3 + +enum +{ + Team_Spectator = 1, + Team_Survivor, + Team_Infected +}; + +enum +{ + ZC_Smoker = 1, + ZC_Boomer, + ZC_Hunter, + ZC_Spitter, + ZC_Jockey, + ZC_Charger, + ZC_Witch, + ZC_Tank +}; + +// enum +// { +// PC_UpperCut = 40, +// PC_RightHook = 43, +// PC_LeftHook = 45, +// PC_PoundGround1 = 46, +// PC_PoundGround2 = 47, +// TH_UnderCut = 48, +// TH_OverHand = 49, +// TH_FromHip = 50, +// TH_OverHead = 51 +// }; + +enum +{ + CmdBot_Attack = 0, // Force the bot to attack a specific target, even bypassing CTerrorPlayer::SetSenseFlags (DirectorScript.BOT_CANT_SEE). + CmdBot_Move = 1, // Force the bot to move to a specific location, which then they will do it unconditionally without performing any other AI behaviours controlled by themselves. + // This means that Survivor Bots and most player-controllable Special Infected won't attack anything when commanded, but Common Infected still automatically attack enemies if they close in enough. + CmdBot_Retreat = 2, // Force the bot to retreat from a target entity. Only works when used on Survivor Bots, and if target is a Tank. + CmdBot_Reset = 3 // Removes the active bot command and lets the AI resume controlling the bot. +}; + +/** + * @brief Returns true if client is correct. + * + * @param client Client index. + * @return True if client is correct. False otherwise. + */ +stock bool IsValidClient(int client) +{ + return (0 < client <= MaxClients && IsClientConnected(client) && IsClientInGame(client)); +} + +/** + * @brief Returns true if client is a survivor. + * + * @param client Client index. + * @return True if client is a survivor. False otherwise. + */ +stock bool IsSurvivor(int client) +{ + return (IsValidClient(client) && GetClientTeam(client) == TEAM_SURVIVOR); +} + +/** + * @brief Returns true if client is a SI(Special Infected). + * + * @param client Client index. + * @return True if client is a SI. False otherwise. + */ +stock bool IsInfected(int client) +{ + return (IsValidClient(client) && GetClientTeam(client) == TEAM_INFECTED); +} + +/** + * @brief Returns true if client is a spectator. + * + * @param client Client index. + * @return True if client is a spectator. False otherwise. + */ +stock bool IsSpectator(int client) +{ + return (IsValidClient(client) && GetClientTeam(client) == TEAM_SPECTATOR); +} + +/** + * @brief Returns true if client is in ghost mode. + * + * @param client Client index. + * @return True if client is in ghost mode. False otherwise. + */ +stock bool IsGhost(int client) +{ + return (IsValidClient(client) && view_as(GetEntProp(client, Prop_Send, "m_isGhost"))); +} + +/** + * @brief Returns true if client is a tank. + * + * @param client Client index. + * @return True if client is a tank. False otherwise. + */ +stock bool IsTank(int client) +{ + return (IsValidClient(client) && GetZombieClass(client) == ZC_Tank); +} + +/** + * @brief Returns true if client is being pinned. + * + * @param client Client index. + * @return True if client is being pinned. False otherwise. + */ +stock bool IsSurvivorPinned(int client) +{ + return (IsSurvivor(client) && (GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0 || // smoker grabbed + GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 || // hunter pounded + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || // charger carred + GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 || // charger pounded + GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0)); // jockey ridden +} + +/** + * @brief Returns who is pinning a client. + * + * @param client Client index. + * @return Infected index if client is being pinned. -1 otherwise. + */ +stock int GetSurvivorPinner(int client) +{ + if (!IsSurvivor(client)) return -1; + + return (GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0 ? // smoker grabbed + GetEntPropEnt(client, Prop_Send, "m_tongueOwner") + : GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 ? // hunter pounded + GetEntPropEnt(client, Prop_Send, "m_carryAttacker") + : GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 ? // charger carred + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") + : GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 ? // charger pounded + GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") + : GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 ? // jockey ridden + GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") + : -1); +} + +/** + * @brief Returns survivor who is pinned by client. + * + * @param client Client index. + * @return Survivor index who is pinned by client. -1 otherwise. + */ +stock int GetPinningSurvivor(int client) +{ + if (!IsInfected(client)) return -1; + return (GetEntPropEnt(client, Prop_Send, "m_tongueVictim") > 0 ? // smoker grabbed + GetEntPropEnt(client, Prop_Send, "m_tongueVictim") + : GetEntPropEnt(client, Prop_Send, "m_pounceVictim") > 0 ? // hunter pounded + GetEntPropEnt(client, Prop_Send, "m_pounceVictim") + : GetEntPropEnt(client, Prop_Send, "m_carryVictim") > 0 ? // charger carred + GetEntPropEnt(client, Prop_Send, "m_carryVictim") + : GetEntPropEnt(client, Prop_Send, "m_pummelVictim") > 0 ? // charger pounded + GetEntPropEnt(client, Prop_Send, "m_pummelVictim") + : GetEntPropEnt(client, Prop_Send, "m_jockeyVictim") > 0 ? // jockey ridden + GetEntPropEnt(client, Prop_Send, "m_jockeyVictim") + : -1); +} + +/** + * @brief Returns true if client is pinning a survivor. + * + * @param client Client index. + * @return True if client is pinning a survivor. False otherwise. + */ +stock bool IsPinningSurvivor(int client) +{ + return (IsInfected(client) && (GetEntPropEnt(client, Prop_Send, "m_tongueVictim") > 0 || // smoker grabbing + GetEntPropEnt(client, Prop_Send, "m_pounceVictim") > 0 || // hunter pounding + GetEntPropEnt(client, Prop_Send, "m_carryVictim") > 0 || // charger carrying + GetEntPropEnt(client, Prop_Send, "m_pummelVictim") > 0 || // charger pounding + GetEntPropEnt(client, Prop_Send, "m_jockeyVictim") > 0)); // jockey riding +} + +/** + * @brief Returns who is aiming client. + * + * @param aimee Client who is aimed. + * @param team Team type to included. + * @param sawable Is target seen threats. + * @return Client index for who aimed aimee. -1 otherwise. + */ +stock int GetClientAimedBy(int aimee, int team = 0, bool sawable = true) +{ + int target = -1; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || (team && GetClientTeam(i) != team) || !IsPlayerAlive(i) || IsPlayerIncap(i) || (sawable && !IsEntitySawThreats(i))) continue; + int aimed = GetClientAimTarget(i); + if (aimed != aimee) continue; + target = i; + break; + } + + return target; +} + +/** + * @brief Returns closest client who is aiming aimee. + * + * @param aimee Client who is aimed. + * @param team Team type to included. + * @param sawable Is target seen threats. + * @return Client index for closest client. -1 otherwise. + */ +stock int GetClosestClientAimer(int aimee, int team, bool sawable = true) +{ + int target = -1; + float dis = -1.0, pos[3], tarPos[3]; + GetClientAbsOrigin(aimee, tarPos); + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || (team && GetClientTeam(i) != team) || !IsPlayerAlive(i) || IsPlayerIncap(i) || (sawable && !IsEntitySawThreats(i))) continue; + int aimed = GetClientAimTarget(i); + if (aimed != aimee) continue; + + GetClientAbsOrigin(i, pos); + float tmp = GetVectorDistance(tarPos, pos, true); + if (dis <= tmp && dis != -1) continue; + + target = i; + dis = tmp; + } + + return target; +} + +/** + * @brief Returns closest client of target or origin. + * + * @param target Target entity to begin searching. + * @param origin Position to begin searching, no use if target is valid. + * @param targetTeam Team type to included. + * @param sawable Is target seen threats. + * @param notAimed Should client not aim target. + * @param isIncap Can target be incap. + * @param isGhost Is target a ghost. + * @return Client index for closest client. -1 otherwise. + */ +stock int GetClosestClient(int target = -1, const float origin[3], int targetTeam = 0, bool sawable = true, bool notAimed = false, bool isIncapable = false, bool isGhost = false) +{ + int result = -1; + float dis = -1.0, pos[3], tarPos[3]; + AddVectors(tarPos, origin, tarPos); + if (IsValidEntity(target)) GetEntPropVector(target, Prop_Send, "m_vecOrigin", tarPos); + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || !IsPlayerAlive(i) || (targetTeam && GetClientTeam(i) != targetTeam) || (notAimed && GetClientAimTarget(i) == target) || (sawable && !IsEntitySawThreats(i)) || (!isIncapable && IsPlayerIncap(i)) || (!isGhost && IsGhost(i))) continue; + + GetClientAbsOrigin(i, pos); + float tmp = GetVectorDistance(tarPos, pos, true); + if (dis <= tmp && dis != -1) continue; + + result = i; + dis = tmp; + } + + return result; +} + +/** + * @brief Returns closest zombies of target or origin. + * + * @param target Target entity to begin searching. + * @param origin Position to begin searching, no use if target is valid. + * @param includeWitch Is included witch. + * @param includeRock Is included rock. + * @return Entity index for closest zombies. -1 otherwise. + */ +stock int GetClosestZombie(int target = -1, const float origin[3], bool includeWitch = true, bool includeRock = true) +{ + int result = -1; + int entCnt = GetMaxEntities(); + float dis = -1.0, pos[3], tarPos[3]; + AddVectors(tarPos, origin, tarPos); + if (IsValidEntity(target)) GetEntPropVector(target, Prop_Send, "m_vecOrigin", tarPos); + for (int i = MaxClients + 1; i <= entCnt; i++) + { + if (!IsValidEntity(i) || IsValidClient(i)) continue; + char clsName[32]; + GetEntityClassname(i, clsName, sizeof(clsName)); + if (strcmp(clsName, "infected") && (includeWitch && strcmp(clsName, "witch")) && (includeRock && strcmp(clsName, "tank_rock"))) continue; + if (GetEntProp(i, Prop_Data, "m_lifeState") || !GetEntProp(i, Prop_Data, "m_iHealth")) continue; + + GetEntPropVector(i, Prop_Send, "m_vecOrigin", pos); + float tmp = GetVectorDistance(tarPos, pos, true); + if (dis <= tmp && dis != -1) continue; + + result = i; + dis = tmp; + } + + return result; +} + +/** + * @brief Returns true if client is incapacitated. + * + * @param client Client index. + * @return True if client is incapacitated. False otherwise. + */ +stock bool IsPlayerIncap(int client) +{ + return (IsValidClient(client) && view_as(GetEntProp(client, Prop_Send, "m_isIncapacitated"))); +} + +/** + * @brief Returns true if survivor team is full. + * + * @return True if survivor team is full. False otherwise. + */ +stock bool IsSurvivorTeamFull() +{ + for (int i = 1; i <= MaxClients; i++) + if (IsSurvivor(i) && IsPlayerAlive(i) && IsFakeClient(i)) + return false; + + return true; +} + +/** + * @brief Returns true if survivors are all pinnded. + * + * @return True if survivors are all pinnded. False otherwise. + */ +stock bool IsAllSurvivorPinned() +{ + for (int i = 1; i <= MaxClients; i++) + if (IsSurvivor(i) && IsPlayerAlive(i) && !(IsSurvivorPinned(i) || IsPlayerIncap(i))) + return false; + + return true; +} + +/** + * @brief Returns true if survivors are all incapped. + * + * @return True if survivora are all incapped. False otherwise. + */ +stock bool IsAllSurvivorIncapped() +{ + for (int i = 1; i <= MaxClients; i++) + if (IsSurvivor(i) && !IsPlayerIncap(i)) + return false; + + return true; +} + +/** + * @brief Returns true if player saw threats. + * + * @param entity Entity index. + * @return True if player saw threats. False otherwise. + */ +stock bool IsEntitySawThreats(int entity) +{ + return (IsValidEntity(entity) && view_as(GetEntProp(entity, Prop_Send, "m_hasVisibleThreats"))); +} + +/** + * @brief Returns true if any survivor alives. + * + * @return True if any survivor alives. False otherwise. + */ +stock bool HasSurvivorAlive() +{ + for (int i = 1; i <= MaxClients; i++) + if (IsSurvivor(i) && IsPlayerAlive(i)) + return true; + + return false; +} + +/** + * @brief Returns true if any SI(Special Infected) but not tank alives. + * + * @return True if any SI(Special Infected) but not tank alives. False otherwise. + */ +stock bool HasInfectedAlive(bool includeTank = false) +{ + for (int i = 1; i <= MaxClients; i++) + if (IsInfected(i) && IsPlayerAlive(i) && !IsPlayerIncap(i)) + { + if (!includeTank && IsTank(i)) continue; + return true; + } + + return false; +} + +/** + * @brief Get tank's frustration. + * + * @param tankClient Client index. + * @return Tank's frustration. -1 for false. + */ +stock int GetTankFrustration(int tankClient) +{ + return IsValidClient(tankClient) ? 100 - GetEntProp(tankClient, Prop_Send, "m_frustration") : -1; +} + +/** + * @brief Set tank's frustration. + * + * @param tankClient Client index. + * @param frustration Frustration value. + * @return True if succeed. False otherwise. + */ +stock bool SetTankFrustration(int tankClient, int frustration) +{ + if (!IsValidClient(tankClient)) return false; + if (frustration < 0 || frustration > 100) return false; + + SetEntProp(tankClient, Prop_Send, "m_frustration", 100 - frustration); + return true; +} + +/** + * @brief Get player's health. + * + * @param client Client index. + * @param temp_heal Client temp health. + * @param total_heal Client total health. + * @return Player's health. -1 for false. + */ +stock int GetPlayerHealth(int client, bool temp_heal = false, bool total_heal = false) +{ + if (!IsValidClient(client)) return -1; + if (!IsPlayerAlive(client)) return 0; + + if (total_heal) temp_heal = false; + if (!IsSurvivor(client)) temp_heal = total_heal = false; + + int temp = 0, health = GetEntProp(client, Prop_Send, "m_iHealth"); + if (temp_heal) temp = RoundToCeil(GetEntPropFloat(client, Prop_Send, "m_healthBuffer") - ((GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime")) * FindConVar("pain_pills_decay_rate").FloatValue)) - 1; + int result = !total_heal ? (temp_heal ? temp : health) : health + temp; + + return result; +} + +/** + * @brief Set player's health. + * + * @param client Client index. + * @param health Health value. + * @return True if succeed. False otherwise. + */ +stock bool SetPlayerHealth(int client, int health) +{ + if (!IsValidClient(client)) return false; + if (health < 0) return false; + + SetEntProp(client, Prop_Send, "m_iHealth", health); + return true; +} + +/** + * Returns whether player is on third strike. + * + * @param client Client index. + * @return True if on third strike, false otherwise. + * @error Invalid client index. + */ +stock bool IsPlayerOnThirdStrike(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_bIsOnThirdStrike")); +} + +/** + * @brief Execute a command. + * + * @param client Client index. + * @param strCmd Command string. + * @param strPrm CmdParam string. + * @return True if succeed. False otherwise. + */ +stock bool ExecuteCommand(int client, const char[] strCmd, const char[] strPrm = "") +{ + if (!IsValidClient(client)) return false; + + int flags = GetCommandFlags(strCmd); + SetCommandFlags(strCmd, flags & (~FCVAR_CHEAT)); + FakeClientCommand(client, "%s %s", strCmd, strPrm); + SetCommandFlags(strCmd, flags); + + return true; +} + +/** + * @brief Get client SI(Special Infected) class. + * + * @param client Client index. + * @return SI class. -1 for false. + */ +stock int GetZombieClass(int client) +{ + if (!IsValidClient(client) || !IsInfected(client)) return -1; + + return GetEntProp(client, Prop_Send, "m_zombieClass"); +} + +/** + * @brief Get survivor count. + * + * @return Survivor count. + */ +stock int GetSurvivorCount() +{ + int count = 0; + for (int i = 1; i <= MaxClients; i++) + if (IsSurvivor(i)) + count++; + + return count; +} + +/** + * @brief Get SI(Special Infected) count. + * + * @return SI count. + */ +stock int GetSInfectedCount() +{ + int count = 0; + for (int i = 1; i <= MaxClients; i++) + if (IsInfected(i)) + count++; + + return count; +} + +/** + * @brief Get client count without special infected. + * + * @return Client count without special infected. + */ +stock int GetClientNoSICount() +{ + int count = 0; + for (int i = 1; i <= MaxClients; i++) + if (IsValidClient(i) && IsClientInGame(i) && !IsInfected(i)) + count++; + + return count; +} + +/** + * @brief Get player survivor count. + * + * @return Player survivor count. + */ +stock int GetSurvivorPlayerCount() +{ + int count = 0; + for (int i = 1; i <= MaxClients; i++) + if (IsSurvivor(i) && !IsFakeClient(i)) + count++; + + return count; +} + +/** + * @brief Get player SI(Special Infected) count. + * + * @return Player SI count. + */ +stock int GetInfectedPlayerCount() +{ + int count = 0; + for (int i = 1; i <= MaxClients; i++) + if (IsInfected(i) && !IsFakeClient(i)) + count++; + + return count; +} + +/** + * @brief Get alive survivor count. + * + * @return Alive survivor count. + */ +stock int GetAliveSurvivorCount() +{ + int count = 0; + for (int i = 1; i <= MaxClients; i++) + if (IsSurvivor(i) && IsPlayerAlive(i)) + count++; + + return count; +} + +/** + * @brief Get alive SI count. + * + * @return Alive SI count. + */ +stock int GetAliveInfectedCount() +{ + int count = 0; + for (int i = 1; i <= MaxClients; i++) + if (IsInfected(i) && IsPlayerAlive(i)) + count++; + + return count; +} + +/** + * @brief Delete client items + * + * @return No return. + */ +stock void DeleteInventoryItem(int client) +{ + if (!IsValidClient(client)) return; + + for (int s = 0; s < 5; s++) + { + int item = GetPlayerWeaponSlot(client, s); + if (item > 0) + RemovePlayerItem(client, item); + } +} + +/** + * @brief Uses the VScript "CommandABot" function to command a bot to attack, move, retreat or reset previous command + * + * @param entity The bot or infected to command + * @param target The Special Infected to target (used for types "CMDBOT_ATTACK" and "CMDBOT_RETREAT") + * @param type Type of command + * @param pos Move to this location (Used for type "CMDBOT_MOVE") + * + * @return Returns false when unable to perform, true otherwise. + */ +stock bool CommandABot(int entity, int target = 0, int type, float pos[3] = NULL_VECTOR) +{ + if (!IsValidClient(entity) || !IsFakeClient(entity)) return false; + char param[PLATFORM_MAX_PATH]; + switch (type) + { + case CMDBOT_ATTACK, CMDBOT_RETREAT: + Format(param, sizeof(param), "CommandABot({cmd = %d, bot = GetPlayerFromUserID(%i), target = GetPlayerFromUserID(%i)})", type, GetClientUserId(entity), GetClientUserId(target)); + case CMDBOT_MOVE: + Format(param, sizeof(param), "CommandABot({cmd = %d, pos = Vector(%f, %f, %f), bot = GetPlayerFromUserID(%i)})", type, pos[0], pos[1], pos[2], GetClientUserId(entity)); + case CMDBOT_RESET: + Format(param, sizeof(param), "CommandABot({cmd = %d, bot = GetPlayerFromUserID(%i)})", type, GetClientUserId(entity)); + + default: + return false; + } + + return ExecuteCommand(entity, "script", param); +} + +/** + * @brief Runs a specified VScript code and returns values from it. + * + * @param code The VScript code to execute. Maximum length seems to be 1006 characters. + * @param result Buffer to copy return data to. You can use StringToInt or StringToFloat if required. + * @param maxlength Maximum size of the result. + * + * @return True on success, false otherwise. + */ +#pragma deprecated This is nonachievement. +stock bool ExecuteVscript(char[] code, char[] result, int maxlength) +{ + int script = CreateEntityByName("logic_script"); + if (!IsValidEdict(script)) return false; + DispatchSpawn(script); + + if ((StrContains(code, "") | StrContains(code, "")) < 0) return false; + + char buffer[1024], put[] = "NetProps.SetPropString(%d, \"m_iName\", "; + Format(put, sizeof(put), put, script); + strcopy(buffer, sizeof(buffer), code); + ReplaceString(buffer, sizeof(buffer), "", put); + ReplaceString(buffer, sizeof(buffer), "", ");"); + SetVariantString(buffer); + PrintToChatAll("%s", buffer); + + AcceptEntityInput(script, "RunScriptCode"); + GetEntPropString(script, Prop_Data, "m_iName", result, maxlength); + + AcceptEntityInput(script, "Kill"); + RemoveEdict(script); + + return true; +} + +/** + * Sets custom ability cooldown of client. + * + * Note: Used for the Infected class abilities. + * + * @param client Client index. + * @param time How long before client can use their custom ability. + * + * @return True if set, false if no ability found. + */ +stock bool SetAbilityCooldown(int client, float time) +{ + int ability = GetEntPropEnt(client, Prop_Send, "m_customAbility"); + if (ability > 0 && IsValidEdict(ability)) + { + SetEntPropFloat(ability, Prop_Send, "m_duration", time); + SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + time); + return true; + } + return false; +} + +/** + * Get entity abs origin. + * + * @param entity Entity index. + * @param origin Entity position. + */ +stock void GetEntityAbsOrigin(int entity, float origin[3]) +{ + if (!IsValidEntity(entity)) + ThrowError("Invalid entity index %d", entity); + float mins[3], maxs[3]; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(entity, Prop_Send, "m_vecMins", mins); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", maxs); + + origin[0] += (mins[0] + maxs[0]) * 0.5; + origin[1] += (mins[1] + maxs[1]) * 0.5; + origin[2] += (mins[2] + maxs[2]) * 0.5; +} + +// stock float GetVectorMinDistanceByPlane(const float vec1[3], const float vec2[3], bool squared = false) +// { +// float zDis = FloatAbs(vec1[2] - vec2[2]); +// if(squared) zDis = Pow(zDis, 2.0); +// float xOyDis = GetVector2Distance(vec1, vec2, squared); + +// return zDis > xOyDis ? xOyDis : zDis; +// } + +/** + * Calculates the distance between two vectors(ignored z axis). + * + * @param vec1 First vector. + * @param vec2 Second vector. + * @param squared If true, the result will be squared (for optimization). + * @return Vector2D distance. + */ +stock float GetVector2Distance(const float vec1[3], const float vec2[3], bool squared = false) +{ + float vecA[3], vecB[3]; + CopyVector(vec1, vecA); + CopyVector(vec2, vecB); + vecA[2] = vecB[2] = 0.0; + + return GetVectorDistance(vecA, vecB, squared); +} + +stock void CopyVector(const float origin[3], float vector[3]) +{ + for (int i = 0; i < 3; i++) + vector[i] = origin[i]; +} + +stock float GetEntityDistance(int ent1, int ent2, bool squared = false) +{ + if (!IsValidEntity(ent1)) + ThrowError("Error: invalid entity index %d", ent1); + if (!IsValidEntity(ent2)) + ThrowError("Error: invalid entity index %d", ent2); + + float pos1[3], pos2[3]; + if (IsValidClient(ent1)) + GetClientAbsOrigin(ent1, pos1); + else GetEntityAbsOrigin(ent1, pos1); + if (IsValidClient(ent2)) + GetClientAbsOrigin(ent2, pos2); + else GetEntityAbsOrigin(ent2, pos2); + + return GetVectorDistance(pos1, pos2, squared); +} + +//== From https://forums.alliedmods.net/showthread.php?t=342637 +//== Author: LinLinLin +stock bool IsInClientView(int entity, int client) +{ + float entPos[3], playerPos[3], playerAng[3]; + float eye2entVector[3]; + float eye2fwdVector[3]; + char temp[16]; + GetClientInfo(client, "fov_desired", temp, sizeof(temp)); + int fov = StringToInt(temp); + + GetClientEyePosition(client, playerPos); + GetEntPropVector(entity, Prop_Data, "m_vecOrigin", entPos); + MakeVectorFromPoints(playerPos, entPos, eye2entVector); + + GetClientEyeAngles(client, playerAng); + GetAngleVectors(playerAng, eye2fwdVector, NULL_VECTOR, NULL_VECTOR); + + NormalizeVector(eye2entVector, eye2entVector); + NormalizeVector(eye2fwdVector, eye2fwdVector); + + float radian = ArcCosine(GetVectorDotProduct(eye2entVector, eye2fwdVector)); + /** + * let me explain how this degree radian. + * + * DotProduct = |a||b|cosθ, this is the vector theorem. + * we have normalize the vector so |a| = |b| = 1. + * in this case DotProduct = cosθ. + * ArcCosine() let us get the radian of θ. + * FOV is a degree, we need make it become radian. + */ + return radian <= DegToRad(fov + 0.0) / 2; +} + +stock bool IsFirstMap() +{ + int count, entity; + while ((entity = FindEntityByClassname(entity, "info_landmark")) > 0) + count++; + return count < 2 && FindEntityByClassname(0, "trigger_finale") == -1; +} + +stock bool IsIntro() +{ + int entity = FindEntityByClassname(0, "terror_gamerules"); + if (!IsValidEntity(entity)) return false; + + return view_as(GetEntProp(entity, Prop_Send, "m_bInIntro")); +} + +stock bool IsMissionFinal() +{ + int count, entity; + while ((entity = FindEntityByClassname(entity, "info_landmark")) > 0) + count++; + + return count < 2 && FindEntityByClassname(0, "trigger_finale") > 0; +} diff --git a/addons/sourcemod/scripting/include/si_pool.inc b/addons/sourcemod/scripting/include/si_pool.inc new file mode 100644 index 000000000..2b05baf95 --- /dev/null +++ b/addons/sourcemod/scripting/include/si_pool.inc @@ -0,0 +1,90 @@ +/* + * @Author: 我是派蒙啊 + * @Last Modified by: 我是派蒙啊 + * @Create Date: 2024-02-17 18:11:22 + * @Last Modified time: 2024-03-25 18:59:48 + * @Github: https://github.com/Paimon-Kawaii + */ + +#if defined _sipool_included_ + #endinput +#endif +#define _sipool_included_ + +public SharedPlugin __pl_sipool = { + name = "si_pool", + file = "si_pool.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +// #define SIZABLE 0 + +public void __pl_sipool_SetNTVOptional() +{ + // MarkNativeAsOptional("SIPool.SIPool"); + MarkNativeAsOptional("SIPool.Instance"); + // MarkNativeAsOptional("SIPool.Size.get"); + +// #if SIZABLE +// MarkNativeAsOptional("SIPool.Narrow"); +// MarkNativeAsOptional("SIPool.Expand"); +// MarkNativeAsOptional("SIPool.Resize"); +// #endif + + MarkNativeAsOptional("SIPool.RequestSIBot"); + MarkNativeAsOptional("SIPool.ReturnSIBot"); +} + +// Provided by "BHaType": +// For the "L4D_State_Transition" native +// X -> Y (means X state will become Y state on next frame or some seconds later) +#define STATE_ACTIVE 0 +#define STATE_WELCOME 1 // -> STATE_PICKING_TEAM +#define STATE_PICKING_TEAM 2 +#define STATE_PICKINGCLASS 3 // -> STATE_ACTIVE +#define STATE_DEATH_ANIM 4 // -> STATE_DEATH_WAIT_FOR_KEY +#define STATE_DEATH_WAIT_FOR_KEY 5 // -> STATE_OBSERVER_MODE +#define STATE_OBSERVER_MODE 6 +#define STATE_WAITING_FOR_RESCUE 7 +#define STATE_GHOST 8 +#define STATE_INTRO_CAMERA 9 + +methodmap SIPool __nullable__ +{ + // public native SIPool(); + + public static native SIPool Instance(); + + // property int Size { + // public native get(); + // } + +// #if SIZABLE +// public native void Narrow(int size); +// public native void Expand(int size); +// public native void Resize(int size); +// #endif + + /** + * Request a Special infected Bot from SIPool + * + * @param zclass Zombie class + * @param origin Position to spawn + * @param angle Angle that zombie look at + * @return Client index + */ + public native int RequestSIBot(int zclass, const float origin[3] = NULL_VECTOR, const float angle[3] = NULL_VECTOR); + + /** + * Return a Special infected Bot to SIPool + * Bot will automatically return after dead + * + * @param client Zombie Bot to return + * @return True is success, false otherwise + */ + public native bool ReturnSIBot(int client); +} \ No newline at end of file diff --git a/cfg/cfgogl/allcharger/confogl_plugins.cfg b/cfg/cfgogl/allcharger/confogl_plugins.cfg index 7410fb0c8..84a679dca 100644 --- a/cfg/cfgogl/allcharger/confogl_plugins.cfg +++ b/cfg/cfgogl/allcharger/confogl_plugins.cfg @@ -32,6 +32,7 @@ sm plugins load optional/l4d2_skill_detect.smx //sm plugins load optional/AnneHappy/ai_spitter_new.smx sm plugins load optional/AnneHappy/ai_charger_2.smx //sm plugins load optional/AnneHappy/ai_boomer_new.smx +sm plugins load optional/AnneHappy/si_pool.smx sm plugins load optional/AnneHappy/infected_control.smx sm plugins load optional/AnneHappy/l4d_target_override.smx sm plugins load optional/AnneHappy/SI_Target_limit.smx diff --git a/cfg/cfgogl/alone/confogl_plugins.cfg b/cfg/cfgogl/alone/confogl_plugins.cfg index ab9d24070..dc17b44b4 100644 --- a/cfg/cfgogl/alone/confogl_plugins.cfg +++ b/cfg/cfgogl/alone/confogl_plugins.cfg @@ -37,6 +37,7 @@ sm plugins load optional/AnneHappy/l4d2_hunter_patch.smx sm plugins load optional/AnneHappy/ai_hunter_2.smx sm plugins load optional/AnneHappy/ai_jockey_2.smx sm plugins load optional/AnneHappy/ai_charger_2.smx +sm plugins load optional/AnneHappy/si_pool.smx sm plugins load optional/AnneHappy/infected_control.smx sm plugins load optional/AnneHappy/l4d_target_override.smx sm plugins load optional/AnneHappy/SI_Target_limit.smx diff --git a/cfg/cfgogl/annehappy/confogl_plugins.cfg b/cfg/cfgogl/annehappy/confogl_plugins.cfg index 8becb8977..bf0d47d8f 100644 --- a/cfg/cfgogl/annehappy/confogl_plugins.cfg +++ b/cfg/cfgogl/annehappy/confogl_plugins.cfg @@ -34,6 +34,7 @@ sm plugins load optional/AnneHappy/ai_jockey_2.smx sm plugins load optional/AnneHappy/ai_spitter_2.smx sm plugins load optional/AnneHappy/ai_charger_2.smx sm plugins load optional/AnneHappy/ai_boomer_2.smx +sm plugins load optional/AnneHappy/si_pool.smx sm plugins load optional/AnneHappy/infected_control.smx sm plugins load optional/AnneHappy/l4d_target_override.smx sm plugins load optional/AnneHappy/SI_Target_limit.smx diff --git a/cfg/cfgogl/hunters/confogl_plugins.cfg b/cfg/cfgogl/hunters/confogl_plugins.cfg index 1d35cbaec..37eb6c047 100644 --- a/cfg/cfgogl/hunters/confogl_plugins.cfg +++ b/cfg/cfgogl/hunters/confogl_plugins.cfg @@ -33,6 +33,7 @@ sm plugins load optional/l4d2_skill_detect.smx //------------------------------------------- sm plugins load optional/AnneHappy/l4d2_hunter_patch.smx sm plugins load optional/AnneHappy/ai_hunter_2.smx +sm plugins load optional/AnneHappy/si_pool.smx sm plugins load optional/AnneHappy/infected_control.smx sm plugins load optional/AnneHappy/l4d_target_override.smx sm plugins load optional/AnneHappy/SI_Target_limit.smx diff --git a/cfg/cfgogl/witchparty/confogl_plugins.cfg b/cfg/cfgogl/witchparty/confogl_plugins.cfg index 45bf58fec..63f347f17 100644 --- a/cfg/cfgogl/witchparty/confogl_plugins.cfg +++ b/cfg/cfgogl/witchparty/confogl_plugins.cfg @@ -28,6 +28,7 @@ sm plugins load optional/l4d2_skill_detect.smx //------------------------------------------- sm plugins load optional/AnneHappy/l4d2_hunter_patch.smx sm plugins load optional/AnneHappy/ai_hunter_2.smx +sm plugins load optional/AnneHappy/si_pool.smx sm plugins load optional/AnneHappy/infected_control.smx sm plugins load optional/AnneHappy/l4d_target_override.smx sm plugins load optional/AnneHappy/SI_Target_limit.smx