Merge pull request #50 from ByDexterTR/main

v1.2.3.b6
This commit is contained in:
Juzlus 2026-08-10 00:30:17 +02:00 • committed by GitHub
commit 38a7cb8c5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
93 changed files with 3414 additions and 329 deletions

View file

@ -31,7 +31,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.2.3.b5";
public override string ModuleVersion => "1.2.3.b6";
public override void Load(bool hotReload)
{
@ -371,6 +371,18 @@ namespace src
public int HideHUD { get; set; }
public bool SkillUsed = false;
public bool? HudOnDeathBlocked { get; set; }
public HudCacheEntry? HudCache { get; set; }
}
public sealed class HudCacheEntry
{
public uint PlayerIndex;
public object? Config;
public string? Header;
public string? Center;
public string? Extra;
public bool IsDescription;
public string? Content;
}
public class jSkill_SkillInfo(Skills skill, string color, bool display)

View file

@ -60,6 +60,13 @@
"blastshot": "Explosivschuss",
"blastshot_desc": "Drücke Attack2 mit der MP5, um eine HE-Granate abzufeuern",
"bounty": "Kopfgeld",
"bounty_desc": "Setze ein Kopfgeld auf einen Gegner aus; wer ihn tötet, bekommt das Geld",
"bounty_select_info": "Wähle einen Spieler:",
"bounty_player_info": "Du hast {1}$ Kopfgeld auf '{0}' ausgesetzt.",
"bounty_enemy_info": "Auf deinen Kopf wurden {0}$ ausgesetzt.",
"bounty_claimed_info": "Du hast '{0}' getötet und {1}$ kassiert.",
"bunnyhop": "Hase",
"bunnyhop_desc": "Du erhältst automatisch bunnyhop",
@ -144,6 +151,10 @@
"dwarf_desc": "Zufällige Charaktergröße zu Beginn der Runde",
"dwarf_desc2": "Dein Größenmultiplikator beträgt: {0}x",
"empgrenade": "EMP-Granate",
"empgrenade_desc": "Wer von deiner Granate getroffen wird, verliert kurzzeitig Radar und Fadenkreuz",
"empgrenade_enemy_info": "Eine EMP-Granate hat dein Radar und Fadenkreuz lahmgelegt.",
"enemyspawn": "Feindes-Spawn",
"enemyspawn_desc": "Klicke auf [css_useSkill], um zum Feindes-Spawn zu teleportieren",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Keine Oberfläche zum Einhaken.",
"grapple_pulling_info": "ZUG",
"gravitydecoy": "Schwerkraft-Ablenker",
"gravitydecoy_desc": "Dein Ablenkungsköder verändert die Schwerkraft aller in der Nähe",
"grenadier": "Grenadier",
"grenadier_desc": "Du hast unendlich viele Granaten",
@ -239,6 +253,9 @@
"healingchicken": "Heilendes Huhn",
"healingchicken_desc": "Deine Hühner heilen dich, solange du in ihrer Nähe bist",
"heavyweight": "Schwergewicht",
"heavyweight_desc": "Fähigkeiten, die dich stoßen oder verlangsamen, wirken nicht auf dich",
"hermit": "Einsiedler",
"hermit_desc": "Durch das Töten von Gegnern werden Munition und ein Teil der Gesundheit wiederhergestellt",
@ -360,6 +377,12 @@
"bankrupt_player_info": "Der Spieler ‚{0}‘ ist bankrott.",
"bankrupt_select_info": "Wähle einen Spieler aus, der bankrott gehen soll:",
"nemesis": "Nemesis",
"nemesis_desc": "Der von dir markierte Spieler erleidet zusätzlichen Schaden",
"nemesis_select_info": "Wähle einen Spieler:",
"nemesis_player_info": "Du hast '{0}' markiert.",
"nemesis_enemy_info": "Du wurdest als Nemesis markiert und erleidest jetzt von allen zusätzlichen Schaden.",
"nightmare": "Albtraum",
"nightmare_desc": "Lass einen ausgewählten Gegner eine schreckliche Vision erleben",
"nightmare_disable_info": "Der Albtraum ist vorbei.",
@ -423,6 +446,10 @@
"prosthesis": "Prothese",
"prosthesis_desc": "Arme und Beine sind kugelsicher",
"punisher": "Bestrafer",
"punisher_desc": "Du sammelst einen Teil des erlittenen Schadens und legst ihn auf deinen nächsten Treffer",
"punisher_released_info": "Du hast {0} zusätzlichen Schaden verursacht und ebenso viel Leben erhalten.",
"push": "Pusher",
"push_desc": "Du hast eine zufällige Chance, einen Gegner zurückzustoßen, wenn du ihn triffst",
"push_desc2": "Deine Chancen, den Gegner zurückzustoßen, betragen: {0} %",
@ -460,6 +487,10 @@
"returntosender": "Zurück zum Absender",
"returntosender_desc": "Der erste Treffer eines Gegners schickt ihn zurück zu seinem Spawn",
"rewind": "Rückspulen",
"rewind_desc": "Drücke [css_useSkill], um hier eine Marke zu setzen und kurz darauf dorthin zurückzukehren",
"rewind_pending_info": "RÜCKKEHR: {0}",
"richboy": "Rich Boy",
"richboy_desc": "Du erhältst zu Beginn der Runde einen zufälligen Geldbetrag",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Stolperdraht platziert.",
"tripwire_triggered_info": "'{0}' hat deinen Stolperdraht ausgelöst.",
"truearmor": "Echte Rüstung",
"truearmor_desc": "Ein Teil des Schadens geht auf deine Rüstung statt auf dein Leben",
"voodoo": "Voodoo",
"voodoo_desc": "Der markierte Gegner erleidet einen Teil des Schadens, den du erleidest",
"voodoo_select_info": "Wähle einen Spieler:",
"voodoo_player_info": "Du hast '{0}' an deinen Voodoo gebunden.",
"wallhack": "Wallhack",
"wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen",

View file

@ -60,6 +60,13 @@
"blastshot": "Blast Shot",
"blastshot_desc": "Press Attack2 with the MP5 to fire an HE grenade",
"bounty": "Bounty",
"bounty_desc": "Put a price on an enemy's head; whoever kills them takes the money",
"bounty_select_info": "Choose a player:",
"bounty_player_info": "You put a {1}$ bounty on '{0}'.",
"bounty_enemy_info": "A {0}$ bounty was placed on your head.",
"bounty_claimed_info": "You killed '{0}' and claimed {1}$.",
"bunnyhop": "Bunny",
"bunnyhop_desc": "You get auto \"BunnyHop\"",
@ -137,6 +144,10 @@
"dwarf_desc": "Random character size at the start of the round",
"dwarf_desc2": "Your size multiplier is: {0}x",
"empgrenade": "EMP Grenade",
"empgrenade_desc": "Anyone hurt by your grenade loses their radar and crosshair for a while",
"empgrenade_enemy_info": "An EMP grenade knocked out your radar and crosshair.",
"enemyspawn": "Enemy Spawn",
"enemyspawn_desc": "Click [css_useSkill] to teleport to the enemy spawn",
@ -223,6 +234,9 @@
"grapple_no_anchor_info": "No surface to hook onto.",
"grapple_pulling_info": "PULLING",
"gravitydecoy": "Gravity Decoy",
"gravitydecoy_desc": "Your decoy changes the gravity of everyone nearby",
"grenadier": "Grenadier",
"grenadier_desc": "You have infinite hegrenade",
@ -232,6 +246,9 @@
"healingsmoke": "Healing Smoke",
"healingsmoke_desc": "Your smoke grenades heal",
"heavyweight": "Heavyweight",
"heavyweight_desc": "Skills that push or slow you down have no effect on you",
"hermit": "Hermit",
"hermit_desc": "Killing restores ammo and a portion of health",
@ -360,6 +377,12 @@
"demoneye": "Demon Eye",
"demoneye_desc": "You deal damage to every enemy you are looking at",
"nemesis": "Nemesis",
"nemesis_desc": "The player you mark takes extra damage",
"nemesis_select_info": "Choose a player:",
"nemesis_player_info": "You marked '{0}'.",
"nemesis_enemy_info": "You have been marked as a nemesis, you now take extra damage from everyone.",
"nightmare": "Nightmare",
"nightmare_desc": "Force a chosen enemy to experience a terrifying vision",
"nightmare_disable_info": "The nightmare has faded.",
@ -423,6 +446,10 @@
"prosthesis": "Prosthesis",
"prosthesis_desc": "Arms and legs are bulletproof",
"punisher": "Punisher",
"punisher_desc": "You bank part of the damage you take and add it to your next hit",
"punisher_released_info": "You dealt {0} extra damage and healed for the same amount.",
"push": "Pusher",
"push_desc": "You have a random chance to push an enemy back when hitting them",
"push_desc2": "Your chances of pushing back the enemy are: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Return to Sender",
"returntosender_desc": "The first hit on an enemy sends them back to their spawn",
"rewind": "Rewind",
"rewind_desc": "Click [css_useSkill] to drop a marker where you stand and return to it shortly after",
"rewind_pending_info": "RETURN: {0}",
"richboy": "Rich Boy",
"richboy_desc": "You receive a random amount of money at the start of the round",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Tripwire placed.",
"tripwire_triggered_info": "'{0}' triggered your tripwire.",
"truearmor": "True Armor",
"truearmor_desc": "Part of the damage you take is paid by your armor instead of your health",
"voodoo": "Voodoo",
"voodoo_desc": "The enemy you mark also takes part of the damage you take",
"voodoo_select_info": "Choose a player:",
"voodoo_player_info": "You bound '{0}' to your voodoo.",
"wallhack": "Wallhack",
"wallhack_desc": "You can see enemies through walls",

View file

@ -60,6 +60,13 @@
"blastshot": "Tir explosif",
"blastshot_desc": "Appuyez sur Attack2 avec le MP5 pour tirer une grenade HE",
"bounty": "Prime",
"bounty_desc": "Mets une prime sur la tête d'un ennemi ; celui qui le tue empoche l'argent",
"bounty_select_info": "Choisis un joueur :",
"bounty_player_info": "Tu as mis une prime de {1}$ sur '{0}'.",
"bounty_enemy_info": "Une prime de {0}$ a été mise sur ta tête.",
"bounty_claimed_info": "Tu as tué '{0}' et empoché {1}$.",
"bunnyhop": "Lapin",
"bunnyhop_desc": "Vous obtenez l’auto « BunnyHop »",
@ -144,6 +151,10 @@
"dwarf_desc": "Taille du personnage aléatoire au début du round",
"dwarf_desc2": "Votre multiplicateur de taille est : {0}x",
"empgrenade": "Grenade EMP",
"empgrenade_desc": "Ceux que ta grenade blesse perdent leur radar et leur viseur un moment",
"empgrenade_enemy_info": "Une grenade EMP a coupé ton radar et ton viseur.",
"enemyspawn": "Spawn Ennemi",
"enemyspawn_desc": "Appuyez sur [css_useSkill] pour vous téléporter au spawn ennemi",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Aucune surface pour accrocher le grappin.",
"grapple_pulling_info": "TRACTION",
"gravitydecoy": "Leurre gravitationnel",
"gravitydecoy_desc": "Ton leurre modifie la gravité de tous ceux qui sont à proximité",
"grenadier": "Grenadier",
"grenadier_desc": "Tu as des grenades HE infinies",
@ -239,6 +253,9 @@
"healingchicken": "Poulet guérisseur",
"healingchicken_desc": "Vos poulets vous soignent lorsque vous êtes à proximité",
"heavyweight": "Poids lourd",
"heavyweight_desc": "Les compétences qui te poussent ou te ralentissent n'ont aucun effet sur toi",
"hermit": "Ermite",
"hermit_desc": "Tuer restaure des munitions et une partie de vos PV",
@ -360,6 +377,12 @@
"bankrupt_player_info": "Le joueur « {0} » est en faillite.",
"bankrupt_select_info": "Mettre un joueur en faillite :",
"nemesis": "Némésis",
"nemesis_desc": "Le joueur que tu marques subit des dégâts supplémentaires",
"nemesis_select_info": "Choisis un joueur :",
"nemesis_player_info": "Tu as marqué '{0}'.",
"nemesis_enemy_info": "Vous avez été marqué comme némésis, vous subissez désormais des dégâts supplémentaires de la part de tous.",
"nightmare": "Cauchemar",
"nightmare_desc": "Faites subir une vision terrifiante à un ennemi choisi",
"nightmare_disable_info": "Le cauchemar s'est dissipé.",
@ -423,6 +446,10 @@
"prosthesis": "Prothèses",
"prosthesis_desc": "Les bras et les jambes sont pare-balles",
"punisher": "Punisseur",
"punisher_desc": "Tu accumules une partie des dégâts subis et l'ajoutes à ton prochain tir",
"punisher_released_info": "Tu as infligé {0} dégâts en plus et récupéré autant de vie.",
"push": "Pousseur",
"push_desc": "Vous avez une chance aléatoire de repousser un ennemi en le touchant",
"push_desc2": "Votre chance de repousser l’ennemi est : {0}%",
@ -460,6 +487,10 @@
"returntosender": "Retour à l’Expéditeur",
"returntosender_desc": "Le premier coup porté à un ennemi le renvoie à son spawn",
"rewind": "Rembobinage",
"rewind_desc": "Appuie sur [css_useSkill] pour poser un repère ici et y revenir peu après",
"rewind_pending_info": "RETOUR : {0}",
"richboy": "Gosse de Riche",
"richboy_desc": "Vous recevez une somme d’argent aléatoire au début du round",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Fil-piège placé.",
"tripwire_triggered_info": "'{0}' a déclenché votre fil-piège.",
"truearmor": "Vraie armure",
"truearmor_desc": "Une partie des dégâts est absorbée par ton armure au lieu de ta vie",
"voodoo": "Vaudou",
"voodoo_desc": "L'ennemi que tu marques subit aussi une partie des dégâts que tu reçois",
"voodoo_select_info": "Choisis un joueur :",
"voodoo_player_info": "Tu as lié '{0}' à ton vaudou.",
"wallhack": "Wallhack",
"wallhack_desc": "Vous pouvez voir les ennemis à travers les murs",

View file

@ -60,6 +60,13 @@
"blastshot": "Mocne Uderzenie",
"blastshot_desc": "Naciśnij Attack2 na MP5, aby wystrzelić granat HE",
"bounty": "Nagroda",
"bounty_desc": "Wyznacz nagrodę za głowę wroga; kto go zabije, dostaje pieniądze",
"bounty_select_info": "Wybierz gracza:",
"bounty_player_info": "Wyznaczyłeś nagrodę {1}$ za '{0}'.",
"bounty_enemy_info": "Za twoją głowę wyznaczono {0}$.",
"bounty_claimed_info": "Zabiłeś '{0}' i zgarnąłeś {1}$.",
"bunnyhop": "Królik",
"bunnyhop_desc": "Otrzymujesz auto \"BunnyHopa\"",
@ -144,6 +151,10 @@
"dwarf_desc": "Losowa wielkość postaci na początku rundy",
"dwarf_desc2": "Twój mnożnik wielkości to {0}x",
"empgrenade": "Granat EMP",
"empgrenade_desc": "Kto oberwie twoim granatem, na chwilę traci radar i celownik",
"empgrenade_enemy_info": "Granat EMP wyłączył twój radar i celownik.",
"enemyspawn": "Resp Wroga",
"enemyspawn_desc": "Kliknij [css_useSkill], aby teleportować się na resp wroga",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Brak powierzchni do zaczepienia haka.",
"grapple_pulling_info": "PRZYCIĄGANIE",
"gravitydecoy": "Grawitacyjna wabik",
"gravitydecoy_desc": "Twój wabik zmienia grawitację wszystkich w pobliżu",
"grenadier": "Granatnik",
"grenadier_desc": "Masz nieskończone granaty odłamkowe",
@ -239,6 +253,9 @@
"healingchicken": "Lecząca Kura",
"healingchicken_desc": "Twoje kury leczą Cię, gdy jesteś w pobliżu",
"heavyweight": "Waga ciężka",
"heavyweight_desc": "Umiejętności, które cię popychają lub spowalniają, nie działają na ciebie",
"hermit": "Pustelnik",
"hermit_desc": "Zabijanie przywraca amunicję i część zdrowia",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Wymieniłeś swoje pieniądze z graczem '{0}'.",
"moneyswap_select_info": "Zamień się kasą z graczem:",
"nemesis": "Nemezis",
"nemesis_desc": "Oznaczony gracz otrzymuje dodatkowe obrażenia",
"nemesis_select_info": "Wybierz gracza:",
"nemesis_player_info": "Oznaczyłeś '{0}'.",
"nemesis_enemy_info": "Zostałeś oznaczony jako nemezis, teraz otrzymujesz dodatkowe obrażenia od wszystkich.",
"nightmare": "Koszmar",
"nightmare_desc": "Wywołaj u wybranego przeciwnika przerażającą wizję",
"nightmare_disable_info": "Koszmar minął.",
@ -423,6 +446,10 @@
"prosthesis": "Proteza",
"prosthesis_desc": "Ramiona i nogi są kuloodporne",
"punisher": "Karzyciel",
"punisher_desc": "Gromadzisz część otrzymanych obrażeń i dodajesz je do następnego trafienia",
"punisher_released_info": "Zadałeś {0} dodatkowych obrażeń i odzyskałeś tyle samo życia.",
"push": "Odpychacz",
"push_desc": "Masz losową szansę na odepchnięcie wroga po trafieniu go",
"push_desc2": "Twoje szanse na odepchnięcie wroga to: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Zwrot do Nadawcy",
"returntosender_desc": "Pierwsze trafienie wroga powoduje, że wraca on na swój resp",
"rewind": "Przewijanie",
"rewind_desc": "Naciśnij [css_useSkill], aby zostawić znacznik i wkrótce do niego wrócić",
"rewind_pending_info": "POWRÓT: {0}",
"richboy": "Bogacz",
"richboy_desc": "Na początku rundy otrzymujesz losową kwotę gotówki",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Linka została rozstawiona.",
"tripwire_triggered_info": "'{0}' uruchomił Twoją linkę.",
"truearmor": "Prawdziwy pancerz",
"truearmor_desc": "Część obrażeń pokrywa twój pancerz zamiast zdrowia",
"voodoo": "Voodoo",
"voodoo_desc": "Oznaczony wróg również otrzymuje część obrażeń, które ty dostajesz",
"voodoo_select_info": "Wybierz gracza:",
"voodoo_player_info": "Związałeś '{0}' swoim voodoo.",
"wallhack": "Wallhack",
"wallhack_desc": "Widzisz wrogów przez ściany",

View file

@ -60,6 +60,13 @@
"blastshot": "Tiro Explosivo",
"blastshot_desc": "Pressione Attack2 com a MP5 para disparar uma granada HE",
"bounty": "Recompensa",
"bounty_desc": "Coloque um preço na cabeça de um inimigo; quem o matar leva o dinheiro",
"bounty_select_info": "Escolha um jogador:",
"bounty_player_info": "Você colocou {1}$ de recompensa em '{0}'.",
"bounty_enemy_info": "Puseram {0}$ de recompensa na sua cabeça.",
"bounty_claimed_info": "Você matou '{0}' e levou {1}$.",
"bunnyhop": "Coelho",
"bunnyhop_desc": "Você ganha \"BunnyHop\" automático",
@ -144,6 +151,10 @@
"dwarf_desc": "Sua altura agora é aleatória",
"dwarf_desc2": "Seu multiplicador de altura é: {0}x",
"empgrenade": "Granada EMP",
"empgrenade_desc": "Quem se ferir com sua granada perde o radar e a mira por um tempo",
"empgrenade_enemy_info": "Uma granada EMP derrubou seu radar e sua mira.",
"enemyspawn": "Spawn Inimigo",
"enemyspawn_desc": "Clique em [css_useSkill] para se teletransportar para a base inimiga",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Nenhuma superfície para prender o gancho.",
"grapple_pulling_info": "PUXANDO",
"gravitydecoy": "Chamariz Gravitacional",
"gravitydecoy_desc": "Seu chamariz muda a gravidade de todos que estão por perto",
"grenadier": "Bomberman",
"grenadier_desc": "Você tem HEs infinitas",
@ -239,6 +253,9 @@
"healingchicken": "Galinha Curadora",
"healingchicken_desc": "Suas galinhas curam você enquanto você estiver por perto",
"heavyweight": "Peso Pesado",
"heavyweight_desc": "Habilidades que empurram ou te deixam lento não fazem efeito em você",
"hermit": "Sanguessuga",
"hermit_desc": "Matar restaura munição e uma parte da sua vida",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Você trocou dinheiro com o jogador '{0}'.",
"moneyswap_select_info": "Trocar dinheiro c/ jogador:",
"nemesis": "Nêmesis",
"nemesis_desc": "O jogador que você marcar recebe dano extra",
"nemesis_select_info": "Escolha um jogador:",
"nemesis_player_info": "Você marcou '{0}'.",
"nemesis_enemy_info": "Você foi marcado como nêmesis, agora recebe dano extra de todos.",
"nightmare": "Pesadelo",
"nightmare_desc": "Faça um inimigo escolhido ter uma visão aterrorizante",
"nightmare_disable_info": "O pesadelo desapareceu.",
@ -423,6 +446,10 @@
"prosthesis": "Prótese",
"prosthesis_desc": "Braços e pernas são à prova de balas",
"punisher": "Punidor",
"punisher_desc": "Você guarda parte do dano recebido e soma no seu próximo tiro",
"punisher_released_info": "Você causou {0} de dano extra e recuperou a mesma vida.",
"push": "Empurrão",
"push_desc": "Você tem uma chance aleatória de empurrar um inimigo ao acertá-lo",
"push_desc2": "Suas chances de empurrar o inimigo são: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Devolução ao Remetente",
"returntosender_desc": "O primeiro tiro em um inimigo o envia de volta a sua base",
"rewind": "Retroceder",
"rewind_desc": "Clique em [css_useSkill] para deixar uma marca aqui e voltar a ela logo depois",
"rewind_pending_info": "RETORNO: {0}",
"richboy": "Filhinho de Papai",
"richboy_desc": "Você recebe uma quantidade aleatória de dinheiro no início da rodada",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Fio de armadilha colocado.",
"tripwire_triggered_info": "'{0}' acionou seu fio.",
"truearmor": "Armadura Real",
"truearmor_desc": "Parte do dano que você recebe sai do colete em vez da vida",
"voodoo": "Vodu",
"voodoo_desc": "O inimigo que você marcar também recebe parte do dano que você leva",
"voodoo_select_info": "Escolha um jogador:",
"voodoo_player_info": "Você ligou '{0}' ao seu vodu.",
"wallhack": "Wallhack",
"wallhack_desc": "Você pode ver inimigos através das paredes",

View file

@ -60,6 +60,13 @@
"blastshot": "Взрывной выстрел",
"blastshot_desc": "Нажмите Attack2 на MP5, чтобы выстрелить гранатой HE",
"bounty": "Награда",
"bounty_desc": "Назначь награду за голову врага; тот, кто убьёт его, получит деньги",
"bounty_select_info": "Выберите игрока:",
"bounty_player_info": "Вы назначили награду {1}$ за '{0}'.",
"bounty_enemy_info": "За вашу голову назначили {0}$.",
"bounty_claimed_info": "Вы убили '{0}' и получили {1}$.",
"bunnyhop": "Заяц",
"bunnyhop_desc": "Вы получаете автоматический «БанниХоп»",
@ -144,6 +151,10 @@
"dwarf_desc": "Случайный размер персонажа в начале раунда",
"dwarf_desc2": "Ваш множитель размера: {0}x",
"empgrenade": "ЭМИ-граната",
"empgrenade_desc": "Тот, кто пострадал от вашей гранаты, на время теряет радар и прицел",
"empgrenade_enemy_info": "ЭМИ-граната вывела из строя ваш радар и прицел.",
"enemyspawn": "Спавн врагов",
"enemyspawn_desc": "Нажмите [css_useSkill], чтобы телепортироваться на спавн врагов",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Нет поверхности для зацепа.",
"grapple_pulling_info": "ПРИТЯГИВАНИЕ",
"gravitydecoy": "Гравитационная обманка",
"gravitydecoy_desc": "Ваша обманка меняет гравитацию всех, кто рядом",
"grenadier": "Гренадёр",
"grenadier_desc": "У вас бесконечные осколочные гранаты",
@ -239,6 +253,9 @@
"healingsmoke": "Лечащий дым",
"healingsmoke_desc": "Ваши дымовые гранаты лечат",
"heavyweight": "Тяжеловес",
"heavyweight_desc": "Способности, которые толкают или замедляют, на вас не действуют",
"hermit": "Отшельник",
"hermit_desc": "Убийства восстанавливают боеприпасы и часть здоровья",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Вы обменялись деньгами с игроком '{0}'.",
"moneyswap_select_info": "Обменять деньги с игроком:",
"nemesis": "Немезида",
"nemesis_desc": "Отмеченный вами игрок получает больше урона",
"nemesis_select_info": "Выберите игрока:",
"nemesis_player_info": "Вы отметили '{0}'.",
"nemesis_enemy_info": "Вы отмечены как немезида и теперь получаете больше урона от всех.",
"nightmare": "Кошмар",
"nightmare_desc": "Заставьте выбранного противника увидеть кошмарное видение",
"nightmare_disable_info": "Кошмар рассеялся.",
@ -423,6 +446,10 @@
"prosthesis": "Протез",
"prosthesis_desc": "Руки и ноги защищены от пуль",
"punisher": "Каратель",
"punisher_desc": "Вы копите часть полученного урона и добавляете его к следующему попаданию",
"punisher_released_info": "Вы нанесли {0} дополнительного урона и восстановили столько же здоровья.",
"push": "Толкач",
"push_desc": "Есть шанс оттолкнуть врага назад при попадании",
"push_desc2": "Ваш шанс оттолкнуть врага: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Вернуть отправителю",
"returntosender_desc": "Первое попадание по врагу отправляет его обратно на спавн",
"rewind": "Перемотка",
"rewind_desc": "Нажмите [css_useSkill], чтобы оставить метку здесь и вскоре вернуться к ней",
"rewind_pending_info": "ВОЗВРАТ: {0}",
"richboy": "Богач",
"richboy_desc": "Вы получаете случайную сумму денег в начале раунда",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Растяжка установлена.",
"tripwire_triggered_info": "'{0}' задел вашу растяжку.",
"truearmor": "Настоящая броня",
"truearmor_desc": "Часть урона принимает броня, а не здоровье",
"voodoo": "Вуду",
"voodoo_desc": "Отмеченный враг тоже получает часть урона, который получаете вы",
"voodoo_select_info": "Выберите игрока:",
"voodoo_player_info": "Вы привязали '{0}' к своему вуду.",
"wallhack": "Воллхак",
"wallhack_desc": "Вы видите врагов сквозь стены",

