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 ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)"; 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 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) public override void Load(bool hotReload)
{ {
@ -371,6 +371,18 @@ namespace src
public int HideHUD { get; set; } public int HideHUD { get; set; }
public bool SkillUsed = false; public bool SkillUsed = false;
public bool? HudOnDeathBlocked { get; set; } 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) public class jSkill_SkillInfo(Skills skill, string color, bool display)

View file

@ -60,6 +60,13 @@
"blastshot": "Explosivschuss", "blastshot": "Explosivschuss",
"blastshot_desc": "Drücke Attack2 mit der MP5, um eine HE-Granate abzufeuern", "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": "Hase",
"bunnyhop_desc": "Du erhältst automatisch bunnyhop", "bunnyhop_desc": "Du erhältst automatisch bunnyhop",
@ -144,6 +151,10 @@
"dwarf_desc": "Zufällige Charaktergröße zu Beginn der Runde", "dwarf_desc": "Zufällige Charaktergröße zu Beginn der Runde",
"dwarf_desc2": "Dein Größenmultiplikator beträgt: {0}x", "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": "Feindes-Spawn",
"enemyspawn_desc": "Klicke auf [css_useSkill], um zum Feindes-Spawn zu teleportieren", "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_no_anchor_info": "Keine Oberfläche zum Einhaken.",
"grapple_pulling_info": "ZUG", "grapple_pulling_info": "ZUG",
"gravitydecoy": "Schwerkraft-Ablenker",
"gravitydecoy_desc": "Dein Ablenkungsköder verändert die Schwerkraft aller in der Nähe",
"grenadier": "Grenadier", "grenadier": "Grenadier",
"grenadier_desc": "Du hast unendlich viele Granaten", "grenadier_desc": "Du hast unendlich viele Granaten",
@ -239,6 +253,9 @@
"healingchicken": "Heilendes Huhn", "healingchicken": "Heilendes Huhn",
"healingchicken_desc": "Deine Hühner heilen dich, solange du in ihrer Nähe bist", "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": "Einsiedler",
"hermit_desc": "Durch das Töten von Gegnern werden Munition und ein Teil der Gesundheit wiederhergestellt", "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_player_info": "Der Spieler ‚{0}‘ ist bankrott.",
"bankrupt_select_info": "Wähle einen Spieler aus, der bankrott gehen soll:", "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": "Albtraum",
"nightmare_desc": "Lass einen ausgewählten Gegner eine schreckliche Vision erleben", "nightmare_desc": "Lass einen ausgewählten Gegner eine schreckliche Vision erleben",
"nightmare_disable_info": "Der Albtraum ist vorbei.", "nightmare_disable_info": "Der Albtraum ist vorbei.",
@ -423,6 +446,10 @@
"prosthesis": "Prothese", "prosthesis": "Prothese",
"prosthesis_desc": "Arme und Beine sind kugelsicher", "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": "Pusher",
"push_desc": "Du hast eine zufällige Chance, einen Gegner zurückzustoßen, wenn du ihn triffst", "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} %", "push_desc2": "Deine Chancen, den Gegner zurückzustoßen, betragen: {0} %",
@ -460,6 +487,10 @@
"returntosender": "Zurück zum Absender", "returntosender": "Zurück zum Absender",
"returntosender_desc": "Der erste Treffer eines Gegners schickt ihn zurück zu seinem Spawn", "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": "Rich Boy",
"richboy_desc": "Du erhältst zu Beginn der Runde einen zufälligen Geldbetrag", "richboy_desc": "Du erhältst zu Beginn der Runde einen zufälligen Geldbetrag",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Stolperdraht platziert.", "tripwire_placed_info": "Stolperdraht platziert.",
"tripwire_triggered_info": "'{0}' hat deinen Stolperdraht ausgelöst.", "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": "Wallhack",
"wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen", "wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen",

View file

@ -60,6 +60,13 @@
"blastshot": "Blast Shot", "blastshot": "Blast Shot",
"blastshot_desc": "Press Attack2 with the MP5 to fire an HE grenade", "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": "Bunny",
"bunnyhop_desc": "You get auto \"BunnyHop\"", "bunnyhop_desc": "You get auto \"BunnyHop\"",
@ -137,6 +144,10 @@
"dwarf_desc": "Random character size at the start of the round", "dwarf_desc": "Random character size at the start of the round",
"dwarf_desc2": "Your size multiplier is: {0}x", "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": "Enemy Spawn",
"enemyspawn_desc": "Click [css_useSkill] to teleport to the 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_no_anchor_info": "No surface to hook onto.",
"grapple_pulling_info": "PULLING", "grapple_pulling_info": "PULLING",
"gravitydecoy": "Gravity Decoy",
"gravitydecoy_desc": "Your decoy changes the gravity of everyone nearby",
"grenadier": "Grenadier", "grenadier": "Grenadier",
"grenadier_desc": "You have infinite hegrenade", "grenadier_desc": "You have infinite hegrenade",
@ -232,6 +246,9 @@
"healingsmoke": "Healing Smoke", "healingsmoke": "Healing Smoke",
"healingsmoke_desc": "Your smoke grenades heal", "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": "Hermit",
"hermit_desc": "Killing restores ammo and a portion of health", "hermit_desc": "Killing restores ammo and a portion of health",
@ -360,6 +377,12 @@
"demoneye": "Demon Eye", "demoneye": "Demon Eye",
"demoneye_desc": "You deal damage to every enemy you are looking at", "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": "Nightmare",
"nightmare_desc": "Force a chosen enemy to experience a terrifying vision", "nightmare_desc": "Force a chosen enemy to experience a terrifying vision",
"nightmare_disable_info": "The nightmare has faded.", "nightmare_disable_info": "The nightmare has faded.",
@ -423,6 +446,10 @@
"prosthesis": "Prosthesis", "prosthesis": "Prosthesis",
"prosthesis_desc": "Arms and legs are bulletproof", "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": "Pusher",
"push_desc": "You have a random chance to push an enemy back when hitting them", "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}%", "push_desc2": "Your chances of pushing back the enemy are: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Return to Sender", "returntosender": "Return to Sender",
"returntosender_desc": "The first hit on an enemy sends them back to their spawn", "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": "Rich Boy",
"richboy_desc": "You receive a random amount of money at the start of the round", "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_placed_info": "Tripwire placed.",
"tripwire_triggered_info": "'{0}' triggered your tripwire.", "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": "Wallhack",
"wallhack_desc": "You can see enemies through walls", "wallhack_desc": "You can see enemies through walls",

View file

@ -60,6 +60,13 @@
"blastshot": "Tir explosif", "blastshot": "Tir explosif",
"blastshot_desc": "Appuyez sur Attack2 avec le MP5 pour tirer une grenade HE", "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": "Lapin",
"bunnyhop_desc": "Vous obtenez l’auto « BunnyHop »", "bunnyhop_desc": "Vous obtenez l’auto « BunnyHop »",
@ -144,6 +151,10 @@
"dwarf_desc": "Taille du personnage aléatoire au début du round", "dwarf_desc": "Taille du personnage aléatoire au début du round",
"dwarf_desc2": "Votre multiplicateur de taille est : {0}x", "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": "Spawn Ennemi",
"enemyspawn_desc": "Appuyez sur [css_useSkill] pour vous téléporter au 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_no_anchor_info": "Aucune surface pour accrocher le grappin.",
"grapple_pulling_info": "TRACTION", "grapple_pulling_info": "TRACTION",
"gravitydecoy": "Leurre gravitationnel",
"gravitydecoy_desc": "Ton leurre modifie la gravité de tous ceux qui sont à proximité",
"grenadier": "Grenadier", "grenadier": "Grenadier",
"grenadier_desc": "Tu as des grenades HE infinies", "grenadier_desc": "Tu as des grenades HE infinies",
@ -239,6 +253,9 @@
"healingchicken": "Poulet guérisseur", "healingchicken": "Poulet guérisseur",
"healingchicken_desc": "Vos poulets vous soignent lorsque vous êtes à proximité", "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": "Ermite",
"hermit_desc": "Tuer restaure des munitions et une partie de vos PV", "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_player_info": "Le joueur « {0} » est en faillite.",
"bankrupt_select_info": "Mettre un joueur 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": "Cauchemar",
"nightmare_desc": "Faites subir une vision terrifiante à un ennemi choisi", "nightmare_desc": "Faites subir une vision terrifiante à un ennemi choisi",
"nightmare_disable_info": "Le cauchemar s'est dissipé.", "nightmare_disable_info": "Le cauchemar s'est dissipé.",
@ -423,6 +446,10 @@
"prosthesis": "Prothèses", "prosthesis": "Prothèses",
"prosthesis_desc": "Les bras et les jambes sont pare-balles", "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": "Pousseur",
"push_desc": "Vous avez une chance aléatoire de repousser un ennemi en le touchant", "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}%", "push_desc2": "Votre chance de repousser l’ennemi est : {0}%",
@ -460,6 +487,10 @@
"returntosender": "Retour à l’Expéditeur", "returntosender": "Retour à l’Expéditeur",
"returntosender_desc": "Le premier coup porté à un ennemi le renvoie à son spawn", "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": "Gosse de Riche",
"richboy_desc": "Vous recevez une somme d’argent aléatoire au début du round", "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_placed_info": "Fil-piège placé.",
"tripwire_triggered_info": "'{0}' a déclenché votre fil-piège.", "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": "Wallhack",
"wallhack_desc": "Vous pouvez voir les ennemis à travers les murs", "wallhack_desc": "Vous pouvez voir les ennemis à travers les murs",

View file

@ -60,6 +60,13 @@
"blastshot": "Mocne Uderzenie", "blastshot": "Mocne Uderzenie",
"blastshot_desc": "Naciśnij Attack2 na MP5, aby wystrzelić granat HE", "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": "Królik",
"bunnyhop_desc": "Otrzymujesz auto \"BunnyHopa\"", "bunnyhop_desc": "Otrzymujesz auto \"BunnyHopa\"",
@ -144,6 +151,10 @@
"dwarf_desc": "Losowa wielkość postaci na początku rundy", "dwarf_desc": "Losowa wielkość postaci na początku rundy",
"dwarf_desc2": "Twój mnożnik wielkości to {0}x", "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": "Resp Wroga",
"enemyspawn_desc": "Kliknij [css_useSkill], aby teleportować się na 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_no_anchor_info": "Brak powierzchni do zaczepienia haka.",
"grapple_pulling_info": "PRZYCIĄGANIE", "grapple_pulling_info": "PRZYCIĄGANIE",
"gravitydecoy": "Grawitacyjna wabik",
"gravitydecoy_desc": "Twój wabik zmienia grawitację wszystkich w pobliżu",
"grenadier": "Granatnik", "grenadier": "Granatnik",
"grenadier_desc": "Masz nieskończone granaty odłamkowe", "grenadier_desc": "Masz nieskończone granaty odłamkowe",
@ -239,6 +253,9 @@
"healingchicken": "Lecząca Kura", "healingchicken": "Lecząca Kura",
"healingchicken_desc": "Twoje kury leczą Cię, gdy jesteś w pobliżu", "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": "Pustelnik",
"hermit_desc": "Zabijanie przywraca amunicję i część zdrowia", "hermit_desc": "Zabijanie przywraca amunicję i część zdrowia",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Wymieniłeś swoje pieniądze z graczem '{0}'.", "moneyswap_player_info": "Wymieniłeś swoje pieniądze z graczem '{0}'.",
"moneyswap_select_info": "Zamień się kasą z graczem:", "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": "Koszmar",
"nightmare_desc": "Wywołaj u wybranego przeciwnika przerażającą wizję", "nightmare_desc": "Wywołaj u wybranego przeciwnika przerażającą wizję",
"nightmare_disable_info": "Koszmar minął.", "nightmare_disable_info": "Koszmar minął.",
@ -423,6 +446,10 @@
"prosthesis": "Proteza", "prosthesis": "Proteza",
"prosthesis_desc": "Ramiona i nogi są kuloodporne", "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": "Odpychacz",
"push_desc": "Masz losową szansę na odepchnięcie wroga po trafieniu go", "push_desc": "Masz losową szansę na odepchnięcie wroga po trafieniu go",
"push_desc2": "Twoje szanse na odepchnięcie wroga to: {0}%", "push_desc2": "Twoje szanse na odepchnięcie wroga to: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Zwrot do Nadawcy", "returntosender": "Zwrot do Nadawcy",
"returntosender_desc": "Pierwsze trafienie wroga powoduje, że wraca on na swój resp", "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": "Bogacz",
"richboy_desc": "Na początku rundy otrzymujesz losową kwotę gotówki", "richboy_desc": "Na początku rundy otrzymujesz losową kwotę gotówki",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Linka została rozstawiona.", "tripwire_placed_info": "Linka została rozstawiona.",
"tripwire_triggered_info": "'{0}' uruchomił Twoją linkę.", "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": "Wallhack",
"wallhack_desc": "Widzisz wrogów przez ściany", "wallhack_desc": "Widzisz wrogów przez ściany",

View file

@ -60,6 +60,13 @@
"blastshot": "Tiro Explosivo", "blastshot": "Tiro Explosivo",
"blastshot_desc": "Pressione Attack2 com a MP5 para disparar uma granada HE", "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": "Coelho",
"bunnyhop_desc": "Você ganha \"BunnyHop\" automático", "bunnyhop_desc": "Você ganha \"BunnyHop\" automático",
@ -144,6 +151,10 @@
"dwarf_desc": "Sua altura agora é aleatória", "dwarf_desc": "Sua altura agora é aleatória",
"dwarf_desc2": "Seu multiplicador de altura é: {0}x", "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": "Spawn Inimigo",
"enemyspawn_desc": "Clique em [css_useSkill] para se teletransportar para a base inimiga", "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_no_anchor_info": "Nenhuma superfície para prender o gancho.",
"grapple_pulling_info": "PUXANDO", "grapple_pulling_info": "PUXANDO",
"gravitydecoy": "Chamariz Gravitacional",
"gravitydecoy_desc": "Seu chamariz muda a gravidade de todos que estão por perto",
"grenadier": "Bomberman", "grenadier": "Bomberman",
"grenadier_desc": "Você tem HEs infinitas", "grenadier_desc": "Você tem HEs infinitas",
@ -239,6 +253,9 @@
"healingchicken": "Galinha Curadora", "healingchicken": "Galinha Curadora",
"healingchicken_desc": "Suas galinhas curam você enquanto você estiver por perto", "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": "Sanguessuga",
"hermit_desc": "Matar restaura munição e uma parte da sua vida", "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_player_info": "Você trocou dinheiro com o jogador '{0}'.",
"moneyswap_select_info": "Trocar dinheiro c/ jogador:", "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": "Pesadelo",
"nightmare_desc": "Faça um inimigo escolhido ter uma visão aterrorizante", "nightmare_desc": "Faça um inimigo escolhido ter uma visão aterrorizante",
"nightmare_disable_info": "O pesadelo desapareceu.", "nightmare_disable_info": "O pesadelo desapareceu.",
@ -423,6 +446,10 @@
"prosthesis": "Prótese", "prosthesis": "Prótese",
"prosthesis_desc": "Braços e pernas são à prova de balas", "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": "Empurrão",
"push_desc": "Você tem uma chance aleatória de empurrar um inimigo ao acertá-lo", "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}%", "push_desc2": "Suas chances de empurrar o inimigo são: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Devolução ao Remetente", "returntosender": "Devolução ao Remetente",
"returntosender_desc": "O primeiro tiro em um inimigo o envia de volta a sua base", "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": "Filhinho de Papai",
"richboy_desc": "Você recebe uma quantidade aleatória de dinheiro no início da rodada", "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_placed_info": "Fio de armadilha colocado.",
"tripwire_triggered_info": "'{0}' acionou seu fio.", "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": "Wallhack",
"wallhack_desc": "Você pode ver inimigos através das paredes", "wallhack_desc": "Você pode ver inimigos através das paredes",

View file

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

View file

@ -60,6 +60,13 @@
"blastshot": "Patlama Atışı", "blastshot": "Patlama Atışı",
"blastshot_desc": "MP5 ile sağ tıklayarak el bombası fırlat", "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": "Tavşan",
"bunnyhop_desc": "Otomatik BunnyHop yaparsın", "bunnyhop_desc": "Otomatik BunnyHop yaparsın",
@ -137,6 +144,10 @@
"dwarf_desc": "Rastgele karakter boyutu", "dwarf_desc": "Rastgele karakter boyutu",
"dwarf_desc2": "Boyutun: {0}x", "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": "Base Baskını",
"enemyspawn_desc": "[css_useSkill] tuşuna basarak düşman doğma noktasına ışınlan", "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_no_anchor_info": "Kanca atılacak yüzey yok",
"grapple_pulling_info": "ÇEKİLİYOR", "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": "Bombacı",
"grenadier_desc": "El bomban sınırsız", "grenadier_desc": "El bomban sınırsız",
@ -232,6 +246,9 @@
"healingsmoke": "Şifalı Sis", "healingsmoke": "Şifalı Sis",
"healingsmoke_desc": "Sis bomban iyileştirir", "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": "Münzevi",
"hermit_desc": "Birini öldürünce mermin ve canın yenilenir", "hermit_desc": "Birini öldürünce mermin ve canın yenilenir",
@ -360,6 +377,12 @@
"demoneye": "Şeytani Göz", "demoneye": "Şeytani Göz",
"demoneye_desc": "Baktığın tüm düşmana hasar verirsin", "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": "Kabus",
"nightmare_desc": "Seçtiğin rakibe kabus gördür", "nightmare_desc": "Seçtiğin rakibe kabus gördür",
"nightmare_disable_info": "Kabus sona erdi.", "nightmare_disable_info": "Kabus sona erdi.",
@ -423,6 +446,10 @@
"prosthesis": "Protez", "prosthesis": "Protez",
"prosthesis_desc": "Kolların ve bacakların kurşun geçirmez", "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": "İtici",
"push_desc": "Düşmana vurduğunda onu geri itme şansın olur", "push_desc": "Düşmana vurduğunda onu geri itme şansın olur",
"push_desc2": "Geri itme şansın: %{0}", "push_desc2": "Geri itme şansın: %{0}",
@ -460,6 +487,10 @@
"returntosender": "İade", "returntosender": "İade",
"returntosender_desc": "Düşmana ilk vuruşun onu kendi başlangıcına ışınlar", "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": "Zengin Çocuk",
"richboy_desc": "Rastgele miktarda paran olur", "richboy_desc": "Rastgele miktarda paran olur",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Tel gerildi.", "tripwire_placed_info": "Tel gerildi.",
"tripwire_triggered_info": "'{0}' teline takıldı.", "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": "Wallhack",
"wallhack_desc": "Düşmanları duvar arkasından görürsün", "wallhack_desc": "Düşmanları duvar arkasından görürsün",

View file

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

View file

@ -76,6 +76,7 @@ public enum Skills
BladeMaster, BladeMaster,
BunnyHop, BunnyHop,
BlastShot, BlastShot,
Bounty,
C4Camouflage, C4Camouflage,
CarefulBullets, CarefulBullets,
Catapult, Catapult,
@ -95,6 +96,7 @@ public enum Skills
Dracula, Dracula,
Duplicator, Duplicator,
Dwarf, Dwarf,
EmpGrenade,
EnemySpawn, EnemySpawn,
ExpensiveAmmo, ExpensiveAmmo,
ExplodingBarrel, ExplodingBarrel,
@ -116,9 +118,11 @@ public enum Skills
Glue, Glue,
GodMode, GodMode,
Grapple, Grapple,
GravityDecoy,
Grenadier, Grenadier,
HealingChicken, HealingChicken,
HealingSmoke, HealingSmoke,
Heavyweight,
Hermit, Hermit,
HolyHandGrenade, HolyHandGrenade,
HomingNades, HomingNades,
@ -148,6 +152,7 @@ public enum Skills
Medic, Medic,
Miner, Miner,
MoneySwap, MoneySwap,
Nemesis,
Nightmare, Nightmare,
Ninja, Ninja,
NoNades, NoNades,
@ -163,6 +168,7 @@ public enum Skills
Poison, Poison,
PrimaryBan, PrimaryBan,
Prosthesis, Prosthesis,
Punisher,
Push, Push,
Pyro, Pyro,
QuickShot, QuickShot,
@ -175,6 +181,7 @@ public enum Skills
Replicator, Replicator,
Retreat, Retreat,
ReturnToSender, ReturnToSender,
Rewind,
RichBoy, RichBoy,
Ricochet, Ricochet,
RobinHood, RobinHood,
@ -199,6 +206,8 @@ public enum Skills
ThrowingKnife, ThrowingKnife,
ToxicSmoke, ToxicSmoke,
Tripwire, Tripwire,
TrueArmor,
Voodoo,
Wallhack, Wallhack,
Watchmaker, Watchmaker,
WeaponsSwap, 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)]; 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 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, jSkill_SkillInfo> staticSkills = [];
public static readonly ConcurrentDictionary<uint, uint> playersCustomFOV = []; public static readonly ConcurrentDictionary<uint, uint> playersCustomFOV = [];
private static readonly object setLock = new(); private static readonly object setLock = new();
@ -564,6 +564,7 @@ namespace src.player
PlayerManager.UnregisterPlayer(player.Index); PlayerManager.UnregisterPlayer(player.Index);
EntityManager.DestroyPlayerEntities(player.Index); EntityManager.DestroyPlayerEntities(player.Index);
SkillUtils.ClearHudSuppression(player.Index);
return HookResult.Continue; return HookResult.Continue;
} }
@ -602,6 +603,8 @@ namespace src.player
var pawn = player.PlayerPawn?.Value; var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) return; if (pawn == null || !pawn.IsValid) return;
SkillUtils.ClearHudSuppression(player.Index);
pawn.HideHUD = (uint)(pawn.HideHUD & ~(1 << 8)); pawn.HideHUD = (uint)(pawn.HideHUD & ~(1 << 8));
Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_iHideHUD"); Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_iHideHUD");
@ -768,6 +771,21 @@ namespace src.player
} }
var config = Config.LoadedConfig.HtmlHudCustomisation; 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 emptySymbol = $"<font class='fontSize-{(string.IsNullOrEmpty(headerLine) || string.IsNullOrEmpty(config.HeaderLineSize) ? "l" : "ml")}'> </font>";
var emptySymbol2 = $"<font class='fontSize-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}"; : $"<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; 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); player.PrintToCenterHtml(hudContent);
} }
} }

