#pragma semicolon 1 #pragma newdecls required #include #include #include #include #include "lvl_ranks" #define AGD_LIBRARY "arcane_duels_core" #define ARENA_CFG "cfg/sourcemod/ArcaneGameDUELS_Arena.cfg" #define BEACON_SOUND "buttons/blip1.wav" #define DUEL_PREFIX "\x02[DUELS]\x01" #define DUEL_OUT_SOUND "buttons/button10.wav" public Plugin myinfo = { name = "[CORE] ArcaneGameDUELS Core", author = "OpenAI / havno", description = "Core of ArcaneGameDUELS (duel deagle compatibility build)", version = "1.5.3", url = "" }; bool g_bDuelActive = false; bool g_bDuelPending = false; bool g_bDuelPreparing = false; bool g_bBombPlanted = false; int g_iDuelP1 = 0; int g_iDuelP2 = 0; int g_iDuelController = 0; int g_iParticipantXP[MAXPLAYERS + 1]; bool g_bAccepted[MAXPLAYERS + 1]; int g_iPrepareLeft = 0; int g_iRoundStartPlayingHumans = 0; char g_sSelectedWeapon[32] = "weapon_ak47"; enum DuelModifier { DuelModifier_None = 0, DuelModifier_NoZoom, DuelModifier_HeadshotOnly }; DuelModifier g_iDuelModifier = DuelModifier_None; char g_sSavedWeapon[MAXPLAYERS + 1][3][32]; int g_iSavedHealth[MAXPLAYERS + 1]; int g_iSavedArmor[MAXPLAYERS + 1]; int g_iSavedMoney[MAXPLAYERS + 1]; int g_iSavedUserFlags[MAXPLAYERS + 1]; bool g_bSavedUserFlags[MAXPLAYERS + 1]; Handle g_hFwdOnDuelStarted = null; Handle g_hFwdOnDuelFinished = null; Handle g_hFwdOnDuelDraw = null; Handle g_hBeaconTimer = null; Handle g_hPrepareTimer = null; Handle g_hInvulnTimer = null; Handle g_hZoneTimer = null; Handle g_hZonePreviewTimer = null; Handle g_hPendingOneVsOneTimer = null; Handle g_hDuelLimitTimer = null; Handle g_hPrepZoneTimer = null; ConVar gCvarWinMoney; ConVar gCvarEnable; ConVar gCvarUseArena; ConVar gCvarBeacon; ConVar gCvarPrepareTime; ConVar gCvarDuelWinXP; ConVar gCvarPostUnfreezeInvuln; ConVar gCvarAllowKnife; ConVar gCvarAllowDeagle; ConVar gCvarAllowAK47; ConVar gCvarAllowM4A4; ConVar gCvarAllowM4A1S; ConVar gCvarAllowAWP; ConVar gCvarAllowScout; ConVar gCvarWeaponBypassFlags; ConVar gCvarZoneEnable; ConVar gCvarZoneGrace; ConVar gCvarZonePaddingXY; ConVar gCvarZonePaddingZDown; ConVar gCvarZonePaddingZUp; ConVar gCvarZonePreviewTime; ConVar gCvarZoneRenderInterval; ConVar gCvarDebugAllowSolo; ConVar gCvarDuelTimeLimit; bool g_bArenaReady = false; float g_vecArenaSpawn1[3]; float g_angArenaSpawn1[3]; float g_vecArenaSpawn2[3]; float g_angArenaSpawn2[3]; int g_iBeamSprite = -1; int g_iHaloSprite = -1; bool g_bArenaZoneReady = false; float g_vecArenaZoneMin[3]; float g_vecArenaZoneMax[3]; float g_vecManualZonePoint1[3]; float g_vecManualZonePoint2[3]; bool g_bManualZonePoint1Set = false; bool g_bManualZonePoint2Set = false; bool g_bZoneOutside[MAXPLAYERS + 1]; int g_iZoneGraceLeft[MAXPLAYERS + 1]; bool g_bDebugSoloMode = false; public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { RegPluginLibrary(AGD_LIBRARY); CreateNative("AGD_IsDuelActive", Native_AGD_IsDuelActive); CreateNative("AGD_IsBettingOpen", Native_AGD_IsBettingOpen); CreateNative("AGD_GetDuelPlayers", Native_AGD_GetDuelPlayers); CreateNative("AGD_GetDuelParticipantXP", Native_AGD_GetDuelParticipantXP); CreateNative("AGD_GetWinnerMoneyReward", Native_AGD_GetWinnerMoneyReward); CreateNative("AGD_IsArenaReady", Native_AGD_IsArenaReady); CreateNative("AGD_IsClientInDuel", Native_AGD_IsClientInDuel); return APLRes_Success; } public void OnPluginStart() { gCvarEnable = CreateConVar("agd_core_enable", "1", "Enable ArcaneGameDUELS core", _, true, 0.0, true, 1.0); gCvarWinMoney = CreateConVar("agd_duel_win_money", "1500", "Money reward for duel winner", _, true, 0.0, true, 16000.0); gCvarUseArena = CreateConVar("agd_use_arena", "1", "Teleport duel players to saved arena spawns", _, true, 0.0, true, 1.0); gCvarBeacon = CreateConVar("agd_duel_beacon", "1", "Enable beacons during duel", _, true, 0.0, true, 1.0); gCvarPrepareTime = CreateConVar("agd_duel_prepare_time", "10", "Prepare time before duel start", _, true, 3.0, true, 30.0); gCvarDuelWinXP = CreateConVar("agd_duel_win_xp", "100", "XP reward for duel winner", _, true, 0.0, true, 1000000.0); gCvarPostUnfreezeInvuln = CreateConVar("agd_duel_post_unfreeze_invuln", "2.0", "Invulnerability time after unfreeze", _, true, 0.0, true, 10.0); gCvarAllowKnife = CreateConVar("agd_weapon_allow_knife", "1", "Allow Knife in duel weapon menu", _, true, 0.0, true, 1.0); gCvarAllowDeagle = CreateConVar("agd_weapon_allow_deagle", "1", "Allow Deagle in duel weapon menu", _, true, 0.0, true, 1.0); gCvarAllowAK47 = CreateConVar("agd_weapon_allow_ak47", "1", "Allow AK-47 in duel weapon menu", _, true, 0.0, true, 1.0); gCvarAllowM4A4 = CreateConVar("agd_weapon_allow_m4a1", "1", "Allow M4A4 in duel weapon menu", _, true, 0.0, true, 1.0); gCvarAllowM4A1S = CreateConVar("agd_weapon_allow_m4a1s", "1", "Allow M4A1-S in duel weapon menu", _, true, 0.0, true, 1.0); gCvarAllowAWP = CreateConVar("agd_weapon_allow_awp", "1", "Allow AWP in duel weapon menu", _, true, 0.0, true, 1.0); gCvarAllowScout = CreateConVar("agd_weapon_allow_scout", "1", "Allow Scout in duel weapon menu", _, true, 0.0, true, 1.0); gCvarWeaponBypassFlags = CreateConVar("agd_duel_bypass_restrict_flags", "1", "Temporarily give duelists bypass flags during duel to ignore weapon restrictions", _, true, 0.0, true, 1.0); gCvarZoneEnable = CreateConVar("agd_zone_enable", "1", "Enable duel zone restriction", _, true, 0.0, true, 1.0); gCvarZoneGrace = CreateConVar("agd_zone_grace_time", "3", "Seconds to return to duel zone before losing", _, true, 1.0, true, 10.0); gCvarZonePaddingXY = CreateConVar("agd_zone_padding_xy", "320.0", "Auto-generated duel zone XY padding from both spawns", _, true, 50.0, true, 2000.0); gCvarZonePaddingZDown = CreateConVar("agd_zone_padding_z_down", "80.0", "Auto-generated duel zone downward padding", _, true, 0.0, true, 1000.0); gCvarZonePaddingZUp = CreateConVar("agd_zone_padding_z_up", "180.0", "Auto-generated duel zone upward padding", _, true, 0.0, true, 2000.0); gCvarZonePreviewTime = CreateConVar("agd_zone_preview_time", "10.0", "How long admin preview of duel zone is shown", _, true, 1.0, true, 60.0); gCvarZoneRenderInterval = CreateConVar("agd_zone_render_interval", "0.5", "Interval for duel zone rendering and checks", _, true, 0.1, true, 2.0); gCvarDebugAllowSolo = CreateConVar("agd_debug_allow_solo", "1", "Allow root admins to start solo duel debug session", _, true, 0.0, true, 1.0); gCvarDuelTimeLimit = CreateConVar("agd_duel_time_limit", "30", "Time limit for duel in seconds (0 = no limit)", _, true, 0.0, true, 300.0); AutoExecConfig(true, "ArcaneGameDUELS_Core"); g_hFwdOnDuelStarted = CreateGlobalForward("AGD_OnDuelStarted", ET_Ignore, Param_Cell, Param_Cell); g_hFwdOnDuelFinished = CreateGlobalForward("AGD_OnDuelFinished", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); g_hFwdOnDuelDraw = CreateGlobalForward("AGD_OnDuelDraw", ET_Ignore, Param_Cell, Param_Cell); RegConsoleCmd("sm_duel", Command_DuelMenu); RegAdminCmd("sm_duel_setspawn1", Command_SetSpawn1, ADMFLAG_ROOT); RegAdminCmd("sm_duel_setspawn2", Command_SetSpawn2, ADMFLAG_ROOT); RegAdminCmd("sm_duel_setzone1", Command_SetZonePoint1, ADMFLAG_ROOT); RegAdminCmd("sm_duel_setzone2", Command_SetZonePoint2, ADMFLAG_ROOT); RegAdminCmd("sm_duel_savearena", Command_SaveArena, ADMFLAG_ROOT); RegAdminCmd("sm_duel_reloadarena", Command_ReloadArena, ADMFLAG_ROOT); RegAdminCmd("sm_duel_arena_info", Command_ArenaInfo, ADMFLAG_ROOT); RegAdminCmd("sm_duel_showzone", Command_ShowZone, ADMFLAG_ROOT); RegAdminCmd("sm_duel_debugsolo", Command_DebugSolo, ADMFLAG_ROOT); HookEvent("player_death", Event_PlayerDeath, EventHookMode_Post); HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy); HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy); HookEvent("bomb_planted", Event_BombPlanted, EventHookMode_PostNoCopy); HookEvent("bomb_defused", Event_BombDefused, EventHookMode_PostNoCopy); HookEvent("bomb_exploded", Event_BombExploded, EventHookMode_PostNoCopy); LoadArenaForCurrentMap(); for (int i = 1; i <= MaxClients; i++) { if (IsClientInGame(i)) OnClientPutInServer(i); } } public void OnMapStart() { LoadArenaForCurrentMap(); PrecacheSound(BEACON_SOUND, true); PrecacheSound(DUEL_OUT_SOUND, true); g_iBeamSprite = PrecacheModel("materials/sprites/laserbeam.vmt", true); g_iHaloSprite = PrecacheModel("materials/sprites/glow01.vmt", true); } public void OnClientPutInServer(int client) { SDKHook(client, SDKHook_TraceAttack, OnTraceAttack); SDKHook(client, SDKHook_WeaponCanUse, OnWeaponCanUse); SDKHook(client, SDKHook_PostThinkPost, OnPlayerPostThink); } public void OnClientDisconnect(int client) { DisableDuelWeaponBypass(client); if ((g_bDuelPending || g_bDuelPreparing) && (client == g_iDuelP1 || client == g_iDuelP2)) { if (g_bDuelPreparing) CancelPreparingDuel("Дуэль отменена: игрок вышел во время подготовки."); else CancelPendingDuel("Дуэль отменена: игрок вышел во время принятия."); return; } if (g_bDebugSoloMode && client == g_iDuelP1) { ResetDuelState(); return; } if (!g_bDuelActive) return; if (client == g_iDuelP1 || client == g_iDuelP2) { int winner = (client == g_iDuelP1) ? g_iDuelP2 : g_iDuelP1; int loser = client; if (IsValidHuman(winner)) FinishDuel(winner, loser); else ResetDuelState(); } } public Action Command_DuelMenu(int client, int args) { if (!IsValidHuman(client)) return Plugin_Handled; PrintToChat(client, "%s Дуэли запускаются автоматически при 1x1, если на сервере больше 2 игроков. Для теста в одиночку: sm_duel_debugsolo", DUEL_PREFIX); return Plugin_Handled; } public Action Command_SetSpawn1(int client, int args) { if (!IsValidHuman(client)) return Plugin_Handled; GetClientAbsOrigin(client, g_vecArenaSpawn1); GetClientAbsAngles(client, g_angArenaSpawn1); PrintToChat(client, "%s Точка арены #1 сохранена.", DUEL_PREFIX); g_bArenaReady = HasBothArenaSpawns(); if (g_bArenaReady && g_bArenaZoneReady) { StartZonePreview(client, gCvarZonePreviewTime.FloatValue); PrintToChat(client, "%s Текущая зона показана для проверки.", DUEL_PREFIX); } return Plugin_Handled; } public Action Command_SetSpawn2(int client, int args) { if (!IsValidHuman(client)) return Plugin_Handled; GetClientAbsOrigin(client, g_vecArenaSpawn2); GetClientAbsAngles(client, g_angArenaSpawn2); PrintToChat(client, "%s Точка арены #2 сохранена.", DUEL_PREFIX); g_bArenaReady = HasBothArenaSpawns(); if (g_bArenaReady && g_bArenaZoneReady) { StartZonePreview(client, gCvarZonePreviewTime.FloatValue); PrintToChat(client, "%s Текущая зона показана для проверки.", DUEL_PREFIX); } return Plugin_Handled; } public Action Command_SetZonePoint1(int client, int args) { if (!IsValidHuman(client)) return Plugin_Handled; GetClientAbsOrigin(client, g_vecManualZonePoint1); g_bManualZonePoint1Set = true; ReplyToCommand(client, "[DUELS] Точка зоны #1 сохранена: %.1f %.1f %.1f", g_vecManualZonePoint1[0], g_vecManualZonePoint1[1], g_vecManualZonePoint1[2]); if (g_bManualZonePoint2Set) { BuildArenaZoneFromManualPoints(); StartZonePreview(client, gCvarZonePreviewTime.FloatValue); ReplyToCommand(client, "[DUELS] Зона пересобрана вручную по двум диагональным точкам. Высота: 200.0"); } else { ReplyToCommand(client, "[DUELS] Теперь встань во второй угол по диагонали и введи sm_duel_setzone2"); } return Plugin_Handled; } public Action Command_SetZonePoint2(int client, int args) { if (!IsValidHuman(client)) return Plugin_Handled; GetClientAbsOrigin(client, g_vecManualZonePoint2); g_bManualZonePoint2Set = true; ReplyToCommand(client, "[DUELS] Точка зоны #2 сохранена: %.1f %.1f %.1f", g_vecManualZonePoint2[0], g_vecManualZonePoint2[1], g_vecManualZonePoint2[2]); if (g_bManualZonePoint1Set) { BuildArenaZoneFromManualPoints(); StartZonePreview(client, gCvarZonePreviewTime.FloatValue); ReplyToCommand(client, "[DUELS] Зона пересобрана вручную по двум диагональным точкам. Высота: 200.0"); } else { ReplyToCommand(client, "[DUELS] Сначала поставь первую точку командой sm_duel_setzone1"); } return Plugin_Handled; } public Action Command_SaveArena(int client, int args) { if (!HasBothArenaSpawns()) { ReplyToCommand(client, "[DUELS] Сначала задай обе точки спавна: sm_duel_setspawn1 и sm_duel_setspawn2"); return Plugin_Handled; } if (!g_bArenaZoneReady) { ReplyToCommand(client, "[DUELS] Сначала задай зону командами sm_duel_setzone1 и sm_duel_setzone2"); return Plugin_Handled; } if (SaveArenaForCurrentMap()) ReplyToCommand(client, "[DUELS] Арена сохранена для текущей карты."); else ReplyToCommand(client, "[DUELS] Не удалось сохранить арену."); return Plugin_Handled; } public Action Command_ReloadArena(int client, int args) { LoadArenaForCurrentMap(); ReplyToCommand(client, g_bArenaReady ? "[DUELS] Арена перезагружена." : "[DUELS] Для этой карты арена не найдена."); return Plugin_Handled; } public Action Command_ArenaInfo(int client, int args) { if (!g_bArenaReady) { ReplyToCommand(client, "[DUELS] Арена для текущей карты не настроена."); return Plugin_Handled; } if (g_bArenaZoneReady) { ReplyToCommand(client, "[DUELS] Арена готова. Зона: min(%.1f %.1f %.1f) | max(%.1f %.1f %.1f)", g_vecArenaZoneMin[0], g_vecArenaZoneMin[1], g_vecArenaZoneMin[2], g_vecArenaZoneMax[0], g_vecArenaZoneMax[1], g_vecArenaZoneMax[2]); } else { ReplyToCommand(client, "[DUELS] Арена готова, но зона еще не задана."); } return Plugin_Handled; } public Action Command_ShowZone(int client, int args) { if (!IsValidHuman(client)) return Plugin_Handled; if (!g_bArenaZoneReady) { ReplyToCommand(client, "[DUELS] Зона арены не настроена."); return Plugin_Handled; } StartZonePreview(client, gCvarZonePreviewTime.FloatValue); ReplyToCommand(client, "[DUELS] Показываю зону на %.1f сек.", gCvarZonePreviewTime.FloatValue); return Plugin_Handled; } public Action Command_DebugSolo(int client, int args) { if (!IsValidHuman(client)) return Plugin_Handled; if (!gCvarDebugAllowSolo.BoolValue) { ReplyToCommand(client, "[DUELS] Соло-дебаг отключен cvar'ом agd_debug_allow_solo."); return Plugin_Handled; } if (g_bDuelActive || g_bDuelPending || g_bDuelPreparing) { ReplyToCommand(client, "[DUELS] Сейчас уже идет дуэль или подготовка."); return Plugin_Handled; } if (!g_bArenaReady) { ReplyToCommand(client, "[DUELS] Сначала настрой арены через sm_duel_setspawn1 / sm_duel_setspawn2."); return Plugin_Handled; } BeginSoloDebugPreparation(client); return Plugin_Handled; } public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { g_bBombPlanted = false; CancelPendingDuel(""); CancelPreparingDuel(""); if (g_bDuelActive) ResetDuelState(); g_iRoundStartPlayingHumans = GetPlayingHumanPlayersCount(); } public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { g_bBombPlanted = false; g_iRoundStartPlayingHumans = 0; CancelPendingDuel(""); CancelPreparingDuel(""); if (!g_bDuelActive) return; if (g_bDebugSoloMode) { ResetDuelState(); return; } bool p1Alive = IsValidHuman(g_iDuelP1) && IsPlayerAlive(g_iDuelP1); bool p2Alive = IsValidHuman(g_iDuelP2) && IsPlayerAlive(g_iDuelP2); if (p1Alive && p2Alive) { FinishDuelDraw(g_iDuelP1, g_iDuelP2); return; } ResetDuelState(); } public void Event_BombPlanted(Event event, const char[] name, bool dontBroadcast) { g_bBombPlanted = true; if (g_bDuelPending) CancelPendingDuel("Дуэль отменена: во время принятия была установлена бомба."); else if (g_bDuelPreparing) CancelPreparingDuel("Дуэль отменена: во время подготовки была установлена бомба."); } public void Event_BombDefused(Event event, const char[] name, bool dontBroadcast) { g_bBombPlanted = false; } public void Event_BombExploded(Event event, const char[] name, bool dontBroadcast) { g_bBombPlanted = false; } public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { int victim = GetClientOfUserId(event.GetInt("userid")); int attacker = GetClientOfUserId(event.GetInt("attacker")); if (!IsValidClient(victim)) return; if (!g_bDuelActive) { if (!g_bBombPlanted && !g_bDuelPending && !g_bDuelPreparing && g_iRoundStartPlayingHumans > 2) QueueOneVsOneCheck(); return; } if (victim != g_iDuelP1 && victim != g_iDuelP2) return; int winner = 0; int loser = victim; if (attacker == g_iDuelP1 || attacker == g_iDuelP2) winner = attacker; else winner = (victim == g_iDuelP1) ? g_iDuelP2 : g_iDuelP1; if (IsValidHuman(winner)) FinishDuel(winner, loser); else if (g_bDebugSoloMode && victim == g_iDuelP1) { PrintToChat(victim, "%s Соло-дебаг завершен: ты умер.", DUEL_PREFIX); RestoreLoadout(g_iDuelP1); ResetDuelState(); } } void QueueOneVsOneCheck() { if (g_hPendingOneVsOneTimer != null) return; g_hPendingOneVsOneTimer = CreateTimer(0.2, Timer_CheckForOneVsOne, _, TIMER_FLAG_NO_MAPCHANGE); } public Action Timer_CheckForOneVsOne(Handle timer, any data) { g_hPendingOneVsOneTimer = null; if (!gCvarEnable.BoolValue || g_bBombPlanted || g_bDuelActive || g_bDuelPending || g_bDuelPreparing) return Plugin_Stop; if (g_iRoundStartPlayingHumans <= 2) return Plugin_Stop; if (GetPlayingHumanPlayersCount() <= 2) return Plugin_Stop; int tAlive = 0, ctAlive = 0; int tClient = 0, ctClient = 0; GetAliveOneVsOne(tAlive, ctAlive, tClient, ctClient); if (tAlive == 1 && ctAlive == 1 && IsValidHuman(tClient) && IsValidHuman(ctClient)) OfferAutomaticDuel(tClient, ctClient); return Plugin_Stop; } void OfferAutomaticDuel(int client1, int client2) { if (!IsValidHuman(client1) || !IsValidHuman(client2) || client1 == client2) return; g_bDuelPending = true; g_bDuelPreparing = false; g_bDuelActive = false; g_iDuelP1 = client1; g_iDuelP2 = client2; g_iDuelController = 0; g_bAccepted[client1] = false; g_bAccepted[client2] = false; PrintToChatAll("\x02[DUELS]\x01 Остались 1 на 1: \x03%N\x01 vs \x03%N\x01. Ожидается подтверждение дуэли.", client1, client2); ShowAcceptMenu(client1); ShowAcceptMenu(client2); } void ShowAcceptMenu(int client) { if (!IsValidHuman(client)) return; Menu menu = new Menu(MenuHandler_Accept); menu.SetTitle("Принять дуэль? (10 сек)"); menu.AddItem("1", "Принять"); menu.AddItem("0", "Отклонить"); menu.ExitButton = false; menu.Display(client, 10); } public int MenuHandler_Accept(Menu menu, MenuAction action, int client, int item) { switch (action) { case MenuAction_End: delete menu; case MenuAction_Cancel: { if (g_bDuelPending && IsValidHuman(client)) CancelPendingDuel("Дуэль отменена: один из игроков не ответил."); } case MenuAction_Select: { if (!g_bDuelPending || !IsValidHuman(client)) return 0; char info[8]; menu.GetItem(item, info, sizeof(info)); if (StringToInt(info) == 1) { g_bAccepted[client] = true; PrintToChat(client, "\x02[DUELS]\x01 Ты принял дуэль."); if (g_bAccepted[g_iDuelP1] && g_bAccepted[g_iDuelP2]) BeginPreparation(g_iDuelP1, g_iDuelP2); } else { CancelPendingDuel("Дуэль отклонена одним из игроков."); } } } return 0; } void BeginPreparation(int client1, int client2) { g_bDuelPending = false; g_bDuelPreparing = true; g_bDuelActive = false; g_iDuelP1 = client1; g_iDuelP2 = client2; g_iDuelController = GetRandomInt(0, 1) == 0 ? client1 : client2; g_iPrepareLeft = gCvarPrepareTime.IntValue; strcopy(g_sSelectedWeapon, sizeof(g_sSelectedWeapon), "weapon_ak47"); g_iDuelModifier = DuelModifier_None; g_iParticipantXP[client1] = 0; g_iParticipantXP[client2] = 0; SaveLoadout(client1); SaveLoadout(client2); if (gCvarUseArena.BoolValue && g_bArenaReady) { TeleportEntity(client1, g_vecArenaSpawn1, g_angArenaSpawn1, NULL_VECTOR); TeleportEntity(client2, g_vecArenaSpawn2, g_angArenaSpawn2, NULL_VECTOR); } SetDuelProtection(client1, true); SetDuelProtection(client2, true); StartPrepZoneTimer(); PrintToChatAll("\x02[DUELS]\x01 Дуэль принята. Настройки выбирает случайный игрок: \x03%N", g_iDuelController); PrintToChatAll("\x02[DUELS]\x01 Подготовка к дуэли: \x05%d\x01 сек. В это время доступны ставки.", g_iPrepareLeft); ShowWeaponMenu(g_iDuelController); StopPrepareTimer(); g_hPrepareTimer = CreateTimer(1.0, Timer_PrepareCountdown, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } void BeginSoloDebugPreparation(int client) { g_bDebugSoloMode = true; g_bDuelPending = false; g_bDuelPreparing = true; g_bDuelActive = false; g_iDuelP1 = client; g_iDuelP2 = 0; g_iDuelController = client; g_iPrepareLeft = gCvarPrepareTime.IntValue; strcopy(g_sSelectedWeapon, sizeof(g_sSelectedWeapon), "weapon_ak47"); g_iDuelModifier = DuelModifier_None; g_iParticipantXP[client] = 0; g_bAccepted[client] = true; SaveLoadout(client); if (gCvarUseArena.BoolValue && g_bArenaReady) TeleportEntity(client, g_vecArenaSpawn1, g_angArenaSpawn1, NULL_VECTOR); SetDuelProtection(client, true); StartPrepZoneTimer(); PrintToChat(client, "%s Запущен соло-дебаг дуэли. Можешь тестировать оружие, подготовку и зону даже один на карте.", DUEL_PREFIX); ShowWeaponMenu(client); StopPrepareTimer(); g_hPrepareTimer = CreateTimer(1.0, Timer_PrepareCountdown, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } void ShowWeaponMenu(int client) { if (!IsValidHuman(client) || !g_bDuelPreparing) return; Menu menu = new Menu(MenuHandler_WeaponMenu); menu.SetTitle("Настройка дуэли: оружие"); int added = 0; if (gCvarAllowKnife.BoolValue) { menu.AddItem("weapon_knife", "Ножи"); added++; } if (gCvarAllowDeagle.BoolValue) { menu.AddItem("weapon_deagle", "Deagle"); added++; } if (gCvarAllowAK47.BoolValue) { menu.AddItem("weapon_ak47", "AK-47"); added++; } if (gCvarAllowM4A4.BoolValue) { menu.AddItem("weapon_m4a1", "M4A4/M4A1-S"); added++; } if (gCvarAllowAWP.BoolValue) { menu.AddItem("weapon_awp", "AWP"); added++; } if (gCvarAllowScout.BoolValue) { menu.AddItem("weapon_ssg08", "SSG08 / Scout"); added++; } if (added <= 0) menu.AddItem("blocked", "Нет доступного оружия", ITEMDRAW_DISABLED); menu.ExitButton = false; menu.Display(client, 10); } public int MenuHandler_WeaponMenu(Menu menu, MenuAction action, int client, int item) { switch (action) { case MenuAction_End: delete menu; case MenuAction_Select: { if (!g_bDuelPreparing || client != g_iDuelController) return 0; char info[32]; menu.GetItem(item, info, sizeof(info)); strcopy(g_sSelectedWeapon, sizeof(g_sSelectedWeapon), info); g_iDuelModifier = DuelModifier_None; if (!StrEqual(g_sSelectedWeapon, "weapon_knife")) ShowModifierMenu(client); else AnnounceSelectedSettings(); } } return 0; } void ShowModifierMenu(int client) { Menu menu = new Menu(MenuHandler_ModifierMenu); menu.SetTitle("Режим дуэли: обычный или только в голову"); menu.AddItem("0", "Обычный"); menu.AddItem("2", "Только в голову"); menu.ExitButton = false; menu.Display(client, 10); } public int MenuHandler_ModifierMenu(Menu menu, MenuAction action, int client, int item) { switch (action) { case MenuAction_End: delete menu; case MenuAction_Select: { if (!g_bDuelPreparing || client != g_iDuelController) return 0; char info[8]; menu.GetItem(item, info, sizeof(info)); g_iDuelModifier = view_as(StringToInt(info)); AnnounceSelectedSettings(); } } return 0; } void AnnounceSelectedSettings() { char weaponName[32]; GetDuelWeaponName(weaponName, sizeof(weaponName)); if (g_iDuelModifier == DuelModifier_HeadshotOnly) PrintToChatAll("\x02[DUELS]\x01 Выбрана дуэль: \x03%s\x01 | режим: \x05Только в голову", weaponName); else PrintToChatAll("\x02[DUELS]\x01 Выбрана дуэль: \x03%s\x01 | режим: \x05Обычный", weaponName); } public Action Timer_PrepareCountdown(Handle timer, any data) { if (!g_bDuelPreparing) return Plugin_Stop; if (g_bBombPlanted) { CancelPreparingDuel("Дуэль отменена: во время подготовки была установлена бомба."); return Plugin_Stop; } if (!IsValidHuman(g_iDuelP1) || (!g_bDebugSoloMode && !IsValidHuman(g_iDuelP2))) { CancelPreparingDuel("Дуэль отменена: один из игроков покинул сервер."); return Plugin_Stop; } g_iPrepareLeft--; if (g_iPrepareLeft > 0) { PrintCenterText(g_iDuelP1, g_bDebugSoloMode ? "Соло-дебаг начнется через %d" : "Дуэль начнется через %d", g_iPrepareLeft); if (!g_bDebugSoloMode && IsValidHuman(g_iDuelP2)) PrintCenterText(g_iDuelP2, "Дуэль начнется через %d", g_iPrepareLeft); return Plugin_Continue; } if (g_bDebugSoloMode) StartSoloDebug(g_iDuelP1); else StartDuel(g_iDuelP1, g_iDuelP2); return Plugin_Stop; } void StartDuel(int client1, int client2) { StopPrepareTimer(); if (!gCvarEnable.BoolValue) return; g_bDuelPending = false; g_bDuelPreparing = false; g_bDuelActive = true; g_iDuelP1 = client1; g_iDuelP2 = client2; EquipPlayerForDuel(client1); EquipPlayerForDuel(client2); EnsureDuelWeaponPresent(client1); EnsureDuelWeaponPresent(client2); // Fair duel start: restore health, armor, and helmet for both players SetEntityHealth(client1, 100); SetEntityHealth(client2, 100); SetEntProp(client1, Prop_Send, "m_ArmorValue", 100); SetEntProp(client2, Prop_Send, "m_ArmorValue", 100); SetEntProp(client1, Prop_Send, "m_bHasHelmet", 1); SetEntProp(client2, Prop_Send, "m_bHasHelmet", 1); SetEntityMoveType(client1, MOVETYPE_WALK); SetEntityMoveType(client2, MOVETYPE_WALK); SetEntProp(client1, Prop_Data, "m_takedamage", 0); SetEntProp(client2, Prop_Data, "m_takedamage", 0); StopInvulnTimer(); StopZoneTimer(); StopZonePreview(); StopPrepZoneTimer(); g_hInvulnTimer = CreateTimer(gCvarPostUnfreezeInvuln.FloatValue, Timer_DisablePostUnfreezeInvuln, _, TIMER_FLAG_NO_MAPCHANGE); StartBeaconTimer(); StartZoneTimer(); StartDuelLimitTimer(); char weaponName[32]; GetDuelWeaponName(weaponName, sizeof(weaponName)); PrintToChatAll("\x02[DUELS]\x01 Дуэль началась: \x03%N\x01 vs \x03%N\x01 | \x05%s", client1, client2, weaponName); PrintToChatAll("\x02[DUELS]\x01 Заморозка снята. Неуязвимость будет убрана через \x05%.0f\x01 сек. | Лимит дуэли: \x05%d\x01 сек.", gCvarPostUnfreezeInvuln.FloatValue, gCvarDuelTimeLimit.IntValue); Call_StartForward(g_hFwdOnDuelStarted); Call_PushCell(client1); Call_PushCell(client2); Call_Finish(); } void StartSoloDebug(int client) { StopPrepareTimer(); if (!gCvarEnable.BoolValue || !IsValidHuman(client)) return; g_bDuelPending = false; g_bDuelPreparing = false; g_bDuelActive = true; g_iDuelP1 = client; g_iDuelP2 = 0; EquipPlayerForDuel(client); SetEntityHealth(client, 100); SetEntProp(client, Prop_Send, "m_ArmorValue", 100); SetEntProp(client, Prop_Send, "m_bHasHelmet", 1); SetEntityMoveType(client, MOVETYPE_WALK); SetEntProp(client, Prop_Data, "m_takedamage", 0); StopInvulnTimer(); StopZoneTimer(); StopZonePreview(); StopPrepZoneTimer(); g_hInvulnTimer = CreateTimer(gCvarPostUnfreezeInvuln.FloatValue, Timer_DisablePostUnfreezeInvuln, _, TIMER_FLAG_NO_MAPCHANGE); StartZoneTimer(); StartZonePreview(client, gCvarZonePreviewTime.FloatValue); char weaponName[32]; GetDuelWeaponName(weaponName, sizeof(weaponName)); PrintToChat(client, "%s Соло-дебаг начался | %s", DUEL_PREFIX, weaponName); } void FinishDuel(int winner, int loser, bool endRoundByTeamDefeat = false) { int loserTeam = IsValidClient(loser) ? GetClientTeam(loser) : 0; StopBeaconTimer(); StopPrepareTimer(); StopInvulnTimer(); StopZoneTimer(); StopZonePreview(); StopDuelLimitTimer(); if (!IsValidClient(winner)) { ResetDuelState(); return; } SetDuelProtection(g_iDuelP1, false); SetDuelProtection(g_iDuelP2, false); int moneyReward = gCvarWinMoney.IntValue; int duelWinXP = gCvarDuelWinXP.IntValue; if (IsValidHuman(winner)) { GivePlayerMoney(winner, moneyReward); if (duelWinXP > 0) { LR_GiveXP_Safe(winner, duelWinXP); g_iParticipantXP[winner] += duelWinXP; } } PrintToChatAll("\x02[DUELS]\x01 Дуэль завершена. Победитель: \x03%N\x01 | Награда: \x05%d$\x01 | Базовый XP: \x05%d", winner, moneyReward, duelWinXP); Call_StartForward(g_hFwdOnDuelFinished); Call_PushCell(winner); Call_PushCell(loser); Call_PushCell(g_iParticipantXP[winner]); Call_PushCell(g_iParticipantXP[loser]); Call_Finish(); RestoreLoadout(g_iDuelP1); RestoreLoadout(g_iDuelP2); ResetDuelState(); if (endRoundByTeamDefeat) EndRoundWithTeamDefeat(loserTeam); } void FinishDuelDraw(int client1, int client2) { StopBeaconTimer(); StopPrepareTimer(); StopInvulnTimer(); StopZoneTimer(); StopZonePreview(); StopDuelLimitTimer(); SetDuelProtection(g_iDuelP1, false); SetDuelProtection(g_iDuelP2, false); PrintToChatAll("%s Дуэль завершилась вничью. Ставки должны быть возвращены модулем ставок.", DUEL_PREFIX); Call_StartForward(g_hFwdOnDuelDraw); Call_PushCell(client1); Call_PushCell(client2); Call_Finish(); RestoreLoadout(g_iDuelP1); RestoreLoadout(g_iDuelP2); ResetDuelState(); } void EndRoundWithTeamDefeat(int loserTeam) { CSRoundEndReason reason; switch (loserTeam) { case CS_TEAM_T: { reason = CSRoundEnd_CTWin; } case CS_TEAM_CT: { reason = CSRoundEnd_TerroristWin; } default: { return; } } CS_TerminateRound(0.1, reason, false); } void CancelPendingDuel(const char[] reason) { if (!g_bDuelPending) return; if (reason[0] != '\0') PrintToChatAll("\x02[DUELS]\x01 %s", reason); g_bDuelPending = false; if (g_iDuelP1 > 0) g_bAccepted[g_iDuelP1] = false; if (g_iDuelP2 > 0) g_bAccepted[g_iDuelP2] = false; g_iDuelP1 = 0; g_iDuelP2 = 0; g_iDuelController = 0; } void CancelPreparingDuel(const char[] reason) { if (!g_bDuelPreparing) return; StopPrepareTimer(); StopZoneTimer(); StopZonePreview(); StopPrepZoneTimer(); SetDuelProtection(g_iDuelP1, false); SetDuelProtection(g_iDuelP2, false); if (reason[0] != '\0') PrintToChatAll("\x02[DUELS]\x01 %s", reason); RestoreLoadout(g_iDuelP1); RestoreLoadout(g_iDuelP2); ResetDuelState(); } void ResetDuelState() { StopBeaconTimer(); StopPrepareTimer(); StopInvulnTimer(); StopZoneTimer(); StopZonePreview(); StopPrepZoneTimer(); StopDuelLimitTimer(); if (g_iDuelP1 > 0) { g_iParticipantXP[g_iDuelP1] = 0; g_bAccepted[g_iDuelP1] = false; SetDuelProtection(g_iDuelP1, false); DisableDuelWeaponBypass(g_iDuelP1); } if (g_iDuelP2 > 0) { g_iParticipantXP[g_iDuelP2] = 0; g_bAccepted[g_iDuelP2] = false; SetDuelProtection(g_iDuelP2, false); DisableDuelWeaponBypass(g_iDuelP2); } g_bDuelActive = false; g_bDuelPending = false; g_bDuelPreparing = false; g_iDuelP1 = 0; g_iDuelP2 = 0; g_iDuelController = 0; g_iPrepareLeft = 0; g_bDebugSoloMode = false; strcopy(g_sSelectedWeapon, sizeof(g_sSelectedWeapon), "weapon_ak47"); g_iDuelModifier = DuelModifier_None; } void StartBeaconTimer() { StopBeaconTimer(); if (!gCvarBeacon.BoolValue) return; g_hBeaconTimer = CreateTimer(1.0, Timer_Beacon, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } void StopBeaconTimer() { if (g_hBeaconTimer != null) { KillTimer(g_hBeaconTimer); g_hBeaconTimer = null; } } void StopPrepareTimer() { if (g_hPrepareTimer != null) { KillTimer(g_hPrepareTimer); g_hPrepareTimer = null; } } void StopInvulnTimer() { if (g_hInvulnTimer != null) { KillTimer(g_hInvulnTimer); g_hInvulnTimer = null; } } void StartDuelLimitTimer() { StopDuelLimitTimer(); int limit = gCvarDuelTimeLimit.IntValue; if (limit <= 0) return; g_hDuelLimitTimer = CreateTimer(float(limit), Timer_DuelTimeLimit, _, TIMER_FLAG_NO_MAPCHANGE); } void StopDuelLimitTimer() { if (g_hDuelLimitTimer != null) { KillTimer(g_hDuelLimitTimer); g_hDuelLimitTimer = null; } } public Action Timer_DuelTimeLimit(Handle timer, any data) { g_hDuelLimitTimer = null; if (!g_bDuelActive) return Plugin_Stop; PrintToChatAll("\x02[DUELS]\x01 Время дуэли истекло. Ничья!"); FinishDuelDraw(g_iDuelP1, g_iDuelP2); return Plugin_Stop; } // ─── Таймер рендера зоны во время подготовки ─────────────────────────────── void StartPrepZoneTimer() { StopPrepZoneTimer(); if (!g_bArenaZoneReady || g_iBeamSprite == -1) return; g_hPrepZoneTimer = CreateTimer(gCvarZoneRenderInterval.FloatValue, Timer_PrepZoneRender, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } void StopPrepZoneTimer() { if (g_hPrepZoneTimer != null) { KillTimer(g_hPrepZoneTimer); g_hPrepZoneTimer = null; } } public Action Timer_PrepZoneRender(Handle timer, any data) { if (!g_bDuelPreparing || !g_bArenaZoneReady) return Plugin_Stop; if (IsValidHuman(g_iDuelP1)) RenderArenaZone(g_iDuelP1); if (!g_bDebugSoloMode && IsValidHuman(g_iDuelP2)) RenderArenaZone(g_iDuelP2); return Plugin_Continue; } // ─── Физическая стена зоны ────────────────────────────────────────────────── public void OnPlayerPostThink(int client) { if (!g_bDuelActive || !g_bArenaZoneReady) return; if (!IsValidHuman(client) || !IsPlayerAlive(client)) return; if (!IsDuelParticipant(client)) return; float pos[3]; GetClientAbsOrigin(client, pos); // Если далеко внутри зоны — не тратим ресурсы if (pos[0] > g_vecArenaZoneMin[0] + 10.0 && pos[0] < g_vecArenaZoneMax[0] - 10.0 && pos[1] > g_vecArenaZoneMin[1] + 10.0 && pos[1] < g_vecArenaZoneMax[1] - 10.0 && pos[2] < g_vecArenaZoneMax[2] - 10.0) return; float vel[3]; GetEntPropVector(client, Prop_Data, "m_vecVelocity", vel); bool clamped = false; float margin = 6.0; // X if (pos[0] < g_vecArenaZoneMin[0] + margin) { pos[0] = g_vecArenaZoneMin[0] + margin; if (vel[0] < 0.0) vel[0] = 0.0; clamped = true; } else if (pos[0] > g_vecArenaZoneMax[0] - margin) { pos[0] = g_vecArenaZoneMax[0] - margin; if (vel[0] > 0.0) vel[0] = 0.0; clamped = true; } // Y if (pos[1] < g_vecArenaZoneMin[1] + margin) { pos[1] = g_vecArenaZoneMin[1] + margin; if (vel[1] < 0.0) vel[1] = 0.0; clamped = true; } else if (pos[1] > g_vecArenaZoneMax[1] - margin) { pos[1] = g_vecArenaZoneMax[1] - margin; if (vel[1] > 0.0) vel[1] = 0.0; clamped = true; } // Z потолок (пол — геометрия карты) if (pos[2] > g_vecArenaZoneMax[2] - margin) { pos[2] = g_vecArenaZoneMax[2] - margin; if (vel[2] > 0.0) vel[2] = 0.0; clamped = true; } if (clamped) TeleportEntity(client, pos, NULL_VECTOR, vel); } public Action Timer_DisablePostUnfreezeInvuln(Handle timer, any data) { g_hInvulnTimer = null; if (!g_bDuelActive) return Plugin_Stop; if (IsValidHuman(g_iDuelP1)) SetEntProp(g_iDuelP1, Prop_Data, "m_takedamage", 2); if (!g_bDebugSoloMode && IsValidHuman(g_iDuelP2)) SetEntProp(g_iDuelP2, Prop_Data, "m_takedamage", 2); if (g_bDebugSoloMode) PrintToChat(g_iDuelP1, "%s Неуязвимость снята. Соло-дебаг активен.", DUEL_PREFIX); else PrintToChatAll("[DUELS] Неуязвимость снята. Бой начался."); return Plugin_Stop; } public Action Timer_Beacon(Handle timer, any data) { if (!g_bDuelActive || !IsValidHuman(g_iDuelP1) || (g_iDuelP2 != 0 && !IsValidHuman(g_iDuelP2))) return Plugin_Stop; if (g_bDebugSoloMode || g_iDuelP2 == 0) return Plugin_Continue; EmitBeacon(g_iDuelP1, g_iDuelP2); EmitBeacon(g_iDuelP2, g_iDuelP1); return Plugin_Continue; } void EmitBeacon(int target, int listener) { float origin[3]; GetClientAbsOrigin(target, origin); origin[2] += 10.0; if (g_iBeamSprite != -1 && g_iHaloSprite != -1) { int color[4] = {255, 0, 0, 255}; TE_SetupBeamRingPoint(origin, 10.0, 220.0, g_iBeamSprite, g_iHaloSprite, 0, 15, 0.5, 5.0, 0.0, color, 0, 0); TE_SendToAll(); } if (IsValidHuman(listener)) EmitSoundToClient(listener, BEACON_SOUND, target, SNDCHAN_AUTO, SNDLEVEL_RAIDSIREN); } void SetDuelProtection(int client, bool enable) { if (!IsValidHuman(client)) return; SetEntityMoveType(client, enable ? MOVETYPE_NONE : MOVETYPE_WALK); SetEntProp(client, Prop_Data, "m_takedamage", enable ? 0 : 2); } void EnableDuelWeaponBypass(int client) { if (!IsValidHuman(client)) return; if (!g_bSavedUserFlags[client]) { g_iSavedUserFlags[client] = GetUserFlagBits(client); g_bSavedUserFlags[client] = true; } if (gCvarWeaponBypassFlags != null && gCvarWeaponBypassFlags.BoolValue) SetUserFlagBits(client, g_iSavedUserFlags[client] | ADMFLAG_ROOT); } void DisableDuelWeaponBypass(int client) { if (client <= 0 || client > MaxClients) return; if (!g_bSavedUserFlags[client]) return; if (IsClientInGame(client)) SetUserFlagBits(client, g_iSavedUserFlags[client]); g_iSavedUserFlags[client] = 0; g_bSavedUserFlags[client] = false; } void ForceEquipDuelWeapon(int client) { if (!IsValidHuman(client) || StrEqual(g_sSelectedWeapon, "weapon_knife", false)) return; int weapon = -1; for (int slot = 0; slot < 6; slot++) { int ent = GetPlayerWeaponSlot(client, slot); if (ent <= MaxClients || !IsValidEdict(ent)) continue; char current[64]; GetEdictClassname(ent, current, sizeof(current)); if (StrEqual(current, g_sSelectedWeapon, false)) { weapon = ent; break; } } if (weapon > MaxClients && IsValidEdict(weapon)) EquipPlayerWeapon(client, weapon); } bool PlayerHasWeaponClass(int client, const char[] classname) { if (!IsValidHuman(client)) return false; for (int slot = 0; slot < 6; slot++) { int weapon = GetPlayerWeaponSlot(client, slot); if (weapon <= MaxClients || !IsValidEdict(weapon)) continue; char current[64]; GetEdictClassname(weapon, current, sizeof(current)); if (StrEqual(current, classname, false)) return true; } return false; } void EnsureDuelWeaponPresent(int client) { if (!g_bDuelActive || !IsValidHuman(client) || !IsDuelParticipant(client)) return; EnableDuelWeaponBypass(client); if (StrEqual(g_sSelectedWeapon, "weapon_knife", false)) return; if (!PlayerHasWeaponClass(client, g_sSelectedWeapon)) { int weapon = GivePlayerItem(client, g_sSelectedWeapon); if (weapon > MaxClients) RefillWeaponAmmo(weapon); } ForceEquipDuelWeapon(client); RefillPlayerAmmo(client); } void EquipPlayerForDuel(int client) { if (!IsValidHuman(client)) return; EnableDuelWeaponBypass(client); RemoveAllWeapons(client); GivePlayerItem(client, "weapon_knife"); if (!StrEqual(g_sSelectedWeapon, "weapon_knife")) { int weapon = GivePlayerItem(client, g_sSelectedWeapon); if (weapon > MaxClients) RefillWeaponAmmo(weapon); } ForceEquipDuelWeapon(client); RefillPlayerAmmo(client); } stock bool IsWeaponAllowed(const char[] weapon) { if (StrEqual(weapon, "weapon_knife")) return gCvarAllowKnife.BoolValue; if (StrEqual(weapon, "weapon_deagle")) return gCvarAllowDeagle.BoolValue; if (StrEqual(weapon, "weapon_ak47")) return gCvarAllowAK47.BoolValue; if (StrEqual(weapon, "weapon_m4a1")) return gCvarAllowM4A4.BoolValue; if (StrEqual(weapon, "weapon_m4a1_silencer")) return gCvarAllowM4A1S.BoolValue; if (StrEqual(weapon, "weapon_awp")) return gCvarAllowAWP.BoolValue; if (StrEqual(weapon, "weapon_ssg08")) return gCvarAllowScout.BoolValue; return false; } void GetDuelWeaponName(char[] buffer, int maxlen) { if (StrEqual(g_sSelectedWeapon, "weapon_knife")) strcopy(buffer, maxlen, "Ножи"); else if (StrEqual(g_sSelectedWeapon, "weapon_deagle")) strcopy(buffer, maxlen, "Deagle"); else if (StrEqual(g_sSelectedWeapon, "weapon_ak47")) strcopy(buffer, maxlen, "AK-47"); else if (StrEqual(g_sSelectedWeapon, "weapon_m4a1")) strcopy(buffer, maxlen, "M4A4/M4A1-S"); else if (StrEqual(g_sSelectedWeapon, "weapon_m4a1_silencer")) strcopy(buffer, maxlen, "M4A1-S"); else if (StrEqual(g_sSelectedWeapon, "weapon_awp")) strcopy(buffer, maxlen, "AWP"); else if (StrEqual(g_sSelectedWeapon, "weapon_ssg08")) strcopy(buffer, maxlen, "SSG08 / Scout"); else strcopy(buffer, maxlen, "Оружие"); } public Action OnTraceAttack(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &ammotype, int hitbox, int hitgroup) { if (!g_bDuelActive || g_iDuelModifier != DuelModifier_HeadshotOnly) return Plugin_Continue; if (!IsValidHuman(victim) || !IsValidHuman(attacker)) return Plugin_Continue; if ((victim != g_iDuelP1 && victim != g_iDuelP2) || (attacker != g_iDuelP1 && attacker != g_iDuelP2)) return Plugin_Continue; if (hitgroup != 1) { damage = 0.0; return Plugin_Handled; } return Plugin_Continue; } bool IsGrenadeWeapon(const char[] classname) { return StrEqual(classname, "weapon_hegrenade", false) || StrEqual(classname, "weapon_flashbang", false) || StrEqual(classname, "weapon_smokegrenade", false) || StrEqual(classname, "weapon_molotov", false) || StrEqual(classname, "weapon_incgrenade", false) || StrEqual(classname, "weapon_decoy", false); } public Action OnWeaponCanUse(int client, int weapon) { if (!g_bDuelActive) return Plugin_Continue; if (!IsValidHuman(client) || !IsDuelParticipant(client)) return Plugin_Continue; if (weapon <= MaxClients || !IsValidEdict(weapon)) return Plugin_Handled; char classname[64]; GetEdictClassname(weapon, classname, sizeof(classname)); if (StrEqual(classname, "weapon_knife", false)) return Plugin_Continue; if (IsGrenadeWeapon(classname)) return Plugin_Handled; if (StrEqual(g_sSelectedWeapon, "weapon_knife", false)) return Plugin_Handled; if (StrEqual(classname, g_sSelectedWeapon, false)) return Plugin_Continue; return Plugin_Handled; } public Action CS_OnCSWeaponDrop(int client, int weapon) { return OnWeaponCanDrop(client, weapon); } public Action OnWeaponCanDrop(int client, int weapon) { if (!g_bDuelActive) return Plugin_Continue; if (!IsValidHuman(client) || !IsDuelParticipant(client)) return Plugin_Continue; if (weapon <= MaxClients || !IsValidEdict(weapon)) return Plugin_Handled; char classname[64]; GetEdictClassname(weapon, classname, sizeof(classname)); if (StrEqual(classname, g_sSelectedWeapon, false) || StrEqual(classname, "weapon_knife", false)) return Plugin_Handled; return Plugin_Continue; } void SaveLoadout(int client) { if (!IsValidHuman(client)) return; SaveWeaponClass(client, 0, 0); SaveWeaponClass(client, 1, 1); SaveWeaponClass(client, 2, 2); g_iSavedHealth[client] = GetClientHealth(client); g_iSavedArmor[client] = GetEntProp(client, Prop_Send, "m_ArmorValue"); g_iSavedMoney[client] = GetEntProp(client, Prop_Send, "m_iAccount"); } void SaveWeaponClass(int client, int slot, int saveIndex) { int weapon = GetPlayerWeaponSlot(client, slot); if (weapon > MaxClients && IsValidEdict(weapon)) GetEdictClassname(weapon, g_sSavedWeapon[client][saveIndex], 32); else g_sSavedWeapon[client][saveIndex][0] = '\0'; } void RestoreLoadout(int client) { if (!IsValidHuman(client)) return; DisableDuelWeaponBypass(client); RemoveAllWeapons(client); for (int i = 0; i < 3; i++) { if (g_sSavedWeapon[client][i][0] == '\0') continue; int weapon = GivePlayerItem(client, g_sSavedWeapon[client][i]); if (weapon > MaxClients) RefillWeaponAmmo(weapon); } SetEntityHealth(client, g_iSavedHealth[client] > 0 ? g_iSavedHealth[client] : 100); SetEntProp(client, Prop_Send, "m_ArmorValue", g_iSavedArmor[client]); SetEntProp(client, Prop_Send, "m_iAccount", g_iSavedMoney[client]); } void RemoveAllWeapons(int client) { for (int slot = 0; slot < 5; slot++) { int weapon = GetPlayerWeaponSlot(client, slot); while (weapon > MaxClients && IsValidEdict(weapon)) { RemovePlayerItem(client, weapon); AcceptEntityInput(weapon, "Kill"); weapon = GetPlayerWeaponSlot(client, slot); } } } void RefillPlayerAmmo(int client) { if (!IsValidHuman(client)) return; for (int slot = 0; slot < 5; slot++) { int weapon = GetPlayerWeaponSlot(client, slot); if (weapon > MaxClients && IsValidEdict(weapon)) RefillWeaponAmmo(weapon); } } void RefillWeaponAmmo(int weapon) { if (!IsValidEdict(weapon)) return; char classname[32]; GetEdictClassname(weapon, classname, sizeof(classname)); int clip = 30; int reserve = 90; if (StrEqual(classname, "weapon_deagle")) { clip = 7; reserve = 35; } else if (StrEqual(classname, "weapon_awp")) { clip = 10; reserve = 30; } else if (StrEqual(classname, "weapon_ssg08")) { clip = 10; reserve = 90; } else if (StrEqual(classname, "weapon_m4a1_silencer")) { clip = 20; reserve = 60; } else if (StrEqual(classname, "weapon_m4a1") || StrEqual(classname, "weapon_ak47")) { clip = 30; reserve = 90; } if (HasEntProp(weapon, Prop_Send, "m_iClip1")) SetEntProp(weapon, Prop_Send, "m_iClip1", clip); if (HasEntProp(weapon, Prop_Send, "m_iPrimaryReserveAmmoCount")) SetEntProp(weapon, Prop_Send, "m_iPrimaryReserveAmmoCount", reserve); if (HasEntProp(weapon, Prop_Send, "m_iSecondaryReserveAmmoCount")) SetEntProp(weapon, Prop_Send, "m_iSecondaryReserveAmmoCount", 0); } void GivePlayerMoney(int client, int amount) { if (!IsValidHuman(client)) return; int money = GetEntProp(client, Prop_Send, "m_iAccount") + amount; if (money > 16000) money = 16000; if (money < 0) money = 0; SetEntProp(client, Prop_Send, "m_iAccount", money); } void GetAliveOneVsOne(int &tAlive, int &ctAlive, int &tClient, int &ctClient) { tAlive = 0; ctAlive = 0; tClient = 0; ctClient = 0; for (int i = 1; i <= MaxClients; i++) { if (!IsValidHuman(i) || !IsPlayerAlive(i)) continue; int team = GetClientTeam(i); if (team == 2) { tAlive++; tClient = i; } else if (team == 3) { ctAlive++; ctClient = i; } } } bool SaveArenaForCurrentMap() { char map[64]; GetCurrentMap(map, sizeof(map)); KeyValues kv = new KeyValues("ArcaneGameDUELS_Arenas"); kv.ImportFromFile(ARENA_CFG); if (!kv.JumpToKey(map, true)) { delete kv; return false; } kv.SetVector("spawn1_origin", g_vecArenaSpawn1); kv.SetVector("spawn1_angles", g_angArenaSpawn1); kv.SetVector("spawn2_origin", g_vecArenaSpawn2); kv.SetVector("spawn2_angles", g_angArenaSpawn2); kv.SetVector("zone_min", g_vecArenaZoneMin); kv.SetVector("zone_max", g_vecArenaZoneMax); kv.SetVector("zone_point1", g_vecManualZonePoint1); kv.SetVector("zone_point2", g_vecManualZonePoint2); kv.Rewind(); bool ok = kv.ExportToFile(ARENA_CFG); delete kv; return ok; } void LoadArenaForCurrentMap() { g_bArenaReady = false; ZeroVector(g_vecArenaSpawn1); ZeroVector(g_angArenaSpawn1); ZeroVector(g_vecArenaSpawn2); ZeroVector(g_angArenaSpawn2); ZeroVector(g_vecArenaZoneMin); ZeroVector(g_vecArenaZoneMax); ZeroVector(g_vecManualZonePoint1); ZeroVector(g_vecManualZonePoint2); g_bManualZonePoint1Set = false; g_bManualZonePoint2Set = false; g_bArenaZoneReady = false; char map[64]; GetCurrentMap(map, sizeof(map)); KeyValues kv = new KeyValues("ArcaneGameDUELS_Arenas"); if (!kv.ImportFromFile(ARENA_CFG)) { delete kv; return; } if (!kv.JumpToKey(map, false)) { delete kv; return; } kv.GetVector("spawn1_origin", g_vecArenaSpawn1); kv.GetVector("spawn1_angles", g_angArenaSpawn1); kv.GetVector("spawn2_origin", g_vecArenaSpawn2); kv.GetVector("spawn2_angles", g_angArenaSpawn2); kv.GetVector("zone_min", g_vecArenaZoneMin); kv.GetVector("zone_max", g_vecArenaZoneMax); kv.GetVector("zone_point1", g_vecManualZonePoint1); kv.GetVector("zone_point2", g_vecManualZonePoint2); g_bArenaReady = HasBothArenaSpawns(); g_bArenaZoneReady = !IsVectorZero(g_vecArenaZoneMin) || !IsVectorZero(g_vecArenaZoneMax); g_bManualZonePoint1Set = !IsVectorZero(g_vecManualZonePoint1); g_bManualZonePoint2Set = !IsVectorZero(g_vecManualZonePoint2); delete kv; } stock void AutoGenerateArenaZone() { if (!HasBothArenaSpawns()) { g_bArenaZoneReady = false; ZeroVector(g_vecArenaZoneMin); ZeroVector(g_vecArenaZoneMax); return; } float padXY = gCvarZonePaddingXY.FloatValue; float padZDown = gCvarZonePaddingZDown.FloatValue; float padZUp = gCvarZonePaddingZUp.FloatValue; g_vecArenaZoneMin[0] = GetMinFloat(g_vecArenaSpawn1[0], g_vecArenaSpawn2[0]) - padXY; g_vecArenaZoneMin[1] = GetMinFloat(g_vecArenaSpawn1[1], g_vecArenaSpawn2[1]) - padXY; g_vecArenaZoneMin[2] = GetMinFloat(g_vecArenaSpawn1[2], g_vecArenaSpawn2[2]) - padZDown; g_vecArenaZoneMax[0] = GetMaxFloat(g_vecArenaSpawn1[0], g_vecArenaSpawn2[0]) + padXY; g_vecArenaZoneMax[1] = GetMaxFloat(g_vecArenaSpawn1[1], g_vecArenaSpawn2[1]) + padXY; g_vecArenaZoneMax[2] = GetMaxFloat(g_vecArenaSpawn1[2], g_vecArenaSpawn2[2]) + padZUp; g_bArenaZoneReady = true; } void BuildArenaZoneFromManualPoints() { if (!g_bManualZonePoint1Set || !g_bManualZonePoint2Set) { g_bArenaZoneReady = false; ZeroVector(g_vecArenaZoneMin); ZeroVector(g_vecArenaZoneMax); return; } g_vecArenaZoneMin[0] = GetMinFloat(g_vecManualZonePoint1[0], g_vecManualZonePoint2[0]); g_vecArenaZoneMin[1] = GetMinFloat(g_vecManualZonePoint1[1], g_vecManualZonePoint2[1]); g_vecArenaZoneMin[2] = GetMinFloat(g_vecManualZonePoint1[2], g_vecManualZonePoint2[2]); g_vecArenaZoneMax[0] = GetMaxFloat(g_vecManualZonePoint1[0], g_vecManualZonePoint2[0]); g_vecArenaZoneMax[1] = GetMaxFloat(g_vecManualZonePoint1[1], g_vecManualZonePoint2[1]); g_vecArenaZoneMax[2] = g_vecArenaZoneMin[2] + 200.0; g_bArenaZoneReady = true; } void StartZonePreview(int viewer, float duration) { if (!g_bArenaZoneReady || g_iBeamSprite == -1) return; StopZonePreview(); RenderArenaZone(viewer); g_hZonePreviewTimer = CreateTimer(gCvarZoneRenderInterval.FloatValue, Timer_ZonePreview, GetClientUserId(viewer), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); CreateTimer(duration, Timer_StopZonePreview, _, TIMER_FLAG_NO_MAPCHANGE); } void StopZonePreview() { if (g_hZonePreviewTimer != null) { KillTimer(g_hZonePreviewTimer); g_hZonePreviewTimer = null; } } void StartZoneTimer() { StopZoneTimer(); if (!gCvarZoneEnable.BoolValue || !g_bArenaZoneReady) return; for (int i = 1; i <= MaxClients; i++) { g_bZoneOutside[i] = false; g_iZoneGraceLeft[i] = 0; } g_hZoneTimer = CreateTimer(gCvarZoneRenderInterval.FloatValue, Timer_CheckDuelZone, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } void StopZoneTimer() { if (g_hZoneTimer != null) { KillTimer(g_hZoneTimer); g_hZoneTimer = null; } for (int i = 1; i <= MaxClients; i++) { g_bZoneOutside[i] = false; g_iZoneGraceLeft[i] = 0; } } public Action Timer_ZonePreview(Handle timer, any userid) { int client = GetClientOfUserId(userid); if (!IsValidHuman(client) || !g_bArenaZoneReady) return Plugin_Stop; RenderArenaZone(client); return Plugin_Continue; } public Action Timer_StopZonePreview(Handle timer, any data) { StopZonePreview(); return Plugin_Stop; } public Action Timer_CheckDuelZone(Handle timer, any data) { if (!g_bDuelActive || !g_bArenaZoneReady) return Plugin_Stop; if (IsValidHuman(g_iDuelP1)) { EnsureDuelWeaponPresent(g_iDuelP1); RenderArenaZone(g_iDuelP1); CheckClientZoneState(g_iDuelP1, g_iDuelP2); } if (!g_bDebugSoloMode && IsValidHuman(g_iDuelP2)) { EnsureDuelWeaponPresent(g_iDuelP2); RenderArenaZone(g_iDuelP2); CheckClientZoneState(g_iDuelP2, g_iDuelP1); } return Plugin_Continue; } void CheckClientZoneState(int client, int opponent) { if (!IsValidHuman(client) || !IsPlayerAlive(client)) return; if (IsClientInsideArenaZone(client)) { if (g_bZoneOutside[client]) { g_bZoneOutside[client] = false; g_iZoneGraceLeft[client] = 0; PrintCenterText(client, "Ты вернулся в зону дуэли"); } return; } if (!g_bZoneOutside[client]) { g_bZoneOutside[client] = true; g_iZoneGraceLeft[client] = gCvarZoneGrace.IntValue; } else if (g_iZoneGraceLeft[client] > 0) { g_iZoneGraceLeft[client]--; } EmitSoundToClient(client, DUEL_OUT_SOUND, client, SNDCHAN_AUTO, SNDLEVEL_RAIDSIREN); if (g_iZoneGraceLeft[client] > 0) { PrintCenterText(client, "Вернись в зону дуэли: %d", g_iZoneGraceLeft[client]); return; } if (g_bDebugSoloMode || opponent == 0 || !IsValidHuman(opponent)) { PrintToChat(client, "%s Ты не вернулся в зону вовремя. Соло-дебаг остановлен.", DUEL_PREFIX); RestoreLoadout(client); ResetDuelState(); return; } ForcePlayerSuicide(client); PrintToChatAll("%s %N покинул зону дуэли и проиграл.", DUEL_PREFIX, client); FinishDuel(opponent, client); } bool IsClientInsideArenaZone(int client) { float pos[3]; GetClientAbsOrigin(client, pos); return (pos[0] >= g_vecArenaZoneMin[0] && pos[0] <= g_vecArenaZoneMax[0] && pos[1] >= g_vecArenaZoneMin[1] && pos[1] <= g_vecArenaZoneMax[1] && pos[2] >= g_vecArenaZoneMin[2] && pos[2] <= g_vecArenaZoneMax[2]); } void RenderArenaZone(int client) { if (!IsValidHuman(client) || !g_bArenaZoneReady || g_iBeamSprite == -1) return; float p1[3], p2[3], p3[3], p4[3], p5[3], p6[3], p7[3], p8[3]; p1[0] = g_vecArenaZoneMin[0]; p1[1] = g_vecArenaZoneMin[1]; p1[2] = g_vecArenaZoneMin[2]; p2[0] = g_vecArenaZoneMax[0]; p2[1] = g_vecArenaZoneMin[1]; p2[2] = g_vecArenaZoneMin[2]; p3[0] = g_vecArenaZoneMax[0]; p3[1] = g_vecArenaZoneMax[1]; p3[2] = g_vecArenaZoneMin[2]; p4[0] = g_vecArenaZoneMin[0]; p4[1] = g_vecArenaZoneMax[1]; p4[2] = g_vecArenaZoneMin[2]; p5[0] = g_vecArenaZoneMin[0]; p5[1] = g_vecArenaZoneMin[1]; p5[2] = g_vecArenaZoneMax[2]; p6[0] = g_vecArenaZoneMax[0]; p6[1] = g_vecArenaZoneMin[1]; p6[2] = g_vecArenaZoneMax[2]; p7[0] = g_vecArenaZoneMax[0]; p7[1] = g_vecArenaZoneMax[1]; p7[2] = g_vecArenaZoneMax[2]; p8[0] = g_vecArenaZoneMin[0]; p8[1] = g_vecArenaZoneMax[1]; p8[2] = g_vecArenaZoneMax[2]; int color[4] = {160, 60, 255, 255}; // 12 рёбер каркаса DrawZoneEdge(client, p1, p2, color); DrawZoneEdge(client, p2, p3, color); DrawZoneEdge(client, p3, p4, color); DrawZoneEdge(client, p4, p1, color); DrawZoneEdge(client, p5, p6, color); DrawZoneEdge(client, p6, p7, color); DrawZoneEdge(client, p7, p8, color); DrawZoneEdge(client, p8, p5, color); DrawZoneEdge(client, p1, p5, color); DrawZoneEdge(client, p2, p6, color); DrawZoneEdge(client, p3, p7, color); DrawZoneEdge(client, p4, p8, color); // Полная сетка на 4 боковых гранях float step = 40.0; float xMin = g_vecArenaZoneMin[0], xMax = g_vecArenaZoneMax[0]; float yMin = g_vecArenaZoneMin[1], yMax = g_vecArenaZoneMax[1]; float zMin = g_vecArenaZoneMin[2], zMax = g_vecArenaZoneMax[2]; float a[3], b[3]; // Грань: y = yMin (горизонтальные + вертикальные) for (float z = zMin + step; z < zMax; z += step) { a[0] = xMin; a[1] = yMin; a[2] = z; b[0] = xMax; b[1] = yMin; b[2] = z; DrawZoneEdge(client, a, b, color); } for (float x = xMin + step; x < xMax; x += step) { a[0] = x; a[1] = yMin; a[2] = zMin; b[0] = x; b[1] = yMin; b[2] = zMax; DrawZoneEdge(client, a, b, color); } // Грань: y = yMax for (float z = zMin + step; z < zMax; z += step) { a[0] = xMin; a[1] = yMax; a[2] = z; b[0] = xMax; b[1] = yMax; b[2] = z; DrawZoneEdge(client, a, b, color); } for (float x = xMin + step; x < xMax; x += step) { a[0] = x; a[1] = yMax; a[2] = zMin; b[0] = x; b[1] = yMax; b[2] = zMax; DrawZoneEdge(client, a, b, color); } // Грань: x = xMin for (float z = zMin + step; z < zMax; z += step) { a[0] = xMin; a[1] = yMin; a[2] = z; b[0] = xMin; b[1] = yMax; b[2] = z; DrawZoneEdge(client, a, b, color); } for (float y = yMin + step; y < yMax; y += step) { a[0] = xMin; a[1] = y; a[2] = zMin; b[0] = xMin; b[1] = y; b[2] = zMax; DrawZoneEdge(client, a, b, color); } // Грань: x = xMax for (float z = zMin + step; z < zMax; z += step) { a[0] = xMax; a[1] = yMin; a[2] = z; b[0] = xMax; b[1] = yMax; b[2] = z; DrawZoneEdge(client, a, b, color); } for (float y = yMin + step; y < yMax; y += step) { a[0] = xMax; a[1] = y; a[2] = zMin; b[0] = xMax; b[1] = y; b[2] = zMax; DrawZoneEdge(client, a, b, color); } } void DrawZoneEdge(int client, const float start[3], const float end[3], const int color[4]) { TE_SetupBeamPoints(start, end, g_iBeamSprite, g_iHaloSprite, 0, 0, gCvarZoneRenderInterval.FloatValue + 0.05, 2.0, 2.0, 0, 0.0, color, 0); TE_SendToClient(client); } bool HasBothArenaSpawns() { return !IsVectorZero(g_vecArenaSpawn1) && !IsVectorZero(g_vecArenaSpawn2); } float GetMinFloat(float a, float b) { return (a < b) ? a : b; } float GetMaxFloat(float a, float b) { return (a > b) ? a : b; } bool IsVectorZero(const float vec[3]) { return (vec[0] == 0.0 && vec[1] == 0.0 && vec[2] == 0.0); } void ZeroVector(float vec[3]) { vec[0] = 0.0; vec[1] = 0.0; vec[2] = 0.0; } stock int GetHumanPlayersCount() { int count = 0; for (int i = 1; i <= MaxClients; i++) { if (IsValidHuman(i)) count++; } return count; } int GetPlayingHumanPlayersCount() { int count = 0; for (int i = 1; i <= MaxClients; i++) { if (!IsValidHuman(i)) continue; int team = GetClientTeam(i); if (team == 2 || team == 3) count++; } return count; } stock int GetRandomOtherPlayer(int client) { for (int i = 1; i <= MaxClients; i++) { if (i == client) continue; if (IsValidHuman(i)) return i; } return 0; } bool IsValidClient(int client) { return (client > 0 && client <= MaxClients); } bool IsDuelParticipant(int client) { return (client == g_iDuelP1 || client == g_iDuelP2); } bool IsValidHuman(int client) { return IsValidClient(client) && IsClientInGame(client) && !IsFakeClient(client); } stock bool IsLRReady() { return LR_IsLoaded(); } stock void LR_GiveXP_Safe(int client, int amount) { if (amount <= 0 || !IsLRReady() || !IsValidHuman(client) || !LR_GetClientStatus(client)) return; LR_ChangeClientValue(client, amount); } public any Native_AGD_IsDuelActive(Handle plugin, int numParams) { return g_bDuelActive; } public any Native_AGD_GetDuelPlayers(Handle plugin, int numParams) { SetNativeCellRef(1, g_iDuelP1); SetNativeCellRef(2, g_iDuelP2); return (g_bDuelActive || g_bDuelPending || g_bDuelPreparing); } public any Native_AGD_GetDuelParticipantXP(Handle plugin, int numParams) { int client = GetNativeCell(1); if (!IsValidClient(client)) return 0; return g_iParticipantXP[client]; } public any Native_AGD_GetWinnerMoneyReward(Handle plugin, int numParams) { return gCvarWinMoney.IntValue; } public any Native_AGD_IsClientInDuel(Handle plugin, int numParams) { int client = GetNativeCell(1); if (client < 1 || client > MaxClients) return false; return g_bDuelActive && (client == g_iDuelP1 || client == g_iDuelP2); } public any Native_AGD_IsArenaReady(Handle plugin, int numParams) { return g_bArenaReady; } public any Native_AGD_IsBettingOpen(Handle plugin, int numParams) { return g_bDuelPreparing; }