View file

@ -60,6 +60,13 @@
"blastshot": "Patlama Atışı",
"blastshot_desc": "MP5 ile sağ tıklayarak el bombası fırlat",
"bounty": "Ödül Avı",
"bounty_desc": "Bir düşmana ödül koy, onu öldüren parayı alır",
"bounty_select_info": "Birini seçin:",
"bounty_player_info": "'{0}' için {1}$ ödül koydun.",
"bounty_enemy_info": "Başına {0}$ ödül kondu.",
"bounty_claimed_info": "'{0}' oyuncusunu öldürdün ve {1}$ kazandın.",
"bunnyhop": "Tavşan",
"bunnyhop_desc": "Otomatik BunnyHop yaparsın",
@ -137,6 +144,10 @@
"dwarf_desc": "Rastgele karakter boyutu",
"dwarf_desc2": "Boyutun: {0}x",
"empgrenade": "EMP Bombası",
"empgrenade_desc": "El bomban, hasar verdiğin düşmanın radarını ve nişangahını kapatır",
"empgrenade_enemy_info": "Radarın ve nişangahın kapandı.",
"enemyspawn": "Base Baskını",
"enemyspawn_desc": "[css_useSkill] tuşuna basarak düşman doğma noktasına ışınlan",
@ -223,6 +234,9 @@
"grapple_no_anchor_info": "Kanca atılacak yüzey yok",
"grapple_pulling_info": "ÇEKİLİYOR",
"gravitydecoy": "Yerçekimi Yanıltıcısı",
"gravitydecoy_desc": "Yanıltıcı bomban yakındaki herkesin yerçekimini değiştirir",
"grenadier": "Bombacı",
"grenadier_desc": "El bomban sınırsız",
@ -232,6 +246,9 @@
"healingsmoke": "Şifalı Sis",
"healingsmoke_desc": "Sis bomban iyileştirir",
"heavyweight": "Ağır Sıklet",
"heavyweight_desc": "Seni iten ve yavaşlatan yetenekler işlemez",
"hermit": "Münzevi",
"hermit_desc": "Birini öldürünce mermin ve canın yenilenir",
@ -360,6 +377,12 @@
"demoneye": "Şeytani Göz",
"demoneye_desc": "Baktığın tüm düşmana hasar verirsin",
"nemesis": "Ezeli Rakip",
"nemesis_desc": "İşaretlediğin düşman herkesten daha fazla hasar alır",
"nemesis_select_info": "Birini seçin:",
"nemesis_player_info": "'{0}' artık herkesten daha fazla hasar alacak.",
"nemesis_enemy_info": "İşaretlendin, artık herkesten daha fazla hasar alıyorsun.",
"nightmare": "Kabus",
"nightmare_desc": "Seçtiğin rakibe kabus gördür",
"nightmare_disable_info": "Kabus sona erdi.",
@ -423,6 +446,10 @@
"prosthesis": "Protez",
"prosthesis_desc": "Kolların ve bacakların kurşun geçirmez",
"punisher": "İntikam",
"punisher_desc": "Aldığın hasarı biriktirir, sonraki vuruşunda geri verirsin",
"punisher_released_info": "{0} ekstra hasar verdin ve o kadar can kazandın.",
"push": "İtici",
"push_desc": "Düşmana vurduğunda onu geri itme şansın olur",
"push_desc2": "Geri itme şansın: %{0}",
@ -460,6 +487,10 @@
"returntosender": "İade",
"returntosender_desc": "Düşmana ilk vuruşun onu kendi başlangıcına ışınlar",
"rewind": "Geri Sarma",
"rewind_desc": "[css_useSkill] tuşuna basarak iz bırak, birazdan oraya geri dön",
"rewind_pending_info": "DÖNÜŞ: {0}",
"richboy": "Zengin Çocuk",
"richboy_desc": "Rastgele miktarda paran olur",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Tel gerildi.",
"tripwire_triggered_info": "'{0}' teline takıldı.",
"truearmor": "Gerçek Zırh",
"truearmor_desc": "Aldığın hasarın bir kısmını canın yerine zırhın karşılar",
"voodoo": "Vudu",
"voodoo_desc": "Aldığın hasarın bir kısmını işaretlediğin düşman da alır",
"voodoo_select_info": "Birini seçin:",
"voodoo_player_info": "'{0}' oyuncusunu kendine bağladın.",
"wallhack": "Wallhack",
"wallhack_desc": "Düşmanları duvar arkasından görürsün",

View file

@ -54,6 +54,13 @@
"blademaster": "我的刀能档子弹!",
"blademaster_desc": "持刀时,你有很高几率格挡子弹",
"bounty": "悬赏",
"bounty_desc": "对一名敌人悬赏,击杀他的人可以获得赏金",
"bounty_select_info": "选择一名玩家:",
"bounty_player_info": "你对 '{0}' 悬赏了 {1}$。",
"bounty_enemy_info": "有人对你悬赏了 {0}$。",
"bounty_claimed_info": "你击杀了 '{0}',获得 {1}$。",
"bunnyhop": "连跳大佬",
"bunnyhop_desc": "你获得自动连跳的能力",
@ -131,6 +138,10 @@
"dwarf_desc": "回合开始时获得随机角色大小",
"dwarf_desc2": "你的体型倍率是:{0}x",
"empgrenade": "电磁脉冲手雷",
"empgrenade_desc": "被你的手雷伤到的人会暂时失去雷达和准星",
"empgrenade_enemy_info": "电磁脉冲手雷让你的雷达和准星失效了。",
"enemyspawn": "传送敌人出生点",
"enemyspawn_desc": "点击 [css_useSkill] 传送到敌人出生点",
@ -211,12 +222,18 @@
"grapple_no_anchor_info": "没有可以钩住的表面。",
"grapple_pulling_info": "拉动中",
"gravitydecoy": "重力诱饵",
"gravitydecoy_desc": "你的诱饵会改变附近所有人的重力",
"grenadier": "投弹手",
"grenadier_desc": "你拥有无限高爆手雷",
"healingsmoke": "治疗烟雾",
"healingsmoke_desc": "你的烟雾弹会治疗",
"heavyweight": "重量级",
"heavyweight_desc": "推开你或让你减速的技能对你无效",
"hermit": "隐士",
"hermit_desc": "击杀敌人会恢复弹药和部分生命值",
@ -330,6 +347,12 @@
"deathbomb_desc": "你死亡时会爆炸,杀死附近的玩家",
"deathbomb_explosion": "砰!",
"nemesis": "宿敌",
"nemesis_desc": "被你标记的玩家会受到额外伤害",
"nemesis_select_info": "选择一名玩家:",
"nemesis_player_info": "你标记了 '{0}'。",
"nemesis_enemy_info": "你被标记为宿敌,现在会受到所有人的额外伤害。",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
@ -393,6 +416,10 @@
"prosthesis": "假肢",
"prosthesis_desc": "手臂和腿部防弹",
"punisher": "惩罚者",
"punisher_desc": "你会积攒受到伤害的一部分,并加到下一次命中上",
"punisher_released_info": "你造成了 {0} 点额外伤害,并回复了同样的生命值。",
"push": "推力者",
"push_desc": "你有随机几率在击中敌人时将其推回",
"push_desc2": "你击退敌人的几率为:{0}%",
@ -430,6 +457,10 @@
"returntosender": "发送他们房子!!!",
"returntosender_desc": "首次击中敌人会将其送回出生点",
"rewind": "回溯",
"rewind_desc": "按 [css_useSkill] 在原地留下标记,稍后回到那里",
"rewind_pending_info": "回溯:{0}",
"richboy": "富少",
"richboy_desc": "回合开始时你获得随机数量的钱",
@ -507,6 +538,14 @@
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"truearmor": "真实护甲",
"truearmor_desc": "你受到的一部分伤害由护甲承担,而不是生命值",
"voodoo": "巫毒",
"voodoo_desc": "被你标记的敌人也会受到你所受伤害的一部分",
"voodoo_select_info": "选择一名玩家:",
"voodoo_player_info": "你把 '{0}' 绑到了你的巫毒上。",
"wallhack": "透视",
"wallhack_desc": "你可以通过墙壁看到敌人",

View file

@ -45,13 +45,13 @@ namespace src.player
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager").Where(t => t != null).ToList();
var tTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.Terrorist);
var ctTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.CounterTerrorist);
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score + 1} (CT {ctTeam?.Score} : {tTeam?.Score} TT) started.");
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score + 1} (CT {ctTeam?.Score} : {tTeam?.Score} TT) started.{WarmupTag()}");
return HookResult.Continue;
});
Instance.RegisterEventHandler<EventRoundFreezeEnd>((@event, info) =>
{
WriteToDebug($"Freeze time ended.");
WriteToDebug($"Freeze time ended.{WarmupTag()}");
return HookResult.Continue;
});
@ -60,7 +60,7 @@ namespace src.player
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager").Where(t => t != null).ToList();
var tTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.Terrorist);
var ctTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.CounterTerrorist);
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score} (CT {ctTeam?.Score} : {tTeam?.Score} TT) ended.");
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score} (CT {ctTeam?.Score} : {tTeam?.Score} TT) ended.{WarmupTag()}");
return HookResult.Continue;
});
@ -137,6 +137,13 @@ namespace src.player
return HookResult.Continue;
}
private static string WarmupTag()
{
var gameRules = Instance?.GameRules;
if (gameRules == null || gameRules.Handle == IntPtr.Zero) return " [gamerules unavailable]";
return gameRules.WarmupPeriod ? " [WARMUP]" : string.Empty;
}
public static void WriteToDebug(string message)
{
if (Config.LoadedConfig.DebugMode != true)

View file

@ -76,6 +76,7 @@ public enum Skills
BladeMaster,
BunnyHop,
BlastShot,
Bounty,
C4Camouflage,
CarefulBullets,
Catapult,
@ -95,6 +96,7 @@ public enum Skills
Dracula,
Duplicator,
Dwarf,
EmpGrenade,
EnemySpawn,
ExpensiveAmmo,
ExplodingBarrel,
@ -116,9 +118,11 @@ public enum Skills
Glue,
GodMode,
Grapple,
GravityDecoy,
Grenadier,
HealingChicken,
HealingSmoke,
Heavyweight,
Hermit,
HolyHandGrenade,
HomingNades,
@ -148,6 +152,7 @@ public enum Skills
Medic,
Miner,
MoneySwap,
Nemesis,
Nightmare,
Ninja,
NoNades,
@ -163,6 +168,7 @@ public enum Skills
Poison,
PrimaryBan,
Prosthesis,
Punisher,
Push,
Pyro,
QuickShot,
@ -175,6 +181,7 @@ public enum Skills
Replicator,
Retreat,
ReturnToSender,
Rewind,
RichBoy,
Ricochet,
RobinHood,
@ -199,6 +206,8 @@ public enum Skills
ThrowingKnife,
ToxicSmoke,
Tripwire,
TrueArmor,
Voodoo,
Wallhack,
Watchmaker,
WeaponsSwap,

View file

@ -33,7 +33,7 @@ namespace src.player
public static readonly SkillsInfo.DefaultSkillInfo[] counterterroristSkills = [.. SkillsInfo.LoadedConfig.Where(s => s.OnlyTeam == (int)CsTeam.CounterTerrorist)];
private static readonly SkillsInfo.DefaultSkillInfo[] allTeamsSkills = [.. SkillsInfo.LoadedConfig.Where(s => s.OnlyTeam == 0)];
private static readonly ConcurrentDictionary<uint, ConcurrentBag<jSkill_SkillInfo>> playersSkills = [];
private static readonly ConcurrentDictionary<uint, HashSet<Skills>> playersSkills = [];
public static readonly ConcurrentDictionary<uint, jSkill_SkillInfo> staticSkills = [];
public static readonly ConcurrentDictionary<uint, uint> playersCustomFOV = [];
private static readonly object setLock = new();
@ -564,6 +564,7 @@ namespace src.player
PlayerManager.UnregisterPlayer(player.Index);
EntityManager.DestroyPlayerEntities(player.Index);
SkillUtils.ClearHudSuppression(player.Index);
return HookResult.Continue;
}
@ -602,6 +603,8 @@ namespace src.player
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) return;
SkillUtils.ClearHudSuppression(player.Index);
pawn.HideHUD = (uint)(pawn.HideHUD & ~(1 << 8));
Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_iHideHUD");
@ -768,6 +771,21 @@ namespace src.player
}
var config = Config.LoadedConfig.HtmlHudCustomisation;
var cache = skillPlayer?.HudCache;
if (cache != null
&& cache.Content != null
&& cache.PlayerIndex == player.Index
&& ReferenceEquals(cache.Config, config)
&& cache.IsDescription == isDescription
&& string.Equals(cache.Header, headerLine, StringComparison.Ordinal)
&& string.Equals(cache.Center, centerLine, StringComparison.Ordinal)
&& string.Equals(cache.Extra, extraLine, StringComparison.Ordinal))
{
player.PrintToCenterHtml(cache.Content);
return;
}
var emptySymbol = $"<font class='fontSize-{(string.IsNullOrEmpty(headerLine) || string.IsNullOrEmpty(config.HeaderLineSize) ? "l" : "ml")}'> </font>";
var emptySymbol2 = $"<font class='fontSize-ml'> </font>";
@ -782,6 +800,19 @@ namespace src.player
: $"<br>{emptySymbol}<font class='fontSize-{(isDescription ? config.SkillDescriptionLineSize : config.InfoLineSize)}' color='{(isDescription ? config.SkillDescriptionLineColor : config.InfoLineColor)}'>{extraLine}</font>{emptySymbol}";
var hudContent = "<jRS/>" + infoLine + skillLine + remainingLine;
if (skillPlayer != null)
{
cache ??= skillPlayer.HudCache = new HudCacheEntry();
cache.PlayerIndex = player.Index;
cache.Config = config;
cache.Header = headerLine;
cache.Center = centerLine;
cache.Extra = extraLine;
cache.IsDescription = isDescription;
cache.Content = hudContent;
}
player.PrintToCenterHtml(hudContent);
}
}

View file

@ -195,6 +195,8 @@ namespace src.player
SkillsUsedThisMap.Clear();
nextRoundPicks.Clear();
SkillUtils.ClearAllHudSuppression();
playersCustomFOV.Clear();
playersSkills.Clear();
staticSkills.Clear();
@ -275,6 +277,7 @@ namespace src.player
public required HashSet<Skills> NeedsTeammates { get; init; }
public required HashSet<Skills> CtOnly { get; init; }
public required HashSet<Skills> TOnly { get; init; }
public required HashSet<Skills> PistolRoundBanned { get; init; }
public required int TerroristCount { get; init; }
public required int CounterTerroristCount { get; init; }
}
@ -296,6 +299,9 @@ namespace src.player
NeedsTeammates = ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates).Select(s => s.Name)),
CtOnly = ToSkillSet(counterterroristSkills.Select(s => s.Name)),
TOnly = ToSkillSet(terroristSkills.Select(s => s.Name)),
PistolRoundBanned = SkillUtils.IsPistolRound()
? ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.DisableOnPistolRound).Select(s => s.Name))
: [],
TerroristCount = validPlayers.Count(p => p.Team == CsTeam.Terrorist),
CounterTerroristCount = validPlayers.Count(p => p.Team == CsTeam.CounterTerrorist),
};
@ -328,9 +334,12 @@ namespace src.player
else
skillList.RemoveAll(s => ctx.TOnly.Contains(s.Skill));
if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag<jSkill_SkillInfo>? skills))
if (ctx.PistolRoundBanned.Count != 0)
skillList.RemoveAll(s => ctx.PistolRoundBanned.Contains(s.Skill));
if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out HashSet<Skills>? skills))
{
skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill));
skillList.RemoveAll(s => skills.Contains(s.Skill));
if (skillList.Count == 0) skills.Clear();
}
@ -338,10 +347,10 @@ namespace src.player
if (gameMode == Config.GameModes.NoRepeat)
{
if (playersSkills.TryGetValue(player.Index, out ConcurrentBag<jSkill_SkillInfo>? value))
value.Add(randomSkill);
if (playersSkills.TryGetValue(player.Index, out HashSet<Skills>? value))
value.Add(randomSkill.Skill);
else
playersSkills.TryAdd(player.Index, [randomSkill]);
playersSkills.TryAdd(player.Index, [randomSkill.Skill]);
}
return randomSkill;
@ -358,6 +367,7 @@ namespace src.player
var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == name);
if (def == null) return false;
if (def.DisableOnPistolRound && SkillUtils.IsPistolRound()) return false;
if (def.NeedsTeammates && validPlayers.Count(p => p.Team == player.Team) == 1) return false;
if (def.MaxPerServer >= 0 && assignmentCounts.TryGetValue(pick.Skill, out var c) && c >= def.MaxPerServer) return false;
@ -648,9 +658,12 @@ namespace src.player
else
skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag<jSkill_SkillInfo>? skills))
if (SkillUtils.IsPistolRound())
skillList.RemoveAll(s => SkillsInfo.GetValue<bool>(s.Skill, "disableOnPistolRound"));
if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out HashSet<Skills>? skills))
{
skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill));
skillList.RemoveAll(s => skills.Contains(s.Skill));
if (skillList.Count == 0) skills.Clear();
}

View file

@ -8,11 +8,9 @@ namespace src.player.skills
public class Assassin : ISkill
{
private const Skills skillName = Skills.Assassin;
private static readonly string[] nadeWeapons =
[
"weapon_inferno", "weapon_molotov", "weapon_incgrenade", "weapon_flashbang",
"weapon_smokegrenade", "weapon_decoy", "weapon_hegrenade"
];
private const string infernoWeapon = "weapon_inferno";
private static bool IsNade(string? weapon) => weapon == infernoWeapon || WeaponPool.IsGrenade(weapon);
public static void LoadSkill()
{
@ -46,7 +44,7 @@ namespace src.player.skills
var weapon = param2.Ability?.Value;
if (weapon == null || !weapon.IsValid) return;
if (nadeWeapons.Contains(weapon.DesignerName)) return;
if (IsNade(weapon.DesignerName)) return;
if (IsBehind(attacker, victim))
param2.Damage *= SkillsInfo.GetValue<float>(skillName, "damageMultiplier");

View file

@ -0,0 +1,175 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class Bounty : ISkill
{
private const Skills skillName = Skills.Bounty;
private static readonly ConcurrentDictionary<uint, byte> bountyTargets = [];
private static readonly ConcurrentDictionary<uint, uint> ownerToTarget = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
bountyTargets.Clear();
ownerToTarget.Clear();
foreach (var player in PlayerManager.GetTickPlayers())
SkillUtils.CloseMenu(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
bountyTargets.TryRemove(playerIndex, out _);
ownerToTarget.TryRemove(playerIndex, out _);
foreach (var kvp in ownerToTarget)
if (kvp.Value == playerIndex)
ownerToTarget.TryRemove(kvp.Key, out _);
}
public static void EnableSkill(CCSPlayerController player)
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) return;
playerInfo.SkillUsed = false;
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.CreateMenu(player, menuItems);
}
else
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
if (ownerToTarget.TryRemove(player.Index, out uint targetIndex))
bountyTargets.TryRemove(targetIndex, out _);
SkillUtils.CloseMenu(player);
}
public static void OnTick()
{
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems);
}
}
public static void TypeSkill(CCSPlayerController player, string[] commands)
{
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
if (playerInfo.SkillUsed)
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("areareaper_used_info")}");
return;
}
if (commands.Length == 0 || !uint.TryParse(commands[0], out uint enemyIndex))
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
return;
}
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (enemy == null || !enemy.IsValid)
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
return;
}
bountyTargets[enemy.Index] = 0;
ownerToTarget[player.Index] = enemy.Index;
playerInfo.SkillUsed = true;
int reward = SkillsInfo.GetValue<int>(skillName, "reward");
playerEvent.PrintToChat($" {ChatColors.Green}{playerEvent.GetTranslation("bounty_player_info", enemy.PlayerName, reward)}");
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent != null && enemyEvent.IsValid)
enemyEvent.PrintToChat($" {ChatColors.Red}{enemyEvent.GetTranslation("bounty_enemy_info", reward)}");
}
public static void PlayerDeath(EventPlayerDeath @event)
{
if (bountyTargets.IsEmpty) return;
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return;
if (!bountyTargets.TryRemove(victim.Index, out _)) return;
foreach (var kvp in ownerToTarget)
if (kvp.Value == victim.Index)
ownerToTarget.TryRemove(kvp.Key, out _);
var killer = PlayerManager.GetPlayerEvent(@event.Attacker);
if (killer == null || !killer.IsValid) return;
if (killer.Index == victim.Index) return;
if (killer.Team == victim.Team) return;
int reward = SkillsInfo.GetValue<int>(skillName, "reward");
if (!GiveMoney(killer, reward)) return;
var killerEvent = PlayerManager.GetPlayerFromEvent(killer);
if (killerEvent != null && killerEvent.IsValid)
killerEvent.PrintToChat($" {ChatColors.Green}{killerEvent.GetTranslation("bounty_claimed_info", victim.PlayerName, reward)}");
}
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000;
private static bool GiveMoney(CCSPlayerController player, int amount)
{
var moneyServices = player.InGameMoneyServices;
if (moneyServices == null) return false;
int maxMoney = GetMaxMoney();
if (moneyServices.Account >= maxMoney) return false;
moneyServices.Account = Math.Clamp(moneyServices.Account + amount, 0, maxMoney);
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
return true;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#f2c14e", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int reward = 300) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int Reward { get; set; } = reward;
}
}
}

View file