View file

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

View file

@ -8,11 +8,9 @@ namespace src.player.skills
public class Assassin : ISkill public class Assassin : ISkill
{ {
private const Skills skillName = Skills.Assassin; private const Skills skillName = Skills.Assassin;
private static readonly string[] nadeWeapons = private const string infernoWeapon = "weapon_inferno";
[
"weapon_inferno", "weapon_molotov", "weapon_incgrenade", "weapon_flashbang", private static bool IsNade(string? weapon) => weapon == infernoWeapon || WeaponPool.IsGrenade(weapon);
"weapon_smokegrenade", "weapon_decoy", "weapon_hegrenade"
];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -46,7 +44,7 @@ namespace src.player.skills
var weapon = param2.Ability?.Value; var weapon = param2.Ability?.Value;
if (weapon == null || !weapon.IsValid) return; if (weapon == null || !weapon.IsValid) return;
if (nadeWeapons.Contains(weapon.DesignerName)) return; if (IsNade(weapon.DesignerName)) return;
if (IsBehind(attacker, victim)) if (IsBehind(attacker, victim))
param2.Damage *= SkillsInfo.GetValue<float>(skillName, "damageMultiplier"); 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 bomb = PlayerManager.GetTickBomb();
var hidden = SkillUtils.ResolveHiddenPawns(invisiblePlayers.Keys, null);
if (hidden.Count == 0) return;
foreach (var (info, player) in infoList) foreach (var (info, player) in infoList)
{ {
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue; if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero; 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 (target.Index == player.Index) continue;
if (playerController == null || !playerController.IsValid || playerController.Index == player.Index) if (player.Team == target.Team) continue;
continue;
if (player.Team == playerController.Team)
continue;
var playerPawn = playerController.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera. // Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle) if (targetHandle != nint.Zero && target.Pawn.Handle == targetHandle) continue;
continue;
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index); if (info.TransmitEntities.Contains(target.Pawn.Index))
if (entity == null || !entity.IsValid) continue; info.TransmitEntities.Remove(target.Pawn.Index);
if (info.TransmitEntities.Contains(entity.Index)) SkillUtils.HideCarriedEntities(info, target.Pawn);
info.TransmitEntities.Remove(entity.Index);
SkillUtils.HideCarriedEntities(info, playerPawn);
if (bomb == null) continue; if (bomb == null) continue;

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class CarefulBullets : ISkill public class CarefulBullets : ISkill
{ {
private const Skills skillName = Skills.CarefulBullets; 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, byte> targetPlayers = [];
private static readonly ConcurrentDictionary<uint, bool> lastShot = []; private static readonly ConcurrentDictionary<uint, bool> lastShot = [];
private static readonly ConcurrentDictionary<uint, int> hitPlayer = []; private static readonly ConcurrentDictionary<uint, int> hitPlayer = [];
@ -153,7 +154,7 @@ namespace src.player.skills
{ {
if (lastShot.TryRemove(playerIndex, out bool didHit) && !didHit) 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); SkillUtils.TakeHealth(playerPawn, SkillsInfo.GetValue<int>(skillName, "damageAfterMiss"), GetSkillOwner(playerIndex), KillfeedIcons.Fist);
} }
@ -257,8 +258,9 @@ namespace src.player.skills
targetToPlayer.TryRemove(index, out _); 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; 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; if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (Heavyweight.Resists(victim)) return;
if (attackerInfo?.Skill == skillName && victim!.PawnIsAlive) if (attackerInfo?.Skill == skillName && victim!.PawnIsAlive)
if (Instance.Random.NextDouble() <= attackerInfo.SkillChance) if (Instance.Random.NextDouble() <= attackerInfo.SkillChance)
{ {

View file

@ -10,16 +10,7 @@ namespace src.player.skills
public class Chicken : ISkill public class Chicken : ISkill
{ {
private const Skills skillName = Skills.Chicken; private const Skills skillName = Skills.Chicken;
private static readonly HashSet<string> disabledWeapons = private static HashSet<string> disabledWeapons => WeaponPool.Rifles;
[
"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 const string chickenModelPath = "models/chicken/chicken.vmdl"; private const string chickenModelPath = "models/chicken/chicken.vmdl";
private static readonly ConcurrentDictionary<uint, uint> chickens = []; 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); var cameraProp = Utilities.GetEntityFromIndex<CDynamicProp>((int)playerSkill.CameraProp);
if (cameraProp != null && cameraProp.IsValid) if (cameraProp != null && cameraProp.IsValid)
cameraProp.EmitSound("SolidMetal.BulletImpact"); cameraProp.EmitSound("SolidMetal.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
SkillUtils.SafeKillEntity<CDynamicProp>(playerSkill.CameraProp); SkillUtils.SafeKillEntity<CDynamicProp>(playerSkill.CameraProp);
playerSkill.CameraProp = null; playerSkill.CameraProp = null;
@ -414,8 +414,9 @@ namespace src.player.skills
public required QAngle LastCameraAngle { get; set; } 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; public float Cooldown { get; set; } = cooldown;
} }
} }

View file

@ -143,6 +143,8 @@ namespace src.player.skills
eventPlayerPawn.AbsVelocity.X = newVelocity.X; eventPlayerPawn.AbsVelocity.X = newVelocity.X;
eventPlayerPawn.AbsVelocity.Y = newVelocity.Y; eventPlayerPawn.AbsVelocity.Y = newVelocity.Y;
eventPlayerPawn.AbsVelocity.Z = newVelocity.Z; 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 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 JumpVelocity { get; set; } = jumpVelocity;
public float PushVelocity { get; set; } = pushVelocity; public float PushVelocity { get; set; } = pushVelocity;
public bool AnyDirection { get; set; } = anyDirection; public bool AnyDirection { get; set; } = anyDirection;

View file

@ -13,6 +13,7 @@ namespace src.player.skills
public class DeathBomb : ISkill public class DeathBomb : ISkill
{ {
private const Skills skillName = Skills.DeathBomb; 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 QAngle angle = new(10, -5, 9);
private static readonly ConcurrentDictionary<int, (byte Team, uint Owner)> nades = []; 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")}", 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"); border: !PlayerManager.GetTickPlayers().Any(p => p.Team == player.Team && p != player) ? "tb" : "t");
var fileNames = new[] { "radiobotfallback01", "radiobotfallback02", "radiobotfallback04" }; player.EmitSound(voiceLines[Random.Shared.Next(voiceLines.Length)], volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
var randomFile = fileNames[new Random().Next(fileNames.Length)];
player.ExecuteClientCommand($"play vo/agents/balkan/{randomFile}.vsnd");
nades.AddOrUpdate(Server.TickCount, (player.TeamNum, player.Index), (_, _) => (player.TeamNum, player.Index)); 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; 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 float ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage; public int ExplosionDamage { get; set; } = explosionDamage;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates; public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;

View file

@ -66,13 +66,14 @@ namespace src.player.skills
if (playerSeesEnemy) if (playerSeesEnemy)
{ {
SkillUtils.TakeHealth(enemyPawn, damage, player, KillfeedIcons.Fist); 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 float SecondCooldown { get; set; } = secondCooldown;
public int Damage { get; set; } = damage; 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() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
DecoyRing.PreloadAssets();
} }
public static void NewRound() public static void NewRound()
{ {
KillAllDecoys(); KillAllDecoys();
rainMolotovs.Clear(); rainMolotovs.Clear();
DecoyRing.ClearAll(skillName);
} }
private static void KillAllDecoys() private static void KillAllDecoys()
@ -276,7 +278,14 @@ namespace src.player.skills
if (decoy != null && decoy.IsValid) if (decoy != null && decoy.IsValid)
decoy.AddEntityIOEvent("Kill", decoy, delay: 0.1f); decoy.AddEntityIOEvent("Kill", decoy, delay: 0.1f);
decoys.TryRemove(key, out _); 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); 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>(); var box = param.As<CDynamicProp>();
if (box == null || !box.IsValid) return; 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)) if (barricades.TryGetValue(box.Index, out int health))
{ {
@ -178,8 +178,9 @@ namespace src.player.skills
public DateTime Cooldown { get; set; } 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 float Cooldown { get; set; } = cooldown;
public int BarricadeHealth { get; set; } = barricadeHealth; public int BarricadeHealth { get; set; } = barricadeHealth;
public string PropModel { get; set; } = propModel; public string PropModel { get; set; } = propModel;

View file

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

View file

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

View file

@ -41,6 +41,18 @@ namespace src.player.skills
{ {
if (smokes.IsEmpty || playersWithSkill.IsEmpty) return; 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) foreach (var (info, player) in infoList)
{ {
if (player == null || !player.IsValid) continue; 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; var observerTarget = player.Pawn?.Value?.ObserverServices?.ObserverTarget?.Value?.Handle ?? nint.Zero;
if (observerTarget == nint.Zero) continue; if (observerTarget == nint.Zero) continue;
var observedPlayer = PlayerManager.GetTickPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == observerTarget); pawnOwners ??= BuildPawnOwners();
if (observedPlayer == null) continue;
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) foreach (var index in smokeIndexes)
{ if (info.TransmitEntities.Contains(index))
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)entityIndex); info.TransmitEntities.Remove(index);
if (entity == null || !entity.IsValid) continue;
if (info.TransmitEntities.Contains(entity.Index))
info.TransmitEntities.Remove(entity.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) public static void GrenadeThrown(EventGrenadeThrown @event)
{ {
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);

View file

@ -9,6 +9,7 @@ namespace src.player.skills
public class Glitch : ISkill public class Glitch : ISkill
{ {
private const Skills skillName = Skills.Glitch; private const Skills skillName = Skills.Glitch;
private const string ownerTag = "Glitch";
private static readonly ConcurrentDictionary<uint, byte> glitchedPlayers = []; private static readonly ConcurrentDictionary<uint, byte> glitchedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = []; private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly object setLock = new(); private static readonly object setLock = new();
@ -100,7 +101,7 @@ namespace src.player.skills
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy); var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) return; if (enemyEvent == null || !enemyEvent.IsValid) return;
enemyEvent.ReplicateConVar("sv_disable_radar", "1"); SkillUtils.SetRadarDisabled(enemyEvent, ownerTag, true);
playerInfo.SkillUsed = true; playerInfo.SkillUsed = true;
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("glitch_player_info", enemy.PlayerName)); 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; if (!glitchedPlayers.ContainsKey(bot.Index)) return;
player.ReplicateConVar("sv_disable_radar", "1"); SkillUtils.SetRadarDisabled(player, ownerTag, true);
} }
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)
@ -146,7 +147,7 @@ namespace src.player.skills
var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex)); var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex));
if (target != null && target.IsValid) if (target != null && target.IsValid)
{ {
target.ReplicateConVar("sv_disable_radar", "0"); SkillUtils.SetRadarDisabled(target, ownerTag, false);
if (target.PawnIsAlive && !SkillUtils.IsFreezeTime()) if (target.PawnIsAlive && !SkillUtils.IsFreezeTime())
target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("glitch_disable_info")); target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("glitch_disable_info"));
} }
@ -161,7 +162,7 @@ namespace src.player.skills
var player = @event.Userid; var player = @event.Userid;
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
player.ReplicateConVar("sv_disable_radar", "0"); SkillUtils.SetRadarDisabled(player, ownerTag, false);
glitchedPlayers.TryRemove(player.Index, out _); glitchedPlayers.TryRemove(player.Index, out _);
SkillUtils.CloseMenu(player); 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 static readonly Color counterTerroristRope = Color.FromArgb(255, 52, 92, 138);
private const double attemptCooldown = .3; 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() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
jRandomSkills.Instance.AddToManifest(hookModel);
} }
public static void NewRound() public static void NewRound()
@ -131,6 +134,8 @@ namespace src.player.skills
skillInfo.Anchor = anchor; skillInfo.Anchor = anchor;
skillInfo.EndTick = Server.TickCount + (int)(SkillsInfo.GetValue<float>(skillName, "maxPullSeconds") * 64); 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()) if (!EntityManager.OverBudget())
{ {
Color ropeColor = player.Team == CsTeam.Terrorist ? terroristRope : counterTerroristRope; Color ropeColor = player.Team == CsTeam.Terrorist ? terroristRope : counterTerroristRope;
@ -155,15 +160,102 @@ namespace src.player.skills
} }
} }
player.EmitSound("SolidMetal.BulletImpact");
return true; 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) private static void DestroyRope(PlayerSkillInfo skillInfo)
{ {
skillInfo.Anchor = null; skillInfo.Anchor = null;
skillInfo.EndTick = 0; skillInfo.EndTick = 0;
DestroyHook(skillInfo);
if (skillInfo.RopeIndex == null) return; if (skillInfo.RopeIndex == null) return;
uint ropeIndex = skillInfo.RopeIndex.Value; uint ropeIndex = skillInfo.RopeIndex.Value;
@ -176,26 +268,7 @@ namespace src.player.skills
{ {
if (!liveRopes.TryRemove(ropeIndex, out _)) return; if (!liveRopes.TryRemove(ropeIndex, out _)) return;
var rope = Utilities.GetEntityFromIndex<CBeam>((int)ropeIndex); EntityManager.DestroyBeam(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();
});
} }
private static void SweepOrphanRopes() private static void SweepOrphanRopes()
@ -299,11 +372,13 @@ namespace src.player.skills
public DateTime LastAttempt { get; set; } public DateTime LastAttempt { get; set; }
public Vector? Anchor { get; set; } public Vector? Anchor { get; set; }
public uint? RopeIndex { get; set; } public uint? RopeIndex { get; set; }
public uint? HookIndex { get; set; }
public int EndTick { 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 Cooldown { get; set; } = cooldown;
public float MaxDistance { get; set; } = maxDistance; public float MaxDistance { get; set; } = maxDistance;
public float MinDistance { get; set; } = minDistance; public float MinDistance { get; set; } = minDistance;
@ -311,6 +386,8 @@ namespace src.player.skills
public float PullSpeed { get; set; } = pullSpeed; public float PullSpeed { get; set; } = pullSpeed;
public float MaxPullSeconds { get; set; } = maxPullSeconds; public float MaxPullSeconds { get; set; } = maxPullSeconds;
public float RopeWidth { get; set; } = ropeWidth; 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.GetDistance(smokePos, pawn.AbsOrigin) <= smokeRadius)
if (SkillUtils.AddHealth(pawn, smokeHeal)) 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); 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 int SmokeHeal { get; set; } = smokeHeal;
public float SmokeRadius { get; set; } = smokeRadius; public float SmokeRadius { get; set; } = smokeRadius;
public int TickCooldown { get; set; } = tickCooldown; 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 public class Hermit : ISkill
{ {
private const Skills skillName = Skills.Hermit; private const Skills skillName = Skills.Hermit;
private static readonly ConcurrentDictionary<string, int> ConcurrentDictionary = new( private const int fallbackReserveAmmo = 100;
[
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;
public static void LoadSkill() public static void LoadSkill()
{ {
@ -43,9 +31,11 @@ namespace src.player.skills
var weapon = pawn.WeaponServices.ActiveWeapon.Value; var weapon = pawn.WeaponServices.ActiveWeapon.Value;
if (weapon == null || !weapon.IsValid || weapon.VData == null) return; 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.Clip1 = weapon.VData.MaxClip1;
weapon.ReserveAmmo.Fill(maxReserveAmmoClip); weapon.ReserveAmmo.Fill(reserve);
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_iClip1"); Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_iClip1");
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_pReserveAmmo"); Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_pReserveAmmo");

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class HotBomb : ISkill public class HotBomb : ISkill
{ {
private const Skills skillName = Skills.HotBomb; private const Skills skillName = Skills.HotBomb;
private const string hurtSound = "Player.DamageBody.Victim";
private readonly static ConcurrentDictionary<uint, byte> players = []; private readonly static ConcurrentDictionary<uint, byte> players = [];
public static void LoadSkill() public static void LoadSkill()
@ -41,7 +42,7 @@ namespace src.player.skills
if (!hasC4) continue; if (!hasC4) continue;
SkillUtils.TakeHealth(pawn, damage, owner, KillfeedIcons.C4); 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 float Cooldown { get; set; } = cooldown;
public int Damage { get; set; } = damage; public int Damage { get; set; } = damage;
} }

View file

@ -112,7 +112,7 @@ namespace src.player.skills
var player = Utilities.GetPlayerFromIndex((int)playerSkill.PlayerIndex); var player = Utilities.GetPlayerFromIndex((int)playerSkill.PlayerIndex);
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
player.EmitSound("SolidMetal.BulletImpact"); player.EmitSound("SolidMetal.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
CDynamicProp? cloneProp = null; CDynamicProp? cloneProp = null;
if (playerSkill.CloneProp != null) if (playerSkill.CloneProp != null)
@ -468,8 +468,9 @@ namespace src.player.skills
public List<ulong> Weapons { get; set; } = []; 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 Cooldown { get; set; } = cooldown;
public float Duration { get; set; } = duration; public float Duration { get; set; } = duration;
} }

View file

@ -50,10 +50,18 @@ namespace src.player.skills
private static void SendAlertToAll() private static void SendAlertToAll()
{ {
holdersTick = int.MinValue;
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
{ {
if (!CheckIlliterateSkill(player)) continue; 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);
} }
} }

View file

@ -199,7 +199,7 @@ namespace src.player.skills
if (!consumedReplicas.TryAdd(replica.Index, 0)) return; 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(); if (ActiveTimers.TryRemove((int)replica.Index, out var timer)) timer?.Kill();
var owner = GetReplicaOwner(replica.Index); var owner = GetReplicaOwner(replica.Index);
@ -230,8 +230,9 @@ namespace src.player.skills
public DateTime Cooldown { get; set; } 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 Cooldown { get; set; } = cooldown;
public float DurationRun { get; set; } = durationRun; public float DurationRun { get; set; } = durationRun;
public float DurationCrouch { get; set; } = durationCrouch; public float DurationCrouch { get; set; } = durationCrouch;

View file

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

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class Jester : ISkill public class Jester : ISkill
{ {
private const Skills skillName = Skills.Jester; private const Skills skillName = Skills.Jester;
private const string toggleSound = "Weapon_Taser.ChargeReady";
private static readonly ConcurrentDictionary<uint, JesterInfo> jesters = []; private static readonly ConcurrentDictionary<uint, JesterInfo> jesters = [];
public static void LoadSkill() public static void LoadSkill()
@ -196,7 +197,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player); var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent != null && playerEvent.IsValid) 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"); var minTime = SkillsInfo.GetValue<float>(skillName, "minTime");
@ -275,8 +276,9 @@ namespace src.player.skills
public Timer? Timer { get; set; } = null; 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 MinTime { get; set; } = minTime;
public float MaxTime { get; set; } = maxTime; public float MaxTime { get; set; } = maxTime;
} }

