From 17a5363f0fb2e7cfad096dd2a00b83c5001997dc Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 17:13:02 +0300 Subject: [PATCH 01/46] Add vip_modules.phrases.txt from 3.1-dev; the core already loads it but the file was missing. Fix duplicate Pets_* phrases and mislabeled language keys in BHOP_TIME, Defuser and EquipmentsMenu. --- .../translations/vip_modules.phrases.txt | 2018 +++++++++++++++++ 1 file changed, 2018 insertions(+) create mode 100644 addons/sourcemod/translations/vip_modules.phrases.txt diff --git a/addons/sourcemod/translations/vip_modules.phrases.txt b/addons/sourcemod/translations/vip_modules.phrases.txt new file mode 100644 index 0000000..ec062a5 --- /dev/null +++ b/addons/sourcemod/translations/vip_modules.phrases.txt @@ -0,0 +1,2018 @@ +"Phrases" +{ + "additional_menu" + { + "ru" "Дополнительное VIP" + "en" "Additional VIP" + "pt" "VIP Adicional" + "fi" "Lisää VIP" + "ua" "Додаткове VIP" + "es" "VIP Adicional" + "ar" "إضافي VIP" + } + "AutoRespawn" + { + "ru" "Авто возрождение" + "en" "Auto Respawn" + "pt" "Respawn Automático" + "fi" "Automaattinen palautuminen" + "ua" "Авто відродження" + "es" "Reaparición automática" + "ar" "بيضة السيارات" + } + "AimTeleport" + { + "ru" "AIM Телепорт" + "en" "AIM Teleport" + "pt" "AIM Teleport" + "fi" "AIM Teleport" + "ua" "AIM Телепорт" + "es" "AIM Teleport" + "ar" "النقل الفضائي AIM" + } + "AustraliumWeapons" + { + "ru" "Оружие из Австралия" + "en" "Weapons from Australium" + "pt" "Armas de Australium" + "fi" "Aseita australiasta" + "ua" "Зброя з Австралія" + "es" "Armas de Australium" + "ar" "أسلحة من أستراليا" + } + "AutoBuy" + { + "ru" "Автозакупка" + "en" "Auto Buy" + "pt" "Auto Buy" + "fi" "Automaattinen ostos" + "ua" "Автозакупівля" + "es" "Compra automática" + "ar" "شراء تلقائي" + } + "AutoBuyMenu" + { + "ru" "Настройка автозакупки" + "en" "Auto Buy Setting" + "pt" "Configurações do Auto Buy" + "fi" "Automaattisen ostoiden valikko" + "ua" "Налаштування автозакупівлі" + "es" "Configuración de compra automática" + "ar" "إعداد شراء السيارات" + } + "AutoSilencer_usp" + { + "ru" "Авто глушитель на USP" + "en" "Auto Silencer for USP" + "pt" "Silenciador Automático USP" + "fi" "Automaatti äänenvaimennin USP varten" + "ua" "Авто глушник на USP-S" + "es" "Silenciador automático para USP" + "ar" "كاتم الصوت التلقائي لـ USP" + } + "AutoSilencer_m4a1" + { + "ru" "Авто глушитель на M4A1" + "en" "Auto Silencer for M4A1" + "pt" "Silenciador Automático M4A1" + "fi" "Automaatti äänenvaimennin M4A1 varten" + "ua" "Авто глушник на M4A1-S" + "es" "Silenciador automático para M4A1" + "ar" "كاتم الصوت التلقائي لـ M4A1" + } + "AntiFlash" + { + "ru" "Анти-флеш" + "en" "Anti-Flash" + "pt" "Anti-Flash" + "fi" "Anti-Flash" + "ua" "Анти-Флеш" + "es" "Anti-Flash" + "ar" "مضاد للفلاش" + } + "Armor" + { + "ru" "Бронь" + "en" "Armor" + "pt" "Colete" + "fi" "Suojaliivi" + "ua" "Броня" + "es" "Armadura" + "ar" "درع" + } + "AURA" + { + "ru" "Аура" + "en" "Aura" + "pt" "Aura" + "fi" "Säteily" + "ua" "Аура" + "es" "Aura" + "ar" "هالة" + } + "AURA_M" + { + "ru" "Цвет ауры" + "en" "Color Aura" + "pt" "Cor da Aura" + "fi" "Säteilyn väri" + "ua" "Колір аури" + "es" "Aura de color" + "ar" "هالة اللون" + } + "Burning" + { + "ru" "Горение" + "en" "Burning" + "pt" "Burning" + "fi" "Polttava" + "ua" "Горіння" + "es" "Ardiente" + "ar" "احتراق" + } + "Breachcharge" + { + "ru" "Взрывчатка" + "en" "Explosive" + "pt" "Explosivo" + "fi" "Räjähtävä" + "ua" "Вибухівка" + "es" "Explosiva" + "ar" "مادة متفجرة" + } + "BunnyHop" + { + "ru" "БанниХоп" + "en" "BunnyHop" + "fi" "BunnyHop" + "ua" "БанніХоп" + "es" "BunnyHop" + "ar" "باني هوب" + } + "BHOP_TIME" + { + "#format" "{1:f}" + "ru" "Банихоп будет доступен через {1} секунд" + "ua" "Банніхоп буде доступний через {1} секунд" + "en" "BunnyHop will be available in {1} seconds" + "pt" "BunnyHop estará disponível em {1} segundos" + "fr" "BunnyHop sera disponible dans {1} secondes" + "ar" "سوف يكون Bunnyhop متاحًا في {1} ثواني" + } + "BuyZona" + { + "ru" "Зона закупки" + "en" "Buy Zona" + "pt" "Buy Zona" + "fi" "Hankinta-alue" + "ua" "Зона закупівлі" + "es" "Comprar Zona" + "ar" "شراء المنطقة" + } + "BoostLadder" + { + "ru" "Ускорение подъема по лестнице" + "en" "Boost Ladder" + "pt" "Boost Ladder" + "fi" "Nopeutuva porraskiipeily" + "ua" "Прискорення підйому по драбині" + "es" "Escalera de refuerzo" + "ar" "رفع السلم" + } + "Chat" + { + "ru" "Чат" + "en" "Chat" + "pt" "Chat" + "fi" "Chatti" + "ua" "Чат" + "es" "Charla" + "ar" "دردشة" + } + "Chat_Menu" + { + "ru" "Настройка чата" + "en" "Chat Settings" + "pt" "Configurações do Chat" + "fi" "Inställning av chatt" + "ua" "Налаштування чату" + "es" "Configuraciones de chat" + "ar" "إعدادات المحادثة" + } + "CT_C4" + { + "ru" "Бомба за КТ" + "en" "C4 CT" + "pt" "C4 CT" + "fi" "C4 Erikoisjoukot" + "ua" "С4 за Спецназ" + "es" "C4 CT" + "ar" "قنبلة CT" + } + "CausedDamage" + { + "ru" "Наносимый урон" + "en" "Caused Damage" + "pt" "Dano Causado" + "fi" "Aiheuttanut vahinkoa" + "ua" "Завдана шкода" + "es" "Daño causado" + "ar" "تسببت في الضرر" + } + "COIN" + { + "ru" "Монеты" + "en" "Coins" + "pt" "Medalhas" + "fi" "Mitaleiden asetukset" + "ua" "Монети" + "es" "Monedas" + "ar" "عملات معدنية" + } + "CarryC4" + { + "ru" "Переносная бомба" + "en" "Carry C4" + "fi" "Kannettava pommi" + "ua" "Переносна С4" + "es" "Llevar C4" + "ar" "حمل C4" + } + "change_color_props" + { + "ru" "Смена цвета пропов" + "en" "Сhange Сolors Props" + "fi" "Värien muutos" + "ua" "Зміна кольорів пропів" + "es" "Сhange Сolors Props" + "ar" "دعائم تغيير الألوان" + } + "Changeteam" + { + "ru" "Смена команды" + "en" "Change Team" + "pt" "Change Team" + "fi" "Tiimin muutos" + "ua" "Зміна команди" + "es" "Cambiar equipo" + "ar" "فريق التغيير" + } + "ColoredFlash" + { + "ru" "Разноцветное ослепление" + "en" "Colored Flash" + "pt" "Flash Colorida" + "fi" "Värikäs sokaisin" + "ua" "Різнокольоровий осліплення" + "es" "Flash de Color" + "ar" "فلاش ملون" + } + "CustomWeapons" + { + "ru" "Модели оружия" + "en" "Weapon Models" + "pt" "Modelos de Armas" + "fi" "Ase Mallit" + "ua" "Моделі зброї" + "es" "Modelos de Armas" + "ar" "نماذج الأسلحة" + } + "C4_Model" + { + "ru" "Измененная бомба" + "en" "C4 Model" + "pt" "Modelo de C4" + "fi" "Muunnettu Pommi" + "ua" "Модель С4" + "es" "Modelo C4" + "ar" "نموذج C4" + } + "CCC_Settings" + { + "ru" "Настройки чата" + "en" "Chat Settings" + "pt" "Configurações do Chat" + "fi" "Chat-asetukset" + "ua" "Налаштування чату" + "es" "Configuraciones de chat" + "ar" "إعدادات المحادثة" + } + "С4" + { + "ru" "Бесплатная бомба" + "en" "Free Bomb" + "fi" "Ilmainen C4" + "ua" "Безкоштовна С4" + "es" "Libre bomba" + "ar" "قنبلة مجانية" + } + "CF_BuyVip" + { + "ru" "Данный функционал доступен только VIP-игрокам!" + "en" "This is feature available only for VIP-players!" + "fi" "Tämä toiminto on käytettävissä vain VIP-pelaajille!" + "ua" "Цей функціонал доступний лише VIP-гравцям!" + "es" "¡Esta función está disponible solo para jugadores VIP!" + "ar" "هذه الميزة متاحة فقط للاعبين VIP!" + } + "CCC_Enabler" + { + "ru" "Активация настроек чата" + "en" "Activate chat settings" + "fi" "Aktivoi chat-asetukset" + "ua" "Активація налаштувань чату" + "es" "Activa la configuración de chat" + "ar" "تنشيط إعدادات الدردشة" + } + "CAUSED_DAMAGE" + { + "ru" "Наносимый урон" + "en" "Caused Damage" + "pt" "Dano Causado" + "fi" "Aiheuttanut vahinkoa" + "ua" "Завдана шкода" + "es" "Daño causado" + "ar" "تسببت في الضرر" + } + "Defuser" + { + "ru" "Набор обезвреживания" + "en" "Defuse Kit" + "pt" "Kit Defuse" + "fi" "Defuse Kit" + "ua" "Набір знешкодження" + "es" "Defuse Kit" + "ar" "طقم نزع الفتيل" + } + "Decoy" + { + "ru" "Телепорт-граната" + "en" "Decoy Teleport" + "fi" "Teleportti-kranaatti" + "ua" "Телепортаційна граната" + "es" "Teletransporte señuelo" + "ar" "شرك التخاطر" + } + "DissolveBody" + { + "ru" "Растворение тела" + "en" "Dissolution of the body" + "fi" "Liukeneminen elimistössä" + "ua" "Розчинення тіла" + "es" "Disolución del cuerpo" + "ar" "انحلال الجسم" + } + "DissolveBodyNew" + { + "ru" "Растворение тела" + "en" "Dissolution of the body" + "pt" "Dissolução do corpo" + "fi" "Liukeneminen elimistössä" + "ua" "Розчинення тіла" + "es" "Disolución del cuerpo" + "ar" "انحلال الجسم" + } + "ElectroEffects" + { + "ru" "Электрические эффекты" + "en" "Electro Effects" + "fi" "Salama Effekti" + "ua" "Електричні ефекти" + "es" "Efectos electricos" + "ar" "التأثيرات الكهربائية" + } + "evade" + { + "ru" "Уворот" + "en" "Dodge" + "fi" "Väistää" + "ua" "Ухилення" + "es" "Esquivar" + "ar" "يتملص" + } + "Endurance" + { + "ru" "Выносливость" + "en" "Endurance" + "fi" "Kestävyys" + "ua" "Витривалість" + "es" "Resistencia" + "ar" "قدرة التحمل" + } + "ExplosiveBullets" + { + "ru" "Взрывные пули" + "en" "Explosive Bullets" + "pt" "Balas Explosivas" + "fi" "Räjähtävät luodit" + "ua" "Вибухові кулі" + "es" "Balas explosivas" + "ar" "الرصاصات المتفجرة" + } + "ExtendedAmmoClip" + { + "ru" "Расширенные патроны в обойме" + "en" "Extended Ammo In Clip" + "fi" "Ammuksien lisäystä pääasiassa aseeseen" + "ua" "Розширені патрони в обоймі" + "es" "Munición extendida en el clip" + "ar" "ذخيرة ممتدة في مقطع" + } + "ExtendedAmmoReserve" + { + "ru" "Расширенные патроны в запасе" + "en" "Extended Ammo In Stock" + "fi" "Ammuksien lisäystä lippaaseen" + "ua" "Розширені патрони в запасі" + "es" "Munición extendida en stock" + "ar" "ممتد الذخيرة في المخزون" + } + "Fzi_Rescued" + { + "#format" "{1:N}" + "ru" "Вы были выбраны первым зомби, но были спасены благодаря VIP. {1} заражен вместо вас." + "en" "You were chosen as the first zombie, but were saved thanks to the VIP. {1} is infected for you." + "fi" "Olet valittu ensimmäiseksi zombiksi, mutta olit tallennettu VIP: n ansiosta. {1} tartunnan saaneet." + "ua" "Ви обрані в якості першого зомбі, але були врятовані завдяки VIP. {1} заражений замість вас." + "es" "Fuiste elegido como el primer zombie, pero te salvaste gracias al VIP. {1} está infectado por ti." + "ar" "تم اختيارك كأول غيبوبة ، ولكن تم إنقاذك بفضل VIP. {1} مصاب لك." + } + "Fzi" + { + "ru" "Иммунитет от первого заражения" + "en" "First Inf. Immunity" + "fi" "Immuniteetti ensimmäisestä infektiosta" + "ua" "Імунітет від першого зараження" + "es" "First Inf. Inmunidad" + "ar" "أول إنف. حصانة" + } + "FastReload" + { + "ru" "Быстрая перезарядка" + "en" "Fast Reload" + "fi" "Nopea lataus" + "ua" "Швидка перезарядка" + "es" "Recarga rápida" + "ar" "إعادة تحميل سريعة" + } + "FastSwitch" + { + "ru" "Быстрая смена оружия" + "en" "Fast Weapon Switch" + "fi" "Nopea aseenvaihto" + "ua" "Швидка заміна зброї" + "es" "Cambio rápido de arma" + "ar" "التبديل سلاح سريع" + } + "Fastplant" + { + "ru" "Быстрый плент бомбы" + "en" "Fast Plant Bomb" + "fi" "Nopean pommin pläntti" + "ua" "Швидкий плент бомби" + "es" "Bomba de planta rápida" + "ar" "قنبلة نباتية سريعة" + } + "FireDamage" + { + "ru" "Поджигающие повреждения" + "en" "Fire Damage" + "fi" "Sytytys Vahinko" + "ua" "Запалювальні пошкодження" + "es" "Daño por fuego" + "ar" "الخسائر الناجمة عن الحرائق" + } + "Gloves" + { + "ru" "Перчатки" + "en" "Gloves" + "pt" "Luvas" + "fi" "Hanskat" + "ua" "Рукавички" + "es" "Guantes" + "ar" "قفاز" + } + "Gloves_MENU" + { + "ru" "Выбор перчаток" + "en" "Choose Gloves" + "pt" "Escolha Luvas" + "fi" "Hanskojen valikko" + "ua" "Вибір рукавичок" + "es" "Elige guantes" + "ar" "اختر القفازات" + } + "GOD" + { + "ru" "Бессмертие" + "en" "GOD" + "fi" "Kuolemattomuus" + "ua" "Безсмертя" + "es" "DIOS" + "ar" "خلود" + } + "GrenadeTrails" + { + "ru" "Трейлы гранат" + "en" "Grenade Trails" + "pt" "Trilhas de Granadas" + "fi" "Kranaattijälki" + "ua" "Сліди гранат" + "es" "Granada Senderos" + "ar" "ممرات القنابل اليدوية" + } + "GrenadeTrails_MENU" + { + "ru" "Цвет трейлов гранат" + "en" "Color Grenade Trails" + "pt" "Cores das Trilhas de Grenadas" + "fi" "Kranaattijäljen väri" + "ua" "Колір слідів гранат" + "es" "Granada de color" + "ar" "مسارات القنابل الملونة" + } + "Grenades" + { + "ru" "Гранаты" + "en" "Grenades" + "pt" "Granadas" + "fi" "Kranaatit" + "ua" "Гранати" + "es" "Granadas" + "ar" "قنبلة يدوية" + } + "GoldStatues" + { + "ru" "Золотые статуи" + "en" "Gold Statues" + "fi" "Kulta patsaat" + "ua" "Золоті статуї" + "es" "Estatuas de oro" + "ar" "التماثيل الذهبية" + } + "Gravity" + { + "ru" "Гравитация" + "en" "Gravity" + "pt" "Gravidade" + "fi" "Gravitaatio" + "ua" "Гравітація" + "es" "Gravedad" + "ar" "الجاذبية" + } + "HP" + { + "ru" "ХП" + "en" "HP" + "fi" "Terveys" + "ua" "Здоров'я" + "es" "Salud" + "ar" "الصحة" + } + "HealCube" + { + "ru" "Регенерационный куб" + "en" "Heal Cube" + "fi" "Paranna kuutio" + "ua" "Відновлювальний куб" + "es" "Curar el cubo" + "ar" "شفاء المكعب" + } + "Healthshot" + { + "ru" "Укол здоровья" + "en" "Healthshot" + "pt" "Healthshot" + "fi" "Terveyden piikki" + "ua" "Укол здоров'я" + "es" "Disparo de salud" + "ar" "لقطة صحية" + } + "HeavyArmor" + { + "ru" "Тяжелая броня" + "en" "Heavy Armor" + "fi" "Kova suojaliivi" + "ua" "Важка броня" + "es" "Armadura pesada" + "ar" "درع ثقيل" + } + "HitMarker" + { + "ru" "Хитмаркер" + "en" "Hit Marker" + "fi" "Hit Marker" + "ua" "Хітмаркер" + "es" "Dar en el objetivo" + "ar" "ضرب علامة" + } + "JoinSound" + { + "ru" "Музыка при входе" + "en" "Join Sound" + "fi" "Musiikki tervehdys" + "ua" "Музика при підключенні" + "es" "Unirse al sonido" + "ar" "انضم إلى الصوت" + } + "jumps" + { + "ru" "Несколько прыжков" + "en" "Some Jumps" + "fi" "Useita hyppyjä" + "ua" "Декілька стрибків" + "es" "Algunos saltos" + "ar" "بعض القفزات" + } + "JoinMessage" + { + "ru" "Оповещение о входе" + "en" "Join Message" + "fi" "Kirjaudu sisään" + "ua" "Оповіщення про вхід" + "es" "Unirse al mensaje" + "ar" "الانضمام إلى الرسالة" + } + "KillScreen" + { + "ru" "Эффект при убийстве" + "en" "Kill Effect" + "fi" "Tappaus effekti" + "ua" "Ефект під час вбивства" + "es" "Efecto de Matar" + "ar" "تأثير القتل" + } + "Kick" + { + "ru" "Кикнуть игрока" + "en" "Kick Player" + "pt" "Kick Player" + "fi" "Potkia pelaaja" + "ua" "Вигнати гравця" + "es" "Jugador de Patada" + "ar" "لاعب ركلة" + } + "Language" + { + "ru" "Изменить язык" + "en" "Change Language" + "pt" "Mudar Idioma" + "fi" "Vaihda kieli" + "ua" "Змінити мову" + "es" "Cambiar Idioma" + "ar" "تغيير اللغة" + } + "LongJump" + { + "ru" "Длинный прыжок" + "en" "Long Jump" + "fi" "Pitkä hyppy" + "ua" "Довгий стрибок" + "es" "Salto Largo" + "ar" "قفزة طويلة" + } + "Left" + { + "#format" "{1:i}" + "ru" "Осталось: {1}" + "en" "Left: {1}" + "fi" "Jäljellä: {1}" + "ua" "Залишилось: {1}" + "es" "Lzquierda: {1}" + "ar" "اليسار: {1}" + } + "MKIT" + { + "ru" "Музыкальный комплект" + "en" "Music Kit" + "pt" "Kit de Música" + "fi" "Music Kit" + "ua" "Музичний набір" + "es" "Kit de Música" + "ar" "إعدادات مجموعة الموسيقى" + } + "Medkit" + { + "ru" "Аптечка" + "en" "Medic Kit" + "pt" "Kit Médico" + "fi" "Lääkkeet" + "ua" "Аптечка" + "es" "Kit Medico" + "ar" "عدة طبية" + } + "MuteGagSilence" + { + "ru" "Чат/Микрофон" + "en" "Mute/Gag/Silence" + "pt" "Mute/Gag/Silence" + "fi" "Chatti/Mikrofoni" + "ua" "Чат/Мікрофон" + "es" "Mute/Gag/Silence" + "ar" "كتم الصوت / الكمامة / الصمت" + } + "Money" + { + "ru" "Деньги" + "en" "Money" + "pt" "Dinheiro" + "fi" "Raha" + "ua" "Гроші" + "es" "Dinero" + "ar" "مال" + } + "mvp" + { + "ru" "MVP значки" + "en" "MVP" + "pt" "MVP" + "fi" "MVP merkit" + "ua" "MVP значки" + "es" "MVP" + "ar" "أفضل لاعب" + } + "NoRecoil" + { + "ru" "Стрельба без отдачи" + "en" "No Recoil" + "fi" "Kuvaaminen ilman palautusta" + "ua" "Стрілянина без віддачі" + "es" "Sin retroceso" + "ar" "لا نكص" + } + "NoSelfDamage" + { + "ru" "Нет урона от себя" + "en" "No Self Damage" + "fi" "Ei vaurioita itseään" + "ua" "Без самопошкодження" + "es" "Sin autolesiones" + "ar" "لا ضرر على النفس" + } + "NEON" + { + "ru" "Неон" + "en" "Neon" + "pt" "Neon" + "fi" "Neon" + "ua" "Неон" + "es" "Neón" + "ar" "نيون" + } + "Non_Lethal_Damage" + { + "ru" "Не смертельный урон" + "en" "No Lethal Damage" + "fi" "Ei tappavaa vahinkoa" + "ua" "Не смертельна шкода" + "es" "Daño no letal" + "ar" "ضرر غير مميت" + } + "NEON_MENU" + { + "ru" "Цвет неона" + "en" "Color Neon" + "pt" "Cor do Neon" + "fi" "Neon Väri" + "ua" "Колір неону" + "es" "Color neón" + "ar" "لون النيون" + } + "nightvision" + { + "ru" "Ночное зрение" + "en" "Night Vision" + "pt" "Visão Noturna" + "fi" "Yö visio" + "ua" "Нічне бачення" + "es" "Vision nocturna" + "ar" "رؤية ليلية" + } + "NoFallDamage" + { + "ru" "Нет урона от мира" + "en" "No Fall Damage" + "fi" "Ei vaurioita maailmalta" + "ua" "Без шкоди від світу" + "es" "No hay daño por caida" + "ar" "لا ضرر السقوط" + } + "OOImmunity" + { + "ru" "Иммунитет от ограничения оружия" + "en" "Weapon restriction immunity" + "fi" "Gun limit immunity" + "ua" "Імунітет від обмеження зброї" + "es" "Inmunidad de restricción de armas" + "ar" "حصانة تقييد السلاح" + } + "paint" + { + "ru" "Рисовалка" + "en" "Paint" + "pt" "Paint" + "fi" "Maalaus" + "ua" "Малювалка" + "es" "Pintar" + "ar" "رسم" + } + "paint_menu" + { + "ru" "Настройка рисовалки" + "en" "Paint Menu" + "pt" "Paint Menu" + "fi" "Maalaus Menu" + "ua" "Налаштування малювалки" + "es" "Menú de pintura" + "ar" "رسم قائمة طعام" + } + "Parachute" + { + "ru" "Парашют" + "en" "Parachute" + "pt" "Paraquedas" + "fi" "Laskuvarjo" + "ua" "Парашут" + "es" "Paracaídas" + "ar" "المظلة" + } + "Pets_Display" + { + "ru" "Питомцы" + "en" "Pets" + "pt" "Pets" + "fi" "Lemmikkieläimet" + "ua" "Вихованці" + "es" "Mascotas" + "ar" "حيوان اليف" + } + "PaintBall" + { + "ru" "Пейнтбол" + "en" "Paintball" + "pt" "Paintball" + "fi" "Paintball" + "ua" "Пейнтбол" + "es" "Paintball" + "ar" "كرات الطلاء" + } + "Pets_Title" + { + "ru" "Выбор питомца" + "en" "Choosing Pet" + "pt" "Escolha um Pet" + "fi" "Lemmikkien valinta" + "ua" "Обрати вихованця" + "es" "Elegir una mascota" + "ar" "اختيار حيوان أليف" + } + "Pets_Disable" + { + "ru" "Выключить" + "en" "Disable" + "pt" "Desabilitar" + "fi" "Sammuta" + "ua" "Вимкнути" + "es" "Inhabilitar" + "ar" "تعطيل" + } + "QuickDefuse" + { + "ru" "Быстрое обезвреживание" + "en" "Quick Defuse" + "fi" "Nopea Purkaus" + "ua" "Швидке знешкодження" + "es" "Desactivación rápida" + "ar" "تحييد سريع" + } + "QHR" + { + "ru" "Быстрое спасение заложников" + "en" "Quick Hostage Rescue" + "fi" "Nopean panttivangien pelastus" + "ua" "Швидке рятування заручників" + "es" "Rescate rápido de rehenes" + "ar" "الإنقاذ السريع للرهائن" + } + "RegenArmor" + { + "ru" "Регенерация брони" + "en" "Regeneration Armor" + "fi" "Suojaliivin uudistaminen" + "ua" "Регенерація броні" + "es" "Armadura de regeneración" + "ar" "تجديد درع" + } + "Radar-Invis" + { + "ru" "Инвиз на радаре" + "en" "Radar Invis" + "fi" "Inviz tutkalla" + "ua" "Невидимість на радарі" + "es" "Radar Invis" + "ar" "رادار إينفيس" + } + "Respawn" + { + "ru" "Возрождение" + "en" "Respawn" + "fi" "Uudestisyntyminen" + "ua" "Відродження" + "es" "Reaparecer" + "ar" "إعادة" + } + "RegenHP" + { + "ru" "Регенерация здоровья" + "en" "HP Regeneration" + "fi" "Terveyden uudistaminen" + "ua" "Регенерація здоров'я" + "es" "Regeneración HP" + "ar" "تجديد الصحة" + } + "ReceivedDamage" + { + "ru" "Получаемый урон" + "en" "Received Damage" + "fi" "Aiheutuvat vahingot" + "ua" "Отримана шкода" + "es" "Daño recibido" + "ar" "تلقى الضرر" + } + "RADAR" + { + "ru" "Невидимость на радаре" + "en" "Hide in Radar" + "pt" "Esconder no Radar" + "fi" "Näkymättömyys tutka" + "ua" "Невидимість на радарі" + "es" "Esconderse en el radar" + "ar" "اختبئ في الرادار" + } + "RANK" + { + "ru" "Настройка ранга" + "en" "Rank Settings" + "fi" "Rank Asetukset" + "ua" "Налаштування рангу" + "es" "Configuraciones de rango" + "ar" "إعدادات الترتيب" + } + "RECEIVED_DAMAGE" + { + "ru" "Получаемый урон" + "en" "Received Damage" + "fi" "Aiheutuvat vahingot" + "ua" "Отримана шкода" + "es" "Daño recibido" + "ar" "تلقى الضرر" + } + "skybox" + { + "ru" "Небо" + "en" "Skybox" + "pt" "SkyBox" + "fi" "Taivas" + "ua" "Небо" + "es" "Skybox" + "ar" "سكايبوكس" + } + "skybox_share" + { + "ru" "Поделиться SkyBox'ом" + "en" "Share SkyBox" + "pt" "Compartilhar SkyBox" + } + "SmokeColors" + { + "ru" "Цвет дыма" + "en" "Smoke Color" + "pt" "Cor da Smoke" + "fi" "Savun väri" + "ua" "Колір диму" + "es" "Color de humo" + "ar" "لون الدخان" + } + "SWGM_VIP_Give" + { + "ru" "Вы получили VIP за вступление в нашу Steam группу." + "en" "You got VIP because you join our Steam group." + "fi" "Sait VIP-jäsenen Steam-ryhmään liittymisestä." + "ua" "Ви отримали VIP за приєднання до нашої групи Steam." + "es" "Tienes VIP porque te unes a nuestro grupo Steam." + "ar" "لقد حصلت على VIP لأنك انضممت إلى مجموعة Steam الخاصة بنا." + } + "SWGM_VIP_Take" + { + "ru" "Вы потеряли VIP, потому что вышли из нашей Steam группы." + "en" "You lost VIP because you leave our Steam group." + "fi" "Menetit VIP, koska jätit Steam-ryhmämme." + "ua" "Ви втратили VIP, тому що покинули нашу групу Steam." + "es" "Perdiste VIP porque dejaste nuestro grupo Steam." + "ar" "لقد فقدت VIP لأنك تركت مجموعة Steam الخاصة بنا." + } + "Skins" + { + "ru" "Скины" + "en" "Skins" + "pt" "Skins" + "fi" "Skinit" + "ua" "Персонаж" + "es" "Piel" + "ar" "بشرة" + } + "Skins_Menu" + { + "ru" "Скины Mеню" + "en" "Skins Menu" + "pt" "Menu Skins" + "fi" "Skini Valikko" + "ua" "Меню Cкінів" + "es" "Menú Skins" + "ar" "اختيار الجلد" + } + "Speed" + { + "ru" "Повышенная скорость" + "en" "Speed" + "fi" "Nopeus" + "ua" "Підвищена швидкість" + "es" "Velocidad" + "ar" "سرعة" + } + "SmokeColors_MENU" + { + "ru" "Выбрать цвет дыма" + "en" "Select Smoke Color" + "fi" "Valitse savun väri" + "ua" "Обрати колір диму" + "es" "Seleccionar color de humo" + "ar" "حدد لون الدخان" + } + "status" + { + "ru" "Показ премиум статуса" + "en" "Show Pemium Status" + "fi" "Näytetään premium-tila" + "ua" "Показ преміум статусу" + "es" "Mostrar estado premium" + "ar" "عرض حالة قسط" + } + "Showdamagehud" + { + "ru" "Показ урона (HUD)" + "en" "Show Damage (HUD)" + "fi" "Vahinkonäyttö (HUD)" + "ua" "Показ пошкоджень (HUD)" + "es" "Mostrar daño (HUD)" + "ar" "إظهار الضرر (HUD)" + } + "ShowDamageExtended" + { + "ru" "Показ урона" + "en" "Show Damage" + "fi" "Näytetään vaurioita" + "ua" "Показ пошкоджень" + "es" "Mostrar daño" + "ar" "عرض الضرر" + } + "Spawn_Effects" + { + "ru" "Эффекты при возрождении" + "en" "Spawn Effects" + "fi" "Uudestisyntymisen effektit" + "ua" "Ефекти при відродженні" + "es" "Efectos de engendro" + "ar" "تأثيرات النشر" + } + "Sparks" + { + "en" "Sparks" + "ru" "Искры" + "fi" "Kipinät" + "ua" "Іскри" + "es" "Chispas" + "ar" "شرارات" + } + "Switchteam" + { + "ru" "Изменить команду" + "en" "Switch Team" + "fi" "Muuta komentoa" + "ua" "Змінити команду" + "es" "Cambiar de equipo" + "ar" "فريق التبديل" + } + "Survivability" + { + "ru" "Выживаемость" + "en" "Survivability" + "fi" "Eloonjäämisaste" + "ua" "Життєздатність" + "es" "Supervivencia" + "ar" "البقاء على قيد الحياة" + } + "ShowDamage" + { + "ru" "Показ урона" + "en" "Displaying Damage" + "fi" "Näytetään vahinkopaneeli" + "ua" "Показ пошкодження" + "es" "Mostrar daño" + "ar" "عرض الضرر" + } + "Trails" + { + "ru" "Трейлы" + "en" "Trails" + "fi" "Jäljet" + "ua" "Трейли" + "es" "Caminos" + "ar" "الممرات" + } + "Trails_MENU" + { + "ru" "Выбор трейла" + "en" "Select Trail" + "fi" "Jäljen Valitseminen" + "ua" "Вибір трейлу" + "es" "Selección de caminos" + "ar" "اختيار الممر" + } + "Tracers" + { + "ru" "Трасеры" + "en" "Tracers" + "fi" "Merkkiaineet" + "ua" "Трасери" + "es" "Trazadores" + "ar" "الراسمات" + } + "Tracers_MENU" + { + "ru" "Цвет трасеров" + "en" "Color Tracers" + "fi" "Merkkiaineiden väri" + "ua" "Колір трасерів" + "es" "Trazadores de color" + "ar" "متتبعات اللون" + } + "Tagrenade" + { + "ru" "Таговая граната" + "en" "TA Grenade" + "pt" "TA Grenade" + "fi" "Tag Granaattiomena" + "ua" "Tag граната" + "es" "Tagrenade" + "ar" "تاجريناد" + } + "TopIcon" + { + "ru" "VIP Mодель над головой" + "en" "VIP Model over the head" + "fi" "VIP Malli päässä" + "ua" "VIP Mодель над головою" + "es" "Modelo VIP sobre la cabeza" + "ar" "نموذج VIP فوق الرأس" + } + "ThrowingKnives" + { + "ru" "Метательные ножи" + "en" "Throwing Knives" + "fi" "Heitto veitset" + "ua" "Метальні ножі" + "es" "Lanzando cuchillos" + "ar" "رمي السكاكين" + } + "TEAMMATES_HEAL" + { + "ru" "Лечение союзников" + "en" "Teammates Heal" + "fi" "Ally-hoito" + "ua" "Лікування союзників" + "es" "Compañeros de equipo sanan" + "ar" "زملاء الفريق شفاء" + } + "Tag" + { + "ru" "Тег" + "en" "TAG" + "fi" "TAG" + "ua" "Тег" + "es" "Etiqueta" + "ar" "بطاقة شعار" + } + "Tag_Menu" + { + "ru" "Настройки тега" + "en" "TAG Settings" + "fi" "TAG Asetukset" + "ua" "Налаштування тегу" + "es" "Configuraciones de etiqueta" + "ar" "إعدادات العلامات" + } + "UnlimitedAmmo" + { + "ru" "Бесконечные Патроны" + "en" "Unlimited Ammo" + "fi" "Rajoittamattomat ammukset" + "ua" "Нескінченні набої" + "es" "Munición ilimitada" + "ar" "إعدادات العلامات" + } + "UnlimPrimaryAmmo" + { + "ru" "Бесконечные патроны в основной обойме" + "en" "Infinite Ammo in the main clip" + "fi" "Loputtomasti ammuksia pääasiassa aseeseen" + "ua" "Нескінченні набої в основній обоймі" + "es" "Munición infinita en el clip principal" + "ar" "الذخيرة اللانهائية في المقطع الرئيسي" + } + "UnlimReserveAmmo" + { + "ru" "Бесконечные патроны в запасе" + "en" "Infinite ammo in stock" + "fi" "Loputtomasti ammuksia lippaaseen" + "ua" "Нескінченні набої у запасі" + "es" "Munición infinita en stock" + "ar" "ذخيرة لانهائية في المخزون" + } + "VIP_SWAPPOSITION" + { + "ru" "Смена позиций" + "en" "Swap Positions" + "fi" "Asemien muuttaminen" + "ua" "Зміна позицій" + "es" "Posiciones de intercambio" + "ar" "مراكز المبادلة" + } + "VIP_ICE" + { + "ru" "Заморозка" + "en" "Freeze" + "fi" "Jäätymispiste" + "ua" "Заморозка" + "es" "Congelar" + "ar" "تجميد" + } + "VIP_PLAYERCOLOR" + { + "ru" "Смена цветов" + "en" "Color Changing" + "fi" "Väri muuttuu" + "ua" "Зміна кольорів" + "es" "Cambio de color" + "ar" "تغيير اللون" + } + "VIP_DROPWEAPON" + { + "ru" "Выбить оружие" + "en" "Drop Weapon" + "fi" "Katkaise aseet" + "ua" "Вибити зброю" + "es" "Soltar arma" + "ar" "إسقاط السلاح" + } + "Vampirism" + { + "ru" "Вампиризм" + "en" "Vampirism" + "pt" "Vampirismo" + "fi" "Vampirismi" + "ua" "Вампіризм" + "es" "Vampirismo" + "ar" "مصاص دماء" + } + "ValveGloves" + { + "ru" "Перчатки (Valve)" + "en" "Gloves (Valve)" + "pt" "Luvas (Valve)" + "fi" "Käsineet (Valve)" + "ua" "Рукавички (Valve)" + "es" "Guantes (Valve)" + "ar" "القفازات (Valve)" + } + "ValveGloves_MENU" + { + "ru" "Выбор перчаток (Valve)" + "en" "Select Gloves (Valve)" + "pt" "Selecione Luvas (Valve)" + "fi" "Käsineiden valinta (Valve)" + "ua" "Вибір рукавичок (Valve)" + "es" "Seleccionar guantes (Valve)" + "ar" "حدد القفازات (Valve)" + } + "VoiceSoundPlayer" + { + "ru" "Фразы" + "en" "Voice Sound" + "fi" "Äänen ääni" + "ua" "Фрази" + "es" "Sonido de voz" + "ar" "صوت الصوت" + } + "WaterEffect" + { + "ru" "Брызги воды" + "en" "Water Splashing" + "fi" "Veden roiskeita" + "ua" "Бризки води" + "es" "Salpicaduras de agua" + "ar" "رش الماء" + } + "Weaponpack" + { + "ru" "Комплект оружий | WP" + "en" "Weapon Pack | WP" + "pt" "Pack de Armas | WP" + "fi" "Aseiden setti | WP" + "ua" "Комплект зброї | WP" + "es" "Set de armas | WP" + "ar" "مجموعة سلاح } WP" + } + "WeaponJumping" + { + "ru" "Прыжок от выстрела" + "en" "Jump from the shot" + "fi" "Shot Jump" + "ua" "Стрибок від пострілу" + "es" "Salta desde el tiro" + "ar" "القفز من اللقطة" + } + "ExoJumpBoots" + { + "ru" "Exo Jump ботинки" + "en" "Exo Jump Boots" + "pt" "Botas Exo Jump" + "fi" "Exo Jump stövlar" + "ua" "Exo Jump черевики" + "es" "Botas Exo Jump" + "ar" "القفز إكسو الروبوتات" + } + "BuyTeamWeapon" + { + "ru" "Покупка оружия команды" + "en" "Buy Team Weapon" + "fi" "Köp lagsvapen" + "ua" "Купівля командої зброї" + "es" "Comprar arma de equipo" + "ar" "شراء سلاح الفريق" + } + "Bumpmine" + { + "ru" "Толчковые мины" + "en" "Bump Mine" + "pt" "Bump Mine" + "fi" "Bump Min" + "ua" "Відштовхувальні міни" + "es" "Golpear la mía" + "ar" "عثرة الألغام" + } + "CashBack" + { + "ru" "Возврат денег" + "en" "Cash Back" + "fi" "Pengar tillbaka" + "ua" "Повернення грошей" + "es" "Devolución de dinero" + "ar" "كاش باك" + } + "BHOP" + { + "ru" "Банихоп" + "ua" "Банніхоп" + "en" "BunnyHop" + "pt" "BunnyHop" + "fi" "BunnyHop" + "es" "BunnyHop" + "ar" "بانيهوب" + } + "TF2_HalloweenFootprints" + { + "ru" "Следы под ногами (Хэллоуин)" + "en" "Footsteps (Halloween)" + "fi" "Fotspår (Halloween)" + "ua" "Сліди під ногами (Хеллоуїн)" + "es" "Pasos (Halloween)" + "ar" "خطى (هالوين)" + } + "TF2_DistortedHalloweenVoice" + { + "ru" "Искажённый голос (Хэллоуин)" + "en" "Distorted Voice (Halloween)" + "fi" "Förvrängd röst (Halloween)" + "ua" "Спотворений голос (Хеллоуїн)" + "es" "Voz distorsionada (Halloween)" + "ar" "صوت مشوه (الهالوين)" + } + "ShowTriggers" + { + "ru" "Показ триггеров" + "en" "Show Triggers" + "fi" "Visa triggers" + "ua" "Показати тригери" + "es" "Mostrar disparadores" + "ar" "إظهار المشغلات" + } + "color_management" + { + "ru" "Управление цветами" + "en" "Color Management" + "fi" "Färghantering" + "ua" "Керування кольорами" + "es" "Manejo del color" + "ar" "إدارة الألوان" + } + "AutoBandage" + { + "ru" "Авто бинтование" + "en" "Auto Bandage" + "fi" "Automaattinen Sidonta" + "ua" "Автоматичне бинтування" + "es" "Vendaje automático" + "ar" "الضمادة التلقائية" + } + "Shield" + { + "ru" "Щит" + "en" "Shield" + "fi" "Skydda" + "ua" "Щит" + "es" "Proteger" + "ar" "درع" + } + "ReWeapon" + { + "ru" "Восстановление боеприпасов" + "en" "Switch Weapon" + "fi" "Byt vapen" + "ua" "Відновлення боєприпасів" + "es" "Cambiar de arma" + "ar" "تبديل السلاح" + } + "FakeRanks" + { + "ru" "Фейк Ранг:" + "en" "Fake Ranks:" + "fi" "Fake Ranks:" + "ua" "Фейк Ранг:" + "es" "Fake Ranks:" + "ar" "الرتب الوهمية:" + } + + "FakeRanks_Menu" + { + "ru" "Меню Фейк-Ранга:" + "en" "Fake Ranks Menu:" + "fi" "Fake Ranks Menyn:" + "ua" "Меню Фейк Рангу:" + "es" "Menú FakeRanks:" + "ar" "قائمة الرتب الوهمية:" + } + "Instruments" + { + "ru" "Инструменты" + "en" "Instruments" + "fi" "instrument" + "ua" "Інструменти" + "es" "Instrumentos" + "ar" "الادوات" + } + "jump_effect" + { + "en" "Jump Effect" + "ru" "Эффект от прыжков" + "fi" "Hoppeffekt" + "ua" "Ефект від стрибків" + "es" "Efecto de salto" + "ar" "تأثير القفز" + } + "nade_wave_effect" + { + "en" "Nade Wave Effect" + "ru" "Волны от гранат" + "fi" "Nade våg effekt" + "ua" "Хвилі від гранат" + "es" "Efecto de onda Nade" + "ar" "تأثير موجة نادى" + } + "HALO" + { + "ru" "Нимб" + "en" "Halo" + "fi" "Halo" + "ua" "Німб" + "es" "Aureola" + "ar" "هالو" + } + "HALO_M" + { + "ru" "Цвет нимба" + "en" "Color Halo" + "fi" "Halo väri" + "ua" "Колір німбу" + "es" "Aureola de color" + "ar" "هالة ملونة" + } + "HALO_BLACK" + { + "ru" "Нимб Черный" + "en" "Halo Black" + "fi" "Halo Musta" + "ua" "Німб Чорний" + "es" "Aureola Svarti" + "ar" "هالو بلاك" + } + "VIP_SOUNDEFFECTS" + { + "ru" "Звуки" + "en" "Sounds" + "fi" "äänet" + "ua" "Звуки" + "es" "Suena" + "ar" "اصوات" + } + "NVD" + { + "ru" "ПНВ" + "en" "NVD" + "fi" "NVD" + "ua" "ПНБ" + "es" "NVD" + "ar" "NVG" + } + "bullet_effect" + { + "ru" "Эффект от выстрела" + "en" "Bullet Effect" + "fi" "Luodin vaikutus" + "ua" "Ефект від пострілу" + "es" "Efecto Bala" + "ar" "تأثير رصاصة" + } + "DuckSpeed" + { + "ru" "Отключить замедление скорости при приседании" + "en" "Disable slowdown when use duck" + "fi" "Poista hidastus käytöstä ankkaa käytettäessä" + "ua" "Відключити уповільнення швидкості під час присідання" + "es" "Desactiva la ralentización cuando usas pato" + "ar" "تعطيل التباطؤ عند استخدام البطة" + } + "vip_name_color" + { + "ru" "Цвет никнейма" + "en" "Client Username Color" + "fi" "Asiakkaan käyttäjänimen väri" + "ua" "Колір нікнейму" + "es" "Color del nombre de usuario del cliente" + "ar" "لون اسم مستخدم العميل" + } + "vip_message_color" + { + "ru" "Цвет сообщения" + "en" "Client Message Color" + "fi" "Asiakkaan viestin väri" + "ua" "Колір повідомлення" + "es" "Color del mensaje del cliente" + "ar" "لون رسالة العميل" + } + "vip_prefix" + { + "ru" "Префикс" + "en" "Client Prefix" + "fi" "Asiakkaan etuliite" + "ua" "Префікс" + "es" "Prefijo de cliente" + "ar" "بادئة العميل" + } + "vip_prefix_color" + { + "ru" "Цвет префикса" + "en" "Client Prefix Color" + "fi" "Asiakkaan etuliitteen väri" + "ua" "Колір префіксу" + "es" "Color del prefijo del cliente" + "ar" "لون بادئة العميل" + } + "vip_chat_nameco" + { + "ru" "Цвет никнейма" + "en" "Client Username Color" + "fi" "Asiakkaan käyttäjänimen väri" + "ua" "Колір нікнейму" + "es" "Color del nombre de usuario del cliente" + "ar" "لون اسم مستخدم العميل" + } + "vip_chat_msgco" + { + "ru" "Цвет сообщения" + "en" "Client Message Color" + "fi" "Asiakkaan viestin väri" + "ua" "Колір повідомлення" + "es" "Color del mensaje del cliente" + "ar" "لون رسالة العميل" + } + "vip_chat_prefix" + { + "ru" "Префикс" + "en" "Client Prefix" + "pt" "Prefixo do Client" + "fi" "Asiakkaan etuliite" + "ua" "Префікс" + "es" "Prefijo de cliente" + "ar" "بادئة العميل" + } + "vip_chat_prefixco" + { + "ru" "Цвет префикса" + "en" "Client Prefix Color" + "fi" "Asiakkaan etuliitteen väri" + "ua" "Колір префіксу" + "es" "Color del prefijo del cliente" + "ar" "لون بادئة العميل" + } + "advanced_message" + { + "ru" "Продвинутые сообщения" + "en" "Advanced Message" + "fi" "Lisäasetukset-viesti" + "ua" "Розширені повідомлення" + "es" "Mensaje avanzado" + "ar" "رسالة متقدمة" + } + "HintShowDamage" + { + "ru" "Отображение урона" + "en" "Damage Display" + "fi" "Skada display" + "ua" "Відображення пошкоджень" + "es" "Pantalla de daños" + "ar" "عرض الضرر" + } + "Rainbow_Player" + { + "ru" "Радужная модель" + "en" "Rainbow Pattern" + "fi" "Sateenkaarikuvio" + "ua" "Райдужна модель" + "es" "Padrão de arco-íris" + "ar" "نمط قوس قزح" + } + "RoundEndBhop" + { + "ru" "Банихоп в конце раунда" + "en" "BunnyHop at the end of the round" + "pt" "BunnyHop no final do round" + "fi" "BunnyHop kierroksen lopussa" + "ua" "Баніхоп у кінці раунду" + "es" "BunnyHop al final de la ronda" + "ar" "بانيهوب في نهاية الجولة" + } + "Xscope" + { + "ru" "Улучшенный снайперский прицел" + "en" "Advanced Sniper Sight" + "fi" "Advanced sniper näky" + "ua" "Покращений снайперський приціл" + "es" "Mira de francotirador avanzada" + "ar" "مشهد قناص متقدم" + } + "AmmoWeapons" + { + "ru" "Расширенная обойма" + "en" "Extended Clip" + "fi" "Laajennettu leike" + "ua" "Розширена обойма" + "es" "Clip extendido" + "ar" "كليب موسع" + } + "Invis" + { + "ru" "Прозрачность" + "en" "Invis" + "fi" "Läpikuultavuus" + "ua" "Прозорість" + "es" "Transparencia" + "ar" "إينفيس" + } + "Equipments" + { + "ru" "Экипировка" + "en" "Equipments" + "pt" "Equipamentos" + "fi" "Tamineet" + "ua" "Спорядження" + "es" "Transparencia" + "ar" "المعدات" + } + "EquipmentsMenu" + { + "ru" "Настройка экипировки" + "en" "Equipment Setting" + "pt" "Configuração do Equipamento" + "fi" "Laitteiden asettaminen" + "ua" "Налаштування екіпіровки" + "es" "Configuración del equipo" + "ar" "إعداد المعدات" + } + "Icon" + { + "ru" "Иконка" + "en" "Icon" + "pt" "Ícone" + "fi" "Ikoni" + "ua" "Іконка" + "es" "Icono" + "ar" "أيقونة" + } + "NadeModels" + { + "ru" "Модели гранат" + "en" "Grenade Models" + "pt" "Modelos de Granada" + "fi" "Kranaatti malli" + "ua" "Моделі гранат" + "es" "Modelos granate" + "ar" "نماذج القنابل اليدوية" + } + "NadeModels_Menu" + { + "ru" "Настройка моделей гранат" + "en" "Grenade Models Settings" + "pt" "Configuração dos Modelos de Granada" + "fi" "Kranaatti mallin asetukset" + "ua" "Налаштування моделей гранат" + "es" "Configuración de modelos granadas" + "ar" "إعدادات نماذج القنبلة" + } + "NoDamageVelocity" + { + "ru" "Не терять скорость, когда в вас стреляют" + "en" "Don't lose speed when you are being shot at" + "fi" "Älä menetä nopeutta, kun olet ammuttu" + "ua" "Не втрачати швидкість, коли по вас стріляють" + "es" "No pierdas la velocidad cuando te disparan" + "ar" "لا تفقد السرعة عندما يتم إطلاق النار عليك" + } + "X16Walls" + { + "ru" "Прострел стен" + "en" "Wall Shot" + "fi" "Seinä laukaus" + "ua" "Простріл стін" + "es" "Muro de tiro" + "ar" "طلقة الجدار" + } + "ReloadProgressBar" + { + "ru" "Индикатор выполнения перезарядки" + "en" "Reload Progress Bar" + "fi" "Latauksen ilmaisin" + "ua" "Індикатор виконання перезарядки" + "es" "Indicador de progreso de recarga" + "ar" "إعادة تحميل شريط التقدم" + } + "Killcredits" + { + "ru" "Кредиты за убийство" + "en" "Credits for Kill" + "pt" "Créditos por Kill" + "fi" "Hyvitykset tappaa" + "ua" "Кредити за вбивство" + "es" "Créditos por matar" + "ar" "قروض للقتل" + } + "HEADcredits" + { + "ru" "Кредиты за HS" + "en" "Credits for HS" + "pt" "Créditos por HS" + "fi" "Hyvitykset HS: lle" + "ua" "Кредити за HS" + "es" "Créditos para HS" + "ar" "قروض لضربة رأس" + } + "KNIFEcredits" + { + "ru" "Кредиты за нож" + "en" "Credits for Knife" + "pt" "Créditos por Faca" + "fi" "Laajuus veitsi" + "ua" "Кредити за ніж" + "es" "Créditos para el cuchillo" + "ar" "قروض للسكين" + } + "two_guns" + { + "ru" "Два оружия в одном слоте" + "en" "Two weapons in one slot" + "fi" "Kaksi asetta yhdessä korttipaikassa" + "ua" "Дві зброї в одному слоті" + "es" "Dos armas en una ranura" + "ar" "سلاحان في فتحة واحدة" + } + "Fists" + { + "ru" "Кулаки" + "en" "Fists" + "pt" "Punhos" + "fi" "Nyrkki" + "ua" "Кулаки" + "es" "Puños" + "ar" "القبضات" + } + "mu_GiveLoots" + { + "ru" "Дополнительные улики" + "en" "Additional Evidence" + "pt" "Evidência Adicional" + "fi" "Lisätodisteet" + "ua" "Додаткові докази" + "es" "Evidencia adicional" + "ar" "أدلة إضافية" + } + "ColoredSmoke" + { + "ru" "Цветной дым" + "en" "Colored Smoke" + "pt" "Smoke Colorida" + "fi" "Värillinen Savu" + "ua" "Кольоровий дим" + "es" "Humo de color" + "ar" "دخان ملون" + } + "SmokeColor" + { + "ru" "Указать цвет" + "en" "Select Color" + "pt" "Selecione a Cor" + "fi" "Valitse väri" + "ua" "Вказати колір" + "es" "Seleccionar color" + "ar" "إختر لون" + } + "Ban" + { + "ru" "Забанить игрока" + "en" "Ban Player" + "pt" "Banir Player" + "fi" "Kieltää pelaaja" + "ua" "Забанити гравця" + "es" "Prohibir al jugador" + "ar" "حظر لاعب" + } + "Agents" + { + "ru" "Агенты" + "en" "Agents" + "pt" "Agentes" + "fi" "Agentti" + "ua" "Агенти" + "es" "Agente" + "ar" "عملاء" + } + "VIP_Agents_M" + { + "ru" "Меню Агентов" + "en" "Agents Menu" + "pt" "Menu Agentes" + "fi" "Agenttien Valikko" + "ua" "Меню Агентів" + "es" "Menú Agentes" + "ar" "قائمة الوكلاء" + } + "resimm" + { + "ru" "Иммунитет от ограничения оружия" + "en" "Weapon Restriction Immunity" + "fi" "Gun limit immunity" + "ua" "Імунітет від обмеження зброї" + "es" "Inmunidad de restricción de armas" + "ar" "حصانة تقييد السلاح" + } + "switch_speed" + { + "ru" "Быстрая смена оружия" + "en" "Quick change of weapons" + "fi" "Nopea aseiden vaihto" + "ua" "Швидка зміна зброї" + "es" "Cambio rápido de armas" + "ar" "تغيير سريع للأسلحة" + } + "Disable" + { + "ru" "Выключить" + "en" "Disable" + "pt" "Desabilitar" + "fi" "Sammuta" + "ua" "Вимкнути" + "es" "Inhabilitar" + "ar" "تعطيل" + } + "FOV" + { + "ru" "Угол обзора" + "en" "Viewing Angle | FOV" + "pt" "Ângulo de Visão | FOV" + "fi" "Katselukulma" + "ua" "Кут огляду" + "es" "Ángulo de visión" + "ar" "زاوية عرض" + } + "ChatSound" + { + "ru" "Звук чата" + "en" "Chat message sound" + "pt" "Som da mensagem do bate-papo" + "fi" "Keskusteluviestin ääni" + "ua" "Звук чату" + "es" "Chat mensaje sonido" + "ar" "رسالة الدردشة الصوتية" + } + "lr_select_fakerank" + { + "ru" "Выбрать ранги" + "en" "Select Ranks" + "pt" "Selecionar Ranks" + "fi" "Valitse rivit" + "ua" "Вибрати ранги" + "es" "Seleccionar rangos" + "ar" "اختر الرتب" + } + "VIP_NO_LIMIT" + { + "#format" "{1:s}" + "ru" "Лимит использования {1} исчерпан!" + "en" "The limit of use {1} has been reached!" + "pt" "O limite de uso {1} foi atingido!" + "fi" "Käyttöraja {1} on saavutettu!" + "ua" "Межа використання {1} досягнута!" + "es" "¡Se ha alcanzado el límite de uso {1}!" + "ar" "تم الوصول إلى حد الاستخدام {1}!" + } + "VIP_END_TIME_USE" + { + "#format" "{1:s}" + "ru" "Время действия {1} закончилось!" + "en" "Action time {1} is over!" + "pt" "O tempo de ação {1} acabou!" + "fi" "Toiminta-aika {1} on ohi!" + "ua" "Час дії {1} закінчився!" + "es" "¡El tiempo de acción {1} ha terminado!" + "ar" "وقت العمل {1} هو أكثر!" + } + "Setclass" + { + "ru" "Смена Классов" + "en" "Set Class" + "pt" "Definir Classe" + "fi" "Luokanvaihto" + "ua" "Зміна КЛАСІВ" + "es" "Cambio De Clases" + "ar" "تغيير الفصول" + } + "sprays" + { + "ru" "Спрей" + "en" "Spray" + "pt" "Spray" + "fi" "Spray" + "ua" "Спрей" + "es" "Spray" + "ar" "رذاذ" + } + "sprays_menu" + { + "ru" "Выбор спрея" + "en" "Spray selection" + "pt" "Selecção da pulverização" + "fi" "Spray valinta" + "ua" "Вибір спрею" + "es" "Selección de Spray" + "ar" "رذاذ اختيار" + } + "AntiKnifeDamage" + { + "ru" "Защита от урона ножом" + "en" "Protection from knife damage" + "pt" "Proteção contra danos com uma faca" + "fi" "Suojaa veitsi vahinko" + "ua" "Захист від шкоди ножем" + "es" "Protección contra daños con cuchillo" + "ar" "الحماية من تلف السكين" + } + "ClanCreate" + { + "ru" "Возможность создавать клан" + "en" "Clan create permission" + "pt" "Clã criar permissão" + "fi" "Klaanin luontioikeus" + "ua" "Дозвіл на створення клану" + "es" "Permiso para crear clan" + "ar" "عشيرة إنشاء إذن" + } +} From c36ac5440882b8fc53e608c93e0265ca8fbd17b9 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 17:14:05 +0300 Subject: [PATCH 02/46] Turn off VIP features when VIP status is removed by a plugin or an admin, as in 3.1-dev. Features_TurnOffAll now skips non-VIP clients instead of touching a null status map. --- addons/sourcemod/scripting/vip/API.sp | 1 + addons/sourcemod/scripting/vip/Features.sp | 3 +++ addons/sourcemod/scripting/vip/adminmenu/Del.sp | 1 + 3 files changed, 5 insertions(+) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 6512523..8fb7720 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -693,6 +693,7 @@ int API_RemoveClientVIP(Handle hPlugin, CancelClientMenu(iClient); } + Features_TurnOffAll(iClient); ResetClient(iClient); char szBuffer[PMP]; diff --git a/addons/sourcemod/scripting/vip/Features.sp b/addons/sourcemod/scripting/vip/Features.sp index 6b72a14..2147cbe 100644 --- a/addons/sourcemod/scripting/vip/Features.sp +++ b/addons/sourcemod/scripting/vip/Features.sp @@ -1,6 +1,9 @@ void Features_TurnOffAll(int iClient) { DebugMessage("Features_TurnOffAll %N (%i)", iClient, iClient); + if(!(g_iClientInfo[iClient] & IS_VIP)) + return; + int iFeatures = g_hFeaturesArray.Length; if(iFeatures == 0) return; diff --git a/addons/sourcemod/scripting/vip/adminmenu/Del.sp b/addons/sourcemod/scripting/vip/adminmenu/Del.sp index 8527e5d..4e6cbd2 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/Del.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/Del.sp @@ -47,6 +47,7 @@ public int MenuHandler_DeleteVipPlayerMenu(Menu hMenu, MenuAction action, int iC if (iTarget) { DB_RemoveClientFromID(iClient, iTarget, _, true); + Features_TurnOffAll(iTarget); ResetClient(iTarget); CreateForward_OnVIPClientRemoved(iTarget, "Removed by Admin", iClient); DisplayClientInfo(iTarget, "expired_info"); From 90bb5bc2bce53aa1d9c9fcc9c0407b3b4b16c849 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 18:08:10 +0300 Subject: [PATCH 03/46] Add VIP_OnConfigsLoaded forward, VIP_IsGroupExists native (VIP_IsValidVIPGroup is now deprecated) and VIP_CORE_INTERFACE 310, as in 3.1-dev. Forwards are now created before configs are read so the new forward can fire on plugin start. --- addons/sourcemod/scripting/VIP_Core.sp | 2 +- .../sourcemod/scripting/include/vip_core.inc | 23 +++++++++++++++---- addons/sourcemod/scripting/vip/API.sp | 17 ++++++++++++++ addons/sourcemod/scripting/vip/Configs.sp | 2 ++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/VIP_Core.sp b/addons/sourcemod/scripting/VIP_Core.sp index d2f9f58..7b9720b 100644 --- a/addons/sourcemod/scripting/VIP_Core.sp +++ b/addons/sourcemod/scripting/VIP_Core.sp @@ -74,6 +74,7 @@ public void OnPluginStart() ITEM_DRAW = hDataPack.Position; delete hDataPack; + API_SetupForwards(); ReadConfigs(); VIPMenu_Setup(); @@ -82,7 +83,6 @@ public void OnPluginStart() #endif Cvars_Setup(); - API_SetupForwards(); HookEvent("player_spawn", Event_PlayerSpawn); HookEvent("player_death", Event_PlayerDeath); diff --git a/addons/sourcemod/scripting/include/vip_core.inc b/addons/sourcemod/scripting/include/vip_core.inc index e35690d..f9e90ca 100644 --- a/addons/sourcemod/scripting/include/vip_core.inc +++ b/addons/sourcemod/scripting/include/vip_core.inc @@ -3,6 +3,8 @@ #endif #define _vip_core_included +#define VIP_CORE_INTERFACE 310 + /* Типы данных VIP-функций */ enum VIP_ValueType { @@ -136,6 +138,15 @@ forward Action VIP_OnFeatureToggle(int iClient, const char[] szFeature, VIP_Togg */ forward void VIP_OnVIPLoaded(); +/** + * Вызывается когда VIP-плагин загрузил настройки (при старте и при перезагрузке конфигов). + * + * @noparams + * + * @noreturn +*/ +forward void VIP_OnConfigsLoaded(); + /** * Вызывается когда VIP-функция была зарегистрирована. * @@ -303,13 +314,16 @@ native bool VIP_SetClientVIPGroup(int iClient, const char[] szGroup, bool bInDB native StringMap VIP_GetVIPClientTrie(int iClient); /** - * Проверяет наличие VIP-группы. + * Проверяет наличие группы. * - * @param szGroup Имя VIP-группы. + * @param szGroup Имя группы. * - * @return true - VIP-группа существует. - * false - VIP-группа не существует. + * @return true - группа существует. + * false - группа не существует. */ +native bool VIP_IsGroupExists(const char[] szGroup); + +#pragma deprecated Use VIP_IsGroupExists() instead native bool VIP_IsValidVIPGroup(const char[] szGroup); /** @@ -679,6 +693,7 @@ public void __pl_vip_core_SetNTVOptional() MarkNativeAsOptional("VIP_GetVIPClientTrie"); MarkNativeAsOptional("VIP_SendClientVIPMenu"); MarkNativeAsOptional("VIP_IsValidVIPGroup"); + MarkNativeAsOptional("VIP_IsGroupExists"); MarkNativeAsOptional("VIP_GetClientFeatureStatus"); MarkNativeAsOptional("VIP_SetClientFeatureStatus"); MarkNativeAsOptional("VIP_IsClientFeatureUse"); diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 8fb7720..f975fe1 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -10,6 +10,7 @@ static GlobalForward g_hGlobalForward_OnFeatureToggle; static GlobalForward g_hGlobalForward_OnFeatureRegistered; static GlobalForward g_hGlobalForward_OnFeatureUnregistered; static GlobalForward g_hGlobalForward_OnClientDisconnect; +static GlobalForward g_hGlobalForward_OnConfigsLoaded; void API_SetupForwards() { @@ -26,6 +27,7 @@ void API_SetupForwards() g_hGlobalForward_OnFeatureRegistered = new GlobalForward("VIP_OnFeatureRegistered", ET_Ignore, Param_String); g_hGlobalForward_OnFeatureUnregistered = new GlobalForward("VIP_OnFeatureUnregistered", ET_Ignore, Param_String); g_hGlobalForward_OnClientDisconnect = new GlobalForward("VIP_OnClientDisconnect", ET_Ignore, Param_Cell, Param_Cell); + g_hGlobalForward_OnConfigsLoaded = new GlobalForward("VIP_OnConfigsLoaded", ET_Ignore); } // Global Forwards @@ -36,6 +38,13 @@ void CreateForward_OnVIPLoaded() Call_Finish(); } +void CreateForward_OnConfigsLoaded() +{ + DBG_API("CreateForward_OnConfigsLoaded()"); + Call_StartForward(g_hGlobalForward_OnConfigsLoaded); + Call_Finish(); +} + bool CreateForward_OnClientPreLoad(int iClient) { DBG_API("g_hGlobalForward_OnClientPreLoad(%N (%d), %b)", iClient, iClient, g_iClientInfo[iClient] & IS_VIP); @@ -249,6 +258,7 @@ public APLRes AskPluginLoad2(Handle myself, bool bLate, char[] szError, int err_ RegNative(SendClientVIPMenu); RegNative(IsValidVIPGroup); + RegNative(IsGroupExists); RegNative(GetClientFeatureStatus); RegNative(SetClientFeatureStatus); @@ -718,6 +728,13 @@ public int Native_IsValidVIPGroup(Handle hPlugin, int iNumParams) return UTIL_CheckValidVIPGroup(szGroup); } +public int Native_IsGroupExists(Handle hPlugin, int iNumParams) +{ + char szGroup[64]; + GetNativeString(1, SZF(szGroup)); + return UTIL_CheckValidVIPGroup(szGroup); +} + public int Native_IsVIPLoaded(Handle hPlugin, int iNumParams) { return ((GLOBAL_INFO & IS_STARTED) && g_hDatabase); diff --git a/addons/sourcemod/scripting/vip/Configs.sp b/addons/sourcemod/scripting/vip/Configs.sp index 6a0daac..637662a 100644 --- a/addons/sourcemod/scripting/vip/Configs.sp +++ b/addons/sourcemod/scripting/vip/Configs.sp @@ -38,6 +38,8 @@ void ReadConfigs() g_hGroups = CreateConfig("data/vip/cfg/groups.ini", "VIP_GROUPS"); g_hInfo = CreateConfig("data/vip/cfg/info.ini", "VIP_INFO"); + + CreateForward_OnConfigsLoaded(); } void ReadSortingMenu() From 68130b1efd041088b35f4f2711e31e7c36c15a95 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 18:08:28 +0300 Subject: [PATCH 04/46] Add VIP_AddGroup and VIP_RemoveGroup natives for in-memory VIP groups, as in 3.1-dev. Groups are reset on config reload, so modules should re-add them in VIP_OnConfigsLoaded. --- .../sourcemod/scripting/include/vip_core.inc | 24 +++++++++++ addons/sourcemod/scripting/vip/API.sp | 42 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/addons/sourcemod/scripting/include/vip_core.inc b/addons/sourcemod/scripting/include/vip_core.inc index f9e90ca..013a353 100644 --- a/addons/sourcemod/scripting/include/vip_core.inc +++ b/addons/sourcemod/scripting/include/vip_core.inc @@ -326,6 +326,28 @@ native bool VIP_IsGroupExists(const char[] szGroup); #pragma deprecated Use VIP_IsGroupExists() instead native bool VIP_IsValidVIPGroup(const char[] szGroup); +/** + * Добавляет группу (только в памяти, groups.ini не изменяется). + * Группы сбрасываются при перезагрузке конфигов — добавляйте их заново в VIP_OnConfigsLoaded(). + * + * @param szGroup Имя группы. + * @param hFeatures Параметры группы (ключи с текущей позиции копируются в группу). + * + * @return true - успешно. + * false - группа уже существует. +*/ +native bool VIP_AddGroup(const char[] szGroup, KeyValues hFeatures); + +/** + * Удаляет группу (только в памяти, groups.ini не изменяется). + * + * @param szGroup Имя группы. + * + * @return true - успешно. + * false - группа не существует. +*/ +native bool VIP_RemoveGroup(const char[] szGroup); + /** * Выводит игроку сообщение в чат с приставкой (фраза в переводе "VIP_CHAT_PREFIX"). * @@ -694,6 +716,8 @@ public void __pl_vip_core_SetNTVOptional() MarkNativeAsOptional("VIP_SendClientVIPMenu"); MarkNativeAsOptional("VIP_IsValidVIPGroup"); MarkNativeAsOptional("VIP_IsGroupExists"); + MarkNativeAsOptional("VIP_AddGroup"); + MarkNativeAsOptional("VIP_RemoveGroup"); MarkNativeAsOptional("VIP_GetClientFeatureStatus"); MarkNativeAsOptional("VIP_SetClientFeatureStatus"); MarkNativeAsOptional("VIP_IsClientFeatureUse"); diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index f975fe1..92ac541 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -259,6 +259,8 @@ public APLRes AskPluginLoad2(Handle myself, bool bLate, char[] szError, int err_ RegNative(IsValidVIPGroup); RegNative(IsGroupExists); + RegNative(AddGroup); + RegNative(RemoveGroup); RegNative(GetClientFeatureStatus); RegNative(SetClientFeatureStatus); @@ -735,6 +737,46 @@ public int Native_IsGroupExists(Handle hPlugin, int iNumParams) return UTIL_CheckValidVIPGroup(szGroup); } +public int Native_AddGroup(Handle hPlugin, int iNumParams) +{ + char szGroup[64]; + GetNativeString(1, SZF(szGroup)); + + KeyValues hFeatures = view_as(GetNativeCell(2)); + if (!hFeatures) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid KeyValues handle/Некорректный KeyValues для группы \"%s\"", szGroup); + } + + if (!szGroup[0] || UTIL_CheckValidVIPGroup(szGroup)) + { + return false; + } + + g_hGroups.Rewind(); + g_hGroups.JumpToKey(szGroup, true); + g_hGroups.Import(hFeatures); + g_hGroups.Rewind(); + + return true; +} + +public int Native_RemoveGroup(Handle hPlugin, int iNumParams) +{ + char szGroup[64]; + GetNativeString(1, SZF(szGroup)); + + if (!UTIL_CheckValidVIPGroup(szGroup)) + { + return false; + } + + g_hGroups.DeleteThis(); + g_hGroups.Rewind(); + + return true; +} + public int Native_IsVIPLoaded(Handle hPlugin, int iNumParams) { return ((GLOBAL_INFO & IS_STARTED) && g_hDatabase); From c3742f96dff20a01f9833dca69833c21ceb8af4f Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 18:25:56 +0300 Subject: [PATCH 05/46] Add per-player storage from 3.1-dev: vip_storage table, sm_vip_storage_id, VIP_Set/GetClientStorageValue natives and VIP_OnClientStorageLoaded forward. Unlike 3.1-dev, storage waits for the database and a valid Steam account, writes only when changed, and uses portable single-quoted SQL. --- addons/sourcemod/scripting/VIP_Core.sp | 1 + .../sourcemod/scripting/include/vip_core.inc | 36 ++++ addons/sourcemod/scripting/vip/API.sp | 46 +++++ addons/sourcemod/scripting/vip/Clients.sp | 3 + addons/sourcemod/scripting/vip/Configs.sp | 4 + addons/sourcemod/scripting/vip/Cvars.sp | 9 + addons/sourcemod/scripting/vip/Database.sp | 24 +++ addons/sourcemod/scripting/vip/Global.sp | 1 + addons/sourcemod/scripting/vip/Storage.sp | 162 ++++++++++++++++++ 9 files changed, 286 insertions(+) create mode 100644 addons/sourcemod/scripting/vip/Storage.sp diff --git a/addons/sourcemod/scripting/VIP_Core.sp b/addons/sourcemod/scripting/VIP_Core.sp index 7b9720b..535a81c 100644 --- a/addons/sourcemod/scripting/VIP_Core.sp +++ b/addons/sourcemod/scripting/VIP_Core.sp @@ -31,6 +31,7 @@ public Plugin myinfo = #include "vip/Downloads.sp" #include "vip/Colors.sp" #include "vip/UTIL.sp" +#include "vip/Storage.sp" #include "vip/Features.sp" #include "vip/Sounds.sp" #include "vip/Info.sp" diff --git a/addons/sourcemod/scripting/include/vip_core.inc b/addons/sourcemod/scripting/include/vip_core.inc index 013a353..d420e5c 100644 --- a/addons/sourcemod/scripting/include/vip_core.inc +++ b/addons/sourcemod/scripting/include/vip_core.inc @@ -147,6 +147,15 @@ forward void VIP_OnVIPLoaded(); */ forward void VIP_OnConfigsLoaded(); +/** + * Вызывается когда загружено хранилище игрока (VIP_SetClientStorageValue / VIP_GetClientStorageValue). + * + * @param iClient Индекс игрока. + * + * @noreturn +*/ +forward void VIP_OnClientStorageLoaded(int iClient); + /** * Вызывается когда VIP-функция была зарегистрирована. * @@ -348,6 +357,31 @@ native bool VIP_AddGroup(const char[] szGroup, KeyValues hFeatures); */ native bool VIP_RemoveGroup(const char[] szGroup); +/** + * Сохраняет значение в хранилище игрока (таблица vip_storage). + * Хранилище доступно для всех игроков (не только VIP) после VIP_OnClientStorageLoaded(), + * до загрузки вызов игнорируется. В базу данных записывается при отключении игрока. + * + * @param iClient Индекс игрока. + * @param szKey Ключ (до 127 символов). + * @param szValue Значение (до 255 символов). + * + * @noreturn +*/ +native void VIP_SetClientStorageValue(int iClient, const char[] szKey, const char[] szValue); + +/** + * Получает значение из хранилища игрока. + * + * @param iClient Индекс игрока. + * @param szKey Ключ. + * @param szValue Буфер, в который будет помещен результат (пустая строка, если значения нет). + * @param iMaxLength Размер буфера. + * + * @noreturn +*/ +native void VIP_GetClientStorageValue(int iClient, const char[] szKey, char[] szValue, int iMaxLength); + /** * Выводит игроку сообщение в чат с приставкой (фраза в переводе "VIP_CHAT_PREFIX"). * @@ -718,6 +752,8 @@ public void __pl_vip_core_SetNTVOptional() MarkNativeAsOptional("VIP_IsGroupExists"); MarkNativeAsOptional("VIP_AddGroup"); MarkNativeAsOptional("VIP_RemoveGroup"); + MarkNativeAsOptional("VIP_SetClientStorageValue"); + MarkNativeAsOptional("VIP_GetClientStorageValue"); MarkNativeAsOptional("VIP_GetClientFeatureStatus"); MarkNativeAsOptional("VIP_SetClientFeatureStatus"); MarkNativeAsOptional("VIP_IsClientFeatureUse"); diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 92ac541..c74412c 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -11,6 +11,7 @@ static GlobalForward g_hGlobalForward_OnFeatureRegistered; static GlobalForward g_hGlobalForward_OnFeatureUnregistered; static GlobalForward g_hGlobalForward_OnClientDisconnect; static GlobalForward g_hGlobalForward_OnConfigsLoaded; +static GlobalForward g_hGlobalForward_OnClientStorageLoaded; void API_SetupForwards() { @@ -28,6 +29,7 @@ void API_SetupForwards() g_hGlobalForward_OnFeatureUnregistered = new GlobalForward("VIP_OnFeatureUnregistered", ET_Ignore, Param_String); g_hGlobalForward_OnClientDisconnect = new GlobalForward("VIP_OnClientDisconnect", ET_Ignore, Param_Cell, Param_Cell); g_hGlobalForward_OnConfigsLoaded = new GlobalForward("VIP_OnConfigsLoaded", ET_Ignore); + g_hGlobalForward_OnClientStorageLoaded = new GlobalForward("VIP_OnClientStorageLoaded", ET_Ignore, Param_Cell); } // Global Forwards @@ -45,6 +47,14 @@ void CreateForward_OnConfigsLoaded() Call_Finish(); } +void CreateForward_OnClientStorageLoaded(int iClient) +{ + DBG_API("CreateForward_OnClientStorageLoaded(%N (%d))", iClient, iClient); + Call_StartForward(g_hGlobalForward_OnClientStorageLoaded); + Call_PushCell(iClient); + Call_Finish(); +} + bool CreateForward_OnClientPreLoad(int iClient) { DBG_API("g_hGlobalForward_OnClientPreLoad(%N (%d), %b)", iClient, iClient, g_iClientInfo[iClient] & IS_VIP); @@ -262,6 +272,10 @@ public APLRes AskPluginLoad2(Handle myself, bool bLate, char[] szError, int err_ RegNative(AddGroup); RegNative(RemoveGroup); + // Storage + RegNative(SetClientStorageValue); + RegNative(GetClientStorageValue); + RegNative(GetClientFeatureStatus); RegNative(SetClientFeatureStatus); @@ -777,6 +791,38 @@ public int Native_RemoveGroup(Handle hPlugin, int iNumParams) return true; } +public int Native_SetClientStorageValue(Handle hPlugin, int iNumParams) +{ + int iClient = GetNativeCell(1); + if (!CheckValidClient(iClient, false)) + { + return 0; + } + + char szKey[STORAGE_KEY_LENGTH], szValue[STORAGE_VALUE_LENGTH]; + GetNativeString(2, SZF(szKey)); + GetNativeString(3, SZF(szValue)); + + Storage_SetClientValue(iClient, szKey, szValue); + return 0; +} + +public int Native_GetClientStorageValue(Handle hPlugin, int iNumParams) +{ + int iClient = GetNativeCell(1); + if (!CheckValidClient(iClient, false)) + { + return 0; + } + + char szKey[STORAGE_KEY_LENGTH], szValue[STORAGE_VALUE_LENGTH]; + GetNativeString(2, SZF(szKey)); + + Storage_GetClientValue(iClient, szKey, SZF(szValue)); + SetNativeString(3, szValue, GetNativeCell(4), true); + return 0; +} + public int Native_IsVIPLoaded(Handle hPlugin, int iNumParams) { return ((GLOBAL_INFO & IS_STARTED) && g_hDatabase); diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index 6ad5ba8..fa4993f 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -13,6 +13,7 @@ public void OnClientPutInServer(int iClient) if(!IsFakeClient(iClient) && !IsClientSourceTV(iClient)) { + Storage_LoadClient(iClient); Clients_CheckVipAccess(iClient, true, true); } } @@ -27,7 +28,9 @@ public void OnClientDisconnect(int iClient) if(!IsFakeClient(iClient)) { CreateForward_OnClientDisconnect(iClient); + Storage_SaveClient(iClient); } + Storage_ResetClient(iClient); ResetClient(iClient); delete g_hClientData[iClient]; diff --git a/addons/sourcemod/scripting/vip/Configs.sp b/addons/sourcemod/scripting/vip/Configs.sp index 637662a..16e903f 100644 --- a/addons/sourcemod/scripting/vip/Configs.sp +++ b/addons/sourcemod/scripting/vip/Configs.sp @@ -22,6 +22,10 @@ void OnReadyToStart() { if (IsClientInGame(iClient)) { + if (!IsFakeClient(iClient)) + { + Storage_LoadClient(iClient); + } Clients_CheckVipAccess(iClient, false); } } diff --git a/addons/sourcemod/scripting/vip/Cvars.sp b/addons/sourcemod/scripting/vip/Cvars.sp index 462cc03..8a7e770 100644 --- a/addons/sourcemod/scripting/vip/Cvars.sp +++ b/addons/sourcemod/scripting/vip/Cvars.sp @@ -12,6 +12,10 @@ void Cvars_Setup() hCvar.AddChangeHook(OnServerIDChange); OnServerIDChange(hCvar, NULL_STRING, NULL_STRING); + hCvar = CreateConVar("sm_vip_storage_id", "0", "ID группы серверов для хранилища данных игроков при использовании MySQL базы данных", _, true, 0.0); + hCvar.AddChangeHook(OnStorageIDChange); + g_CVAR_iStorageID = hCvar.IntValue; + hCvar = CreateConVar("sm_vip_auto_open_menu", "0", "Автоматически открывать VIP-меню при входе (0 - Выключено, 1 - Включено)", _, true, 0.0, true, 1.0); hCvar.AddChangeHook(OnAutoOpenMenuChange); g_CVAR_bAutoOpenMenu = hCvar.BoolValue; @@ -82,6 +86,11 @@ public void OnServerIDChange(ConVar hCvar, const char[] szOldValue, const char[] } } +public void OnStorageIDChange(ConVar hCvar, const char[] szOldValue, const char[] szNewValue) +{ + g_CVAR_iStorageID = hCvar.IntValue; +} + public void OnAutoOpenMenuChange(ConVar hCvar, const char[] szOldValue, const char[] szNewValue) { g_CVAR_bAutoOpenMenu = hCvar.BoolValue; diff --git a/addons/sourcemod/scripting/vip/Database.sp b/addons/sourcemod/scripting/vip/Database.sp index 1d297b1..bcd8d13 100644 --- a/addons/sourcemod/scripting/vip/Database.sp +++ b/addons/sourcemod/scripting/vip/Database.sp @@ -86,6 +86,14 @@ void CreateTables() #else FormatEx(SZF(g_szSID), " AND `sid` = %d", g_CVAR_iServerID); #endif + g_hDatabase.Query(SQL_Callback_StorageTableCreate, "CREATE TABLE IF NOT EXISTS `vip_storage` (\ + `account_id` INT NOT NULL, \ + `sid` INT UNSIGNED NOT NULL, \ + `key` VARCHAR(128) NOT NULL, \ + `value` VARCHAR(256) NOT NULL default '', \ + `updated` INT UNSIGNED NOT NULL default 0, \ + CONSTRAINT pk_StorageID PRIMARY KEY (`account_id`, `sid`, `key`) \ + ) DEFAULT CHARSET=" ... CHARSET ... ";", _, DBPrio_High); g_hDatabase.Query(SQL_Callback_TableCreate, "CREATE TABLE IF NOT EXISTS `vip_users` (\ `account_id` INT NOT NULL, \ `name` VARCHAR(64) NOT NULL default 'unknown' COLLATE '" ... COLLATION ... "', \ @@ -99,6 +107,12 @@ void CreateTables() else { g_szSID[0] = 0; + g_hDatabase.Query(SQL_Callback_StorageTableCreate, "CREATE TABLE IF NOT EXISTS `vip_storage` (\ + `account_id` INTEGER NOT NULL, \ + `key` VARCHAR(128) NOT NULL, \ + `value` TEXT NOT NULL default '', \ + `updated` INTEGER UNSIGNED NOT NULL default 0, \ + PRIMARY KEY (`account_id`, `key`));", _, DBPrio_High); g_hDatabase.Query(SQL_Callback_TableCreate, "CREATE TABLE IF NOT EXISTS `vip_users` (\ `account_id` INTEGER PRIMARY KEY NOT NULL, \ `name` VARCHAR(64) NOT NULL default 'unknown', \ @@ -138,6 +152,16 @@ public void SQL_Callback_TableCreate(Database hOwner, DBResultSet hResult, const } } +public void SQL_Callback_StorageTableCreate(Database hOwner, DBResultSet hResult, const char[] szError, any data) +{ + DBG_SQL_Response("SQL_Callback_StorageTableCreate"); + + if (szError[0]) + { + SetFailState("SQL_Callback_StorageTableCreate: %s", szError); + } +} + void RemoveExpAndOutPlayers() { if (g_CVAR_iDeleteExpired >= 0) diff --git a/addons/sourcemod/scripting/vip/Global.sp b/addons/sourcemod/scripting/vip/Global.sp index 76c9843..0a3dfc7 100644 --- a/addons/sourcemod/scripting/vip/Global.sp +++ b/addons/sourcemod/scripting/vip/Global.sp @@ -110,6 +110,7 @@ ConVar g_CVAR_hVIPMenu_CMD; int g_CVAR_iAdminFlag; int g_CVAR_iServerID; +int g_CVAR_iStorageID; int g_CVAR_iTimeMode; int g_CVAR_iDeleteExpired; int g_CVAR_iOutdatedExpired; diff --git a/addons/sourcemod/scripting/vip/Storage.sp b/addons/sourcemod/scripting/vip/Storage.sp new file mode 100644 index 0000000..8cb8924 --- /dev/null +++ b/addons/sourcemod/scripting/vip/Storage.sp @@ -0,0 +1,162 @@ +// Per-player key/value storage (table `vip_storage`), ported from 3.1-dev. +// Loaded on connect for every player with a valid Steam account, saved on disconnect. + +#define STORAGE_KEY_LENGTH 128 +#define STORAGE_VALUE_LENGTH 256 + +static StringMap g_hStorage[MAXPLAYERS+1]; +static int g_iStorageAccountID[MAXPLAYERS+1]; +static bool g_bStorageChanged[MAXPLAYERS+1]; + +bool Storage_IsClientLoaded(int iClient) +{ + return g_hStorage[iClient] != null; +} + +void Storage_SetClientValue(int iClient, const char[] szKey, const char[] szValue) +{ + if (Storage_IsClientLoaded(iClient)) + { + g_hStorage[iClient].SetString(szKey, szValue); + g_bStorageChanged[iClient] = true; + } +} + +bool Storage_GetClientValue(int iClient, const char[] szKey, char[] szValue, int iMaxLength) +{ + szValue[0] = 0; + return Storage_IsClientLoaded(iClient) && g_hStorage[iClient].GetString(szKey, szValue, iMaxLength); +} + +void Storage_LoadClient(int iClient) +{ + if (!g_hDatabase || !(GLOBAL_INFO & IS_STARTED) || Storage_IsClientLoaded(iClient)) + { + return; + } + + // Not authorized yet: retried from OnClientAuthorized, never stored under account 0 + int iAccountID = GetSteamAccountID(iClient); + if (!iAccountID) + { + return; + } + + char szFilter[64], szQuery[256]; + Storage_GetServerFilter(SZF(szFilter)); + FormatEx(SZF(szQuery), "SELECT `key`, `value` FROM `vip_storage` WHERE `account_id` = %d%s;", iAccountID, szFilter); + + DataPack hDataPack = new DataPack(); + hDataPack.WriteCell(UID(iClient)); + hDataPack.WriteCell(iAccountID); + + DBG_SQL_Query(szQuery); + g_hDatabase.Query(SQL_Callback_OnClientLoadStorage, szQuery, hDataPack); +} + +public void OnClientAuthorized(int iClient, const char[] szAuth) +{ + if (IsClientInGame(iClient) && !IsFakeClient(iClient)) + { + Storage_LoadClient(iClient); + } +} + +public void SQL_Callback_OnClientLoadStorage(Database hOwner, DBResultSet hResult, const char[] szError, any hPack) +{ + DBG_SQL_Response("SQL_Callback_OnClientLoadStorage"); + + DataPack hDataPack = view_as(hPack); + hDataPack.Reset(); + int iClient = CID(hDataPack.ReadCell()); + int iAccountID = hDataPack.ReadCell(); + delete hDataPack; + + if (hResult == null || szError[0]) + { + LogError("SQL_Callback_OnClientLoadStorage: %s", szError); + return; + } + + if (!iClient || !IsClientInGame(iClient) || Storage_IsClientLoaded(iClient)) + { + return; + } + + g_hStorage[iClient] = new StringMap(); + g_iStorageAccountID[iClient] = iAccountID; + g_bStorageChanged[iClient] = false; + + char szKey[STORAGE_KEY_LENGTH], szValue[STORAGE_VALUE_LENGTH]; + while (hResult.FetchRow()) + { + hResult.FetchString(0, SZF(szKey)); + hResult.FetchString(1, SZF(szValue)); + g_hStorage[iClient].SetString(szKey, szValue); + } + + CreateForward_OnClientStorageLoaded(iClient); +} + +void Storage_SaveClient(int iClient) +{ + if (!Storage_IsClientLoaded(iClient) || !g_bStorageChanged[iClient] || !g_hDatabase) + { + return; + } + + bool bMySQL = view_as(GLOBAL_INFO & IS_MySQL); + int iUpdated = GetTime(); + char szKey[STORAGE_KEY_LENGTH], szValue[STORAGE_VALUE_LENGTH], szQuery[1024]; + + Transaction hTxn = new Transaction(); + StringMapSnapshot hSnapshot = g_hStorage[iClient].Snapshot(); + for (int i = 0, iSize = hSnapshot.Length; i < iSize; ++i) + { + hSnapshot.GetKey(i, SZF(szKey)); + g_hStorage[iClient].GetString(szKey, SZF(szValue)); + + // Database.Format escapes %s + if (bMySQL) + { + g_hDatabase.Format(SZF(szQuery), "INSERT INTO `vip_storage` (`account_id`, `sid`, `key`, `value`, `updated`) \ + VALUES (%d, %d, '%s', '%s', %d) \ + ON DUPLICATE KEY UPDATE `value` = VALUES(`value`), `updated` = VALUES(`updated`);", + g_iStorageAccountID[iClient], g_CVAR_iStorageID, szKey, szValue, iUpdated); + } + else + { + g_hDatabase.Format(SZF(szQuery), "INSERT OR REPLACE INTO `vip_storage` (`account_id`, `key`, `value`, `updated`) \ + VALUES (%d, '%s', '%s', %d);", + g_iStorageAccountID[iClient], szKey, szValue, iUpdated); + } + + DBG_SQL_Query(szQuery); + hTxn.AddQuery(szQuery); + } + delete hSnapshot; + + g_hDatabase.Execute(hTxn, _, SQL_TxnCallback_SaveStorageFailure); + g_bStorageChanged[iClient] = false; +} + +public void SQL_TxnCallback_SaveStorageFailure(Database hDatabase, any data, int iNumQueries, const char[] szError, int iFailIndex, any[] queryData) +{ + LogError("SQL_TxnCallback_SaveStorageFailure (%d/%d): %s", iFailIndex, iNumQueries, szError); +} + +void Storage_ResetClient(int iClient) +{ + delete g_hStorage[iClient]; + g_iStorageAccountID[iClient] = 0; + g_bStorageChanged[iClient] = false; +} + +static void Storage_GetServerFilter(char[] szFilter, int iMaxLength) +{ + szFilter[0] = 0; + if (GLOBAL_INFO & IS_MySQL) + { + FormatEx(szFilter, iMaxLength, " AND `sid` = %d", g_CVAR_iStorageID); + } +} From 00db3b549f7f8e6491dfa9fb5c0a19311f4a6494 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 18:45:08 +0300 Subject: [PATCH 06/46] Store feature toggle statuses in the VIP storage instead of clientprefs cookies (USE_CLIENTPREFS 0, as in 3.1-dev), migrating existing cookie values on first load. The status-store wait timer now reloads features once when ready instead of on every tick, and gives up after 60s. --- addons/sourcemod/scripting/VIP_Core.sp | 2 + addons/sourcemod/scripting/vip/API.sp | 3 +- addons/sourcemod/scripting/vip/Clients.sp | 75 ++++++++++++---------- addons/sourcemod/scripting/vip/Features.sp | 57 +++++++++++++++- addons/sourcemod/scripting/vip/VipMenu.sp | 4 +- 5 files changed, 102 insertions(+), 39 deletions(-) diff --git a/addons/sourcemod/scripting/VIP_Core.sp b/addons/sourcemod/scripting/VIP_Core.sp index 535a81c..a87209d 100644 --- a/addons/sourcemod/scripting/VIP_Core.sp +++ b/addons/sourcemod/scripting/VIP_Core.sp @@ -17,6 +17,8 @@ #define USE_MORE_SERVERS 1 // Включить/Отключить режим при котором если ID сервера у игрока 0 - то VIP будет работать на всех серверах +#define USE_CLIENTPREFS 0 // Хранить статусы VIP-функций в куках (1) или в хранилище VIP vip_storage (0, старые куки переносятся автоматически) + public Plugin myinfo = { diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index c74412c..30188d3 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -1138,8 +1138,7 @@ public int Native_SetClientFeatureStatus(Handle hPlugin, int iNumParams) Features_SetStatus(iClient, szFeature, eNewStatus); if (iNumParams > 4 && GetNativeCell(5)) { - IntToString(view_as(eNewStatus), SZF(szFeature)); - view_as(hArray.Get(FEATURES_COOKIE)).Set(iClient, szFeature); + Features_SaveStatus(iClient, szFeature, hArray, eNewStatus); } return true; } diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index fa4993f..1fbe7d0 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -220,28 +220,24 @@ public void OnClientCookiesCached(int iClient) } #endif +// ponytail: fixed retry window, statuses fall back to defaults if the store isn't ready by then +#define STATUS_STORE_WAIT_ATTEMPTS 120 // x 0.5s + void Clients_LoadVIPFeaturesPre(int iClient, const char[] szFeature = NULL_STRING) { DBG_Clients("Clients_LoadVIPFeaturesPre %N", iClient); - DBG_Clients("AreClientCookiesCached %b", AreClientCookiesCached(iClient)); - - if (!AreClientCookiesCached(iClient)) + // Load now with what is available and reload once the status store (cookies / storage) is ready + if (!Features_IsStatusStoreReady(iClient)) { - DataPack hDataPack = new DataPack(); - hDataPack.WriteCell(UID(iClient)); - if(szFeature[0]) - { - hDataPack.WriteCell(true); - hDataPack.WriteString(szFeature); - } - else - { - hDataPack.WriteCell(false); - } - CreateTimer(0.5, Timer_CheckCookies, hDataPack, TIMER_FLAG_NO_MAPCHANGE|TIMER_DATA_HNDL_CLOSE); + Clients_WaitForStatusStore(iClient, szFeature, 0); } + Clients_LoadVIPFeaturesNow(iClient, szFeature); +} + +void Clients_LoadVIPFeaturesNow(int iClient, const char[] szFeature) +{ if(szFeature[0]) { Clients_LoadVIPFeature(iClient, szFeature); @@ -251,25 +247,37 @@ void Clients_LoadVIPFeaturesPre(int iClient, const char[] szFeature = NULL_STRIN Clients_LoadVIPFeatures(iClient); } +void Clients_WaitForStatusStore(int iClient, const char[] szFeature, int iAttempt) +{ + DataPack hDataPack = new DataPack(); + hDataPack.WriteCell(UID(iClient)); + hDataPack.WriteCell(iAttempt); + hDataPack.WriteString(szFeature); + CreateTimer(0.5, Timer_CheckCookies, hDataPack, TIMER_FLAG_NO_MAPCHANGE|TIMER_DATA_HNDL_CLOSE); +} + public Action Timer_CheckCookies(Handle hTimer, Handle hDP) { DataPack hDataPack = view_as(hDP); hDataPack.Reset(); int iClient = CID(hDataPack.ReadCell()); + int iAttempt = hDataPack.ReadCell(); + char szFeature[FEATURE_NAME_LENGTH]; + hDataPack.ReadString(SZF(szFeature)); - DBG_Clients("Timer_CheckCookies -> iClient: %N (%d), IsClientVIP: %b,", iClient, iClient, view_as(g_iClientInfo[iClient] & IS_VIP)); - if (iClient && g_iClientInfo[iClient] & IS_VIP) + DBG_Clients("Timer_CheckCookies -> iClient: %N (%d), IsClientVIP: %b, attempt: %d", iClient, iClient, view_as(g_iClientInfo[iClient] & IS_VIP), iAttempt); + if (!iClient || !(g_iClientInfo[iClient] & IS_VIP)) { - char szFeature[FEATURE_NAME_LENGTH]; - if(hDataPack.ReadCell()) - { - hDataPack.ReadString(SZF(szFeature)); - } - else - { - szFeature[0] = 0; - } - Clients_LoadVIPFeaturesPre(iClient, szFeature); + return Plugin_Stop; + } + + if (Features_IsStatusStoreReady(iClient)) + { + Clients_LoadVIPFeaturesNow(iClient, szFeature); + } + else if (++iAttempt < STATUS_STORE_WAIT_ATTEMPTS) + { + Clients_WaitForStatusStore(iClient, szFeature, iAttempt); } return Plugin_Stop; @@ -340,11 +348,9 @@ void Clients_LoadFeature(int iClient, const char[] szFeature) if (view_as(hArray.Get(FEATURES_ITEM_TYPE)) == TOGGLABLE) { static char szBuffer[4]; - static Cookie hCookie; - hCookie = view_as(hArray.Get(FEATURES_COOKIE)); - hCookie.Get(iClient, SZF(szBuffer)); + Features_GetSavedStatus(iClient, szFeature, hArray, SZF(szBuffer)); eStatus = view_as(StringToInt(szBuffer)); - DBG_Clients("GetFeatureCookie: '%s'", szBuffer); + DBG_Clients("GetSavedStatus: '%s'", szBuffer); if (szBuffer[0] == '\0' || !Features_IsValidStatus(eStatus)) { switch(hArray.Get(FEATURES_DEF_STATUS)) @@ -354,8 +360,11 @@ void Clients_LoadFeature(int iClient, const char[] szFeature) case DISABLED: eStatus = DISABLED; } - IntToString(view_as(eStatus), SZF(szBuffer)); - hCookie.Set(iClient, szBuffer); + // Persist the default only once the store is loaded, so it can't shadow a value that is still loading + if (Features_IsStatusStoreReady(iClient)) + { + Features_SaveStatus(iClient, szFeature, hArray, eStatus); + } // Features_SaveStatus(iClient, szFeature, hCookie, eStatus); } } diff --git a/addons/sourcemod/scripting/vip/Features.sp b/addons/sourcemod/scripting/vip/Features.sp index 2147cbe..1536f44 100644 --- a/addons/sourcemod/scripting/vip/Features.sp +++ b/addons/sourcemod/scripting/vip/Features.sp @@ -88,4 +88,59 @@ VIP_ToggleState Features_GetStatus(const int &iClient, const char[] szFeature) DebugMessage("Features_GetStatus: %N (%i) -> Feature: %s, eStatus: %i", iClient, iClient, szFeature, NO_ACCESS); return NO_ACCESS; -} \ No newline at end of file +} +// Toggle statuses persist in clientprefs cookies (USE_CLIENTPREFS 1) or in the VIP storage (USE_CLIENTPREFS 0). +// In storage mode cookies are still read once to migrate statuses saved by older versions. +bool Features_IsStatusStoreReady(int iClient) +{ + #if USE_CLIENTPREFS + return AreClientCookiesCached(iClient); + #else + return Storage_IsClientLoaded(iClient) && AreClientCookiesCached(iClient); + #endif +} + +void Features_GetSavedStatus(int iClient, const char[] szFeature, ArrayList hArray, char[] szValue, int iMaxLength) +{ + Cookie hCookie = view_as(hArray.Get(FEATURES_COOKIE)); + + #if USE_CLIENTPREFS + #pragma unused szFeature + hCookie.Get(iClient, szValue, iMaxLength); + #else + char szKey[STORAGE_KEY_LENGTH]; + Features_GetStorageKey(szFeature, SZF(szKey)); + if (!Storage_GetClientValue(iClient, szKey, szValue, iMaxLength) && hCookie && AreClientCookiesCached(iClient)) + { + hCookie.Get(iClient, szValue, iMaxLength); + if (szValue[0]) + { + Storage_SetClientValue(iClient, szKey, szValue); + } + } + #endif +} + +void Features_SaveStatus(int iClient, const char[] szFeature, ArrayList hArray, VIP_ToggleState eStatus) +{ + char szValue[4]; + IntToString(view_as(eStatus), SZF(szValue)); + + #if USE_CLIENTPREFS + #pragma unused szFeature + view_as(hArray.Get(FEATURES_COOKIE)).Set(iClient, szValue); + #else + #pragma unused hArray + char szKey[STORAGE_KEY_LENGTH]; + Features_GetStorageKey(szFeature, SZF(szKey)); + Storage_SetClientValue(iClient, szKey, szValue); + #endif +} + +#if !USE_CLIENTPREFS +// Same key format as 3.1-dev, so statuses saved there are picked up +void Features_GetStorageKey(const char[] szFeature, char[] szKey, int iMaxLength) +{ + FormatEx(szKey, iMaxLength, "FeatureStatus-%s", szFeature); +} +#endif diff --git a/addons/sourcemod/scripting/vip/VipMenu.sp b/addons/sourcemod/scripting/vip/VipMenu.sp index 5a9762b..1f88677 100644 --- a/addons/sourcemod/scripting/vip/VipMenu.sp +++ b/addons/sourcemod/scripting/vip/VipMenu.sp @@ -282,7 +282,6 @@ public int Handler_VIPMenu(Menu hMenu, MenuAction action, int iClient, int iOpti hPlugin = view_as(hBuffer.Get(FEATURES_PLUGIN)); if (view_as(hBuffer.Get(FEATURES_ITEM_TYPE)) == TOGGLABLE) { - char szBuffer[4]; VIP_ToggleState eOldStatus, eNewStatus; eOldStatus = Features_GetStatus(iClient, szItemInfo); @@ -298,8 +297,7 @@ public int Handler_VIPMenu(Menu hMenu, MenuAction action, int iClient, int iOpti if (eNewStatus != eOldStatus) { Features_SetStatus(iClient, szItemInfo, eNewStatus); - IntToString(view_as(eNewStatus), SZF(szBuffer)); - view_as(hBuffer.Get(FEATURES_COOKIE)).Set(iClient, szBuffer); + Features_SaveStatus(iClient, szItemInfo, hBuffer, eNewStatus); } } From 585cfb6ac240be97d302d5e6f510a8f299fa1783 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 19:58:44 +0300 Subject: [PATCH 07/46] Document USE_CLIENTPREFS, the vip_storage table and the account_id 0 rule in AGENTS.md. --- AGENTS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 83626e2..11f6806 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ ## Layout - `scripting/VIP_Core.sp` — entry point; `#include`s every `vip/*.sp` into ONE compilation unit (order matters: Global → Debug → ... → API → CMD → Clients). New file = add `#include` there. -- Compile-time switches in `VIP_Core.sp`: `DEBUG_MODE`, `USE_ADMINMENU` (gates `AdminMenu.sp` + `vip/adminmenu/*`), `USE_MORE_SERVERS`. +- Compile-time switches in `VIP_Core.sp`: `DEBUG_MODE`, `USE_ADMINMENU` (gates `AdminMenu.sp` + `vip/adminmenu/*`), `USE_MORE_SERVERS`, `USE_CLIENTPREFS` (toggle statuses in cookies vs `vip_storage`; status I/O only via `Features_GetSavedStatus`/`Features_SaveStatus`). - `vip/Global.sp` — all globals, macros (`SZF`, `UID/CID`, `SET_BIT/UNSET_BIT`), client flag bits (`IS_VIP`, `IS_LOADED`, ...), core state bits (`IS_STARTED`, `IS_MySQL`, `IS_LOADING`). - `vip/API.sp` — forwards (`API_SetupForwards`, `CreateForward_*`) and natives (`AskPluginLoad2`, `RegNative(X)` → `VIP_X` bound to `Native_X`). - `vip/Database.sp` — connection, schema, queries. `UTIL.sp` — shared helpers; check here before writing new ones. @@ -33,7 +33,8 @@ - Config name `vip_core` in databases.cfg → MySQL; otherwise falls back to local SQLite. Every query must work on BOTH. - Schemas differ: MySQL has `sid` column (PK `account_id, sid`), SQLite has no `sid`. Use `g_szSID` (empty on SQLite) when filtering by server. - MySQL uses `utf8mb4`. Escape user strings with `g_hDatabase.Escape`. All queries are async (`g_hDatabase.Query` + callback); check `szError` in callbacks. -- Players are keyed by `account_id` (SteamID AccountID), not userid/client index. +- Players are keyed by `account_id` (SteamID AccountID), not userid/client index. Never store data under `account_id` 0 (unauthorized client). +- `vip/Storage.sp` — per-player key/value cache over `vip_storage` (filtered by `sm_vip_storage_id` on MySQL), loaded on connect, written on disconnect. ## Conventions - `#pragma semicolon 1`, `#pragma newdecls required`. Tabs. Hungarian-ish names: `iClient`, `szName`, `hMenu`, `bFlag`, globals `g_*`, cvars `g_CVAR_*`. From 3b2afb9ca08cfb1c59168a47b1077a5f2af6b312 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:13:50 +0300 Subject: [PATCH 08/46] Fix server hang in UTIL_LoadVipCmd when sm_vip_menu_commands is empty or ends with ';'. The loop now stops on the last part even if it is empty after trimming. --- addons/sourcemod/scripting/vip/UTIL.sp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/vip/UTIL.sp b/addons/sourcemod/scripting/vip/UTIL.sp index 3d04dfa..2cf6177 100644 --- a/addons/sourcemod/scripting/vip/UTIL.sp +++ b/addons/sourcemod/scripting/vip/UTIL.sp @@ -127,11 +127,11 @@ void UTIL_LoadVipCmd(ConVar &hCvar, ConCmd Call_CMD) if (szPart[0]) { RegConsoleCmd(szPart, Call_CMD); - - if (iPos == -1) - { - return; - } + } + + if (iPos == -1) + { + return; } } } From 5171d5f9296bad4197af96c781b4d1af3af8863b Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:14:04 +0300 Subject: [PATCH 09/46] Fix admin menu acting on a previously picked online player after picking another one from the DB list. Picking from the DB list now clears the stale target userid, and deletion looks the target up online by account id either way. --- .../sourcemod/scripting/vip/adminmenu/Del.sp | 29 ++++++++++--------- .../sourcemod/scripting/vip/adminmenu/List.sp | 2 ++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/addons/sourcemod/scripting/vip/adminmenu/Del.sp b/addons/sourcemod/scripting/vip/adminmenu/Del.sp index 4e6cbd2..2dc292f 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/Del.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/Del.sp @@ -39,21 +39,22 @@ public int MenuHandler_DeleteVipPlayerMenu(Menu hMenu, MenuAction action, int iC if(g_hClientData[iClient].GetValue(DATA_KEY_TargetUID, iTarget)) { iTarget = CID(iTarget); - if (!iTarget && iTargetID != -1) - { - iTarget = IsClientOnline(iTargetID); - } + } - if (iTarget) - { - DB_RemoveClientFromID(iClient, iTarget, _, true); - Features_TurnOffAll(iTarget); - ResetClient(iTarget); - CreateForward_OnVIPClientRemoved(iTarget, "Removed by Admin", iClient); - DisplayClientInfo(iTarget, "expired_info"); - BackToAdminMenu(iClient); - return 0; - } + if (!iTarget && iTargetID != -1) + { + iTarget = IsClientOnline(iTargetID); + } + + if (iTarget) + { + DB_RemoveClientFromID(iClient, iTarget, _, true); + Features_TurnOffAll(iTarget); + ResetClient(iTarget); + CreateForward_OnVIPClientRemoved(iTarget, "Removed by Admin", iClient); + DisplayClientInfo(iTarget, "expired_info"); + BackToAdminMenu(iClient); + return 0; } if(iTargetID != -1) diff --git a/addons/sourcemod/scripting/vip/adminmenu/List.sp b/addons/sourcemod/scripting/vip/adminmenu/List.sp index 34fff2b..aac63a9 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/List.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/List.sp @@ -115,6 +115,8 @@ public int MenuHandler_VipPlayersListMenu(Menu hMenu, MenuAction action, int iCl } + // Picked from the DB list: forget a player picked earlier from the online list + g_hClientData[iClient].Remove(DATA_KEY_TargetUID); g_hClientData[iClient].SetValue(DATA_KEY_TargetID, S2I(szUserID)); ShowTargetInfo(iClient); From 48c0fae19031e8aec02bdd71fcf709b3735b818b Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:14:12 +0300 Subject: [PATCH 10/46] Fix VIP rows being deleted on in-game expiry when sm_vip_delete_expired is -1 (never delete). The check now matches the one used on connect. --- addons/sourcemod/scripting/vip/Clients.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index 1fbe7d0..3f7b85c 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -556,7 +556,7 @@ void Clients_ExpiredClient(int iClient) int iClientID; g_hFeatures[iClient].GetValue(KEY_EXPIRES, iClientID); - if (g_CVAR_iDeleteExpired == 0 || GetTime() >= ((g_CVAR_iDeleteExpired*86400) + iClientID)) + if (g_CVAR_iDeleteExpired == 0 || (g_CVAR_iDeleteExpired > 0 && GetTime() >= ((g_CVAR_iDeleteExpired*86400) + iClientID))) { if (g_hFeatures[iClient].GetValue(KEY_CID, iClientID) && iClientID != -1) { From 8ce899ec656df38539404c363d9610bbb6843df0 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:14:30 +0300 Subject: [PATCH 11/46] Fix every in-game player being loaded twice on start and late load, which leaked feature maps and fired load forwards and feature toggles twice. OnReadyToStart now only loads storage and leaves the VIP check to UTIL_ReloadVIPPlayers; Clients_CreateClientVIPSettings frees old maps in case of overlapping reloads. --- addons/sourcemod/scripting/vip/Clients.sp | 4 ++++ addons/sourcemod/scripting/vip/Configs.sp | 9 +++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index 3f7b85c..21b54f4 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -205,6 +205,10 @@ void Clients_OnVIPClientLoaded(int iClient) void Clients_CreateClientVIPSettings(int iClient, int iExp) { + // A reload may overlap with a load still in flight + delete g_hFeatures[iClient]; + delete g_hFeatureStatus[iClient]; + g_hFeatures[iClient] = new StringMap(); g_hFeatureStatus[iClient] = new StringMap(); diff --git a/addons/sourcemod/scripting/vip/Configs.sp b/addons/sourcemod/scripting/vip/Configs.sp index 16e903f..df57f96 100644 --- a/addons/sourcemod/scripting/vip/Configs.sp +++ b/addons/sourcemod/scripting/vip/Configs.sp @@ -18,15 +18,12 @@ void OnReadyToStart() CreateForward_OnVIPLoaded(); + // VIP access is checked right after by UTIL_ReloadVIPPlayers (SQL_Callback_TableCreate) for (int iClient = 1; iClient <= MaxClients; ++iClient) { - if (IsClientInGame(iClient)) + if (IsClientInGame(iClient) && !IsFakeClient(iClient)) { - if (!IsFakeClient(iClient)) - { - Storage_LoadClient(iClient); - } - Clients_CheckVipAccess(iClient, false); + Storage_LoadClient(iClient); } } } From 672558e214aba03006dd0430d7a319919d228936 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:14:41 +0300 Subject: [PATCH 12/46] Fix SQL injection in the admin menu player search: the search text is now escaped via Database.Format. Names containing a quote no longer break the query either. --- addons/sourcemod/scripting/vip/adminmenu/List.sp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/adminmenu/List.sp b/addons/sourcemod/scripting/vip/adminmenu/List.sp index aac63a9..2891a68 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/List.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/List.sp @@ -210,7 +210,8 @@ void ShowVipPlayersFromDBMenu(int iClient, int iOffset = 0) } else { - FormatEx(SZF(szWhere), " AND `name` LIKE '%%%s%%'", szSearch); + // Database.Format escapes %s (search text comes straight from admin chat) + g_hDatabase.Format(SZF(szWhere), " AND `name` LIKE '%%%s%%'", szSearch); } } From 30fa7217891bdf8882ffd906bfa89a003e458b10 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:14:48 +0300 Subject: [PATCH 13/46] Fix sm_delvip callback calling FetchRow on a null result after a database error. --- addons/sourcemod/scripting/vip/CMD.sp | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/sourcemod/scripting/vip/CMD.sp b/addons/sourcemod/scripting/vip/CMD.sp index 16b6996..1eba254 100644 --- a/addons/sourcemod/scripting/vip/CMD.sp +++ b/addons/sourcemod/scripting/vip/CMD.sp @@ -167,6 +167,7 @@ public void SQL_Callback_OnSelectRemoveClient(Database hOwner, DBResultSet hResu if (hResult == null || szError[0]) { LogError("SQL_Callback_OnSelectRemoveClient: %s", szError); + return; } if (iClient) From ff38fdc94a990443e63a2833bd3107368a43af3e Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:19:04 +0300 Subject: [PATCH 14/46] Add braces to the loop in UTIL_SearchCharInString. --- addons/sourcemod/scripting/vip/UTIL.sp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/UTIL.sp b/addons/sourcemod/scripting/vip/UTIL.sp index 3d04dfa..dbb0ee9 100644 --- a/addons/sourcemod/scripting/vip/UTIL.sp +++ b/addons/sourcemod/scripting/vip/UTIL.sp @@ -155,7 +155,12 @@ stock int UTIL_SearchCharInString(const char[] szBuffer, int c) iNum = 0; iLen = strlen(szBuffer); for (i = 0; i < iLen; ++i) - if (szBuffer[i] == c)iNum++; + { + if (szBuffer[i] == c) + { + iNum++; + } + } return iNum; } From 66b80573522c6af6507cd1ffa1f894b1b3b1bc7c Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:23:29 +0300 Subject: [PATCH 15/46] Brace every single-statement if/for/while body, per the AGENTS.md code style. Formatting only, no behavior change. --- addons/sourcemod/scripting/vip/AdminMenu.sp | 10 +++++-- addons/sourcemod/scripting/vip/Clients.sp | 4 +++ addons/sourcemod/scripting/vip/Colors.sp | 25 +++++++++++++---- addons/sourcemod/scripting/vip/Configs.sp | 2 ++ addons/sourcemod/scripting/vip/Downloads.sp | 26 +++++++++++++---- addons/sourcemod/scripting/vip/Features.sp | 28 ++++++++++++++----- addons/sourcemod/scripting/vip/VipMenu.sp | 5 +++- .../sourcemod/scripting/vip/adminmenu/Add.sp | 5 +++- .../sourcemod/scripting/vip/adminmenu/Del.sp | 5 +++- .../sourcemod/scripting/vip/adminmenu/List.sp | 5 +++- 10 files changed, 91 insertions(+), 24 deletions(-) diff --git a/addons/sourcemod/scripting/vip/AdminMenu.sp b/addons/sourcemod/scripting/vip/AdminMenu.sp index d9b59db..3607d65 100644 --- a/addons/sourcemod/scripting/vip/AdminMenu.sp +++ b/addons/sourcemod/scripting/vip/AdminMenu.sp @@ -75,7 +75,10 @@ int IsClientOnline(int ID) int iClientID; for (int i = 1; i <= MaxClients; ++i) { - if (IsClientInGame(i) && g_hFeatures[i] != null && g_hFeatures[i].GetValue(KEY_CID, iClientID) && iClientID == ID) return i; + if (IsClientInGame(i) && g_hFeatures[i] != null && g_hFeatures[i].GetValue(KEY_CID, iClientID) && iClientID == ID) + { + return i; + } } return 0; } @@ -307,7 +310,10 @@ void ShowTimeMenu(int iClient) { hKv.GetSectionName(SZF(szTime)); - if (iMenuType != TIME_SET && szTime[0] == '0') continue; + if (iMenuType != TIME_SET && szTime[0] == '0') + { + continue; + } hKv.GetString(szClientLang, SZF(szBuffer)); if (!szBuffer[0]) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index 1fbe7d0..97d5f8e 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -336,12 +336,16 @@ void Clients_LoadFeature(int iClient, const char[] szFeature) { static ArrayList hArray; if (!GLOBAL_TRIE.GetValue(szFeature, hArray)) + { return; + } DBG_Clients("LoadClientFeature: %s", szFeature); if (!GetValue(iClient, view_as(hArray.Get(FEATURES_VALUE_TYPE)), szFeature)) + { return; + } static VIP_ToggleState eStatus; DBG_Clients("GetValue: == true"); diff --git a/addons/sourcemod/scripting/vip/Colors.sp b/addons/sourcemod/scripting/vip/Colors.sp index a0e0706..5e3ebc3 100644 --- a/addons/sourcemod/scripting/vip/Colors.sp +++ b/addons/sourcemod/scripting/vip/Colors.sp @@ -88,11 +88,23 @@ void Colors_Print(int iClient, const char[] szFormat) int Colors_ReplaceColors(char[] sMsg, int MaxLength) { - if (ReplaceString(sMsg, MaxLength, "{TEAM}", "\x03"))return 0; + if (ReplaceString(sMsg, MaxLength, "{TEAM}", "\x03")) + { + return 0; + } - if (ReplaceString(sMsg, MaxLength, "{BLUE}", "\x03"))return 3; - if (ReplaceString(sMsg, MaxLength, "{RED}", "\x03"))return 2; - if (ReplaceString(sMsg, MaxLength, "{GRAY}", "\x03"))return 1; + if (ReplaceString(sMsg, MaxLength, "{BLUE}", "\x03")) + { + return 3; + } + if (ReplaceString(sMsg, MaxLength, "{RED}", "\x03")) + { + return 2; + } + if (ReplaceString(sMsg, MaxLength, "{GRAY}", "\x03")) + { + return 1; + } return -1; } @@ -101,7 +113,10 @@ int Colors_FindPlayerByTeam(int iTeam) { for (int i = 1; i <= MaxClients; i++) { - if (IsClientInGame(i) && GetClientTeam(i) == iTeam)return i; + if (IsClientInGame(i) && GetClientTeam(i) == iTeam) + { + return i; + } } return 0; diff --git a/addons/sourcemod/scripting/vip/Configs.sp b/addons/sourcemod/scripting/vip/Configs.sp index 16e903f..c61d65b 100644 --- a/addons/sourcemod/scripting/vip/Configs.sp +++ b/addons/sourcemod/scripting/vip/Configs.sp @@ -58,7 +58,9 @@ void ReadSortingMenu() BuildPath(Path_SM, SZF(szFeature), "data/vip/cfg/sort_menu.ini"); File hFile = OpenFile(szFeature, "r"); if (!hFile) + { return; + } g_hSortArray = new ArrayList(ByteCountToCells(FEATURE_NAME_LENGTH)); diff --git a/addons/sourcemod/scripting/vip/Downloads.sp b/addons/sourcemod/scripting/vip/Downloads.sp index 54bfc0e..0a4436e 100644 --- a/addons/sourcemod/scripting/vip/Downloads.sp +++ b/addons/sourcemod/scripting/vip/Downloads.sp @@ -6,8 +6,10 @@ void ReadDownloads() BuildPath(Path_SM, SZF(szBuffer), "data/vip/modules/downloadlist.txt"); File hFile = OpenFile(szBuffer, "r"); - if(!hFile) + if (!hFile) + { return; + } DBG_Download("OpenFile('%s')", szBuffer); int iPos; @@ -17,16 +19,22 @@ void ReadDownloads() iPos = StrContains(szBuffer, "//"); DBG_Download("iPos = %d", iPos); - if(iPos == 0) + if (iPos == 0) + { continue; + } - if(iPos != -1) + if (iPos != -1) + { szBuffer[iPos] = 0; + } TrimString(szBuffer); - if(!szBuffer[0] || !IsCharAlpha(szBuffer[0])) + if (!szBuffer[0] || !IsCharAlpha(szBuffer[0])) + { continue; + } DBG_Download("ReadFileLine: '%s'", szBuffer); @@ -46,8 +54,10 @@ void DownloadPath(const char[] szPath) { DBG_Download("DownloadPath: '%s'", szPath); - if(DownloadFile(szPath)) + if (DownloadFile(szPath)) + { return; + } DBG_Download("DirExists: %d", DirExists(szPath)); if(DirExists(szPath)) @@ -61,8 +71,10 @@ void DownloadDirectory(const char[] szPath) DBG_Download("DownloadDirectory: '%s'", szPath); DirectoryListing hDir = OpenDirectory(szPath); - if(!hDir) + if (!hDir) + { return; + } char szEntry[PMP], szNewPath[PMP]; FileType type; @@ -70,7 +82,9 @@ void DownloadDirectory(const char[] szPath) { DBG_Download("GetNext: '%s' %d", szEntry, type); if (type == FileType_Directory && (!strcmp(szEntry, ".") || !strcmp(szEntry, ".."))) + { continue; + } FormatEx(SZF(szNewPath), "%s/%s", szPath, szEntry); DBG_Download("szNewPath: '%s'", szNewPath); diff --git a/addons/sourcemod/scripting/vip/Features.sp b/addons/sourcemod/scripting/vip/Features.sp index 1536f44..380b527 100644 --- a/addons/sourcemod/scripting/vip/Features.sp +++ b/addons/sourcemod/scripting/vip/Features.sp @@ -1,12 +1,16 @@ void Features_TurnOffAll(int iClient) { DebugMessage("Features_TurnOffAll %N (%i)", iClient, iClient); - if(!(g_iClientInfo[iClient] & IS_VIP)) + if (!(g_iClientInfo[iClient] & IS_VIP)) + { return; + } int iFeatures = g_hFeaturesArray.Length; - if(iFeatures == 0) + if (iFeatures == 0) + { return; + } char szFeature[FEATURE_NAME_LENGTH]; VIP_ToggleState eOldStatus; @@ -17,11 +21,15 @@ void Features_TurnOffAll(int iClient) for(int i = 0; i < iFeatures; ++i) { g_hFeaturesArray.GetString(i, SZF(szFeature)); - if(!GLOBAL_TRIE.GetValue(szFeature, hArray)) + if (!GLOBAL_TRIE.GetValue(szFeature, hArray)) + { continue; + } - if(view_as(hArray.Get(FEATURES_ITEM_TYPE)) != TOGGLABLE) + if (view_as(hArray.Get(FEATURES_ITEM_TYPE)) != TOGGLABLE) + { continue; + } eOldStatus = Features_GetStatus(iClient, szFeature); hDataPack = view_as(hArray.Get(FEATURES_MENU_CALLBACKS)); @@ -39,8 +47,10 @@ void Features_TurnOnAll(int iClient) DebugMessage("Features_TurnOnAll %N (%i)", iClient, iClient); int iFeatures = g_hFeaturesArray.Length; - if(iFeatures == 0) + if (iFeatures == 0) + { return; + } char szFeature[FEATURE_NAME_LENGTH]; VIP_ToggleState eNewStatus; @@ -51,11 +61,15 @@ void Features_TurnOnAll(int iClient) for(int i = 0; i < iFeatures; ++i) { g_hFeaturesArray.GetString(i, SZF(szFeature)); - if(!GLOBAL_TRIE.GetValue(szFeature, hArray)) + if (!GLOBAL_TRIE.GetValue(szFeature, hArray)) + { continue; + } - if(view_as(hArray.Get(FEATURES_ITEM_TYPE)) != TOGGLABLE) + if (view_as(hArray.Get(FEATURES_ITEM_TYPE)) != TOGGLABLE) + { continue; + } hDataPack = view_as(hArray.Get(FEATURES_MENU_CALLBACKS)); hDataPack.Position = ITEM_SELECT; diff --git a/addons/sourcemod/scripting/vip/VipMenu.sp b/addons/sourcemod/scripting/vip/VipMenu.sp index 1f88677..d36c2ae 100644 --- a/addons/sourcemod/scripting/vip/VipMenu.sp +++ b/addons/sourcemod/scripting/vip/VipMenu.sp @@ -329,7 +329,10 @@ bool OnVipMenuFlood(int iClient) if (fLastTime[iClient] > 0.0) { float fSec = GetGameTime(); - if ((fSec - fLastTime[iClient]) < 3.0)return true; + if ((fSec - fLastTime[iClient]) < 3.0) + { + return true; + } fLastTime[iClient] = fSec; } return false; diff --git a/addons/sourcemod/scripting/vip/adminmenu/Add.sp b/addons/sourcemod/scripting/vip/adminmenu/Add.sp index cc76324..73157ec 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/Add.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/Add.sp @@ -43,7 +43,10 @@ public int MenuHandler_AddVip_PlayerList(Menu hMenu, MenuAction action, int iCli case MenuAction_End:delete hMenu; case MenuAction_Cancel: { - if (iItem == MenuCancel_ExitBack) BackToAdminMenu(iClient); + if (iItem == MenuCancel_ExitBack) + { + BackToAdminMenu(iClient); + } } case MenuAction_Select: { diff --git a/addons/sourcemod/scripting/vip/adminmenu/Del.sp b/addons/sourcemod/scripting/vip/adminmenu/Del.sp index 4e6cbd2..7d5bfa3 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/Del.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/Del.sp @@ -24,7 +24,10 @@ public int MenuHandler_DeleteVipPlayerMenu(Menu hMenu, MenuAction action, int iC case MenuAction_End: delete hMenu; case MenuAction_Cancel: { - if (iItem == MenuCancel_ExitBack) ShowTargetInfoMenu(iClient); + if (iItem == MenuCancel_ExitBack) + { + ShowTargetInfoMenu(iClient); + } } case MenuAction_Select: { diff --git a/addons/sourcemod/scripting/vip/adminmenu/List.sp b/addons/sourcemod/scripting/vip/adminmenu/List.sp index 34fff2b..0d1335a 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/List.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/List.sp @@ -57,7 +57,10 @@ public int MenuHandler_VipPlayersListMenu(Menu hMenu, MenuAction action, int iCl case MenuAction_End: delete hMenu; case MenuAction_Cancel: { - if (iItem == MenuCancel_ExitBack) BackToAdminMenu(iClient); + if (iItem == MenuCancel_ExitBack) + { + BackToAdminMenu(iClient); + } } case MenuAction_Select: { From 0e866f8da74185f5efcf0867687e4d9eed7b272e Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:26:33 +0300 Subject: [PATCH 16/46] Fix chat messages, VIP_LogMessage and debug logs breaking on text containing '%' (e.g. player names): already formatted text is now passed through "%s" instead of being used as a format string. --- addons/sourcemod/scripting/vip/API.sp | 2 +- addons/sourcemod/scripting/vip/Debug.sp | 16 ++++++++-------- addons/sourcemod/scripting/vip/Info.sp | 2 +- addons/sourcemod/scripting/vip/UTIL.sp | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 30188d3..6dfc457 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -386,7 +386,7 @@ public int Native_LogMessage(Handle hPlugin, int iNumParams) SetGlobalTransTarget(LANG_SERVER); FormatNativeString(0, 1, 2, sizeof(szMessage), _, szMessage); - LogToFile(g_szLogFile, szMessage); + LogToFile(g_szLogFile, "%s", szMessage); } return 0; diff --git a/addons/sourcemod/scripting/vip/Debug.sp b/addons/sourcemod/scripting/vip/Debug.sp index b528401..8ab07e2 100644 --- a/addons/sourcemod/scripting/vip/Debug.sp +++ b/addons/sourcemod/scripting/vip/Debug.sp @@ -15,7 +15,7 @@ public void DebugMessage(const char[] szMsg, any ...) { static char szBuffer[512]; VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #if defined LOG_DOWNLOADS @@ -24,7 +24,7 @@ public void DBG_Download(const char[] szMsg, any ...) static char szBuffer[512]; Format(szBuffer, sizeof(szBuffer), "Download: %s", szMsg); VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #else public void DBG_Download(const char[] szMsg, any ...) {} @@ -36,7 +36,7 @@ public void DBG_Config(const char[] szMsg, any ...) static char szBuffer[512]; Format(szBuffer, sizeof(szBuffer), "Config: %s", szMsg); VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #else public void DBG_Config(const char[] szMsg, any ...) {} @@ -48,7 +48,7 @@ public void DBG_Clients(const char[] szMsg, any ...) static char szBuffer[512]; Format(szBuffer, sizeof(szBuffer), "Clients: %s", szMsg); VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #else public void DBG_Clients(const char[] szMsg, any ...) {} @@ -60,7 +60,7 @@ public void DBG_SQL_Query(const char[] szMsg, any ...) static char szBuffer[512]; Format(szBuffer, sizeof(szBuffer), "SQL_Query: %s", szMsg); VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #else public void DBG_SQL_Query(const char[] szMsg, any ...) {} @@ -72,7 +72,7 @@ public void DBG_SQL_Response(const char[] szMsg, any ...) static char szBuffer[512]; Format(szBuffer, sizeof(szBuffer), "SQL_Response: %s", szMsg); VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #else public void DBG_SQL_Response(const char[] szMsg, any ...) {} @@ -84,7 +84,7 @@ public void DBG_API(const char[] szMsg, any ...) static char szBuffer[512]; Format(szBuffer, sizeof(szBuffer), "API: %s", szMsg); VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #else public void DBG_API(const char[] szMsg, any ...) {} @@ -96,7 +96,7 @@ public void DBG_Database(const char[] szMsg, any ...) static char szBuffer[512]; Format(szBuffer, sizeof(szBuffer), "Database: %s", szMsg); VFormat(szBuffer, sizeof(szBuffer), szMsg, 2); - LogToFile(g_szDebugLogFile, szBuffer); + LogToFile(g_szDebugLogFile, "%s", szBuffer); } #else public void DBG_Database(const char[] szMsg, any ...) {} diff --git a/addons/sourcemod/scripting/vip/Info.sp b/addons/sourcemod/scripting/vip/Info.sp index 3575152..15a1761 100644 --- a/addons/sourcemod/scripting/vip/Info.sp +++ b/addons/sourcemod/scripting/vip/Info.sp @@ -57,7 +57,7 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] { ReplaceValues(iClient, szBuffer, iBufLen, (szEvent[13] == 't')); } - VIP_PrintToChatClient(iClient, szBuffer); + VIP_PrintToChatClient(iClient, "%s", szBuffer); } } case 'm': diff --git a/addons/sourcemod/scripting/vip/UTIL.sp b/addons/sourcemod/scripting/vip/UTIL.sp index dbb0ee9..bd0fa37 100644 --- a/addons/sourcemod/scripting/vip/UTIL.sp +++ b/addons/sourcemod/scripting/vip/UTIL.sp @@ -260,12 +260,12 @@ void UTIL_Reply(int iClient, const char[] szMsg, any ...) VFormat(SZF(szMessage), szMsg, 3); if (iClient) { - VIP_PrintToChatClient(iClient, szMessage); + VIP_PrintToChatClient(iClient, "%s", szMessage); } else { Colors_RemoveColors(szMessage); - PrintToServer(szMessage); + PrintToServer("%s", szMessage); } } From 7be38bfede2bc5363190f4e2ff9cfad2ee9c80db Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:26:42 +0300 Subject: [PATCH 17/46] Fix VIP removals not being logged and leaking a DataPack when the admin leaves before the query finishes. Also free the DataPack when the row to remove is already gone. --- addons/sourcemod/scripting/vip/Database.sp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Database.sp b/addons/sourcemod/scripting/vip/Database.sp index bcd8d13..6914e72 100644 --- a/addons/sourcemod/scripting/vip/Database.sp +++ b/addons/sourcemod/scripting/vip/Database.sp @@ -329,6 +329,10 @@ public void SQL_Callback_SelectRemoveClient(Database hOwner, DBResultSet hResult DB_RemoveClient(hPack, iClientID); } + else + { + delete hPack; + } } void DB_RemoveClient(DataPack hDataPack, int iClientID) @@ -365,11 +369,7 @@ public void SQL_Callback_RemoveClient(Database hOwner, DBResultSet hResult, cons hPack.ReadString(SZF(szName)); hPack.ReadString(SZF(szGroup)); - if(iAdmin == -1) - { - return; - } - + // iAdmin is -1 when the admin left before the query finished: still log, just don't reply if (g_CVAR_bLogsEnable) { LogToFile(g_szLogFile, "%T", "LOG_VIP_DELETED", LANG_SERVER, szName, iClientID, szGroup, szAdmin); From 8d16bf53e8435a7eebd7077e767e0eb5b059469c Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:26:53 +0300 Subject: [PATCH 18/46] Fix VIP_GiveClientVIP and VIP_RemoveClientVIP2 throwing errors for iAdmin -1, which the include documents as "plugin". Any negative iAdmin is now treated as REASON_PLUGIN. --- addons/sourcemod/scripting/include/vip_core.inc | 4 ++-- addons/sourcemod/scripting/vip/API.sp | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/include/vip_core.inc b/addons/sourcemod/scripting/include/vip_core.inc index d420e5c..2c5b11b 100644 --- a/addons/sourcemod/scripting/include/vip_core.inc +++ b/addons/sourcemod/scripting/include/vip_core.inc @@ -428,7 +428,7 @@ native void VIP_SendClientVIPMenu(int iClient, bool bSelection = false); /** * Выдает игроку VIP-права. * - * @param iAdmin Индекс админа (0 - сервер, -1 - плагин). + * @param iAdmin Индекс админа (0 - сервер, -1 или любое отрицательное - плагин). * @param iClient Индекс игрока. * @param iTime Время в секундах. * @param szGroup Имя VIP-группы. @@ -444,7 +444,7 @@ native bool VIP_SetClientVIP(int iClient, int iTime, any AuthType, const char[] /** * Отнимает у игрока VIP-права. * - * @param iAdmin Индекс админа (0 - сервер, -1 - плагин). + * @param iAdmin Индекс админа (0 - сервер, -1 или любое отрицательное - плагин). * @param iClient Индекс игрока. * @param bInDB Удалять ли из базы данных. * @param bNotify Уведомление игрока (true - уведомить/false - не уведомлять). diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 6dfc457..ef3d623 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -589,7 +589,7 @@ public int Native_GiveClientVIP(Handle hPlugin, int iNumParams) char szGroup[64]; GetNativeString(4, SZF(szGroup)); - return API_GiveClientVIP(hPlugin, iAdmin, iClient, iTime, szGroup, bAddToDB); + return API_GiveClientVIP(hPlugin, API_GetAdminOrPlugin(iAdmin), iClient, iTime, szGroup, bAddToDB); } public int Native_SetClientVIP(Handle hPlugin, int iNumParams) @@ -687,7 +687,13 @@ public int Native_RemoveClientVIP2(Handle hPlugin, int iNumParams) bool bInDB = GetNativeCell(3); bool bNotify = GetNativeCell(4); - return API_RemoveClientVIP(hPlugin, iAdmin, iClient, bInDB, bNotify); + return API_RemoveClientVIP(hPlugin, API_GetAdminOrPlugin(iAdmin), iClient, bInDB, bNotify); +} + +// The .inc documents -1 as "plugin" while the core uses REASON_PLUGIN (-2): accept any negative value +int API_GetAdminOrPlugin(int iAdmin) +{ + return iAdmin < 0 ? REASON_PLUGIN : iAdmin; } int API_RemoveClientVIP(Handle hPlugin, @@ -698,7 +704,7 @@ int API_RemoveClientVIP(Handle hPlugin, { if (CheckValidClient(iClient)) { - if (iAdmin) + if (iAdmin > 0) { CheckValidClient(iAdmin, false); } From 8f2ced16f198ab80698d663747db7f9639b86e27 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:27:03 +0300 Subject: [PATCH 19/46] Fix VIP_SetClientVIPGroup logging the new group as the old one: the old group is now read before it is overwritten. --- addons/sourcemod/scripting/vip/API.sp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index ef3d623..b0e306c 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -440,6 +440,8 @@ public int Native_SetClientVIPGroup(Handle hPlugin, int iNumParams) GetNativeString(2, SZF(szGroup)); if (UTIL_CheckValidVIPGroup(szGroup)) { + char szOldGroup[64]; + g_hFeatures[iClient].GetString(KEY_GROUP, SZF(szOldGroup)); if (g_hFeatures[iClient].SetString(KEY_GROUP, szGroup)) { if (view_as(GetNativeCell(3))) @@ -454,12 +456,10 @@ public int Native_SetClientVIPGroup(Handle hPlugin, int iNumParams) if (g_CVAR_bLogsEnable) { - char szName[MNL], szAdmin[128], szPluginName[128], szOldGroup[64]; + char szName[MNL], szAdmin[128], szPluginName[128]; GetClientName(iClient, SZF(szName)); GetPluginInfo(hPlugin, PlInfo_Name, SZF(szPluginName)); FormatEx(SZF(szAdmin), "%T %s", "BY_PLUGIN", LANG_SERVER, szPluginName); - g_hFeatures[iClient].GetString(KEY_GROUP, SZF(szOldGroup)); - g_hFeatures[iClient].GetValue(KEY_CID, iClientID); LogToFile(g_szLogFile, "%T", "LOG_CHANGE_GROUP", LANG_SERVER, szName, iClientID, szOldGroup, szGroup, szAdmin); } } From 27c7f3190fd21af1c1d364b49a014bf77470d2e1 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:27:32 +0300 Subject: [PATCH 20/46] Fix the admin menu add flow: after adding a VIP the admin now returns to the menu they came from instead of the SourceMod top menu (an error if adminmenu is not loaded). Also fix the missing phrase shown when the target left. --- addons/sourcemod/scripting/vip/AdminMenu.sp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/AdminMenu.sp b/addons/sourcemod/scripting/vip/AdminMenu.sp index d9b59db..9d9aa84 100644 --- a/addons/sourcemod/scripting/vip/AdminMenu.sp +++ b/addons/sourcemod/scripting/vip/AdminMenu.sp @@ -542,12 +542,18 @@ public int MenuHandler_GroupsList(Menu hMenu, MenuAction action, int iClient, in if (iTarget) { g_hClientData[iClient].GetValue(DATA_KEY_Time, iBuffer); + + // Keep the menu to return to (BackToAdminMenu below) + AdminMenuType eThrowMenuType; + g_hClientData[iClient].GetValue(DATA_KEY_ThrowMenuType, eThrowMenuType); g_hClientData[iClient].Clear(); + g_hClientData[iClient].SetValue(DATA_KEY_ThrowMenuType, eThrowMenuType); + UTIL_ADD_VIP_PLAYER(iClient, iTarget, _, iBuffer, szGroup); } else { - VIP_PrintToChatClient(iClient, "%t", "Player no longer available"); + VIP_PrintToChatClient(iClient, "%t", "PLAYER_NO_LONGER_AVAILABLE"); } BackToAdminMenu(iClient); From ac9ec712da90b2d55eb8a4a68874007b98f7e434 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:27:36 +0300 Subject: [PATCH 21/46] Fix the temporary VIP info menu showing the admin's SteamID instead of the target's. --- addons/sourcemod/scripting/vip/adminmenu/List.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/adminmenu/List.sp b/addons/sourcemod/scripting/vip/adminmenu/List.sp index 34fff2b..dbf1e5d 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/List.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/List.sp @@ -388,7 +388,7 @@ void ShowTemporaryTargetInfo(int iClient) g_hFeatures[iTarget].GetValue(KEY_EXPIRES, iExpires); g_hClientData[iClient].SetValue(DATA_KEY_Time, iExpires); GetClientName(iTarget, SZF(szName)); - iAccountID = GetSteamAccountID(iClient); + iAccountID = GetSteamAccountID(iTarget); g_hClientData[iClient].SetString(DATA_KEY_Name, szName); g_hClientData[iClient].SetValue(DATA_KEY_Auth, iAccountID); From 95b3225b3b62553d937a3b84f0be241ec762280a Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:28:02 +0300 Subject: [PATCH 22/46] Fix VIP_OnVIPClientLoaded and feature toggles firing twice when the status store (cookies / storage) was not ready yet. Features are now loaded once, after the store is ready or the 60s wait runs out. --- addons/sourcemod/scripting/vip/Clients.sp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index 1fbe7d0..ac159a8 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -227,10 +227,11 @@ void Clients_LoadVIPFeaturesPre(int iClient, const char[] szFeature = NULL_STRIN { DBG_Clients("Clients_LoadVIPFeaturesPre %N", iClient); - // Load now with what is available and reload once the status store (cookies / storage) is ready + // Load once, when the status store (cookies / storage) is ready: loading fires OnVIPClientLoaded and turns features on if (!Features_IsStatusStoreReady(iClient)) { Clients_WaitForStatusStore(iClient, szFeature, 0); + return; } Clients_LoadVIPFeaturesNow(iClient, szFeature); @@ -271,15 +272,14 @@ public Action Timer_CheckCookies(Handle hTimer, Handle hDP) return Plugin_Stop; } - if (Features_IsStatusStoreReady(iClient)) - { - Clients_LoadVIPFeaturesNow(iClient, szFeature); - } - else if (++iAttempt < STATUS_STORE_WAIT_ATTEMPTS) + if (!Features_IsStatusStoreReady(iClient) && ++iAttempt < STATUS_STORE_WAIT_ATTEMPTS) { Clients_WaitForStatusStore(iClient, szFeature, iAttempt); + return Plugin_Stop; } + Clients_LoadVIPFeaturesNow(iClient, szFeature); + return Plugin_Stop; } From ac4921bdd849fde65680c80b625a4ad80ffeb64c Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:36:11 +0300 Subject: [PATCH 23/46] Ignore .omc/ (oh-my-claudecode local state). --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 01c0c85..acdc492 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.smx .sourceknight plugins/ +.omc/ From 38a6e25502b3790b43dd7ba709ad5e51ea993cdc Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:36:11 +0300 Subject: [PATCH 24/46] Add code style rules to AGENTS.md, derived from the prevailing style of the codebase. --- AGENTS.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 11f6806..62e2ed7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,18 +8,14 @@ - `-E` = warnings are errors. Fix every warning, including deprecation ones. SM 1.10 is the strictest parser (e.g. missing `;`), so code must build there too. - Alt: `sourceknight build` (`sourceknight.yaml`, SM 1.13). It ignores spcomp's exit code — check output for `error`. - Never commit `*.smx`, `plugins/`, `.sourceknight`. - -## Git -- `dev` is the integration branch: branch from `dev`, merge back into `dev`. `master` is for releases. -- LF line endings only (enforced by `.gitattributes`); don't introduce CRLF. +- Git: `dev` is the integration branch: branch from `dev`, merge back into `dev`. `master` is for releases. LF line endings only (`.gitattributes`). ## Layout - `scripting/VIP_Core.sp` — entry point; `#include`s every `vip/*.sp` into ONE compilation unit (order matters: Global → Debug → ... → API → CMD → Clients). New file = add `#include` there. - Compile-time switches in `VIP_Core.sp`: `DEBUG_MODE`, `USE_ADMINMENU` (gates `AdminMenu.sp` + `vip/adminmenu/*`), `USE_MORE_SERVERS`, `USE_CLIENTPREFS` (toggle statuses in cookies vs `vip_storage`; status I/O only via `Features_GetSavedStatus`/`Features_SaveStatus`). - `vip/Global.sp` — all globals, macros (`SZF`, `UID/CID`, `SET_BIT/UNSET_BIT`), client flag bits (`IS_VIP`, `IS_LOADED`, ...), core state bits (`IS_STARTED`, `IS_MySQL`, `IS_LOADING`). - `vip/API.sp` — forwards (`API_SetupForwards`, `CreateForward_*`) and natives (`AskPluginLoad2`, `RegNative(X)` → `VIP_X` bound to `Native_X`). -- `vip/Database.sp` — connection, schema, queries. `UTIL.sp` — shared helpers; check here before writing new ones. -- `vip/Clients.sp` — client load/unload lifecycle, spawn handling. `Features.sp` — feature registry. +- `vip/Database.sp` — connection, schema, queries. `UTIL.sp` — shared helpers; check here before writing new ones. `Clients.sp` — client load/unload lifecycle, spawn. `Features.sp` — feature registry. - `include/vip_core.inc` — PUBLIC API for third-party modules. - `data/vip/cfg/*.ini` — runtime configs (groups, info, sort_menu, times). `translations/vip_core.phrases.txt` — all user-facing strings. @@ -36,12 +32,19 @@ - Players are keyed by `account_id` (SteamID AccountID), not userid/client index. Never store data under `account_id` 0 (unauthorized client). - `vip/Storage.sp` — per-player key/value cache over `vip_storage` (filtered by `sm_vip_storage_id` on MySQL), loaded on connect, written on disconnect. +## Code style (new/changed code; legacy deviations are not a reason to copy them) +- `#pragma semicolon 1`, `#pragma newdecls required`. Tabs, no trailing whitespace. Allman braces, always braced, also single-statement `if`/`for` and `case` bodies (`case MenuAction_End: delete hMenu;` is the only one-liner). +- Space after keywords: `if (`, `for (`, `while (`, `switch (`. `++i` in loops. `!bFlag`, not `bFlag == false`. Buffers: `SZF(szBuf)`, not `szBuf, sizeof(szBuf)`; `FormatEx` unless formatting a buffer into itself. +- Names: locals/params Hungarian `iClient`, `szName`, `hMenu`, `bFlag`, `fTime`, `eStatus`; globals `g_` + prefix (`g_hDatabase`, `g_szSID`), cvars `g_CVAR_*`; macros/consts `UPPER_SNAKE`, StringMap keys `KEY_*` / `DATA_KEY_*`. +- Functions: `PascalCase` with file prefix: `Clients_*`, `Features_*`, `Storage_*`, `DB_*`, `UTIL_*`, `API_*`. Callbacks: `SQL_Callback_*`, `SQL_TxnCallback_*`, `Timer_*`, `MenuHandler_*`, `Event_*`; commands `*_CMD`; natives `Native_*`; forward wrappers `CreateForward_*`. +- SQL: backticks around identifiers, single quotes for values, trailing `;`, user strings via `g_hDatabase.Format`/`Escape`. Callbacks: `if (hResult == null || szError[0]) { LogError(": %s", szError); return; }`, free the DataPack on every path. +- File-private state/helpers are `static`. Never pass already formatted text as a format string: `PrintToServer("%s", szMsg)`, not `PrintToServer(szMsg)`. + ## Conventions -- `#pragma semicolon 1`, `#pragma newdecls required`. Tabs. Hungarian-ish names: `iClient`, `szName`, `hMenu`, `bFlag`, globals `g_*`, cvars `g_CVAR_*`. - Pass client across async boundaries as userid (`UID`/`CID`), never raw index. - `GLOBAL_TRIE` / `GLOBAL_INFO` are index `[0]` of per-client arrays — slot 0 = global, not a player. - Use methodmaps (`StringMap`, `Cookie`, `KeyValues`, `delete`), not legacy procedural calls (`CreateTrie`, `SetClientCookie`, `CloseHandle`, `Kv*`). - Debug logging via `DBG_*` / `DebugMessage` functions from `Debug.sp` (empty unless `DEBUG_MODE 1`); calls still need a trailing `;`. - Any new user-facing text → new phrase in `vip_core.phrases.txt` with at least `en` and `ru`. -- Comments and cvar descriptions are mostly Russian; either language is fine, keep the surrounding style. +- Comments are Russian or English (cvar descriptions Russian); keep the surrounding style. - Bump `VIP_VERSION` in `VIP_Core.sp` only when asked; changelog lives in `updates.md`. From 79a95c031b3e2fa8f3ff4990d2ba92b630e4c8a7 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:41:37 +0300 Subject: [PATCH 25/46] Fix client reset wiping session flags (spawned, chat input) and VIP_OnClientLoaded never firing for an invalid group or a database error. A status-store wait timer left over from a previous load no longer loads features a second time. --- addons/sourcemod/scripting/vip/Clients.sp | 49 ++++++++++++++--------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index a97a05f..0e1df92 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -1,6 +1,11 @@ +// Bumped on every reset, so timers started for a previous load can tell they are stale +static int g_iClientLoadSerial[MAXPLAYERS+1]; + +// Drops the VIP status only: session flags (spawned, menu open, chat input) stay void ResetClient(int iClient) { - g_iClientInfo[iClient] = IS_LOADED; + g_iClientInfo[iClient] = (g_iClientInfo[iClient] & ~IS_VIP) | IS_LOADED; + ++g_iClientLoadSerial[iClient]; delete g_hFeatures[iClient]; delete g_hFeatureStatus[iClient]; @@ -44,9 +49,8 @@ void Clients_CheckVipAccess(int iClient, bool bNotify = false, bool bForward = f return; } - g_iClientInfo[iClient] &= ~IS_LOADED; - ResetClient(iClient); + g_iClientInfo[iClient] &= ~IS_LOADED; if (IsFakeClient(iClient) == false && (GLOBAL_INFO & IS_STARTED) && g_hDatabase) { @@ -55,11 +59,16 @@ void Clients_CheckVipAccess(int iClient, bool bNotify = false, bool bForward = f } else { - g_iClientInfo[iClient] |= IS_LOADED; - CreateForward_OnClientLoaded(iClient); + Clients_SetLoaded(iClient); } } +void Clients_SetLoaded(int iClient) +{ + g_iClientInfo[iClient] |= IS_LOADED; + CreateForward_OnClientLoaded(iClient); +} + void Clients_LoadClient(int iClient, bool bNotify) { char szQuery[512]; @@ -96,13 +105,6 @@ public void SQL_Callback_OnClientAuthorized(Database hOwner, DBResultSet hResult { DBG_SQL_Response("SQL_Callback_OnClientAuthorized"); DataPack hDataPack = view_as(hPack); - if (hResult == null || szError[0]) - { - LogError("SQL_Callback_OnClientAuthorized: %s", szError); - delete hDataPack; - return; - } - hDataPack.Reset(); int iClient = CID(hDataPack.ReadCell()); @@ -116,6 +118,14 @@ public void SQL_Callback_OnClientAuthorized(Database hOwner, DBResultSet hResult return; } + if (hResult == null || szError[0]) + { + LogError("SQL_Callback_OnClientAuthorized: %s", szError); + // Load the player as non-VIP so modules still get OnClientLoaded + Clients_SetLoaded(iClient); + return; + } + if (hResult.FetchRow()) { DBG_SQL_Response("hResult.FetchRow()"); @@ -147,8 +157,7 @@ public void SQL_Callback_OnClientAuthorized(Database hOwner, DBResultSet hResult DisplayClientInfo(iClient, "expired_info"); - g_iClientInfo[iClient] |= IS_LOADED; - CreateForward_OnClientLoaded(iClient); + Clients_SetLoaded(iClient); return; } @@ -166,9 +175,9 @@ public void SQL_Callback_OnClientAuthorized(Database hOwner, DBResultSet hResult g_hFeatures[iClient].SetString(KEY_GROUP, szGroup); - g_iClientInfo[iClient] |= IS_VIP|IS_LOADED; + g_iClientInfo[iClient] |= IS_VIP; - CreateForward_OnClientLoaded(iClient); + Clients_SetLoaded(iClient); char szName[MAX_NAME_LENGTH*2+1]; hResult.FetchString(2, SZF(szName)); @@ -189,11 +198,12 @@ public void SQL_Callback_OnClientAuthorized(Database hOwner, DBResultSet hResult else { LogError("Invalid VIP-Group/Некорректная VIP-группа: %s (Игрок: %d)", szGroup, iAccountID); + Clients_SetLoaded(iClient); } return; } - CreateForward_OnClientLoaded(iClient); + Clients_SetLoaded(iClient); } void Clients_OnVIPClientLoaded(int iClient) @@ -256,6 +266,7 @@ void Clients_WaitForStatusStore(int iClient, const char[] szFeature, int iAttemp { DataPack hDataPack = new DataPack(); hDataPack.WriteCell(UID(iClient)); + hDataPack.WriteCell(g_iClientLoadSerial[iClient]); hDataPack.WriteCell(iAttempt); hDataPack.WriteString(szFeature); CreateTimer(0.5, Timer_CheckCookies, hDataPack, TIMER_FLAG_NO_MAPCHANGE|TIMER_DATA_HNDL_CLOSE); @@ -266,12 +277,14 @@ public Action Timer_CheckCookies(Handle hTimer, Handle hDP) DataPack hDataPack = view_as(hDP); hDataPack.Reset(); int iClient = CID(hDataPack.ReadCell()); + int iLoadSerial = hDataPack.ReadCell(); int iAttempt = hDataPack.ReadCell(); char szFeature[FEATURE_NAME_LENGTH]; hDataPack.ReadString(SZF(szFeature)); DBG_Clients("Timer_CheckCookies -> iClient: %N (%d), IsClientVIP: %b, attempt: %d", iClient, iClient, view_as(g_iClientInfo[iClient] & IS_VIP), iAttempt); - if (!iClient || !(g_iClientInfo[iClient] & IS_VIP)) + // Reloaded in the meantime: the new load waits on its own timer + if (!iClient || !(g_iClientInfo[iClient] & IS_VIP) || iLoadSerial != g_iClientLoadSerial[iClient]) { return Plugin_Stop; } From c23637ccc8bcdc945baaf0d16acefa0d1f638bce Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:41:58 +0300 Subject: [PATCH 26/46] Fix VIPs who were not yet authorized by Steam on connect never getting VIP for that session. They are loaded as non-VIP without querying account 0, then checked again from OnClientAuthorized (moved to Clients.sp, it also retries storage). --- addons/sourcemod/scripting/vip/Clients.sp | 17 +++++++++++++++++ addons/sourcemod/scripting/vip/Storage.sp | 8 -------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index 0e1df92..e293470 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -23,6 +23,16 @@ public void OnClientPutInServer(int iClient) } } +// Steam authorized the player after OnClientPutInServer: storage and the VIP check were skipped then (no account id yet) +public void OnClientAuthorized(int iClient, const char[] szAuth) +{ + if (IsClientInGame(iClient) && !IsFakeClient(iClient) && !IsClientSourceTV(iClient)) + { + Storage_LoadClient(iClient); + Clients_CheckVipAccess(iClient, true, true); + } +} + public void OnClientDisconnect(int iClient) { /* if (g_bIsClientVIP[iClient]) @@ -77,6 +87,13 @@ void Clients_LoadClient(int iClient, bool bNotify) DBG_Clients("Clients_LoadClient %N (%d), %b: - > %x, %u", iClient, iClient, g_iClientInfo[iClient], g_hDatabase, g_hDatabase); + // Not authorized yet: loaded as non-VIP for now, checked again from OnClientAuthorized + if (!iAccountID) + { + Clients_SetLoaded(iClient); + return; + } + char szWhere[64]; if(g_szSID[0]) { diff --git a/addons/sourcemod/scripting/vip/Storage.sp b/addons/sourcemod/scripting/vip/Storage.sp index 8cb8924..cbaf136 100644 --- a/addons/sourcemod/scripting/vip/Storage.sp +++ b/addons/sourcemod/scripting/vip/Storage.sp @@ -54,14 +54,6 @@ void Storage_LoadClient(int iClient) g_hDatabase.Query(SQL_Callback_OnClientLoadStorage, szQuery, hDataPack); } -public void OnClientAuthorized(int iClient, const char[] szAuth) -{ - if (IsClientInGame(iClient) && !IsFakeClient(iClient)) - { - Storage_LoadClient(iClient); - } -} - public void SQL_Callback_OnClientLoadStorage(Database hOwner, DBResultSet hResult, const char[] szError, any hPack) { DBG_SQL_Response("SQL_Callback_OnClientLoadStorage"); From 67ac19920149e8f955157e91c016a41fbaf213b1 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:42:37 +0300 Subject: [PATCH 27/46] Fix VIP reloads and group changes taking VIP away without toggling features off, which left module effects active. All in-game VIP removals now go through Clients_RemoveVIPStatus (features off, then reset); disconnect still only resets. --- addons/sourcemod/scripting/vip/API.sp | 5 ++--- addons/sourcemod/scripting/vip/AdminMenu.sp | 2 +- addons/sourcemod/scripting/vip/Clients.sp | 12 +++++++++--- addons/sourcemod/scripting/vip/adminmenu/Del.sp | 3 +-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index b0e306c..83b1908 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -614,7 +614,7 @@ int API_GiveClientVIP(Handle hPlugin, int iAdmin, int iClient, int iTime, const g_hFeatures[iClient].GetValue(KEY_CID, iClientID); if (iClientID == -1 && bAddToDB) { - ResetClient(iClient); + Clients_RemoveVIPStatus(iClient); CreateForward_OnVIPClientRemoved(iClient, "Removed for VIP-status change", iAdmin); } @@ -725,8 +725,7 @@ int API_RemoveClientVIP(Handle hPlugin, CancelClientMenu(iClient); } - Features_TurnOffAll(iClient); - ResetClient(iClient); + Clients_RemoveVIPStatus(iClient); char szBuffer[PMP]; FormatEx(SZF(szBuffer), "Removed by %s", szPluginName); diff --git a/addons/sourcemod/scripting/vip/AdminMenu.sp b/addons/sourcemod/scripting/vip/AdminMenu.sp index a3a71dd..f290630 100644 --- a/addons/sourcemod/scripting/vip/AdminMenu.sp +++ b/addons/sourcemod/scripting/vip/AdminMenu.sp @@ -588,7 +588,7 @@ public int MenuHandler_GroupsList(Menu hMenu, MenuAction action, int iClient, in if (iTarget) { - ResetClient(iTarget); + Clients_RemoveVIPStatus(iTarget); CreateForward_OnVIPClientRemoved(iTarget, "VIP-Group Changed", iClient); Clients_CheckVipAccess(iTarget, false); } diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index e293470..8924146 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -11,6 +11,13 @@ void ResetClient(int iClient) delete g_hFeatureStatus[iClient]; } +// Takes the VIP status away from a player in game: modules get their features toggled off first +void Clients_RemoveVIPStatus(int iClient) +{ + Features_TurnOffAll(iClient); + ResetClient(iClient); +} + public void OnClientPutInServer(int iClient) { // g_iClientInfo[iClient] = 0; @@ -59,7 +66,7 @@ void Clients_CheckVipAccess(int iClient, bool bNotify = false, bool bForward = f return; } - ResetClient(iClient); + Clients_RemoveVIPStatus(iClient); g_iClientInfo[iClient] &= ~IS_LOADED; if (IsFakeClient(iClient) == false && (GLOBAL_INFO & IS_STARTED) && g_hDatabase) @@ -590,7 +597,6 @@ public Action Timer_VIP_Expired(Handle hTimer, any UserID) void Clients_ExpiredClient(int iClient) { DBG_Clients("Clients_ExpiredClient %N:", iClient); - Features_TurnOffAll(iClient); int iClientID; g_hFeatures[iClient].GetValue(KEY_EXPIRES, iClientID); @@ -612,7 +618,7 @@ void Clients_ExpiredClient(int iClient) CancelClientMenu(iClient); } - ResetClient(iClient); + Clients_RemoveVIPStatus(iClient); CreateForward_OnVIPClientRemoved(iClient, "Expired"); diff --git a/addons/sourcemod/scripting/vip/adminmenu/Del.sp b/addons/sourcemod/scripting/vip/adminmenu/Del.sp index 85d6fea..40be6e1 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/Del.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/Del.sp @@ -52,8 +52,7 @@ public int MenuHandler_DeleteVipPlayerMenu(Menu hMenu, MenuAction action, int iC if (iTarget) { DB_RemoveClientFromID(iClient, iTarget, _, true); - Features_TurnOffAll(iTarget); - ResetClient(iTarget); + Clients_RemoveVIPStatus(iTarget); CreateForward_OnVIPClientRemoved(iTarget, "Removed by Admin", iClient); DisplayClientInfo(iTarget, "expired_info"); BackToAdminMenu(iClient); From bcf6621d036c591cab3e67ac69ddf4aae3e201a4 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:42:57 +0300 Subject: [PATCH 28/46] Fix storage changes (feature statuses) being lost when the plugin is unloaded or reloaded. OnPluginEnd now saves every changed client synchronously, since async queries may never run at that point. --- addons/sourcemod/scripting/vip/Storage.sp | 44 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Storage.sp b/addons/sourcemod/scripting/vip/Storage.sp index cbaf136..a09ad49 100644 --- a/addons/sourcemod/scripting/vip/Storage.sp +++ b/addons/sourcemod/scripting/vip/Storage.sp @@ -90,7 +90,8 @@ public void SQL_Callback_OnClientLoadStorage(Database hOwner, DBResultSet hResul CreateForward_OnClientStorageLoaded(iClient); } -void Storage_SaveClient(int iClient) +// bSync: write right away (plugin unload, where async queries may never run) +void Storage_SaveClient(int iClient, bool bSync = false) { if (!Storage_IsClientLoaded(iClient) || !g_bStorageChanged[iClient] || !g_hDatabase) { @@ -101,7 +102,16 @@ void Storage_SaveClient(int iClient) int iUpdated = GetTime(); char szKey[STORAGE_KEY_LENGTH], szValue[STORAGE_VALUE_LENGTH], szQuery[1024]; - Transaction hTxn = new Transaction(); + Transaction hTxn = null; + if (bSync) + { + SQL_LockDatabase(g_hDatabase); + } + else + { + hTxn = new Transaction(); + } + StringMapSnapshot hSnapshot = g_hStorage[iClient].Snapshot(); for (int i = 0, iSize = hSnapshot.Length; i < iSize; ++i) { @@ -124,11 +134,29 @@ void Storage_SaveClient(int iClient) } DBG_SQL_Query(szQuery); - hTxn.AddQuery(szQuery); + if (bSync) + { + if (!SQL_FastQuery(g_hDatabase, szQuery)) + { + SQL_GetError(g_hDatabase, SZF(szQuery)); + LogError("Storage_SaveClient: %s", szQuery); + } + } + else + { + hTxn.AddQuery(szQuery); + } } delete hSnapshot; - g_hDatabase.Execute(hTxn, _, SQL_TxnCallback_SaveStorageFailure); + if (bSync) + { + SQL_UnlockDatabase(g_hDatabase); + } + else + { + g_hDatabase.Execute(hTxn, _, SQL_TxnCallback_SaveStorageFailure); + } g_bStorageChanged[iClient] = false; } @@ -137,6 +165,14 @@ public void SQL_TxnCallback_SaveStorageFailure(Database hDatabase, any data, int LogError("SQL_TxnCallback_SaveStorageFailure (%d/%d): %s", iFailIndex, iNumQueries, szError); } +public void OnPluginEnd() +{ + for (int iClient = 1; iClient <= MaxClients; ++iClient) + { + Storage_SaveClient(iClient, true); + } +} + void Storage_ResetClient(int iClient) { delete g_hStorage[iClient]; From 9aefd08bf6acaaabdb62d64464f77e736af49905 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:43:25 +0300 Subject: [PATCH 29/46] Fix VIP_RegisterFeature throwing for modules built against an older include with fewer params, which left the feature half-registered. The optional default status and cookie flag are now read only when passed, before anything is registered. --- addons/sourcemod/scripting/vip/API.sp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 83b1908..ac1d887 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -851,14 +851,16 @@ public int Native_RegisterFeature(Handle hPlugin, int iNumParams) g_hVIPMenu.RemoveItem(0); } - g_hFeaturesArray.PushString(szFeature); VIP_ValueType eValType = view_as(GetNativeCell(2)); VIP_FeatureType eType = view_as(GetNativeCell(3)); Function ItemSelectCallback = GetNativeFunction(4); Function ItemDisplayCallback = GetNativeFunction(5); Function ItemDrawCallback = GetNativeFunction(6); - VIP_ToggleState eDefStatus = view_as(GetNativeCell(7)); - bool bCookie = view_as(GetNativeCell(8)); + // Modules built against an older include pass fewer params + VIP_ToggleState eDefStatus = iNumParams > 6 ? view_as(GetNativeCell(7)) : NO_ACCESS; + bool bCookie = iNumParams > 7 && view_as(GetNativeCell(8)); + + g_hFeaturesArray.PushString(szFeature); DBG_API("PushArrayString -> %i", g_hFeaturesArray.FindString(szFeature)); DBG_API("VIP_ValueType -> %s", g_szValueType[view_as(eValType)]); @@ -879,7 +881,7 @@ public int Native_RegisterFeature(Handle hPlugin, int iNumParams) if (eType != HIDE) { Cookie hCookie = null; - bool bRegisterCookie = (eType == SELECTABLE && iNumParams > 7 && (bCookie != false)); + bool bRegisterCookie = (eType == SELECTABLE && bCookie); if (eType == TOGGLABLE || bRegisterCookie) { hCookie = new Cookie(szFeature, szFeature, CookieAccess_Private); @@ -895,7 +897,7 @@ public int Native_RegisterFeature(Handle hPlugin, int iNumParams) if (eType == TOGGLABLE) { - hArray.Push(iNumParams > 6 ? eDefStatus : NO_ACCESS); + hArray.Push(eDefStatus); } AddFeatureToVIPMenu(szFeature); From 4a8f53f1a3dd07cb85aaf6aae00af77e4cb4a7ec Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:43:56 +0300 Subject: [PATCH 30/46] Fix sm_addvip accepting durations that overflow an int (e.g. 99999 days) or an unknown time mode, which wrote garbage expiry dates. UTIL_TimeToSeconds now returns -1 on overflow, and sm_addvip validates the converted duration and the resulting expiry. --- addons/sourcemod/scripting/vip/CMD.sp | 9 +++++---- addons/sourcemod/scripting/vip/UTIL.sp | 20 ++++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/addons/sourcemod/scripting/vip/CMD.sp b/addons/sourcemod/scripting/vip/CMD.sp index 1eba254..18a7e27 100644 --- a/addons/sourcemod/scripting/vip/CMD.sp +++ b/addons/sourcemod/scripting/vip/CMD.sp @@ -87,8 +87,9 @@ public Action AddVIP_CMD(int iClient, int iArgs) char szGroup[64]; GetCmdArg(3, SZF(szGroup)); - int iTime = StringToInt(szGroup); - if (iTime < 0) + // Duration in seconds; the expiry date (now + duration) must fit into an int too + int iTime = UTIL_TimeToSeconds(StringToInt(szGroup)); + if (iTime < 0 || iTime > cellmax - GetTime()) { ReplyToCommand(iClient, "[VIP] %t", "INCORRECT_TIME"); return Plugin_Handled; @@ -114,14 +115,14 @@ public Action AddVIP_CMD(int iClient, int iArgs) continue; } - UTIL_ADD_VIP_PLAYER(iClient, iTargetList[i], _, UTIL_TimeToSeconds(iTime), szGroup); + UTIL_ADD_VIP_PLAYER(iClient, iTargetList[i], _, iTime, szGroup); } } return Plugin_Handled; } - UTIL_ADD_VIP_PLAYER(iClient, _, iAccountID, UTIL_TimeToSeconds(iTime), szGroup); + UTIL_ADD_VIP_PLAYER(iClient, _, iAccountID, iTime, szGroup); return Plugin_Handled; } diff --git a/addons/sourcemod/scripting/vip/UTIL.sp b/addons/sourcemod/scripting/vip/UTIL.sp index e9ef0ef..4ddea73 100644 --- a/addons/sourcemod/scripting/vip/UTIL.sp +++ b/addons/sourcemod/scripting/vip/UTIL.sp @@ -32,17 +32,25 @@ stock int UTIL_ReplaceChars(char[] szBuffer, int InChar, int OutChar) return iNum; } +// Returns -1 for an unknown time mode or a value that doesn't fit into an int int UTIL_TimeToSeconds(int iTime) { + int iMultiplier; switch (g_CVAR_iTimeMode) { - case TIME_MODE_SECONDS:return iTime; - case TIME_MODE_MINUTES:return iTime * 60; - case TIME_MODE_HOURS:return iTime * 3600; - case TIME_MODE_DAYS:return iTime * 86400; + case TIME_MODE_SECONDS: iMultiplier = 1; + case TIME_MODE_MINUTES: iMultiplier = 60; + case TIME_MODE_HOURS: iMultiplier = 3600; + case TIME_MODE_DAYS: iMultiplier = 86400; + default: return -1; } - - return -1; + + if (iTime > cellmax / iMultiplier || iTime < -(cellmax / iMultiplier)) + { + return -1; + } + + return iTime * iMultiplier; } int UTIL_SecondsToTime(int iTime) From 70e41d8c44186031d62cae2b5b8bd834d33374db Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:44:08 +0300 Subject: [PATCH 31/46] Fix VIP_GetClientFeatureBool returning true for a BOOL feature given the value "0" via VIP_GiveClientFeature. It now returns the stored value, not whether the key exists. --- addons/sourcemod/scripting/vip/API.sp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index ac1d887..778c486 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -1200,7 +1200,8 @@ public int Native_GetClientFeatureBool(Handle hPlugin, int iNumParams) bool bValue; GetNativeString(2, SZF(szFeature)); - return g_hFeatures[iClient].GetValue(szFeature, bValue); + // VIP_GiveClientFeature can store false, so the key existing is not enough + return g_hFeatures[iClient].GetValue(szFeature, bValue) && bValue; } return false; From bf8aec775c16a6494a065cb25d1882b0356c0a92 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:44:28 +0300 Subject: [PATCH 32/46] Fix the 3-second flood guard on the VIP menu command never triggering, because the last-use time was never stored. It now uses engine time, which unlike game time does not restart on map change. --- addons/sourcemod/scripting/vip/VipMenu.sp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/addons/sourcemod/scripting/vip/VipMenu.sp b/addons/sourcemod/scripting/vip/VipMenu.sp index d36c2ae..142c5e1 100644 --- a/addons/sourcemod/scripting/vip/VipMenu.sp +++ b/addons/sourcemod/scripting/vip/VipMenu.sp @@ -325,15 +325,14 @@ bool IsValidFeature(const char[] szFeature) bool OnVipMenuFlood(int iClient) { + // Engine time, not game time: game time restarts on map change static float fLastTime[MAXPLAYERS + 1]; - if (fLastTime[iClient] > 0.0) + float fNow = GetEngineTime(); + if (fLastTime[iClient] > 0.0 && (fNow - fLastTime[iClient]) < 3.0) { - float fSec = GetGameTime(); - if ((fSec - fLastTime[iClient]) < 3.0) - { - return true; - } - fLastTime[iClient] = fSec; + return true; } + + fLastTime[iClient] = fNow; return false; } \ No newline at end of file From 0f2de4d1c398252766c8447885a669f641961079 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:45:08 +0300 Subject: [PATCH 33/46] Fix a KeyValues handle leaking each time a module blocks an info message (Plugin_Stop/Plugin_Handled) in VIP_OnShowClientInfo. With Plugin_Changed the module's edited copy is now actually shown instead of the config. --- addons/sourcemod/scripting/vip/Info.sp | 32 ++++++++++++++------------ 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Info.sp b/addons/sourcemod/scripting/vip/Info.sp index 15a1761..5b2f5ed 100644 --- a/addons/sourcemod/scripting/vip/Info.sp +++ b/addons/sourcemod/scripting/vip/Info.sp @@ -28,12 +28,14 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] g_hInfo.Rewind(); if (g_hInfo.JumpToKey(szEvent) && g_hInfo.JumpToKey(szType)) { + // Modules get a copy; with Plugin_Changed the (edited) copy is shown, otherwise the config itself KeyValues hKeyValues = new KeyValues(szType); hKeyValues.Import(g_hInfo); switch(CreateForward_OnShowClientInfo(iClient, szEvent, szType, hKeyValues)) { case Plugin_Stop, Plugin_Handled: { + delete hKeyValues; return; } case Plugin_Continue: @@ -49,7 +51,7 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] case 'c': { DebugMessage("case 'c'"); - if (KvGetLangString(szBuffer, iBufLen, szClientLang, szServLang)) + if (KvGetLangString(hKeyValues, szBuffer, iBufLen, szClientLang, szServLang)) { DebugMessage("KvGetLangString: (%s, %s) = '%s'", szClientLang, szServLang, szBuffer); ReplaceString(szBuffer, iBufLen, "\\n", " \n"); @@ -64,12 +66,12 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] { DebugMessage("case 'm'"); - int iTime = g_hInfo.GetNum("time", 0); - if (!g_hInfo.JumpToKey(szClientLang)) + int iTime = hKeyValues.GetNum("time", 0); + if (!hKeyValues.JumpToKey(szClientLang)) { - if (!g_hInfo.JumpToKey(szServLang)) + if (!hKeyValues.JumpToKey(szServLang)) { - if (!g_hInfo.GotoFirstSubKey()) + if (!hKeyValues.GotoFirstSubKey()) { if(hKeyValues != g_hInfo) { @@ -81,19 +83,19 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] } DebugMessage("KvJumpToKey: (%s|%s)", szClientLang, szServLang); - if (g_hInfo.GotoFirstSubKey(false)) + if (hKeyValues.GotoFirstSubKey(false)) { DebugMessage("KvGotoFirstSubKey"); Panel hPanel = new Panel(); do { - g_hInfo.GetSectionName(szBuffer, 32); + hKeyValues.GetSectionName(szBuffer, 32); if (strcmp(szBuffer, "item")) { continue; } - g_hInfo.GetString(NULL_STRING, szBuffer, 128); + hKeyValues.GetString(NULL_STRING, szBuffer, 128); DebugMessage("KvGetString = '%s'", szBuffer); if (szBuffer[0]) { @@ -109,13 +111,13 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] } hPanel.DrawText(szBuffer); } - } while (g_hInfo.GotoNextKey(false)); + } while (hKeyValues.GotoNextKey(false)); - g_hInfo.GoBack(); + hKeyValues.GoBack(); hPanel.DrawText(" \n"); - hPanel.CurrentKey = g_hInfo.GetNum("exit_button", g_iMaxPageItems); + hPanel.CurrentKey = hKeyValues.GetNum("exit_button", g_iMaxPageItems); FormatEx(szBuffer, 128, "%T", "Exit", iClient); hPanel.DrawItem(szBuffer, ITEMDRAW_CONTROL); @@ -129,7 +131,7 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] DebugMessage("case 'u'"); if (g_EngineVersion != Engine_CSGO) { - if (KvGetLangString(szBuffer, iBufLen, szClientLang, szServLang)) + if (KvGetLangString(hKeyValues, szBuffer, iBufLen, szClientLang, szServLang)) { DebugMessage("KvGetLangString: (%s, %s) = '%s'", szClientLang, szServLang, szBuffer); if (strncmp(szBuffer, "http://", 7, true) != 0) @@ -150,14 +152,14 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] } } -bool KvGetLangString(char[] szBuffer, int iBufLen, char[] szClientLang, char[] szServLang) +bool KvGetLangString(KeyValues hKeyValues, char[] szBuffer, int iBufLen, char[] szClientLang, char[] szServLang) { DebugMessage("KvGetLangString: '%s', '%s'", szClientLang, szServLang); - g_hInfo.GetString(szClientLang, szBuffer, iBufLen); + hKeyValues.GetString(szClientLang, szBuffer, iBufLen); DebugMessage("KvGetString (%s) = '%s'", szClientLang, szBuffer); if (!szBuffer[0]) { - g_hInfo.GetString(szServLang, szBuffer, iBufLen); + hKeyValues.GetString(szServLang, szBuffer, iBufLen); DebugMessage("KvGetString (%s) = '%s'", szServLang, szBuffer); if (!szBuffer[0]) { From f5735e6aff9def361886d97eac69f06de89f14fa Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:45:08 +0300 Subject: [PATCH 34/46] Fix https:// info URLs being turned into http://https://... in the MOTD panel. A scheme is now only added when the URL has none. --- addons/sourcemod/scripting/vip/Info.sp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/scripting/vip/Info.sp b/addons/sourcemod/scripting/vip/Info.sp index 5b2f5ed..4ae8438 100644 --- a/addons/sourcemod/scripting/vip/Info.sp +++ b/addons/sourcemod/scripting/vip/Info.sp @@ -134,9 +134,10 @@ void DisplayInfo(int iClient, const char[] szEvent, const char[] szType, char[] if (KvGetLangString(hKeyValues, szBuffer, iBufLen, szClientLang, szServLang)) { DebugMessage("KvGetLangString: (%s, %s) = '%s'", szClientLang, szServLang, szBuffer); - if (strncmp(szBuffer, "http://", 7, true) != 0) + // Only add a scheme when there is none (keeps https:// links intact) + if (StrContains(szBuffer, "://") == -1) { - Format(szBuffer, 256, "http://%s", szBuffer); + Format(szBuffer, iBufLen, "http://%s", szBuffer); } ShowMOTDPanel(iClient, "VIP_INFO", szBuffer, MOTDPANEL_TYPE_URL); From f552bc74eb4e25978e05794481544fee5028cdfe Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:46:16 +0300 Subject: [PATCH 35/46] Fix the cookie handle of a SELECTABLE feature registered with bCookie leaking on unregister. --- addons/sourcemod/scripting/vip/API.sp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 778c486..489e5ec 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -983,13 +983,10 @@ public int Native_UnregisterMe(Handle hPlugin, int iNumParams) void UnregisterFeature(const char[] szFeature, ArrayList hArray) { VIP_FeatureType eType = view_as(hArray.Get(FEATURES_ITEM_TYPE)); - if (eType == TOGGLABLE) - { - delete view_as(hArray.Get(FEATURES_COOKIE)); - } - if (eType != HIDE) { + // TOGGLABLE and SELECTABLE (bCookie) features own a cookie handle, others store null + delete view_as(hArray.Get(FEATURES_COOKIE)); delete view_as(hArray.Get(FEATURES_MENU_CALLBACKS)); } From 0e4573c7138a8702db8eb8e0b45242a3a7e9ffd1 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:46:30 +0300 Subject: [PATCH 36/46] Fix UTIL_GetAccountIDFromSteamID turning malformed STEAM_ ids (STEAM_ID_PENDING, truncated input) into garbage account ids used by sm_addvip, sm_delvip and the admin search. The STEAM_X:Y:Z format is now validated, otherwise 0 is returned. --- addons/sourcemod/scripting/vip/UTIL.sp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/UTIL.sp b/addons/sourcemod/scripting/vip/UTIL.sp index 4ddea73..4551b29 100644 --- a/addons/sourcemod/scripting/vip/UTIL.sp +++ b/addons/sourcemod/scripting/vip/UTIL.sp @@ -175,9 +175,23 @@ stock int UTIL_SearchCharInString(const char[] szBuffer, int c) int UTIL_GetAccountIDFromSteamID(const char[] szSteamID) { + // STEAM_X:Y:Z, Y is 0 or 1, Z is digits (rejects STEAM_ID_PENDING, STEAM_ID_LAN, truncated input) if (!strncmp(szSteamID, "STEAM_", 6)) { - return S2I(szSteamID[10]) << 1 | (szSteamID[8] - 48); + if (strlen(szSteamID) < 11 || szSteamID[7] != ':' || (szSteamID[8] != '0' && szSteamID[8] != '1') || szSteamID[9] != ':') + { + return 0; + } + + for (int i = 10; szSteamID[i]; ++i) + { + if (!IsCharNumeric(szSteamID[i])) + { + return 0; + } + } + + return S2I(szSteamID[10]) << 1 | (szSteamID[8] - '0'); } if (!strncmp(szSteamID, "[U:1:", 5) && szSteamID[strlen(szSteamID)-1] == ']') From 7f59152985b7a8b77f1b6811d1cea78d51f703ad Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:46:52 +0300 Subject: [PATCH 37/46] Fix VIP_GetDatabase throwing when called before the core has connected to the database. It now returns null in that case, as documented in the include. --- addons/sourcemod/scripting/include/vip_core.inc | 2 +- addons/sourcemod/scripting/vip/API.sp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/include/vip_core.inc b/addons/sourcemod/scripting/include/vip_core.inc index 2c5b11b..c2e8ade 100644 --- a/addons/sourcemod/scripting/include/vip_core.inc +++ b/addons/sourcemod/scripting/include/vip_core.inc @@ -654,7 +654,7 @@ native bool VIP_RemoveClientFeature(int iClient, const char[] szFeature); * * @noparams * - * @return Database (Handle) базы данных + * @return Database (Handle) базы данных или null, если ядро ещё не подключилось к БД (до VIP_OnVIPLoaded) */ native Database VIP_GetDatabase(); diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 489e5ec..9019895 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -1334,6 +1334,12 @@ public int Native_RemoveClientFeature(Handle hPlugin, int iNumParams) public int Native_GetDatabase(Handle hPlugin, int iNumParams) { + // Not connected yet (before VIP_OnVIPLoaded): CloneHandle would throw on null + if (!g_hDatabase) + { + return 0; + } + return view_as(CloneHandle(g_hDatabase, hPlugin)); } From 281c7b65c890eb9be227325cad7650c307ac1332 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:49:05 +0300 Subject: [PATCH 38/46] Re-check VIP access from OnClientAuthorized only for players that were loaded before Steam authorized them, so an already loaded player is never loaded twice. --- addons/sourcemod/scripting/vip/Clients.sp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index 8924146..d2ab91b 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -1,5 +1,7 @@ // Bumped on every reset, so timers started for a previous load can tell they are stale static int g_iClientLoadSerial[MAXPLAYERS+1]; +// Loaded before Steam authorized the player: OnClientAuthorized has to check again +static bool g_bClientWaitsForAuth[MAXPLAYERS+1]; // Drops the VIP status only: session flags (spawned, menu open, chat input) stay void ResetClient(int iClient) @@ -33,8 +35,9 @@ public void OnClientPutInServer(int iClient) // Steam authorized the player after OnClientPutInServer: storage and the VIP check were skipped then (no account id yet) public void OnClientAuthorized(int iClient, const char[] szAuth) { - if (IsClientInGame(iClient) && !IsFakeClient(iClient) && !IsClientSourceTV(iClient)) + if (g_bClientWaitsForAuth[iClient] && IsClientInGame(iClient)) { + g_bClientWaitsForAuth[iClient] = false; Storage_LoadClient(iClient); Clients_CheckVipAccess(iClient, true, true); } @@ -57,6 +60,7 @@ public void OnClientDisconnect(int iClient) ResetClient(iClient); delete g_hClientData[iClient]; g_iClientInfo[iClient] = 0; + g_bClientWaitsForAuth[iClient] = false; } void Clients_CheckVipAccess(int iClient, bool bNotify = false, bool bForward = false) @@ -97,6 +101,7 @@ void Clients_LoadClient(int iClient, bool bNotify) // Not authorized yet: loaded as non-VIP for now, checked again from OnClientAuthorized if (!iAccountID) { + g_bClientWaitsForAuth[iClient] = true; Clients_SetLoaded(iClient); return; } From 3dba0f4cdd5582684367c3753f828f2fe8657407 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:49:05 +0300 Subject: [PATCH 39/46] Document that VIP_TimeToSeconds returns -1 for an unknown time mode or a result that does not fit into an int. --- addons/sourcemod/scripting/include/vip_core.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/sourcemod/scripting/include/vip_core.inc b/addons/sourcemod/scripting/include/vip_core.inc index c2e8ade..9fa83c7 100644 --- a/addons/sourcemod/scripting/include/vip_core.inc +++ b/addons/sourcemod/scripting/include/vip_core.inc @@ -674,6 +674,7 @@ native bool VIP_GetDatabaseType(); * @param iTime Время. * * @return Возвращает количество секунд в зависимости от sm_vip_time_mode. + * -1 - неизвестный sm_vip_time_mode или результат не помещается в int. */ native int VIP_TimeToSeconds(int iTime); From 4c5b43f389d95aa73e9d2e8146f9227ab2d36987 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:53:06 +0300 Subject: [PATCH 40/46] Add comment rules to AGENTS.md, based on the Stack Overflow blog's best practices for code comments: explain why, not what, and comment only what a reader would trip over. --- AGENTS.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 62e2ed7..2f29f8e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,14 +16,12 @@ - `vip/Global.sp` — all globals, macros (`SZF`, `UID/CID`, `SET_BIT/UNSET_BIT`), client flag bits (`IS_VIP`, `IS_LOADED`, ...), core state bits (`IS_STARTED`, `IS_MySQL`, `IS_LOADING`). - `vip/API.sp` — forwards (`API_SetupForwards`, `CreateForward_*`) and natives (`AskPluginLoad2`, `RegNative(X)` → `VIP_X` bound to `Native_X`). - `vip/Database.sp` — connection, schema, queries. `UTIL.sp` — shared helpers; check here before writing new ones. `Clients.sp` — client load/unload lifecycle, spawn. `Features.sp` — feature registry. -- `include/vip_core.inc` — PUBLIC API for third-party modules. -- `data/vip/cfg/*.ini` — runtime configs (groups, info, sort_menu, times). `translations/vip_core.phrases.txt` — all user-facing strings. +- `include/vip_core.inc` — PUBLIC API for third-party modules. `data/vip/cfg/*.ini` — runtime configs (groups, info, sort_menu, times). `translations/vip_core.phrases.txt` — all user-facing strings. ## Public API rules (vip_core.inc) -- Third-party modules depend on it: never change existing native/forward signatures or semantics. Add new params with defaults or add new natives. +- Third-party modules depend on it: never change existing native/forward signatures or semantics. Add new params with defaults or add new natives. Deprecate via `#pragma deprecated`, don't delete (see `update_modules.md`). - New native: implement `Native_X` in API.sp, `RegNative(X)` in `AskPluginLoad2`, declare in `.inc`, add `MarkNativeAsOptional("VIP_X")` in `__pl_vip_core_SetNTVOptional`. - New forward: `static GlobalForward` field + `new GlobalForward(...)` in `API_SetupForwards` + `CreateForward_*` wrapper + `forward` declaration with doc comment in `.inc`. -- Deprecate via `#pragma deprecated`, don't delete (see `update_modules.md`). ## Database - Config name `vip_core` in databases.cfg → MySQL; otherwise falls back to local SQLite. Every query must work on BOTH. @@ -40,11 +38,13 @@ - SQL: backticks around identifiers, single quotes for values, trailing `;`, user strings via `g_hDatabase.Format`/`Escape`. Callbacks: `if (hResult == null || szError[0]) { LogError(": %s", szError); return; }`, free the DataPack on every path. - File-private state/helpers are `static`. Never pass already formatted text as a format string: `PrintToServer("%s", szMsg)`, not `PrintToServer(szMsg)`. +## Comments +- Explain *why*, never restate *what* the code does. A comment doesn't excuse unclear code: rename/restructure first; if a clear comment is hard to write, the code is the problem. +- Comment what a reader would trip over: unidiomatic code, SourceMod quirks, ordering constraints, odd-looking bug fixes. Mark unfinished parts `// TODO: `. Link the source of copied code and external refs (SM docs, AlliedModders) where they help. Russian or English, match the surroundings (cvar descriptions Russian). + ## Conventions - Pass client across async boundaries as userid (`UID`/`CID`), never raw index. - `GLOBAL_TRIE` / `GLOBAL_INFO` are index `[0]` of per-client arrays — slot 0 = global, not a player. - Use methodmaps (`StringMap`, `Cookie`, `KeyValues`, `delete`), not legacy procedural calls (`CreateTrie`, `SetClientCookie`, `CloseHandle`, `Kv*`). - Debug logging via `DBG_*` / `DebugMessage` functions from `Debug.sp` (empty unless `DEBUG_MODE 1`); calls still need a trailing `;`. -- Any new user-facing text → new phrase in `vip_core.phrases.txt` with at least `en` and `ru`. -- Comments are Russian or English (cvar descriptions Russian); keep the surrounding style. -- Bump `VIP_VERSION` in `VIP_Core.sp` only when asked; changelog lives in `updates.md`. +- Any new user-facing text → new phrase in `vip_core.phrases.txt` with at least `en` and `ru`. Bump `VIP_VERSION` in `VIP_Core.sp` only when asked; changelog lives in `updates.md`. From fbbf801495eab1ee9c184fc77f4bcf254cabefcf Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 20:53:06 +0300 Subject: [PATCH 41/46] Remove or trim comments that only restated the code, per the new comment rules. --- addons/sourcemod/scripting/vip/API.sp | 1 - addons/sourcemod/scripting/vip/CMD.sp | 2 +- addons/sourcemod/scripting/vip/Clients.sp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 9019895..43b3f4b 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -272,7 +272,6 @@ public APLRes AskPluginLoad2(Handle myself, bool bLate, char[] szError, int err_ RegNative(AddGroup); RegNative(RemoveGroup); - // Storage RegNative(SetClientStorageValue); RegNative(GetClientStorageValue); diff --git a/addons/sourcemod/scripting/vip/CMD.sp b/addons/sourcemod/scripting/vip/CMD.sp index 18a7e27..103828f 100644 --- a/addons/sourcemod/scripting/vip/CMD.sp +++ b/addons/sourcemod/scripting/vip/CMD.sp @@ -87,7 +87,7 @@ public Action AddVIP_CMD(int iClient, int iArgs) char szGroup[64]; GetCmdArg(3, SZF(szGroup)); - // Duration in seconds; the expiry date (now + duration) must fit into an int too + // The expiry date (now + duration) must fit into an int too int iTime = UTIL_TimeToSeconds(StringToInt(szGroup)); if (iTime < 0 || iTime > cellmax - GetTime()) { diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index d2ab91b..ab5ee37 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -263,7 +263,7 @@ public void OnClientCookiesCached(int iClient) } #endif -// ponytail: fixed retry window, statuses fall back to defaults if the store isn't ready by then +// Fixed retry window: if the store isn't ready by then, statuses fall back to defaults #define STATUS_STORE_WAIT_ATTEMPTS 120 // x 0.5s void Clients_LoadVIPFeaturesPre(int iClient, const char[] szFeature = NULL_STRING) From 12554db03a35288207adfd78067969c8a3c04672 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 21:04:20 +0300 Subject: [PATCH 42/46] Replace the hardcoded Russian "Show more" item in the admin VIP list with a new SHOW_MORE phrase (en/ru/ua). Also translate the remaining Russian comments in List.sp. --- addons/sourcemod/scripting/vip/adminmenu/List.sp | 15 ++++++++------- .../sourcemod/translations/vip_core.phrases.txt | 6 ++++++ issue_info.md => docs/issue_labels.md | 0 update_modules.md => docs/update_modules.md | 0 4 files changed, 14 insertions(+), 7 deletions(-) rename issue_info.md => docs/issue_labels.md (100%) rename update_modules.md => docs/update_modules.md (100%) diff --git a/addons/sourcemod/scripting/vip/adminmenu/List.sp b/addons/sourcemod/scripting/vip/adminmenu/List.sp index 9e87891..0d194b2 100644 --- a/addons/sourcemod/scripting/vip/adminmenu/List.sp +++ b/addons/sourcemod/scripting/vip/adminmenu/List.sp @@ -67,14 +67,14 @@ public int MenuHandler_VipPlayersListMenu(Menu hMenu, MenuAction action, int iCl char szUserID[16]; hMenu.GetItem(iItem, SZF(szUserID)); - if (!strcmp(szUserID, "search")) // Найти игрока + if (!strcmp(szUserID, "search")) // Search for a player { ShowWaitSearchMenu(iClient); return 0; } - if (!strcmp(szUserID, "show_all")) // Показать всех + if (!strcmp(szUserID, "show_all")) // Show all { g_hClientData[iClient].Remove(DATA_KEY_Search); ShowVipPlayersFromDBMenu(iClient); @@ -82,7 +82,7 @@ public int MenuHandler_VipPlayersListMenu(Menu hMenu, MenuAction action, int iCl return 0; } - if (!strcmp(szUserID, "more")) // Показать еще + if (!strcmp(szUserID, "more")) // Show more { int iOffset; g_hClientData[iClient].GetValue(DATA_KEY_Offset, iOffset); @@ -306,7 +306,8 @@ public void SQL_Callback_SelectVipPlayers(Database hOwner, DBResultSet hResult, if (hResult.RowCount == LIST_OFFSET) { hMenu.AddItem(NULL_STRING, "ITEMDRAW_SPACER", ITEMDRAW_SPACER); - hMenu.AddItem("more", "Показать еще"); + FormatEx(SZF(szName), "%T", "SHOW_MORE", iClient); + hMenu.AddItem("more", szName); } hMenu.Display(iClient, MENU_TIME_FOREVER); @@ -442,11 +443,11 @@ void ShowTargetInfoMenu(int iClient) hMenu.ExitBackButton = true; hMenu.SetTitle("%T\n ", "MENU_INFO_VIP_PLAYER", iClient, szName, szAuth, szGroup, szBuffer); - FormatEx(SZF(szBuffer), "%T", "MENU_DEL_VIP", iClient); // 1. Удалить игрока + FormatEx(SZF(szBuffer), "%T", "MENU_DEL_VIP", iClient); // 1. Delete player hMenu.AddItem(NULL_STRING, szBuffer); - FormatEx(SZF(szBuffer), "%T", "MENU_EDIT_TIME", iClient); // 2. Изменить срок + FormatEx(SZF(szBuffer), "%T", "MENU_EDIT_TIME", iClient); // 2. Change expiry hMenu.AddItem(NULL_STRING, szBuffer, iClientID == -1 ? ITEMDRAW_DISABLED:ITEMDRAW_DEFAULT); - FormatEx(SZF(szBuffer), "%T", "MENU_EDIT_GROUP", iClient); // 3. Изменить группу + FormatEx(SZF(szBuffer), "%T", "MENU_EDIT_GROUP", iClient); // 3. Change group hMenu.AddItem(NULL_STRING, szBuffer); ReductionMenu(hMenu, 2); diff --git a/addons/sourcemod/translations/vip_core.phrases.txt b/addons/sourcemod/translations/vip_core.phrases.txt index c68ccdf..deb89b7 100644 --- a/addons/sourcemod/translations/vip_core.phrases.txt +++ b/addons/sourcemod/translations/vip_core.phrases.txt @@ -840,6 +840,12 @@ "pt" "Mostrar todos jogadores" "ru" "Показать всех" } + "SHOW_MORE" + { + "en" "Show more" + "ru" "Показать еще" + "ua" "Показати ще" + } "y." { "en" "y." diff --git a/issue_info.md b/docs/issue_labels.md similarity index 100% rename from issue_info.md rename to docs/issue_labels.md diff --git a/update_modules.md b/docs/update_modules.md similarity index 100% rename from update_modules.md rename to docs/update_modules.md From 613e80c49b737d2f19ccf601ff54f6be59c586a6 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 21:04:20 +0300 Subject: [PATCH 43/46] Translate all Russian comments, cvar descriptions, error/log messages, vip_core.inc docs and the changelog to English. Other languages now live only in translation files and per-language config values; AGENTS.md states the rule. --- AGENTS.md | 6 +- addons/sourcemod/data/vip/cfg/groups.ini | 4 +- addons/sourcemod/data/vip/cfg/info.ini | 50 +- addons/sourcemod/data/vip/cfg/times.ini | 4 +- addons/sourcemod/scripting/VIP_Core.sp | 8 +- .../sourcemod/scripting/include/vip_core.inc | 564 +++++++++--------- addons/sourcemod/scripting/vip/API.sp | 28 +- addons/sourcemod/scripting/vip/Clients.sp | 4 +- addons/sourcemod/scripting/vip/Cvars.sp | 26 +- addons/sourcemod/scripting/vip/Global.sp | 12 +- addons/sourcemod/scripting/vip/Info.sp | 8 +- updates.md | 288 ++++----- 12 files changed, 501 insertions(+), 501 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2f29f8e..6cc2945 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ - `include/vip_core.inc` — PUBLIC API for third-party modules. `data/vip/cfg/*.ini` — runtime configs (groups, info, sort_menu, times). `translations/vip_core.phrases.txt` — all user-facing strings. ## Public API rules (vip_core.inc) -- Third-party modules depend on it: never change existing native/forward signatures or semantics. Add new params with defaults or add new natives. Deprecate via `#pragma deprecated`, don't delete (see `update_modules.md`). +- Third-party modules depend on it: never change existing native/forward signatures or semantics. Add new params with defaults or add new natives. Deprecate via `#pragma deprecated`, don't delete (see `docs/update_modules.md`). - New native: implement `Native_X` in API.sp, `RegNative(X)` in `AskPluginLoad2`, declare in `.inc`, add `MarkNativeAsOptional("VIP_X")` in `__pl_vip_core_SetNTVOptional`. - New forward: `static GlobalForward` field + `new GlobalForward(...)` in `API_SetupForwards` + `CreateForward_*` wrapper + `forward` declaration with doc comment in `.inc`. @@ -40,11 +40,11 @@ ## Comments - Explain *why*, never restate *what* the code does. A comment doesn't excuse unclear code: rename/restructure first; if a clear comment is hard to write, the code is the problem. -- Comment what a reader would trip over: unidiomatic code, SourceMod quirks, ordering constraints, odd-looking bug fixes. Mark unfinished parts `// TODO: `. Link the source of copied code and external refs (SM docs, AlliedModders) where they help. Russian or English, match the surroundings (cvar descriptions Russian). +- Comment what a reader would trip over: unidiomatic code, SourceMod quirks, ordering constraints, odd-looking bug fixes. Mark unfinished parts `// TODO: `. Link the source of copied code and external refs (SM docs, AlliedModders) where they help. ## Conventions - Pass client across async boundaries as userid (`UID`/`CID`), never raw index. - `GLOBAL_TRIE` / `GLOBAL_INFO` are index `[0]` of per-client arrays — slot 0 = global, not a player. - Use methodmaps (`StringMap`, `Cookie`, `KeyValues`, `delete`), not legacy procedural calls (`CreateTrie`, `SetClientCookie`, `CloseHandle`, `Kv*`). - Debug logging via `DBG_*` / `DebugMessage` functions from `Debug.sp` (empty unless `DEBUG_MODE 1`); calls still need a trailing `;`. -- Any new user-facing text → new phrase in `vip_core.phrases.txt` with at least `en` and `ru`. Bump `VIP_VERSION` in `VIP_Core.sp` only when asked; changelog lives in `updates.md`. +- English only in code, comments, cvar descriptions, log/error strings and docs. Other languages live only in `translations/*.phrases.txt` and the per-language values of `data/vip/cfg/*.ini`. Any new user-facing text → new phrase in `vip_core.phrases.txt` with at least `en` and `ru`. Bump `VIP_VERSION` in `VIP_Core.sp` only when asked; changelog lives in `updates.md`. diff --git a/addons/sourcemod/data/vip/cfg/groups.ini b/addons/sourcemod/data/vip/cfg/groups.ini index 5f20d1a..5778098 100644 --- a/addons/sourcemod/data/vip/cfg/groups.ini +++ b/addons/sourcemod/data/vip/cfg/groups.ini @@ -1,8 +1,8 @@ "VIP_GROUPS" { - "vip1" // Имя группы | Group name + "vip1" // Group name { - // Опции (основные VIP-модули) | Options (VIP Core Modules) + // Options (VIP Core Modules) } "vip2" diff --git a/addons/sourcemod/data/vip/cfg/info.ini b/addons/sourcemod/data/vip/cfg/info.ini index 88083cc..15015a4 100644 --- a/addons/sourcemod/data/vip/cfg/info.ini +++ b/addons/sourcemod/data/vip/cfg/info.ini @@ -1,25 +1,25 @@ "VIP_INFO" { - "connect_info_time" // Сообщение для VIP-игрока при входе на сервер (когда вип на время) | Message for the VIP player when entering the server (when the VIP is on time) + "connect_info_time" // Message for the VIP player when entering the server (timed VIP) { - // {NAME} - Ник игрока | Player Nickname - // {GROUP} - Группа игрока | Player Group - // {TIMELEFT} - Через сколько истекает VIP-статус | How long does it take for VIP status to expire? - // {EXPIRES} - Когда истекает VIP-статус | When VIP status expires + // {NAME} - Player Nickname + // {GROUP} - Player Group + // {TIMELEFT} - Time left until VIP status expires + // {EXPIRES} - When VIP status expires - "chat" // Сообщение в чат | Chat Message + "chat" // Chat Message { "ru" "{DEFAULT}Здравствуйте, {GREEN}{NAME}{DEFAULT}! Вы являетесь {LIGHTGREEN}VIP-игроком{DEFAULT}. Ваш VIP статус истекает {GREEN}{EXPIRES}" "en" "{DEFAULT}Hello, {GREEN}{NAME}{DEFAULT}! You have {LIGHTGREEN}VIP features {DEFAULT}for {GREEN}{EXPIRES}" "pt" "{DEFAULT}Olá, {GREEN}{NAME}{DEFAULT}! Você tem {LIGHTGREEN}status VIP {DEFAULT}para {GREEN}{EXPIRES}" "fr" "{DEFAULT}Bonjour, {GREEN}{NAME}{DEFAULT}! Vous avez un {LIGHTGREEN}statut VIP {DEFAULT}pendant {GREEN}{EXPIRES}" } - "menu" // Сообщение в меню | Menu Message + "menu" // Menu Message { - "time" "12" // Через сколько секунд меню будет закрыто автоматически (0 - пока игрок сам не закроет) | After how many seconds the menu will be closed automatically (0 - until the player closes himself) + "time" "12" // After how many seconds the menu will be closed automatically (0 - until the player closes it) "ru" { - // Пукнты меню | Menu Items + // Menu Items "item" "Вы имеете VIP-статус" "item" "SPACER" "item" "Ник: {NAME}" @@ -56,24 +56,24 @@ } } } - "connect_info_perm" // Сообщение для VIP-игрока при входе на сервер (когда вип навсегда) | A message for the VIP player when entering the server (when the VIP is forever) + "connect_info_perm" // A message for the VIP player when entering the server (when the VIP is forever) { - // {NAME} - Ник игрока | Player Nickname - // {GROUP} - Группа игрока | Player Group + // {NAME} - Player Nickname + // {GROUP} - Player Group - "chat" // Сообщение в чат | Chat Message + "chat" // Chat Message { "ru" "{DEFAULT}Здравствуйте, {GREEN}{NAME}{DEFAULT}! Вы являетесь {LIGHTGREEN}VIP-игроком{DEFAULT}." "en" "{DEFAULT}Hello, {GREEN}{NAME}{DEFAULT}! You have {LIGHTGREEN}VIP features{DEFAULT}." "pt" "{DEFAULT}Olá, {GREEN}{NAME}{DEFAULT}! Você tem {LIGHTGREEN}status VIP{DEFAULT}." "fr" "{DEFAULT}Bonjour, {GREEN}{NAME}{DEFAULT}! Vous avez un {LIGHTGREEN}statut VIP{DEFAULT}." } - "menu" // Сообщение в меню | Menu Message + "menu" // Menu Message { - "time" "30" // Через сколько секунд меню будет закрыто автоматически (0 - пока игрок сам не закроет) | After how many seconds the menu will be closed automatically (0 - until the player closes himself) + "time" "30" // After how many seconds the menu will be closed automatically (0 - until the player closes it) "ru" { - // Пукнты меню | Menu Items + // Menu Items "item" "Вы имеете VIP-статус" "item" "Группа: {GROUP}" } @@ -94,14 +94,14 @@ } } } - "no_access_info" // Сообщение об остутствии VIP-статуса + "no_access_info" // Message shown when the player has no VIP status { - "menu" // Сообщение в меню | Menu Message + "menu" // Menu Message { - "time" "12" // Через сколько секунд меню будет закрыто автоматически (0 - пока игрок сам не закроет) | After how many seconds the menu will be closed automatically (0 - until the player closes himself) + "time" "12" // After how many seconds the menu will be closed automatically (0 - until the player closes it) "ru" { - // Пукнты меню | Menu Items + // Menu Items "item" "У вас нет доступа к этому меню" "item" "Чтобы приобрести VIP-статус" "item" "Oбратитесь к администратору" @@ -126,7 +126,7 @@ } } - "url" // Ссылка, которую нужно открыть | Link to open + "url" // Link to open { "ru" "http://mysite.ru/vip/no_access.php?lang=ru" "en" "http://mysite.ru/vip/no_access.php?lang=en" @@ -134,14 +134,14 @@ "fr" "http://mysite.ru/vip/no_access.php?lang=fr" } } - "expired_info" // Сообщение об истечении VIP-статуса + "expired_info" // Message shown when the VIP status expires { - "menu" // Сообщение в меню | Menu Message + "menu" // Menu Message { - "time" "12" // Через сколько секунд меню будет закрыто автоматически (0 - пока игрок сам не закроет) | After how many seconds the menu will be closed automatically (0 - until the player closes himself) + "time" "12" // After how many seconds the menu will be closed automatically (0 - until the player closes it) "ru" { - // Пукнты меню | Menu Items + // Menu Items "item" "Срок вашего VIP-статуса истек" "item" "Чтобы вновь приобрести VIP-статус" "item" "Oбратитесь к администратору" diff --git a/addons/sourcemod/data/vip/cfg/times.ini b/addons/sourcemod/data/vip/cfg/times.ini index 52fd4b0..01b9c25 100644 --- a/addons/sourcemod/data/vip/cfg/times.ini +++ b/addons/sourcemod/data/vip/cfg/times.ini @@ -1,8 +1,8 @@ "TIMES" { - "0" // Время в секундах | Time in seconds + "0" // Time in seconds { - "ru" "Навсегда" // Отображение в меню | Display in the menu + "ru" "Навсегда" // Display in the menu "en" "Permanent" "pt" "Permanente" "fr" "Permanent" diff --git a/addons/sourcemod/scripting/VIP_Core.sp b/addons/sourcemod/scripting/VIP_Core.sp index a87209d..e204cb7 100644 --- a/addons/sourcemod/scripting/VIP_Core.sp +++ b/addons/sourcemod/scripting/VIP_Core.sp @@ -11,13 +11,13 @@ #endif -#define DEBUG_MODE 0 // Режим отладки +#define DEBUG_MODE 0 // Debug mode -#define USE_ADMINMENU 1 // Включение админ-меню для управления VIP +#define USE_ADMINMENU 1 // Admin menu for managing VIPs -#define USE_MORE_SERVERS 1 // Включить/Отключить режим при котором если ID сервера у игрока 0 - то VIP будет работать на всех серверах +#define USE_MORE_SERVERS 1 // A VIP with server ID 0 is active on all servers -#define USE_CLIENTPREFS 0 // Хранить статусы VIP-функций в куках (1) или в хранилище VIP vip_storage (0, старые куки переносятся автоматически) +#define USE_CLIENTPREFS 0 // Store VIP feature statuses in cookies (1) or in the vip_storage table (0, old cookies are migrated automatically) public Plugin myinfo = diff --git a/addons/sourcemod/scripting/include/vip_core.inc b/addons/sourcemod/scripting/include/vip_core.inc index 9fa83c7..b143796 100644 --- a/addons/sourcemod/scripting/include/vip_core.inc +++ b/addons/sourcemod/scripting/include/vip_core.inc @@ -5,132 +5,132 @@ #define VIP_CORE_INTERFACE 310 -/* Типы данных VIP-функций */ +/* VIP feature data types */ enum VIP_ValueType { - VIP_NULL = 0, // Нет данных - INT, // Целочисленный тип - FLOAT, // Числа с плавающей точкой (Дробные) - BOOL, // Логический тип (1/0) - STRING // Строчный тип + VIP_NULL = 0, // No data + INT, // Integer type + FLOAT, // Floating-point type + BOOL, // Boolean type (1/0) + STRING // String type } -/* Статусы VIP-функций */ +/* VIP feature statuses */ enum VIP_ToggleState { - DISABLED = 0, // Выключено - ENABLED, // Включено - NO_ACCESS // Нет доступа + DISABLED = 0, // Disabled + ENABLED, // Enabled + NO_ACCESS // No access } -/* Типы VIP-функций */ +/* VIP feature types */ enum VIP_FeatureType { - TOGGLABLE = 0, // Вкл/Выкл в меню - SELECTABLE, // Только нажатие - HIDE // Скрытый + TOGGLABLE = 0, // Toggled on/off in the menu + SELECTABLE, // Press only + HIDE // Hidden } typeset ItemSelectCallback { /** - * Вызывается когда VIP-игрок нажимает на пункт в меню (для типа TOGGLABLE). + * Called when a VIP player selects a menu item (for the TOGGLABLE type). * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. - * @param eOldStatus Старое значение статуса. - * @param eNewStatus Новое значение статуса. + * @param iClient Client index. + * @param szFeature Unique feature name. + * @param eOldStatus Old status. + * @param eNewStatus New status. * - * @return Plugin_Stop или Plugin_Handled - остановит переключение статуса; - * Plugin_Continue - разрешит переключение статуса без изменений; - * Plugin_Changed - разрешит переключение статуса на eNewStatus. + * @return Plugin_Stop or Plugin_Handled - stops the status change; + * Plugin_Continue - allows the status change as is; + * Plugin_Changed - allows the status change to eNewStatus. */ function Action (int iClient, const char[] szFeature, VIP_ToggleState eOldStatus, VIP_ToggleState &eNewStatus); /** - * Вызывается когда VIP-игрок нажимает на пункт в меню (для типа SELECTABLE). + * Called when a VIP player selects a menu item (for the SELECTABLE type). * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. + * @param iClient Client index. + * @param szFeature Unique feature name. * - * @return true - снова откроет игроку VIP-меню; + * @return true - reopens the VIP menu for the player; * false. */ function bool (int iClient, const char[] szFeature); }; /** - * Вызывается когда VIP-игроку отображается пункт в меню. + * Called when a menu item is displayed to a VIP player. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. - * @param szDisplay Буфер. - * @param iMaxLength Размер буфера. + * @param iClient Client index. + * @param szFeature Unique feature name. + * @param szDisplay Buffer. + * @param iMaxLength Buffer size. * - * @return true - Отображение содержимого szDisplay. - * false - Отображение без изменений. + * @return true - display the contents of szDisplay. + * false - display unchanged. */ typedef ItemDisplayCallback = function bool (int iClient, const char[] szFeature, char[] szDisplay, int iMaxLength); /** - * Вызывается когда VIP-игроку отображается стиль пункта в меню. + * Called when the style of a menu item is displayed to a VIP player. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. - * @param iStyle Стиль. + * @param iClient Client index. + * @param szFeature Unique feature name. + * @param iStyle Style. * - * @return Необходимый стиль (ITEMDRAW_*). + * @return Required style (ITEMDRAW_*). */ typedef ItemDrawCallback = function int (int iClient, const char[] szFeature, int iStyle); /** - * Вызывается когда игрок возрождается. + * Called when a player spawns. * - * @param iClient Индекс игрока. - * @param iTeam Команда игрока (2 - Т, 3- КТ). - * @param bIsVIP Имеет ли игрок VIP-статус. + * @param iClient Client index. + * @param iTeam Player team (2 - T, 3 - CT). + * @param bIsVIP Whether the player has VIP status. * * @noreturn */ forward void VIP_OnPlayerSpawn(int iClient, int iTeam, bool bIsVIP); /** - * Вызывается когда игру отображается информация. + * Called when information is displayed to a player. * - * @param iClient Индекс игрока. - * @param szEvent Имя события: - * "connect_info_time" - VIP-игрок вошел на сервер (VIP-статус на время); - * "connect_info_perm" - VIP-игрок вошел на сервер (VIP-статус навсегда); - * "no_access_info" - Игрок попытался открыть VIP-меню; - * "connect_info_time" - VIP-статус игрока истек. - * @param szType Тип сообщения: - * "chat" - В чат; - * "menu" - В меню; - * "url" - В MOTD окно. - * @param hKeyValues KeyValues ключа события. + * @param iClient Client index. + * @param szEvent Event name: + * "connect_info_time" - VIP player joined the server (timed VIP status); + * "connect_info_perm" - VIP player joined the server (permanent VIP status); + * "no_access_info" - Player tried to open the VIP menu; + * "connect_info_time" - Player's VIP status expired. + * @param szType Message type: + * "chat" - In chat; + * "menu" - In a menu; + * "url" - In a MOTD window. + * @param hKeyValues KeyValues of the event key. * - * @return Plugin_Stop или Plugin_Handled - запретит показ уведомления; - * Plugin_Changed - разрешит с изменениями; - * Plugin_Continue - разрешит без изменений. + * @return Plugin_Stop or Plugin_Handled - blocks the notification; + * Plugin_Changed - allows it with changes; + * Plugin_Continue - allows it unchanged. */ forward Action VIP_OnShowClientInfo(int iClient, const char[] szEvent, const char[] szType, KeyValues hKeyValues); /** - * Вызывается когда у VIP-игрока изменяется статус функции. + * Called when a feature status changes for a VIP player. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. - * @param eOldStatus Старое значение статуса. - * @param eNewStatus Новое значение статуса. + * @param iClient Client index. + * @param szFeature Unique feature name. + * @param eOldStatus Old status. + * @param eNewStatus New status. * - * @return Plugin_Stop или Plugin_Handled - остановит переключение статуса; - * Plugin_Continue - разрешит переключение статуса без изменений; - * Plugin_Changed - разрешит переключение статуса на eNewStatus. + * @return Plugin_Stop or Plugin_Handled - stops the status change; + * Plugin_Continue - allows the status change as is; + * Plugin_Changed - allows the status change to eNewStatus. */ forward Action VIP_OnFeatureToggle(int iClient, const char[] szFeature, VIP_ToggleState eOldStatus, VIP_ToggleState &eNewStatus); /** - * Вызывается когда VIP-плагин загрузился. + * Called when the VIP plugin has loaded. * * @noparams * @@ -139,7 +139,7 @@ forward Action VIP_OnFeatureToggle(int iClient, const char[] szFeature, VIP_Togg forward void VIP_OnVIPLoaded(); /** - * Вызывается когда VIP-плагин загрузил настройки (при старте и при перезагрузке конфигов). + * Called when the VIP plugin has loaded its settings (on start and on config reload). * * @noparams * @@ -148,187 +148,187 @@ forward void VIP_OnVIPLoaded(); forward void VIP_OnConfigsLoaded(); /** - * Вызывается когда загружено хранилище игрока (VIP_SetClientStorageValue / VIP_GetClientStorageValue). + * Called when the player's storage has loaded (VIP_SetClientStorageValue / VIP_GetClientStorageValue). * - * @param iClient Индекс игрока. + * @param iClient Client index. * * @noreturn */ forward void VIP_OnClientStorageLoaded(int iClient); /** - * Вызывается когда VIP-функция была зарегистрирована. + * Called when a VIP feature has been registered. * - * @param szFeature Уникальное имя ф-и. + * @param szFeature Unique feature name. * * @noreturn */ forward void VIP_OnFeatureRegistered(const char[] szFeature); /** - * Вызывается когда VIP-функция была удалена. + * Called when a VIP feature has been unregistered. * - * @param szFeature Уникальное имя ф-и. + * @param szFeature Unique feature name. * * @noreturn */ forward void VIP_OnFeatureUnregistered(const char[] szFeature); /** - * Вызывается перед проверкой игрока на наличие VIP-статуса. + * Called before a player is checked for VIP status. * - * @param iClient Индекс игрока. + * @param iClient Client index. * - * @return true - продолжить. - * false - не выполнять проверку. + * @return true - continue. + * false - skip the check. */ forward bool VIP_OnClientPreLoad(int iClient); /** - * Вызывается когда игрок был загружен. + * Called when a player has been loaded. * - * @param iClient Индекс игрока. - * @param bIsVIP Имеет ли игрок VIP-статус. + * @param iClient Client index. + * @param bIsVIP Whether the player has VIP status. * * @noreturn */ forward void VIP_OnClientLoaded(int iClient, bool bIsVIP); /** - * Вызывается когда VIP-игрок был загружен. + * Called when a VIP player has been loaded. * - * @param iClient Индекс игрока. + * @param iClient Client index. * * @noreturn */ forward void VIP_OnVIPClientLoaded(int iClient); /** - * Вызывается когда игрок на стадии отключения. + * Called when a player is disconnecting. * - * @param iClient Индекс игрока. - * @param bIsVIP Имеет ли игрок VIP-статус. + * @param iClient Client index. + * @param bIsVIP Whether the player has VIP status. * * @noreturn */ forward void VIP_OnClientDisconnect(int iClient, bool bIsVIP); /** - * Вызывается когда игрок получает VIP-статус. + * Called when a player receives VIP status. * - * @param iClient Индекс игрока. - * @param iAdmin Индекс админа (0 - сервер). + * @param iClient Client index. + * @param iAdmin Admin index (0 - server). * * @noreturn */ forward void VIP_OnVIPClientAdded(int iClient, int iAdmin); /** - * Вызывается когда игрок лишается VIP-статуса. + * Called when a player loses VIP status. * - * @param iClient Индекс игрока. - * @param szReason Причина: - * "Expired" - Истек; - * "Removed by Admin" - Удален админом; - * "Removed by native" - Удален нативом. - * @param iAdmin Индекс админа (0 - сервер). + * @param iClient Client index. + * @param szReason Reason: + * "Expired" - Expired; + * "Removed by Admin" - Removed by an admin; + * "Removed by native" - Removed by a native. + * @param iAdmin Admin index (0 - server). * * @noreturn */ forward void VIP_OnVIPClientRemoved(int iClient, const char[] szReason, int iAdmin); /** - * Запускает проверку игрока на наличие VIP прав. + * Starts checking a player for VIP rights. * - * @param iClient Индекс игрока. - * @param bNotify Известить ли игрока о результате проверки. - * @param bForward Вызывать ли форврад VIP_OnClientPreLoad перед проверкой. + * @param iClient Client index. + * @param bNotify Whether to notify the player of the check result. + * @param bForward Whether to call the VIP_OnClientPreLoad forward before the check. * * @noreturn */ native void VIP_CheckClient(int iClient, bool bNotify = false, bool bForward = true); /** - * Получает наличие VIP прав у игрока. + * Checks whether a player has VIP rights. * - * @param iClient Индекс игрока. + * @param iClient Client index. * - * @return true - У игрока имеются VIP-статус. - * false - У игрока нет VIP-статуса. + * @return true - the player has VIP status. + * false - the player has no VIP status. */ native bool VIP_IsClientVIP(int iClient); /** - * Получает уникальный ID VIP-игрока. + * Gets the unique ID of a VIP player. * - * @param iClient Индекс игрока. + * @param iClient Client index. * - * @return Уникальный ID или -1 если временный. + * @return Unique ID, or -1 if temporary. */ native int VIP_GetClientID(int iClient); /** - * Получает время окончания VIP-прав у игрока (unix). + * Gets the expiration time of a player's VIP rights (unix). * - * @param iClient Индекс игрока. + * @param iClient Client index. * - * @return Дата в unix формате (0 - навсегда, -1 если не удалось получить). + * @return Unix timestamp (0 - permanent, -1 on failure). */ native int VIP_GetClientAccessTime(int iClient); /** - * Устанавливает время окончания VIP-прав у игрока (unix). + * Sets the expiration time of a player's VIP rights (unix). * - * @param iClient Индекс игрока. - * @param iTime Дата в unix формате, 0 если права навсегда, -1 если временный. - * @param bInDB Изменять ли в базе данных. + * @param iClient Client index. + * @param iTime Unix timestamp, 0 if permanent, -1 if temporary. + * @param bInDB Whether to update the database. * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_SetClientAccessTime(int iClient, int iTime, bool bInDB = true); /** - * Получает VIP-группу игрока. + * Gets a player's VIP group. * - * @param iClient Индекс игрока. - * @param szGroup Буфер, в который будет помещен результат. - * @param iMaxLength Размер буфера. + * @param iClient Client index. + * @param szGroup Buffer to store the result in. + * @param iMaxLength Buffer size. * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_GetClientVIPGroup(int iClient, char[] szGroup, int iMaxLength); /** - * Устанавливает игроку VIP-группу. + * Sets a player's VIP group. * - * @param iClient Индекс игрока. - * @param szGroup VIP-группа. - * @param bInDBB Изменять ли в базе данных. + * @param iClient Client index. + * @param szGroup VIP group. + * @param bInDBB Whether to update the database. * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_SetClientVIPGroup(int iClient, const char[] szGroup, bool bInDB = true); /** - * Получает Handle adt trie настроек VIP-игрока. + * Gets the adt trie handle of a VIP player's settings. * - * @param iClient Индекс игрока. + * @param iClient Client index. * - * @return StringMap (Handle) adt trie настроек VIP-игрока. + * @return StringMap (Handle) adt trie of the VIP player's settings. */ native StringMap VIP_GetVIPClientTrie(int iClient); /** - * Проверяет наличие группы. + * Checks whether a group exists. * - * @param szGroup Имя группы. + * @param szGroup Group name. * - * @return true - группа существует. - * false - группа не существует. + * @return true - the group exists. + * false - the group does not exist. */ native bool VIP_IsGroupExists(const char[] szGroup); @@ -336,103 +336,103 @@ native bool VIP_IsGroupExists(const char[] szGroup); native bool VIP_IsValidVIPGroup(const char[] szGroup); /** - * Добавляет группу (только в памяти, groups.ini не изменяется). - * Группы сбрасываются при перезагрузке конфигов — добавляйте их заново в VIP_OnConfigsLoaded(). + * Adds a group (in memory only, groups.ini is not modified). + * Groups are reset when configs are reloaded; add them again in VIP_OnConfigsLoaded(). * - * @param szGroup Имя группы. - * @param hFeatures Параметры группы (ключи с текущей позиции копируются в группу). + * @param szGroup Group name. + * @param hFeatures Group parameters (keys from the current position are copied into the group). * - * @return true - успешно. - * false - группа уже существует. + * @return true - on success. + * false - the group already exists. */ native bool VIP_AddGroup(const char[] szGroup, KeyValues hFeatures); /** - * Удаляет группу (только в памяти, groups.ini не изменяется). + * Removes a group (in memory only, groups.ini is not modified). * - * @param szGroup Имя группы. + * @param szGroup Group name. * - * @return true - успешно. - * false - группа не существует. + * @return true - on success. + * false - the group does not exist. */ native bool VIP_RemoveGroup(const char[] szGroup); /** - * Сохраняет значение в хранилище игрока (таблица vip_storage). - * Хранилище доступно для всех игроков (не только VIP) после VIP_OnClientStorageLoaded(), - * до загрузки вызов игнорируется. В базу данных записывается при отключении игрока. + * Saves a value to the player's storage (vip_storage table). + * The storage is available for all players (not only VIP) after VIP_OnClientStorageLoaded(); + * before that, the call is ignored. It is written to the database when the player disconnects. * - * @param iClient Индекс игрока. - * @param szKey Ключ (до 127 символов). - * @param szValue Значение (до 255 символов). + * @param iClient Client index. + * @param szKey Key (up to 127 characters). + * @param szValue Value (up to 255 characters). * * @noreturn */ native void VIP_SetClientStorageValue(int iClient, const char[] szKey, const char[] szValue); /** - * Получает значение из хранилища игрока. + * Gets a value from the player's storage. * - * @param iClient Индекс игрока. - * @param szKey Ключ. - * @param szValue Буфер, в который будет помещен результат (пустая строка, если значения нет). - * @param iMaxLength Размер буфера. + * @param iClient Client index. + * @param szKey Key. + * @param szValue Buffer to store the result in (empty string if there is no value). + * @param iMaxLength Buffer size. * * @noreturn */ native void VIP_GetClientStorageValue(int iClient, const char[] szKey, char[] szValue, int iMaxLength); /** - * Выводит игроку сообщение в чат с приставкой (фраза в переводе "VIP_CHAT_PREFIX"). + * Prints a chat message to a player with a prefix (translation phrase "VIP_CHAT_PREFIX"). * - * @param iClient Индекс игрока. - * @param szMessage Правила форматирования сообщение. - * @param ... Параметры форматирования. + * @param iClient Client index. + * @param szMessage Formatting rules. + * @param ... Formatting parameters. * * @noreturn */ native void VIP_PrintToChatClient(int iClient, const char[] szMessage, any ...); /** - * Выводит всем игрокам сообщение в чат с приставкой (фраза в переводе "VIP_CHAT_PREFIX"). + * Prints a chat message to all players with a prefix (translation phrase "VIP_CHAT_PREFIX"). * - * @param szMessage Правила форматирования сообщение. - * @param ... Параметры форматирования. + * @param szMessage Formatting rules. + * @param ... Formatting parameters. * * @noreturn */ native void VIP_PrintToChatAll(const char[] szMessage, any ...); /** - * Записывает сообщение в лог. + * Writes a message to the log. * - * @param szMessage Правила форматирования сообщение. - * @param ... Параметры форматирования. + * @param szMessage Formatting rules. + * @param ... Formatting parameters. * * @noreturn */ native void VIP_LogMessage(const char[] szMessage, any ...); /** - * Отправляет игроку VIP-меню. + * Opens the VIP menu for a player. * - * @param iClient Индекс игрока. - * @param bSelection Режим открытия: - * true - на первой странице; - * false - на странице последнего выбранного пункта. + * @param iClient Client index. + * @param bSelection Open mode: + * true - on the first page; + * false - on the page of the last selected item. * * @noreturn */ native void VIP_SendClientVIPMenu(int iClient, bool bSelection = false); /** - * Выдает игроку VIP-права. + * Gives VIP rights to a player. * - * @param iAdmin Индекс админа (0 - сервер, -1 или любое отрицательное - плагин). - * @param iClient Индекс игрока. - * @param iTime Время в секундах. - * @param szGroup Имя VIP-группы. - * @param bAddToDB Добавлять ли в базу данных. + * @param iAdmin Admin index (0 - server, -1 or any negative - plugin). + * @param iClient Client index. + * @param iTime Time in seconds. + * @param szGroup VIP group name. + * @param bAddToDB Whether to add to the database. * * @noreturn */ @@ -442,15 +442,15 @@ native void VIP_GiveClientVIP(int iAdmin = 0, int iClient, int iTime, const char native bool VIP_SetClientVIP(int iClient, int iTime, any AuthType, const char[] szGroup, bool bAddToDB = true); /** - * Отнимает у игрока VIP-права. + * Removes VIP rights from a player. * - * @param iAdmin Индекс админа (0 - сервер, -1 или любое отрицательное - плагин). - * @param iClient Индекс игрока. - * @param bInDB Удалять ли из базы данных. - * @param bNotify Уведомление игрока (true - уведомить/false - не уведомлять). + * @param iAdmin Admin index (0 - server, -1 or any negative - plugin). + * @param iClient Client index. + * @param bInDB Whether to remove from the database. + * @param bNotify Player notification (true - notify / false - do not notify). * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_RemoveClientVIP2(int iAdmin = 0, int iClient, bool bInDB, bool bNotify); @@ -458,29 +458,29 @@ native bool VIP_RemoveClientVIP2(int iAdmin = 0, int iClient, bool bInDB, bool b native bool VIP_RemoveClientVIP(int iClient, bool bInDB, bool bNotify); /** - * Проверка что ядро загружено и готово к регистрации функций. + * Checks that the core is loaded and ready for feature registration. * * @noparams * - * @return true - Ядро загружено. - * false - Ядро не загружено. + * @return true - the core is loaded. + * false - the core is not loaded. */ native bool VIP_IsVIPLoaded(); /** - * Добавляет новую VIP-функцию. + * Registers a new VIP feature. * - * @param szFeature Уникальное имя ф-и. - * @param ValType Тип данных ф-и. - * @param FeatureType Тип ф-и. - * @param Item_select_callback Обратный вызов при смене статуса ф-и. - * @param Item_display_callback Обратный вызов при отображении пункта ф-и . - * @param Item_draw_callback Обратный вызов при отображении стиля пункта ф-и. - * @param eDefStatus Значение по-умолчанию: - * ENABLED - Включена - * DISABLED - Отключена - * NO_ACCESS - Не использовать (будет зависеть от sm_vip_features_default_status) - * @param bCookie Регистрировать ли куки для ф-и (действительно только для типа SELECTABLE). + * @param szFeature Unique feature name. + * @param ValType Feature data type. + * @param FeatureType Feature type. + * @param Item_select_callback Callback for a feature status change. + * @param Item_display_callback Callback for displaying the feature menu item. + * @param Item_draw_callback Callback for displaying the feature menu item style. + * @param eDefStatus Default status: + * ENABLED - Enabled + * DISABLED - Disabled + * NO_ACCESS - Not set (depends on sm_vip_features_default_status) + * @param bCookie Whether to register a cookie for the feature (valid only for the SELECTABLE type). * * @noreturn */ @@ -495,16 +495,16 @@ native void VIP_RegisterFeature(const char[] szFeature, bool bCookie = false); /** - * Удаляет VIP-функцию. + * Unregisters a VIP feature. * - * @param szFeature Уникальное имя ф-и. + * @param szFeature Unique feature name. * * @noreturn */ native void VIP_UnregisterFeature(const char[] szFeature); /** - * Удаляет все VIP-функции зарегистрированные плагином (вызывать в OnPluginEnd()). + * Unregisters all VIP features registered by the plugin (call in OnPluginEnd()). * * @noparams * @@ -513,153 +513,153 @@ native void VIP_UnregisterFeature(const char[] szFeature); native void VIP_UnregisterMe(); /** - * Проверяет существование функции. + * Checks whether a feature exists. * - * @param szFeature Уникальное имя ф-и. + * @param szFeature Unique feature name. * - * @return true - VIP-функция существует. - * false - VIP-функция не существует. + * @return true - the VIP feature exists. + * false - the VIP feature does not exist. */ native bool VIP_IsValidFeature(const char[] szFeature); /** - * Получает тип функции. + * Gets the feature type. * - * @param szFeature Уникальное имя ф-и. + * @param szFeature Unique feature name. * - * @return Возвращает элемент списка VIP_FeatureType. + * @return A VIP_FeatureType value. */ native VIP_FeatureType VIP_GetFeatureType(const char[] szFeature); /** - * Получает тип значения функции. + * Gets the feature value type. * - * @param szFeature Уникальное имя ф-и. + * @param szFeature Unique feature name. * - * @return Возвращает элемент списка VIP_ValueType. + * @return A VIP_ValueType value. */ native VIP_ValueType VIP_GetFeatureValueType(const char[] szFeature); /** - * Заполняет adt массив ф-ями. + * Fills an adt array with feature names. * - * @param hArray Массив типа ArrayList. + * @param hArray ArrayList array. * - * @return Количество добавленных элементов. + * @return Number of added elements. */ native int VIP_FillArrayByFeatures(ArrayList hArray); /** - * Возвращает статус VIP-функции у игрока. + * Gets a player's VIP feature status. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. + * @param iClient Client index. + * @param szFeature Unique feature name. * - * @return Возвращает элемент списка VIP_ToggleState. + * @return A VIP_ToggleState value. */ native VIP_ToggleState VIP_GetClientFeatureStatus(int iClient, const char[] szFeature); /** - * Устанавливает статус VIP-функции игроку. + * Sets a player's VIP feature status. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. - * @param eStatus Статус VIP-функции. - * @param bCallback Вызывать ли toggle каллбэк. - * @param bSave Сохранить ли в куки. + * @param iClient Client index. + * @param szFeature Unique feature name. + * @param eStatus VIP feature status. + * @param bCallback Whether to call the toggle callback. + * @param bSave Whether to save to the cookie. * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_SetClientFeatureStatus(int iClient, const char[] szFeature, VIP_ToggleState eStatus, bool bCallback = true, bool bSave = false); /** - * Возвращает статус VIP-функции у игрока. + * Checks whether a player's VIP feature is enabled. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. + * @param iClient Client index. + * @param szFeature Unique feature name. * - * @return true - VIP-функции включена. - * false - VIP-функции выключена или нет доступа. + * @return true - the VIP feature is enabled. + * false - the VIP feature is disabled or the player has no access. */ native bool VIP_IsClientFeatureUse(int iClient, const char[] szFeature); /** - * Получает целочисленное значение параметра VIP-функции у игрока. + * Gets the integer value of a player's VIP feature parameter. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. + * @param iClient Client index. + * @param szFeature Unique feature name. * - * @return Возвращает целочисленное значение параметра VIP-функции у игрока. + * @return Integer value of the player's VIP feature parameter. */ native int VIP_GetClientFeatureInt(int iClient, const char[] szFeature); /** - * Получает дробное значение параметра VIP-функции у игрока. + * Gets the float value of a player's VIP feature parameter. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. + * @param iClient Client index. + * @param szFeature Unique feature name. * - * @return Возвращает дробное значение параметра VIP-функции у игрока. + * @return Float value of the player's VIP feature parameter. */ native float VIP_GetClientFeatureFloat(int iClient, const char[] szFeature); /** - * Получает логическое значение параметра VIP-функции у игрока. + * Gets the boolean value of a player's VIP feature parameter. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. + * @param iClient Client index. + * @param szFeature Unique feature name. * - * @return Возвращает логическое значение параметра VIP-функции у игрока (true/false). + * @return Boolean value of the player's VIP feature parameter (true/false). */ native bool VIP_GetClientFeatureBool(int iClient, const char[] szFeature); /** - * Получает строчное значение параметра VIP-функции у игрока. + * Gets the string value of a player's VIP feature parameter. * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. - * @param szBuffer Буфер, в который будет помещен результат. - * @param iMaxLength Размер буфера. + * @param iClient Client index. + * @param szFeature Unique feature name. + * @param szBuffer Buffer to store the result in. + * @param iMaxLength Buffer size. * * @noreturn */ native void VIP_GetClientFeatureString(int iClient, const char[] szFeature, char[] szBuffer, int iMaxLength); /** - * Выдает игроку VIP-функцию и устанавливает её значение (на время сессии). + * Gives a VIP feature to a player and sets its value (for the session). * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. - * @param szValue Значение (автоматически будет приведено к типу ф-и). + * @param iClient Client index. + * @param szFeature Unique feature name. + * @param szValue Value (automatically converted to the feature type). * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_GiveClientFeature(int iClient, const char[] szFeature, const char[] szValue); /** - * Отнимает у VIP-игрока VIP-функцию (на время сессии). + * Removes a VIP feature from a VIP player (for the session). * - * @param iClient Индекс игрока. - * @param szFeature Уникальное имя ф-и. + * @param iClient Client index. + * @param szFeature Unique feature name. * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_RemoveClientFeature(int iClient, const char[] szFeature); /** - * Получает Handle базы данных. После работы необходимо закрыть с помощью CloseHandle() или delete + * Gets the database handle. After use, it must be closed with CloseHandle() or delete * * @noparams * - * @return Database (Handle) базы данных или null, если ядро ещё не подключилось к БД (до VIP_OnVIPLoaded) + * @return Database (Handle), or null if the core has not connected to the database yet (before VIP_OnVIPLoaded) */ native Database VIP_GetDatabase(); /** - * Получает тип базы данных. + * Gets the database type. * * @noparams * @@ -669,45 +669,45 @@ native Database VIP_GetDatabase(); native bool VIP_GetDatabaseType(); /** - * Получает время в секундах в зависимости от sm_vip_time_mode. + * Converts a time value to seconds according to sm_vip_time_mode. * - * @param iTime Время. + * @param iTime Time. * - * @return Возвращает количество секунд в зависимости от sm_vip_time_mode. - * -1 - неизвестный sm_vip_time_mode или результат не помещается в int. + * @return Number of seconds according to sm_vip_time_mode. + * -1 - unknown sm_vip_time_mode, or the result does not fit into an int. */ native int VIP_TimeToSeconds(int iTime); /** - * Получает время в зависимости от sm_vip_time_mode. + * Converts seconds to a time value according to sm_vip_time_mode. * - * @param iTime Количество секунд. + * @param iTime Number of seconds. * - * @return Возвращает время в зависимости от sm_vip_time_mode. + * @return Time value according to sm_vip_time_mode. */ native int VIP_SecondsToTime(int iTime); /** - * Переводит секунды в человекопонятный вид. + * Converts seconds to a human-readable string. * - * @param szBuffer Буфер, в который будет помещен результат. - * @param iMaxLength Размер буфера. - * @param iTimeStamp Кол-во секунд. - * @param iClient Индекс игрока или LANG_SERVER. + * @param szBuffer Buffer to store the result in. + * @param iMaxLength Buffer size. + * @param iTimeStamp Number of seconds. + * @param iClient Client index or LANG_SERVER. * - * @return true - Успешно. - * false - Не удалось выполнить. + * @return true - on success. + * false - on failure. */ native bool VIP_GetTimeFromStamp(char[] szBuffer, int iMaxLength, int iTimeStamp, int iClient = LANG_SERVER); /** - * Добавляет к строке статус ф-и. + * Appends the feature status to a string. * - * @param szInput Исходная строка. - * @param szBuffer Буфер, в который будет помещен результат. - * @param iMaxLength Размер буфера. - * @param szFeature Уникальное имя ф-и. - * @param iClient Индекс игрока. + * @param szInput Source string. + * @param szBuffer Buffer to store the result in. + * @param iMaxLength Buffer size. + * @param szFeature Unique feature name. + * @param iClient Client index. * * @noreturn */ diff --git a/addons/sourcemod/scripting/vip/API.sp b/addons/sourcemod/scripting/vip/API.sp index 43b3f4b..45dfb06 100644 --- a/addons/sourcemod/scripting/vip/API.sp +++ b/addons/sourcemod/scripting/vip/API.sp @@ -469,7 +469,7 @@ public int Native_SetClientVIPGroup(Handle hPlugin, int iNumParams) } else { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid group/Некорректная VIP-группа (%s)", szGroup); + ThrowNativeError(SP_ERROR_NATIVE, "Invalid VIP group (%s)", szGroup); } } return false; @@ -499,7 +499,7 @@ public int Native_SetClientAccessTime(Handle hPlugin, int iNumParams) if (iTime < 0 || (iTime != 0 && iTime < GetTime())) { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid time/Некорректное время (%i)", iTime); + ThrowNativeError(SP_ERROR_NATIVE, "Invalid time (%i)", iTime); return false; } @@ -619,17 +619,17 @@ int API_GiveClientVIP(Handle hPlugin, int iAdmin, int iClient, int iTime, const } else { - return ThrowNativeError(SP_ERROR_NATIVE, "The player %L is already a VIP/Игрок %L уже является VIP-игроком", iClient, iClient); + return ThrowNativeError(SP_ERROR_NATIVE, "The player %L is already a VIP", iClient); } } if (!UTIL_CheckValidVIPGroup(szGroup)) { - return ThrowNativeError(SP_ERROR_NATIVE, "Invalid VIP-group/Некорректная VIP-группа (%s)", szGroup); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid VIP group (%s)", szGroup); } if (iTime < 0) { - return ThrowNativeError(SP_ERROR_NATIVE, "Invalid time/Некорректное время (%d)", iTime); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid time (%d)", iTime); } if (bAddToDB) @@ -763,7 +763,7 @@ public int Native_AddGroup(Handle hPlugin, int iNumParams) KeyValues hFeatures = view_as(GetNativeCell(2)); if (!hFeatures) { - return ThrowNativeError(SP_ERROR_NATIVE, "Invalid KeyValues handle/Некорректный KeyValues для группы \"%s\"", szGroup); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid KeyValues handle for group \"%s\"", szGroup); } if (!szGroup[0] || UTIL_CheckValidVIPGroup(szGroup)) @@ -942,7 +942,7 @@ public int Native_UnregisterFeature(Handle hPlugin, int iNumParams) } else { - ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid/Функция \"%s\" не существует", szFeature, szFeature); + ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid", szFeature); } return 0; @@ -1044,7 +1044,7 @@ public int Native_GetFeatureType(Handle hPlugin, int iNumParams) return hArray.Get(FEATURES_ITEM_TYPE); } - return ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid/Функция \"%s\" не существует", szFeature, szFeature); + return ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid", szFeature); } public int Native_GetFeatureValueType(Handle hPlugin, int iNumParams) @@ -1058,7 +1058,7 @@ public int Native_GetFeatureValueType(Handle hPlugin, int iNumParams) return hArray.Get(FEATURES_VALUE_TYPE); } - return ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid/Функция \"%s\" не существует", szFeature, szFeature); + return ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid", szFeature); } public int Native_FillArrayByFeatures(Handle hPlugin, int iNumParams) @@ -1233,7 +1233,7 @@ public int Native_GiveClientFeature(Handle hPlugin, int iNumParams) ArrayList hArray; if (!IsValidFeature(szFeature) || !GLOBAL_TRIE.GetValue(szFeature, hArray)) { - ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid/Функция \"%s\" не существует", szFeature, szFeature); + ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid", szFeature); return 0; } @@ -1298,7 +1298,7 @@ public int Native_RemoveClientFeature(Handle hPlugin, int iNumParams) ArrayList hArray; if (!IsValidFeature(szFeature) || !GLOBAL_TRIE.GetValue(szFeature, hArray)) { - ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid/Функция \"%s\" не существует", szFeature, szFeature); + ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid", szFeature); return 0; } @@ -1381,7 +1381,7 @@ public int Native_AddStringToggleStatus(Handle hPlugin, int iNumParams) GetNativeString(4, SZF(szFeature)); if (!IsValidFeature(szFeature)) { - return ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid/Функция \"%s\" не существует", szFeature, szFeature); + return ThrowNativeError(SP_ERROR_NATIVE, "Feature \"%s\" is invalid", szFeature); } int iClient = GetNativeCell(5); @@ -1401,12 +1401,12 @@ bool CheckValidClient(const int &iClient, bool bCheckVIP = true) { if (iClient < 1 || iClient > MaxClients) { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index/Некорректный индекс игрока (%i)", iClient); + ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%i)", iClient); return false; } if (IsClientInGame(iClient) == false) { - ThrowNativeError(SP_ERROR_NATIVE, "Client %i is not connected/Игрок %i не подключен", iClient, iClient); + ThrowNativeError(SP_ERROR_NATIVE, "Client %i is not connected", iClient); return false; } if (bCheckVIP) diff --git a/addons/sourcemod/scripting/vip/Clients.sp b/addons/sourcemod/scripting/vip/Clients.sp index ab5ee37..2eb0547 100644 --- a/addons/sourcemod/scripting/vip/Clients.sp +++ b/addons/sourcemod/scripting/vip/Clients.sp @@ -76,7 +76,7 @@ void Clients_CheckVipAccess(int iClient, bool bNotify = false, bool bForward = f if (IsFakeClient(iClient) == false && (GLOBAL_INFO & IS_STARTED) && g_hDatabase) { Clients_LoadClient(iClient, bNotify); - // DBG_Clients("Clients_CheckVipAccess %N:\tИгрок %sявляется VIP игроком", iClient, g_bIsClientVIP[iClient] ? "":"не ") + // DBG_Clients("Clients_CheckVipAccess %N:\tis %sa VIP", iClient, g_bIsClientVIP[iClient] ? "":"not ") } else { @@ -226,7 +226,7 @@ public void SQL_Callback_OnClientAuthorized(Database hOwner, DBResultSet hResult } else { - LogError("Invalid VIP-Group/Некорректная VIP-группа: %s (Игрок: %d)", szGroup, iAccountID); + LogError("Invalid VIP group: %s (player: %d)", szGroup, iAccountID); Clients_SetLoaded(iClient); } return; diff --git a/addons/sourcemod/scripting/vip/Cvars.sp b/addons/sourcemod/scripting/vip/Cvars.sp index 8a7e770..eb3e3e4 100644 --- a/addons/sourcemod/scripting/vip/Cvars.sp +++ b/addons/sourcemod/scripting/vip/Cvars.sp @@ -2,53 +2,53 @@ void Cvars_Setup() { CreateConVar("sm_vip_core_version", VIP_VERSION, "VIP-CORE VERSION", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_CHEAT|FCVAR_DONTRECORD); - ConVar hCvar = CreateConVar("sm_vip_admin_flag", "z", "Флаг админа, необходимый чтобы иметь доступ к управлению VIP-игроками."); + ConVar hCvar = CreateConVar("sm_vip_admin_flag", "z", "Admin flag required to manage VIP players."); hCvar.AddChangeHook(OnAdminFlagChange); OnAdminFlagChange(hCvar, NULL_STRING, NULL_STRING); - g_CVAR_hVIPMenu_CMD = CreateConVar("sm_vip_menu_commands", "vip;sm_vip;sm_vipmenu", "Команды для вызова VIP-меню (разделять ;)"); + g_CVAR_hVIPMenu_CMD = CreateConVar("sm_vip_menu_commands", "vip;sm_vip;sm_vipmenu", "Commands that open the VIP menu (separated by ;)"); - hCvar = CreateConVar("sm_vip_server_id", "0", "ID сервера при использовании MySQL базы данных", _, true, 0.0); + hCvar = CreateConVar("sm_vip_server_id", "0", "Server ID when using a MySQL database", _, true, 0.0); hCvar.AddChangeHook(OnServerIDChange); OnServerIDChange(hCvar, NULL_STRING, NULL_STRING); - hCvar = CreateConVar("sm_vip_storage_id", "0", "ID группы серверов для хранилища данных игроков при использовании MySQL базы данных", _, true, 0.0); + hCvar = CreateConVar("sm_vip_storage_id", "0", "Server group ID for the player data storage when using a MySQL database", _, true, 0.0); hCvar.AddChangeHook(OnStorageIDChange); g_CVAR_iStorageID = hCvar.IntValue; - hCvar = CreateConVar("sm_vip_auto_open_menu", "0", "Автоматически открывать VIP-меню при входе (0 - Выключено, 1 - Включено)", _, true, 0.0, true, 1.0); + hCvar = CreateConVar("sm_vip_auto_open_menu", "0", "Open the VIP menu automatically on join (0 - Disabled, 1 - Enabled)", _, true, 0.0, true, 1.0); hCvar.AddChangeHook(OnAutoOpenMenuChange); g_CVAR_bAutoOpenMenu = hCvar.BoolValue; - hCvar = CreateConVar("sm_vip_time_mode", "0", "Формат времени (0 - Секунды, 1 - Минуты, 2 - Часы, 3 - Дни)", _, true, 0.0, true, 3.0); + hCvar = CreateConVar("sm_vip_time_mode", "0", "Time format (0 - Seconds, 1 - Minutes, 2 - Hours, 3 - Days)", _, true, 0.0, true, 3.0); hCvar.AddChangeHook(OnTimeModeChange); g_CVAR_iTimeMode = hCvar.IntValue; - hCvar = CreateConVar("sm_vip_delete_expired", "1", "Удалять VIP-игроков у которых истек срок (-1 - Не удалять, 0 - Удалять сразу, > 0 - Количество дней, по истечению которых удалять)", _, true, -1.0, true, 365.0); + hCvar = CreateConVar("sm_vip_delete_expired", "1", "Delete expired VIP players (-1 - Never, 0 - Immediately, > 0 - Days after expiry)", _, true, -1.0, true, 365.0); hCvar.AddChangeHook(OnDeleteExpiredChange); g_CVAR_iDeleteExpired = hCvar.IntValue; - hCvar = CreateConVar("sm_vip_delete_outdated", "-1", "Удалять VIP-игроков которые не заходили на сервер X дней (-1 - Не удалять, > 0 - Количество дней, по истечению которых удалять (минимум 3 суток))", _, true, -1.0, true, 365.0); + hCvar = CreateConVar("sm_vip_delete_outdated", "-1", "Delete VIP players who have not joined for X days (-1 - Never, > 0 - Number of days (minimum 3))", _, true, -1.0, true, 365.0); hCvar.AddChangeHook(OnDeleteOutdatedChange); g_CVAR_iOutdatedExpired = hCvar.IntValue; - hCvar = CreateConVar("sm_vip_update_name", "1", "Обновлять имена VIP-игроков в базе данных при входе(0 - Выключено, 1 - Включено)", _, true, 0.0, true, 1.0); + hCvar = CreateConVar("sm_vip_update_name", "1", "Update VIP player names in the database on join (0 - Disabled, 1 - Enabled)", _, true, 0.0, true, 1.0); hCvar.AddChangeHook(OnUpdateNameChange); g_CVAR_bUpdateName = hCvar.BoolValue; - hCvar = CreateConVar("sm_vip_spawn_delay", "1.0", "Задержка перед установкой привилегий при возрождении игрока", _, true, 0.1, true, 60.0); + hCvar = CreateConVar("sm_vip_spawn_delay", "1.0", "Delay before applying privileges after the player spawns", _, true, 0.1, true, 60.0); hCvar.AddChangeHook(OnSpawnDelayChange); g_CVAR_fSpawnDelay = hCvar.FloatValue; - hCvar = CreateConVar("sm_vip_hide_no_access_items", "0", "Режим отображения недоступных функций в вип меню (0 - Сделать пункты неактивными, 1 - Скрывать пункты)", _, true, 0.0, true, 1.0); + hCvar = CreateConVar("sm_vip_hide_no_access_items", "0", "How unavailable features are shown in the VIP menu (0 - Disabled items, 1 - Hidden)", _, true, 0.0, true, 1.0); hCvar.AddChangeHook(OnHideNoAccessItemsChange); g_CVAR_bHideNoAccessItems = hCvar.BoolValue; - hCvar = CreateConVar("sm_vip_features_default_status", "1", "Статус функций по-умолчанию (0 - Выключены, 1 - Включены)", _, true, 0.0, true, 1.0); + hCvar = CreateConVar("sm_vip_features_default_status", "1", "Default feature status (0 - Disabled, 1 - Enabled)", _, true, 0.0, true, 1.0); hCvar.AddChangeHook(OnDefaultStatusChange); g_CVAR_bDefaultStatus = hCvar.BoolValue; - hCvar = CreateConVar("sm_vip_logs_enable", "1", "Вести ли лог logs/VIP_Logs.log (0 - Выключено, 1 - Включено)", _, true, 0.0, true, 1.0); + hCvar = CreateConVar("sm_vip_logs_enable", "1", "Write logs/VIP_Logs.log (0 - Disabled, 1 - Enabled)", _, true, 0.0, true, 1.0); hCvar.AddChangeHook(OnLogsEnableChange); g_CVAR_bLogsEnable = hCvar.BoolValue; diff --git a/addons/sourcemod/scripting/vip/Global.sp b/addons/sourcemod/scripting/vip/Global.sp index 0a3dfc7..c5348b9 100644 --- a/addons/sourcemod/scripting/vip/Global.sp +++ b/addons/sourcemod/scripting/vip/Global.sp @@ -20,12 +20,12 @@ #define SET_BIT(%0,%1) %0 |= %1 #define UNSET_BIT(%0,%1) %0 &= ~%1 -#define IS_VIP (1<<0) // VIP ли игрок -#define IS_LOADED (1<<1) // Загружен ли игрок -#define IS_WAIT_CHAT_PASS (1<<2) // Ожидается ввод пароля в чат -#define IS_WAIT_CHAT_SEARCH (1<<3) // Ожидается ввод значения для поиска в чат -#define IS_SPAWNED (1<<4) // Игрок возродился -#define IS_MENU_OPEN (1<<5) // VIP-меню открыто +#define IS_VIP (1<<0) // Player is a VIP +#define IS_LOADED (1<<1) // Player is loaded +#define IS_WAIT_CHAT_PASS (1<<2) // Waiting for a password in chat +#define IS_WAIT_CHAT_SEARCH (1<<3) // Waiting for a search value in chat +#define IS_SPAWNED (1<<4) // Player has spawned +#define IS_MENU_OPEN (1<<5) // VIP menu is open #define IS_STARTED (1<<0) #define IS_MySQL (1<<1) diff --git a/addons/sourcemod/scripting/vip/Info.sp b/addons/sourcemod/scripting/vip/Info.sp index 4ae8438..3c6ad46 100644 --- a/addons/sourcemod/scripting/vip/Info.sp +++ b/addons/sourcemod/scripting/vip/Info.sp @@ -190,10 +190,10 @@ void ReplaceValues(int iClient, char[] szBuffer, int iBufLen, bool bExt) UTIL_GetTimeFromStamp(SZF(szExpires), iExpires - GetTime(), iClient); ReplaceString(szBuffer, iBufLen, "{TIMELEFT}", szExpires); } - // {NAME} - Ник игрока - // {GROUP} - Группа игрока - // {TIMELEFT} - Через сколько истекает VIP-статус - // {EXPIRES} - Когда истекает VIP-статус + // {NAME} - Player name + // {GROUP} - Player group + // {TIMELEFT} - Time left until VIP status expires + // {EXPIRES} - When VIP status expires } public int SelectInfoPanel(Menu hPanel, MenuAction action, int iClient, int iOption) diff --git a/updates.md b/updates.md index f28b50b..62784bb 100644 --- a/updates.md +++ b/updates.md @@ -1,239 +1,239 @@ # [VIP] Core 3.0 DEV #41 -### Изменения: -- Доработана формировка sql запросов для `USE_MORE_SERVERS`. +### Changes: +- Improved SQL query formation for `USE_MORE_SERVERS`. # [VIP] Core 3.0 DEV #40 -### Изменения: -- Изменен натив `VIP_RegisterFeature` (Добавлен параметр `bCookie` - Регистрировать ли куки для ф-и (действительно только для типа SELECTABLE)). +### Changes: +- Changed native `VIP_RegisterFeature` (added parameter `bCookie` - whether to register cookies for the feature (only valid for the SELECTABLE type)). # [VIP] Core 3.0 DEV #39 -### Изменения: -- Исправлена ошибка на SQLite `SQL_Callback_RemoveClient: no such column: sid`. +### Changes: +- Fixed error `SQL_Callback_RemoveClient: no such column: sid` on SQLite. # [VIP] Core 3.0 DEV #38 -### Изменения: -- Исправлена ошибка на SQLite `SQL_Callback_ErrorCheck: no such column: sid`. +### Changes: +- Fixed error `SQL_Callback_ErrorCheck: no such column: sid` on SQLite. # [VIP] Core 3.0 DEV #37 -### Изменения: -- Исправлена ошибка когда не удалялись истекшие VIP-игроки. +### Changes: +- Fixed an error where expired VIP players were not being removed. # [VIP] Core 3.0 DEV #36 -### Изменения: -- Теперь точно исправлена ошибка `Invalid Handle 0 (error 4)` (`DB_RemoveClientFromID`). +### Changes: +- Now properly fixed the `Invalid Handle 0 (error 4)` error (`DB_RemoveClientFromID`). # [VIP] Core 3.0 DEV #35 -### Изменения: -- Исправлена ошибка `Invalid Handle 0 (error 4)` (`DB_RemoveClientFromID`). +### Changes: +- Fixed error `Invalid Handle 0 (error 4)` (`DB_RemoveClientFromID`). # [VIP] Core 3.0 DEV #34 -### Изменения: -- Натив `VIP_RemoveClientVIP` помечен как устаревший и будет удален в будущем. -- Добавлен новый натив `VIP_RemoveClientVIP2`. -- Исправлена ошибка `Client index 0 is invalid` при добавлении VIP-игрока через `sm_addvip`. +### Changes: +- Native `VIP_RemoveClientVIP` is marked as deprecated and will be removed in the future. +- Added new native `VIP_RemoveClientVIP2`. +- Fixed error `Client index 0 is invalid` when adding a VIP player via `sm_addvip`. # [VIP] Core 3.0 DEV #33 -### Изменения: -- Изменени натив `VIP_SetClientFeatureStatus`. -- Исправлена ошибка `SQL_Callback_SelectExpiredAndOutdated: Unknown column 'iTime' in 'where clause'`. -- Переработана система логов. +### Changes: +- Changed native `VIP_SetClientFeatureStatus`. +- Fixed error `SQL_Callback_SelectExpiredAndOutdated: Unknown column 'iTime' in 'where clause'`. +- Reworked the logging system. -# [VIP] Core 3.0 DEV #32 (спасибо DarklSide за обнаруженные ошибок) +# [VIP] Core 3.0 DEV #32 (thanks to DarklSide for finding the errors) -### Изменения: -- Исправлено описание форварда `VIP_OnClientPreLoad`. -- Исправлен натив `VIP_GiveClientFeature`. -- Исправлена ошибка `SQL_Callback_SelectExpiredAndOutdated: no such function: UNIX_TIMESTAMP` на SQLite. -- Исправлено обновление ников игроков. -- Исправлена работа DataPack на sm 1.10. +### Changes: +- Fixed the description of forward `VIP_OnClientPreLoad`. +- Fixed native `VIP_GiveClientFeature`. +- Fixed error `SQL_Callback_SelectExpiredAndOutdated: no such function: UNIX_TIMESTAMP` on SQLite. +- Fixed player nickname updates. +- Fixed DataPack handling on sm 1.10. # [VIP] Core 3.0 DEV #31 -### Изменения: -- Добавлен форвард `VIP_OnClientPreLoad`. -- Исправлен и включен натив `VIP_GiveClientFeature`. -- Добавлен натив `VIP_RemoveClientFeature`. -- Изменен натив `VIP_CheckClient`. -- Значение по-умолчанию для `sm_vip_delete_outdated` изменено на `-1`. -- Исправлено удаление истекших и не активных игроков. +### Changes: +- Added forward `VIP_OnClientPreLoad`. +- Fixed and enabled native `VIP_GiveClientFeature`. +- Added native `VIP_RemoveClientFeature`. +- Changed native `VIP_CheckClient`. +- Changed the default value of `sm_vip_delete_outdated` to `-1`. +- Fixed removal of expired and inactive players. # [VIP] Core 3.0 DEV #30 -### Изменения: -- Исправлена работа списка VIP-игроков. +### Changes: +- Fixed the VIP players list. # [VIP] Core 3.0 DEV #29 -### Изменения: -- Исправлены SQL ошибки. -- Небольшая оптимизация. -- Исправлена отладка. +### Changes: +- Fixed SQL errors. +- Minor optimization. +- Fixed debugging. # [VIP] Core 3.0 DEV #28 -### Изменения: -- Исправлена ошибка из-за которой плагин не запускался `Fatal error creating dynamic native!/unexpected error 23 in AskPluginLoad callback`. -- Изменено имя базы данных с `"vip"` на `"vip_core"` (еще в версии 3.0 DEV #25, просто забыл упомянуть). -- Исправлена попытка установки кодировки для SQLite базы. +### Changes: +- Fixed the error that prevented the plugin from starting: `Fatal error creating dynamic native!/unexpected error 23 in AskPluginLoad callback`. +- Changed the database name from `"vip"` to `"vip_core"` (already in version 3.0 DEV #25, just forgot to mention it). +- Fixed an attempt to set the encoding for the SQLite database. # [VIP] Core 3.0 DEV #27 -### Изменения: -- Активирована работа квара `sm_vip_delete_outdated`. -- Полный переход на новую структуру базы данных. +### Changes: +- Enabled the `sm_vip_delete_outdated` cvar. +- Full transition to the new database structure. # [VIP] Core 3.0 DEV #26 -### Изменения: -- Вернул прежний вид натива VIP_SetClientVIP и пометил как устаревший. -- Добавлен новый натив `VIP_GiveClientVIP`, как замена устаревшему `VIP_SetClientVIP`. -- Добавлена обратная совместимость натива `VIP_RemoveClientVIP`. -- Изменента структура таблиц (MySQL). +### Changes: +- Restored the previous form of native VIP_SetClientVIP and marked it as deprecated. +- Added new native `VIP_GiveClientVIP` as a replacement for the deprecated `VIP_SetClientVIP`. +- Added backward compatibility for native `VIP_RemoveClientVIP`. +- Changed the table structure (MySQL). # [VIP] Core 3.0 DEV #25 -### Изменения: -- Добавлено использование `TranslationPhraseExists` для предовтращения ошибок (При отсутствии фразы модуля в переводе). -- Начал переводить отладку более расширенный и гибкий вид. -- Добавлен квар `sm_vip_delete_outdated` (пока не функционирует). +### Changes: +- Added use of `TranslationPhraseExists` to prevent errors (when a module's phrase is missing from the translation). +- Started reworking debugging into a more extended and flexible form. +- Added cvar `sm_vip_delete_outdated` (not functional yet). # [VIP] Core 3.0 DEV #24 -### Изменения: -- Исправлены SQL ошибки. +### Changes: +- Fixed SQL errors. # [VIP] Core 3.0 DEV #23 -### Изменения: -- Удален столбец `id` из базы данных. -- Исправлена ошибка `SQL_Callback_SelectVipClientInfo` . -- Исправлена ошибка `SQL_Callback_ChangeTime: Unknown column 'user_id' in 'where clause'`. +### Changes: +- Removed the `id` column from the database. +- Fixed error `SQL_Callback_SelectVipClientInfo` . +- Fixed error `SQL_Callback_ChangeTime: Unknown column 'user_id' in 'where clause'`. # [VIP] Core 3.0 DEV #22 -### Изменения: -- Попытка исправления ошибки `Invalid Handle 0 (error 4)`. -- Исправлена ошибка когда у no-steam игроков значение `account_id` в базе данных становилось равным 0. +### Changes: +- Attempt to fix error `Invalid Handle 0 (error 4)`. +- Fixed an error where the `account_id` value in the database became 0 for no-steam players. # [VIP] Core 3.0 DEV #21 -### Изменения: -- Исправлена ошибка `SQL_Callback_SelectVipPlayers: near ")": syntax error`. +### Changes: +- Fixed error `SQL_Callback_SelectVipPlayers: near ")": syntax error`. # [VIP] Core 3.0 DEV #20 -### Изменения: -- Исправлена ошибка когда значение `name` в базе данных становилось равным 0. +### Changes: +- Fixed an error where the `name` value in the database became 0. # [VIP] Core 3.0 DEV #19 -### Изменения: -- Добавлена мультиязычность кнопки `"Выход"` в инфо-меню. +### Changes: +- Added multilanguage support for the `"Exit"` button in the info menu. # [VIP] Core 3.0 DEV #18 -### Изменения: -- Исправлена ошибка `SQL_Callback_SelectVipPlayers: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '0, 60' at line 1`. +### Changes: +- Fixed error `SQL_Callback_SelectVipPlayers: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '0, 60' at line 1`. # [VIP] Core 3.0 DEV #17 -### Изменения: -- Добавлено сохранение последнего входа игрока на сервер. +### Changes: +- Added saving of the player's last server login. # [VIP] Core 3.0 DEV #16 -### Изменения: -- Теперь игроки хранятся в базе по AccountID. -- Исправлена работа команды `sm_addvip`: - - Теперь она может принимать: SteamID2, SteamID3 и ник. - - Исправлена выдача VIP-статуса оффлайн. +### Changes: +- Players are now stored in the database by AccountID. +- Fixed the `sm_addvip` command: + - It can now accept: SteamID2, SteamID3 and a nickname. + - Fixed granting VIP status to offline players. # [VIP] Core 3.0 DEV #15 -### Изменения: -- Добавлено исправление работы MOTD окна на CS:GO. -- Добавлена директива FIX_CSGO_MOTD, которая позволяет компилировать ядро с фиксом MOTD и без. +### Changes: +- Added a fix for the MOTD window on CS:GO. +- Added the FIX_CSGO_MOTD directive, which allows compiling the core with or without the MOTD fix. # [VIP] Core 3.0 DEV #14 -### Изменения: -- Обновлено API: - - Добавлен форвард `VIP_OnShowClientInfo`. - - Изменен натив `VIP_RemoveClientVIP`. - - Доработано и исправлено описание всех нативов и форвадов. -- Обновлен файл `addons/sourcemod/data/vip/cfg/info.ini` (Исправлено описание и опечатки, добавлен английский перевод). +### Changes: +- Updated the API: + - Added forward `VIP_OnShowClientInfo`. + - Changed native `VIP_RemoveClientVIP`. + - Improved and fixed the descriptions of all natives and forwards. +- Updated file `addons/sourcemod/data/vip/cfg/info.ini` (fixed the description and typos, added English translation). # [VIP] Core 3.0 DEV #13 -### Изменения: -- Исправлена логическая ошибка в работе `VIP_UnregisterFeature` когда добавлялись лишние пункты в VIP-меню. -- Изменен натив `VIP_RegisterFeature` для устранения конфликта с `sm_vip_features_default_status`. -- Добавлен натив `VIP_UnregisterMe()`. +### Changes: +- Fixed a logic error in `VIP_UnregisterFeature` that caused extra items to be added to the VIP menu. +- Changed native `VIP_RegisterFeature` to resolve a conflict with `sm_vip_features_default_status`. +- Added native `VIP_UnregisterMe()`. # [VIP] Core 3.0 DEV #12 -### Изменения: -- Исправлена ошибка `Invalid data pack handle 1f4 (error 1)`. +### Changes: +- Fixed error `Invalid data pack handle 1f4 (error 1)`. # [VIP] Core 3.0 DEV #11 -### Изменения: -- Удален натив `VIP_SetFeatureDefStatus`. -- Добавлен параметр в натив `VIP_RegisterFeature`: +### Changes: +- Removed native `VIP_SetFeatureDefStatus`. +- Added a parameter to native `VIP_RegisterFeature`: ``` -@param bDefStatus Значение по-умолчанию (true - Включена, false - Выключена). +@param bDefStatus Default value (true - enabled, false - disabled). ``` -- Исправлена ошибка работы натива `VIP_SetClientFeatureStatus`. -- Исправлена ошибка при отправке rcon команд для работы с вип от имени сервера. +- Fixed native `VIP_SetClientFeatureStatus`. +- Fixed an error when sending rcon commands for VIP management on behalf of the server. # [VIP] Core 3.0 DEV #10 -### Изменения: -- Исправлена ошибка `DataPack operation is out of bounds`. +### Changes: +- Fixed error `DataPack operation is out of bounds`. # [VIP] Core 3.0 DEV #9 -### Изменения: -- Исправлены ошибки компиляции. +### Changes: +- Fixed compilation errors. # [VIP] Core 3.0 DEV #8 -### Изменения: -- Добавлен параметр в натив `VIP_SetClientFeatureStatus`: +### Changes: +- Added a parameter to native `VIP_SetClientFeatureStatus`: ``` -@param bCallback Вызывать ли toggle каллбэк. +@param bCallback Whether to call the toggle callback. ``` # [VIP] Core 3.0 DEV #7 -### Изменения: -- Доработано меню управления VIP-игроками. -- Добавлены форварды: +### Changes: +- Improved the VIP player management menu. +- Added forwards: ``` forward void VIP_OnFeatureRegistered(const char[] sFeatureName); forward void VIP_OnFeatureUnregistered(const char[] sFeatureName); ``` -- Теперь `VIP_OnVIPClientLoaded` вызывается когда игрок уже полностью был загружен. -- Изменены нативы: +- `VIP_OnVIPClientLoaded` is now called when the player has been fully loaded. +- Changed natives: ``` native void VIP_GetClientID(int iClient); -// На +// To native int VIP_GetClientID(int iClient); native void VIP_SendClientVIPMenu(int iClient); -// На +// To native void VIP_SendClientVIPMenu(int iClient, bool bSelection = false); ``` -- Добавлены нативы: +- Added natives: ``` native VIP_FeatureType VIP_GetFeatureType(const char[] sFeatureName); native VIP_ValueType VIP_GetFeatureValueType(const char[] sFeatureName); @@ -243,49 +243,49 @@ native int VIP_FillArrayByFeatures(ArrayList hArray); # [VIP] Core 3.0 DEV #6 -### Изменения: -- Исправлены ошибки. +### Changes: +- Fixed errors. # [VIP] Core 3.0 DEV #5 -### Изменения: -- Исправлены ошибки. +### Changes: +- Fixed errors. # [VIP] Core 3.0 DEV #4 -### Изменения: -- Полный перевод на новый синтаксис (`newdecls required`) -- VIP_OnVIPClientLoaded теперь вызывается когда полностью загружены настройки игрока. -- Изменен натив `VIP_SetClientVIP`. -- Удалены лишние фразы из файла перевода. +### Changes: +- Full transition to the new syntax (`newdecls required`) +- VIP_OnVIPClientLoaded is now called when the player's settings are fully loaded. +- Changed native `VIP_SetClientVIP`. +- Removed unused phrases from the translation file. # [VIP] Core 3.0 DEV #3 -### Изменения: -- Удалены все типы авторизации кроме AUTH_STEAM. -- Изменена команда sm_addvip. -- Временно отключены все функции админ-меню, кроме добавления VIP-игроков. +### Changes: +- Removed all authorization types except AUTH_STEAM. +- Changed the sm_addvip command. +- Temporarily disabled all admin menu functions except adding VIP players. # [VIP] Core 3.0 DEV #2 -### Изменения: -- Перевод на новый синтаксис файла vars.sp и частичный перевод других. -- Удалены пароли (из базы, запросов, api, cvars). +### Changes: +- Ported the vars.sp file to the new syntax and partially ported others. +- Removed passwords (from the database, queries, api, cvars). # [VIP] Core 3.0 DEV #1 -### Изменения: -- Корректная компиляция на см >= 1.7. -- Добавлена переменная time в информационных сообщениях типа меню. Отвечает за время отображения меню игроку. -- Исправлен сброс настроек при перезаходе в случае когда куки не успели загрузится. +### Changes: +- Correct compilation on sm >= 1.7. +- Added a time variable to menu-type info messages. It sets how long the menu is shown to the player. +- Fixed settings being reset on rejoin when cookies had not finished loading. # [VIP] Core 3.0 DEV -### Что изменено: -- Добавлена директива USE_ADMINMENU, которая позволяет компилировать ядро с админкой vip и без неё. -- Добавлен квар sm_vip_add_item_to_admin_menu - Добавить пункт "Управление VIP" в админ-меню. -- Добавлено отдельно меню администрирования VIP, команда для открытия sm_vipadmin, !vipadmin, /vipadmin. -- Удален частный форвард спавна игрока (и нативы хука/анхука). -- Теперь для работы требуется SM 1.5.0 или выше. +### Changes: +- Added the USE_ADMINMENU directive, which allows compiling the core with or without the VIP admin menu. +- Added cvar sm_vip_add_item_to_admin_menu - add the "VIP Management" item to the admin menu. +- Added a separate VIP administration menu, opened with the command sm_vipadmin, !vipadmin, /vipadmin. +- Removed the private player spawn forward (and the hook/unhook natives). +- SM 1.5.0 or higher is now required to work. From 4bdd257239f4c066824f065068472312d6cde7ff Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 21:04:23 +0300 Subject: [PATCH 44/46] Move the module update guide and issue labels guide to docs/, translate them to English and link them from the README. --- README.md | 21 ++++------ docs/issue_labels.md | 28 ++++++------- docs/update_modules.md | 94 +++++++++++++++++++++--------------------- 3 files changed, 69 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 7556f02..6e9d9e6 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,15 @@ Download latest vesrion [https://github.com/R1KO/VIP-Core/releases](Releases) ## [VIP] Core -RU: Плагин позволяет выдавать игрокам VIP-статус на определенный срок или навсегда. -Учитывая модульную систему плагина вы всегда имеете возможность расширить функционал плагина. -Плагин очень гибкий и удобный в настройке, может работать как с SQLite так и с MySQL базой. - - -[RU: [VIP] Core Официальная тема на HlMod.ru](http://hlmod.ru/resources/vip-core.245/) - -[RU: Документация](https://r1ko.github.io/VIP-Core/) - - -EN: This plugin allows you to give VIP status to players for a specific period or forever. +This plugin allows you to give VIP status to players for a specific period or forever. Given the plugin’s modular system, the functionality can always be expanded. The plugin is very flexible and easy to configure, it can work with both SQLite and MySQL database. -[EN: [VIP] Core official theme on HlMod.ru](http://hlmod.ru/resources/vip-core.245/) +[[VIP] Core official thread on HlMod.ru](http://hlmod.ru/resources/vip-core.245/) + +[Documentation](https://r1ko.github.io/VIP-Core/) -[EN: Documentation](https://r1ko.github.io/VIP-Core/) +### Guides +- [Module update guide](docs/update_modules.md) +- [Issue labels](docs/issue_labels.md) +- [Changelog](updates.md) diff --git a/docs/issue_labels.md b/docs/issue_labels.md index 22941e7..2a3c49c 100644 --- a/docs/issue_labels.md +++ b/docs/issue_labels.md @@ -1,14 +1,14 @@ -# Issues labels | Метки проблем - - - `Приоритет (Priority)` - это атрибут, указывающий на очередность выполнения задачи или устранения дефекта. - - `Высокий приоритет P1 (High)` - Ошибка должна быть исправлена как можно быстрее, т.к. ее наличие является критической для проекта. - - `Средний приоритет P2 (Medium)` - Ошибка должна быть исправлена, ее наличие не является критичной, но требует обязательного решения. - - `Низкий приоритет P3 (Low)` - Ошибка должна быть исправлена, ее наличие не является критичной, и не требует срочного решения. - - `Серьезность (Severity)` - это атрибут, характеризующий влияние дефекта на работоспособность приложения. - - `Блокирующая ошибка S1 (Blocker)` - Блокирующая ошибка, приводящая приложение в нерабочее состояние, в результате которого дальнейшая работа с тестируемой системой или ее ключевыми функциями становится невозможна. Решение проблемы необходимо для дальнейшего функционирования системы. - - `Критическая ошибка S2 (Critical)` - Критическая ошибка, неправильно работающая ключевая логика, дыра в системе безопасности, проблема, приведшая к временному падению сервера или приводящая в нерабочее состояние некоторую часть системы, без возможности решения проблемы, используя другие входные точки. Решение проблемы необходимо для дальнейшей работы с ключевыми функциями системы. - - `Значительная ошибка S3 (Major)` - Значительная ошибка, часть основной логики работает некорректно. Ошибка не критична или есть возможность для работы с тестируемой функцией, используя другие входные точки. - - `Незначительная ошибка S4 (Minor)` - Незначительная ошибка, не нарушающая логику части приложения, очевидная проблема пользовательского интерфейса. - - `Тривиальная ошибка S5 (Trivial)` - Тривиальная ошибка, не касающаяся логики приложения, плохо воспроизводимая проблема, малозаметная посредствам пользовательского интерфейса, проблема сторонних библиотек или сервисов, проблема, не оказывающая никакого влияния на общее качество продукта. - - `Вопрос` - - `Предложение` +# Issue labels +- Issue + - `Priority` - the order in which a task should be done or a defect fixed. + - `P1 (High)` - Must be fixed as soon as possible, the defect is critical for the project. + - `P2 (Medium)` - Must be fixed; not critical, but requires a solution. + - `P3 (Low)` - Must be fixed; not critical and not urgent. + - `Severity` - how much the defect affects the application. + - `S1 (Blocker)` - The application doesn't work; further work with the system or its key features is impossible. Must be fixed for the system to function. + - `S2 (Critical)` - Key logic works incorrectly, a security hole, a server crash, or part of the system is broken with no workaround. Must be fixed to keep working with the key features. + - `S3 (Major)` - Part of the main logic works incorrectly. Not critical, or there is a workaround. + - `S4 (Minor)` - Doesn't break the logic; an obvious UI problem. + - `S5 (Trivial)` - Doesn't affect the logic; a hard-to-reproduce or barely visible UI problem, a third-party library/service problem, no effect on overall quality. + - `Question` + - `Suggestion` diff --git a/docs/update_modules.md b/docs/update_modules.md index 7d19c00..e0901a4 100644 --- a/docs/update_modules.md +++ b/docs/update_modules.md @@ -1,22 +1,22 @@ -# Мануал по обновлению модулей +# Module update guide -Обновление позволяет решить такие проблемы: -1. Адекватная загрузка/выгрузка модуля средствами SourceMod (`sm plugins load/reload/unload`) -2. Исправление события возрождения игрока: - - Фатальная ошибка при запуске модуля: `Native "VIP_HookClientSpawn" was not found` - - Предупреждение при компиляции модуля: `warning 234: symbol "VIP_HookClientSpawn" is marked as deprecated: Use VIP_OnPlayerSpawn` - - Фатальная ошибка при компиляции модуля: ` error 017: undefined symbol "VIP_HookClientSpawn"` +The update solves the following problems: +1. Proper loading/unloading of a module with SourceMod tools (`sm plugins load/reload/unload`) +2. Fixing the player spawn event: + - Fatal error when starting the module: `Native "VIP_HookClientSpawn" was not found` + - Warning when compiling the module: `warning 234: symbol "VIP_HookClientSpawn" is marked as deprecated: Use VIP_OnPlayerSpawn` + - Fatal error when compiling the module: ` error 017: undefined symbol "VIP_HookClientSpawn"` -Для примера будет использован следующий код: +The following code is used as an example: ```cpp -// Это уникальное имя ф-и. Оно разное в каждом модуле +// This is the unique feature name. It is different in every module #define VIP_MODULE "module1" -// Может выглядеть так +// It may also look like this new const String:g_sFeature[] = "module1"; public OnPluginStart() { - // код + // code } public VIP_OnVIPLoaded() @@ -24,45 +24,45 @@ public VIP_OnVIPLoaded() VIP_RegisterFeature(VIP_MODULE, BOOL); } ``` -1. Если уникальное имя ф-и выглядит так: +1. If the unique feature name looks like this: ```cpp #define VIP_MODULE "module1" ``` -То меняем на +Change it to ```cpp new const String:g_sFeature[] = "module1"; ``` -Или для нового синтаксиса +Or, for the new syntax ```cpp static const char g_sFeature[] = "module1"; ``` -2. Затем заменяем все `VIP_MODULE` (может быть другим) на `g_sFeature` -3. Ищем функцию +2. Then replace every `VIP_MODULE` (it may be named differently) with `g_sFeature` +3. Find the function ```cpp public OnPluginStart() { - // код + // code } ``` -И добавляем в её конце +And add the following at its end ```cpp if(VIP_IsVIPLoaded()) { VIP_OnVIPLoaded(); } ``` -Должно получиться так: +It should look like this: ```cpp public OnPluginStart() { - // код + // code if(VIP_IsVIPLoaded()) { VIP_OnVIPLoaded(); } } ``` -Если же функции `OnPluginStart()` нет то дописываем её: +If there is no `OnPluginStart()` function, add it: ```cpp public OnPluginStart() { @@ -72,18 +72,18 @@ public OnPluginStart() } } ``` -4. Дальше ищем функцию +4. Next, find the function ```cpp public OnPluginEnd() { - // код + // code } ``` -Чаще всего её нет. Поэтом добавляем её: +Most often it does not exist, so add it: ```cpp public OnPluginEnd() { - // код (если он был) + // code (if there was any) if(CanTestFeatures() && GetFeatureStatus(FeatureType_Native, "VIP_UnregisterFeature") == FeatureStatus_Available) { VIP_UnregisterFeature(g_sFeature); @@ -91,13 +91,13 @@ public OnPluginEnd() } ``` -После этого проблема `#1` будет решена. +After this, problem `#1` is solved. -Еще 1 пример для модулей с 2-я функциями (на подобии скинов, трейлов и т.д.): -- Функция вкл/выкл -- Функция настройки +One more example, for modules with 2 features (like skins, trails, etc.): +- A toggle (on/off) feature +- A settings feature -Выглядят они примерно так: +They look roughly like this: ```cpp #define VIP_SKINS "Skins" #define VIP_SKINS_MENU "Skins_Menu" @@ -108,37 +108,37 @@ public VIP_OnVIPLoaded() VIP_RegisterFeature(VIP_SKINS_MENU, _, SELECTABLE, OnSelectItem, _, OnDrawItem); } ``` -Для обновления такого типа модулей делаем так: -1. Заменяем объявление ф-й +To update this type of module, do the following: +1. Replace the feature declarations ```cpp #define VIP_SKINS "Skins" #define VIP_SKINS_MENU "Skins_Menu" ``` -на +with ```cpp static const String:g_sFeature[][] = {"Skins", "Skins_Menu"}; ``` -Или для нового синтаксиса +Or, for the new syntax ```cpp static const char g_sFeature[][] = {"Skins", "Skins_Menu"}; ``` -2. Заменяем все `VIP_SKINS` на `g_sFeature[0]` и `VIP_SKINS_MENU` на `g_sFeature[1]` -3. Изменяем/добавляем `OnPluginStart()` +2. Replace every `VIP_SKINS` with `g_sFeature[0]` and `VIP_SKINS_MENU` with `g_sFeature[1]` +3. Modify/add `OnPluginStart()` ```cpp public OnPluginStart() { - // Код если был + // Code, if there was any if(VIP_IsVIPLoaded()) { VIP_OnVIPLoaded(); } } ``` -4. Изменяем/добавляем `OnPluginEnd()` +4. Modify/add `OnPluginEnd()` ```cpp public OnPluginEnd() { - // код (если он был) + // code (if there was any) if(CanTestFeatures() && GetFeatureStatus(FeatureType_Native, "VIP_UnregisterFeature") == FeatureStatus_Available) { VIP_UnregisterFeature(g_sFeature[0]); @@ -147,19 +147,19 @@ public OnPluginEnd() } ``` -Главное соблюдать закономерность: ->Для каждой ```VIP_RegisterFeature``` в ```VIP_OnVIPLoaded()``` должна быть ```VIP_UnregisterFeature``` в ```OnPluginEnd()``` +The main thing is to follow this rule: +>Every ```VIP_RegisterFeature``` in ```VIP_OnVIPLoaded()``` must have a matching ```VIP_UnregisterFeature``` in ```OnPluginEnd()``` -Больше примеров можете посмотреть в уже обновлённых модулях. +You can find more examples in the modules that have already been updated. -Для решения проблемы `#2`: -1. В `OnPluginStart()` ищем `VIP_HookClientSpawn(OnPlayerSpawn);` -2. Удаляем эту строку -3. Далее ищем: +To solve problem `#2`: +1. In `OnPluginStart()` find `VIP_HookClientSpawn(OnPlayerSpawn);` +2. Delete this line +3. Then find: ```cpp public OnPlayerSpawn(iClient, iTeam, bool:bIsVIP) ``` -и заменяем на +and replace it with ```cpp public VIP_OnPlayerSpawn(iClient, iTeam, bool:bIsVIP) ``` From dbf776aba16e4cec2077d0f8f5c914c51315a8fe Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 21:08:16 +0300 Subject: [PATCH 45/46] Bump version to 3.1.0 R. --- addons/sourcemod/scripting/VIP_Core.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/VIP_Core.sp b/addons/sourcemod/scripting/VIP_Core.sp index e204cb7..257c696 100644 --- a/addons/sourcemod/scripting/VIP_Core.sp +++ b/addons/sourcemod/scripting/VIP_Core.sp @@ -7,7 +7,7 @@ #include #if !defined VIP_VERSION -#define VIP_VERSION "3.0.7 R" +#define VIP_VERSION "3.1.0 R" #endif From da91a52c87b025a00068d2722cf7740d4daf73a4 Mon Sep 17 00:00:00 2001 From: R1KO Date: Sat, 10 Oct 2026 21:11:04 +0300 Subject: [PATCH 46/46] Remove updates.md: its 3.0 DEV history now lives in the 3.0 R release notes, and the changelog is kept in GitHub Releases. --- updates.md | 291 ----------------------------------------------------- 1 file changed, 291 deletions(-) delete mode 100644 updates.md diff --git a/updates.md b/updates.md deleted file mode 100644 index 62784bb..0000000 --- a/updates.md +++ /dev/null @@ -1,291 +0,0 @@ -# [VIP] Core 3.0 DEV #41 - -### Changes: -- Improved SQL query formation for `USE_MORE_SERVERS`. - -# [VIP] Core 3.0 DEV #40 - -### Changes: -- Changed native `VIP_RegisterFeature` (added parameter `bCookie` - whether to register cookies for the feature (only valid for the SELECTABLE type)). - -# [VIP] Core 3.0 DEV #39 - -### Changes: -- Fixed error `SQL_Callback_RemoveClient: no such column: sid` on SQLite. - -# [VIP] Core 3.0 DEV #38 - -### Changes: -- Fixed error `SQL_Callback_ErrorCheck: no such column: sid` on SQLite. - -# [VIP] Core 3.0 DEV #37 - -### Changes: -- Fixed an error where expired VIP players were not being removed. - -# [VIP] Core 3.0 DEV #36 - -### Changes: -- Now properly fixed the `Invalid Handle 0 (error 4)` error (`DB_RemoveClientFromID`). - -# [VIP] Core 3.0 DEV #35 - -### Changes: -- Fixed error `Invalid Handle 0 (error 4)` (`DB_RemoveClientFromID`). - -# [VIP] Core 3.0 DEV #34 - -### Changes: -- Native `VIP_RemoveClientVIP` is marked as deprecated and will be removed in the future. -- Added new native `VIP_RemoveClientVIP2`. -- Fixed error `Client index 0 is invalid` when adding a VIP player via `sm_addvip`. - -# [VIP] Core 3.0 DEV #33 - -### Changes: -- Changed native `VIP_SetClientFeatureStatus`. -- Fixed error `SQL_Callback_SelectExpiredAndOutdated: Unknown column 'iTime' in 'where clause'`. -- Reworked the logging system. - -# [VIP] Core 3.0 DEV #32 (thanks to DarklSide for finding the errors) - -### Changes: -- Fixed the description of forward `VIP_OnClientPreLoad`. -- Fixed native `VIP_GiveClientFeature`. -- Fixed error `SQL_Callback_SelectExpiredAndOutdated: no such function: UNIX_TIMESTAMP` on SQLite. -- Fixed player nickname updates. -- Fixed DataPack handling on sm 1.10. - -# [VIP] Core 3.0 DEV #31 - -### Changes: -- Added forward `VIP_OnClientPreLoad`. -- Fixed and enabled native `VIP_GiveClientFeature`. -- Added native `VIP_RemoveClientFeature`. -- Changed native `VIP_CheckClient`. -- Changed the default value of `sm_vip_delete_outdated` to `-1`. -- Fixed removal of expired and inactive players. - -# [VIP] Core 3.0 DEV #30 - -### Changes: -- Fixed the VIP players list. - -# [VIP] Core 3.0 DEV #29 - -### Changes: -- Fixed SQL errors. -- Minor optimization. -- Fixed debugging. - -# [VIP] Core 3.0 DEV #28 - -### Changes: -- Fixed the error that prevented the plugin from starting: `Fatal error creating dynamic native!/unexpected error 23 in AskPluginLoad callback`. -- Changed the database name from `"vip"` to `"vip_core"` (already in version 3.0 DEV #25, just forgot to mention it). -- Fixed an attempt to set the encoding for the SQLite database. - -# [VIP] Core 3.0 DEV #27 - -### Changes: -- Enabled the `sm_vip_delete_outdated` cvar. -- Full transition to the new database structure. - -# [VIP] Core 3.0 DEV #26 - -### Changes: -- Restored the previous form of native VIP_SetClientVIP and marked it as deprecated. -- Added new native `VIP_GiveClientVIP` as a replacement for the deprecated `VIP_SetClientVIP`. -- Added backward compatibility for native `VIP_RemoveClientVIP`. -- Changed the table structure (MySQL). - -# [VIP] Core 3.0 DEV #25 - -### Changes: -- Added use of `TranslationPhraseExists` to prevent errors (when a module's phrase is missing from the translation). -- Started reworking debugging into a more extended and flexible form. -- Added cvar `sm_vip_delete_outdated` (not functional yet). - -# [VIP] Core 3.0 DEV #24 - -### Changes: -- Fixed SQL errors. - -# [VIP] Core 3.0 DEV #23 - -### Changes: -- Removed the `id` column from the database. -- Fixed error `SQL_Callback_SelectVipClientInfo` . -- Fixed error `SQL_Callback_ChangeTime: Unknown column 'user_id' in 'where clause'`. - -# [VIP] Core 3.0 DEV #22 - -### Changes: -- Attempt to fix error `Invalid Handle 0 (error 4)`. -- Fixed an error where the `account_id` value in the database became 0 for no-steam players. - -# [VIP] Core 3.0 DEV #21 - -### Changes: -- Fixed error `SQL_Callback_SelectVipPlayers: near ")": syntax error`. - -# [VIP] Core 3.0 DEV #20 - -### Changes: -- Fixed an error where the `name` value in the database became 0. - -# [VIP] Core 3.0 DEV #19 - -### Changes: -- Added multilanguage support for the `"Exit"` button in the info menu. - -# [VIP] Core 3.0 DEV #18 - -### Changes: -- Fixed error `SQL_Callback_SelectVipPlayers: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '0, 60' at line 1`. - -# [VIP] Core 3.0 DEV #17 - -### Changes: -- Added saving of the player's last server login. - -# [VIP] Core 3.0 DEV #16 - -### Changes: -- Players are now stored in the database by AccountID. -- Fixed the `sm_addvip` command: - - It can now accept: SteamID2, SteamID3 and a nickname. - - Fixed granting VIP status to offline players. - -# [VIP] Core 3.0 DEV #15 - -### Changes: -- Added a fix for the MOTD window on CS:GO. -- Added the FIX_CSGO_MOTD directive, which allows compiling the core with or without the MOTD fix. - -# [VIP] Core 3.0 DEV #14 - -### Changes: -- Updated the API: - - Added forward `VIP_OnShowClientInfo`. - - Changed native `VIP_RemoveClientVIP`. - - Improved and fixed the descriptions of all natives and forwards. -- Updated file `addons/sourcemod/data/vip/cfg/info.ini` (fixed the description and typos, added English translation). - -# [VIP] Core 3.0 DEV #13 - -### Changes: -- Fixed a logic error in `VIP_UnregisterFeature` that caused extra items to be added to the VIP menu. -- Changed native `VIP_RegisterFeature` to resolve a conflict with `sm_vip_features_default_status`. -- Added native `VIP_UnregisterMe()`. - -# [VIP] Core 3.0 DEV #12 - -### Changes: -- Fixed error `Invalid data pack handle 1f4 (error 1)`. - -# [VIP] Core 3.0 DEV #11 - -### Changes: -- Removed native `VIP_SetFeatureDefStatus`. -- Added a parameter to native `VIP_RegisterFeature`: -``` -@param bDefStatus Default value (true - enabled, false - disabled). -``` -- Fixed native `VIP_SetClientFeatureStatus`. -- Fixed an error when sending rcon commands for VIP management on behalf of the server. - -# [VIP] Core 3.0 DEV #10 - -### Changes: -- Fixed error `DataPack operation is out of bounds`. - -# [VIP] Core 3.0 DEV #9 - -### Changes: -- Fixed compilation errors. - -# [VIP] Core 3.0 DEV #8 - -### Changes: -- Added a parameter to native `VIP_SetClientFeatureStatus`: -``` -@param bCallback Whether to call the toggle callback. -``` - -# [VIP] Core 3.0 DEV #7 - -### Changes: -- Improved the VIP player management menu. -- Added forwards: -``` -forward void VIP_OnFeatureRegistered(const char[] sFeatureName); -forward void VIP_OnFeatureUnregistered(const char[] sFeatureName); -``` -- `VIP_OnVIPClientLoaded` is now called when the player has been fully loaded. -- Changed natives: -``` -native void VIP_GetClientID(int iClient); -// To -native int VIP_GetClientID(int iClient); - -native void VIP_SendClientVIPMenu(int iClient); -// To -native void VIP_SendClientVIPMenu(int iClient, bool bSelection = false); -``` -- Added natives: -``` -native VIP_FeatureType VIP_GetFeatureType(const char[] sFeatureName); -native VIP_ValueType VIP_GetFeatureValueType(const char[] sFeatureName); -native void VIP_SetFeatureDefStatus(const char[] sFeatureName, bool bStatus); -native int VIP_FillArrayByFeatures(ArrayList hArray); -``` - -# [VIP] Core 3.0 DEV #6 - -### Changes: -- Fixed errors. - -# [VIP] Core 3.0 DEV #5 - -### Changes: -- Fixed errors. - -# [VIP] Core 3.0 DEV #4 - -### Changes: -- Full transition to the new syntax (`newdecls required`) -- VIP_OnVIPClientLoaded is now called when the player's settings are fully loaded. -- Changed native `VIP_SetClientVIP`. -- Removed unused phrases from the translation file. - - -# [VIP] Core 3.0 DEV #3 - -### Changes: -- Removed all authorization types except AUTH_STEAM. -- Changed the sm_addvip command. -- Temporarily disabled all admin menu functions except adding VIP players. - - -# [VIP] Core 3.0 DEV #2 - -### Changes: -- Ported the vars.sp file to the new syntax and partially ported others. -- Removed passwords (from the database, queries, api, cvars). - -# [VIP] Core 3.0 DEV #1 - -### Changes: -- Correct compilation on sm >= 1.7. -- Added a time variable to menu-type info messages. It sets how long the menu is shown to the player. -- Fixed settings being reset on rejoin when cookies had not finished loading. - -# [VIP] Core 3.0 DEV - -### Changes: -- Added the USE_ADMINMENU directive, which allows compiling the core with or without the VIP admin menu. -- Added cvar sm_vip_add_item_to_admin_menu - add the "VIP Management" item to the admin menu. -- Added a separate VIP administration menu, opened with the command sm_vipadmin, !vipadmin, /vipadmin. -- Removed the private player spawn forward (and the hook/unhook natives). -- SM 1.5.0 or higher is now required to work.