@ -105,35 +105,27 @@ namespace src.player.skills
var bomb = PlayerManager.GetTickBomb();
var hidden = SkillUtils.ResolveHiddenPawns(invisiblePlayers.Keys, null);
if (hidden.Count == 0) return;
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
foreach (var playerIndex in invisiblePlayers.Keys)
foreach (var target in hidden)
{
var playerController = PlayerManager.GetPlayerEvent(Utilities.GetPlayerFromIndex((int)playerIndex));
if (playerController == null || !playerController.IsValid || playerController.Index == player.Index)
continue;
if (player.Team == playerController.Team)
continue;
var playerPawn = playerController.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;
if (target.Index == player.Index) continue;
if (player.Team == target.Team) continue;
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle)
continue;
if (targetHandle != nint.Zero && target.Pawn.Handle == targetHandle) continue;
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
if (entity == null || !entity.IsValid) continue;
if (info.TransmitEntities.Contains(target.Pawn.Index))
info.TransmitEntities.Remove(target.Pawn.Index);
if (info.TransmitEntities.Contains(entity.Index))
info.TransmitEntities.Remove(entity.Index);
SkillUtils.HideCarriedEntities(info, playerPawn);
SkillUtils.HideCarriedEntities(info, target.Pawn);
if (bomb == null) continue;

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class CarefulBullets : ISkill
{
private const Skills skillName = Skills.CarefulBullets;
private const string hurtSound = "Player.DamageBody.Victim";
private static readonly ConcurrentDictionary<uint, byte> targetPlayers = [];
private static readonly ConcurrentDictionary<uint, bool> lastShot = [];
private static readonly ConcurrentDictionary<uint, int> hitPlayer = [];
@ -153,7 +154,7 @@ namespace src.player.skills
{
if (lastShot.TryRemove(playerIndex, out bool didHit) && !didHit)
{
eventPlayer.ExecuteClientCommand($"play player/player_damagebody_0{Instance.Random.Next(4, 8)}");
SkillUtils.EmitSoundToPlayer(eventPlayer, hurtSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
SkillUtils.TakeHealth(playerPawn, SkillsInfo.GetValue<int>(skillName, "damageAfterMiss"), GetSkillOwner(playerIndex), KillfeedIcons.Fist);
}
@ -257,8 +258,9 @@ namespace src.player.skills
targetToPlayer.TryRemove(index, out _);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#db6c35", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int damageAfterMiss = 5) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#db6c35", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int damageAfterMiss = 5, float soundVolume = .35f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int DamageAfterMiss { get; set; } = damageAfterMiss;
}
}

View file

@ -23,6 +23,8 @@ namespace src.player.skills
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (Heavyweight.Resists(victim)) return;
if (attackerInfo?.Skill == skillName && victim!.PawnIsAlive)
if (Instance.Random.NextDouble() <= attackerInfo.SkillChance)
{

View file

@ -10,16 +10,7 @@ namespace src.player.skills
public class Chicken : ISkill
{
private const Skills skillName = Skills.Chicken;
private static readonly HashSet<string> disabledWeapons =
[
"weapon_ak47", "weapon_m4a4", "weapon_m4a1", "weapon_m4a1_silencer",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg556",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7",
"weapon_ump45", "weapon_p90", "weapon_mp5sd", "weapon_ssg08",
"weapon_awp", "weapon_scar20", "weapon_g3sg1", "weapon_nova",
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev"
];
private static HashSet<string> disabledWeapons => WeaponPool.Rifles;
private const string chickenModelPath = "models/chicken/chicken.vmdl";
private static readonly ConcurrentDictionary<uint, uint> chickens = [];

View file

@ -88,7 +88,7 @@ namespace src.player.skills
{
var cameraProp = Utilities.GetEntityFromIndex<CDynamicProp>((int)playerSkill.CameraProp);
if (cameraProp != null && cameraProp.IsValid)
cameraProp.EmitSound("SolidMetal.BulletImpact");
cameraProp.EmitSound("SolidMetal.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
SkillUtils.SafeKillEntity<CDynamicProp>(playerSkill.CameraProp);
playerSkill.CameraProp = null;
@ -414,8 +414,9 @@ namespace src.player.skills
public required QAngle LastCameraAngle { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#34ebd5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 30) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#34ebd5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 30, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
}
}

View file

@ -143,6 +143,8 @@ namespace src.player.skills
eventPlayerPawn.AbsVelocity.X = newVelocity.X;
eventPlayerPawn.AbsVelocity.Y = newVelocity.Y;
eventPlayerPawn.AbsVelocity.Z = newVelocity.Z;
eventPlayerPawn.EmitSound("Default.WalkJump", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
@ -164,8 +166,9 @@ namespace src.player.skills
public int JumpReleasedTicks { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42bbfc", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float jumpVelocity = 150f, float pushVelocity = 600f, bool anyDirection = true, float cooldown = 2f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42bbfc", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float jumpVelocity = 150f, float pushVelocity = 600f, bool anyDirection = true, float cooldown = 2f, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float JumpVelocity { get; set; } = jumpVelocity;
public float PushVelocity { get; set; } = pushVelocity;
public bool AnyDirection { get; set; } = anyDirection;

View file

@ -13,6 +13,7 @@ namespace src.player.skills
public class DeathBomb : ISkill
{
private const Skills skillName = Skills.DeathBomb;
private static readonly string[] voiceLines = ["balkan.radiobotfallback01", "balkan.radiobotfallback02", "balkan.radiobotfallback04"];
private static readonly QAngle angle = new(10, -5, 9);
private static readonly ConcurrentDictionary<int, (byte Team, uint Owner)> nades = [];
@ -61,9 +62,7 @@ namespace src.player.skills
SkillUtils.PrintToChat(_p, $"{ChatColors.DarkRed}\u202A{player.PlayerName}\u202C: {ChatColors.Lime}{_p.GetTranslation("deathbomb_explosion")}",
border: !PlayerManager.GetTickPlayers().Any(p => p.Team == player.Team && p != player) ? "tb" : "t");
var fileNames = new[] { "radiobotfallback01", "radiobotfallback02", "radiobotfallback04" };
var randomFile = fileNames[new Random().Next(fileNames.Length)];
player.ExecuteClientCommand($"play vo/agents/balkan/{randomFile}.vsnd");
player.EmitSound(voiceLines[Random.Shared.Next(voiceLines.Length)], volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
nades.AddOrUpdate(Server.TickCount, (player.TeamNum, player.Index), (_, _) => (player.TeamNum, player.Index));
}
@ -136,8 +135,9 @@ namespace src.player.skills
return player != null && player.IsValid && player.PlayerPawn?.Value != null;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#F5CB42", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float explosionRadius = 500.0f, int explosionDamage = 999, float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#F5CB42", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float explosionRadius = 500.0f, int explosionDamage = 999, float dmgReductionForTeamates = 0.5f, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;

View file

@ -66,13 +66,14 @@ namespace src.player.skills
if (playerSeesEnemy)
{
SkillUtils.TakeHealth(enemyPawn, damage, player, KillfeedIcons.Fist);
enemyEvent.EmitSound("Player.DamageBody.Onlooker");
SkillUtils.EmitSoundToPlayer(enemyEvent, "Player.DamageBody.Victim", SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c91243", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float secondCooldown = 1f, int damage = 5) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c91243", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float secondCooldown = 1f, int damage = 5, float soundVolume = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float SecondCooldown { get; set; } = secondCooldown;
public int Damage { get; set; } = damage;
}

View file

@ -0,0 +1,154 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class EmpGrenade : ISkill
{
private const Skills skillName = Skills.EmpGrenade;
private const string ownerTag = "EmpGrenade";
private static readonly ConcurrentDictionary<uint, int> jammedUntilTick = [];
private static readonly ConcurrentDictionary<uint, int> playersWithSkill = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
RestoreAll();
jammedUntilTick.Clear();
}
public static void RoundEnd()
{
RestoreAll();
jammedUntilTick.Clear();
}
public static void PlayerDisconnect(uint playerIndex)
{
jammedUntilTick.TryRemove(playerIndex, out _);
playersWithSkill.TryRemove(playerIndex, out _);
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return;
if (jammedUntilTick.TryRemove(victim.Index, out _))
SetHud(victim.Index, false);
}
public static void PlayerHurt(EventPlayerHurt @event)
{
if (@event.Weapon != "hegrenade") return;
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
if (attacker == null || !attacker.IsValid) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (attackerInfo?.Skill != skillName) return;
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return;
float duration = SkillsInfo.GetValue<float>(skillName, "jamSeconds");
jammedUntilTick[victim.Index] = Server.TickCount + (int)(duration * 64);
SetHud(victim.Index, true);
var victimEvent = PlayerManager.GetPlayerFromEvent(victim);
if (victimEvent != null && victimEvent.IsValid)
victimEvent.PrintToChat($" {ChatColors.Red}{victimEvent.GetTranslation("empgrenade_enemy_info")}");
}
public static void OnTick()
{
if (jammedUntilTick.IsEmpty) return;
int now = Server.TickCount;
foreach (var kvp in jammedUntilTick)
{
if (now < kvp.Value) continue;
if (!jammedUntilTick.TryRemove(kvp.Key, out _)) continue;
SetHud(kvp.Key, false);
}
}
private static void RestoreAll()
{
foreach (var playerIndex in jammedUntilTick.Keys)
SetHud(playerIndex, false);
}
private static void SetHud(uint playerIndex, bool jam)
{
var player = Utilities.GetPlayerFromIndex((int)playerIndex);
if (player == null || !player.IsValid) return;
ApplyHud(player, jam);
ApplyHud(PlayerManager.GetPlayerFromEvent(player), jam);
}
private static void ApplyHud(CCSPlayerController? player, bool jam)
{
if (player == null || !player.IsValid) return;
SkillUtils.SetCrosshairHidden(player, ownerTag, jam);
SkillUtils.SetRadarDisabled(player, ownerTag, jam);
}
public static void GrenadeThrown(EventGrenadeThrown @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
if (@event.Weapon != "hegrenade") return;
var playerInfo = PlayerManager.GetPlayerByIndex(player.Index);
if (playerInfo?.Skill != skillName) return;
if (playersWithSkill.TryGetValue(player.Index, out int grenadesLeft) && grenadesLeft > 1)
{
playersWithSkill[player.Index] = grenadesLeft - 1;
player!.GiveNamedItem("weapon_hegrenade");
SkillUtils.UpdateGrenadeCount(player, CsItem.HEGrenade, grenadesLeft - 1);
}
}
public static void EnableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit");
playersWithSkill[player.Index] = grenadeLimit;
SkillUtils.TryGiveWeapon(player, CsItem.HEGrenade);
SkillUtils.UpdateGrenadeCount(player, CsItem.HEGrenade, grenadeLimit);
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _);
SkillUtils.UpdateGrenadeCount(player, CsItem.HEGrenade, 1);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#3ad6e8", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Uncommon, float jamSeconds = 3f, int grenadeLimit = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float JamSeconds { get; set; } = jamSeconds;
public int GrenadeLimit { get; set; } = grenadeLimit;
}
}
}

View file

@ -19,12 +19,14 @@ namespace src.player.skills
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
DecoyRing.PreloadAssets();
}
public static void NewRound()
{
KillAllDecoys();
rainMolotovs.Clear();
DecoyRing.ClearAll(skillName);
}
private static void KillAllDecoys()
@ -276,7 +278,14 @@ namespace src.player.skills
if (decoy != null && decoy.IsValid)
decoy.AddEntityIOEvent("Kill", decoy, delay: 0.1f);
decoys.TryRemove(key, out _);
SpawnRain(player, new Vector(@event.X, @event.Y, @event.Z));
Vector impact = new(@event.X, @event.Y, @event.Z);
SpawnRain(player, impact);
DecoyRing.Show(skillName, key, impact, 130f);
jRandomSkills.Instance.AddTimer(SkillsInfo.GetValue<float>(skillName, "markerSeconds"),
() => DecoyRing.Hide(skillName, key),
CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
}
@ -286,8 +295,9 @@ namespace src.player.skills
SkillUtils.TryGiveWeapon(player, CsItem.DecoyGrenade);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffbf47", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Epic) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffbf47", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Epic, float markerSeconds = 4f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float MarkerSeconds { get; set; } = markerSeconds;
}
}
}

View file

@ -154,7 +154,7 @@ namespace src.player.skills
var box = param.As<CDynamicProp>();
if (box == null || !box.IsValid) return;
box.EmitSound("Wood_Plank.BulletImpact", volume: 1f);
box.EmitSound("Wood_Plank.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
if (barricades.TryGetValue(box.Index, out int health))
{
@ -178,8 +178,9 @@ namespace src.player.skills
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1b04cc", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 5, Rarity rarity = Rarity.Common, float cooldown = 2f, int barricadeHealth = 115, string propModel = "models/props/de_aztec/hr_aztec/aztec_scaffolding/aztec_scaffold_wall_support_128.vmdl") : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1b04cc", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 5, Rarity rarity = Rarity.Common, float cooldown = 2f, int barricadeHealth = 115, string propModel = "models/props/de_aztec/hr_aztec/aztec_scaffolding/aztec_scaffold_wall_support_128.vmdl", float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
public int BarricadeHealth { get; set; } = barricadeHealth;
public string PropModel { get; set; } = propModel;

View file

@ -16,11 +16,19 @@ namespace src.player.skills
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
DecoyRing.PreloadAssets();
}
public static void NewRound()
{
decoys.Clear();
DecoyRing.ClearAll(skillName);
}
public static void RoundEnd()
{
decoys.Clear();
DecoyRing.ClearAll(skillName);
}
public static void DecoyStarted(EventDecoyStarted @event)
@ -31,7 +39,9 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
decoys.TryAdd(new Vector(@event.X, @event.Y, @event.Z), 0);
Vector pos = new(@event.X, @event.Y, @event.Z);
decoys.TryAdd(pos, 0);
DecoyRing.Show(skillName, (uint)@event.Entityid, pos, SkillsInfo.GetValue<float>(skillName, "triggerRadius"));
}
public static void DecoyDetonate(EventDecoyDetonate @event)
@ -44,6 +54,8 @@ namespace src.player.skills
foreach (var decoy in decoys.Keys.Where(v => v.X == @event.X && v.Y == @event.Y && v.Z == @event.Z))
decoys.TryRemove(decoy, out _);
DecoyRing.Hide(skillName, (uint)@event.Entityid);
}
public static void OnTick()

View file

@ -13,9 +13,13 @@ namespace src.player.skills
public class Ghost : ISkill
{
private const Skills skillName = Skills.Ghost;
private static readonly string[] allowedWeapons = [
"weapon_molotov", "weapon_incgrenade", "weapon_flashbang", "weapon_smokegrenade", "weapon_decoy", "weapon_hegrenade", "weapon_knife", "weapon_bayonet", "weapon_c4"
];
private static readonly HashSet<string> alwaysAllowed = new(StringComparer.Ordinal)
{
"weapon_knife", "weapon_bayonet", "weapon_c4"
};
private static bool IsAllowed(string? weapon) =>
weapon != null && (alwaysAllowed.Contains(weapon) || WeaponPool.IsGrenade(weapon));
private static readonly ConcurrentDictionary<uint, byte> invisiblePlayers = [];
private const string bloodParticle = "particles/blood_impact/blood_impact_high.vpcf";
private const string cameraViewModel = "models/sprays/spray_plane.vmdl";
@ -61,38 +65,30 @@ namespace src.player.skills
var bomb = PlayerManager.GetTickBomb();
uint? bombOwnerIndex = bomb != null && bomb.IsValid ? bomb.OwnerEntity?.Index : null;
var hidden = SkillUtils.ResolveHiddenPawns(invisiblePlayers.Keys, bombOwnerIndex);
if (hidden.Count == 0) return;
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
foreach (var playerIndex in invisiblePlayers.Keys)
foreach (var target in hidden)
{
var playerController = Utilities.GetPlayerFromIndex((int)playerIndex);
if (playerController == null || !playerController.IsValid || playerController.Index == player.Index)
continue;
if (player.Team == playerController.Team)
continue;
var playerPawn = playerController.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;
if (target.Index == player.Index) continue;
if (player.Team == target.Team) continue;
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle)
continue;
if (targetHandle != nint.Zero && target.Pawn.Handle == targetHandle) continue;
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
if (entity == null || !entity.IsValid) continue;
if (info.TransmitEntities.Contains(target.Pawn.Index))
info.TransmitEntities.Remove(target.Pawn.Index);
if (info.TransmitEntities.Contains(entity.Index))
info.TransmitEntities.Remove(entity.Index);
SkillUtils.HideCarriedEntities(info, playerPawn);
SkillUtils.HideCarriedEntities(info, target.Pawn);
// Hide the bomb as well, but only while this hidden player is the one holding it.
if (bomb == null || bombOwnerIndex != playerController.Index) continue;
if (bomb == null || !target.HoldsBomb) continue;
if (info.TransmitEntities.Contains(bomb.Index))
info.TransmitEntities.Remove(bomb.Index);
@ -156,7 +152,7 @@ namespace src.player.skills
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill == skillName)
if (playerInfo?.Skill == skillName && SkillUtils.IsHudFrame())
UpdateHUD(player);
if (player.LifeState != (byte)LifeState_t.LIFE_ALIVE)
@ -206,7 +202,7 @@ namespace src.player.skills
if (weapon != null && weapon.IsValid && weapon.Value != null && weapon.Value.IsValid && !string.IsNullOrEmpty(weapon.Value.DesignerName))
{
string weaponName = weapon.Value.DesignerName;
if (!allowedWeapons.Contains(weaponName))
if (!IsAllowed(weaponName))
{
weapon.Value.NextPrimaryAttackTick = disableWeapon ? int.MaxValue : Server.TickCount;
weapon.Value.NextSecondaryAttackTick = disableWeapon ? int.MaxValue : Server.TickCount;
@ -227,7 +223,7 @@ namespace src.player.skills
if (playerInfo == null) return;
var weapon = pawn.WeaponServices.ActiveWeapon.Value;
if (weapon == null || !weapon.IsValid || allowedWeapons.Contains(weapon.DesignerName))
if (weapon == null || !weapon.IsValid || IsAllowed(weapon.DesignerName))
{
playerInfo.PrintHTML = null;
return;

View file

@ -41,6 +41,18 @@ namespace src.player.skills
{
if (smokes.IsEmpty || playersWithSkill.IsEmpty) return;
List<uint> smokeIndexes = new(smokes.Count);
foreach (var entityIndex in smokes.Keys)
{
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)entityIndex);
if (entity == null || !entity.IsValid) continue;
smokeIndexes.Add(entity.Index);
}
if (smokeIndexes.Count == 0) return;
Dictionary<nint, uint>? pawnOwners = null;
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid) continue;
@ -51,23 +63,36 @@ namespace src.player.skills
var observerTarget = player.Pawn?.Value?.ObserverServices?.ObserverTarget?.Value?.Handle ?? nint.Zero;
if (observerTarget == nint.Zero) continue;
var observedPlayer = PlayerManager.GetTickPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == observerTarget);
if (observedPlayer == null) continue;
pawnOwners ??= BuildPawnOwners();
if (PlayerManager.GetPlayerByIndex(observedPlayer.Index)?.Skill != skillName) continue;
if (!pawnOwners.TryGetValue(observerTarget, out uint observedIndex)) continue;
if (PlayerManager.GetPlayerByIndex(observedIndex)?.Skill != skillName) continue;
}
foreach (var entityIndex in smokes.Keys)
{
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)entityIndex);
if (entity == null || !entity.IsValid) continue;
if (info.TransmitEntities.Contains(entity.Index))
info.TransmitEntities.Remove(entity.Index);
}
foreach (var index in smokeIndexes)
if (info.TransmitEntities.Contains(index))
info.TransmitEntities.Remove(index);
}
}
private static Dictionary<nint, uint> BuildPawnOwners()
{
var players = PlayerManager.GetTickPlayers();
Dictionary<nint, uint> owners = new(players.Count);
foreach (var p in players)
{
if (p == null || !p.IsValid) continue;
nint handle = p.Pawn?.Value?.Handle ?? nint.Zero;
if (handle == nint.Zero) continue;
owners.TryAdd(handle, p.Index);
}
return owners;
}
public static void GrenadeThrown(EventGrenadeThrown @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);

View file

@ -9,6 +9,7 @@ namespace src.player.skills
public class Glitch : ISkill
{
private const Skills skillName = Skills.Glitch;
private const string ownerTag = "Glitch";
private static readonly ConcurrentDictionary<uint, byte> glitchedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly object setLock = new();
@ -100,7 +101,7 @@ namespace src.player.skills
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) return;
enemyEvent.ReplicateConVar("sv_disable_radar", "1");
SkillUtils.SetRadarDisabled(enemyEvent, ownerTag, true);
playerInfo.SkillUsed = true;
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("glitch_player_info", enemy.PlayerName));
@ -117,7 +118,7 @@ namespace src.player.skills
if (!glitchedPlayers.ContainsKey(bot.Index)) return;
player.ReplicateConVar("sv_disable_radar", "1");
SkillUtils.SetRadarDisabled(player, ownerTag, true);
}
public static void EnableSkill(CCSPlayerController player)
@ -146,7 +147,7 @@ namespace src.player.skills
var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex));
if (target != null && target.IsValid)
{
target.ReplicateConVar("sv_disable_radar", "0");
SkillUtils.SetRadarDisabled(target, ownerTag, false);
if (target.PawnIsAlive && !SkillUtils.IsFreezeTime())
target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("glitch_disable_info"));
}
@ -161,7 +162,7 @@ namespace src.player.skills
var player = @event.Userid;
if (player == null || !player.IsValid) return;
player.ReplicateConVar("sv_disable_radar", "0");
SkillUtils.SetRadarDisabled(player, ownerTag, false);
glitchedPlayers.TryRemove(player.Index, out _);
SkillUtils.CloseMenu(player);
}

View file