View file

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

View file

@ -9,6 +9,7 @@ namespace src.player.skills
public class LastGasp : ISkill public class LastGasp : ISkill
{ {
private const Skills skillName = Skills.LastGasp; private const Skills skillName = Skills.LastGasp;
private const string hurtSound = "Player.DamageBody.Victim";
public static void LoadSkill() public static void LoadSkill()
{ {
@ -45,11 +46,12 @@ namespace src.player.skills
} }
SkillUtils.TakeHealth(attackerPawn, damageAfterDeath, victim, KillfeedIcons.Fist); 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 int DamageAfterDeath { get; set; } = damageAfterDeath;
public bool CanKill { get; set; } = canKill; public bool CanKill { get; set; } = canKill;

View file

@ -12,6 +12,9 @@ namespace src.player.skills
public class LongKnife : ISkill public class LongKnife : ISkill
{ {
private const Skills skillName = Skills.LongKnife; 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 static bool hooked = false;
private const int actionCode = 503; private const int actionCode = 503;
@ -121,12 +124,15 @@ namespace src.player.skills
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire") && player.Team == target.Team) return; 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); 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 float MaxDistance { get; set; } = maxDistance;
public bool FriendlyFire { get; set; } = friendlyFire; public bool FriendlyFire { get; set; } = friendlyFire;
} }

View file

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

View file

@ -93,7 +93,7 @@ namespace src.player.skills
skillInfo.Cooldown = DateTime.Now; skillInfo.Cooldown = DateTime.Now;
skillInfo.Count -= 1; skillInfo.Count -= 1;
SkillUtils.AddHealth(playerPawn, SkillsInfo.GetValue<int>(skillName, "healthToAdd")); 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 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 HealthToAdd { get; set; } = healthToAdd;
public int HealthShotLimit { get; set; } = healthShotLimit; public int HealthShotLimit { get; set; } = healthShotLimit;
public float Cooldown { get; set; } = cooldown; public float Cooldown { get; set; } = cooldown;

View file

@ -68,7 +68,7 @@ namespace src.player.skills
position.Z += 60; position.Z += 60;
grenade.Teleport(position); grenade.Teleport(position);
grenade.EmitSound("IncGrenade.Bounce_M"); grenade.EmitSound("IncGrenade.Bounce_M", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
grenade.DetonateTime = Server.CurrentTime + .5f; grenade.DetonateTime = Server.CurrentTime + .5f;
Utilities.SetStateChanged(grenade, "CBaseGrenade", "m_flDetonateTime"); Utilities.SetStateChanged(grenade, "CBaseGrenade", "m_flDetonateTime");
@ -161,8 +161,9 @@ namespace src.player.skills
SkillUtils.UpdateGrenadeCount(player, CsItem.HEGrenade, 1); 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 float DetonationRange { get; set; } = detonationRange;
public int GrenadeLimit { get; set; } = grenadeLimit; 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 public class Nightmare : ISkill
{ {
private const Skills skillName = Skills.Nightmare; 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> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetVolumes = []; private static readonly ConcurrentDictionary<uint, uint> targetVolumes = [];
private static readonly object setLock = new(); 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)); playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("nightmare_player_info", enemy.PlayerName));
if (enemyEvent != null && enemyEvent.IsValid) if (enemyEvent != null && enemyEvent.IsValid)
{
enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("nightmare_enemy_info")); enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("nightmare_enemy_info"));
SkillUtils.EmitSoundToPlayer(enemyEvent, cursedSound, SkillsInfo.GetValue<float>(skillName, "soundVolume"));
}
} }
public static void DisableSkill(CCSPlayerController player) public static void DisableSkill(CCSPlayerController player)
@ -227,8 +231,9 @@ namespace src.player.skills
EntityManager.DestroyEntity(volumeIndex); 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 string PostProcessing { get; set; } = postProcessing;
public float FadeTime { get; set; } = fadeTime; public float FadeTime { get; set; } = fadeTime;
public float MinExposure { get; set; } = minExposure; public float MinExposure { get; set; } = minExposure;

