/* История изменений:
0.2 ->
* Добавлена опция ответного ослепления (квар 'flashban_reflect_flash')
* Исправлен неверный lang-ключ 'FLBAN__FLASHED_BY' (спасибо Fanfar: https://dev-cs.ru/members/2074/)
0.3 ->
* Добавлен учёт mp_freeforall
* Добавлена поддержка актуального реапи (ATYPE_BOOL)
* Отказ от использования get_user_name() в пользу форматирования имени через '%n' (ВНИМАНИЕ, обновлён flashbang_ban.txt)
* Добавлен учёт выпадания флешки при смерти с зажатой кнопкой атаки (квар flashban_skip_deaththrow)
* Квар flashban_only_full упразднён
* Добавлены квары flashban_warns_for_partial, flashban_warns_for_full, flashban_dec_warns_kill
0.4 ->
* Добавлен функционал анти-ослепления тиммейтов
* Квар flashban_immune_flags переименован в flashban_punish_immune_flags
* Добавлен квар flashban_teamflash_immune_flags
* Добавлен квар flashban_allow_selfflash
*/
new const PLUGIN_NAME[] = "Flashbang Ban"
new const PLUGIN_VERSION[] = "0.4"
new const PLUGIN_AUTHOR[] = "mx?!"
#include <amxmodx>
#include <reapi>
#include <xs>
#include <fakemeta>
#if REAPI_VERSION < 59177
#define BOOL_RETURN ATYPE_INTEGER
#else
#define BOOL_RETURN ATYPE_BOOL
#endif
#define chx charsmax
#define AUTO_CFG // Создавать конфиг с кварами в 'configs/plugins', и запускать его ?
new const SOUND__BLIP1[] = "sound/buttons/blip1.wav"
new const SOUND__ERROR[] = "sound/buttons/button2.wav"
new const SOUND__TUTOR_MSG[] = "sound/events/tutor_msg.wav"
enum _:CVAR_ENUM {
CVAR__ENABLED,
CVAR__MAX_WARNS,
CVAR__BAN_ROUNDS,
CVAR__DEC_WARNS,
CVAR__DEC_WARNS_KILL,
CVAR__SKIP_DEATHTHROW,
CVAR__WARNS_FOR_PARTIAL,
CVAR__WARNS_FOR_FULL,
CVAR__REFLECT,
CVAR__PUNISH_IMMUNE_FLAGS[32],
CVAR__BLIND_IMMUNE_FLAGS[32],
CVAR__FREEFORALL,
CVAR__ALLOW_SELFFLASH
}
enum _:RGB { any:R, any:G, any:B }
const FULL_BLIND = 255
enum _:DATA_STRUCT {
DATA__WARNS,
DATA__BAN
}
new g_eCvar[CVAR_ENUM], g_szAuthID[64], Trie:g_tTrie, g_MsgIdScreenFade, g_eData[MAX_PLAYERS + 1][DATA_STRUCT]
/* -------------------- */
public plugin_init() {
register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR)
register_dictionary("flashbang_ban.txt")
func_RegCvars()
RegisterHookChain(RG_RadiusFlash_TraceLine, "RadiusFlash_TraceLine_Post", true)
RegisterHookChain(RG_PlayerBlind, "PlayerBlind_Pre")
RegisterHookChain(RG_ThrowFlashbang, "ThrowFlashBang_Post", true)
RegisterHookChain(RG_CBasePlayer_HasRestrictItem, "CBasePlayer_HasRestrictItem_Pre")
RegisterHookChain(RG_CSGameRules_RestartRound, "CSGameRules_RestartRound_Pre")
RegisterHookChain(RG_CBasePlayer_Killed, "CBasePlayer_Killed_Post", true)
g_tTrie = TrieCreate()
g_MsgIdScreenFade = get_user_msgid("ScreenFade")
}
/* -------------------- */
func_RegCvars() {
bind_pcvar_num(create_cvar("flashban_enabled", "1", .description = "Working state"), g_eCvar[CVAR__ENABLED])
bind_pcvar_num(create_cvar("flashban_max_warns", "5", .description = "Warns limit to ban flash", .has_min = true, .min_val = 1.0), g_eCvar[CVAR__MAX_WARNS])
bind_pcvar_num(create_cvar("flashban_ban_rounds", "3", .description = "How many rounds flash will be banned", .has_min = true, .min_val = 1.0), g_eCvar[CVAR__BAN_ROUNDS])
bind_pcvar_num(create_cvar("flashban_dec_warns", "1", .description = "How many warns decreased every round", .has_min = true, .min_val = 0.0), g_eCvar[CVAR__DEC_WARNS])
bind_pcvar_num(create_cvar("flashban_dec_warns_kill", "1", .description = "How many warns decreased every kill", .has_min = true, .min_val = 0.0), g_eCvar[CVAR__DEC_WARNS_KILL])
bind_pcvar_num(create_cvar("flashban_skip_deaththrow", "1", .description = "Skip warning if flashbang was throwed by dying"), g_eCvar[CVAR__SKIP_DEATHTHROW])
bind_pcvar_num(create_cvar("flashban_warns_for_partial", "1", .description = "Warns for partial flash"), g_eCvar[CVAR__WARNS_FOR_PARTIAL])
bind_pcvar_num(create_cvar("flashban_warns_for_full", "2", .description = "Warns for full flash"), g_eCvar[CVAR__WARNS_FOR_FULL])
bind_pcvar_num(create_cvar("flashban_reflect_flash", "1", .description = "Reflect teamflash to flasher"), g_eCvar[CVAR__REFLECT])
bind_pcvar_num(get_cvar_pointer("mp_freeforall"), g_eCvar[CVAR__FREEFORALL])
bind_pcvar_string(create_cvar("flashban_punish_immune_flags", "a", .description = "Punish immunity flags"), g_eCvar[CVAR__PUNISH_IMMUNE_FLAGS], chx(g_eCvar[CVAR__PUNISH_IMMUNE_FLAGS]))
bind_pcvar_string(create_cvar("flashban_teamflash_immune_flags", "a", .description = "Teamflash immunity flags"), g_eCvar[CVAR__BLIND_IMMUNE_FLAGS], chx(g_eCvar[CVAR__BLIND_IMMUNE_FLAGS]))
bind_pcvar_num(create_cvar("flashban_allow_selfflash", "1", .description = "Allow selfflash (override immunity)"), g_eCvar[CVAR__ALLOW_SELFFLASH])
#if defined AUTO_CFG
AutoExecConfig()
#endif
}
/* -------------------- */
bool:ShouldWork() {
return (g_eCvar[CVAR__ENABLED] && !g_eCvar[CVAR__FREEFORALL])
}
/* -------------------- */
public PlayerBlind_Pre(iVictim, iInflictor, iAttacker, Float:fFadeTime, Float:fFadeHold, iAlpha, Float:fColor[RGB]) {
if(
!ShouldWork()
||
iVictim == iAttacker
||
!is_user_connected(iAttacker)
||
get_member(iVictim, m_iTeam) != get_member(iAttacker, m_iTeam)
) {
return
}
client_print_color(iVictim, print_team_default, "%L", iVictim, "FLBAN__CHAT_FLASHED_BY", iAttacker)
console_print(iVictim, "%L", iVictim, "FLBAN__CON_FLASHED_BY", iAttacker)
rg_send_audio(iVictim, SOUND__TUTOR_MSG)
if(get_entvar(iInflictor, var_iuser3)) {
return
}
/* --- */
rg_send_audio(iAttacker, SOUND__TUTOR_MSG)
if(get_user_flags(iAttacker) & read_flags(g_eCvar[CVAR__PUNISH_IMMUNE_FLAGS])) {
client_print_color( iAttacker, print_team_default, "%L", iAttacker,
iAlpha == FULL_BLIND ? "FLBAN__IMM_FLASHED_PLAYER_FULL" : "FLBAN__IMM_FLASHED_PLAYER_PART", iVictim );
return
}
new iWarnsToAdd = g_eCvar[ (iAlpha == FULL_BLIND) ? CVAR__WARNS_FOR_FULL : CVAR__WARNS_FOR_PARTIAL ]
g_eData[iAttacker][DATA__WARNS] = min(g_eCvar[CVAR__MAX_WARNS], g_eData[iAttacker][DATA__WARNS] + iWarnsToAdd)
if(g_eCvar[CVAR__REFLECT] && IsNotFullyBlinded(iAttacker, iAlpha)) {
func_FlashPlayer(iAttacker, fFadeTime, fFadeHold, fColor, iAlpha)
}
client_print_color( iAttacker, print_team_default, "%L", iAttacker,
iAlpha == FULL_BLIND ? "FLBAN__FLASHED_PLAYER_FULL" : "FLBAN__FLASHED_PLAYER_PART",
iVictim, g_eData[iAttacker][DATA__WARNS], g_eCvar[CVAR__MAX_WARNS] );
if(g_eData[iAttacker][DATA__WARNS] < g_eCvar[CVAR__MAX_WARNS] || g_eData[iAttacker][DATA__BAN]) {
return
}
client_print(iAttacker, print_center, "%L", iAttacker, "FLBAN__CENTER_FLASH_BANNED")
g_eData[iAttacker][DATA__BAN] = g_eCvar[CVAR__BAN_ROUNDS]
}
/* -------------------- */
public RadiusFlash_TraceLine_Post(const index, inflictor, attacker, Float:vecSrc[3], Float:vecSpot[3], tracehandle) {
if(
!ShouldWork()
||
!is_user_connected(index)
||
!is_user_connected(attacker)
||
get_member(index, m_iTeam) != get_member(attacker, m_iTeam)
) {
return
}
if(ShouldAvoidFlash(index, attacker)) {
// https://github.com/s1lentq/ReGameDLL_CS/blob/efb06a7a201829bdbe13218bc5f5342e1f2ed8f1/regamedll/dlls/combat.cpp#L88
set_tr2(tracehandle, TR_flFraction, 0.0)
}
}
/* -------------------- */
bool:ShouldAvoidFlash(victim, attacker) {
if(get_user_flags(victim) & read_flags(g_eCvar[CVAR__BLIND_IMMUNE_FLAGS])) {
if(g_eCvar[CVAR__ALLOW_SELFFLASH] && victim == attacker) {
return false
}
return true
}
return false
}
/* -------------------- */
public ThrowFlashBang_Post(id, Float:vecStart[3], Float:vecVelocity[3], Float:time) {
if(!ShouldWork()) {
return
}
new pGrenade = GetHookChainReturn(ATYPE_INTEGER)
if(is_nullent(pGrenade)) {
return
}
if(g_eData[id][DATA__BAN]) {
if(!CheckThrowByDeath(id, vecVelocity)) {
func_BanInfo(id)
}
set_entvar(pGrenade, var_flags, FL_KILLME)
return
}
if(g_eCvar[CVAR__SKIP_DEATHTHROW] && CheckThrowByDeath(id, vecVelocity)) {
set_entvar(pGrenade, var_iuser3, 1)
}
}
/* -------------------- */
// https://github.com/s1lentq/ReGameDLL_CS/blob/15328fd76421caae2f133a25465dc90da590a6c6/regamedll/dlls/player.cpp#L2142
// https://github.com/s1lentq/ReGameDLL_CS/blob/efb06a7a201829bdbe13218bc5f5342e1f2ed8f1/regamedll/dlls/wpn_shared/wpn_flashbang.cpp#L197
stock CheckThrowByDeath(id, Float:vecVelocity[3]) {
new Float:fAngles[3]
get_entvar(id, var_angles, fAngles)
return xs_vec_equal(fAngles, vecVelocity)
}
/* -------------------- */
public CBasePlayer_HasRestrictItem_Pre(id, ItemID:iItem, ItemRestType:iRestType) {
if(!g_eData[id][DATA__BAN] || !ShouldWork() || iItem != ITEM_FLASHBANG || iRestType != ITEM_TYPE_BUYING) {
return HC_CONTINUE
}
func_BanInfo(id)
SetHookChainReturn(BOOL_RETURN, true)
return HC_SUPERCEDE
}
/* -------------------- */
public CSGameRules_RestartRound_Pre() {
if(!ShouldWork()) {
return
}
new iPlayers[MAX_PLAYERS], iPlCount
get_players(iPlayers, iPlCount, "ch")
for(new i, iPlayer; i < iPlCount; i++) {
iPlayer = iPlayers[i]
if(g_eData[iPlayer][DATA__WARNS]) {
g_eData[iPlayer][DATA__WARNS] = max(0, g_eData[iPlayer][DATA__WARNS] - g_eCvar[CVAR__DEC_WARNS])
}
if(!g_eData[iPlayer][DATA__BAN]) {
continue
}
g_eData[iPlayer][DATA__BAN]--
if(!g_eData[iPlayer][DATA__BAN]) {
rg_send_audio(iPlayer, SOUND__BLIP1)
client_print_color(iPlayer, print_team_default, "%L", iPlayer, "FLBAN__BAN_EXPIRED")
}
}
}
/* -------------------- */
public CBasePlayer_Killed_Post(iVictim, iKiller, iGibType) {
if(is_user_connected(iKiller) && g_eData[iKiller][DATA__WARNS] && iVictim != iKiller) {
g_eData[iKiller][DATA__WARNS] = max(0, g_eData[iKiller][DATA__WARNS] - g_eCvar[CVAR__DEC_WARNS_KILL])
}
}
/* -------------------- */
stock func_BanInfo(id) {
rg_send_audio(id, SOUND__ERROR)
client_print(id, print_center, "%L", id, "FLBAN__CENTER_FLASH_BANNED")
client_print_color(id, print_team_red, "%L", id, "FLBAN__INFO", g_eData[id][DATA__BAN])
}
/* -------------------- */
public client_putinserver(id) {
if(!ShouldWork()) {
return
}
get_user_authid(id, g_szAuthID, chx(g_szAuthID))
if(TrieGetArray(g_tTrie, g_szAuthID, g_eData[id], sizeof(g_eData[]))) {
TrieDeleteKey(g_tTrie, g_szAuthID)
}
}
/* -------------------- */
public client_disconnected(id) {
if(g_eData[id][DATA__WARNS] || g_eData[id][DATA__BAN]) {
get_user_authid(id, g_szAuthID, chx(g_szAuthID))
TrieSetArray(g_tTrie, g_szAuthID, g_eData[id], sizeof(g_eData[]))
g_eData[id][DATA__WARNS] = 0
g_eData[id][DATA__BAN] = 0
}
}
/* -------------------- */
stock func_FlashPlayer(id, Float:fOutTime, Float:fHoldTime, Float:fColor[RGB], iAlpha) {
message_begin(MSG_ONE_UNRELIABLE, g_MsgIdScreenFade, .player = id)
write_short(FixedUnsigned16(fOutTime)) // SCREENFADE__OUT_TIME (duration / fade time)
write_short(FixedUnsigned16(fHoldTime)) // SCREENFADE__HOLD_TIME (hold time)
write_short(0x0000) // fade in
write_byte(floatround(fColor[R]))
write_byte(floatround(fColor[G]))
write_byte(floatround(fColor[B]))
write_byte(iAlpha)
message_end()
new Float:fGameTime = get_gametime()
set_member(id, m_blindStartTime, fGameTime)
set_member(id, m_blindFadeTime, fOutTime)
set_member(id, m_blindHoldTime, fHoldTime)
set_member(id, m_blindUntilTime, fGameTime + fOutTime)
set_member(id, m_blindAlpha, iAlpha)
}
/* -------------------- */
stock FixedUnsigned16(Float:fVal, iScale = (1 << 12)) {
return clamp(floatround(fVal * iScale), 0, 0xFFFF)
}
/* -------------------- */
stock bool:IsNotFullyBlinded(id, iAlpha) {
return bool:(
get_member(id, m_blindAlpha) < iAlpha
||
Float:get_member(id, m_blindStartTime) + Float:get_member(id, m_blindHoldTime) < get_gametime()
);
}