@ -20,10 +20,13 @@ namespace src.player.skills
private static readonly Color counterTerroristRope = Color.FromArgb(255, 52, 92, 138);
private const double attemptCooldown = .3;
private const string hookModel = "models/generic/grapplinghook_01/grapplinghook_hook_01_open.vmdl";
private const string hookSound = "SolidMetal.BulletImpact";
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
jRandomSkills.Instance.AddToManifest(hookModel);
}
public static void NewRound()
@ -131,6 +134,8 @@ namespace src.player.skills
skillInfo.Anchor = anchor;
skillInfo.EndTick = Server.TickCount + (int)(SkillsInfo.GetValue<float>(skillName, "maxPullSeconds") * 64);
SpawnHook(player, skillInfo, anchor, result.Value.NormalX, result.Value.NormalY, result.Value.NormalZ);
if (!EntityManager.OverBudget())
{
Color ropeColor = player.Team == CsTeam.Terrorist ? terroristRope : counterTerroristRope;
@ -155,15 +160,102 @@ namespace src.player.skills
}
}
player.EmitSound("SolidMetal.BulletImpact");
return true;
}
private static void EmitHookSound(CBaseEntity? source, CCSPlayerController player)
{
float volume = SkillsInfo.GetValue<float>(skillName, "soundVolume");
if (source != null && source.IsValid)
{
source.EmitSound(hookSound, volume: volume);
return;
}
var pawn = player.PlayerPawn?.Value;
if (pawn != null && pawn.IsValid)
pawn.EmitSound(hookSound, volume: volume);
}
private static void SpawnHook(CCSPlayerController player, PlayerSkillInfo skillInfo, Vector anchor, float normalX, float normalY, float normalZ)
{
float length = MathF.Sqrt(normalX * normalX + normalY * normalY + normalZ * normalZ);
if (length < .0001f)
{
EmitHookSound(null, player);
return;
}
normalX /= length;
normalY /= length;
normalZ /= length;
var hook = EntityManager.CreateTrackedPropOverride(player.Index);
if (hook == null || !hook.IsValid)
{
EmitHookSound(null, player);
return;
}
float pitch = -MathF.Asin(Math.Clamp(-normalZ, -1f, 1f)) * 180f / MathF.PI;
float yaw = MathF.Atan2(-normalY, -normalX) * 180f / MathF.PI;
QAngle angle = new(pitch, yaw, 0);
float scale = SkillsInfo.GetValue<float>(skillName, "hookScale");
float embed = SkillsInfo.GetValue<float>(skillName, "hookEmbed") * scale;
Vector pos = new(anchor.X + normalX * embed, anchor.Y + normalY * embed, anchor.Z + normalZ * embed);
uint hookIndex = hook.Index;
skillInfo.HookIndex = hookIndex;
Server.NextFrame(() =>
{
var prop = Utilities.GetEntityFromIndex<CDynamicProp>((int)hookIndex);
if (prop == null || !prop.IsValid)
{
EmitHookSound(null, player);
return;
}
var node = prop.CBodyComponent?.SceneNode?.Owner?.Entity;
if (node != null)
node.Flags = (uint)(node.Flags & ~(1 << 2));
prop.SetModel(hookModel);
prop.Teleport(pos, angle);
prop.DispatchSpawn();
EmitHookSound(prop, player);
var skeleton = prop.CBodyComponent?.SceneNode?.GetSkeletonInstance();
if (skeleton != null)
{
skeleton.Scale = scale;
prop.AcceptInput("SetScale", null, null, scale.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
Utilities.SetStateChanged(prop, "CBaseEntity", "m_CBodyComponent");
});
}
private static void DestroyHook(PlayerSkillInfo skillInfo)
{
if (skillInfo.HookIndex == null) return;
uint hookIndex = skillInfo.HookIndex.Value;
skillInfo.HookIndex = null;
EntityManager.DestroyEntity(hookIndex);
}
private static void DestroyRope(PlayerSkillInfo skillInfo)
{
skillInfo.Anchor = null;
skillInfo.EndTick = 0;
DestroyHook(skillInfo);
if (skillInfo.RopeIndex == null) return;
uint ropeIndex = skillInfo.RopeIndex.Value;
@ -176,26 +268,7 @@ namespace src.player.skills
{
if (!liveRopes.TryRemove(ropeIndex, out _)) return;
var rope = Utilities.GetEntityFromIndex<CBeam>((int)ropeIndex);
if (rope != null && rope.IsValid)
{
rope.Width = 0;
rope.EndWidth = 0;
rope.Render = Color.FromArgb(0, 0, 0, 0);
Utilities.SetStateChanged(rope, "CBeam", "m_fWidth");
Utilities.SetStateChanged(rope, "CBeam", "m_fEndWidth");
Utilities.SetStateChanged(rope, "CBaseModelEntity", "m_clrRender");
}
EntityManager.DestroyEntity(ropeIndex);
Server.NextFrame(() =>
{
var leftover = Utilities.GetEntityFromIndex<CBeam>((int)ropeIndex);
if (leftover != null && leftover.IsValid)
leftover.Remove();
});
EntityManager.DestroyBeam(ropeIndex);
}
private static void SweepOrphanRopes()
@ -299,11 +372,13 @@ namespace src.player.skills
public DateTime LastAttempt { get; set; }
public Vector? Anchor { get; set; }
public uint? RopeIndex { get; set; }
public uint? HookIndex { get; set; }
public int EndTick { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#38e0c4", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, float cooldown = 10f, float maxDistance = 1500f, float minDistance = 150f, float stopDistance = 90f, float pullSpeed = 850f, float maxPullSeconds = 3f, float ropeWidth = 0.8f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#38e0c4", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, float cooldown = 10f, float maxDistance = 1500f, float minDistance = 150f, float stopDistance = 90f, float pullSpeed = 850f, float maxPullSeconds = 3f, float ropeWidth = 0.8f, float hookEmbed = 8f, float hookScale = .4f, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
public float MaxDistance { get; set; } = maxDistance;
public float MinDistance { get; set; } = minDistance;
@ -311,6 +386,8 @@ namespace src.player.skills
public float PullSpeed { get; set; } = pullSpeed;
public float MaxPullSeconds { get; set; } = maxPullSeconds;
public float RopeWidth { get; set; } = ropeWidth;
public float HookEmbed { get; set; } = hookEmbed;
public float HookScale { get; set; } = hookScale;
}
}
}

View file

@ -0,0 +1,189 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class GravityDecoy : ISkill
{
private const Skills skillName = Skills.GravityDecoy;
private const float defaultGravity = 1f;
private static readonly ConcurrentDictionary<Vector, byte> decoys = [];
private static readonly ConcurrentDictionary<uint, int> playersWithSkill = [];
private static readonly ConcurrentDictionary<uint, byte> affected = [];
private static readonly ConcurrentDictionary<uint, byte> restoreOnRespawn = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
DecoyRing.PreloadAssets();
}
public static void NewRound()
{
RestoreAll();
decoys.Clear();
DecoyRing.ClearAll(skillName);
}
public static void RoundEnd()
{
RestoreAll();
decoys.Clear();
DecoyRing.ClearAll(skillName);
}
public static void DecoyStarted(EventDecoyStarted @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
Vector pos = new(@event.X, @event.Y, @event.Z);
decoys.TryAdd(pos, 0);
DecoyRing.Show(skillName, (uint)@event.Entityid, pos, SkillsInfo.GetValue<float>(skillName, "triggerRadius"));
}
public static void DecoyDetonate(EventDecoyDetonate @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
foreach (var decoy in decoys.Keys.Where(v => v.X == @event.X && v.Y == @event.Y && v.Z == @event.Z))
decoys.TryRemove(decoy, out _);
DecoyRing.Hide(skillName, (uint)@event.Entityid);
if (decoys.IsEmpty) RestoreAll();
}
public static void OnTick()
{
if (decoys.IsEmpty && affected.IsEmpty && restoreOnRespawn.IsEmpty) return;
if (decoys.IsEmpty && !affected.IsEmpty) RestoreAll();
float radius = SkillsInfo.GetValue<float>(skillName, "triggerRadius");
float gravity = SkillsInfo.GetValue<float>(skillName, "gravityScale");
foreach (var player in PlayerManager.GetTickPlayers())
{
if (player == null || !player.IsValid) continue;
if (player.Team is not (CsTeam.CounterTerrorist or CsTeam.Terrorist)) continue;
var eventPlayer = PlayerManager.GetPlayerEvent(player);
if (eventPlayer == null || !eventPlayer.IsValid) continue;
var pawn = eventPlayer.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) continue;
if (pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) continue;
if (restoreOnRespawn.TryRemove(eventPlayer.Index, out _))
pawn.ActualGravityScale = defaultGravity;
bool inside = !decoys.IsEmpty && decoys.Keys.Any(d => SkillUtils.GetDistance(d, pawn.AbsOrigin) <= radius);
if (inside)
{
if (!affected.TryAdd(eventPlayer.Index, 0)) continue;
pawn.ActualGravityScale = gravity;
}
else if (affected.TryRemove(eventPlayer.Index, out _))
pawn.ActualGravityScale = defaultGravity;
}
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return;
if (!affected.TryRemove(victim.Index, out _)) return;
Restore(victim.Index);
restoreOnRespawn[victim.Index] = 0;
}
public static void PlayerDisconnect(uint playerIndex)
{
affected.TryRemove(playerIndex, out _);
restoreOnRespawn.TryRemove(playerIndex, out _);
playersWithSkill.TryRemove(playerIndex, out _);
}
private static void RestoreAll()
{
foreach (uint playerIndex in affected.Keys)
Restore(playerIndex);
foreach (uint playerIndex in restoreOnRespawn.Keys)
Restore(playerIndex);
affected.Clear();
restoreOnRespawn.Clear();
}
private static void Restore(uint playerIndex)
{
var player = Utilities.GetPlayerFromIndex((int)playerIndex);
var pawn = player?.PlayerPawn?.Value;
if (pawn != null && pawn.IsValid)
pawn.ActualGravityScale = defaultGravity;
}
public static void GrenadeThrown(EventGrenadeThrown @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
if (@event.Weapon != "decoy") return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
if (playersWithSkill.TryGetValue(player.Index, out int grenadesLeft) && grenadesLeft > 1)
{
playersWithSkill[player.Index] = grenadesLeft - 1;
player!.GiveNamedItem("weapon_decoy");
SkillUtils.UpdateGrenadeCount(player, CsItem.DecoyGrenade, grenadesLeft - 1);
}
}
public static void EnableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit");
playersWithSkill[player.Index] = grenadeLimit;
SkillUtils.TryGiveWeapon(player, CsItem.DecoyGrenade);
SkillUtils.UpdateGrenadeCount(player, CsItem.DecoyGrenade, grenadeLimit);
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _);
SkillUtils.UpdateGrenadeCount(player, CsItem.DecoyGrenade, 1);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#a06cd5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float triggerRadius = 180f, float gravityScale = .5f, int grenadeLimit = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float TriggerRadius { get; set; } = triggerRadius;
public float GravityScale { get; set; } = gravityScale;
public int GrenadeLimit { get; set; } = grenadeLimit;
}
}
}

View file

@ -92,7 +92,7 @@ namespace src.player.skills
if (SkillUtils.GetDistance(smokePos, pawn.AbsOrigin) <= smokeRadius)
if (SkillUtils.AddHealth(pawn, smokeHeal))
player.EmitSound("Healthshot.Success");
SkillUtils.EmitSoundToPlayer(player, "Healthshot.Success", SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
@ -156,8 +156,9 @@ namespace src.player.skills
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1fe070", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int smokeHeal = 1, float smokeRadius = 180, int tickCooldown = 16, int grenadeLimit = 1) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1fe070", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int smokeHeal = 1, float smokeRadius = 180, int tickCooldown = 16, int grenadeLimit = 1, float soundVolume = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int SmokeHeal { get; set; } = smokeHeal;
public float SmokeRadius { get; set; } = smokeRadius;
public int TickCooldown { get; set; } = tickCooldown;

View file

@ -0,0 +1,55 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class Heavyweight : ISkill
{
private const Skills skillName = Skills.Heavyweight;
private static readonly ConcurrentDictionary<uint, byte> holders = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
holders.Clear();
}
public static void EnableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
holders[player.Index] = 0;
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null) return;
holders.TryRemove(player.Index, out _);
}
public static void PlayerDisconnect(uint playerIndex)
{
holders.TryRemove(playerIndex, out _);
}
public static bool Resists(CCSPlayerController? player)
{
if (player == null || !player.IsValid || holders.IsEmpty) return false;
if (holders.ContainsKey(player.Index)) return true;
uint routedIndex = PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index;
return routedIndex != player.Index && holders.ContainsKey(routedIndex);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8d6e63", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Uncommon) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
}
}
}

View file

@ -10,19 +10,7 @@ namespace src.player.skills
public class Hermit : ISkill
{
private const Skills skillName = Skills.Hermit;
private static readonly ConcurrentDictionary<string, int> ConcurrentDictionary = new(
[
new KeyValuePair<string, int>("weapon_glock", 3), new KeyValuePair<string, int>("weapon_usp_silencer", 2), new KeyValuePair<string, int>("weapon_hkp2000", 4), new KeyValuePair<string, int>("weapon_p250", 3),
new KeyValuePair<string, int>("weapon_cz75", 2), new KeyValuePair<string, int>("weapon_deagle", 3), new KeyValuePair<string, int>("weapon_fiveseven", 2), new KeyValuePair<string, int>("weapon_elite", 2),
new KeyValuePair<string, int>("weapon_tec9", 3), new KeyValuePair<string, int>("weapon_revolver", 2), new KeyValuePair<string, int>("weapon_mac10", 3), new KeyValuePair<string, int>("weapon_mp9", 2),
new KeyValuePair<string, int>("weapon_mp7", 3), new KeyValuePair<string, int>("weapon_mp5", 3), new KeyValuePair<string, int>("weapon_mp5sd", 3), new KeyValuePair<string, int>("weapon_ump45", 3),
new KeyValuePair<string, int>("weapon_p90", 2), new KeyValuePair<string, int>("weapon_bizon", 2), new KeyValuePair<string, int>("weapon_ak47", 3), new KeyValuePair<string, int>("weapon_m4a1", 4),
new KeyValuePair<string, int>("weapon_m4a1_silencer", 3), new KeyValuePair<string, int>("weapon_galilar", 4), new KeyValuePair<string, int>("weapon_famas", 4), new KeyValuePair<string, int>("weapon_aug", 3),
new KeyValuePair<string, int>("weapon_sg556", 3), new KeyValuePair<string, int>("weapon_ssg08", 2), new KeyValuePair<string, int>("weapon_awp", 2), new KeyValuePair<string, int>("weapon_scar20", 2),
new KeyValuePair<string, int>("weapon_g3sg1", 2), new KeyValuePair<string, int>("weapon_nova", 32), new KeyValuePair<string, int>("weapon_xm1014", 32), new KeyValuePair<string, int>("weapon_sawedoff", 32),
new KeyValuePair<string, int>("weapon_mag7", 3), new KeyValuePair<string, int>("weapon_m249", 2), new KeyValuePair<string, int>("weapon_negev", 2)
]);
private static readonly ConcurrentDictionary<string, int> maxReserveAmmo = ConcurrentDictionary;
private const int fallbackReserveAmmo = 100;
public static void LoadSkill()
{
@ -43,9 +31,11 @@ namespace src.player.skills
var weapon = pawn.WeaponServices.ActiveWeapon.Value;
if (weapon == null || !weapon.IsValid || weapon.VData == null) return;
var maxReserveAmmoClip = maxReserveAmmo.TryGetValue(weapon.DesignerName, out var reserve) ? reserve : 100;
int reserve = weapon.As<CCSWeaponBase>().GetVData<CCSWeaponBaseVData>()?.PrimaryReserveAmmoMax ?? 0;
if (reserve <= 0) reserve = fallbackReserveAmmo;
weapon.Clip1 = weapon.VData.MaxClip1;
weapon.ReserveAmmo.Fill(maxReserveAmmoClip);
weapon.ReserveAmmo.Fill(reserve);
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_iClip1");
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_pReserveAmmo");

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class HotBomb : ISkill
{
private const Skills skillName = Skills.HotBomb;
private const string hurtSound = "Player.DamageBody.Victim";
private readonly static ConcurrentDictionary<uint, byte> players = [];
public static void LoadSkill()
@ -41,7 +42,7 @@ namespace src.player.skills
if (!hasC4) continue;
SkillUtils.TakeHealth(pawn, damage, owner, KillfeedIcons.C4);
playerEvent?.ExecuteClientCommand($"play player/player_damagebody_0{jRandomSkills.Instance.Random.Next(4, 8)}");
SkillUtils.EmitSoundToPlayer(playerEvent, hurtSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
@ -121,8 +122,9 @@ namespace src.player.skills
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#baf081", CsTeam onlyTeam = CsTeam.CounterTerrorist, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 1, Rarity rarity = Rarity.Common, float cooldown = 1, int damage = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#baf081", CsTeam onlyTeam = CsTeam.CounterTerrorist, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 1, Rarity rarity = Rarity.Common, float cooldown = 1, int damage = 2, float soundVolume = .35f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
public int Damage { get; set; } = damage;
}

View file

@ -112,7 +112,7 @@ namespace src.player.skills
var player = Utilities.GetPlayerFromIndex((int)playerSkill.PlayerIndex);
if (player == null || !player.IsValid) return;
player.EmitSound("SolidMetal.BulletImpact");
player.EmitSound("SolidMetal.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
CDynamicProp? cloneProp = null;
if (playerSkill.CloneProp != null)
@ -468,8 +468,9 @@ namespace src.player.skills
public List<ulong> Weapons { get; set; } = [];
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#d0d930", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 30, float duration = 10) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#d0d930", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 30, float duration = 10, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
public float Duration { get; set; } = duration;
}

View file

@ -50,11 +50,19 @@ namespace src.player.skills
private static void SendAlertToAll()
{
holdersTick = int.MinValue;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!CheckIlliterateSkill(player)) continue;
player.PrintToCenterAlert(player.GetTranslationWithoutIlliterate("illiterate_alert"));
}
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) continue;
string alert = playerEvent.GetTranslationWithoutIlliterate("illiterate_alert");
playerEvent.PrintToCenterAlert(alert);
SkillUtils.PrintToChat(playerEvent, $"{ChatColors.Red}{alert}", ignoreIlliterate: true);
}
}
public static bool CheckIlliterateSkill(CCSPlayerController? player)

View file

@ -199,7 +199,7 @@ namespace src.player.skills
if (!consumedReplicas.TryAdd(replica.Index, 0)) return;
replica.EmitSound("GlassBottle.BulletImpact", volume: 1f);
replica.EmitSound("GlassBottle.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
if (ActiveTimers.TryRemove((int)replica.Index, out var timer)) timer?.Kill();
var owner = GetReplicaOwner(replica.Index);
@ -230,8 +230,9 @@ namespace src.player.skills
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5ef", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = 30f, float durationRun = 5, float durationCrouch = 12, int yourTeamDamage = 10, int enemyTeamDamage = 20) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5ef", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = 30f, float durationRun = 5, float durationCrouch = 12, int yourTeamDamage = 10, int enemyTeamDamage = 20, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
public float DurationRun { get; set; } = durationRun;
public float DurationCrouch { get; set; } = durationCrouch;

View file

@ -9,6 +9,7 @@ namespace src.player.skills
public class Jammer : ISkill
{
private const Skills skillName = Skills.Jammer;
private const string ownerTag = "Jammer";
private static readonly ConcurrentDictionary<uint, byte> jammedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> jammerToTarget = [];
private static readonly object setLock = new();
@ -118,14 +119,7 @@ namespace src.player.skills
private static void SetCrosshair(CCSPlayerController player, bool enabled)
{
var pawn = player.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid) return;
pawn.HideHUD = (uint)(enabled
? (pawn.HideHUD & ~(1 << 8))
: (pawn.HideHUD | (1 << 8)));
Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_iHideHUD");
SkillUtils.SetCrosshairHidden(player, ownerTag, !enabled);
}
public static void BotTakeover(EventBotTakeover @event)

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class Jester : ISkill
{
private const Skills skillName = Skills.Jester;
private const string toggleSound = "Weapon_Taser.ChargeReady";
private static readonly ConcurrentDictionary<uint, JesterInfo> jesters = [];
public static void LoadSkill()
@ -196,7 +197,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent != null && playerEvent.IsValid)
playerEvent.ExecuteClientCommand("play sounds/weapons/taser/taser_charge_ready");
SkillUtils.EmitSoundToPlayer(playerEvent, toggleSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
var minTime = SkillsInfo.GetValue<float>(skillName, "minTime");
@ -275,8 +276,9 @@ namespace src.player.skills
public Timer? Timer { get; set; } = null;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8f108f", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float minTime = 10f, float maxTime = 25f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8f108f", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float minTime = 10f, float maxTime = 25f, float soundVolume = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float MinTime { get; set; } = minTime;
public float MaxTime { get; set; } = maxTime;
}

View file

@ -166,6 +166,8 @@ namespace src.player.skills
private static void PushPlayer(CCSPlayerController player)
{
if (Heavyweight.Resists(player)) return;
var playerPawn = player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid || playerPawn.Health <= 0) return;

View file

@ -9,6 +9,7 @@ namespace src.player.skills
public class LastGasp : ISkill
{
private const Skills skillName = Skills.LastGasp;
private const string hurtSound = "Player.DamageBody.Victim";
public static void LoadSkill()
{
@ -45,11 +46,12 @@ namespace src.player.skills
}
SkillUtils.TakeHealth(attackerPawn, damageAfterDeath, victim, KillfeedIcons.Fist);
PlayerManager.GetPlayerFromEvent(attacker)?.ExecuteClientCommand($"play player/player_damagebody_0{jRandomSkills.Instance.Random.Next(4, 8)}");
SkillUtils.EmitSoundToPlayer(attacker, hurtSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#88bdba", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, int damageAfterDeath = 30, bool canKill = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#88bdba", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, int damageAfterDeath = 30, bool canKill = true, float soundVolume = .35f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int DamageAfterDeath { get; set; } = damageAfterDeath;
public bool CanKill { get; set; } = canKill;

View file

@ -12,6 +12,9 @@ namespace src.player.skills
public class LongKnife : ISkill
{
private const Skills skillName = Skills.LongKnife;
private const string victimSound = "Player.DamageBody.Victim";
private const string heavyHitSound = "Weapon_Knife.Hit.Heavy.Flesh";
private const string lightHitSound = "Weapon_Knife.Hit.Light.Flesh";
private static bool hooked = false;
private const int actionCode = 503;
@ -121,12 +124,15 @@ namespace src.player.skills
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire") && player.Team == target.Team) return;
target.PlayerPawn.Value.EmitSound("Player.DamageBody.Onlooker");
SkillUtils.EmitSoundToPlayer(target, victimSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
SkillUtils.EmitSoundToPlayer(player, heavyHit ? heavyHitSound : lightHitSound, SkillsInfo.GetValue<float>(skillName, "hitSoundVolume"));
SkillUtils.TakeHealth(target.PlayerPawn.Value, heavyHit ? Instance.Random.Next(45, 55) : Instance.Random.Next(21, 34), player, killfeedIcon ?? KillfeedIcons.Knife);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c9f8ff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float maxDistance = 4096f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c9f8ff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float maxDistance = 4096f, bool friendlyFire = true, float soundVolume = .35f, float hitSoundVolume = .35f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float HitSoundVolume { get; set; } = hitSoundVolume;
public float MaxDistance { get; set; } = maxDistance;
public bool FriendlyFire { get; set; } = friendlyFire;
}

View file

@ -16,11 +16,19 @@ namespace src.player.skills
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
DecoyRing.PreloadAssets();
}
public static void NewRound()
{
decoys.Clear();
DecoyRing.ClearAll(skillName);
}
public static void RoundEnd()
{
decoys.Clear();
DecoyRing.ClearAll(skillName);
}
public static void DecoyStarted(EventDecoyStarted @event)
@ -31,7 +39,9 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
decoys.TryAdd(new Vector(@event.X, @event.Y, @event.Z), 0);
Vector pos = new(@event.X, @event.Y, @event.Z);
decoys.TryAdd(pos, 0);
DecoyRing.Show(skillName, (uint)@event.Entityid, pos, SkillsInfo.GetValue<float>(skillName, "triggerRadius"));
}
public static void DecoyDetonate(EventDecoyDetonate @event)
@ -44,6 +54,8 @@ namespace src.player.skills
foreach (var decoy in decoys.Keys.Where(v => v.X == @event.X && v.Y == @event.Y && v.Z == @event.Z))
decoys.TryRemove(decoy, out _);
DecoyRing.Hide(skillName, (uint)@event.Entityid);
}
public static void OnTick()

View file

@ -93,7 +93,7 @@ namespace src.player.skills
skillInfo.Cooldown = DateTime.Now;
skillInfo.Count -= 1;
SkillUtils.AddHealth(playerPawn, SkillsInfo.GetValue<int>(skillName, "healthToAdd"));
player.EmitSound("Healthshot.Success");
SkillUtils.EmitSoundToPlayer(player, "Healthshot.Success", SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
}
@ -106,8 +106,9 @@ namespace src.player.skills
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#10c212", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int healthToAdd = 50, int healthShotLimit = 3, float cooldown = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#10c212", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int healthToAdd = 50, int healthShotLimit = 3, float cooldown = 1f, float soundVolume = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int HealthToAdd { get; set; } = healthToAdd;
public int HealthShotLimit { get; set; } = healthShotLimit;
public float Cooldown { get; set; } = cooldown;

View file

@ -68,7 +68,7 @@ namespace src.player.skills
position.Z += 60;
grenade.Teleport(position);
grenade.EmitSound("IncGrenade.Bounce_M");
grenade.EmitSound("IncGrenade.Bounce_M", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
grenade.DetonateTime = Server.CurrentTime + .5f;
Utilities.SetStateChanged(grenade, "CBaseGrenade", "m_flDetonateTime");
@ -161,8 +161,9 @@ namespace src.player.skills
SkillUtils.UpdateGrenadeCount(player, CsItem.HEGrenade, 1);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#adf542", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float detonationRange = 130, int grenadeLimit = 3) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#adf542", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float detonationRange = 130, int grenadeLimit = 3, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float DetonationRange { get; set; } = detonationRange;
public int GrenadeLimit { get; set; } = grenadeLimit;
}

View file

@ -0,0 +1,156 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
namespace src.player.skills
{
public class Nemesis : ISkill
{
private const Skills skillName = Skills.Nemesis;
private static readonly ConcurrentDictionary<uint, byte> markedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> ownerToTarget = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
markedPlayers.Clear();
ownerToTarget.Clear();
foreach (var player in PlayerManager.GetTickPlayers())
SkillUtils.CloseMenu(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
markedPlayers.TryRemove(playerIndex, out _);
ownerToTarget.TryRemove(playerIndex, out _);
foreach (var kvp in ownerToTarget)
if (kvp.Value == playerIndex)
ownerToTarget.TryRemove(kvp.Key, out _);
}
public static void EnableSkill(CCSPlayerController player)
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) return;
playerInfo.SkillUsed = false;
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.CreateMenu(player, menuItems);
}
else
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
if (ownerToTarget.TryRemove(player.Index, out uint targetIndex))
markedPlayers.TryRemove(targetIndex, out _);
SkillUtils.CloseMenu(player);
}
public static void OnTick()
{
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems);
}
}
public static void TypeSkill(CCSPlayerController player, string[] commands)
{
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
if (playerInfo.SkillUsed)
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("areareaper_used_info")}");
return;
}
if (commands.Length == 0 || !uint.TryParse(commands[0], out uint enemyIndex))
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
return;
}
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (enemy == null || !enemy.IsValid)
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
return;
}
markedPlayers[enemy.Index] = 0;
ownerToTarget[player.Index] = enemy.Index;
playerInfo.SkillUsed = true;
playerEvent.PrintToChat($" {ChatColors.Green}{playerEvent.GetTranslation("nemesis_player_info", enemy.PlayerName)}");
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent != null && enemyEvent.IsValid)
enemyEvent.PrintToChat($" {ChatColors.Red}{enemyEvent.GetTranslation("nemesis_enemy_info")}");
}
public static void OnTakeDamage(DynamicHook h)
{
if (markedPlayers.IsEmpty) return;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return;
var victimPawn = param.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value;
if (victimController == null || !victimController.IsValid) return;
var victim = victimController.As<CCSPlayerController>();
if (victim == null || !victim.IsValid) return;
uint victimIndex = PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index;
if (!markedPlayers.ContainsKey(victimIndex)) return;
param2.Damage *= 1f + SkillsInfo.GetValue<float>(skillName, "extraDamagePercent");
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#b8003a", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Rare, float extraDamagePercent = .25f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float ExtraDamagePercent { get; set; } = extraDamagePercent;
}
}
}

View file