View file

@ -63,37 +63,29 @@ namespace src.player.skills
var bomb = PlayerManager.GetTickBomb(); var bomb = PlayerManager.GetTickBomb();
uint? bombOwnerIndex = bomb != null && bomb.IsValid ? bomb.OwnerEntity?.Index : null; 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) foreach (var (info, player) in infoList)
{ {
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue; if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero; 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 (target.Index == player.Index) continue;
if (playerController == null || !playerController.IsValid || playerController.Index == player.Index) if (player.Team == target.Team) continue;
continue;
if (player.Team == playerController.Team)
continue;
var playerPawn = playerController.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera. // Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle) if (targetHandle != nint.Zero && target.Pawn.Handle == targetHandle) continue;
continue;
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index); if (info.TransmitEntities.Contains(target.Pawn.Index))
if (entity == null || !entity.IsValid) continue; info.TransmitEntities.Remove(target.Pawn.Index);
if (info.TransmitEntities.Contains(entity.Index)) SkillUtils.HideCarriedEntities(info, target.Pawn);
info.TransmitEntities.Remove(entity.Index);
SkillUtils.HideCarriedEntities(info, playerPawn); if (bomb == null || !target.HoldsBomb) continue;
if (bomb == null || bombOwnerIndex != playerController.Index) continue;
if (info.TransmitEntities.Contains(bomb.Index)) if (info.TransmitEntities.Contains(bomb.Index))
info.TransmitEntities.Remove(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")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
private static readonly HashSet<string> blockedWeapons = private const string infernoDamage = "inferno";
["hegrenade", "inferno", "decoy", "flashbang", "smokegrenade", "molotov"];
public static bool PlayerHurtPre(EventPlayerHurt @event) public static bool PlayerHurtPre(EventPlayerHurt @event)
{ {
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(player)) return false; 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; if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != skillName) return false;
SkillUtils.RestoreHealth(player); SkillUtils.RestoreHealth(player);

View file

@ -9,6 +9,7 @@ namespace src.player.skills
public class PawelJumper : ISkill public class PawelJumper : ISkill
{ {
private const Skills skillName = Skills.PawelJumper; private const Skills skillName = Skills.PawelJumper;
private const string jumpSound = "Default.WalkJump";
private static readonly int?[] J = new int?[64]; private static readonly int?[] J = new int?[64];
private static readonly PlayerButtons[] LB = new PlayerButtons[64]; private static readonly PlayerButtons[] LB = new PlayerButtons[64];
@ -73,13 +74,17 @@ namespace src.player.skills
{ {
J[player.Slot]++; J[player.Slot]++;
playerEventPawn.AbsVelocity.Z = 300; playerEventPawn.AbsVelocity.Z = 300;
if (J[player.Slot] > 1)
playerEventPawn.EmitSound(jumpSound, volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
} }
LB[player.Slot] = buttons; 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 ExtraJumpsMin { get; set; } = extraJumpsMin;
public int ExtraJumpsMax { get; set; } = extraJumpsMax; public int ExtraJumpsMax { get; set; } = extraJumpsMax;
} }

View file

@ -15,13 +15,29 @@ namespace src.player.skills
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
Instance.AddToManifest(SkillsInfo.GetValue<string>(skillName, "particleName"));
} }
public static void NewRound() public static void NewRound()
{ {
foreach (var pilotInfo in PlayerPilotInfo.Values)
ClearTrail(pilotInfo);
PlayerPilotInfo.Clear(); 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() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
@ -39,7 +55,9 @@ namespace src.player.skills
public static void DisableSkill(CCSPlayerController player) public static void DisableSkill(CCSPlayerController player)
{ {
PlayerPilotInfo.TryRemove(player.Index, out _); if (PlayerPilotInfo.TryRemove(player.Index, out var pilotInfo))
ClearTrail(pilotInfo);
SkillUtils.ResetPrintHTML(player); SkillUtils.ResetPrintHTML(player);
} }
@ -96,9 +114,18 @@ namespace src.player.skills
); );
if (inUse) if (inUse)
{
ApplyPilotEffect(player); ApplyPilotEffect(player);
else if (pilotInfo.Fuel <= 0) StartTrail(player, playerPawn, pilotInfo);
pilotInfo.IsFlying = false; }
else
{
if (pilotInfo.TrailIndex != null)
ClearTrail(pilotInfo);
if (pilotInfo.Fuel <= 0)
pilotInfo.IsFlying = false;
}
pilotInfo.LastButtons = buttons; pilotInfo.LastButtons = buttons;
UpdateHUD(player, pilotInfo); UpdateHUD(player, pilotInfo);
@ -132,6 +159,62 @@ namespace src.player.skills
return "#FF0000"; 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) private static void ApplyPilotEffect(CCSPlayerController player)
{ {
var playerPawn = player.PlayerPawn.Value; var playerPawn = player.PlayerPawn.Value;
@ -168,10 +251,13 @@ namespace src.player.skills
public int JumpCount { get; set; } = 0; public int JumpCount { get; set; } = 0;
public float LastJumpTime { get; set; } = 0; public float LastJumpTime { get; set; } = 0;
public bool IsFlying { get; set; } = false; 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 MaximumFuel { get; set; } = maximumFuel;
public float FuelConsumption { get; set; } = fuelConsumption; public float FuelConsumption { get; set; } = fuelConsumption;
public float Refuelling { get; set; } = refuelling; public float Refuelling { get; set; } = refuelling;

View file

@ -94,6 +94,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
float currentTime = Server.CurrentTime; float currentTime = Server.CurrentTime;
bool hudFrame = SkillUtils.IsHudFrame();
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p.Team == CsTeam.Terrorist)) foreach (var player in PlayerManager.GetTickPlayers().Where(p => p.Team == CsTeam.Terrorist))
{ {
if (!Instance.IsPlayerValid(player)) continue; if (!Instance.IsPlayerValid(player)) continue;
@ -111,7 +113,7 @@ namespace src.player.skills
pawn.InBombZone = true; pawn.InBombZone = true;
Schema.SetSchemaValue<bool>(pawn.Handle, "CCSPlayerPawn", "m_bInBombZone", 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; float remaining = plantTime + 3f - currentTime;
playerInfo.PrintHTML = $"{player.GetTranslation("planter_planting", $"<font color='#00FF00'>{Math.Max(0, remaining):0.0}s</font>")}"; 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 public class Poison : ISkill
{ {
private const Skills skillName = Skills.Poison; 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, byte> poisonedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = []; private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetToPlayer = []; 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); SkillUtils.TakeHealth(pawn, SkillsInfo.GetValue<int>(skillName, "Damage"), GetSkillOwner(playerIndex), KillfeedIcons.Spray);
if (Server.TickCount % cooldown2 == 0) 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); 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 int Damage { get; set; } = damage;
public float Cooldown { get; set; } = cooldown; public float Cooldown { get; set; } = cooldown;
public int MinHealth { get; set; } = minHealth; 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, byte> bannedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = []; private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly object setLock = new(); private static readonly object setLock = new();
private static readonly string[] disabledWeapons = private static HashSet<string> disabledWeapons => WeaponPool.Rifles;
[
"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"
];
public static void LoadSkill() public static void LoadSkill()
{ {

View file

@ -69,7 +69,7 @@ namespace src.player.skills
} }
if (!info.Defusing) if (!info.Defusing)
pawn.EmitSound("c4.disarmstart"); pawn.EmitSound("c4.disarmstart", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
info.Defusing = true; info.Defusing = true;
info.DefusingTime -= (1f / tickRate); info.DefusingTime -= (1f / tickRate);
@ -128,8 +128,9 @@ namespace src.player.skills
public float DefusingTime { get; set; } 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 MaxDefusingRange { get; set; } = maxDefusingRange;
public float DefusingTime { get; set; } = defusingTime; 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) if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim)
return; return;
if (Heavyweight.Resists(victim)) return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (playerInfo?.Skill == skillName && victim!.PawnIsAlive) 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 ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = new();
private static readonly object setLock = new(); private static readonly object setLock = new();
private static readonly HashSet<string> pistols = new(StringComparer.Ordinal) private static HashSet<string> pistols => WeaponPool.Pistols;
{ private static HashSet<string> rifles => WeaponPool.Rifles;
"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"
};
public static void LoadSkill() public static void LoadSkill()
{ {
@ -104,6 +93,19 @@ namespace src.player.skills
RemoveAndGiveWeapon(player); 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) private static void RemoveAndGiveWeapon(CCSPlayerController player)
{ {
var pawn = player.PlayerPawn.Value; var pawn = player.PlayerPawn.Value;
@ -117,12 +119,15 @@ namespace src.player.skills
if (playerWeapons.Count == 0) if (playerWeapons.Count == 0)
return; return;
var available = pistols.Concat(rifles).Where(w => !playerWeapons.Contains(w)).ToArray(); var availablePistols = pistols.Where(w => !playerWeapons.Contains(w)).ToArray();
if (available.Length == 0) var availableRifles = rifles.Where(w => !playerWeapons.Contains(w)).ToArray();
if (availablePistols.Length == 0 && availableRifles.Length == 0)
return; return;
bool isPistol = RollPistol(availablePistols.Length > 0, availableRifles.Length > 0);
var available = isPistol ? availablePistols : availableRifles;
string newWeapon = available[Instance.Random.Next(available.Length)]; string newWeapon = available[Instance.Random.Next(available.Length)];
bool isPistol = pistols.Contains(newWeapon);
string? weaponToRemove = null; string? weaponToRemove = null;
foreach (var item in pawn.WeaponServices.MyWeapons) foreach (var item in pawn.WeaponServices.MyWeapons)
@ -163,9 +168,11 @@ namespace src.player.skills
public DateTime Cooldown { get; set; } 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 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; 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); var owner = GetReplicaOwner(replica.Index);
EntityManager.DestroyEntity(replica.Index); EntityManager.DestroyEntity(replica.Index);
@ -183,8 +183,9 @@ namespace src.player.skills
public DateTime Cooldown { get; set; } 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 float Cooldown { get; set; } = cooldown;
public int YourTeamDamage { get; set; } = yourTeamDamage; public int YourTeamDamage { get; set; } = yourTeamDamage;
public int EnemyTeamDamage { get; set; } = enemyTeamDamage; 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 public class Ricochet : ISkill
{ {
private const Skills skillName = Skills.Ricochet; private const Skills skillName = Skills.Ricochet;
private const string bounceSound = "FX_RicochetSound.Ricochet_Legacy";
private static readonly ConcurrentDictionary<uint, ImpactBudget> impactBudgets = []; private static readonly ConcurrentDictionary<uint, ImpactBudget> impactBudgets = [];
private static readonly HashSet<uint> ownedTracers = []; private static readonly HashSet<uint> ownedTracers = [];
@ -57,18 +58,10 @@ namespace src.player.skills
if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill != skillName) return; if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill != skillName) return;
if (!RayTrace.IsAvailable) if (!RayTrace.IsAvailable) return;
{
Trace("RayTrace module unavailable");
return;
}
var pawn = player.PlayerPawn?.Value; var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
{
Trace("shooter pawn invalid or dead");
return;
}
if (!TryConsumeBudget(player.Index)) return; if (!TryConsumeBudget(player.Index)) return;
@ -76,32 +69,14 @@ namespace src.player.skills
Vector3 impact = new(@event.X, @event.Y, @event.Z); Vector3 impact = new(@event.X, @event.Y, @event.Z);
Vector3 toImpact = impact - eyePos; Vector3 toImpact = impact - eyePos;
if (toImpact.LengthSquared() < 1f) if (toImpact.LengthSquared() < 1f) return;
{
Trace("impact too close to eye");
return;
}
Vector3 direction = Vector3.Normalize(toImpact); Vector3 direction = Vector3.Normalize(toImpact);
var surface = RayTrace.TraceShape(player, ToVector(eyePos), ToVector(impact + direction * 8f), worldOnlyMask, 0); var surface = RayTrace.TraceShape(player, ToVector(eyePos), ToVector(impact + direction * 8f), worldOnlyMask, 0);
if (surface == null) if (surface == null || !surface.Value.DidHit) return;
{
Trace("surface trace returned null");
return;
}
if (!surface.Value.DidHit) if (!TryResolveNormal(player, surface.Value, direction, out Vector3 normal)) return;
{
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;
}
Bounce(player, pawn, surface.Value.EndPos, direction, normal); 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)); KillfeedIcons? killfeedIcon = KillfeedIconsExtensions.FromWeaponName(SkillUtils.GetDesignerName(pawn.WeaponServices?.ActiveWeapon?.Value));
List<Vector3> path = []; List<Vector3> path = [];
List<int> soundAt = [];
for (int i = 0; i < bounces; i++) for (int i = 0; i < bounces; i++)
{ {
direction = Vector3.Reflect(direction, normal); direction = Vector3.Reflect(direction, normal);
Vector3 start = point + normal * 2f; 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; Vector3 end = start + direction * segmentDistance;
var hit = RayTrace.TraceShape(player, ToVector(start), ToVector(end)); var hit = RayTrace.TraceShape(player, ToVector(start), ToVector(end));
if (hit == null) if (hit == null) break;
{
Trace($"bounce {i + 1}: segment trace returned null");
break;
}
Vector3 stop = hit.Value.DidHit ? hit.Value.EndPos : end; Vector3 stop = hit.Value.DidHit ? hit.Value.EndPos : end;
path.Add(stop); path.Add(stop);
if (!hit.Value.DidHit) if (!hit.Value.DidHit) break;
{
Trace($"bounce {i + 1}: no surface, len={Vector3.Distance(start, stop):0.#}");
break;
}
if (hit.Value.HitPlayer(out var victim)) if (hit.Value.HitPlayer(out var victim))
{ {
@ -150,17 +122,14 @@ namespace src.player.skills
break; break;
} }
if (!TryResolveNormal(player, hit.Value, direction, out normal)) if (!TryResolveNormal(player, hit.Value, direction, out normal)) break;
{
Trace($"bounce {i + 1}: could not resolve normal");
break;
}
soundAt.Add(path.Count - 1);
point = hit.Value.EndPos; point = hit.Value.EndPos;
damage *= damageFalloff; damage *= damageFalloff;
} }
StartFlight(player, path); StartFlight(player, path, soundAt);
} }
public static void OnTick() public static void OnTick()
@ -191,11 +160,12 @@ namespace src.player.skills
float tail = MathF.Max(head - trail, flight.Cumulative[segment]); float tail = MathF.Max(head - trail, flight.Cumulative[segment]);
UpdateTracer(flight, PointAt(flight, tail), PointAt(flight, head)); 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 (path.Count < 2) return;
if (!SkillsInfo.GetValue<bool>(skillName, "showTracer")) return; if (!SkillsInfo.GetValue<bool>(skillName, "showTracer")) return;
@ -223,10 +193,27 @@ namespace src.player.skills
Cumulative = cumulative, Cumulative = cumulative,
TotalLength = total, TotalLength = total,
DeadlineTick = Server.TickCount + (int)(total / speed * 64f) + 128, 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) private static int SegmentAt(Flight flight, float distance)
{ {
for (int i = flight.Points.Length - 2; i >= 0; i--) for (int i = flight.Points.Length - 2; i >= 0; i--)
@ -332,9 +319,8 @@ namespace src.player.skills
beam.DispatchSpawn(); beam.DispatchSpawn();
return beam; return beam;
} }
catch (Exception ex) catch
{ {
Trace($"beam create failed: {ex.Message}");
return null; return null;
} }
} }
@ -352,7 +338,6 @@ namespace src.player.skills
if (beam == null) if (beam == null)
{ {
ownedTracers.Remove(beamIndex); ownedTracers.Remove(beamIndex);
Trace($"tracer {beamIndex} vanished before release");
return; return;
} }
@ -395,9 +380,6 @@ namespace src.player.skills
if (beam != null) HideTracer(beam); if (beam != null) HideTracer(beam);
} }
if (ownedTracers.Count > 0)
Trace($"pool reset: {ownedTracers.Count} tracer(s) released");
tracerPool.Clear(); tracerPool.Clear();
ownedTracers.Clear(); ownedTracers.Clear();
} }
@ -454,7 +436,6 @@ namespace src.player.skills
int finalDamage = (int)MathF.Round(damage); int finalDamage = (int)MathF.Round(damage);
if (finalDamage <= 0) return; if (finalDamage <= 0) return;
Trace($"hit {victimEvent.PlayerName} for {finalDamage}");
SkillUtils.TakeHealth(victimPawn, finalDamage, attacker, killfeedIcon ?? KillfeedIcons.Leg); 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; if (TryRefineNormal(player, result.EndPos, direction, rough, out normal)) return true;
Trace("surface refine failed, using rough normal");
normal = rough; normal = rough;
return true; return true;
} }
@ -600,11 +580,6 @@ namespace src.player.skills
return Vector3.Distance(point, baseHit) <= tolerance; return Vector3.Distance(point, baseHit) <= tolerance;
} }
private static void Trace(string message)
{
Debug.WriteToDebug($"[Ricochet] {message}");
}
private static bool TryConsumeBudget(uint playerIndex) private static bool TryConsumeBudget(uint playerIndex)
{ {
int tick = Server.TickCount; int tick = Server.TickCount;
@ -637,12 +612,15 @@ namespace src.player.skills
public required float[] Cumulative { get; set; } public required float[] Cumulative { get; set; }
public required float TotalLength { get; set; } public required float TotalLength { get; set; }
public required int DeadlineTick { get; set; } public required int DeadlineTick { get; set; }
public required int[] SoundAt { get; set; }
public float Travelled { get; set; } public float Travelled { get; set; }
public int NextSound { get; set; }
public uint? BeamIndex { 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 int Bounces { get; set; } = bounces;
public float SegmentDistance { get; set; } = segmentDistance; public float SegmentDistance { get; set; } = segmentDistance;
public float DamageMultiplier { get; set; } = damageMultiplier; 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; if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (Heavyweight.Resists(victim)) return;
var victimPawn = victim!.PlayerPawn.Value; var victimPawn = victim!.PlayerPawn.Value;
if (victimPawn == null || !victimPawn.IsValid) return; if (victimPawn == null || !victimPawn.IsValid) return;

View file

@ -16,13 +16,7 @@ namespace src.player.skills
private static readonly object setLock = new(); private static readonly object setLock = new();
private const string weapon_awp = "weapon_awp"; private const string weapon_awp = "weapon_awp";
private static readonly string[] rifles = private static HashSet<string> rifles => WeaponPool.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"
];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -81,7 +75,7 @@ namespace src.player.skills
foreach (var index in AWPs.ToList()) foreach (var index in AWPs.ToList())
SkillUtils.SafeKillEntity<CBasePlayerWeapon>(index); 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(() => Server.NextFrame(() =>
{ {
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; 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 (SkillPlayerInfo.IsEmpty) return;
if (Server.TickCount % 60 != 0) return;
int cooldown = SkillsInfo.GetValue<int>(skillName, "cooldown"); int cooldown = SkillsInfo.GetValue<int>(skillName, "cooldown");
if (cooldown < 1) cooldown = 1; 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() 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)) .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) if (entities.Count == 0)
{ {
player.PlayerPawn.Value!.EmitSound(soundEventName, volume: 1f); player.PlayerPawn.Value!.EmitSound(soundEventName, volume: volume);
continue; continue;
} }
var entity = Utilities.GetEntityFromIndex<CDynamicProp>((int)entities[0]); var entity = Utilities.GetEntityFromIndex<CDynamicProp>((int)entities[0]);
if (entity != null && entity.IsValid) 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; public int Cooldown { get; set; } = cooldown;
} }
} }