@ -10,6 +10,7 @@ namespace src.player.skills
public class Nightmare : ISkill
{
private const Skills skillName = Skills.Nightmare;
private const string cursedSound = "UI.ArmsRace.FinalKill_Tone";
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetVolumes = [];
private static readonly object setLock = new();
@ -129,7 +130,10 @@ namespace src.player.skills
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("nightmare_player_info", enemy.PlayerName));
if (enemyEvent != null && enemyEvent.IsValid)
{
enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("nightmare_enemy_info"));
SkillUtils.EmitSoundToPlayer(enemyEvent, cursedSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
public static void DisableSkill(CCSPlayerController player)
@ -227,8 +231,9 @@ namespace src.player.skills
EntityManager.DestroyEntity(volumeIndex);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5b2c6f", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, string postProcessing = "lighting/postprocessing/effects/death_cam_phase1_low_violence.vpost", float fadeTime = .25f, float minExposure = .5f, float maxExposure = 2f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5b2c6f", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, string postProcessing = "lighting/postprocessing/effects/death_cam_phase1_low_violence.vpost", float fadeTime = .25f, float minExposure = .5f, float maxExposure = 2f, float soundVolume = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public string PostProcessing { get; set; } = postProcessing;
public float FadeTime { get; set; } = fadeTime;
public float MinExposure { get; set; } = minExposure;

View file

@ -63,37 +63,29 @@ namespace src.player.skills
var bomb = PlayerManager.GetTickBomb();
uint? bombOwnerIndex = bomb != null && bomb.IsValid ? bomb.OwnerEntity?.Index : null;
var hidden = SkillUtils.ResolveHiddenPawns(invisiblePlayers.Keys, bombOwnerIndex);
if (hidden.Count == 0) return;
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
foreach (var playerIndex in invisiblePlayers.Keys)
foreach (var target in hidden)
{
var playerController = PlayerManager.GetPlayerEvent(Utilities.GetPlayerFromIndex((int)playerIndex));
if (playerController == null || !playerController.IsValid || playerController.Index == player.Index)
continue;
if (player.Team == playerController.Team)
continue;
var playerPawn = playerController.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;
if (target.Index == player.Index) continue;
if (player.Team == target.Team) continue;
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle)
continue;
if (targetHandle != nint.Zero && target.Pawn.Handle == targetHandle) continue;
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
if (entity == null || !entity.IsValid) continue;
if (info.TransmitEntities.Contains(target.Pawn.Index))
info.TransmitEntities.Remove(target.Pawn.Index);
if (info.TransmitEntities.Contains(entity.Index))
info.TransmitEntities.Remove(entity.Index);
SkillUtils.HideCarriedEntities(info, target.Pawn);
SkillUtils.HideCarriedEntities(info, playerPawn);
if (bomb == null || bombOwnerIndex != playerController.Index) continue;
if (bomb == null || !target.HoldsBomb) continue;
if (info.TransmitEntities.Contains(bomb.Index))
info.TransmitEntities.Remove(bomb.Index);

View file

@ -15,15 +15,14 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
private static readonly HashSet<string> blockedWeapons =
["hegrenade", "inferno", "decoy", "flashbang", "smokegrenade", "molotov"];
private const string infernoDamage = "inferno";
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(player)) return false;
if (!blockedWeapons.Contains(@event.Weapon)) return false;
if (@event.Weapon != infernoDamage && !WeaponPool.IsGrenade(@event.Weapon)) return false;
if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != skillName) return false;
SkillUtils.RestoreHealth(player);

View file

@ -9,6 +9,7 @@ namespace src.player.skills
public class PawelJumper : ISkill
{
private const Skills skillName = Skills.PawelJumper;
private const string jumpSound = "Default.WalkJump";
private static readonly int?[] J = new int?[64];
private static readonly PlayerButtons[] LB = new PlayerButtons[64];
@ -73,13 +74,17 @@ namespace src.player.skills
{
J[player.Slot]++;
playerEventPawn.AbsVelocity.Z = 300;
if (J[player.Slot] > 1)
playerEventPawn.EmitSound(jumpSound, volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
LB[player.Slot] = buttons;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#FFA500", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int extraJumpsMin = 1, int extraJumpsMax = 4) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#FFA500", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int extraJumpsMin = 1, int extraJumpsMax = 4, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int ExtraJumpsMin { get; set; } = extraJumpsMin;
public int ExtraJumpsMax { get; set; } = extraJumpsMax;
}

View file

@ -15,13 +15,29 @@ namespace src.player.skills
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
Instance.AddToManifest(SkillsInfo.GetValue<string>(skillName, "particleName"));
}
public static void NewRound()
{
foreach (var pilotInfo in PlayerPilotInfo.Values)
ClearTrail(pilotInfo);
PlayerPilotInfo.Clear();
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
if (PlayerPilotInfo.TryGetValue(player.Index, out var pilotInfo))
{
ClearTrail(pilotInfo);
pilotInfo.IsFlying = false;
}
}
public static void OnTick()
{
foreach (var player in PlayerManager.GetTickPlayers())
@ -39,7 +55,9 @@ namespace src.player.skills
public static void DisableSkill(CCSPlayerController player)
{
PlayerPilotInfo.TryRemove(player.Index, out _);
if (PlayerPilotInfo.TryRemove(player.Index, out var pilotInfo))
ClearTrail(pilotInfo);
SkillUtils.ResetPrintHTML(player);
}
@ -96,9 +114,18 @@ namespace src.player.skills
);
if (inUse)
{
ApplyPilotEffect(player);
else if (pilotInfo.Fuel <= 0)
pilotInfo.IsFlying = false;
StartTrail(player, playerPawn, pilotInfo);
}
else
{
if (pilotInfo.TrailIndex != null)
ClearTrail(pilotInfo);
if (pilotInfo.Fuel <= 0)
pilotInfo.IsFlying = false;
}
pilotInfo.LastButtons = buttons;
UpdateHUD(player, pilotInfo);
@ -132,6 +159,62 @@ namespace src.player.skills
return "#FF0000";
}
private static void StartTrail(CCSPlayerController player, CCSPlayerPawn pawn, Pilot_PlayerInfo pilotInfo)
{
if (pilotInfo.TrailIndex != null)
{
var existing = Utilities.GetEntityFromIndex<CParticleSystem>((int)pilotInfo.TrailIndex.Value);
if (existing != null && existing.IsValid) return;
pilotInfo.TrailIndex = null;
}
if (pawn.AbsOrigin == null) return;
var particle = EntityManager.CreateTrackedParticleSystem(player.Index, SkillsInfo.GetValue<string>(skillName, "particleName"));
if (particle == null || !particle.IsValid) return;
float offset = SkillsInfo.GetValue<float>(skillName, "particleOffset");
particle.Teleport(new Vector(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + offset));
particle.AcceptInput("SetParent", pawn, particle, "!activator");
particle.AcceptInput("Start");
pilotInfo.TrailIndex = particle.Index;
}
private static void StopTrail(Pilot_PlayerInfo pilotInfo)
{
if (pilotInfo.TrailIndex == null) return;
uint index = pilotInfo.TrailIndex.Value;
pilotInfo.TrailIndex = null;
var particle = Utilities.GetEntityFromIndex<CParticleSystem>((int)index);
if (particle != null && particle.IsValid)
{
particle.AcceptInput("Stop");
particle.AcceptInput("DestroyImmediately");
}
EntityManager.DestroyEntity(index);
if (EntityManager.SuppressKills) return;
Server.NextFrame(() =>
{
var leftover = Utilities.GetEntityFromIndex<CParticleSystem>((int)index);
if (leftover != null && leftover.IsValid)
leftover.Remove();
});
}
private static void ClearTrail(Pilot_PlayerInfo pilotInfo)
{
StopTrail(pilotInfo);
foreach (var leftover in EntityManager.GetPlayerEntities((uint)pilotInfo.SteamID, "particle_system"))
EntityManager.DestroyEntity(leftover);
}
private static void ApplyPilotEffect(CCSPlayerController player)
{
var playerPawn = player.PlayerPawn.Value;
@ -168,10 +251,13 @@ namespace src.player.skills
public int JumpCount { get; set; } = 0;
public float LastJumpTime { get; set; } = 0;
public bool IsFlying { get; set; } = false;
public uint? TrailIndex { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1466F5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float maximumFuel = 150f, float fuelConsumption = .64f, float refuelling = .1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1466F5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float maximumFuel = 150f, float fuelConsumption = .64f, float refuelling = .1f, string particleName = "particles/inferno_fx/incgrenade_thrown_trail.vpcf", float particleOffset = 0f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public string ParticleName { get; set; } = particleName;
public float ParticleOffset { get; set; } = particleOffset;
public float MaximumFuel { get; set; } = maximumFuel;
public float FuelConsumption { get; set; } = fuelConsumption;
public float Refuelling { get; set; } = refuelling;

View file

@ -94,6 +94,8 @@ namespace src.player.skills
public static void OnTick()
{
float currentTime = Server.CurrentTime;
bool hudFrame = SkillUtils.IsHudFrame();
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p.Team == CsTeam.Terrorist))
{
if (!Instance.IsPlayerValid(player)) continue;
@ -111,7 +113,7 @@ namespace src.player.skills
pawn.InBombZone = true;
Schema.SetSchemaValue<bool>(pawn.Handle, "CCSPlayerPawn", "m_bInBombZone", true);
if (plantingPlayers.TryGetValue(player.Index, out float plantTime))
if (hudFrame && plantingPlayers.TryGetValue(player.Index, out float plantTime))
{
float remaining = plantTime + 3f - currentTime;
playerInfo.PrintHTML = $"{player.GetTranslation("planter_planting", $"<font color='#00FF00'>{Math.Max(0, remaining):0.0}s</font>")}";

View file

@ -10,6 +10,7 @@ namespace src.player.skills
public class Poison : ISkill
{
private const Skills skillName = Skills.Poison;
private const string poisonSound = "Player.DamageBody.Victim";
private static readonly ConcurrentDictionary<uint, byte> poisonedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetToPlayer = [];
@ -69,7 +70,7 @@ namespace src.player.skills
SkillUtils.TakeHealth(pawn, SkillsInfo.GetValue<int>(skillName, "Damage"), GetSkillOwner(playerIndex), KillfeedIcons.Spray);
if (Server.TickCount % cooldown2 == 0)
PlayerManager.GetPlayerFromEvent(player)?.ExecuteClientCommand($"play player/player_damagebody_0{jRandomSkills.Instance.Random.Next(4, 8)}");
SkillUtils.EmitSoundToPlayer(player, poisonSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
@ -186,8 +187,9 @@ namespace src.player.skills
SkillUtils.CloseMenu(player);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#902eff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = .85f, int damage = 1, int minHealth = 30) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#902eff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = .85f, int damage = 1, int minHealth = 30, float soundVolume = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int Damage { get; set; } = damage;
public float Cooldown { get; set; } = cooldown;
public int MinHealth { get; set; } = minHealth;

View file

@ -12,16 +12,7 @@ namespace src.player.skills
private static readonly ConcurrentDictionary<uint, byte> bannedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly object setLock = new();
private static readonly string[] disabledWeapons =
[
"weapon_ak47", "weapon_m4a4", "weapon_m4a1", "weapon_m4a1_silencer",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg556",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7",
"weapon_ump45", "weapon_p90", "weapon_mp5sd", "weapon_ssg08",
"weapon_awp", "weapon_scar20", "weapon_g3sg1", "weapon_nova",
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev"
];
private static HashSet<string> disabledWeapons => WeaponPool.Rifles;
public static void LoadSkill()
{

View file

@ -69,7 +69,7 @@ namespace src.player.skills
}
if (!info.Defusing)
pawn.EmitSound("c4.disarmstart");
pawn.EmitSound("c4.disarmstart", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
info.Defusing = true;
info.DefusingTime -= (1f / tickRate);
@ -128,8 +128,9 @@ namespace src.player.skills
public float DefusingTime { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", CsTeam onlyTeam = CsTeam.CounterTerrorist, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float maxDefusingRange = 80f, float defusingTime = 10f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", CsTeam onlyTeam = CsTeam.CounterTerrorist, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float maxDefusingRange = 80f, float defusingTime = 10f, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float MaxDefusingRange { get; set; } = maxDefusingRange;
public float DefusingTime { get; set; } = defusingTime;
}

View file

@ -0,0 +1,110 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class Punisher : ISkill
{
private const Skills skillName = Skills.Punisher;
private static readonly ConcurrentDictionary<uint, float> storedDamage = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
storedDamage.Clear();
}
public static void EnableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
storedDamage[player.Index] = 0f;
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null) return;
storedDamage.TryRemove(player.Index, out _);
SkillUtils.ResetPrintHTML(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
storedDamage.TryRemove(playerIndex, out _);
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return;
if (storedDamage.ContainsKey(victim.Index))
storedDamage[victim.Index] = 0f;
}
public static void OnTakeDamage(DynamicHook h)
{
if (storedDamage.IsEmpty) return;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return;
var victimPawn = param.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value;
if (victimController == null || !victimController.IsValid) return;
var victim = victimController.As<CCSPlayerController>();
if (victim == null || !victim.IsValid) return;
uint victimIndex = PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index;
var attacker = param2.Attacker?.Value?.As<CCSPlayerPawn>();
var attackerController = attacker?.Controller?.Value?.As<CCSPlayerController>();
uint? attackerIndex = attackerController != null && attackerController.IsValid
? PlayerManager.GetPlayerEvent(attackerController)?.Index ?? attackerController.Index
: null;
if (attackerIndex.HasValue && attackerIndex.Value != victimIndex && storedDamage.TryGetValue(attackerIndex.Value, out float banked) && banked > 0f)
{
storedDamage[attackerIndex.Value] = 0f;
param2.Damage += banked;
var puncher = CounterStrikeSharp.API.Utilities.GetPlayerFromIndex((int)attackerIndex.Value);
var puncherPawn = puncher?.PlayerPawn?.Value;
if (puncherPawn != null && puncherPawn.IsValid && puncherPawn.Health > 0)
SkillUtils.SetHealth(puncherPawn, puncherPawn.Health + (int)MathF.Round(banked));
var puncherEvent = PlayerManager.GetPlayerFromEvent(puncher);
if (puncherEvent != null && puncherEvent.IsValid)
puncherEvent.PrintToChat($" {ChatColors.Green}{puncherEvent.GetTranslation("punisher_released_info", (int)MathF.Round(banked))}");
}
if (!storedDamage.ContainsKey(victimIndex)) return;
if (attackerIndex.HasValue && attackerIndex.Value == victimIndex) return;
float ratio = SkillsInfo.GetValue<float>(skillName, "storePercent");
float cap = SkillsInfo.GetValue<float>(skillName, "maxStored");
float stored = storedDamage[victimIndex] + (param2.Damage * ratio);
storedDamage[victimIndex] = MathF.Min(stored, cap);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffb347", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float storePercent = .5f, float maxStored = 100f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float StorePercent { get; set; } = storePercent;
public float MaxStored { get; set; } = maxStored;
}
}
}

View file

@ -23,6 +23,8 @@ namespace src.player.skills
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim)
return;
if (Heavyweight.Resists(victim)) return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (playerInfo?.Skill == skillName && victim!.PawnIsAlive)

View file

@ -13,19 +13,8 @@ namespace src.player.skills
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = new();
private static readonly object setLock = new();
private static readonly HashSet<string> pistols = new(StringComparer.Ordinal)
{
"weapon_deagle", "weapon_revolver", "weapon_glock", "weapon_usp_silencer",
"weapon_cz75a", "weapon_fiveseven", "weapon_p250", "weapon_tec9", "weapon_elite", "weapon_hkp2000"
};
private static readonly HashSet<string> rifles = new(StringComparer.Ordinal)
{
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a1", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev"
};
private static HashSet<string> pistols => WeaponPool.Pistols;
private static HashSet<string> rifles => WeaponPool.Rifles;
public static void LoadSkill()
{
@ -104,6 +93,19 @@ namespace src.player.skills
RemoveAndGiveWeapon(player);
}
private static bool RollPistol(bool pistolAvailable, bool rifleAvailable)
{
if (!rifleAvailable) return true;
if (!pistolAvailable) return false;
float pistolChance = Math.Max(SkillsInfo.GetValue<float>(skillName, "pistolChance"), 0);
float rifleChance = Math.Max(SkillsInfo.GetValue<float>(skillName, "rifleChance"), 0);
float total = pistolChance + rifleChance;
if (total <= 0) return Instance.Random.Next(2) == 0;
return Instance.Random.NextDouble() * total < pistolChance;
}
private static void RemoveAndGiveWeapon(CCSPlayerController player)
{
var pawn = player.PlayerPawn.Value;
@ -117,12 +119,15 @@ namespace src.player.skills
if (playerWeapons.Count == 0)
return;
var available = pistols.Concat(rifles).Where(w => !playerWeapons.Contains(w)).ToArray();
if (available.Length == 0)
var availablePistols = pistols.Where(w => !playerWeapons.Contains(w)).ToArray();
var availableRifles = rifles.Where(w => !playerWeapons.Contains(w)).ToArray();
if (availablePistols.Length == 0 && availableRifles.Length == 0)
return;
bool isPistol = RollPistol(availablePistols.Length > 0, availableRifles.Length > 0);
var available = isPistol ? availablePistols : availableRifles;
string newWeapon = available[Instance.Random.Next(available.Length)];
bool isPistol = pistols.Contains(newWeapon);
string? weaponToRemove = null;
foreach (var item in pawn.WeaponServices.MyWeapons)
@ -163,9 +168,11 @@ namespace src.player.skills
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e0873a", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 15f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e0873a", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 15f, float pistolChance = 60f, float rifleChance = 40f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float Cooldown { get; set; } = cooldown;
public float PistolChance { get; set; } = pistolChance;
public float RifleChance { get; set; } = rifleChance;
}
}
}

View file

@ -152,7 +152,7 @@ namespace src.player.skills
if (!consumedReplicas.TryAdd(replica.Index, 0)) return;
replica.EmitSound("GlassBottle.BulletImpact", volume: 1f);
replica.EmitSound("GlassBottle.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
var owner = GetReplicaOwner(replica.Index);
EntityManager.DestroyEntity(replica.Index);
@ -183,8 +183,9 @@ namespace src.player.skills
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#a3000b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = 15f, int yourTeamDamage = 10, int enemyTeamDamage = 20) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#a3000b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = 15f, int yourTeamDamage = 10, int enemyTeamDamage = 20, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
public int YourTeamDamage { get; set; } = yourTeamDamage;
public int EnemyTeamDamage { get; set; } = enemyTeamDamage;

View file

@ -0,0 +1,205 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
namespace src.player.skills
{
public class Rewind : ISkill
{
private const Skills skillName = Skills.Rewind;
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
foreach (var skillInfo in SkillPlayerInfo.Values)
DestroyMarker(skillInfo);
SkillPlayerInfo.Clear();
}
public static void RoundEnd()
{
foreach (var skillInfo in SkillPlayerInfo.Values)
DestroyMarker(skillInfo);
}
public static void EnableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
SkillPlayerInfo[player.Index] = new PlayerSkillInfo
{
CanUse = true,
Cooldown = DateTime.MinValue,
};
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null) return;
if (SkillPlayerInfo.TryRemove(player.Index, out var skillInfo))
DestroyMarker(skillInfo);
SkillUtils.ResetPrintHTML(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
if (SkillPlayerInfo.TryRemove(playerIndex, out var skillInfo))
DestroyMarker(skillInfo);
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo))
{
skillInfo.ReturnTick = 0;
DestroyMarker(skillInfo);
}
}
public static void UseSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) return;
if (!skillInfo.CanUse || skillInfo.ReturnTick != 0) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
skillInfo.Mark = new Vector(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z);
skillInfo.MarkAngle = new QAngle(0, pawn.V_angle.Y, 0);
skillInfo.ReturnTick = Server.TickCount + (int)(SkillsInfo.GetValue<float>(skillName, "returnDelay") * 64);
skillInfo.CanUse = false;
skillInfo.Cooldown = DateTime.Now;
SpawnMarker(player, skillInfo);
SkillUtils.EmitSoundToPlayer(player, "UIPanorama.buttonclick", SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
public static void OnTick()
{
if (SkillPlayerInfo.IsEmpty) return;
bool hudFrame = SkillUtils.IsHudFrame();
foreach (var player in PlayerManager.GetTickPlayers())
{
if (player == null || !player.IsValid) continue;
if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) continue;
if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill != skillName) continue;
if (skillInfo.ReturnTick != 0 && Server.TickCount >= skillInfo.ReturnTick)
{
skillInfo.ReturnTick = 0;
Teleport(player, skillInfo);
DestroyMarker(skillInfo);
}
if (hudFrame)
UpdateHUD(player, skillInfo);
}
}
private static void Teleport(CCSPlayerController player, PlayerSkillInfo skillInfo)
{
if (skillInfo.Mark == null) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE)
{
skillInfo.Mark = null;
return;
}
pawn.Teleport(skillInfo.Mark, skillInfo.MarkAngle, new Vector(0, 0, 0));
skillInfo.Mark = null;
}
private static void SpawnMarker(CCSPlayerController player, PlayerSkillInfo skillInfo)
{
if (skillInfo.Mark == null) return;
if (EntityManager.OverBudget()) return;
Color color = Color.White;
try { color = ColorTranslator.FromHtml(SkillsInfo.GetValue<string>(skillName, "color")); }
catch { }
Vector bottom = new(skillInfo.Mark.X, skillInfo.Mark.Y, skillInfo.Mark.Z + 2);
Vector top = new(skillInfo.Mark.X, skillInfo.Mark.Y, skillInfo.Mark.Z + 68);
var beam = EntityManager.CreateTrackedBeam(player.Index, bottom, top, color);
if (beam == null || !beam.IsValid) return;
beam.Width = .6f;
beam.EndWidth = .6f;
Utilities.SetStateChanged(beam, "CBeam", "m_fWidth");
Utilities.SetStateChanged(beam, "CBeam", "m_fEndWidth");
skillInfo.MarkerIndex = beam.Index;
}
private static void DestroyMarker(PlayerSkillInfo skillInfo)
{
if (skillInfo.MarkerIndex == null) return;
uint markerIndex = skillInfo.MarkerIndex.Value;
skillInfo.MarkerIndex = null;
EntityManager.DestroyBeam(markerIndex);
}
private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo)
{
float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue<float>(skillName, "cooldown")) - DateTime.Now).TotalSeconds);
float cooldown = Math.Max(time, 0);
if (cooldown == 0 && !skillInfo.CanUse)
skillInfo.CanUse = true;
var playerInfo = PlayerManager.GetPlayerByIndex(player.Index);
if (playerInfo == null) return;
if (skillInfo.ReturnTick != 0)
{
int remaining = (int)Math.Ceiling((skillInfo.ReturnTick - Server.TickCount) / 64f);
playerInfo.PrintHTML = $"<font color='#00FF00'>{player.GetTranslation("rewind_pending_info", Math.Max(remaining, 0))}</font>";
}
else if (cooldown == 0)
playerInfo.PrintHTML = null;
else
playerInfo.PrintHTML = $"{player.GetTranslation("hud_info", $"<font color='#FF0000'>{cooldown}</font>")}";
}
public class PlayerSkillInfo
{
public bool CanUse { get; set; }
public DateTime Cooldown { get; set; }
public Vector? Mark { get; set; }
public QAngle? MarkAngle { get; set; }
public uint? MarkerIndex { get; set; }
public int ReturnTick { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#4fc3f7", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Uncommon, float returnDelay = 4f, float cooldown = 30f, float soundVolume = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float ReturnDelay { get; set; } = returnDelay;
public float Cooldown { get; set; } = cooldown;
}
}
}

View file

@ -14,6 +14,7 @@ namespace src.player.skills
public class Ricochet : ISkill
{
private const Skills skillName = Skills.Ricochet;
private const string bounceSound = "FX_RicochetSound.Ricochet_Legacy";
private static readonly ConcurrentDictionary<uint, ImpactBudget> impactBudgets = [];
private static readonly HashSet<uint> ownedTracers = [];
@ -57,18 +58,10 @@ namespace src.player.skills
if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill != skillName) return;
if (!RayTrace.IsAvailable)
{
Trace("RayTrace module unavailable");
return;
}
if (!RayTrace.IsAvailable) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE)
{
Trace("shooter pawn invalid or dead");
return;
}
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
if (!TryConsumeBudget(player.Index)) return;
@ -76,32 +69,14 @@ namespace src.player.skills
Vector3 impact = new(@event.X, @event.Y, @event.Z);
Vector3 toImpact = impact - eyePos;
if (toImpact.LengthSquared() < 1f)
{
Trace("impact too close to eye");
return;
}
if (toImpact.LengthSquared() < 1f) return;
Vector3 direction = Vector3.Normalize(toImpact);
var surface = RayTrace.TraceShape(player, ToVector(eyePos), ToVector(impact + direction * 8f), worldOnlyMask, 0);
if (surface == null)
{
Trace("surface trace returned null");
return;
}
if (surface == null || !surface.Value.DidHit) return;
if (!surface.Value.DidHit)
{
Trace($"surface trace missed (fraction={surface.Value.Fraction:0.###})");
return;
}
if (!TryResolveNormal(player, surface.Value, direction, out Vector3 normal))
{
Trace("could not resolve surface normal");
return;
}
if (!TryResolveNormal(player, surface.Value, direction, out Vector3 normal)) return;
Bounce(player, pawn, surface.Value.EndPos, direction, normal);
}
@ -118,31 +93,28 @@ namespace src.player.skills
KillfeedIcons? killfeedIcon = KillfeedIconsExtensions.FromWeaponName(SkillUtils.GetDesignerName(pawn.WeaponServices?.ActiveWeapon?.Value));
List<Vector3> path = [];
List<int> soundAt = [];
for (int i = 0; i < bounces; i++)
{
direction = Vector3.Reflect(direction, normal);
Vector3 start = point + normal * 2f;
if (path.Count == 0) path.Add(start);
if (path.Count == 0)
{
path.Add(start);
soundAt.Add(0);
}
Vector3 end = start + direction * segmentDistance;
var hit = RayTrace.TraceShape(player, ToVector(start), ToVector(end));
if (hit == null)
{
Trace($"bounce {i + 1}: segment trace returned null");
break;
}
if (hit == null) break;
Vector3 stop = hit.Value.DidHit ? hit.Value.EndPos : end;
path.Add(stop);
if (!hit.Value.DidHit)
{
Trace($"bounce {i + 1}: no surface, len={Vector3.Distance(start, stop):0.#}");
break;
}
if (!hit.Value.DidHit) break;
if (hit.Value.HitPlayer(out var victim))
{
@ -150,17 +122,14 @@ namespace src.player.skills
break;
}
if (!TryResolveNormal(player, hit.Value, direction, out normal))
{
Trace($"bounce {i + 1}: could not resolve normal");
break;
}
if (!TryResolveNormal(player, hit.Value, direction, out normal)) break;
soundAt.Add(path.Count - 1);
point = hit.Value.EndPos;
damage *= damageFalloff;
}
StartFlight(player, path);
StartFlight(player, path, soundAt);
}
public static void OnTick()
@ -191,11 +160,12 @@ namespace src.player.skills
float tail = MathF.Max(head - trail, flight.Cumulative[segment]);
UpdateTracer(flight, PointAt(flight, tail), PointAt(flight, head));
EmitBounceSound(flight, tail);
}
}
}
private static void StartFlight(CCSPlayerController player, List<Vector3> path)
private static void StartFlight(CCSPlayerController player, List<Vector3> path, List<int> soundAt)
{
if (path.Count < 2) return;
if (!SkillsInfo.GetValue<bool>(skillName, "showTracer")) return;
@ -223,10 +193,27 @@ namespace src.player.skills
Cumulative = cumulative,
TotalLength = total,
DeadlineTick = Server.TickCount + (int)(total / speed * 64f) + 128,
SoundAt = [.. soundAt.Where(index => index < points.Length)],
});
}
}
private static void EmitBounceSound(Flight flight, float tail)
{
if (flight.NextSound >= flight.SoundAt.Length) return;
if (tail < flight.Cumulative[flight.SoundAt[flight.NextSound]]) return;
while (flight.NextSound < flight.SoundAt.Length && tail >= flight.Cumulative[flight.SoundAt[flight.NextSound]])
flight.NextSound++;
if (flight.BeamIndex == null) return;
var beam = ResolveTracer(flight.BeamIndex.Value);
if (beam == null) return;
beam.EmitSound(bounceSound, volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
private static int SegmentAt(Flight flight, float distance)
{
for (int i = flight.Points.Length - 2; i >= 0; i--)
@ -332,9 +319,8 @@ namespace src.player.skills
beam.DispatchSpawn();
return beam;
}
catch (Exception ex)
catch
{
Trace($"beam create failed: {ex.Message}");
return null;
}
}
@ -352,7 +338,6 @@ namespace src.player.skills
if (beam == null)
{
ownedTracers.Remove(beamIndex);
Trace($"tracer {beamIndex} vanished before release");
return;
}
@ -395,9 +380,6 @@ namespace src.player.skills
if (beam != null) HideTracer(beam);
}
if (ownedTracers.Count > 0)
Trace($"pool reset: {ownedTracers.Count} tracer(s) released");
tracerPool.Clear();
ownedTracers.Clear();
}
@ -454,7 +436,6 @@ namespace src.player.skills
int finalDamage = (int)MathF.Round(damage);
if (finalDamage <= 0) return;
Trace($"hit {victimEvent.PlayerName} for {finalDamage}");
SkillUtils.TakeHealth(victimPawn, finalDamage, attacker, killfeedIcon ?? KillfeedIcons.Leg);
}
@ -466,7 +447,6 @@ namespace src.player.skills
if (TryRefineNormal(player, result.EndPos, direction, rough, out normal)) return true;
Trace("surface refine failed, using rough normal");
normal = rough;
return true;
}
@ -600,11 +580,6 @@ namespace src.player.skills
return Vector3.Distance(point, baseHit) <= tolerance;
}
private static void Trace(string message)
{
Debug.WriteToDebug($"[Ricochet] {message}");
}
private static bool TryConsumeBudget(uint playerIndex)
{
int tick = Server.TickCount;
@ -637,12 +612,15 @@ namespace src.player.skills
public required float[] Cumulative { get; set; }
public required float TotalLength { get; set; }
public required int DeadlineTick { get; set; }
public required int[] SoundAt { get; set; }
public float Travelled { get; set; }
public int NextSound { get; set; }
public uint? BeamIndex { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffd75e", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Rare, int bounces = 3, float segmentDistance = 1200f, float damageMultiplier = .5f, float fallbackDamage = 25f, bool respectArmor = true, float damageFalloff = .75f, int maxImpactsPerTick = 2, bool showTracer = true, float tracerWidth = 0.5f, float tracerSpeed = 2600f, float tracerLength = 220f, int maxActiveTracers = 12, float normalProbeOffset = 6f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffd75e", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Rare, int bounces = 3, float segmentDistance = 1200f, float damageMultiplier = .5f, float fallbackDamage = 25f, bool respectArmor = true, float damageFalloff = .75f, int maxImpactsPerTick = 2, bool showTracer = true, float tracerWidth = 0.5f, float tracerSpeed = 2600f, float tracerLength = 220f, int maxActiveTracers = 12, float normalProbeOffset = 6f, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int Bounces { get; set; } = bounces;
public float SegmentDistance { get; set; } = segmentDistance;
public float DamageMultiplier { get; set; } = damageMultiplier;

View file

@ -30,6 +30,8 @@ namespace src.player.skills
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (Heavyweight.Resists(victim)) return;
var victimPawn = victim!.PlayerPawn.Value;
if (victimPawn == null || !victimPawn.IsValid) return;

View file

@ -16,13 +16,7 @@ namespace src.player.skills
private static readonly object setLock = new();
private const string weapon_awp = "weapon_awp";
private static readonly string[] rifles =
[
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a1", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev"
];
private static HashSet<string> rifles => WeaponPool.Rifles;
public static void LoadSkill()
{
@ -81,7 +75,7 @@ namespace src.player.skills
foreach (var index in AWPs.ToList())
SkillUtils.SafeKillEntity<CBasePlayerWeapon>(index);
if (savedWeapons.TryGetValue(player.Index, out string? savedWeapon) && !string.IsNullOrWhiteSpace(savedWeapon))
if (!SkillUtils.IsPistolRound() && savedWeapons.TryGetValue(player.Index, out string? savedWeapon) && !string.IsNullOrWhiteSpace(savedWeapon))
Server.NextFrame(() =>
{
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;

View file

@ -100,9 +100,13 @@ namespace src.player.skills
{
if (SkillPlayerInfo.IsEmpty) return;
if (Server.TickCount % 60 != 0) return;
int cooldown = SkillsInfo.GetValue<int>(skillName, "cooldown");
if (cooldown < 1) cooldown = 1;
if (Server.TickCount % (60 * cooldown) != 0) return;
if ((Server.TickCount / 60) % cooldown != 0) return;
float volume = SkillsInfo.GetValue<float>(skillName, "soundVolume");
foreach (var player in PlayerManager.GetTickPlayers()
.Where(p => p != null && p.IsValid && p.PlayerPawn.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0))
@ -111,18 +115,19 @@ namespace src.player.skills
if (entities.Count == 0)
{
player.PlayerPawn.Value!.EmitSound(soundEventName, volume: 1f);
player.PlayerPawn.Value!.EmitSound(soundEventName, volume: volume);
continue;
}
var entity = Utilities.GetEntityFromIndex<CDynamicProp>((int)entities[0]);
if (entity != null && entity.IsValid)
entity.EmitSound(soundEventName, volume: 1f);
entity.EmitSound(soundEventName, volume: volume);
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e3ed8c", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int cooldown = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e3ed8c", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int cooldown = 2, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int Cooldown { get; set; } = cooldown;
}
}

View file

@ -167,7 +167,7 @@ namespace src.player.skills
SetMagazines(player, playerMagazines + enemyMagazines);
SetMagazines(enemy, 0);
PlayerManager.GetPlayerFromEvent(enemy)?.ExecuteClientCommand($"play sounds/weapons/g3sg1/g3sg1_clipout");
player.PlayerPawn?.Value?.EmitSound("BaseCombatCharacter.AmmoPickup", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) return;
@ -299,8 +299,9 @@ namespace src.player.skills
public required int Reserve { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5eb8b0", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 30f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5eb8b0", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 30f, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float Cooldown { get; set; } = cooldown;
}
}

View file

@ -40,12 +40,13 @@ namespace src.player.skills
damage = Math.Min(damage, SkillsInfo.GetValue<int>(skillName, "maxTakenDamagePerShot"));
SkillUtils.TakeHealth(attackerEvent.PlayerPawn.Value, damage, victimEvent, KillfeedIcons.Armor);
attackerEvent.EmitSound("Player.DamageBody.Onlooker");
SkillUtils.EmitSoundToPlayer(attackerEvent, "Player.DamageBody.Victim", SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#962631", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float healthTakenScale = .3f, int maxTakenDamagePerShot = 37) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#962631", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float healthTakenScale = .3f, int maxTakenDamagePerShot = 37, float soundVolume = .35f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float HealthTakenScale { get; set; } = healthTakenScale;
public int MaxTakenDamagePerShot { get; set; } = maxTakenDamagePerShot;
}

View file

@ -105,7 +105,7 @@ namespace src.player.skills
}
if (SkillUtils.TakeHealth(pawn, damage, thrower, KillfeedIcons.Smokegrenade))
player.EmitSound("Player.DamageBody.Onlooker");
SkillUtils.EmitSoundToPlayer(eventPlayer, "Player.DamageBody.Victim", SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
}
}
@ -170,8 +170,9 @@ namespace src.player.skills
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1, float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1, float dmgReductionForTeamates = 0.5f, float soundVolume = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int SmokeDamage { get; set; } = smokeDamage;
public float SmokeRadius { get; set; } = smokeRadius;
public int TickCooldown { get; set; } = tickCooldown;

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class Tripwire : ISkill
{
private const Skills skillName = Skills.Tripwire;
private const string triggerSound = "Weapon_Taser.ChargeReady";
private static readonly ConcurrentDictionary<uint, WireInfo> wires = [];
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
@ -267,7 +268,7 @@ namespace src.player.skills
var ownerEvent = PlayerManager.GetPlayerFromEvent(owner);
if (ownerEvent != null && ownerEvent.IsValid)
{
ownerEvent.ExecuteClientCommand("play sounds/weapons/taser/taser_charge_ready");
SkillUtils.EmitSoundToPlayer(ownerEvent, triggerSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
ownerEvent.PrintToChat($" {ChatColors.Red}" + ownerEvent.GetTranslation("tripwire_triggered_info", enemyEvent.PlayerName));
}
}
@ -314,8 +315,9 @@ namespace src.player.skills
public DateTime LastAttempt { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff3b3b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, float radarDuration = 5f, float triggerRadius = 24f, float wireHeight = 30f, float wireWidth = 0.7f, float maxWallDistance = 400f, float cooldown = 20f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff3b3b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, float radarDuration = 5f, float triggerRadius = 24f, float wireHeight = 30f, float wireWidth = 0.7f, float maxWallDistance = 400f, float cooldown = 20f, float soundVolume = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public float RadarDuration { get; set; } = radarDuration;
public float TriggerRadius { get; set; } = triggerRadius;
public float WireHeight { get; set; } = wireHeight;

View file

@ -0,0 +1,116 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class TrueArmor : ISkill
{
private const Skills skillName = Skills.TrueArmor;
private static readonly ConcurrentDictionary<uint, byte> holders = [];
private static readonly ConcurrentDictionary<uint, int> pending = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
pending.Clear();
}
public static void EnableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
holders[player.Index] = 0;
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null) return;
holders.TryRemove(player.Index, out _);
pending.TryRemove(player.Index, out _);
}
public static void PlayerDisconnect(uint playerIndex)
{
holders.TryRemove(playerIndex, out _);
pending.TryRemove(playerIndex, out _);
}
public static void OnTakeDamage(DynamicHook h)
{
if (holders.IsEmpty) return;
if (!TryResolveVictim(h, out var victim, out var victimPawn, out var info)) return;
if (!holders.ContainsKey(victim!.Index)) return;
int armor = victimPawn!.ArmorValue;
if (armor <= 0) return;
float damage = info!.Damage;
if (damage <= 0) return;
float ratio = SkillsInfo.GetValue<float>(skillName, "armorShare");
float absorbed = MathF.Min(damage * ratio, armor);
if (absorbed <= 0) return;
info.Damage = damage - absorbed;
pending[victim.Index] = (int)MathF.Round(armor - absorbed);
victimPawn.ArmorValue = 0;
Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue");
Debug.WriteToDebug($"[TrueArmor] {victim.PlayerName}: raw={damage:0.#} absorbed={absorbed:0.#} " +
$"passed={info.Damage:0.#} armor={armor}->{pending[victim.Index]} hp={victimPawn.Health}");
}
public static void OnTakeDamagePost(DynamicHook h)
{
if (pending.IsEmpty) return;
if (!TryResolveVictim(h, out var victim, out var victimPawn, out _)) return;
if (!pending.TryRemove(victim!.Index, out int restored)) return;
victimPawn!.ArmorValue = restored;
Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue");
}
private static bool TryResolveVictim(DynamicHook h, out CCSPlayerController? victim, out CCSPlayerPawn? pawn, out CTakeDamageInfo? info)
{
victim = null;
pawn = null;
info = null;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return false;
var victimPawn = param.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return false;
var controller = victimPawn.Controller.Value;
if (controller == null || !controller.IsValid) return false;
var resolved = controller.As<CCSPlayerController>();
if (resolved == null || !resolved.IsValid) return false;
var routed = PlayerManager.GetPlayerEvent(resolved);
victim = routed != null && routed.IsValid ? routed : resolved;
pawn = victimPawn;
info = param2;
return true;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9ad3f5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Uncommon, float armorShare = .33f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float ArmorShare { get; set; } = armorShare;
}
}
}

View file

@ -0,0 +1,157 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class Voodoo : ISkill
{
private const Skills skillName = Skills.Voodoo;
private static readonly ConcurrentDictionary<uint, uint> ownerToVictim = [];
private static readonly ConcurrentDictionary<uint, byte> reflecting = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
ownerToVictim.Clear();
reflecting.Clear();
foreach (var player in PlayerManager.GetTickPlayers())
SkillUtils.CloseMenu(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
ownerToVictim.TryRemove(playerIndex, out _);
reflecting.TryRemove(playerIndex, out _);
foreach (var kvp in ownerToVictim)
if (kvp.Value == playerIndex)
ownerToVictim.TryRemove(kvp.Key, out _);
}
public static void EnableSkill(CCSPlayerController player)
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) return;
playerInfo.SkillUsed = false;
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.CreateMenu(player, menuItems);
}
else
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
ownerToVictim.TryRemove(player.Index, out _);
SkillUtils.CloseMenu(player);
}
public static void OnTick()
{
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems);
}
}
public static void TypeSkill(CCSPlayerController player, string[] commands)
{
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
if (playerInfo.SkillUsed)
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("areareaper_used_info")}");
return;
}
if (commands.Length == 0 || !uint.TryParse(commands[0], out uint enemyIndex))
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
return;
}
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (enemy == null || !enemy.IsValid)
{
playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
return;
}
ownerToVictim[player.Index] = enemy.Index;
playerInfo.SkillUsed = true;
playerEvent.PrintToChat($" {ChatColors.Green}{playerEvent.GetTranslation("voodoo_player_info", enemy.PlayerName)}");
}
public static void PlayerHurt(EventPlayerHurt @event)
{
if (ownerToVictim.IsEmpty) return;
var owner = PlayerManager.GetPlayerEvent(@event.Userid);
if (owner == null || !owner.IsValid) return;
if (!ownerToVictim.TryGetValue(owner.Index, out uint victimIndex)) return;
if (@event.DmgHealth <= 0) return;
if (!reflecting.TryAdd(owner.Index, 0)) return;
try
{
var victim = Utilities.GetPlayerFromIndex((int)victimIndex);
if (victim == null || !victim.IsValid) return;
var victimPawn = victim.PlayerPawn?.Value;
if (victimPawn == null || !victimPawn.IsValid || victimPawn.Health <= 0) return;
if (victimPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
int reflected = (int)MathF.Round(@event.DmgHealth * SkillsInfo.GetValue<float>(skillName, "reflectPercent"));
if (reflected < 1) return;
SkillUtils.TakeHealth(victimPawn, reflected, owner, KillfeedIcons.Fist);
}
finally
{
reflecting.TryRemove(owner.Index, out _);
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#7b2d8b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Rare, float reflectPercent = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float ReflectPercent { get; set; } = reflectPercent;
}
}
}

View file

@ -50,15 +50,16 @@ namespace src.player.skills
else
Localization.PrintTranslationToChatAll($" {ChatColors.LightBlue}{{0}}", ["watchmaker_ct"], true, [roundTime]);
player.EmitSound(SkillsInfo.GetValue<string>(skillName, "SoundEvent"));
SkillUtils.EmitSoundToPlayer(player, SkillsInfo.GetValue<string>(skillName, "SoundEvent"), SkillsInfo.GetValue<float>(skillName, "soundVolume"));
var proxy = Utilities.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules").FirstOrDefault();
if (proxy == null) return;
Utilities.SetStateChanged(proxy, "CCSGameRulesProxy", "m_pGameRules");
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff462e", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 1, Rarity rarity = Rarity.Common, int changeRoundTime = 7, string soundEvent = "UIPanorama.sidemenu_select") : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff462e", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 1, Rarity rarity = Rarity.Common, int changeRoundTime = 7, string soundEvent = "UIPanorama.sidemenu_select", float soundVolume = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float SoundVolume { get; set; } = soundVolume;
public int ChangeRoundTime { get; set; } = changeRoundTime;
public string SoundEvent { get; set; } = soundEvent;
}

View file