View file

@ -167,7 +167,7 @@ namespace src.player.skills
SetMagazines(player, playerMagazines + enemyMagazines); SetMagazines(player, playerMagazines + enemyMagazines);
SetMagazines(enemy, 0); 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); var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) return; if (enemyEvent == null || !enemyEvent.IsValid) return;
@ -299,8 +299,9 @@ namespace src.player.skills
public required int Reserve { get; set; } 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; 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")); damage = Math.Min(damage, SkillsInfo.GetValue<int>(skillName, "maxTakenDamagePerShot"));
SkillUtils.TakeHealth(attackerEvent.PlayerPawn.Value, damage, victimEvent, KillfeedIcons.Armor); 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 float HealthTakenScale { get; set; } = healthTakenScale;
public int MaxTakenDamagePerShot { get; set; } = maxTakenDamagePerShot; public int MaxTakenDamagePerShot { get; set; } = maxTakenDamagePerShot;
} }

View file

@ -105,7 +105,7 @@ namespace src.player.skills
} }
if (SkillUtils.TakeHealth(pawn, damage, thrower, KillfeedIcons.Smokegrenade)) 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); 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 int SmokeDamage { get; set; } = smokeDamage;
public float SmokeRadius { get; set; } = smokeRadius; public float SmokeRadius { get; set; } = smokeRadius;
public int TickCooldown { get; set; } = tickCooldown; public int TickCooldown { get; set; } = tickCooldown;

View file

@ -12,6 +12,7 @@ namespace src.player.skills
public class Tripwire : ISkill public class Tripwire : ISkill
{ {
private const Skills skillName = Skills.Tripwire; 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, WireInfo> wires = [];
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = []; private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
@ -267,7 +268,7 @@ namespace src.player.skills
var ownerEvent = PlayerManager.GetPlayerFromEvent(owner); var ownerEvent = PlayerManager.GetPlayerFromEvent(owner);
if (ownerEvent != null && ownerEvent.IsValid) 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)); 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 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 RadarDuration { get; set; } = radarDuration;
public float TriggerRadius { get; set; } = triggerRadius; public float TriggerRadius { get; set; } = triggerRadius;
public float WireHeight { get; set; } = wireHeight; 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 else
Localization.PrintTranslationToChatAll($" {ChatColors.LightBlue}{{0}}", ["watchmaker_ct"], true, [roundTime]); 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(); var proxy = Utilities.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules").FirstOrDefault();
if (proxy == null) return; if (proxy == null) return;
Utilities.SetStateChanged(proxy, "CCSGameRulesProxy", "m_pGameRules"); 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 int ChangeRoundTime { get; set; } = changeRoundTime;
public string SoundEvent { get; set; } = soundEvent; 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 ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
private static readonly object setLock = new(); private static readonly object setLock = new();
private static readonly HashSet<string> weapons = [ "weapon_deagle", "weapon_revolver", "weapon_glock", "weapon_usp_silencer", private static HashSet<string> weapons => WeaponPool.All;
"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" ];
public static void LoadSkill() public static void LoadSkill()
{ {

View file

@ -33,6 +33,9 @@ namespace src.utils
using (var sr = new StreamReader(fs)) using (var sr = new StreamReader(fs))
json = sr.ReadToEnd(); json = sr.ReadToEnd();
newConfig = JsonConvert.DeserializeObject<SettingsModel>(json) ?? new SettingsModel(); newConfig = JsonConvert.DeserializeObject<SettingsModel>(json) ?? new SettingsModel();
if (IsSectionMissing(json, nameof(SettingsModel.Weapons)))
SaveConfig(newConfig);
} }
catch 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) public static void SaveConfig(SettingsModel config)
{ {
lock (fileLock) lock (fileLock)
@ -92,6 +107,7 @@ namespace src.utils
public string DisableHUDOnDeathPermission { get; set; } public string DisableHUDOnDeathPermission { get; set; }
public bool DisableSkillsOnRoundEnd { get; set; } public bool DisableSkillsOnRoundEnd { get; set; }
public int? CurseSkillPerPlayer { get; set; } public int? CurseSkillPerPlayer { get; set; }
public WeaponPools Weapons { get; set; }
public LanguageSystem LanguageSystem { get; set; } public LanguageSystem LanguageSystem { get; set; }
public HtmlHudCustomisation HtmlHudCustomisation { get; set; } public HtmlHudCustomisation HtmlHudCustomisation { get; set; }
public ChatMessage ChatMessage { get; set; } public ChatMessage ChatMessage { get; set; }
@ -124,6 +140,31 @@ namespace src.utils
DisableSkillsOnRoundEnd = false; DisableSkillsOnRoundEnd = false;
CurseSkillPerPlayer = null; 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 LanguageSystem = new LanguageSystem
{ {
DefaultLangCode = "en", 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 class ChatMessage
{ {
public required float MaxWidth { get; set; } 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; 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 public static bool DestroyEntity<T>(uint? entityIndex) where T : CBaseEntity
{ {
if (entityIndex == null) return false; if (entityIndex == null) return false;

View file

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

View file

@ -40,7 +40,7 @@ namespace src.utils
LazySig<MemoryFunctionVoid<CBasePlayerPawn, QAngle>>("SnapViewAngles", s => new(s)); LazySig<MemoryFunctionVoid<CBasePlayerPawn, QAngle>>("SnapViewAngles", s => new(s));
// private static readonly int collisionRulesChangedOffset = GameData.GetOffset("CBaseEntity_CollisionRulesChanged"); // 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; if (!player.IsValid) return;
@ -49,7 +49,7 @@ namespace src.utils
char symbol = config.LineSymbol; char symbol = config.LineSymbol;
if (string.IsNullOrEmpty(title)) title = player.GetTranslation("jRandomSkills"); if (string.IsNullOrEmpty(title)) title = player.GetTranslation("jRandomSkills");
if (Illiterate.CheckIlliterateSkill(player)) if (!ignoreIlliterate && Illiterate.CheckIlliterateSkill(player))
msg = Illiterate.GetRandomText(msg); msg = Illiterate.GetRandomText(msg);
if (border.Contains('t') && config.LineShow) if (border.Contains('t') && config.LineShow)
@ -60,11 +60,39 @@ namespace src.utils
player.PrintToChat($" {MeansureString.GetTextDashed("", maxWidth, symbol, config.LineColor)}"); 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() public static bool IsFreezeTime()
{ {
return jRandomSkills.Instance?.GameRules?.FreezePeriod == true; 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 bool IsHudFrame() => Server.TickCount % 4 == 0;
public static void RegisterSkill(Skills skill, string color, bool display = true) 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.Deaf, Skills.ExpensiveAmmo, Skills.Giant, Skills.Glitch,
Skills.Jammer, Skills.JumpBan, Skills.JumpCurse, Skills.LifeSwap, Skills.Jammer, Skills.JumpBan, Skills.JumpCurse, Skills.LifeSwap,
Skills.Magnifier, Skills.MoneySwap, Skills.Nightmare, Skills.Poison, 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); 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; 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) public static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player, bool respectCurseLimit = false)
{ {
if (player == null || !player.IsValid) return []; if (player == null || !player.IsValid) return [];
@ -575,6 +671,26 @@ namespace src.utils
return true; 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) public static void HideCarriedEntities(CCheckTransmitInfo info, CCSPlayerPawn? pawn)
{ {
if (pawn == null || !pawn.IsValid) return; if (pawn == null || !pawn.IsValid) return;

View file

@ -21,6 +21,7 @@ namespace src.utils
private static SkillsInfoModel? _indexedConfig; private static SkillsInfoModel? _indexedConfig;
private static ConcurrentDictionary<string, DefaultSkillInfo> _byName = new(); private static ConcurrentDictionary<string, DefaultSkillInfo> _byName = new();
private static readonly ConcurrentDictionary<(Type Type, string Key), MemberInfo?> _memberCache = 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() public static SkillsInfoModel LoadSkillsInfo()
{ {
@ -92,6 +93,12 @@ namespace src.utils
if (!_byName.TryGetValue(skill.ToString()!, out var skillConfig) || skillConfig == null) if (!_byName.TryGetValue(skill.ToString()!, out var skillConfig) || skillConfig == null)
return default!; 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 => var member = _memberCache.GetOrAdd((skillConfig.GetType(), key), k =>
{ {
MemberInfo? m = k.Type.GetProperty(k.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); MemberInfo? m = k.Type.GetProperty(k.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
@ -106,11 +113,10 @@ namespace src.utils
_ => null _ => null
}; };
if (value == null) return default!; if (value == null) return null;
Type targetType = typeof(T);
Type? underlyingType = Nullable.GetUnderlyingType(targetType); Type? underlyingType = Nullable.GetUnderlyingType(targetType);
return (T)Convert.ChangeType(value, underlyingType ?? targetType); return Convert.ChangeType(value, underlyingType ?? targetType);
} }
private static void EnsureIndex() private static void EnsureIndex()
@ -124,6 +130,7 @@ namespace src.utils
_byName = dict; _byName = dict;
_indexedConfig = config; _indexedConfig = config;
_memberCache.Clear(); _memberCache.Clear();
_valueCache.Clear();
} }
public class SkillsInfoModel : ConcurrentBag<DefaultSkillInfo> 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 NeedsTeammates { get; set; } = needsTeammates;
public bool DisableOnFreezeTime { get; set; } = disableOnFreezeTime; public bool DisableOnFreezeTime { get; set; } = disableOnFreezeTime;
public bool DisableOnPistolRound { get; set; } = disableOnPistolRound;
public int OnlyTeam { get; set; } = (int)onlyTeam; public int OnlyTeam { get; set; } = (int)onlyTeam;
public string Color { get; set; } = color; public string Color { get; set; } = color;
public bool Active { get; set; } = active; 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", "DisableHUDOnDeathPermission": "@jRandomSkills/death",
"DisableSkillsOnRoundEnd": false, "DisableSkillsOnRoundEnd": false,
"CurseSkillPerPlayer": null, "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": { "LanguageSystem": {
"DefaultLangCode": "en", "DefaultLangCode": "en",
"DisableGeoLite": false, "DisableGeoLite": false,

View file

@ -60,6 +60,13 @@
"blastshot": "Explosivschuss", "blastshot": "Explosivschuss",
"blastshot_desc": "Drücke Attack2 mit der MP5, um eine HE-Granate abzufeuern", "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": "Hase",
"bunnyhop_desc": "Du erhältst automatisch bunnyhop", "bunnyhop_desc": "Du erhältst automatisch bunnyhop",
@ -144,6 +151,10 @@
"dwarf_desc": "Zufällige Charaktergröße zu Beginn der Runde", "dwarf_desc": "Zufällige Charaktergröße zu Beginn der Runde",
"dwarf_desc2": "Dein Größenmultiplikator beträgt: {0}x", "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": "Feindes-Spawn",
"enemyspawn_desc": "Klicke auf [css_useSkill], um zum Feindes-Spawn zu teleportieren", "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_no_anchor_info": "Keine Oberfläche zum Einhaken.",
"grapple_pulling_info": "ZUG", "grapple_pulling_info": "ZUG",
"gravitydecoy": "Schwerkraft-Ablenker",
"gravitydecoy_desc": "Dein Ablenkungsköder verändert die Schwerkraft aller in der Nähe",
"grenadier": "Grenadier", "grenadier": "Grenadier",
"grenadier_desc": "Du hast unendlich viele Granaten", "grenadier_desc": "Du hast unendlich viele Granaten",
@ -239,6 +253,9 @@
"healingchicken": "Heilendes Huhn", "healingchicken": "Heilendes Huhn",
"healingchicken_desc": "Deine Hühner heilen dich, solange du in ihrer Nähe bist", "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": "Einsiedler",
"hermit_desc": "Durch das Töten von Gegnern werden Munition und ein Teil der Gesundheit wiederhergestellt", "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_player_info": "Der Spieler ‚{0}‘ ist bankrott.",
"bankrupt_select_info": "Wähle einen Spieler aus, der bankrott gehen soll:", "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": "Albtraum",
"nightmare_desc": "Lass einen ausgewählten Gegner eine schreckliche Vision erleben", "nightmare_desc": "Lass einen ausgewählten Gegner eine schreckliche Vision erleben",
"nightmare_disable_info": "Der Albtraum ist vorbei.", "nightmare_disable_info": "Der Albtraum ist vorbei.",
@ -423,6 +446,10 @@
"prosthesis": "Prothese", "prosthesis": "Prothese",
"prosthesis_desc": "Arme und Beine sind kugelsicher", "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": "Pusher",
"push_desc": "Du hast eine zufällige Chance, einen Gegner zurückzustoßen, wenn du ihn triffst", "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} %", "push_desc2": "Deine Chancen, den Gegner zurückzustoßen, betragen: {0} %",
@ -460,6 +487,10 @@
"returntosender": "Zurück zum Absender", "returntosender": "Zurück zum Absender",
"returntosender_desc": "Der erste Treffer eines Gegners schickt ihn zurück zu seinem Spawn", "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": "Rich Boy",
"richboy_desc": "Du erhältst zu Beginn der Runde einen zufälligen Geldbetrag", "richboy_desc": "Du erhältst zu Beginn der Runde einen zufälligen Geldbetrag",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Stolperdraht platziert.", "tripwire_placed_info": "Stolperdraht platziert.",
"tripwire_triggered_info": "'{0}' hat deinen Stolperdraht ausgelöst.", "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": "Wallhack",
"wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen", "wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen",

View file

@ -60,6 +60,13 @@
"blastshot": "Blast Shot", "blastshot": "Blast Shot",
"blastshot_desc": "Press Attack2 with the MP5 to fire an HE grenade", "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": "Bunny",
"bunnyhop_desc": "You get auto \"BunnyHop\"", "bunnyhop_desc": "You get auto \"BunnyHop\"",
@ -137,6 +144,10 @@
"dwarf_desc": "Random character size at the start of the round", "dwarf_desc": "Random character size at the start of the round",
"dwarf_desc2": "Your size multiplier is: {0}x", "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": "Enemy Spawn",
"enemyspawn_desc": "Click [css_useSkill] to teleport to the 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_no_anchor_info": "No surface to hook onto.",
"grapple_pulling_info": "PULLING", "grapple_pulling_info": "PULLING",
"gravitydecoy": "Gravity Decoy",
"gravitydecoy_desc": "Your decoy changes the gravity of everyone nearby",
"grenadier": "Grenadier", "grenadier": "Grenadier",
"grenadier_desc": "You have infinite hegrenade", "grenadier_desc": "You have infinite hegrenade",
@ -232,6 +246,9 @@
"healingsmoke": "Healing Smoke", "healingsmoke": "Healing Smoke",
"healingsmoke_desc": "Your smoke grenades heal", "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": "Hermit",
"hermit_desc": "Killing restores ammo and a portion of health", "hermit_desc": "Killing restores ammo and a portion of health",
@ -360,6 +377,12 @@
"demoneye": "Demon Eye", "demoneye": "Demon Eye",
"demoneye_desc": "You deal damage to every enemy you are looking at", "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": "Nightmare",
"nightmare_desc": "Force a chosen enemy to experience a terrifying vision", "nightmare_desc": "Force a chosen enemy to experience a terrifying vision",
"nightmare_disable_info": "The nightmare has faded.", "nightmare_disable_info": "The nightmare has faded.",
@ -423,6 +446,10 @@
"prosthesis": "Prosthesis", "prosthesis": "Prosthesis",
"prosthesis_desc": "Arms and legs are bulletproof", "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": "Pusher",
"push_desc": "You have a random chance to push an enemy back when hitting them", "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}%", "push_desc2": "Your chances of pushing back the enemy are: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Return to Sender", "returntosender": "Return to Sender",
"returntosender_desc": "The first hit on an enemy sends them back to their spawn", "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": "Rich Boy",
"richboy_desc": "You receive a random amount of money at the start of the round", "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_placed_info": "Tripwire placed.",
"tripwire_triggered_info": "'{0}' triggered your tripwire.", "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": "Wallhack",
"wallhack_desc": "You can see enemies through walls", "wallhack_desc": "You can see enemies through walls",

View file

@ -60,6 +60,13 @@
"blastshot": "Tir explosif", "blastshot": "Tir explosif",
"blastshot_desc": "Appuyez sur Attack2 avec le MP5 pour tirer une grenade HE", "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": "Lapin",
"bunnyhop_desc": "Vous obtenez l’auto « BunnyHop »", "bunnyhop_desc": "Vous obtenez l’auto « BunnyHop »",
@ -144,6 +151,10 @@
"dwarf_desc": "Taille du personnage aléatoire au début du round", "dwarf_desc": "Taille du personnage aléatoire au début du round",
"dwarf_desc2": "Votre multiplicateur de taille est : {0}x", "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": "Spawn Ennemi",
"enemyspawn_desc": "Appuyez sur [css_useSkill] pour vous téléporter au 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_no_anchor_info": "Aucune surface pour accrocher le grappin.",
"grapple_pulling_info": "TRACTION", "grapple_pulling_info": "TRACTION",
"gravitydecoy": "Leurre gravitationnel",
"gravitydecoy_desc": "Ton leurre modifie la gravité de tous ceux qui sont à proximité",
"grenadier": "Grenadier", "grenadier": "Grenadier",
"grenadier_desc": "Tu as des grenades HE infinies", "grenadier_desc": "Tu as des grenades HE infinies",
@ -239,6 +253,9 @@
"healingchicken": "Poulet guérisseur", "healingchicken": "Poulet guérisseur",
"healingchicken_desc": "Vos poulets vous soignent lorsque vous êtes à proximité", "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": "Ermite",
"hermit_desc": "Tuer restaure des munitions et une partie de vos PV", "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_player_info": "Le joueur « {0} » est en faillite.",
"bankrupt_select_info": "Mettre un joueur 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": "Cauchemar",
"nightmare_desc": "Faites subir une vision terrifiante à un ennemi choisi", "nightmare_desc": "Faites subir une vision terrifiante à un ennemi choisi",
"nightmare_disable_info": "Le cauchemar s'est dissipé.", "nightmare_disable_info": "Le cauchemar s'est dissipé.",
@ -423,6 +446,10 @@
"prosthesis": "Prothèses", "prosthesis": "Prothèses",
"prosthesis_desc": "Les bras et les jambes sont pare-balles", "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": "Pousseur",
"push_desc": "Vous avez une chance aléatoire de repousser un ennemi en le touchant", "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}%", "push_desc2": "Votre chance de repousser l’ennemi est : {0}%",
@ -460,6 +487,10 @@
"returntosender": "Retour à l’Expéditeur", "returntosender": "Retour à l’Expéditeur",
"returntosender_desc": "Le premier coup porté à un ennemi le renvoie à son spawn", "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": "Gosse de Riche",
"richboy_desc": "Vous recevez une somme d’argent aléatoire au début du round", "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_placed_info": "Fil-piège placé.",
"tripwire_triggered_info": "'{0}' a déclenché votre fil-piège.", "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": "Wallhack",
"wallhack_desc": "Vous pouvez voir les ennemis à travers les murs", "wallhack_desc": "Vous pouvez voir les ennemis à travers les murs",

View file

@ -60,6 +60,13 @@
"blastshot": "Mocne Uderzenie", "blastshot": "Mocne Uderzenie",
"blastshot_desc": "Naciśnij Attack2 na MP5, aby wystrzelić granat HE", "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": "Królik",
"bunnyhop_desc": "Otrzymujesz auto \"BunnyHopa\"", "bunnyhop_desc": "Otrzymujesz auto \"BunnyHopa\"",
@ -144,6 +151,10 @@
"dwarf_desc": "Losowa wielkość postaci na początku rundy", "dwarf_desc": "Losowa wielkość postaci na początku rundy",
"dwarf_desc2": "Twój mnożnik wielkości to {0}x", "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": "Resp Wroga",
"enemyspawn_desc": "Kliknij [css_useSkill], aby teleportować się na 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_no_anchor_info": "Brak powierzchni do zaczepienia haka.",
"grapple_pulling_info": "PRZYCIĄGANIE", "grapple_pulling_info": "PRZYCIĄGANIE",
"gravitydecoy": "Grawitacyjna wabik",
"gravitydecoy_desc": "Twój wabik zmienia grawitację wszystkich w pobliżu",
"grenadier": "Granatnik", "grenadier": "Granatnik",
"grenadier_desc": "Masz nieskończone granaty odłamkowe", "grenadier_desc": "Masz nieskończone granaty odłamkowe",
@ -239,6 +253,9 @@
"healingchicken": "Lecząca Kura", "healingchicken": "Lecząca Kura",
"healingchicken_desc": "Twoje kury leczą Cię, gdy jesteś w pobliżu", "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": "Pustelnik",
"hermit_desc": "Zabijanie przywraca amunicję i część zdrowia", "hermit_desc": "Zabijanie przywraca amunicję i część zdrowia",
@ -360,6 +377,12 @@
"moneyswap_player_info": "Wymieniłeś swoje pieniądze z graczem '{0}'.", "moneyswap_player_info": "Wymieniłeś swoje pieniądze z graczem '{0}'.",
"moneyswap_select_info": "Zamień się kasą z graczem:", "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": "Koszmar",
"nightmare_desc": "Wywołaj u wybranego przeciwnika przerażającą wizję", "nightmare_desc": "Wywołaj u wybranego przeciwnika przerażającą wizję",
"nightmare_disable_info": "Koszmar minął.", "nightmare_disable_info": "Koszmar minął.",
@ -423,6 +446,10 @@
"prosthesis": "Proteza", "prosthesis": "Proteza",
"prosthesis_desc": "Ramiona i nogi są kuloodporne", "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": "Odpychacz",
"push_desc": "Masz losową szansę na odepchnięcie wroga po trafieniu go", "push_desc": "Masz losową szansę na odepchnięcie wroga po trafieniu go",
"push_desc2": "Twoje szanse na odepchnięcie wroga to: {0}%", "push_desc2": "Twoje szanse na odepchnięcie wroga to: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Zwrot do Nadawcy", "returntosender": "Zwrot do Nadawcy",
"returntosender_desc": "Pierwsze trafienie wroga powoduje, że wraca on na swój resp", "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": "Bogacz",
"richboy_desc": "Na początku rundy otrzymujesz losową kwotę gotówki", "richboy_desc": "Na początku rundy otrzymujesz losową kwotę gotówki",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Linka została rozstawiona.", "tripwire_placed_info": "Linka została rozstawiona.",
"tripwire_triggered_info": "'{0}' uruchomił Twoją linkę.", "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": "Wallhack",
"wallhack_desc": "Widzisz wrogów przez ściany", "wallhack_desc": "Widzisz wrogów przez ściany",

View file

@ -60,6 +60,13 @@
"blastshot": "Tiro Explosivo", "blastshot": "Tiro Explosivo",
"blastshot_desc": "Pressione Attack2 com a MP5 para disparar uma granada HE", "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": "Coelho",
"bunnyhop_desc": "Você ganha \"BunnyHop\" automático", "bunnyhop_desc": "Você ganha \"BunnyHop\" automático",
@ -144,6 +151,10 @@
"dwarf_desc": "Sua altura agora é aleatória", "dwarf_desc": "Sua altura agora é aleatória",
"dwarf_desc2": "Seu multiplicador de altura é: {0}x", "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": "Spawn Inimigo",
"enemyspawn_desc": "Clique em [css_useSkill] para se teletransportar para a base inimiga", "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_no_anchor_info": "Nenhuma superfície para prender o gancho.",
"grapple_pulling_info": "PUXANDO", "grapple_pulling_info": "PUXANDO",
"gravitydecoy": "Chamariz Gravitacional",
"gravitydecoy_desc": "Seu chamariz muda a gravidade de todos que estão por perto",
"grenadier": "Bomberman", "grenadier": "Bomberman",
"grenadier_desc": "Você tem HEs infinitas", "grenadier_desc": "Você tem HEs infinitas",
@ -239,6 +253,9 @@
"healingchicken": "Galinha Curadora", "healingchicken": "Galinha Curadora",
"healingchicken_desc": "Suas galinhas curam você enquanto você estiver por perto", "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": "Sanguessuga",
"hermit_desc": "Matar restaura munição e uma parte da sua vida", "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_player_info": "Você trocou dinheiro com o jogador '{0}'.",
"moneyswap_select_info": "Trocar dinheiro c/ jogador:", "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": "Pesadelo",
"nightmare_desc": "Faça um inimigo escolhido ter uma visão aterrorizante", "nightmare_desc": "Faça um inimigo escolhido ter uma visão aterrorizante",
"nightmare_disable_info": "O pesadelo desapareceu.", "nightmare_disable_info": "O pesadelo desapareceu.",
@ -423,6 +446,10 @@
"prosthesis": "Prótese", "prosthesis": "Prótese",
"prosthesis_desc": "Braços e pernas são à prova de balas", "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": "Empurrão",
"push_desc": "Você tem uma chance aleatória de empurrar um inimigo ao acertá-lo", "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}%", "push_desc2": "Suas chances de empurrar o inimigo são: {0}%",
@ -460,6 +487,10 @@
"returntosender": "Devolução ao Remetente", "returntosender": "Devolução ao Remetente",
"returntosender_desc": "O primeiro tiro em um inimigo o envia de volta a sua base", "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": "Filhinho de Papai",
"richboy_desc": "Você recebe uma quantidade aleatória de dinheiro no início da rodada", "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_placed_info": "Fio de armadilha colocado.",
"tripwire_triggered_info": "'{0}' acionou seu fio.", "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": "Wallhack",
"wallhack_desc": "Você pode ver inimigos através das paredes", "wallhack_desc": "Você pode ver inimigos através das paredes",

View file

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

View file

@ -60,6 +60,13 @@
"blastshot": "Patlama Atışı", "blastshot": "Patlama Atışı",
"blastshot_desc": "MP5 ile sağ tıklayarak el bombası fırlat", "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": "Tavşan",
"bunnyhop_desc": "Otomatik BunnyHop yaparsın", "bunnyhop_desc": "Otomatik BunnyHop yaparsın",
@ -137,6 +144,10 @@
"dwarf_desc": "Rastgele karakter boyutu", "dwarf_desc": "Rastgele karakter boyutu",
"dwarf_desc2": "Boyutun: {0}x", "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": "Base Baskını",
"enemyspawn_desc": "[css_useSkill] tuşuna basarak düşman doğma noktasına ışınlan", "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_no_anchor_info": "Kanca atılacak yüzey yok",
"grapple_pulling_info": "ÇEKİLİYOR", "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": "Bombacı",
"grenadier_desc": "El bomban sınırsız", "grenadier_desc": "El bomban sınırsız",
@ -232,6 +246,9 @@
"healingsmoke": "Şifalı Sis", "healingsmoke": "Şifalı Sis",
"healingsmoke_desc": "Sis bomban iyileştirir", "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": "Münzevi",
"hermit_desc": "Birini öldürünce mermin ve canın yenilenir", "hermit_desc": "Birini öldürünce mermin ve canın yenilenir",
@ -360,6 +377,12 @@
"demoneye": "Şeytani Göz", "demoneye": "Şeytani Göz",
"demoneye_desc": "Baktığın tüm düşmana hasar verirsin", "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": "Kabus",
"nightmare_desc": "Seçtiğin rakibe kabus gördür", "nightmare_desc": "Seçtiğin rakibe kabus gördür",
"nightmare_disable_info": "Kabus sona erdi.", "nightmare_disable_info": "Kabus sona erdi.",
@ -423,6 +446,10 @@
"prosthesis": "Protez", "prosthesis": "Protez",
"prosthesis_desc": "Kolların ve bacakların kurşun geçirmez", "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": "İtici",
"push_desc": "Düşmana vurduğunda onu geri itme şansın olur", "push_desc": "Düşmana vurduğunda onu geri itme şansın olur",
"push_desc2": "Geri itme şansın: %{0}", "push_desc2": "Geri itme şansın: %{0}",
@ -460,6 +487,10 @@
"returntosender": "İade", "returntosender": "İade",
"returntosender_desc": "Düşmana ilk vuruşun onu kendi başlangıcına ışınlar", "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": "Zengin Çocuk",
"richboy_desc": "Rastgele miktarda paran olur", "richboy_desc": "Rastgele miktarda paran olur",
@ -549,6 +580,14 @@
"tripwire_placed_info": "Tel gerildi.", "tripwire_placed_info": "Tel gerildi.",
"tripwire_triggered_info": "'{0}' teline takıldı.", "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": "Wallhack",
"wallhack_desc": "Düşmanları duvar arkasından görürsün", "wallhack_desc": "Düşmanları duvar arkasından görürsün",

View file

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