@ -13,12 +13,7 @@ namespace src.player.skills
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
private static readonly object setLock = new();
private static readonly HashSet<string> weapons = [ "weapon_deagle", "weapon_revolver", "weapon_glock", "weapon_usp_silencer",
"weapon_cz75a", "weapon_fiveseven", "weapon_p250", "weapon_tec9", "weapon_elite", "weapon_hkp2000",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90", "weapon_sg556",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a4", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev" ];
private static HashSet<string> weapons => WeaponPool.All;
public static void LoadSkill()
{

View file

@ -33,6 +33,9 @@ namespace src.utils
using (var sr = new StreamReader(fs))
json = sr.ReadToEnd();
newConfig = JsonConvert.DeserializeObject<SettingsModel>(json) ?? new SettingsModel();
if (IsSectionMissing(json, nameof(SettingsModel.Weapons)))
SaveConfig(newConfig);
}
catch
{
@ -45,6 +48,18 @@ namespace src.utils
}
}
private static bool IsSectionMissing(string json, string section)
{
try
{
return Newtonsoft.Json.Linq.JObject.Parse(json)[section] == null;
}
catch
{
return false;
}
}
public static void SaveConfig(SettingsModel config)
{
lock (fileLock)
@ -92,6 +107,7 @@ namespace src.utils
public string DisableHUDOnDeathPermission { get; set; }
public bool DisableSkillsOnRoundEnd { get; set; }
public int? CurseSkillPerPlayer { get; set; }
public WeaponPools Weapons { get; set; }
public LanguageSystem LanguageSystem { get; set; }
public HtmlHudCustomisation HtmlHudCustomisation { get; set; }
public ChatMessage ChatMessage { get; set; }
@ -124,6 +140,31 @@ namespace src.utils
DisableSkillsOnRoundEnd = false;
CurseSkillPerPlayer = null;
Weapons = new WeaponPools
{
Rifle =
[
"weapon_ak47", "weapon_m4a1", "weapon_m4a1_silencer",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg556",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7",
"weapon_ump45", "weapon_p90", "weapon_mp5sd", "weapon_ssg08",
"weapon_awp", "weapon_scar20", "weapon_g3sg1", "weapon_nova",
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev"
],
Pistol =
[
"weapon_deagle", "weapon_revolver", "weapon_glock", "weapon_usp_silencer",
"weapon_cz75a", "weapon_fiveseven", "weapon_p250", "weapon_tec9",
"weapon_elite", "weapon_hkp2000"
],
Grenade =
[
"weapon_hegrenade", "weapon_flashbang", "weapon_smokegrenade",
"weapon_molotov", "weapon_incgrenade", "weapon_decoy"
],
};
LanguageSystem = new LanguageSystem
{
DefaultLangCode = "en",
@ -204,6 +245,13 @@ namespace src.utils
}
}
public class WeaponPools
{
public required List<string> Rifle { get; set; }
public required List<string> Pistol { get; set; }
public required List<string> Grenade { get; set; }
}
public class ChatMessage
{
public required float MaxWidth { get; set; }

View file

@ -0,0 +1,204 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using src.player;
using System.Collections.Concurrent;
using System.Drawing;
using System.Globalization;
namespace src.utils
{
public static class DecoyRing
{
private const string discModel = "models/dev/grenade_trajectory/grenade_target.vmdl";
private const float discBaseRadius = 13.462f;
private const int discAlpha = 178;
private const int outerSegments = 24;
private const int innerSegments = 18;
private const float ringHeight = 3f;
private const float maxLifetime = 25f;
private static readonly ConcurrentDictionary<string, Ring> rings = [];
private sealed class Ring
{
public uint? Disc { get; set; }
public List<uint> Beams { get; } = [];
}
public static void PreloadAssets()
{
jRandomSkills.Instance.AddToManifest(discModel);
}
public static void Show(Skills skill, uint id, Vector center, float radius)
{
if (radius <= 0) return;
string key = MakeKey(skill, id);
var ring = new Ring();
if (!rings.TryAdd(key, ring)) return;
SpawnDisc(center, radius, ring);
if (!EntityManager.OverBudget())
{
Color color = GetSkillColor(skill);
SpawnRing(center, radius, outerSegments, color, ring);
SpawnRing(center, radius * .5f, innerSegments, color, ring);
}
jRandomSkills.Instance.AddTimer(maxLifetime, () => Hide(skill, id), TimerFlags.STOP_ON_MAPCHANGE);
}
public static void Hide(Skills skill, uint id)
{
if (!rings.TryRemove(MakeKey(skill, id), out var ring)) return;
Destroy(ring);
}
public static void ClearAll(Skills skill)
{
string prefix = skill + "|";
foreach (var key in rings.Keys)
{
if (!key.StartsWith(prefix, StringComparison.Ordinal)) continue;
if (!rings.TryRemove(key, out var ring)) continue;
Destroy(ring);
}
}
private static void Destroy(Ring ring)
{
foreach (uint beamIndex in ring.Beams)
EntityManager.DestroyBeam(beamIndex);
ring.Beams.Clear();
if (ring.Disc == null) return;
uint discIndex = ring.Disc.Value;
ring.Disc = null;
EntityManager.DestroyEntity(discIndex, 0f);
}
private static string MakeKey(Skills skill, uint id)
{
return string.Create(CultureInfo.InvariantCulture, $"{skill}|{id}");
}
private static Color GetSkillColor(Skills skill)
{
try
{
string hex = SkillsInfo.GetValue<string>(skill, "color");
if (!string.IsNullOrEmpty(hex))
return ColorTranslator.FromHtml(hex);
}
catch { }
return Color.White;
}
private static void SpawnDisc(Vector center, float radius, Ring ring)
{
var disc = EntityManager.CreateTrackedPropOverride(EntityManager.SystemOwnerIndex);
if (disc == null || !disc.IsValid) return;
uint discIndex = disc.Index;
ring.Disc = discIndex;
float scale = radius / discBaseRadius;
Vector pos = new(center.X, center.Y, center.Z + 1f);
Server.NextFrame(() =>
{
var prop = Utilities.GetEntityFromIndex<CDynamicProp>((int)discIndex);
if (prop == null || !prop.IsValid) return;
var node = prop.CBodyComponent?.SceneNode?.Owner?.Entity;
if (node != null)
node.Flags = (uint)(node.Flags & ~(1 << 2));
MakeNonSolid(prop);
prop.SetModel(discModel);
prop.Teleport(pos, new QAngle(0, 0, 0));
prop.DispatchSpawn();
prop.Render = Color.FromArgb(discAlpha, 255, 255, 255);
Utilities.SetStateChanged(prop, "CBaseModelEntity", "m_clrRender");
var skeleton = prop.CBodyComponent?.SceneNode?.GetSkeletonInstance();
if (skeleton != null)
{
skeleton.Scale = scale;
prop.AcceptInput("SetScale", null, null, scale.ToString(CultureInfo.InvariantCulture));
}
Utilities.SetStateChanged(prop, "CBaseEntity", "m_CBodyComponent");
});
}
private static void MakeNonSolid(CDynamicProp prop)
{
var collision = prop.Collision;
if (collision == null) return;
collision.SolidType = SolidType_t.SOLID_NONE;
collision.SolidFlags = 12;
var attribute = collision.CollisionAttribute;
if (attribute == null) return;
attribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_NEVER;
attribute.CollisionFunctionMask = 0;
attribute.InteractsAs = 0;
attribute.InteractsWith = 0;
attribute.InteractsExclude = 0;
}
private static void SpawnRing(Vector center, float radius, int segments, Color color, Ring ring)
{
if (segments < 3 || radius <= 0) return;
float step = MathF.Tau / segments;
Vector previous = PointOn(center, radius, 0);
for (int i = 1; i <= segments; i++)
{
Vector next = PointOn(center, radius, step * i);
var beam = EntityManager.CreateTrackedBeam(EntityManager.SystemOwnerIndex, previous, next, color);
if (beam != null && beam.IsValid)
{
beam.Width = 1.2f;
beam.EndWidth = 1.2f;
Utilities.SetStateChanged(beam, "CBeam", "m_fWidth");
Utilities.SetStateChanged(beam, "CBeam", "m_fEndWidth");
ring.Beams.Add(beam.Index);
}
previous = next;
}
}
private static Vector PointOn(Vector center, float radius, float angle)
{
return new Vector(
center.X + MathF.Cos(angle) * radius,
center.Y + MathF.Sin(angle) * radius,
center.Z + ringHeight);
}
}
}

View file

@ -273,6 +273,40 @@ namespace src.utils
return false;
}
public static bool DestroyBeam(uint entityIndex)
{
try
{
var beam = Utilities.GetEntityFromIndex<CBeam>((int)entityIndex);
if (beam != null && beam.IsValid)
{
beam.Width = 0;
beam.EndWidth = 0;
beam.Render = Color.FromArgb(0, 0, 0, 0);
Utilities.SetStateChanged(beam, "CBeam", "m_fWidth");
Utilities.SetStateChanged(beam, "CBeam", "m_fEndWidth");
Utilities.SetStateChanged(beam, "CBaseModelEntity", "m_clrRender");
}
}
catch (Exception ex)
{
Server.PrintToConsole($"[EntityManager] DestroyBeam {entityIndex}: {ex.Message}");
}
bool killed = DestroyEntity(entityIndex);
if (!SuppressKills)
Server.NextFrame(() =>
{
var leftover = Utilities.GetEntityFromIndex<CBeam>((int)entityIndex);
if (leftover != null && leftover.IsValid)
leftover.Remove();
});
return killed;
}
public static bool DestroyEntity<T>(uint? entityIndex) where T : CBaseEntity
{
if (entityIndex == null) return false;

View file

@ -281,7 +281,7 @@ namespace jRandomSkills.src.utils
"weapon_sg556" => KillfeedIcons.Sg556,
"weapon_famas" => KillfeedIcons.Famas,
"weapon_galilar" => KillfeedIcons.Galilar,
"weapon_m4a4" => KillfeedIcons.M4A4,
"weapon_m4a1" => KillfeedIcons.M4A4,
"weapon_m4a1_silencer" => KillfeedIcons.M4A1,
"weapon_ak47" => KillfeedIcons.Ak47,
"weapon_aug" => KillfeedIcons.Aug,

View file

@ -40,7 +40,7 @@ namespace src.utils
LazySig<MemoryFunctionVoid<CBasePlayerPawn, QAngle>>("SnapViewAngles", s => new(s));
// private static readonly int collisionRulesChangedOffset = GameData.GetOffset("CBaseEntity_CollisionRulesChanged");
public static void PrintToChat(CCSPlayerController player, string? msg, string border = "tb", string? title = null)
public static void PrintToChat(CCSPlayerController player, string? msg, string border = "tb", string? title = null, bool ignoreIlliterate = false)
{
if (!player.IsValid) return;
@ -49,7 +49,7 @@ namespace src.utils
char symbol = config.LineSymbol;
if (string.IsNullOrEmpty(title)) title = player.GetTranslation("jRandomSkills");
if (Illiterate.CheckIlliterateSkill(player))
if (!ignoreIlliterate && Illiterate.CheckIlliterateSkill(player))
msg = Illiterate.GetRandomText(msg);
if (border.Contains('t') && config.LineShow)
@ -60,11 +60,39 @@ namespace src.utils
player.PrintToChat($" {MeansureString.GetTextDashed("", maxWidth, symbol, config.LineColor)}");
}
public static void EmitSoundToPlayer(CCSPlayerController? listener, string soundEvent, float volume)
{
var target = PlayerManager.GetPlayerFromEvent(listener);
if (target == null || !target.IsValid) return;
target.EmitSound(soundEvent, new RecipientFilter(target), volume);
}
public static bool IsFreezeTime()
{
return jRandomSkills.Instance?.GameRules?.FreezePeriod == true;
}
public static bool IsPistolRound()
{
var gameRules = jRandomSkills.Instance?.GameRules;
if (gameRules == null) return false;
if (gameRules.TotalRoundsPlayed == 0 || gameRules.GameRestart) return true;
try
{
if (ConVar.Find("mp_halftime")?.GetPrimitiveValue<bool>() != true) return false;
int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue<int>() ?? 0;
return maxRounds > 0 && maxRounds / 2 == gameRules.TotalRoundsPlayed;
}
catch
{
return false;
}
}
public static bool IsHudFrame() => Server.TickCount % 4 == 0;
public static void RegisterSkill(Skills skill, string color, bool display = true)
@ -396,7 +424,8 @@ namespace src.utils
Skills.Deaf, Skills.ExpensiveAmmo, Skills.Giant, Skills.Glitch,
Skills.Jammer, Skills.JumpBan, Skills.JumpCurse, Skills.LifeSwap,
Skills.Magnifier, Skills.MoneySwap, Skills.Nightmare, Skills.Poison,
Skills.JetKick, Skills.PrimaryBan, Skills.Thief, Skills.WildThrow
Skills.JetKick, Skills.PrimaryBan, Skills.Thief, Skills.WildThrow,
Skills.Voodoo, Skills.Nemesis, Skills.Bounty
];
private static readonly HashSet<string> curseSkillNames = new(curseSkills.Select(s => s.ToString()), StringComparer.Ordinal);
@ -490,6 +519,73 @@ namespace src.utils
else curseCounts[victimIndex] = used - 1;
}
private const uint crosshairBit = 1u << 8;
private static readonly object hudSuppressionLock = new();
private static readonly Dictionary<uint, HashSet<string>> crosshairOwners = [];
private static readonly Dictionary<uint, HashSet<string>> radarOwners = [];
public static void SetCrosshairHidden(CCSPlayerController? player, string owner, bool hide)
{
if (player == null || !player.IsValid) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) return;
if (!TryUpdateOwners(crosshairOwners, player.Index, owner, hide, out bool suppressed)) return;
if (suppressed) pawn.HideHUD |= crosshairBit;
else pawn.HideHUD &= ~crosshairBit;
Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_iHideHUD");
}
public static void SetRadarDisabled(CCSPlayerController? player, string owner, bool disable)
{
if (player == null || !player.IsValid) return;
if (!TryUpdateOwners(radarOwners, player.Index, owner, disable, out bool suppressed)) return;
player.ReplicateConVar("sv_disable_radar", suppressed ? "1" : "0");
}
public static void ClearHudSuppression(uint playerIndex)
{
lock (hudSuppressionLock)
{
crosshairOwners.Remove(playerIndex);
radarOwners.Remove(playerIndex);
}
}
public static void ClearAllHudSuppression()
{
lock (hudSuppressionLock)
{
crosshairOwners.Clear();
radarOwners.Clear();
}
}
private static bool TryUpdateOwners(Dictionary<uint, HashSet<string>> registry, uint playerIndex, string owner, bool claim, out bool suppressed)
{
lock (hudSuppressionLock)
{
if (!registry.TryGetValue(playerIndex, out var owners))
{
owners = [];
registry[playerIndex] = owners;
}
bool before = owners.Count > 0;
bool changed = claim ? owners.Add(owner) : owners.Remove(owner);
suppressed = owners.Count > 0;
if (!suppressed) registry.Remove(playerIndex);
return changed && before != suppressed;
}
}
public static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player, bool respectCurseLimit = false)
{
if (player == null || !player.IsValid) return [];
@ -575,6 +671,26 @@ namespace src.utils
return true;
}
public readonly record struct HiddenPawn(uint Index, CsTeam Team, CCSPlayerPawn Pawn, bool HoldsBomb);
public static List<HiddenPawn> ResolveHiddenPawns(ICollection<uint> playerIndexes, uint? bombOwnerIndex)
{
List<HiddenPawn> hidden = new(playerIndexes.Count);
foreach (var playerIndex in playerIndexes)
{
var controller = PlayerManager.GetPlayerEvent(Utilities.GetPlayerFromIndex((int)playerIndex));
if (controller == null || !controller.IsValid) continue;
var pawn = controller.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid) continue;
hidden.Add(new HiddenPawn(controller.Index, controller.Team, pawn, bombOwnerIndex == controller.Index));
}
return hidden;
}
public static void HideCarriedEntities(CCheckTransmitInfo info, CCSPlayerPawn? pawn)
{
if (pawn == null || !pawn.IsValid) return;

View file

@ -21,6 +21,7 @@ namespace src.utils
private static SkillsInfoModel? _indexedConfig;
private static ConcurrentDictionary<string, DefaultSkillInfo> _byName = new();
private static readonly ConcurrentDictionary<(Type Type, string Key), MemberInfo?> _memberCache = new();
private static readonly ConcurrentDictionary<(DefaultSkillInfo Config, string Key, Type Target), object?> _valueCache = new();
public static SkillsInfoModel LoadSkillsInfo()
{
@ -92,6 +93,12 @@ namespace src.utils
if (!_byName.TryGetValue(skill.ToString()!, out var skillConfig) || skillConfig == null)
return default!;
object? cached = _valueCache.GetOrAdd((skillConfig, key, typeof(T)), k => Resolve(k.Config, k.Key, k.Target));
return cached == null ? default! : (T)cached;
}
private static object? Resolve(DefaultSkillInfo skillConfig, string key, Type targetType)
{
var member = _memberCache.GetOrAdd((skillConfig.GetType(), key), k =>
{
MemberInfo? m = k.Type.GetProperty(k.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
@ -106,11 +113,10 @@ namespace src.utils
_ => null
};
if (value == null) return default!;
if (value == null) return null;
Type targetType = typeof(T);
Type? underlyingType = Nullable.GetUnderlyingType(targetType);
return (T)Convert.ChangeType(value, underlyingType ?? targetType);
return Convert.ChangeType(value, underlyingType ?? targetType);
}
private static void EnsureIndex()
@ -124,6 +130,7 @@ namespace src.utils
_byName = dict;
_indexedConfig = config;
_memberCache.Clear();
_valueCache.Clear();
}
public class SkillsInfoModel : ConcurrentBag<DefaultSkillInfo>
@ -147,10 +154,11 @@ namespace src.utils
}
}
public class DefaultSkillInfo(Skills skill, bool active = true, string color = "#ffffff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common)
public class DefaultSkillInfo(Skills skill, bool active = true, string color = "#ffffff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, bool disableOnPistolRound = false)
{
public bool NeedsTeammates { get; set; } = needsTeammates;
public bool DisableOnFreezeTime { get; set; } = disableOnFreezeTime;
public bool DisableOnPistolRound { get; set; } = disableOnPistolRound;
public int OnlyTeam { get; set; } = (int)onlyTeam;
public string Color { get; set; } = color;
public bool Active { get; set; } = active;

View file

@ -0,0 +1,84 @@
namespace src.utils
{
public static class WeaponPool
{
private static readonly object buildLock = new();
private static bool built;
private static HashSet<string> rifles = new(StringComparer.Ordinal);
private static HashSet<string> pistols = new(StringComparer.Ordinal);
private static HashSet<string> grenades = new(StringComparer.Ordinal);
private static HashSet<string> all = new(StringComparer.Ordinal);
private static string[] rifleArray = [];
private static string[] pistolArray = [];
private static string[] grenadeArray = [];
private static string[] allArray = [];
public static HashSet<string> Rifles { get { Build(); return rifles; } }
public static HashSet<string> Pistols { get { Build(); return pistols; } }
public static HashSet<string> Grenades { get { Build(); return grenades; } }
public static HashSet<string> All { get { Build(); return all; } }
public static string[] RifleList { get { Build(); return rifleArray; } }
public static string[] PistolList { get { Build(); return pistolArray; } }
public static string[] GrenadeList { get { Build(); return grenadeArray; } }
public static string[] AllList { get { Build(); return allArray; } }
public static bool IsRifle(string? weapon) => weapon != null && Rifles.Contains(Normalize(weapon));
public static bool IsPistol(string? weapon) => weapon != null && Pistols.Contains(Normalize(weapon));
public static bool IsGrenade(string? weapon) => weapon != null && Grenades.Contains(Normalize(weapon));
public static bool IsWeapon(string? weapon) => weapon != null && All.Contains(Normalize(weapon));
public static string Normalize(string weapon)
{
if (string.IsNullOrEmpty(weapon)) return string.Empty;
return weapon.StartsWith("weapon_", StringComparison.Ordinal) ? weapon : "weapon_" + weapon;
}
public static void Invalidate()
{
lock (buildLock) built = false;
}
private static void Build()
{
if (built) return;
lock (buildLock)
{
if (built) return;
var pools = Config.LoadedConfig?.Weapons;
rifles = Collect(pools?.Rifle);
pistols = Collect(pools?.Pistol);
grenades = Collect(pools?.Grenade);
all = new HashSet<string>(rifles, StringComparer.Ordinal);
all.UnionWith(pistols);
rifleArray = [.. rifles];
pistolArray = [.. pistols];
grenadeArray = [.. grenades];
allArray = [.. all];
built = true;
}
}
private static HashSet<string> Collect(List<string>? source)
{
var set = new HashSet<string>(StringComparer.Ordinal);
if (source == null) return set;
foreach (var entry in source)
{
if (string.IsNullOrWhiteSpace(entry)) continue;
set.Add(Normalize(entry.Trim()));
}
return set;
}
}
}

View file

@ -22,6 +22,54 @@
"DisableHUDOnDeathPermission": "@jRandomSkills/death",
"DisableSkillsOnRoundEnd": false,
"CurseSkillPerPlayer": null,
"Weapons": {
"Rifle": [
"weapon_ak47",
"weapon_m4a1",
"weapon_m4a1_silencer",
"weapon_famas",
"weapon_galilar",
"weapon_aug",
"weapon_sg556",
"weapon_mp9",
"weapon_mac10",
"weapon_bizon",
"weapon_mp7",
"weapon_ump45",
"weapon_p90",
"weapon_mp5sd",
"weapon_ssg08",
"weapon_awp",
"weapon_scar20",
"weapon_g3sg1",
"weapon_nova",
"weapon_xm1014",
"weapon_mag7",
"weapon_sawedoff",
"weapon_m249",
"weapon_negev"
],
"Pistol": [
"weapon_deagle",
"weapon_revolver",
"weapon_glock",
"weapon_usp_silencer",
"weapon_cz75a",
"weapon_fiveseven",
"weapon_p250",
"weapon_tec9",
"weapon_elite",
"weapon_hkp2000"
],
"Grenade": [
"weapon_hegrenade",
"weapon_flashbang",
"weapon_smokegrenade",
"weapon_molotov",
"weapon_incgrenade",
"weapon_decoy"
]
},
"LanguageSystem": {
"DefaultLangCode": "en",
"DisableGeoLite": false,

View file

@ -60,6 +60,13 @@
"blastshot": "Explosivschuss",
"blastshot_desc": "Drücke Attack2 mit der MP5, um eine HE-Granate abzufeuern",
"bounty": "Kopfgeld",
"bounty_desc": "Setze ein Kopfgeld auf einen Gegner aus; wer ihn tötet, bekommt das Geld",
"bounty_select_info": "Wähle einen Spieler:",
"bounty_player_info": "Du hast {1}$ Kopfgeld auf '{0}' ausgesetzt.",
"bounty_enemy_info": "Auf deinen Kopf wurden {0}$ ausgesetzt.",
"bounty_claimed_info": "Du hast '{0}' getötet und {1}$ kassiert.",
"bunnyhop": "Hase",
"bunnyhop_desc": "Du erhältst automatisch bunnyhop",
@ -144,6 +151,10 @@
"dwarf_desc": "Zufällige Charaktergröße zu Beginn der Runde",
"dwarf_desc2": "Dein Größenmultiplikator beträgt: {0}x",
"empgrenade": "EMP-Granate",
"empgrenade_desc": "Wer von deiner Granate getroffen wird, verliert kurzzeitig Radar und Fadenkreuz",
"empgrenade_enemy_info": "Eine EMP-Granate hat dein Radar und Fadenkreuz lahmgelegt.",
"enemyspawn": "Feindes-Spawn",
"enemyspawn_desc": "Klicke auf [css_useSkill], um zum Feindes-Spawn zu teleportieren",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Keine Oberfläche zum Einhaken.",
"grapple_pulling_info": "ZUG",
"gravitydecoy": "Schwerkraft-Ablenker",
"gravitydecoy_desc": "Dein Ablenkungsköder verändert die Schwerkraft aller in der Nähe",
"grenadier": "Grenadier",
"grenadier_desc": "Du hast unendlich viele Granaten",
@ -239,6 +253,9 @@
"healingchicken": "Heilendes Huhn",
"healingchicken_desc": "Deine Hühner heilen dich, solange du in ihrer Nähe bist",
"heavyweight": "Schwergewicht",
"heavyweight_desc": "Fähigkeiten, die dich stoßen oder verlangsamen, wirken nicht auf dich",
"hermit": "Einsiedler",
"hermit_desc": "Durch das Töten von Gegnern werden Munition und ein Teil der Gesundheit wiederhergestellt",
@ -360,6 +377,12 @@
"bankrupt_player_info": "Der Spieler ‚{0}‘ ist bankrott.",
"bankrupt_select_info": "Wähle einen Spieler aus, der bankrott gehen soll:",
"nemesis": "Nemesis",
"nemesis_desc": "Der von dir markierte Spieler erleidet zusätzlichen Schaden",
"nemesis_select_info": "Wähle einen Spieler:",
"nemesis_player_info": "Du hast '{0}' markiert.",
"nemesis_enemy_info": "Du wurdest als Nemesis markiert und erleidest jetzt von allen zusätzlichen Schaden.",
"nightmare": "Albtraum",
"nightmare_desc": "Lass einen ausgewählten Gegner eine schreckliche Vision erleben",
"nightmare_disable_info": "Der Albtraum ist vorbei.",
@ -423,6 +446,10 @@
"prosthesis": "Prothese",
"prosthesis_desc": "Arme und Beine sind kugelsicher",
"punisher": "Bestrafer",
"punisher_desc": "Du sammelst einen Teil des erlittenen Schadens und legst ihn auf deinen nächsten Treffer",
"punisher_released_info": "Du hast {0} zusätzlichen Schaden verursacht und ebenso viel Leben erhalten.",
"push": "Pusher",
"push_desc": "Du hast eine zufällige Chance, einen Gegner zurückzustoßen, wenn du ihn triffst",
"push_desc2": "Deine Chancen, den Gegner zurückzustoßen, betragen: {0} %",
@ -460,6 +487,10 @@
"returntosender": "Zurück zum Absender",
"returntosender_desc": "Der erste Treffer eines Gegners schickt ihn zurück zu seinem Spawn",
"rewind": "Rückspulen",
"rewind_desc": "Drücke [css_useSkill], um hier eine Marke zu setzen und kurz darauf dorthin zurückzukehren",
"rewind_pending_info": "RÜCKKEHR: {0}",
"richboy": "Rich Boy",
"richboy_desc": "Du erhältst zu Beginn der Runde einen zufälligen Geldbetrag",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Stolperdraht platziert.",
"tripwire_triggered_info": "'{0}' hat deinen Stolperdraht ausgelöst.",
"truearmor": "Echte Rüstung",
"truearmor_desc": "Ein Teil des Schadens geht auf deine Rüstung statt auf dein Leben",
"voodoo": "Voodoo",
"voodoo_desc": "Der markierte Gegner erleidet einen Teil des Schadens, den du erleidest",
"voodoo_select_info": "Wähle einen Spieler:",
"voodoo_player_info": "Du hast '{0}' an deinen Voodoo gebunden.",
"wallhack": "Wallhack",
"wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen",

View file

@ -60,6 +60,13 @@
"blastshot": "Blast Shot",
"blastshot_desc": "Press Attack2 with the MP5 to fire an HE grenade",
"bounty": "Bounty",
"bounty_desc": "Put a price on an enemy's head; whoever kills them takes the money",
"bounty_select_info": "Choose a player:",
"bounty_player_info": "You put a {1}$ bounty on '{0}'.",
"bounty_enemy_info": "A {0}$ bounty was placed on your head.",
"bounty_claimed_info": "You killed '{0}' and claimed {1}$.",
"bunnyhop": "Bunny",
"bunnyhop_desc": "You get auto \"BunnyHop\"",
@ -137,6 +144,10 @@
"dwarf_desc": "Random character size at the start of the round",
"dwarf_desc2": "Your size multiplier is: {0}x",
"empgrenade": "EMP Grenade",
"empgrenade_desc": "Anyone hurt by your grenade loses their radar and crosshair for a while",
"empgrenade_enemy_info": "An EMP grenade knocked out your radar and crosshair.",
"enemyspawn": "Enemy Spawn",
"enemyspawn_desc": "Click [css_useSkill] to teleport to the enemy spawn",
@ -223,6 +234,9 @@
"grapple_no_anchor_info": "No surface to hook onto.",
"grapple_pulling_info": "PULLING",
"gravitydecoy": "Gravity Decoy",
"gravitydecoy_desc": "Your decoy changes the gravity of everyone nearby",
"grenadier": "Grenadier",
"grenadier_desc": "You have infinite hegrenade",
@ -232,6 +246,9 @@
"healingsmoke": "Healing Smoke",
"healingsmoke_desc": "Your smoke grenades heal",
"heavyweight": "Heavyweight",
"heavyweight_desc": "Skills that push or slow you down have no effect on you",
"hermit": "Hermit",
"hermit_desc": "Killing restores ammo and a portion of health",
@ -360,6 +377,12 @@
"demoneye": "Demon Eye",
"demoneye_desc": "You deal damage to every enemy you are looking at",
"nemesis": "Nemesis",
"nemesis_desc": "The player you mark takes extra damage",
"nemesis_select_info": "Choose a player:",
"nemesis_player_info": "You marked '{0}'.",
"nemesis_enemy_info": "You have been marked as a nemesis, you now take extra damage from everyone.",
"nightmare": "Nightmare",
"nightmare_desc": "Force a chosen enemy to experience a terrifying vision",
"nightmare_disable_info": "The nightmare has faded.",
@ -423,6 +446,10 @@
"prosthesis": "Prosthesis",
"prosthesis_desc": "Arms and legs are bulletproof",
"punisher": "Punisher",
"punisher_desc": "You bank part of the damage you take and add it to your next hit",
"punisher_released_info": "You dealt {0} extra damage and healed for the same amount.",
"push": "Pusher",
"push_desc": "You have a random chance to push an enemy back when hitting them",
"push_desc2": "Your chances of pushing back the enemy are: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Return to Sender",
"returntosender_desc": "The first hit on an enemy sends them back to their spawn",
"rewind": "Rewind",
"rewind_desc": "Click [css_useSkill] to drop a marker where you stand and return to it shortly after",
"rewind_pending_info": "RETURN: {0}",
"richboy": "Rich Boy",
"richboy_desc": "You receive a random amount of money at the start of the round",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Tripwire placed.",
"tripwire_triggered_info": "'{0}' triggered your tripwire.",
"truearmor": "True Armor",
"truearmor_desc": "Part of the damage you take is paid by your armor instead of your health",
"voodoo": "Voodoo",
"voodoo_desc": "The enemy you mark also takes part of the damage you take",
"voodoo_select_info": "Choose a player:",
"voodoo_player_info": "You bound '{0}' to your voodoo.",
"wallhack": "Wallhack",
"wallhack_desc": "You can see enemies through walls",

View file

@ -60,6 +60,13 @@
"blastshot": "Tir explosif",
"blastshot_desc": "Appuyez sur Attack2 avec le MP5 pour tirer une grenade HE",
"bounty": "Prime",
"bounty_desc": "Mets une prime sur la tête d'un ennemi ; celui qui le tue empoche l'argent",
"bounty_select_info": "Choisis un joueur :",
"bounty_player_info": "Tu as mis une prime de {1}$ sur '{0}'.",
"bounty_enemy_info": "Une prime de {0}$ a été mise sur ta tête.",
"bounty_claimed_info": "Tu as tué '{0}' et empoché {1}$.",
"bunnyhop": "Lapin",
"bunnyhop_desc": "Vous obtenez l’auto « BunnyHop »",
@ -144,6 +151,10 @@
"dwarf_desc": "Taille du personnage aléatoire au début du round",
"dwarf_desc2": "Votre multiplicateur de taille est : {0}x",
"empgrenade": "Grenade EMP",
"empgrenade_desc": "Ceux que ta grenade blesse perdent leur radar et leur viseur un moment",
"empgrenade_enemy_info": "Une grenade EMP a coupé ton radar et ton viseur.",
"enemyspawn": "Spawn Ennemi",
"enemyspawn_desc": "Appuyez sur [css_useSkill] pour vous téléporter au spawn ennemi",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Aucune surface pour accrocher le grappin.",
"grapple_pulling_info": "TRACTION",
"gravitydecoy": "Leurre gravitationnel",
"gravitydecoy_desc": "Ton leurre modifie la gravité de tous ceux qui sont à proximité",
"grenadier": "Grenadier",
"grenadier_desc": "Tu as des grenades HE infinies",
@ -239,6 +253,9 @@
"healingchicken": "Poulet guérisseur",
"healingchicken_desc": "Vos poulets vous soignent lorsque vous êtes à proximité",
"heavyweight": "Poids lourd",
"heavyweight_desc": "Les compétences qui te poussent ou te ralentissent n'ont aucun effet sur toi",
"hermit": "Ermite",
"hermit_desc": "Tuer restaure des munitions et une partie de vos PV",
@ -360,6 +377,12 @@
"bankrupt_player_info": "Le joueur « {0} » est en faillite.",
"bankrupt_select_info": "Mettre un joueur en faillite :",
"nemesis": "Némésis",
"nemesis_desc": "Le joueur que tu marques subit des dégâts supplémentaires",
"nemesis_select_info": "Choisis un joueur :",
"nemesis_player_info": "Tu as marqué '{0}'.",
"nemesis_enemy_info": "Vous avez été marqué comme némésis, vous subissez désormais des dégâts supplémentaires de la part de tous.",
"nightmare": "Cauchemar",
"nightmare_desc": "Faites subir une vision terrifiante à un ennemi choisi",
"nightmare_disable_info": "Le cauchemar s'est dissipé.",
@ -423,6 +446,10 @@
"prosthesis": "Prothèses",
"prosthesis_desc": "Les bras et les jambes sont pare-balles",
"punisher": "Punisseur",
"punisher_desc": "Tu accumules une partie des dégâts subis et l'ajoutes à ton prochain tir",
"punisher_released_info": "Tu as infligé {0} dégâts en plus et récupéré autant de vie.",
"push": "Pousseur",
"push_desc": "Vous avez une chance aléatoire de repousser un ennemi en le touchant",
"push_desc2": "Votre chance de repousser l’ennemi est : {0}%",
@ -460,6 +487,10 @@
"returntosender": "Retour à l’Expéditeur",
"returntosender_desc": "Le premier coup porté à un ennemi le renvoie à son spawn",
"rewind": "Rembobinage",
"rewind_desc": "Appuie sur [css_useSkill] pour poser un repère ici et y revenir peu après",
"rewind_pending_info": "RETOUR : {0}",
"richboy": "Gosse de Riche",
"richboy_desc": "Vous recevez une somme d’argent aléatoire au début du round",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Fil-piège placé.",
"tripwire_triggered_info": "'{0}' a déclenché votre fil-piège.",
"truearmor": "Vraie armure",
"truearmor_desc": "Une partie des dégâts est absorbée par ton armure au lieu de ta vie",
"voodoo": "Vaudou",
"voodoo_desc": "L'ennemi que tu marques subit aussi une partie des dégâts que tu reçois",
"voodoo_select_info": "Choisis un joueur :",
"voodoo_player_info": "Tu as lié '{0}' à ton vaudou.",
"wallhack": "Wallhack",
"wallhack_desc": "Vous pouvez voir les ennemis à travers les murs",

View file

@ -60,6 +60,13 @@
"blastshot": "Mocne Uderzenie",
"blastshot_desc": "Naciśnij Attack2 na MP5, aby wystrzelić granat HE",
"bounty": "Nagroda",
"bounty_desc": "Wyznacz nagrodę za głowę wroga; kto go zabije, dostaje pieniądze",
"bounty_select_info": "Wybierz gracza:",
"bounty_player_info": "Wyznaczyłeś nagrodę {1}$ za '{0}'.",
"bounty_enemy_info": "Za twoją głowę wyznaczono {0}$.",
"bounty_claimed_info": "Zabiłeś '{0}' i zgarnąłeś {1}$.",
"bunnyhop": "Królik",
"bunnyhop_desc": "Otrzymujesz auto \"BunnyHopa\"",
@ -144,6 +151,10 @@
"dwarf_desc": "Losowa wielkość postaci na początku rundy",
"dwarf_desc2": "Twój mnożnik wielkości to {0}x",
"empgrenade": "Granat EMP",
"empgrenade_desc": "Kto oberwie twoim granatem, na chwilę traci radar i celownik",
"empgrenade_enemy_info": "Granat EMP wyłączył twój radar i celownik.",
"enemyspawn": "Resp Wroga",
"enemyspawn_desc": "Kliknij [css_useSkill], aby teleportować się na resp wroga",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Brak powierzchni do zaczepienia haka.",
"grapple_pulling_info": "PRZYCIĄGANIE",
"gravitydecoy": "Grawitacyjna wabik",
"gravitydecoy_desc": "Twój wabik zmienia grawitację wszystkich w pobliżu",
"grenadier": "Granatnik",
"grenadier_desc": "Masz nieskończone granaty odłamkowe",
@ -239,6 +253,9 @@
"healingchicken": "Lecząca Kura",
"healingchicken_desc": "Twoje kury leczą Cię, gdy jesteś w pobliżu",
"heavyweight": "Waga ciężka",
"heavyweight_desc": "Umiejętności, które cię popychają lub spowalniają, nie działają na ciebie",
"hermit": "Pustelnik",
"hermit_desc": "Zabijanie przywraca amunicję i część zdrowia",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Wymieniłeś swoje pieniądze z graczem '{0}'.",
"moneyswap_select_info": "Zamień się kasą z graczem:",
"nemesis": "Nemezis",
"nemesis_desc": "Oznaczony gracz otrzymuje dodatkowe obrażenia",
"nemesis_select_info": "Wybierz gracza:",
"nemesis_player_info": "Oznaczyłeś '{0}'.",
"nemesis_enemy_info": "Zostałeś oznaczony jako nemezis, teraz otrzymujesz dodatkowe obrażenia od wszystkich.",
"nightmare": "Koszmar",
"nightmare_desc": "Wywołaj u wybranego przeciwnika przerażającą wizję",
"nightmare_disable_info": "Koszmar minął.",
@ -423,6 +446,10 @@
"prosthesis": "Proteza",
"prosthesis_desc": "Ramiona i nogi są kuloodporne",
"punisher": "Karzyciel",
"punisher_desc": "Gromadzisz część otrzymanych obrażeń i dodajesz je do następnego trafienia",
"punisher_released_info": "Zadałeś {0} dodatkowych obrażeń i odzyskałeś tyle samo życia.",
"push": "Odpychacz",
"push_desc": "Masz losową szansę na odepchnięcie wroga po trafieniu go",
"push_desc2": "Twoje szanse na odepchnięcie wroga to: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Zwrot do Nadawcy",
"returntosender_desc": "Pierwsze trafienie wroga powoduje, że wraca on na swój resp",
"rewind": "Przewijanie",
"rewind_desc": "Naciśnij [css_useSkill], aby zostawić znacznik i wkrótce do niego wrócić",
"rewind_pending_info": "POWRÓT: {0}",
"richboy": "Bogacz",
"richboy_desc": "Na początku rundy otrzymujesz losową kwotę gotówki",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Linka została rozstawiona.",
"tripwire_triggered_info": "'{0}' uruchomił Twoją linkę.",
"truearmor": "Prawdziwy pancerz",
"truearmor_desc": "Część obrażeń pokrywa twój pancerz zamiast zdrowia",
"voodoo": "Voodoo",
"voodoo_desc": "Oznaczony wróg również otrzymuje część obrażeń, które ty dostajesz",
"voodoo_select_info": "Wybierz gracza:",
"voodoo_player_info": "Związałeś '{0}' swoim voodoo.",
"wallhack": "Wallhack",
"wallhack_desc": "Widzisz wrogów przez ściany",

View file

@ -60,6 +60,13 @@
"blastshot": "Tiro Explosivo",
"blastshot_desc": "Pressione Attack2 com a MP5 para disparar uma granada HE",
"bounty": "Recompensa",
"bounty_desc": "Coloque um preço na cabeça de um inimigo; quem o matar leva o dinheiro",
"bounty_select_info": "Escolha um jogador:",
"bounty_player_info": "Você colocou {1}$ de recompensa em '{0}'.",
"bounty_enemy_info": "Puseram {0}$ de recompensa na sua cabeça.",
"bounty_claimed_info": "Você matou '{0}' e levou {1}$.",
"bunnyhop": "Coelho",
"bunnyhop_desc": "Você ganha \"BunnyHop\" automático",
@ -144,6 +151,10 @@
"dwarf_desc": "Sua altura agora é aleatória",
"dwarf_desc2": "Seu multiplicador de altura é: {0}x",
"empgrenade": "Granada EMP",
"empgrenade_desc": "Quem se ferir com sua granada perde o radar e a mira por um tempo",
"empgrenade_enemy_info": "Uma granada EMP derrubou seu radar e sua mira.",
"enemyspawn": "Spawn Inimigo",
"enemyspawn_desc": "Clique em [css_useSkill] para se teletransportar para a base inimiga",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Nenhuma superfície para prender o gancho.",
"grapple_pulling_info": "PUXANDO",
"gravitydecoy": "Chamariz Gravitacional",
"gravitydecoy_desc": "Seu chamariz muda a gravidade de todos que estão por perto",
"grenadier": "Bomberman",
"grenadier_desc": "Você tem HEs infinitas",
@ -239,6 +253,9 @@
"healingchicken": "Galinha Curadora",
"healingchicken_desc": "Suas galinhas curam você enquanto você estiver por perto",
"heavyweight": "Peso Pesado",
"heavyweight_desc": "Habilidades que empurram ou te deixam lento não fazem efeito em você",
"hermit": "Sanguessuga",
"hermit_desc": "Matar restaura munição e uma parte da sua vida",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Você trocou dinheiro com o jogador '{0}'.",
"moneyswap_select_info": "Trocar dinheiro c/ jogador:",
"nemesis": "Nêmesis",
"nemesis_desc": "O jogador que você marcar recebe dano extra",
"nemesis_select_info": "Escolha um jogador:",
"nemesis_player_info": "Você marcou '{0}'.",
"nemesis_enemy_info": "Você foi marcado como nêmesis, agora recebe dano extra de todos.",
"nightmare": "Pesadelo",
"nightmare_desc": "Faça um inimigo escolhido ter uma visão aterrorizante",
"nightmare_disable_info": "O pesadelo desapareceu.",
@ -423,6 +446,10 @@
"prosthesis": "Prótese",
"prosthesis_desc": "Braços e pernas são à prova de balas",
"punisher": "Punidor",
"punisher_desc": "Você guarda parte do dano recebido e soma no seu próximo tiro",
"punisher_released_info": "Você causou {0} de dano extra e recuperou a mesma vida.",
"push": "Empurrão",
"push_desc": "Você tem uma chance aleatória de empurrar um inimigo ao acertá-lo",
"push_desc2": "Suas chances de empurrar o inimigo são: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Devolução ao Remetente",
"returntosender_desc": "O primeiro tiro em um inimigo o envia de volta a sua base",
"rewind": "Retroceder",
"rewind_desc": "Clique em [css_useSkill] para deixar uma marca aqui e voltar a ela logo depois",
"rewind_pending_info": "RETORNO: {0}",
"richboy": "Filhinho de Papai",
"richboy_desc": "Você recebe uma quantidade aleatória de dinheiro no início da rodada",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Fio de armadilha colocado.",
"tripwire_triggered_info": "'{0}' acionou seu fio.",
"truearmor": "Armadura Real",
"truearmor_desc": "Parte do dano que você recebe sai do colete em vez da vida",
"voodoo": "Vodu",
"voodoo_desc": "O inimigo que você marcar também recebe parte do dano que você leva",
"voodoo_select_info": "Escolha um jogador:",
"voodoo_player_info": "Você ligou '{0}' ao seu vodu.",
"wallhack": "Wallhack",
"wallhack_desc": "Você pode ver inimigos através das paredes",

View file

@ -60,6 +60,13 @@
"blastshot": "Взрывной выстрел",
"blastshot_desc": "Нажмите Attack2 на MP5, чтобы выстрелить гранатой HE",
"bounty": "Награда",
"bounty_desc": "Назначь награду за голову врага; тот, кто убьёт его, получит деньги",
"bounty_select_info": "Выберите игрока:",
"bounty_player_info": "Вы назначили награду {1}$ за '{0}'.",
"bounty_enemy_info": "За вашу голову назначили {0}$.",
"bounty_claimed_info": "Вы убили '{0}' и получили {1}$.",
"bunnyhop": "Заяц",
"bunnyhop_desc": "Вы получаете автоматический «БанниХоп»",
@ -144,6 +151,10 @@
"dwarf_desc": "Случайный размер персонажа в начале раунда",
"dwarf_desc2": "Ваш множитель размера: {0}x",
"empgrenade": "ЭМИ-граната",
"empgrenade_desc": "Тот, кто пострадал от вашей гранаты, на время теряет радар и прицел",
"empgrenade_enemy_info": "ЭМИ-граната вывела из строя ваш радар и прицел.",
"enemyspawn": "Спавн врагов",
"enemyspawn_desc": "Нажмите [css_useSkill], чтобы телепортироваться на спавн врагов",
@ -230,6 +241,9 @@
"grapple_no_anchor_info": "Нет поверхности для зацепа.",
"grapple_pulling_info": "ПРИТЯГИВАНИЕ",
"gravitydecoy": "Гравитационная обманка",
"gravitydecoy_desc": "Ваша обманка меняет гравитацию всех, кто рядом",
"grenadier": "Гренадёр",
"grenadier_desc": "У вас бесконечные осколочные гранаты",
@ -239,6 +253,9 @@
"healingsmoke": "Лечащий дым",
"healingsmoke_desc": "Ваши дымовые гранаты лечат",
"heavyweight": "Тяжеловес",
"heavyweight_desc": "Способности, которые толкают или замедляют, на вас не действуют",
"hermit": "Отшельник",
"hermit_desc": "Убийства восстанавливают боеприпасы и часть здоровья",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Вы обменялись деньгами с игроком '{0}'.",
"moneyswap_select_info": "Обменять деньги с игроком:",
"nemesis": "Немезида",
"nemesis_desc": "Отмеченный вами игрок получает больше урона",
"nemesis_select_info": "Выберите игрока:",
"nemesis_player_info": "Вы отметили '{0}'.",
"nemesis_enemy_info": "Вы отмечены как немезида и теперь получаете больше урона от всех.",
"nightmare": "Кошмар",
"nightmare_desc": "Заставьте выбранного противника увидеть кошмарное видение",
"nightmare_disable_info": "Кошмар рассеялся.",
@ -423,6 +446,10 @@
"prosthesis": "Протез",
"prosthesis_desc": "Руки и ноги защищены от пуль",
"punisher": "Каратель",
"punisher_desc": "Вы копите часть полученного урона и добавляете его к следующему попаданию",
"punisher_released_info": "Вы нанесли {0} дополнительного урона и восстановили столько же здоровья.",
"push": "Толкач",
"push_desc": "Есть шанс оттолкнуть врага назад при попадании",
"push_desc2": "Ваш шанс оттолкнуть врага: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Вернуть отправителю",
"returntosender_desc": "Первое попадание по врагу отправляет его обратно на спавн",
"rewind": "Перемотка",
"rewind_desc": "Нажмите [css_useSkill], чтобы оставить метку здесь и вскоре вернуться к ней",
"rewind_pending_info": "ВОЗВРАТ: {0}",
"richboy": "Богач",
"richboy_desc": "Вы получаете случайную сумму денег в начале раунда",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Растяжка установлена.",
"tripwire_triggered_info": "'{0}' задел вашу растяжку.",
"truearmor": "Настоящая броня",
"truearmor_desc": "Часть урона принимает броня, а не здоровье",
"voodoo": "Вуду",
"voodoo_desc": "Отмеченный враг тоже получает часть урона, который получаете вы",
"voodoo_select_info": "Выберите игрока:",
"voodoo_player_info": "Вы привязали '{0}' к своему вуду.",
"wallhack": "Воллхак",
"wallhack_desc": "Вы видите врагов сквозь стены",

View file

@ -60,6 +60,13 @@
"blastshot": "Patlama Atışı",
"blastshot_desc": "MP5 ile sağ tıklayarak el bombası fırlat",
"bounty": "Ödül Avı",
"bounty_desc": "Bir düşmana ödül koy, onu öldüren parayı alır",
"bounty_select_info": "Birini seçin:",
"bounty_player_info": "'{0}' için {1}$ ödül koydun.",
"bounty_enemy_info": "Başına {0}$ ödül kondu.",
"bounty_claimed_info": "'{0}' oyuncusunu öldürdün ve {1}$ kazandın.",
"bunnyhop": "Tavşan",
"bunnyhop_desc": "Otomatik BunnyHop yaparsın",
@ -137,6 +144,10 @@
"dwarf_desc": "Rastgele karakter boyutu",
"dwarf_desc2": "Boyutun: {0}x",
"empgrenade": "EMP Bombası",
"empgrenade_desc": "El bomban, hasar verdiğin düşmanın radarını ve nişangahını kapatır",
"empgrenade_enemy_info": "Radarın ve nişangahın kapandı.",
"enemyspawn": "Base Baskını",
"enemyspawn_desc": "[css_useSkill] tuşuna basarak düşman doğma noktasına ışınlan",
@ -223,6 +234,9 @@
"grapple_no_anchor_info": "Kanca atılacak yüzey yok",
"grapple_pulling_info": "ÇEKİLİYOR",
"gravitydecoy": "Yerçekimi Yanıltıcısı",
"gravitydecoy_desc": "Yanıltıcı bomban yakındaki herkesin yerçekimini değiştirir",
"grenadier": "Bombacı",
"grenadier_desc": "El bomban sınırsız",
@ -232,6 +246,9 @@
"healingsmoke": "Şifalı Sis",
"healingsmoke_desc": "Sis bomban iyileştirir",
"heavyweight": "Ağır Sıklet",
"heavyweight_desc": "Seni iten ve yavaşlatan yetenekler işlemez",
"hermit": "Münzevi",
"hermit_desc": "Birini öldürünce mermin ve canın yenilenir",
@ -360,6 +377,12 @@
"demoneye": "Şeytani Göz",
"demoneye_desc": "Baktığın tüm düşmana hasar verirsin",
"nemesis": "Ezeli Rakip",
"nemesis_desc": "İşaretlediğin düşman herkesten daha fazla hasar alır",
"nemesis_select_info": "Birini seçin:",
"nemesis_player_info": "'{0}' artık herkesten daha fazla hasar alacak.",
"nemesis_enemy_info": "İşaretlendin, artık herkesten daha fazla hasar alıyorsun.",
"nightmare": "Kabus",
"nightmare_desc": "Seçtiğin rakibe kabus gördür",
"nightmare_disable_info": "Kabus sona erdi.",
@ -423,6 +446,10 @@
"prosthesis": "Protez",
"prosthesis_desc": "Kolların ve bacakların kurşun geçirmez",
"punisher": "İntikam",
"punisher_desc": "Aldığın hasarı biriktirir, sonraki vuruşunda geri verirsin",
"punisher_released_info": "{0} ekstra hasar verdin ve o kadar can kazandın.",
"push": "İtici",
"push_desc": "Düşmana vurduğunda onu geri itme şansın olur",
"push_desc2": "Geri itme şansın: %{0}",
@ -460,6 +487,10 @@
"returntosender": "İade",
"returntosender_desc": "Düşmana ilk vuruşun onu kendi başlangıcına ışınlar",
"rewind": "Geri Sarma",
"rewind_desc": "[css_useSkill] tuşuna basarak iz bırak, birazdan oraya geri dön",
"rewind_pending_info": "DÖNÜŞ: {0}",
"richboy": "Zengin Çocuk",
"richboy_desc": "Rastgele miktarda paran olur",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Tel gerildi.",
"tripwire_triggered_info": "'{0}' teline takıldı.",
"truearmor": "Gerçek Zırh",
"truearmor_desc": "Aldığın hasarın bir kısmını canın yerine zırhın karşılar",
"voodoo": "Vudu",
"voodoo_desc": "Aldığın hasarın bir kısmını işaretlediğin düşman da alır",
"voodoo_select_info": "Birini seçin:",
"voodoo_player_info": "'{0}' oyuncusunu kendine bağladın.",
"wallhack": "Wallhack",
"wallhack_desc": "Düşmanları duvar arkasından görürsün",

View file

@ -54,6 +54,13 @@
"blademaster": "我的刀能档子弹!",
"blademaster_desc": "持刀时,你有很高几率格挡子弹",
"bounty": "悬赏",
"bounty_desc": "对一名敌人悬赏,击杀他的人可以获得赏金",
"bounty_select_info": "选择一名玩家:",
"bounty_player_info": "你对 '{0}' 悬赏了 {1}$。",
"bounty_enemy_info": "有人对你悬赏了 {0}$。",
"bounty_claimed_info": "你击杀了 '{0}',获得 {1}$。",
"bunnyhop": "连跳大佬",
"bunnyhop_desc": "你获得自动连跳的能力",
@ -131,6 +138,10 @@
"dwarf_desc": "回合开始时获得随机角色大小",
"dwarf_desc2": "你的体型倍率是:{0}x",
"empgrenade": "电磁脉冲手雷",
"empgrenade_desc": "被你的手雷伤到的人会暂时失去雷达和准星",
"empgrenade_enemy_info": "电磁脉冲手雷让你的雷达和准星失效了。",
"enemyspawn": "传送敌人出生点",
"enemyspawn_desc": "点击 [css_useSkill] 传送到敌人出生点",
@ -211,12 +222,18 @@
"grapple_no_anchor_info": "没有可以钩住的表面。",
"grapple_pulling_info": "拉动中",
"gravitydecoy": "重力诱饵",
"gravitydecoy_desc": "你的诱饵会改变附近所有人的重力",
"grenadier": "投弹手",
"grenadier_desc": "你拥有无限高爆手雷",
"healingsmoke": "治疗烟雾",
"healingsmoke_desc": "你的烟雾弹会治疗",
"heavyweight": "重量级",
"heavyweight_desc": "推开你或让你减速的技能对你无效",
"hermit": "隐士",
"hermit_desc": "击杀敌人会恢复弹药和部分生命值",
@ -330,6 +347,12 @@
"deathbomb_desc": "你死亡时会爆炸,杀死附近的玩家",
"deathbomb_explosion": "砰!",
"nemesis": "宿敌",
"nemesis_desc": "被你标记的玩家会受到额外伤害",
"nemesis_select_info": "选择一名玩家:",
"nemesis_player_info": "你标记了 '{0}'。",
"nemesis_enemy_info": "你被标记为宿敌,现在会受到所有人的额外伤害。",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
@ -393,6 +416,10 @@
"prosthesis": "假肢",
"prosthesis_desc": "手臂和腿部防弹",
"punisher": "惩罚者",
"punisher_desc": "你会积攒受到伤害的一部分,并加到下一次命中上",
"punisher_released_info": "你造成了 {0} 点额外伤害,并回复了同样的生命值。",
"push": "推力者",
"push_desc": "你有随机几率在击中敌人时将其推回",
"push_desc2": "你击退敌人的几率为:{0}%",
@ -430,6 +457,10 @@
"returntosender": "发送他们房子!!!",
"returntosender_desc": "首次击中敌人会将其送回出生点",
"rewind": "回溯",
"rewind_desc": "按 [css_useSkill] 在原地留下标记,稍后回到那里",
"rewind_pending_info": "回溯:{0}",
"richboy": "富少",
"richboy_desc": "回合开始时你获得随机数量的钱",
@ -507,6 +538,14 @@
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"truearmor": "真实护甲",
"truearmor_desc": "你受到的一部分伤害由护甲承担,而不是生命值",
"voodoo": "巫毒",
"voodoo_desc": "被你标记的敌人也会受到你所受伤害的一部分",
"voodoo_select_info": "选择一名玩家:",
"voodoo_player_info": "你把 '{0}' 绑到了你的巫毒上。",
"wallhack": "透视",
"wallhack_desc": "你可以通过墙壁看到敌人",