Merge pull request #45 from ByDexterTR/main

v1.2.3.b2 - Part 1
This commit is contained in:
Juzlus 2026-07-31 20:00:38 +02:00 • committed by GitHub
commit 203a0c3986
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
103 changed files with 4440 additions and 1533 deletions

9
.github/en.md vendored
View file

@ -48,7 +48,7 @@ Join the 3v3 test server and try out the jRandomSkills plugin:
Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.html).
## ✨ Current Skills (124)
## ✨ Current Skills (131)
<details>
<summary>The table below lists all available skills in the game, along with their descriptions.</summary>
@ -86,6 +86,8 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Duplicator | Choose a player to copy their skill | - |
| Dwarf | Random character size at the start of the round | (60 - 95)% |
| Enemy Spawn | Click [css_useSkill] to teleport to the enemy spawn | 15 s |
| Expensive Ammo | A chosen enemy loses money with every shot | - |
| Exploding Barrel | Place a barrel that explodes when shot | - |
| Explosive Shot | Random chance to fire an explosive bullet while shooting | (15 - 30)% |
| Falcon Eye | Click [css_useSkill] to activate a bird's-eye view camera| - |
| Fastreload | Click [css_useSkill] to reload the weapon you are currently holding | - |
@ -96,6 +98,7 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Freezing Decoy | Your decoy freezes all nearby players | - |
| Gambler | Select a skill from the list provided | - |
| Ghost | You are completely invisible | - |
| Giant | Enlarges a chosen enemy | (110 - 140)% |
| Glaz | You can see through smoke grenades | - |
| Glitch | Disables the radar for a chosen enemy | - |
| Glue | Your grenades stick to walls | - |
@ -115,8 +118,10 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Jammer | Choose a player to disable their crosshair | - |
| Jester | In jester mode, you cannot get or take any damage. This mode changes every few seconds | (10 - 25) s |
| Legless | Choose a player who cannot jump | - |
| Jump Curse | A chosen enemy jumps whenever their teammates jump | - |
| Jumping Jack | Jumping restores health | - |
| Killer Flash | Anyone fully blinded by your flashbang dies (including you) | - |
| Knockback | Firing while airborne pushes you backwards | - |
| Life Swap | Choose a player to swap health with | - |
| Long Knife | A primary knife attack deals damage regardless of distance | - |
| Long Zeus | Zeus deals damage regardless of distance | - |
@ -126,6 +131,7 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Medic | Click [css_useSkill] to use a healing charge that restores 50 health | 1 s |
| Bomb Miner | Your HE grenade only explode when there is an enemy nearby | - |
| Taxman | Choose a player to swap money with | - |
| Nightmare | Gives a chosen enemy a nightmarish vision | - |
| Ninja | Standing still increases your invisibility by 33%, crouching by 33%, and holding a knife by 33% | - |
| No-Nades | Grenades deal no damage to you | - |
| Focus | No recoil while shooting | - |
@ -172,6 +178,7 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Thorns | Your opponent will receive a portion of the damage that they inflicted on you | - |
| Throwing Knife | Click [css_useSkill] to throw a knife. But watch out for others | - |
| Toxic Smoke | Your smoke grenades deal damage | - |
| Tripwire | String a wire between two walls; enemies touching it show on your radar | - |
| Wallhack | You can see enemies through walls | - |
| Watchmaker | Every grenade throw alters the round time | - |
| Weapon Swap | Click [css_useSkill] to swap weapons with a random enemy | 30 s |

2
.gitignore vendored
View file

@ -31,6 +31,8 @@ project.fragment.lock.json
# Logs, caches, temp
*.log
debug_*.txt
perf_*.txt
*.cache
*.tmp
*.temp

View file

@ -48,7 +48,7 @@ Join the 3v3 test server and try out the jRandomSkills plugin:
Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.html).
## ✨ Current Skills (124)
## ✨ Current Skills (131)
<details>
<summary>The table below lists all available skills in the game, along with their descriptions.</summary>
@ -86,6 +86,8 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Duplicator | Choose a player to copy their skill | - |
| Dwarf | Random character size at the start of the round | (60 - 95)% |
| Enemy Spawn | Click [css_useSkill] to teleport to the enemy spawn | 15 s |
| Expensive Ammo | A chosen enemy loses money with every shot | - |
| Exploding Barrel | Place a barrel that explodes when shot | - |
| Explosive Shot | Random chance to fire an explosive bullet while shooting | (15 - 30)% |
| Falcon Eye | Click [css_useSkill] to activate a bird's-eye view camera| - |
| Fastreload | Click [css_useSkill] to reload the weapon you are currently holding | - |
@ -96,6 +98,7 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Freezing Decoy | Your decoy freezes all nearby players | - |
| Gambler | Select a skill from the list provided | - |
| Ghost | You are completely invisible | - |
| Giant | Enlarges a chosen enemy | (110 - 140)% |
| Glaz | You can see through smoke grenades | - |
| Glitch | Disables the radar for a chosen enemy | - |
| Glue | Your grenades stick to walls | - |
@ -115,8 +118,10 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Jammer | Choose a player to disable their crosshair | - |
| Jester | In jester mode, you cannot get or take any damage. This mode changes every few seconds | (10 - 25) s |
| Legless | Choose a player who cannot jump | - |
| Jump Curse | A chosen enemy jumps whenever their teammates jump | - |
| Jumping Jack | Jumping restores health | - |
| Killer Flash | Anyone fully blinded by your flashbang dies (including you) | - |
| Knockback | Firing while airborne pushes you backwards | - |
| Life Swap | Choose a player to swap health with | - |
| Long Knife | A primary knife attack deals damage regardless of distance | - |
| Long Zeus | Zeus deals damage regardless of distance | - |
@ -126,6 +131,7 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Medic | Click [css_useSkill] to use a healing charge that restores 50 health | 1 s |
| Bomb Miner | Your HE grenade only explode when there is an enemy nearby | - |
| Taxman | Choose a player to swap money with | - |
| Nightmare | Gives a chosen enemy a nightmarish vision | - |
| Ninja | Standing still increases your invisibility by 33%, crouching by 33%, and holding a knife by 33% | - |
| No-Nades | Grenades deal no damage to you | - |
| Focus | No recoil while shooting | - |
@ -172,6 +178,7 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
| Thorns | Your opponent will receive a portion of the damage that they inflicted on you | - |
| Throwing Knife | Click [css_useSkill] to throw a knife. But watch out for others | - |
| Toxic Smoke | Your smoke grenades deal damage | - |
| Tripwire | String a wire between two walls; enemies touching it show on your radar | - |
| Wallhack | You can see enemies through walls | - |
| Watchmaker | Every grenade throw alters the round time | - |
| Weapon Swap | Click [css_useSkill] to swap weapons with a random enemy | 30 s |

View file

@ -31,7 +31,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.2.3.b1";
public override string ModuleVersion => "1.2.3.b2";
public override void Load(bool hotReload)
{
@ -61,6 +61,10 @@ namespace src
{
src.player.PerfLog.Info("===== PLUGIN UNLOAD (clean shutdown/reload) =====");
Debug.WriteToDebug("===== PLUGIN UNLOAD (clean shutdown/reload) =====");
Event.Unload();
Debug.Unload();
base.Unload(hotReload);
}
@ -88,6 +92,26 @@ namespace src
Debug.WriteToDebug($"Loaded: {skill.Skill}");
}
private static bool TryClaimCurseTarget(object[]? param)
{
if (param == null || param.Length < 2) return true;
if (param[0] is not CCSPlayerController curser || !curser.IsValid) return true;
if (param[1] is not string[] commands || commands.Length < 1) return true;
if (!uint.TryParse(commands[0], out uint victimIndex)) return true;
var victim = Utilities.GetPlayerFromIndex((int)victimIndex);
if (victim == null || !victim.IsValid) return true;
if (SkillUtils.TryClaimCurse(curser.Index, victimIndex)) return true;
if (!SkillUtils.AnyCurseCapacity(curser))
return SkillUtils.TryClaimCurse(curser.Index, victimIndex, true);
var curserEvent = PlayerManager.GetPlayerFromEvent(curser);
curserEvent?.PrintToChat($" {ChatColors.Red}{curserEvent.GetTranslation("curse_limit_info", victim.PlayerName)}");
return false;
}
private static readonly ConcurrentDictionary<(string Skill, string Method), MethodInfo?> _skillMethodCache = new();
internal object? SkillAction(string skill, string methodName, object[]? param = null)
@ -101,6 +125,15 @@ namespace src
SkillsUsedThisMap.TryAdd(skill, 0);
}
if (SkillUtils.CurseLimitEnabled && SkillUtils.IsCurseSkill(skill))
{
if (methodName == "DisableSkill" && param?.Length > 0 && param[0] is CCSPlayerController curser && curser.IsValid)
SkillUtils.ReleaseCurse(curser.Index);
if (methodName == "TypeSkill" && !TryClaimCurseTarget(param))
return null;
}
var method = _skillMethodCache.GetOrAdd((skill, methodName), key =>
{
string className = $"src.player.skills.{key.Skill}";
@ -333,9 +366,11 @@ namespace src
public bool IsDrawing { get; set; }
public DateTime SkillHudExpired { get; set; }
public DateTime SkillDescriptionHudExpired { get; set; }
public DateTime HudSuppressedUntil { get; set; }
public string? PrintHTML { get; set; }
public bool DisplayHUD { get; set; }
public bool SkillUsed = false;
public bool? HudOnDeathBlocked { get; set; }
}
public class jSkill_SkillInfo(Skills skill, string color, bool display)

View file

@ -129,6 +129,16 @@
"enemyspawn": "Feindes-Spawn",
"enemyspawn_desc": "Klicke auf [css_useSkill], um zum Feindes-Spawn zu teleportieren",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Explosiver Schuss",
"explosiveshot_desc": "Zufällige Chance, beim Schießen eine explosive Kugel abzufeuern",
"explosiveshot_desc2": "Deine Chance, eine explosive Kugel abzufeuern: {0} %",
@ -165,6 +175,14 @@
"ghost": "Geist",
"ghost_desc": "Du bist vollständig unsichtbar",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Glaz",
"glaz_desc": "Du kannst durch Rauchgranaten hindurchsehen",
@ -241,12 +259,22 @@
"jumpban_player_info": "Der Spieler ‚{0}‘ kann nicht mehr springen.",
"jumpban_select_info": "Spieler vom Springen ausschließen:",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Jumping Jack",
"jumpingjack_desc": "Durch Springen wird die Gesundheit wiederhergestellt",
"killerflash": "Killer Flash",
"killerflash_desc": "Jeder, der durch deine Blendgranate vollständig geblendet wird, stirbt (einschließlich dir selbst)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Lebensaustausch",
"lifeswap_desc": "Wähle einen Spieler aus, mit dem du deine Gesundheit austauschen möchtest",
"lifeswap_enemy_info": "Jemand hat sich deine Gesundheit ausgeliehen",
@ -288,6 +316,13 @@
"deathbomb_desc": "Du explodierst beim Tod und tötest Spieler in deiner Nähe",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Wenn du stillstehst, erhöht sich deine Unsichtbarkeit um 33 %, wenn du dich duckst um 33 % und wenn du ein Messer hältst um 33 %",
@ -450,6 +485,12 @@
"toxicsmoke": "Giftiger Rauch",
"toxicsmoke_desc": "Deine Rauchgranaten verursachen Schaden",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen",
@ -491,6 +532,7 @@
"duplicate_player": "Mehr als ein Spieler mit demselben Namen gefunden.",
"selectplayerskill_command": "Gib /t ein",
"selectplayerskill_incorrect_enemy_index": "Es wurden keine Spieler gefunden, die ausgewählt werden können.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Keine solche CHATCOLORS.REDskill gefunden",
"player_not_found_setskill": "Kein solcher CHATCOLORS.REDplayer gefunden",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Enemy Spawn",
"enemyspawn_desc": "Click [css_useSkill] to teleport to the enemy spawn",
"expensiveammo": "Expensive Ammo",
"expensiveammo_desc": "A chosen enemy loses money with every shot",
"expensiveammo_disable_info": "Your bullets are free again.",
"expensiveammo_enemy_info": "Every shot you fire now costs you {0}$.",
"expensiveammo_player_info": "Player '{0}' now pays for every bullet.",
"expensiveammo_select_info": "Select player whose ammo becomes expensive:",
"explodingbarrel": "Exploding Barrel",
"explodingbarrel_desc": "Click [css_useSkill] to place a barrel that explodes when shot",
"explosiveshot": "Explosive Shot",
"explosiveshot_desc": "Random chance to fire an explosive bullet while shooting",
"explosiveshot_desc2": "Your chance to fire an explosive bullet: {0}%",
@ -165,6 +175,14 @@
"ghost": "Ghost",
"ghost_desc": "You are completely invisible",
"giant": "Giant",
"giant_desc": "Enlarge an enemy of your choice",
"giant_desc2": "Enlarge an enemy of your choice to {0}x",
"giant_disable_info": "You are back to your normal size.",
"giant_enemy_info": "You have been enlarged to {0}x.",
"giant_player_info": "You enlarged player '{0}' to {1}x.",
"giant_select_info": "Enlarge player:",
"glaz": "Glaz",
"glaz_desc": "You can see through smoke grenades",
@ -241,12 +259,22 @@
"jumpban_player_info": "Player '{0}' can no longer jump.",
"jumpban_select_info": "Ban player from jumping:",
"jumpcurse": "Jump Curse",
"jumpcurse_desc": "A chosen enemy jumps whenever their teammates jump",
"jumpcurse_disable_info": "Your legs are your own again.",
"jumpcurse_enemy_info": "You now jump whenever your teammates jump.",
"jumpcurse_player_info": "Player '{0}' now jumps with their team.",
"jumpcurse_select_info": "Select player for the jump curse:",
"jumpingjack": "Jumping Jack",
"jumpingjack_desc": "Jumping restores health",
"killerflash": "Killer Flash",
"killerflash_desc": "Anyone fully blinded by your flashbang dies (including you)",
"knockback": "Knockback",
"knockback_desc": "Firing while airborne pushes you backwards",
"lifeswap": "Life Swap",
"lifeswap_desc": "Choose a player to swap health with",
"lifeswap_enemy_info": "Someone borrowed your health.",
@ -288,6 +316,13 @@
"deathbomb_desc": "You explode upon death, killing nearby players",
"deathbomb_explosion": "Kaboom!",
"nightmare": "Nightmare",
"nightmare_desc": "Give a chosen enemy a nightmarish vision",
"nightmare_disable_info": "The nightmare has faded.",
"nightmare_enemy_info": "A nightmare clouds your vision.",
"nightmare_player_info": "You cast a nightmare on player '{0}'.",
"nightmare_select_info": "Select player for the nightmare:",
"ninja": "Ninja",
"ninja_desc": "Standing still increases your invisibility by 33%, crouching by 33%, and holding a knife by 33%",
@ -450,6 +485,12 @@
"toxicsmoke": "Toxic Smoke",
"toxicsmoke_desc": "Your smoke grenades deal damage",
"tripwire": "Tripwire",
"tripwire_desc": "Click [css_useSkill] to string a wire between two walls. Enemies touching it show on your radar",
"tripwire_no_wall_info": "No walls close enough on both sides.",
"tripwire_placed_info": "Tripwire placed.",
"tripwire_triggered_info": "'{0}' tripped your wire.",
"wallhack": "Wallhack",
"wallhack_desc": "You can see enemies through walls",
@ -491,6 +532,7 @@
"duplicate_player": "More than one player found with the same name.",
"selectplayerskill_command": "Type /t",
"selectplayerskill_incorrect_enemy_index": "No players were found to select.",
"curse_limit_info": "The curse limit for {0} has been reached",
"skill_not_found_setskill": "No such CHATCOLORS.REDskill found",
"player_not_found_setskill": "No such CHATCOLORS.REDplayer found",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Spawn Ennemi",
"enemyspawn_desc": "Appuyez sur [css_useSkill] pour vous téléporter au spawn ennemi",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Tir Explosif",
"explosiveshot_desc": "Chance aléatoire de tirer une balle explosive en tirant",
"explosiveshot_desc2": "Votre chance de tirer une balle explosive est : {0}%",
@ -165,6 +175,14 @@
"ghost": "Fantôme",
"ghost_desc": "Vous êtes complètement invisible",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Glaz",
"glaz_desc": "Vous pouvez voir à travers les fumigènes",
@ -241,12 +259,21 @@
"jumpban_player_info": "Le joueur « {0} » ne peut plus sauter.",
"jumpban_select_info": "Interdire de sauter au joueur :",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Sauteur",
"jumpingjack_desc": "Sauter vous rend de la vie",
"killerflash": "Flash Mortel",
"killerflash_desc": "Toute personne totalement aveuglée par votre flashbang meurt (y compris vous)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Échange de Vie",
"lifeswap_desc": "Choisissez un joueur avec qui échanger votre santé",
"lifeswap_enemy_info": "Quelqu’un a emprunté votre santé.",
@ -288,6 +315,13 @@
"deathbomb_desc": "Vous explosez à votre mort, tuant les joueurs proches",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Rester immobile augmente votre invisibilité de 33%, s’accroupir de 33%, et tenir un couteau de 33%",
@ -450,6 +484,12 @@
"toxicsmoke": "Fumée Toxique",
"toxicsmoke_desc": "Vos grenades fumigènes infligent des dégâts",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Vous pouvez voir les ennemis à travers les murs",
@ -491,6 +531,7 @@
"duplicate_player": "Plus d’un joueur trouvé avec le même nom.",
"selectplayerskill_command": "Tapez /t",
"selectplayerskill_incorrect_enemy_index": "Aucun joueur trouvé à sélectionner.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Aucune CHATCOLORS.REDcompétence trouvée",
"player_not_found_setskill": "Aucun CHATCOLORS.REDjoueur trouvé",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Resp Wroga",
"enemyspawn_desc": "Kliknij [css_useSkill], aby teleportować się na resp wroga",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Strzał Wybuchowy",
"explosiveshot_desc": "Losowa szansa wystrzelenia pocisku wybuchowego podczas strzelania",
"explosiveshot_desc2": "Twoja szansa na wystrzelenie pocisku wybuchowego: {0}%",
@ -165,6 +175,14 @@
"ghost": "Duszek",
"ghost_desc": "Jesteś całkowicie niewidzialny",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Glaz",
"glaz_desc": "Nie widzisz granatów dymnych",
@ -241,12 +259,21 @@
"jumpban_player_info": "Gracz '{0}' nie może teraz skakać.",
"jumpban_select_info": "Zablokuj skok dla gracza:",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Pajacyk",
"jumpingjack_desc": "Skakanie przywraca zdrowie",
"killerflash": "Zabójczy Flash",
"killerflash_desc": "Każdy całkowicie oślepiony twoim granatem umiera (również ty)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Zamiana Żyć",
"lifeswap_desc": "Wybierasz gracza, z którym chcesz wymienić się zdrowiem",
"lifeswap_enemy_info": "Ktoś pożyczył sobie twoje zdrowie.",
@ -288,6 +315,13 @@
"deathbomb_desc": "Po śmierci eksplodujesz i zabijasz graczy znajdujących się w zasięgu",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Stojąc nieruchomo zwiększasz swoją niewidzialność o 33%, kucając o 33%, a trzymając nóż o 33%",
@ -450,6 +484,12 @@
"toxicsmoke": "Toksyczny Dym",
"toxicsmoke_desc": "Twoje granaty dymny zadają obrażenia",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Widzisz wrogów przez ściany",
@ -491,6 +531,7 @@
"duplicate_player": "Znaleziono więcej niż jednego gracza o tej samej nazwie.",
"selectplayerskill_command": "Wpisz /t",
"selectplayerskill_incorrect_enemy_index": "Nie znaleziono graczy do wyboru.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Nie znaleziono takiej CHATCOLORS.REDsupermocy",
"player_not_found_setskill": "Nie znaleziono takiego CHATCOLORS.REDgracza",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Spawn Inimigo",
"enemyspawn_desc": "Clique em [css_useSkill] para se teletransportar para a base inimiga",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Tiro Explosivo",
"explosiveshot_desc": "Chance aleatória de disparar uma bala explosiva enquanto atira",
"explosiveshot_desc2": "Sua chance de disparar uma bala explosiva: {0}%",
@ -165,6 +175,14 @@
"ghost": "Fantasma",
"ghost_desc": "Você é completamente invisível",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Smoke Transparente",
"glaz_desc": "Smokes tornam-se transparentes para você",
@ -241,12 +259,22 @@
"jumpban_player_info": "O jogador '{0}' não pode mais pular.",
"jumpban_select_info": "Banir pulo do jogador:",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Pula-Pula",
"jumpingjack_desc": "Pular restaura vida",
"killerflash": "Flash Mortal",
"killerflash_desc": "Qualquer pessoa cega pela sua flash morre (incluindo você)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Troca de Vida",
"lifeswap_desc": "Escolha um jogador para trocar de vida",
"lifeswap_enemy_info": "Alguém pegou sua vida emprestada.",
@ -288,6 +316,13 @@
"deathbomb_desc": "Você explode ao morrer, matando os jogadores próximos",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Ficar parado aumenta sua invisibilidade em 33%, agachar em 33%, e segurar uma faca em 33%",
@ -450,6 +485,12 @@
"toxicsmoke": "Smoke Tóxica",
"toxicsmoke_desc": "Suas smokes causam dano",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Você pode ver inimigos através das paredes",
@ -491,6 +532,7 @@
"duplicate_player": "Mais de um jogador encontrado com o mesmo nome.",
"selectplayerskill_command": "Digite /t",
"selectplayerskill_incorrect_enemy_index": "Não foram encontrados jogadores para selecionar.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Nenhuma habilidade CHATCOLORS.RED encontrada",
"player_not_found_setskill": "Nenhum jogador CHATCOLORS.RED encontrado",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Спавн врагов",
"enemyspawn_desc": "Нажмите [css_useSkill], чтобы телепортироваться на спавн врагов",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Взрывной выстрел",
"explosiveshot_desc": "Случайный шанс выстрелить взрывной пулей при стрельбе",
"explosiveshot_desc2": "Ваш шанс выстрелить взрывной пулей: {0}%",
@ -165,6 +175,14 @@
"ghost": "Призрак",
"ghost_desc": "Вы полностью невидимы",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Глаз",
"glaz_desc": "Вы видите сквозь дымовые гранаты",
@ -241,12 +259,22 @@
"jumpban_player_info": "Игрок '{0}' больше не может прыгать.",
"jumpban_select_info": "Запретить прыжки игроку:",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Прыгающий Джек",
"jumpingjack_desc": "Прыжки восстанавливают здоровье",
"killerflash": "Смертельная флешка",
"killerflash_desc": "Любой, полностью ослеплённый вашей флешкой, погибает (включая вас)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Обмен жизнями",
"lifeswap_desc": "Выберите игрока для обмена здоровьем",
"lifeswap_enemy_info": "Кто-то занял ваше здоровье.",
@ -288,6 +316,13 @@
"deathbomb_desc": "Вы взрываетесь при смерти, убивая ближайших игроков",
"deathbomb_explosion": "АЛЛАХУ АКБАР!!!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ниндзя",
"ninja_desc": "Стоя на месте, ваша невидимость увеличивается на 33%, в приседе на 33%, с ножом на 33%",
@ -450,6 +485,12 @@
"toxicsmoke": "Токсичный дым",
"toxicsmoke_desc": "Ваши дымовые гранаты наносят урон",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Воллхак",
"wallhack_desc": "Вы видите врагов сквозь стены",
@ -491,6 +532,7 @@
"duplicate_player": "Найдено несколько игроков с таким именем.",
"selectplayerskill_command": "Введите /t",
"selectplayerskill_incorrect_enemy_index": "Игроки для выбора не найдены.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Навык не найден",
"player_not_found_setskill": "Игрок не найден",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Base Baskını",
"enemyspawn_desc": "[css_useSkill] tuşuna basarak düşman doğma noktasına ışınlan",
"expensiveammo": "Pahalı Mermi",
"expensiveammo_desc": "Seçtiğin rakip her ateş ettiğinde para kaybeder",
"expensiveammo_disable_info": "Mermilerin yeniden bedava.",
"expensiveammo_enemy_info": "Artık her atışın sana {0}$ mal oluyor.",
"expensiveammo_player_info": "'{0}' artık her mermisinin parasını ödüyor.",
"expensiveammo_select_info": "Mermisi pahalanacak oyuncu:",
"explodingbarrel": "Patlayan Varil",
"explodingbarrel_desc": "[css_useSkill] ile vurulunca patlayan bir varil koyarsın",
"explosiveshot": "Patlayıcı Mermi",
"explosiveshot_desc": "Ateş ederken rastgele mermilerin patlayabilir",
"explosiveshot_desc2": "Patlayan mermi şansı: %{0}",
@ -159,12 +169,20 @@
"gambler": "Kumarbaz",
"gambler_desc": "Sunulan listeden bir yetenek seç.",
"gambler_select_info": "Yeteneğini seç:",
"gambler_more": "#00FF00|${0} karşılığında yeni yetenekler",
"gambler_more": "#00FF00|${0} yenile",
"gambler_no_money": "Yeterli para yok.",
"ghost": "Hayalet",
"ghost_desc": "Tamamen görünmezsin",
"giant": "Dev",
"giant_desc": "Seçtiğin bir rakibi büyütür",
"giant_desc2": "Seçtiğin bir rakibi {0}x büyütür",
"giant_disable_info": "Normal boyutuna geri döndün.",
"giant_enemy_info": "{0}x boyutuna büyütüldün.",
"giant_player_info": "'{0}' oyuncusunu {1}x büyüttün.",
"giant_select_info": "Büyütülecek oyuncu:",
"glaz": "Glaz",
"glaz_desc": "Sis bombalarının içini görebilirsin",
@ -190,7 +208,7 @@
"healingsmoke_desc": "Sis bombaların iyileştirir",
"hermit": "Münzevi",
"hermit_desc": "Öldüğünde mernin ve canının biraz yenilenir",
"hermit_desc": "Öldürdüğünde mermi ve canın yenilenir",
"holyhandgrenade": "Kutsal El Bombası",
"holyhandgrenade_desc": "El bomban iki kat hasara ve menzile sahip",
@ -241,12 +259,22 @@
"jumpban_player_info": "'{0}' artık zıplayamaz.",
"jumpban_select_info": "Zıplaması yasaklanacak oyuncu:",
"jumpcurse": "Zıplama Laneti",
"jumpcurse_desc": "Seçtiğin rakip, takım arkadaşları her zıpladığında zıplar",
"jumpcurse_disable_info": "Bacakların yeniden senin.",
"jumpcurse_enemy_info": "Artık takım arkadaşların zıpladıkça sen de zıplıyorsun.",
"jumpcurse_player_info": "'{0}' artık takımıyla birlikte zıplıyor.",
"jumpcurse_select_info": "Zıplama laneti verilecek oyuncu:",
"jumpingjack": "Zıpzıp Jack",
"jumpingjack_desc": "Zıplamak canını yeniler",
"killerflash": "Ölümcül Flaş",
"killerflash_desc": "Flaşına tamamen kör olan herkes ölür (sen dahil)",
"knockback": "Savurma",
"knockback_desc": "Havadayken ateş etmek seni geriye savurur",
"lifeswap": "Can Takası",
"lifeswap_desc": "Can değerlerini takas etmek için bir oyuncu seç",
"lifeswap_enemy_info": "Birisi canını çaldı",
@ -288,6 +316,13 @@
"deathbomb_desc": "Öldüğünde patlayarak etraftaki oyuncuları öldürürsün",
"deathbomb_explosion": "BOOM!!!",
"nightmare": "Kabus",
"nightmare_desc": "Seçtiğin bir rakibe kabus görüntüsü verir",
"nightmare_disable_info": "Kabus sona erdi.",
"nightmare_enemy_info": "Gözlerini bir kabus bürüdü.",
"nightmare_player_info": "'{0}' oyuncusuna kabus gördürdün.",
"nightmare_select_info": "Kabus görecek oyuncu:",
"ninja": "Ninja",
"ninja_desc": "Görünmezliğini sabit durmak %33, eğilmek %33 ve bıçak tutmak %33 artırır",
@ -408,7 +443,7 @@
"silent_desc": "Ayak seslerini ve zıplamalarını DİĞER oyuncular duyamaz",
"smoker": "Tiryaki",
"smoker_desc": "Sis bombalarınız asla bitmez",
"smoker_desc": "Attığın Sis asla bitmez",
"sniperelite": "Keskin Nişancı",
"sniperelite_customname": "Keskin Nişancı",
@ -450,6 +485,12 @@
"toxicsmoke": "Zehirli Sis",
"toxicsmoke_desc": "Sis bombaların hasar verir",
"tripwire": "Tel Tuzağı",
"tripwire_desc": "[css_useSkill] ile iki duvar arasına tel ger. Tele değen düşman radarında görünür",
"tripwire_no_wall_info": "İki yanında da yeterince yakın duvar yok.",
"tripwire_placed_info": "Tel gerildi.",
"tripwire_triggered_info": "'{0}' teline takıldı.",
"wallhack": "Wallhack",
"wallhack_desc": "Düşmanları duvarların arkasından görebilirsin",
@ -491,6 +532,7 @@
"duplicate_player": "Aynı isimde birden fazla oyuncu bulundu.",
"selectplayerskill_command": "Sohbete /t yazın",
"selectplayerskill_incorrect_enemy_index": "Seçilecek oyuncu yok.",
"curse_limit_info": "'{0}' üzerindeki lanet sınırı doldu, başka bir oyuncu seç",
"skill_not_found_setskill": "Böyle bir yetenek yok",
"player_not_found_setskill": "Böyle bir oyuncu yok",

View file

@ -4,6 +4,7 @@ using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using static CounterStrikeSharp.API.Core.Listeners;
using static src.jRandomSkills;
@ -103,9 +104,43 @@ namespace src.player
return HookResult.Continue;
});
Instance.RegisterEventHandler<EventPlayerHurt>((@event, info) =>
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return HookResult.Continue;
if (!lastNativeHitGroup.TryRemove(victim.Index, out var native)) return HookResult.Continue;
if (native.HitGroup == @event.Hitgroup) return HookResult.Continue;
WriteToDebug($"[HITGROUP] mismatch on {victim.PlayerName}: native={(HitGroup_t)native.HitGroup} event={(HitGroup_t)@event.Hitgroup} " +
$"weapon={@event.Weapon} rawDmg={native.Damage:0.#} appliedDmg={@event.DmgHealth}");
return HookResult.Continue;
});
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre);
}
private static readonly ConcurrentDictionary<uint, (int HitGroup, float Damage)> lastNativeHitGroup = [];
public static void Unload()
{
try { VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); }
catch { }
lock (_writeLock) { _writer?.Dispose(); _writer = null; }
}
private static string DescribeIdentitySplit(CCSPlayerController victim)
{
var routed = PlayerManager.GetPlayerEvent(victim);
uint routedIndex = routed?.Index ?? victim.Index;
if (routedIndex == victim.Index) return string.Empty;
return $" SPLIT(idx {victim.Index}->{routedIndex}, skill {PlayerManager.GetPlayerByIndex(victim.Index)?.Skill}" +
$"->{PlayerManager.GetPlayerByIndex(routedIndex)?.Skill}, controllingBot={victim.ControllingBot})";
}
private static HookResult OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
@ -129,7 +164,13 @@ namespace src.player
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (playerInfo == null) return HookResult.Continue;
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} took damage from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}.");
var nativeHitGroup = SkillUtils.GetHitGroup(param2);
lastNativeHitGroup[victim.Index] = ((int)nativeHitGroup, param2.Damage);
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} took damage from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}. " +
$"[dmg={param2.Damage:0.#} hp={victimPawn.Health}/{victimPawn.MaxHealth} armor={victimPawn.ArmorValue} hitgroup={nativeHitGroup} " +
$"takes={victimPawn.TakesDamage} vskill={PlayerManager.GetPlayerByIndex(victim.Index)?.Skill} askill={playerInfo.Skill}" +
$"{DescribeIdentitySplit(victim)}]");
return HookResult.Continue;
}

View file

@ -0,0 +1,249 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
using RayTraceAPI;
using src.player.skills;
using src.utils;
using System.Collections.Concurrent;
using static CounterStrikeSharp.API.Core.Listeners;
using static src.jRandomSkills;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace src.player
{
public static partial class Event
{
private static HookResult BombBeginplant(EventBombBeginplant @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("BombBeginplant", @event);
return HookResult.Continue;
}
}
private static HookResult BombAbortplant(EventBombAbortplant @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("BombAbortplant", @event);
return HookResult.Continue;
}
}
private static HookResult BombPlanted(EventBombPlanted @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("BombPlanted", @event);
return HookResult.Continue;
}
}
private static HookResult BombBegindefuse(EventBombBegindefuse @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("BombBegindefuse", @event);
return HookResult.Continue;
}
}
private static HookResult DecoyStarted(EventDecoyStarted @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("DecoyStarted", @event);
return HookResult.Continue;
}
}
private static HookResult DecoyDetonate(EventDecoyDetonate @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("DecoyDetonate", @event);
return HookResult.Continue;
}
}
private static HookResult SmokegrenadeDetonate(EventSmokegrenadeDetonate @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("SmokegrenadeDetonate", @event);
return HookResult.Continue;
}
}
private static HookResult SmokegrenadeExpired(EventSmokegrenadeExpired @event, GameEventInfo info)
{
lock (setLock)
{
DispatchToActiveSkills("SmokegrenadeExpired", @event);
return HookResult.Continue;
}
}
private static HookResult OnTakeDamage(DynamicHook h)
{
lock (setLock)
{
DispatchOnTakeDamage(h);
if (Fortnite.skillInThisRound == true &&
!Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite))
Instance.SkillAction("Fortnite", "OnTakeDamage", [h]);
return HookResult.Continue;
}
}
private static HookResult OnTriggerEnter(DynamicHook hook)
{
lock (setLock)
{
CBaseTrigger trigger = hook.GetParam<CBaseTrigger>(0);
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
DispatchToActiveSkills("OnTriggerEnter", trigger, entity);
return HookResult.Continue;
}
}
private static HookResult OnTriggerExit(DynamicHook hook)
{
lock (setLock)
{
CBaseTrigger trigger = hook.GetParam<CBaseTrigger>(0);
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
DispatchToActiveSkills("OnTriggerExit", trigger, entity);
return HookResult.Continue;
}
}
private static HookResult OnWeaponCanAcquire(DynamicHook hook)
{
lock (setLock)
{
CCSPlayer_ItemServices itemServices = hook.GetParam<CCSPlayer_ItemServices>(0);
if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue;
CEconItemView econItem = hook.GetParam<CEconItemView>(1);
if (econItem == null) return HookResult.Continue;
CBasePlayerPawn pawn = itemServices.Pawn.Value;
if (pawn == null || !pawn.IsValid || pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return HookResult.Continue;
CCSPlayerController player = pawn.Controller.Value.As<CCSPlayerController>();
if (player == null || !player.IsValid) return HookResult.Continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player.Index);
if (playerInfo == null) return HookResult.Continue;
CCSWeaponBaseVData vdata = VirtualFunctions.GetCSWeaponDataFromKeyFunc.Invoke(-1, econItem.ItemDefinitionIndex.ToString());
if (vdata == null || vdata.Handle == IntPtr.Zero) return HookResult.Continue;
var activeSkills = Instance.SkillPlayer
.Where(p => !p.IsDrawing)
.Select(p => p.Skill.ToString())
.Distinct();
bool block = false;
foreach (string skillName in activeSkills)
{
bool? result = (bool?)Instance.SkillAction(skillName, "OnWeaponCanAcquire", [hook, player, econItem, vdata]);
if (result == true)
{
block = true;
break;
}
}
return block ? HookResult.Handled : HookResult.Continue;
}
}
private static HookResult WeaponDrop(DynamicHook hook)
{
lock (setLock)
{
CCSPlayerController player = hook.GetParam<CCSPlayerController>(0);
if (player == null || !player.IsValid)
return HookResult.Continue;
var activeSkills = Instance.SkillPlayer
.Where(p => !p.IsDrawing)
.Select(p => p.Skill.ToString())
.Distinct();
bool block = false;
foreach (string skillName in activeSkills)
{
bool? result = (bool?)Instance.SkillAction(skillName, "WeaponDrop", [hook, player]);
if (result == true)
{
block = true;
break;
}
}
return block ? HookResult.Handled : HookResult.Continue;
}
}
private static void EntitySpawned(CEntityInstance entity)
{
lock (setLock)
{
DispatchToActiveSkills("OnEntitySpawned", entity);
}
}
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
{
long perfStart = PerfLog.Start();
lock (setLock)
{
try
{
// Keep dying entities out of snapshots until the engine processes the kill.
var dying = EntityManager.GetRecentlyDestroyedSnapshot();
if (dying.Count > 0)
{
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid) continue;
foreach (var entityIndex in dying)
if (info.TransmitEntities.Contains(entityIndex))
info.TransmitEntities.Remove(entityIndex);
}
}
}
catch (Exception ex)
{
Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}");
}
DispatchToActiveSkills("CheckTransmit", infoList);
}
PerfLog.Sample("CheckTransmit", perfStart);
}
public static void EnableTransmit()
{
if (!isTransmitRegistered)
{
Instance?.RegisterListener<CheckTransmit>(CheckTransmit);
isTransmitRegistered = true;
}
}
}
}

View file

@ -19,9 +19,11 @@ public interface ISkill
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList _) { }
public static void NewRound() { }
public static void RoundEnd() { }
public static void PlayerMakeSound(UserMessage _) { }
public static void PlayerBlind(EventPlayerBlind _) { }
public static void PlayerHurt(EventPlayerHurt _) { }
public static bool PlayerHurtPre(EventPlayerHurt _) { return false; }
public static void PlayerDeath(EventPlayerDeath _) { }
public static void PlayerJump(EventPlayerJump _) { }
public static void SwitchTeam(EventSwitchTeam _, GameEventInfo __) { }
@ -88,6 +90,8 @@ public enum Skills
Duplicator,
Dwarf,
EnemySpawn,
ExpensiveAmmo,
ExplodingBarrel,
ExplosiveShot,
FalconEye,
FastReload,
@ -98,6 +102,7 @@ public enum Skills
FrozenDecoy,
Gambler,
Ghost,
Giant,
Glaz,
Glitch,
Glue,
@ -117,8 +122,10 @@ public enum Skills
Jammer,
Jester,
JumpBan,
JumpCurse,
JumpingJack,
KillerFlash,
Knockback,
LifeSwap,
LongKnife,
LongZeus,
@ -128,6 +135,7 @@ public enum Skills
Medic,
Miner,
MoneySwap,
Nightmare,
Ninja,
NoNades,
NoRecoil,
@ -174,6 +182,7 @@ public enum Skills
Thorns,
ThrowingKnife,
ToxicSmoke,
Tripwire,
Wallhack,
Watchmaker,
WeaponsSwap,

File diff suppressed because it is too large Load diff

View file

@ -27,10 +27,11 @@ namespace src.player
long perfStart = PerfLog.Start();
// Shared per-tick controller snapshot: the skill OnTick loop already scans the
// player list this frame, so reuse that native scan instead of running a second one.
var now = DateTime.Now;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (player != null && player.IsValid)
UpdatePlayerHud(player);
UpdatePlayerHud(player, now);
}
PerfLog.Sample("OnTick(hud)", perfStart);
});
@ -70,7 +71,7 @@ namespace src.player
Instance.GameRules.GameRestart = Instance.GameRules.RestartRoundTime < Server.CurrentTime;
}
private static void UpdatePlayerHud(CCSPlayerController player)
private static void UpdatePlayerHud(CCSPlayerController player, DateTime now)
{
if (player == null || !player.IsValid || player.IsBot) return;
@ -81,9 +82,11 @@ namespace src.player
var skillPlayer = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index);
if (skillPlayer == null || !skillPlayer.DisplayHUD) return;
var now = DateTime.Now;
if (skillPlayer.HudSuppressedUntil > now) return;
if (player.PawnIsAlive && skillPlayer.SkillHudExpired < now) return;
if (player.PawnIsAlive && skillPlayer.SkillHudExpired < now && string.IsNullOrEmpty(skillPlayer.PrintHTML)) return;
if (SkillUtils.HasMenu(player)) return;
string infoLine = string.Empty;
string skillLine = string.Empty;
@ -135,17 +138,19 @@ namespace src.player
}
else
{
if ((player.Team is CsTeam.Spectator or CsTeam.None && Config.LoadedConfig.DisableSpectateHUD)
|| AdminManager.PlayerHasPermissions(player, Config.LoadedConfig.DisableHUDOnDeathPermission))
if (player.Team is CsTeam.Spectator or CsTeam.None && Config.LoadedConfig.DisableSpectateHUD)
return;
skillPlayer.HudOnDeathBlocked ??= AdminManager.PlayerHasPermissions(player, Config.LoadedConfig.DisableHUDOnDeathPermission);
if (skillPlayer.HudOnDeathBlocked == true) return;
var pawn = player.Pawn.Value;
if (pawn?.ObserverServices == null) return;
var observerTarget = pawn.ObserverServices.ObserverTarget?.Value;
if (observerTarget == null || !observerTarget.IsValid) return;
var observedPlayer = Utilities.GetPlayers().FirstOrDefault(p =>
var observedPlayer = PlayerManager.GetTickPlayers().FirstOrDefault(p =>
p != null && p.IsValid && p.Pawn?.Value?.Handle == observerTarget.Handle);
if (observedPlayer == null) return;
@ -185,7 +190,6 @@ namespace src.player
}
if (string.IsNullOrEmpty(skillLine)) return;
if (SkillUtils.HasMenu(player)) return;
Event.UpdateSkillHUD(player, infoLine, skillLine, remainingLine, isDescription);
}

View file

@ -0,0 +1,692 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
using RayTraceAPI;
using src.player.skills;
using src.utils;
using System.Collections.Concurrent;
using static CounterStrikeSharp.API.Core.Listeners;
using static src.jRandomSkills;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace src.player
{
public static partial class Event
{
private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List<jSkill_SkillInfo> candidates, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
{
if (candidates == null || candidates.Count == 0) return noneSkill;
bool ignoreMax = gameMode == Config.GameModes.SameSkills || gameMode == Config.GameModes.TeamSkills;
const int attempts = 6;
for (int attempt = 0; attempt < attempts; attempt++)
{
var (roll, rolled) = RarityManager.RollRarity();
var filtered = candidates.Where(s =>
{
if (s == null) return false;
var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString());
if (def == null) return false;
if (!string.Equals(def.Rarity ?? string.Empty, rolled.ToString(), StringComparison.OrdinalIgnoreCase))
return false;
if (!ignoreMax && def.MaxPerServer >= 0)
{
int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0;
if (current >= def.MaxPerServer) return false;
}
return true;
}).ToList();
if (filtered.Count > 0)
return filtered[Random.Shared.Next(filtered.Count)];
}
var fallback = candidates.Where(s =>
{
var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString());
if (def == null) return true;
if (ignoreMax) return true;
if (def.MaxPerServer < 0) return true;
int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0;
return current < def.MaxPerServer;
}).ToList();
if (fallback.Count > 0)
return fallback[Random.Shared.Next(fallback.Count)];
return candidates[Random.Shared.Next(candidates.Count)];
}
private static HookResult RoundStart(EventRoundStart @event, GameEventInfo info)
{
lock (setLock)
{
bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true;
isTransmitRegistered = false;
SkillUtils.ClearKillCredits();
SkillUtils.ClearCurses();
Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist))
{
var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index);
if (skillPlayer == null) continue;
skillPlayer.IsDrawing = !isWarmup;
skillPlayer.PrintHTML = null;
}
Instance.RemoveListener<CheckTransmit>(CheckTransmit);
int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue<Int32>() ?? 0;
freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0));
setSkillTimer?.Kill();
if (isWarmup)
{
setSkillTimer = Instance?.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
return HookResult.Continue;
}
float timeToDraw = (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f;
setSkillTimer = Instance?.AddTimer(timeToDraw, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
return HookResult.Continue;
}
}
private static void DisableAll()
{
long perfStart = PerfLog.Start();
DisableAllCore();
PerfLog.End("DisableAll total", perfStart, 2.0);
}
private static void DisableAllCore()
{
lock (setLock)
{
// Re-register CheckTransmit so the dying-entity filter covers the kills below.
EnableTransmit();
Fortnite.skillInThisRound = false;
EntityManager.DestroyAllTracked();
foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid))
{
if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) continue;
ActiveSkillsThisRound.TryAdd(playerInfo.Skill.ToString(), 0);
SkillsUsedThisMap.TryAdd(playerInfo.Skill.ToString(), 0);
if (playerInfo.SpecialSkill != noneSkill.Skill)
{
ActiveSkillsThisRound.TryAdd(playerInfo.SpecialSkill.ToString(), 0);
SkillsUsedThisMap.TryAdd(playerInfo.SpecialSkill.ToString(), 0);
}
Instance.SkillAction(playerInfo.Skill.ToString(), "DisableSkill", [player]);
playerInfo.Skill = noneSkill.Skill;
playerInfo.SpecialSkill = noneSkill.Skill;
playerInfo.PrintHTML = null;
playerInfo.SkillChance = 1;
playerInfo.SkillUsed = false;
RestorePlayer(player);
}
// Reset every skill used so far on this map, not only the ones held this round: a skill
// nobody drew now would otherwise never clear state left over from an earlier round.
// Skills that never ran cannot hold state, so they stay out of the sweep.
foreach (var skillName in SkillsUsedThisMap.Keys)
Instance.SkillAction(skillName, "NewRound");
ActiveSkillsThisRound.Clear();
tickFailuresLogged.Clear();
}
}
public static void OnMapChange()
{
lock (setLock)
{
isTransmitRegistered = false;
Instance.RemoveListener<CheckTransmit>(CheckTransmit);
Fortnite.skillInThisRound = false;
EntityManager.SuppressKills = true;
EntityManager.DestroyAllTracked();
foreach (var skill in SkillData.Skills)
Instance.SkillAction(skill.Skill.ToString(), "NewRound");
EntityManager.SuppressKills = false;
ActiveSkillsThisRound.Clear();
SkillsUsedThisMap.Clear();
nextRoundPicks.Clear();
playersSkills.Clear();
staticSkills.Clear();
ctSkill = noneSkill;
tSkill = noneSkill;
allSkill = noneSkill;
PlayerManager.Clear();
ConVar.Find("sv_legacy_jump")?.SetValue("1");
}
}
private static HookResult RoundEnd(EventRoundEnd @event, GameEventInfo info)
{
Illiterate.Disable();
DispatchToActiveSkills("RoundEnd");
lock (setLock)
{
Instance.AddTimer(.5f, () =>
{
if (!Config.LoadedConfig.SummaryAfterTheRound) return;
var _players = Utilities.GetPlayers().Where(p => p.IsValid && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).OrderBy(p => p.Team).ToList();
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid))
{
string skillsText = "";
foreach (var _player in _players)
{
var _playerSkill = PlayerManager.GetPlayerByIndex(_player.Index);
if (_playerSkill == null) continue;
var skillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.Skill);
var specialSkillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.SpecialSkill);
if (skillInfo == null) continue;
skillsText += $" {ChatColors.DarkRed}\u202A{_player.PlayerName}\u202C{ChatColors.Lime}: {(_playerSkill.SpecialSkill == Skills.None || specialSkillInfo == null ? player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance) : $"{player.GetSkillName(specialSkillInfo.Skill)} -> {player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance)}")}\n";
}
if (string.IsNullOrEmpty(skillsText)) continue;
SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("summary"), border: "t");
foreach (string text in skillsText.Split("\n"))
if (!string.IsNullOrEmpty(text))
SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: "");
SkillUtils.PrintToChat(player, string.Empty, border: "b");
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
// Before the optional disable below, so the "don't repeat the current skill"
// exclusion still sees this round's skills.
Instance.AddTimer(.6f, PrecomputeNextRoundSkills, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
if (Config.LoadedConfig.DisableSkillsOnRoundEnd)
{
isTransmitRegistered = false;
Instance.AddTimer(1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
Instance.RemoveListener<CheckTransmit>(CheckTransmit);
}
return HookResult.Continue;
}
}
private static void SetSkill()
{
long perfStart = PerfLog.Start();
SetSkillCore();
PerfLog.End("SetSkill total", perfStart, 2.0);
}
private sealed class PickContext
{
public required List<jSkill_SkillInfo> BaseList { get; init; }
public required Dictionary<Skills, string> RequiredPermissions { get; init; }
public required HashSet<Skills> NeedsTeammates { get; init; }
public required HashSet<Skills> CtOnly { get; init; }
public required HashSet<Skills> TOnly { get; init; }
public required int TerroristCount { get; init; }
public required int CounterTerroristCount { get; init; }
}
private static PickContext BuildPickContext(List<CCSPlayerController> validPlayers)
{
Dictionary<Skills, string> perms = [];
foreach (var s in SkillData.Skills)
{
if (s == null || s.Skill == Skills.None) continue;
string perm = SkillsInfo.GetValue<string>(s.Skill, "requiredPermission");
if (!string.IsNullOrEmpty(perm)) perms[s.Skill] = perm;
}
return new PickContext
{
BaseList = [.. SkillData.Skills.Where(s => s != null && s.Skill != Skills.None)],
RequiredPermissions = perms,
NeedsTeammates = ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates).Select(s => s.Name)),
CtOnly = ToSkillSet(counterterroristSkills.Select(s => s.Name)),
TOnly = ToSkillSet(terroristSkills.Select(s => s.Name)),
TerroristCount = validPlayers.Count(p => p.Team == CsTeam.Terrorist),
CounterTerroristCount = validPlayers.Count(p => p.Team == CsTeam.CounterTerrorist),
};
}
private static HashSet<Skills> ToSkillSet(IEnumerable<string> names)
{
HashSet<Skills> set = [];
foreach (var name in names)
if (Enum.TryParse<Skills>(name, out var skill)) set.Add(skill);
return set;
}
private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, PickContext ctx, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
{
List<jSkill_SkillInfo> skillList = [.. ctx.BaseList];
if (!player.IsBot && ctx.RequiredPermissions.Count != 0)
skillList.RemoveAll(s => ctx.RequiredPermissions.TryGetValue(s.Skill, out var perm) && !AdminManager.PlayerHasPermissions(player, perm));
if (gameMode != Config.GameModes.FullRandom)
skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill);
int teamCount = player.Team == CsTeam.Terrorist ? ctx.TerroristCount : ctx.CounterTerroristCount;
if (teamCount == 1)
skillList.RemoveAll(s => ctx.NeedsTeammates.Contains(s.Skill));
if (player.Team == CsTeam.Terrorist)
skillList.RemoveAll(s => ctx.CtOnly.Contains(s.Skill));
else
skillList.RemoveAll(s => ctx.TOnly.Contains(s.Skill));
if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag<jSkill_SkillInfo>? skills))
{
skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill));
if (skillList.Count == 0) skills.Clear();
}
var randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode);
if (gameMode == Config.GameModes.NoRepeat)
{
if (playersSkills.TryGetValue(player.Index, out ConcurrentBag<jSkill_SkillInfo>? value))
value.Add(randomSkill);
else
playersSkills.TryAdd(player.Index, [randomSkill]);
}
return randomSkill;
}
private static bool IsPickStillValid(jSkill_SkillInfo pick, CCSPlayerController player, List<CCSPlayerController> validPlayers, Dictionary<Skills, int> assignmentCounts)
{
if (pick.Skill == Skills.None) return true;
if (!SkillData.Skills.Any(s => s.Skill == pick.Skill)) return false;
string name = pick.Skill.ToString();
if (player.Team == CsTeam.Terrorist && counterterroristSkills.Any(s => s.Name == name)) return false;
if (player.Team == CsTeam.CounterTerrorist && terroristSkills.Any(s => s.Name == name)) return false;
var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == name);
if (def == null) 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;
return true;
}
// Runs at round end so the expensive skill selection is off the round-start hot path;
// SetSkillCore then only applies the picks.
private static void PrecomputeNextRoundSkills()
{
long perfStart = PerfLog.Start();
lock (setLock)
{
nextRoundPicks.Clear();
var gameMode = (Config.GameModes)Config.LoadedConfig.GameMode;
if (gameMode is not (Config.GameModes.Normal or Config.GameModes.FullRandom or Config.GameModes.NoRepeat)) return;
if (Instance?.GameRules == null || Instance.GameRules.WarmupPeriod == true) return;
var validPlayers = Utilities.GetPlayers()
.Where(p => p != null && p.IsValid && !p.IsHLTV)
.Where(p => { try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } catch { return false; } }).ToList();
var ctx = BuildPickContext(validPlayers);
Dictionary<Skills, int> assignmentCounts = [];
foreach (var player in validPlayers)
{
var skillPlayer = PlayerManager.GetPlayerByIndex(player.Index);
if (skillPlayer == null) continue;
var pick = PickSkillForPlayer(player, skillPlayer, ctx, assignmentCounts, gameMode);
nextRoundPicks[player.Index] = pick;
if (pick.Skill != Skills.None)
assignmentCounts[pick.Skill] = assignmentCounts.TryGetValue(pick.Skill, out var c) ? c + 1 : 1;
}
}
PerfLog.End("PrecomputeSkills total", perfStart, 2.0);
}
public static void UpdateSkillHudExpired(jSkill_PlayerInfo skillPlayer, Skills skill)
{
float globalHudExpired = Config.LoadedConfig.SkillHudDuration;
float? skillHudExpired = SkillsInfo.GetValue<float?>(skill, "hudDuration");
skillPlayer.SkillHudExpired =
!skillHudExpired.HasValue ?
(globalHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalHudExpired))
: skillHudExpired.Value == -1 ? DateTime.MaxValue
: DateTime.Now.AddSeconds(skillHudExpired.Value);
float globalDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration;
float? skillDescriptionHudExpired = SkillsInfo.GetValue<float?>(skill, "descriptionHudDuration");
skillPlayer.SkillDescriptionHudExpired =
!skillDescriptionHudExpired.HasValue ?
(globalDescriptionHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalDescriptionHudExpired))
: skillDescriptionHudExpired.Value == -1 ? DateTime.MaxValue
: DateTime.Now.AddSeconds(skillDescriptionHudExpired.Value);
}
private static void SetSkillCore()
{
setSkillTimer = null;
lock (setLock)
{
if (Instance == null) return;
// GameRules null = not ready; keep polling so skills land right after warmup ends.
if (Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true)
{
setSkillTimer?.Kill();
setSkillTimer = Instance.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
return;
}
var validPlayers = Utilities.GetPlayers()
.Where(p => p != null && p.IsValid && !p.IsHLTV)
.Where(p =>
{
try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; }
catch { return false; }
}).ToList();
if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills)
{
List<jSkill_SkillInfo> tSkills = [.. SkillData.Skills];
tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
tSkill = tSkills.Count == 0 ? noneSkill : tSkills[Instance.Random.Next(tSkills.Count)];
List<jSkill_SkillInfo> ctSkills = [.. SkillData.Skills];
ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[Instance.Random.Next(ctSkills.Count)];
}
else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.SameSkills)
{
List<jSkill_SkillInfo> allSkills = [.. SkillData.Skills];
allSkills.RemoveAll(s => s.Skill == allSkill.Skill || s.Skill == Skills.None || !allTeamsSkills.Any(s2 => s2.Name == s.Skill.ToString()));
allSkill = allSkills.Count == 0 ? noneSkill : allSkills[Instance.Random.Next(allSkills.Count)];
}
else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.Debug && debugSkills.Count == 0)
debugSkills = [.. SkillData.Skills];
Dictionary<Skills, int> assignmentCounts = new();
foreach (var sp in Instance.SkillPlayer)
{
if (sp == null) continue;
if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1;
else assignmentCounts[sp.Skill] = 1;
}
PickContext? pickContext = null;
foreach (var player in validPlayers)
{
if (player == null) continue;
var teammates = validPlayers.Where(p => p != null && p.IsValid && p.Team == player.Team && p != player).ToList();
string teammateSkills = "";
var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index);
if (skillPlayer == null) continue;
skillPlayer.IsDrawing = false;
skillPlayer.HudOnDeathBlocked = null;
if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid)
{
skillPlayer.Skill = Skills.None;
continue;
}
jSkill_SkillInfo randomSkill = noneSkill;
Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode;
if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat)
{
// Prefer the pick made at the end of the previous round; re-pick only when
// it no longer fits (team change, missing player, max reached).
if (nextRoundPicks.TryGetValue(player.Index, out var pre) && IsPickStillValid(pre, player, validPlayers, assignmentCounts))
randomSkill = pre;
else
{
pickContext ??= BuildPickContext(validPlayers);
randomSkill = PickSkillForPlayer(player, skillPlayer, pickContext, assignmentCounts, gameMode);
}
}
else if (gameMode == Config.GameModes.TeamSkills)
randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill;
else if (gameMode == Config.GameModes.SameSkills)
randomSkill = allSkill;
else if (gameMode == Config.GameModes.Debug)
{
if (debugSkills.Count == 0)
debugSkills = [.. SkillData.Skills];
randomSkill = debugSkills[0];
debugSkills.RemoveAt(0);
player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}");
}
Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]);
skillPlayer.Skill = randomSkill.Skill;
skillPlayer.SpecialSkill = Skills.None;
if (randomSkill.Skill != Skills.None)
{
if (assignmentCounts.TryGetValue(randomSkill.Skill, out var cnt)) assignmentCounts[randomSkill.Skill] = cnt + 1;
else assignmentCounts[randomSkill.Skill] = 1;
}
if (randomSkill.Skill == Skills.Illiterate)
Illiterate.Enable();
var playerIndex = player.Index;
Instance?.AddTimer(.2f, () =>
{
var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex);
if (playerTarget == null || !playerTarget.IsValid) return;
if (randomSkill.Display)
SkillUtils.PrintToChat(playerTarget, $"{ChatColors.DarkRed}{playerTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {playerTarget.GetSkillDescription(randomSkill.Skill)}",
border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == playerTarget.Team && p != playerTarget) ? "tb" : "t");
if (SkillsInfo.GetValue<bool>(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime())
Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () =>
{
var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex);
if (playerTarget == null || !playerTarget.IsValid) return;
if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return;
Debug.WriteToDebug("Enabling skill after freeze time: " + randomSkill.Skill);
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]);
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
else
{
if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return;
Debug.WriteToDebug("Enabling skill: " + randomSkill.Skill);
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]);
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\".");
UpdateSkillHudExpired(skillPlayer, randomSkill.Skill);
if (Config.LoadedConfig.TeamMateSkillChatInfo)
{
Instance?.AddTimer(.6f, () =>
{
if (player == null || !player.IsValid) return;
foreach (var teammate in teammates)
{
var teammateInfo = PlayerManager.GetPlayerByIndex(teammate.Index);
if (teammateInfo != null && teammateInfo?.Skill != null)
{
var skillInfo = SkillData.Skills.FirstOrDefault(p => p.Skill == teammateInfo.Skill);
teammateSkills += $" {ChatColors.DarkRed}\u202A{teammate.PlayerName}\u202C{ChatColors.Lime}: {(skillInfo == null ? player.GetSkillName(Skills.None) : player.GetSkillName(skillInfo.Skill, teammateInfo.SkillChance))}\n";
}
}
if (!string.IsNullOrEmpty(teammateSkills))
{
SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "t");
foreach (string text in teammateSkills.Split("\n"))
if (!string.IsNullOrEmpty(text))
SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: "");
SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "b");
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
}
nextRoundPicks.Clear();
}
}
public static void SetRandomSkill(CCSPlayerController player)
{
lock (setLock)
{
var validPlayers = Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).ToList();
if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills)
{
List<jSkill_SkillInfo> tSkills = [.. SkillData.Skills];
tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
tSkill = tSkills.Count == 0 ? noneSkill : tSkills[0];
List<jSkill_SkillInfo> ctSkills = [.. SkillData.Skills];
ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[0];
}
if (player == null) return;
var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index);
if (skillPlayer == null) return;
skillPlayer.IsDrawing = false;
if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid)
{
skillPlayer.Skill = Skills.None;
return;
}
jSkill_SkillInfo randomSkill = noneSkill;
if (Instance?.GameRules != null && Instance?.GameRules.WarmupPeriod == false)
{
Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode;
if (staticSkills.TryGetValue(player.Index, out var staticSkill))
randomSkill = staticSkill;
else if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat)
{
List<jSkill_SkillInfo> skillList = [.. SkillData.Skills];
skillList.RemoveAll(s => s?.Skill == Skills.None);
if (!player.IsBot)
skillList.RemoveAll(s => !string.IsNullOrEmpty(SkillsInfo.GetValue<string>(s.Skill, "requiredPermission")) && !AdminManager.PlayerHasPermissions(player, SkillsInfo.GetValue<string>(s.Skill, "requiredPermission")));
if (gameMode != Config.GameModes.FullRandom)
skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill);
if (validPlayers.Count(p => p.Team == player.Team) == 1)
{
SkillsInfo.DefaultSkillInfo[] skillsNeedsTeammates = [.. SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates)];
skillList.RemoveAll(s => skillsNeedsTeammates.Any(s2 => s2.Name == s.Skill.ToString()));
}
if (player.Team == CsTeam.Terrorist)
skillList.RemoveAll(s => counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
else
skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag<jSkill_SkillInfo>? skills))
{
skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill));
if (skillList.Count == 0) skills.Clear();
}
var assignmentCounts = new Dictionary<Skills, int>();
foreach (var sp in Instance.SkillPlayer)
{
if (sp == null) continue;
if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1;
else assignmentCounts[sp.Skill] = 1;
}
randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode);
}
else if (gameMode == Config.GameModes.TeamSkills)
randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill;
else if (gameMode == Config.GameModes.SameSkills)
randomSkill = allSkill;
else if (gameMode == Config.GameModes.Debug)
{
if (debugSkills.Count == 0)
debugSkills = [.. SkillData.Skills];
randomSkill = debugSkills[0];
debugSkills.RemoveAt(0);
player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}");
}
}
Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]);
skillPlayer.Skill = randomSkill.Skill;
skillPlayer.SpecialSkill = Skills.None;
if (randomSkill.Display && Config.LoadedConfig.YourSkillChatInfo)
SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}",
border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t");
if (randomSkill.Skill == Skills.Illiterate)
Illiterate.Enable();
Instance?.AddTimer(.2f, () =>
{
if (SkillsInfo.GetValue<bool>(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime())
Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () =>
{
if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != randomSkill.Skill) return;
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
else
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\".");
UpdateSkillHudExpired(skillPlayer, randomSkill.Skill);
}
}
public static DateTime GetFreezeTimeEnd() => freezeTimeEnd;
}
}

View file

@ -39,6 +39,8 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo == null || playerInfo.Skill != skillName) return;
if (!SkillUtils.IsBulletDamage(param2)) return;
int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup");
if (offset <= 0) return;

View file

@ -13,17 +13,17 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerHurt(EventPlayerHurt @event)
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
int hitgroup = @event.Hitgroup;
if (victim == null || !victim.IsValid || attacker == null || !attacker.IsValid || attacker == victim) return;
if (victim == null || !victim.IsValid || attacker == null || !attacker.IsValid || attacker == victim) return false;
if (@event.Hitgroup != (int)HitGroup_t.HITGROUP_HEAD) return false;
if (PlayerManager.GetPlayerByIndex(victim.Index)?.Skill != skillName) return false;
var playerInfo = PlayerManager.GetPlayerByIndex(victim.Index);
if (playerInfo?.Skill == skillName && hitgroup == (int)HitGroup_t.HITGROUP_HEAD)
SkillUtils.RestoreHealth(victim);
SkillUtils.RestoreHealth(victim);
return true;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8B4513", 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) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -1,4 +1,5 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
@ -7,32 +8,48 @@ namespace src.player.skills
public class Assassin : ISkill
{
private const Skills skillName = Skills.Assassin;
private static readonly string[] nades = ["inferno", "flashbang", "smokegrenade", "decoy", "hegrenade"];
private static readonly string[] nadeWeapons =
[
"weapon_inferno", "weapon_molotov", "weapon_incgrenade", "weapon_flashbang",
"weapon_smokegrenade", "weapon_decoy", "weapon_hegrenade"
];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerHurt(EventPlayerHurt @event)
public static void OnTakeDamage(DynamicHook h)
{
var damage = @event.DmgHealth;
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var weapon = @event.Weapon;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (victim == null || !victim.IsValid || attacker == null || !attacker.IsValid || attacker == victim) return;
if (string.IsNullOrEmpty(weapon) || nades.Contains(weapon)) return;
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;
if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null)
return;
var attacker = PlayerManager.GetPlayerEvent(attackerPawn.Controller.Value.As<CCSPlayerController>());
var victim = PlayerManager.GetPlayerEvent(victimPawn.Controller.Value.As<CCSPlayerController>());
if (attacker == null || !attacker.IsValid || victim == null || !victim.IsValid) return;
if (attacker.Index == victim.Index) return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo?.Skill != skillName) return;
var weapon = param2.Ability?.Value;
if (weapon == null || !weapon.IsValid) return;
if (nadeWeapons.Contains(weapon.DesignerName)) return;
if (IsBehind(attacker, victim))
{
var victimPawn = victim.PlayerPawn.Value;
if (victimPawn != null && victimPawn.IsValid)
SkillUtils.TakeHealth(victimPawn, (int)(damage * (SkillsInfo.GetValue<float>(skillName, "damageMultiplier") - 1)));
}
param2.Damage *= SkillsInfo.GetValue<float>(skillName, "damageMultiplier");
}
private static bool IsBehind(CCSPlayerController attacker, CCSPlayerController victim)

View file

@ -30,12 +30,13 @@ namespace src.player.skills
foreach (var player in PlayerManager.GetTickPlayers())
{
if (player == null || !player.IsValid || !SkillUtils.HasMenu(player)) continue;
if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [];
foreach (var e in enemies)

View file

@ -20,10 +20,22 @@ namespace src.player.skills
public static void NewRound()
{
decoys.Clear();
KillAllDecoys();
playersWithSkill.Clear();
}
private static void KillAllDecoys()
{
foreach (var decoyIndex in decoys.Keys.ToArray())
{
var decoy = Utilities.GetEntityFromIndex<CDecoyProjectile>((int)decoyIndex);
if (decoy != null && decoy.IsValid && decoy.DesignerName == "decoy_projectile")
decoy.AddEntityIOEvent("Kill", decoy, delay: 0.1f);
}
decoys.Clear();
}
public static void PlayerHurt(EventPlayerHurt @event)
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
@ -45,7 +57,7 @@ namespace src.player.skills
var attackerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(attacker)?.Index ?? attacker.Index));
if (attackerInfo?.Skill != skillName) return;
SkillUtils.TakeHealth(victim!.PlayerPawn.Value, SkillsInfo.GetValue<int>(skillName, "damageDeal"));
SkillUtils.TakeHealth(victim!.PlayerPawn.Value, SkillsInfo.GetValue<int>(skillName, "damageDeal"), attacker, "decoy");
}
public static void OnEntitySpawned(CEntityInstance entity)
@ -94,6 +106,7 @@ namespace src.player.skills
if (Server.TickCount % 8 != 0) return;
var keys = decoys.Keys.ToArray();
if (keys.Length == 0) return;
foreach (var decoyIndex in keys)
{

View file

@ -45,20 +45,19 @@ namespace src.player.skills
}
}
public static void PlayerHurt(EventPlayerHurt @event)
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var damage = @event.DmgHealth;
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
var weapon = @event.Weapon;
int hitgroup = @event.Hitgroup;
if (victim == null || !victim.IsValid || attacker == null || !attacker.IsValid || attacker == victim) return;
if (victim == null || !victim.IsValid || attacker == null || !attacker.IsValid || attacker == victim) return false;
var playerInfo = PlayerManager.GetPlayerByIndex(victim.Index);
if (playerInfo?.Skill != skillName) return;
if (playerInfo?.Skill != skillName) return false;
if (string.IsNullOrEmpty(weapon) || noReflectionWeapon.Contains(weapon)) return;
if (string.IsNullOrEmpty(weapon) || noReflectionWeapon.Contains(weapon)) return false;
float chance = (hitgroup == (int)HitGroup_t.HITGROUP_LEFTLEG || hitgroup == (int)HitGroup_t.HITGROUP_RIGHTLEG)
? SkillsInfo.GetValue<float>(skillName, "legReflectionChance")
@ -66,10 +65,10 @@ namespace src.player.skills
var victimPawn = victim.PlayerPawn?.Value;
if (victimPawn == null || !victimPawn.IsValid || Instance.Random.NextDouble() > chance)
return;
return false;
var weaponServices = victimPawn.WeaponServices;
if (weaponServices == null) return;
if (weaponServices == null) return false;
if (weaponServices.ActiveWeapon == null
|| !weaponServices.ActiveWeapon.IsValid
@ -77,9 +76,10 @@ namespace src.player.skills
|| !weaponServices.ActiveWeapon.Value.IsValid
|| (weaponServices.ActiveWeapon.Value.DesignerName != "weapon_knife"
&& weaponServices.ActiveWeapon.Value.DesignerName != "weapon_bayonet"))
return;
return false;
SkillUtils.RestoreHealth(victim);
return true;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#cc7504", 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 torseReflectionChance = .95f, float legReflectionChance = .70f, float velocityModifier = .85f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -11,6 +11,16 @@ namespace src.player.skills
private const Skills skillName = Skills.BunnyHop;
private static readonly ConcurrentDictionary<uint, int> playersLastJump = [];
public static void NewRound()
{
playersLastJump.Clear();
}
public static void PlayerDisconnect(uint playerIndex)
{
playersLastJump.TryRemove(playerIndex, out _);
}
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));

View file

@ -133,6 +133,8 @@ namespace src.player.skills
if (info.TransmitEntities.Contains(entity.Index))
info.TransmitEntities.Remove(entity.Index);
SkillUtils.HideCarriedEntities(info, playerPawn);
if (bomb == null) continue;
if (info.TransmitEntities.Contains(bomb.Index))

View file

@ -15,6 +15,7 @@ namespace src.player.skills
private static readonly ConcurrentDictionary<uint, bool> lastShot = [];
private static readonly ConcurrentDictionary<uint, int> hitPlayer = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetToPlayer = [];
private static readonly object setLock = new();
public static void LoadSkill()
@ -36,6 +37,7 @@ namespace src.player.skills
lastShot.Clear();
hitPlayer.Clear();
playersToTarget.Clear();
targetToPlayer.Clear();
}
}
@ -44,11 +46,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
SkillUtils.UpdateMenu(player, menuItems);
@ -88,6 +91,7 @@ namespace src.player.skills
targetPlayers.TryAdd(enemy.Index, 0);
playersToTarget[player.Index] = enemy.Index;
targetToPlayer[enemy.Index] = player.Index;
playerInfo.SkillUsed = true;
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
@ -106,7 +110,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
@ -131,13 +135,14 @@ namespace src.player.skills
return;
uint playerIndex = player.Index;
int impactTick = Server.TickCount;
Server.NextFrame(() =>
{
if (player == null || !player.IsValid) return;
if (playerPawn == null || !playerPawn.IsValid) return;
bool ishitPlayer = hitPlayer.TryGetValue(playerIndex, out int tick) && tick + 10 >= Server.TickCount;
bool ishitPlayer = hitPlayer.TryGetValue(playerIndex, out int tick) && tick + 1 >= impactTick;
if (!lastShot.ContainsKey(playerIndex))
{
@ -148,7 +153,8 @@ namespace src.player.skills
if (lastShot.TryRemove(playerIndex, out bool didHit) && !didHit)
{
eventPlayer.ExecuteClientCommand($"play player/player_damagebody_0{Instance.Random.Next(4, 8)}");
SkillUtils.TakeHealth(playerPawn, SkillsInfo.GetValue<int>(skillName, "damageAfterMiss"));
SkillUtils.TakeHealth(playerPawn, SkillsInfo.GetValue<int>(skillName, "damageAfterMiss"), GetSkillOwner(playerIndex), "planted_c4");
}
lastShot.TryRemove(playerIndex, out _);
});
@ -158,6 +164,14 @@ namespace src.player.skills
});
}
private static CCSPlayerController? GetSkillOwner(uint targetIndex)
{
if (!targetToPlayer.TryGetValue(targetIndex, out uint ownerIndex)) return null;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
return owner != null && owner.IsValid ? owner : null;
}
public static void OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
@ -188,12 +202,14 @@ namespace src.player.skills
targetPlayers.TryRemove(player.Index, out _);
lastShot.TryRemove(player.Index, out _);
hitPlayer.TryRemove(player.Index, out _);
targetToPlayer.TryRemove(player.Index, out _);
if (playersToTarget.TryRemove(player.Index, out uint targetIndex))
{
targetPlayers.TryRemove(targetIndex, out _);
lastShot.TryRemove(targetIndex, out _);
hitPlayer.TryRemove(targetIndex, out _);
targetToPlayer.TryRemove(targetIndex, out _);
var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex));
if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime())
@ -212,6 +228,8 @@ namespace src.player.skills
lastShot.TryRemove(player.Index, out _);
hitPlayer.TryRemove(player.Index, out _);
targetToPlayer.TryRemove(player.Index, out _);
SkillUtils.CloseMenu(player);
}

View file

@ -20,11 +20,13 @@ namespace src.player.skills
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev", "weapon_sg556"
];
private const string chickenModelPath = "models/chicken/chicken.vmdl";
private static readonly ConcurrentDictionary<uint, uint> chickens = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
jRandomSkills.Instance.AddToManifest(chickenModelPath);
}
public static void NewRound()
@ -127,7 +129,7 @@ namespace src.player.skills
return;
chickenModel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(chickenModel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
chickenModel.SetModel("models/chicken/chicken.vmdl");
chickenModel.SetModel(chickenModelPath);
chickenModel.Render = Color.FromArgb(255, 255, 255, 255);
chickenModel.Teleport(playerPawn.AbsOrigin, playerPawn.AbsRotation, null);
chickenModel.DispatchSpawn();

View file

@ -46,17 +46,25 @@ namespace src.player.skills
plantingPlayers.TryAdd(player!.Index, Server.CurrentTime);
var anyChillOut = PlayerManager.GetAllPlayers().FirstOrDefault(p => p.Skill == skillName);
if (anyChillOut != null)
if (!IsAnyOwnerAlive()) return;
var bomb = PlayerManager.GetTickBomb();
if (bomb == null || !bomb.IsValid) return;
bomb.ArmedTime = Server.CurrentTime + SkillsInfo.GetValue<float>(skillName, "bombArmedTime");
}
private static bool IsAnyOwnerAlive()
{
foreach (var player in PlayerManager.GetTickPlayers())
{
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
if (bombEntities.Count != 0)
{
var bomb = bombEntities.FirstOrDefault();
if (bomb != null)
bomb.ArmedTime = Server.CurrentTime + SkillsInfo.GetValue<float>(skillName, "bombArmedTime");
}
if (player == null || !player.IsValid || !player.PawnIsAlive) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index);
if (playerInfo?.Skill == skillName) return true;
}
return false;
}
public static void BombPlanted(EventBombPlanted @event)
@ -70,6 +78,8 @@ namespace src.player.skills
public static void OnTick()
{
if (plantingPlayers.IsEmpty) return;
float currentTime = Server.CurrentTime;
float extraTime = SkillsInfo.GetValue<float>(skillName, "bombArmedTime");

View file

@ -1,7 +1,7 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using static src.jRandomSkills;
namespace src.player.skills
{
@ -14,19 +14,35 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerHurt(EventPlayerHurt @event)
public static void OnTakeDamage(DynamicHook h)
{
var damage = @event.DmgHealth;
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
var weapon = @event.Weapon;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;
if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null)
return;
var attacker = PlayerManager.GetPlayerEvent(attackerPawn.Controller.Value.As<CCSPlayerController>());
if (attacker == null || !attacker.IsValid) return;
if (attacker.Index == victimPawn.Controller.Value.Index) return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo?.Skill != skillName) return;
if (weapon == "knife" || weapon == "bayonet")
SkillUtils.TakeHealth(victim!.PlayerPawn.Value, 9999);
var weapon = param2.Ability?.Value;
if (weapon == null || !weapon.IsValid) return;
if (weapon.DesignerName != "weapon_knife" && weapon.DesignerName != "weapon_bayonet") return;
param2.Damage = 9999f;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#88a31a", 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) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -54,11 +54,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
SkillUtils.UpdateMenu(player, menuItems);
@ -129,7 +130,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];

View file

@ -27,11 +27,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [];
foreach (var enemy in enemies)
@ -84,7 +85,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = [];

View file

@ -59,11 +59,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
SkillUtils.UpdateMenu(player, menuItems);
@ -121,7 +122,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));

View file

@ -1,5 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
@ -12,7 +13,7 @@ namespace src.player.skills
{
private const Skills skillName = Skills.DeathBomb;
private static readonly QAngle angle = new(10, -5, 9);
private static readonly ConcurrentDictionary<int, byte> nades = [];
private static readonly ConcurrentDictionary<int, (byte Team, uint Owner)> nades = [];
public static void LoadSkill()
{
@ -63,7 +64,7 @@ namespace src.player.skills
var randomFile = fileNames[new Random().Next(fileNames.Length)];
player.ExecuteClientCommand($"play vo/agents/balkan/{randomFile}.vsnd");
nades.AddOrUpdate(Server.TickCount, player.TeamNum, (_, _) => player.TeamNum);
nades.AddOrUpdate(Server.TickCount, (player.TeamNum, player.Index), (_, _) => (player.TeamNum, player.Index));
}
public static void OnEntitySpawned(CEntityInstance entity)
@ -86,8 +87,8 @@ namespace src.player.skills
heProjectile.DmgRadius = SkillsInfo.GetValue<float>(skillName, "explosionRadius");
heProjectile.DetonateTime = 0;
if (nades.TryRemove(lastTick, out byte teamNum))
heProjectile.Globalname = $"deathbomb_team_{teamNum}_{heProjectile.Index}";
if (nades.TryRemove(lastTick, out var source))
heProjectile.Globalname = $"deathbomb_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
});
}
@ -104,16 +105,48 @@ namespace src.player.skills
if (nade.DesignerName != "hegrenade_projectile") return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("deathbomb_team_")) return;
if (!int.TryParse(nade.Globalname.Split('_')[2], out int nadeTeam)) return;
var parts = nade.Globalname.Split('_');
if (parts.Length < 4) return;
if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle);
if (victimPawn.DesignerName != "player") return;
if (victimPawn == null || victimPawn.Controller?.Value == null) return;
if (victimPawn.TeamNum != nadeTeam) return;
if (victimPawn.Controller?.Value == null) return;
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
var victim = victimPawn.Controller.Value.As<CCSPlayerController>();
if (victim == null || !victim.IsValid || victim.Index == ownerIndex) return;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, "hegrenade");
return;
}
}
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, "hegrenade");
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
@ -123,7 +156,7 @@ namespace src.player.skills
return player != null && player.IsValid && player.PlayerPawn?.Value != null;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#F5CB42", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float explosionRadius = 500.0f, int explosionDamage = 999, float dmgReductionForTeamates = .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) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage;

View file

@ -27,18 +27,41 @@ namespace src.player.skills
HealAttacker(attacker!, @event.DmgHealth);
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) return;
pawn.MaxHealth = 100;
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iMaxHealth");
if (pawn.Health > 100)
{
pawn.Health = 100;
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
}
}
private static void HealAttacker(CCSPlayerController attacker, float damage)
{
var attackerPawn = attacker.PlayerPawn.Value;
if (attackerPawn == null) return;
var attackerPawn = attacker.PlayerPawn?.Value;
if (attackerPawn == null || !attackerPawn.IsValid) return;
int newHealth = (int)(attackerPawn.Health + (damage * SkillsInfo.GetValue<float>(skillName, "healthRegainScale")));
if (attackerPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || attackerPawn.Health <= 0) return;
attackerPawn.MaxHealth = Math.Max(newHealth, 100);
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iMaxHealth");
int extraHealth = (int)(damage * SkillsInfo.GetValue<float>(skillName, "healthRegainScale"));
if (extraHealth <= 0) return;
attackerPawn.Health = newHealth;
attackerPawn.Health += extraHealth;
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iHealth");
if (attackerPawn.MaxHealth < attackerPawn.Health)
{
attackerPawn.MaxHealth = attackerPawn.Health;
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iMaxHealth");
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#FA050D", 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 healthRegainScale = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -27,10 +27,11 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Index != player.Index && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
ConcurrentBag<(string, string)> menuItems = [];

View file

@ -1,141 +0,0 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using static src.jRandomSkills;
using System.Collections.Concurrent;
using src.utils;
namespace src.player.skills
{
public class Earthquake : ISkill
{
//private const Skills skillName = Skills.Earthquake;
//private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
//private static readonly object setLock = new();
//public static void LoadSkill()
//{
// SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
//}
//public static void NewRound()
//{
// lock (setLock)
// SkillPlayerInfo.Clear();
//}
//public static void EnableSkill(CCSPlayerController player)
//{
// SkillPlayerInfo.TryAdd(player.Index, new PlayerSkillInfo
// {
// SteamID = player.Index,
// CanUse = true,
// Cooldown = DateTime.MinValue,
// });
//}
//public static void DisableSkill(CCSPlayerController player)
//{
// SkillPlayerInfo.TryRemove(player.Index, out _);
// SkillUtils.ResetPrintHTML(player);
//}
//public static void PlayerDeath(EventPlayerDeath @event)
//{
// var player = PlayerManager.GetPlayerEvent(@event.Userid);
// if (player == null || !player.IsValid) return;
// var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
// if (playerInfo?.Skill == skillName)
// SkillPlayerInfo.TryRemove(player.Index, out _);
//}
//public static void OnTick()
//{
// foreach (var player in PlayerManager.GetTickPlayers())
// {
// var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
// if (playerInfo?.Skill == skillName)
// if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo))
// UpdateHUD(player, skillInfo);
// }
//}
//private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo)
//{
// float cooldown = 0;
// if (skillInfo != null)
// {
// float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue<float>(skillName, "cooldown")) - DateTime.Now).TotalSeconds);
// cooldown = Math.Max(time, 0);
// if (cooldown == 0 && skillInfo?.CanUse == false)
// skillInfo.CanUse = true;
// }
// if (cooldown == 0)
// return;
// var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
// if (playerInfo == null) return;
// string remainingLine = $"{player.GetTranslation("hud_info", $"<font color='#FF0000'>{cooldown}</font>")}";
//}
//public static void UseSkill(CCSPlayerController player)
//{
// var playerPawn = player.PlayerPawn.Value;
// if (playerPawn?.CBodyComponent == null) return;
// if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo))
// {
// if (!player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
// if (skillInfo.CanUse)
// {
// skillInfo.CanUse = false;
// skillInfo.Cooldown = DateTime.Now;
// MakeShake(player);
// }
// }
//}
//private static void MakeShake(CCSPlayerController player)
//{
// foreach (var enemy in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.PawnIsAlive))
// CreateShake(player);
//}
//private static void CreateShake(CCSPlayerController player)
//{
// var pawn = player.PlayerPawn.Value;
// if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
// var shake = EntityManager.CreateTrackedEnvShake(player.Index);
// if (shake == null || !shake.IsValid) return;
// shake.Amplitude = SkillsInfo.GetValue<float>(skillName, "amplitude");
// shake.Frequency = SkillsInfo.GetValue<float>(skillName, "frequency");
// shake.Duration = SkillsInfo.GetValue<float>(skillName, "duration");
// shake.Radius = SkillsInfo.GetValue<float>(skillName, "radius");
// shake.Teleport(new Vector(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z));
// shake.AcceptInput("SetParent", pawn, pawn, "!activator");
// shake.AcceptInput("StartShake");
//}
//public class PlayerSkillInfo
//{
// public ulong SteamID { get; set; }
// public bool CanUse { get; set; }
// public DateTime Cooldown { get; set; }
//}
//public class SkillConfig(Skills skill = skillName, bool active = false, string color = "#42f59b", 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 = 16f, float amplitude = 15f, float frequency = 500f, float duration = 8f, float radius = 50f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
//{
// public float Cooldown { get; set; } = cooldown;
// public float Amplitude { get; set; } = amplitude;
// public float Frequency { get; set; } = frequency;
// public float Duration { get; set; } = duration;
// public float Radius { get; set; } = radius;
//}
}
}

View file

@ -0,0 +1,163 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class ExpensiveAmmo : ISkill
{
private const Skills skillName = Skills.ExpensiveAmmo;
private static readonly ConcurrentDictionary<uint, byte> cursedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerDisconnect(uint playerIndex)
{
cursedPlayers.TryRemove(playerIndex, out _);
playersToTarget.TryRemove(playerIndex, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
public static void NewRound()
{
cursedPlayers.Clear();
playersToTarget.Clear();
foreach (var player in PlayerManager.GetTickPlayers())
SkillUtils.CloseMenu(player);
}
public static void WeaponFire(EventWeaponFire @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
if (!cursedPlayers.ContainsKey(player.Index)) return;
if (!SkillUtils.FiresBullets(@event.Weapon)) return;
var moneyServices = player.InGameMoneyServices;
if (moneyServices == null) return;
int cost = SkillsInfo.GetValue<int>(skillName, "moneyPerShot");
if (cost <= 0) return;
moneyServices.Account = Math.Max(moneyServices.Account - cost, 0);
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
}
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 = [.. 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;
}
string enemyId = commands[0];
if (!uint.TryParse(enemyId, 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 || enemy.PlayerPawn.Value == null || !enemy.PlayerPawn.Value.IsValid)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;
}
cursedPlayers.TryAdd(enemy.Index, 0);
playersToTarget[player.Index] = enemy.Index;
playerInfo.SkillUsed = true;
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) return;
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("expensiveammo_player_info", enemy.PlayerName));
enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("expensiveammo_enemy_info", SkillsInfo.GetValue<int>(skillName, "moneyPerShot")));
}
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 = [.. 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 (playersToTarget.TryRemove(player.Index, out uint targetIndex))
{
cursedPlayers.TryRemove(targetIndex, out _);
var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex));
if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime())
target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("expensiveammo_disable_info"));
}
SkillUtils.CloseMenu(player);
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return;
cursedPlayers.TryRemove(player.Index, out _);
SkillUtils.CloseMenu(player);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e0c341", 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 moneyPerShot = 50) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int MoneyPerShot { get; set; } = moneyPerShot;
}
}
}

View file

@ -0,0 +1,276 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class ExplodingBarrel : ISkill
{
private const Skills skillName = Skills.ExplodingBarrel;
private static readonly QAngle angle = new(7, -3, 11);
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
private static readonly ConcurrentDictionary<uint, byte> consumedBarrels = [];
private static readonly ConcurrentDictionary<int, (byte Team, uint Owner)> nades = [];
private static readonly object setLock = new();
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
jRandomSkills.Instance.AddToManifest(SkillsInfo.GetValue<string>(skillName, "propModel"));
}
public static void NewRound()
{
ClearAllBarrels();
lock (setLock)
{
SkillPlayerInfo.Clear();
consumedBarrels.Clear();
nades.Clear();
}
}
private static void ClearAllBarrels()
{
var entities = Utilities.FindAllEntitiesByDesignerName<CDynamicProp>("prop_dynamic_override");
foreach (var entity in entities)
if (entity != null && entity.IsValid && entity.Entity != null && (entity.Entity.Name?.StartsWith("Barrel_") ?? false))
EntityManager.DestroyEntity(entity.Index);
}
public static void OnTick()
{
if (SkillPlayerInfo.IsEmpty || Server.TickCount % 8 != 0) return;
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)
UpdateHUD(player, skillInfo);
}
}
public static void EnableSkill(CCSPlayerController player)
{
SkillPlayerInfo.TryAdd(player.Index, new PlayerSkillInfo
{
SteamID = player.Index,
CanUse = true,
Cooldown = DateTime.MinValue,
});
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null) return;
SkillPlayerInfo.TryRemove(player.Index, out _);
EntityManager.DestroyPlayerEntities(player.Index);
SkillUtils.ResetPrintHTML(player);
}
private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo)
{
float cooldown = 0;
if (skillInfo != null)
{
float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue<float>(skillName, "cooldown")) - DateTime.Now).TotalSeconds);
cooldown = Math.Max(time, 0);
if (cooldown == 0 && skillInfo?.CanUse == false)
skillInfo.CanUse = true;
}
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) return;
if (cooldown == 0)
playerInfo.PrintHTML = null;
else
playerInfo.PrintHTML = $"{player.GetTranslation("hud_info", $"<font color='#FF0000'>{cooldown}</font>")}";
}
public static void UseSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
var playerPawn = player.PlayerPawn?.Value;
if (playerPawn?.CBodyComponent == null) return;
if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo) && skillInfo.CanUse)
{
skillInfo.CanUse = false;
skillInfo.Cooldown = DateTime.Now;
CreateBarrel(player);
}
}
private static void CreateBarrel(CCSPlayerController player)
{
var playerPawn = player.PlayerPawn?.Value;
if (playerPawn == null || !playerPawn.IsValid || playerPawn.AbsOrigin == null || playerPawn.AbsRotation == null)
return;
var barrel = EntityManager.CreateTrackedPropOverride(player.Index);
if (barrel == null || !barrel.IsValid)
return;
consumedBarrels.TryRemove(barrel.Index, out _);
float distance = 50;
Vector pos = playerPawn.AbsOrigin + SkillUtils.GetForwardVector(playerPawn.AbsRotation) * distance;
QAngle rotation = new(playerPawn.AbsRotation.X, playerPawn.AbsRotation.Y, playerPawn.AbsRotation.Z);
barrel.Entity!.Name = barrel.Globalname = $"Barrel_{Server.TickCount}_{player.TeamNum}_{player.Index}";
barrel.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
barrel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(barrel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
barrel.DispatchSpawn();
Server.NextFrame(() =>
{
if (barrel == null || !barrel.IsValid) return;
barrel.SetModel(SkillsInfo.GetValue<string>(skillName, "propModel"));
barrel.Teleport(pos, rotation, null);
});
}
public static void OnEntitySpawned(CEntityInstance entity)
{
if (entity.DesignerName != "hegrenade_projectile") return;
var heProjectile = entity.As<CBaseCSGrenadeProjectile>();
if (heProjectile == null || !heProjectile.IsValid || heProjectile.AbsRotation == null) return;
int lastTick = Server.TickCount;
Server.NextFrame(() =>
{
if (heProjectile == null || !heProjectile.IsValid) return;
if (!(NearlyEquals(angle.X, heProjectile.AbsRotation.X) && NearlyEquals(angle.Y, heProjectile.AbsRotation.Y) && NearlyEquals(angle.Z, heProjectile.AbsRotation.Z)))
return;
heProjectile.TicksAtZeroVelocity = 100;
heProjectile.Damage = SkillsInfo.GetValue<int>(skillName, "explosionDamage");
heProjectile.DmgRadius = SkillsInfo.GetValue<float>(skillName, "explosionRadius");
heProjectile.DetonateTime = 0;
if (nades.TryRemove(lastTick, out var source))
heProjectile.Globalname = $"barrel_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
});
}
public static void OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null) return;
if (HandleBarrelHit(param)) return;
HandleNadeDamage(param, param2);
}
private static bool HandleBarrelHit(CEntityInstance param)
{
if (string.IsNullOrEmpty(param.Entity!.Name)) return false;
if (!param.Entity.Name.StartsWith("Barrel_")) return false;
var barrel = param.As<CDynamicProp>();
if (barrel == null || !barrel.IsValid) return true;
if (!consumedBarrels.TryAdd(barrel.Index, 0)) return true;
var nameParts = param.Entity.Name.Split('_');
if (nameParts.Length < 4) return true;
if (!int.TryParse(nameParts[2], out int teamNum)) return true;
if (!uint.TryParse(nameParts[3], out uint ownerIndex)) return true;
if (barrel.AbsOrigin == null) return true;
float x = barrel.AbsOrigin.X;
float y = barrel.AbsOrigin.Y;
float z = barrel.AbsOrigin.Z + 32;
EntityManager.DestroyEntity(barrel.Index, 0f);
jRandomSkills.Instance.AddTickTimer(4, () =>
{
SkillUtils.CreateHEGrenadeProjectile(new Vector(x, y, z), angle, new Vector(0, 0, -10), teamNum);
nades.AddOrUpdate(Server.TickCount, ((byte)teamNum, ownerIndex), (_, _) => ((byte)teamNum, ownerIndex));
});
return true;
}
private static void HandleNadeDamage(CEntityInstance param, CTakeDamageInfo param2)
{
var nade = param2.Attacker?.Value;
if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("barrel_team_")) return;
var parts = nade.Globalname.Split('_');
if (parts.Length < 4) return;
if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle);
if (victimPawn.DesignerName != "player" || victimPawn.Controller?.Value == null) return;
var victim = victimPawn.Controller.Value.As<CCSPlayerController>();
if (victim == null || !victim.IsValid || victim.Index == ownerIndex) return;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, "planted_c4");
return;
}
}
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, "planted_c4");
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
public class PlayerSkillInfo
{
public ulong SteamID { get; set; }
public bool CanUse { get; set; }
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c0392b", 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 = 20f, float explosionRadius = 210f, int explosionDamage = 50, string propModel = "models/props/de_train/hr_t/barrel_a/barrel_a.vmdl", float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float Cooldown { get; set; } = cooldown;
public float ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage;
public string PropModel { get; set; } = propModel;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
}
}
}

View file

@ -11,11 +11,13 @@ namespace src.player.skills
public class FalconEye : ISkill
{
private const Skills skillName = Skills.FalconEye;
private const string cameraViewModel = "models/sprays/spray_plane.vmdl";
private static readonly ConcurrentDictionary<uint, (uint, uint)> cameras = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
Instance.AddToManifest(cameraViewModel);
}
public static void NewRound()
@ -127,7 +129,7 @@ namespace src.player.skills
if (camNode != null)
camNode.Flags = (uint)(camNode.Flags & ~(1 << 2));
camera.SetModel("models/sprays/spray_plane.vmdl");
camera.SetModel(cameraViewModel);
camera.Render = Color.FromArgb(0, 255, 255, 255);
camera.Teleport(pos, new QAngle(90, 0, 0));
camera.DispatchSpawn();

View file

@ -89,6 +89,8 @@ namespace src.player.skills
if (info.TransmitEntities.Contains(entity.Index))
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.
if (bomb == null || bombOwnerIndex != playerController.Index) continue;

View file

@ -0,0 +1,188 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
namespace src.player.skills
{
public class Giant : ISkill
{
private const Skills skillName = Skills.Giant;
private static readonly ConcurrentDictionary<uint, uint> enlargedEnemies = [];
private static readonly object setLock = new();
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
lock (setLock)
{
foreach (var targetIndex in enlargedEnemies.Values.ToArray())
ResetScale(targetIndex);
enlargedEnemies.Clear();
foreach (var player in PlayerManager.GetTickPlayers())
SkillUtils.CloseMenu(player);
}
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
if (enlargedEnemies.TryRemove(playerIndex, out uint targetIndex))
ResetScale(targetIndex);
foreach (var kvp in enlargedEnemies)
if (kvp.Value == playerIndex)
enlargedEnemies.TryRemove(kvp.Key, out _);
}
}
public static void EnableSkill(CCSPlayerController player)
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) return;
playerInfo.SkillUsed = false;
float minScale = SkillsInfo.GetValue<float>(skillName, "minScale");
float maxScale = SkillsInfo.GetValue<float>(skillName, "maxScale");
playerInfo.SkillChance = (float)Math.Round((float)Instance.Random.NextDouble() * (maxScale - minScale) + minScale, 2);
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = GetSelectableEnemies(player);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = [.. 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 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 = GetSelectableEnemies(player);
ConcurrentBag<(string, string)> menuItems = [.. 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 || enemy.Team == player.Team)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;
}
if (PlayerManager.GetPlayerByIndex(enemy.Index)?.Skill == Skills.Chicken)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;
}
float newSize = playerInfo.SkillChance ?? 1f;
lock (setLock)
{
if (enlargedEnemies.TryRemove(player.Index, out uint previousTarget))
ResetScale(previousTarget, notify: true);
SkillUtils.ChangePlayerScale(enemy, newSize);
enlargedEnemies[player.Index] = enemy.Index;
}
playerInfo.SkillUsed = true;
SkillUtils.CloseMenu(player);
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("giant_player_info", enemy.PlayerName, newSize));
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) return;
enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("giant_enemy_info", newSize));
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
lock (setLock)
{
if (enlargedEnemies.TryRemove(player.Index, out uint targetIndex))
ResetScale(targetIndex, notify: true);
SkillUtils.CloseMenu(player);
}
}
private static void ResetScale(uint targetIndex, bool notify = false)
{
var target = Utilities.GetPlayerFromIndex((int)targetIndex);
if (target == null || !target.IsValid) return;
var pawn = target.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.CBodyComponent == null) return;
SkillUtils.ChangePlayerScale(target, 1);
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_CBodyComponent");
if (!notify) return;
var targetEvent = PlayerManager.GetPlayerFromEvent(target);
if (targetEvent != null && targetEvent.IsValid && targetEvent.PawnIsAlive && !SkillUtils.IsFreezeTime())
targetEvent.PrintToChat($" {ChatColors.Green}" + targetEvent.GetTranslation("giant_disable_info"));
}
private static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player)
{
return [.. SkillUtils.GetSelectableEnemies(player, true)
.Where(p => PlayerManager.GetPlayerByIndex(p.Index)?.Skill != Skills.Chicken)];
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8ad3ff", 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 minScale = 1.1f, float maxScale = 1.4f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float MinScale { get; set; } = minScale;
public float MaxScale { get; set; } = maxScale;
}
}
}

View file

@ -39,6 +39,8 @@ namespace src.player.skills
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
{
if (smokes.IsEmpty || playersWithSkill.IsEmpty) return;
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid) continue;

View file

@ -51,11 +51,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
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);
@ -128,7 +129,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));

View file

@ -3,6 +3,7 @@ using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
using static src.jRandomSkills;
namespace src.player.skills
@ -53,12 +54,34 @@ namespace src.player.skills
public static void DisableSkill(CCSPlayerController player)
{
SkillPlayerInfo.TryRemove(player.Index, out _);
SkillPlayerInfo.TryRemove(player.Index, out var skillInfo);
SkillUtils.ResetPrintHTML(player);
var playerPawn = player.PlayerPawn.Value;
if (playerPawn != null && playerPawn.IsValid)
{
playerPawn.TakesDamage = true;
RestoreRender(playerPawn, skillInfo?.OriginalRender);
}
}
private static void SetGodModeRender(CCSPlayerPawn pawn, PlayerSkillInfo skillInfo)
{
if (pawn == null || !pawn.IsValid) return;
var current = pawn.Render;
skillInfo.OriginalRender ??= current;
pawn.Render = Color.FromArgb(current.A, 255, 255, 0);
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
}
private static void RestoreRender(CCSPlayerPawn? pawn, Color? originalRender)
{
if (pawn == null || !pawn.IsValid || originalRender == null) return;
pawn.Render = originalRender.Value;
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
}
private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo)
@ -100,11 +123,17 @@ namespace src.player.skills
PlayerManager.GetPlayerFromEvent(player)?.PrintToChat($" {ChatColors.Green} {player.GetTranslation("godmode_on")}");
player.PlayerPawn.Value.TakesDamage = false;
SetGodModeRender(player.PlayerPawn.Value, skillInfo);
Instance.AddTimer(SkillsInfo.GetValue<float>(skillName, "duration"), () =>
{
Color? originalRender = null;
if (SkillPlayerInfo.TryGetValue(playerIndex, out var skillInfo))
{
skillInfo.HaveGodMode = false;
originalRender = skillInfo.OriginalRender;
skillInfo.OriginalRender = null;
}
var player = Utilities.GetPlayerFromIndex((int)playerIndex);
if (player != null && player.IsValid)
@ -112,6 +141,7 @@ namespace src.player.skills
if (player.PlayerPawn == null || player.PlayerPawn.Value == null || !player.PlayerPawn.Value.IsValid) return;
player.PlayerPawn.Value.TakesDamage = true;
RestoreRender(player.PlayerPawn.Value, originalRender);
PlayerManager.GetPlayerFromEvent(player)?.PrintToChat($" {ChatColors.Red} {player.GetTranslation("godmode_off")}");
if (player.PlayerPawn.Value.Health <= 0)
@ -128,6 +158,7 @@ namespace src.player.skills
public bool CanUse { get; set; }
public bool HaveGodMode { get; set; }
public DateTime Cooldown { get; set; }
public Color? OriginalRender { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e0d83a", 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 = 30f, float duration = 2f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -1,82 +0,0 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
using static src.jRandomSkills;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace src.player.skills
{
public class HealingChicken : ISkill
{
//private const Skills skillName = Skills.HealingChicken;
//private readonly static ConcurrentDictionary<uint, List<Timer>> playerSmokes = [];
//private static readonly object setLock = new();
//public static void LoadSkill()
//{
// SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
//}
//public static void NewRound()
//{
//}
//public static void EnableSkill(CCSPlayerController player)
//{
// SpawnChicken(player);
//}
//public static void DisableSkill(CCSPlayerController player)
//{
//}
//private static void SpawnChicken(CCSPlayerController player)
//{
// if (player == null || !player.IsValid) return;
// var pawn = player.PlayerPawn.Value;
// if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
// int amount = SkillsInfo.GetValue<int>(skillName, "amount");
// for (int i = 0; i < 1; i++)
// {
// CChicken? chicken = EntityManager.CreateTrackedChicken(player.Index);
// if (chicken == null || !chicken.IsValid) continue;
// chicken.Render = Color.Green;
// Vector offset = new (
// (float)(100 * Math.Cos(2 * Math.PI * i / amount)),
// (float)(100 * Math.Sin(2 * Math.PI * i / amount)),
// 0
// );
// chicken.Teleport(pawn.AbsOrigin + offset);
// // Schema.SetSchemaValue(chicken.Handle, "CChicken", "m_leader", player.PlayerPawn.Raw);
// Vector? spawn = SkillUtils.GetSpawnPointVector(player);
// Schema.SetSchemaValue(chicken.Handle, "CChicken", "m_vecPathGoal", spawn);
// Instance.AddTickTimer(1, () =>
// {
// if (chicken == null || !chicken.IsValid) return;
// Server.PrintToChatAll($"{chicken.PathGoal}, {chicken.UpdateTimer.Duration}, {chicken.UpdateTimer.Timescale}");
// }, TimerFlags.REPEAT | TimerFlags.STOP_ON_MAPCHANGE);
// }
//}
//public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#b5ab8f", 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 amount = 3, int heal = 2, int tickCooldown = 16) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
//{
// public int Amount { get; set; } = amount;
// public int Heal { get; set; } = heal;
// public int TickCooldown { get; set; } = tickCooldown;
//}
}
}

View file

@ -50,11 +50,19 @@ namespace src.player.skills
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_iClip1");
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_pReserveAmmo");
SkillUtils.AddHealth(pawn, SkillsInfo.GetValue<int>(skillName, "healthToAdd"));
float effectDuration = SkillsInfo.GetValue<float>(skillName, "effectDuration");
if (effectDuration > 0)
{
pawn.HealthShotBoostExpirationTime = Server.CurrentTime + effectDuration;
Utilities.SetStateChanged(pawn, "CCSPlayerPawn", "m_flHealthShotBoostExpirationTime");
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ded678", 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 healthToAdd = 100) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ded678", 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 healthToAdd = 100, float effectDuration = 1.0f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int HealthToAdd { get; set; } = healthToAdd;
public float EffectDuration { get; set; } = effectDuration;
}
}
}

View file

@ -26,6 +26,8 @@ namespace src.player.skills
if (players.IsEmpty || jRandomSkills.Instance.GameRules?.FreezePeriod == true) return;
int damage = SkillsInfo.GetValue<int>(skillName, "damage");
var owner = GetSkillOwner();
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.Team == CsTeam.Terrorist && p.PlayerPawn?.Value?.Health > 0))
{
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
@ -37,11 +39,22 @@ namespace src.player.skills
bool hasC4 = pawn.WeaponServices.MyWeapons.Any(w => w.Value?.DesignerName == "weapon_c4");
if (!hasC4) continue;
SkillUtils.TakeHealth(pawn, damage);
SkillUtils.TakeHealth(pawn, damage, owner, "planted_c4");
playerEvent?.ExecuteClientCommand($"play player/player_damagebody_0{jRandomSkills.Instance.Random.Next(4, 8)}");
}
}
private static CCSPlayerController? GetSkillOwner()
{
foreach (var ownerIndex in players.Keys)
{
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner != null && owner.IsValid) return owner;
}
return null;
}
public static void WeaponPickup(EventItemPickup @event)
{
var weapon = @event.Item;
@ -49,7 +62,7 @@ namespace src.player.skills
// Gate on the bomb being hot (red), not on `players`: the fresh round's C4 is
// handed out before NewRound clears `players`, but a new C4 spawns white, so colour is race-free.
var bomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
var bomb = PlayerManager.GetTickBomb();
if (bomb == null || !bomb.IsValid) return;
if (bomb.Render.R != 255 || bomb.Render.G != 0 || bomb.Render.B != 0) return;
@ -63,11 +76,8 @@ namespace src.player.skills
{
Server.NextFrame(() =>
{
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
if (bombEntities.Count == 0) return;
var bomb = bombEntities.FirstOrDefault();
if (bomb == null) return;
var bomb = PlayerManager.GetTickBomb();
if (bomb == null || !bomb.IsValid) return;
bomb.Render = color;
Utilities.SetStateChanged(bomb, "CBaseModelEntity", "m_clrRender");

View file

@ -14,6 +14,7 @@ namespace src.player.skills
{
private const Skills skillName = Skills.Iana;
private static readonly ConcurrentDictionary<uint, PlayerSkill> playersInfo = [];
private static readonly ConcurrentDictionary<uint, byte> consumedClones = [];
private static readonly object setLock = new();
public static void LoadSkill()
@ -27,7 +28,10 @@ namespace src.player.skills
KillClone(playerSkill);
lock (setLock)
{
playersInfo.Clear();
consumedClones.Clear();
}
}
public static void WeaponPickup(EventItemPickup @event)
@ -68,7 +72,7 @@ namespace src.player.skills
public static void EnableSkill(CCSPlayerController player)
{
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.CameraServices == null) return;
if (player == null || !player.IsValid) return;
playersInfo.TryAdd(player.Index, new PlayerSkill
{
PlayerIndex = player.Index,
@ -203,6 +207,8 @@ namespace src.player.skills
playerSkill.NextUse = Server.TickCount + 64000;
playerSkill.CloneProp = clone.Index;
consumedClones.TryRemove(clone.Index, out _);
jRandomSkills.Instance.AddTimer(SkillsInfo.GetValue<float>(skillName, "Duration"), () =>
{
if (playerSkill.CloneProp != null)
@ -283,6 +289,19 @@ namespace src.player.skills
return calculatedDamage;
}
private static CCSPlayerController? GetShooter(CTakeDamageInfo info)
{
var attackerEnt = info.Attacker?.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return null;
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return null;
if (attackerPawn.Controller?.Value == null) return null;
var shooter = PlayerManager.GetPlayerEvent(attackerPawn.Controller.Value.As<CCSPlayerController>());
return shooter != null && shooter.IsValid ? shooter : null;
}
public static void OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
@ -327,11 +346,14 @@ namespace src.player.skills
dealDamage = CalculateDamage(player, weapon, damage, isHead);
}
if (playerSkill.CloneProp != null && !consumedClones.TryAdd(playerSkill.CloneProp.Value, 0))
return;
KillClone(playerSkill);
var playerPawn = player.PlayerPawn.Value;
if (playerPawn != null)
SkillUtils.TakeHealth(playerPawn, (int)dealDamage);
SkillUtils.TakeHealth(playerPawn, (int)dealDamage, GetShooter(param2), param2.Ability?.Value?.DesignerName);
}
}
@ -408,9 +430,8 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return false;
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
if (playerSkill.CloneProp == null || playerSkill.Weapons.Contains(econItem.ItemID))
return false;
if (!playersInfo.TryGetValue(player.Index, out var playerSkill)) return false;
if (playerSkill.CloneProp == null || playerSkill.Weapons.Contains(econItem.ItemID)) return false;
hook.SetReturn(AcquireResult.InvalidItem);
return true;

View file

@ -13,6 +13,9 @@ namespace src.player.skills
private static int lastChange = 0;
private static readonly object offsetLock = new();
private static int holdersTick = int.MinValue;
private static readonly HashSet<uint> holders = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
@ -49,17 +52,28 @@ namespace src.player.skills
if (!isActive || player == null || !player.IsValid) return false;
if (player.Team == CsTeam.Spectator) return false;
var playersWithSkill = jRandomSkills.Instance.SkillPlayer.Where(p => p.Skill == skillName).Select(p => p.PlayerIndex).ToHashSet();
if (playersWithSkill.Count == 0) return false;
int tick = Server.TickCount;
if (tick != holdersTick)
{
holdersTick = tick;
holders.Clear();
foreach (var p in jRandomSkills.Instance.SkillPlayer)
if (p.Skill == skillName) holders.Add(p.PlayerIndex);
}
return PlayerManager.GetTickPlayers().Any(
p => p != null &&
p.IsValid &&
p.Pawn?.Value != null &&
p.PlayerPawn?.Value != null &&
p.PlayerPawn.Value.Health > 0 &&
p.Team != player.Team &&
playersWithSkill.Contains(p.Index));
if (holders.Count == 0) return false;
foreach (var p in PlayerManager.GetTickPlayers())
{
if (p == null || !p.IsValid) continue;
if (p.Team == player.Team || !holders.Contains(p.Index)) continue;
if (p.Pawn?.Value == null) continue;
var pawn = p.PlayerPawn?.Value;
if (pawn != null && pawn.Health > 0) return true;
}
return false;
}
public static string? GetRandomText(string? input)

View file

@ -15,6 +15,7 @@ namespace src.player.skills
private const Skills skillName = Skills.Illusionist;
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
private static readonly ConcurrentDictionary<int, Timer> ActiveTimers = [];
private static readonly ConcurrentDictionary<uint, byte> consumedReplicas = [];
private static readonly object setLock = new();
public static void LoadSkill()
@ -26,7 +27,10 @@ namespace src.player.skills
{
ClearAllReplicas();
lock (setLock)
{
SkillPlayerInfo.Clear();
consumedReplicas.Clear();
}
}
private static void ClearAllReplicas()
@ -113,6 +117,8 @@ namespace src.player.skills
var replica = EntityManager.CreateTrackedPropOverride(player.Index);
if (replica == null || !replica.IsValid) return;
consumedReplicas.TryRemove(replica.Index, out _);
replica.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
replica.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(replica.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
@ -189,8 +195,12 @@ namespace src.player.skills
var replica = param.As<CDynamicProp>();
if (replica == null || !replica.IsValid) return;
if (!consumedReplicas.TryAdd(replica.Index, 0)) return;
replica.EmitSound("GlassBottle.BulletImpact", volume: 1f);
if (ActiveTimers.TryRemove((int)replica.Index, out var timer)) timer?.Kill();
var owner = GetReplicaOwner(replica.Index);
EntityManager.DestroyEntity(replica.Index);
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
@ -198,7 +208,17 @@ namespace src.player.skills
var attackerTeam = attackerPawn.TeamNum;
var replicaTeam = replica.Globalname.EndsWith("CT") ? 3 : 2;
SkillUtils.TakeHealth(attackerPawn, attackerTeam != replicaTeam ? SkillsInfo.GetValue<int>(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue<int>(skillName, "YourTeamDamage"));
SkillUtils.TakeHealth(attackerPawn, attackerTeam != replicaTeam ? SkillsInfo.GetValue<int>(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue<int>(skillName, "YourTeamDamage"), owner, "planted_c4");
}
private static CCSPlayerController? GetReplicaOwner(uint replicaIndex)
{
var ownerIndex = EntityManager.GetEntityOwner(replicaIndex);
if (ownerIndex == null) return null;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex.Value);
return owner != null && owner.IsValid ? owner : null;
}
public class PlayerSkillInfo

View file

@ -59,11 +59,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
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);
@ -111,7 +112,7 @@ namespace src.player.skills
SetCrosshair(enemyEvent, false);
playerInfo.SkillUsed = true;
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("jammer_player_info", player.PlayerName));
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("jammer_player_info", enemy.PlayerName));
enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("jammer_enemy_info"));
}
@ -150,7 +151,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));

View file

@ -120,28 +120,19 @@ namespace src.player.skills
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
public static void PlayerHurt(EventPlayerHurt @event)
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(victim)) return false;
if (!Instance.IsPlayerValid(victim)) return;
if (!Instance.IsPlayerValid(attacker))
if (!IsActiveJester(victim!.Index))
{
if (IsActiveJester(victim!.Index))
{
SkillUtils.RestoreHealth(victim);
RestoreArmor(victim, @event.DmgArmor);
}
return;
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
if (!Instance.IsPlayerValid(attacker) || !IsActiveJester(attacker!.Index)) return false;
}
if (IsActiveJester(victim!.Index) || IsActiveJester(attacker!.Index))
{
SkillUtils.RestoreHealth(victim);
RestoreArmor(victim, @event.DmgArmor);
}
SkillUtils.RestoreHealth(victim);
return true;
}
public static bool IsActiveJester(uint playerIndex)
@ -150,17 +141,6 @@ namespace src.player.skills
return PlayerManager.GetPlayerByIndex(playerIndex)?.Skill == skillName;
}
private static void RestoreArmor(CCSPlayerController? victim, int dmgArmor)
{
if (victim == null || !victim.IsValid || dmgArmor <= 0) return;
var pawn = victim.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) return;
pawn.ArmorValue += dmgArmor;
Utilities.SetStateChanged(pawn, "CCSPlayerPawn", "m_ArmorValue");
}
public static void EnableSkill(CCSPlayerController player)
{
var minTime = SkillsInfo.GetValue<float>(skillName, "minTime");

View file

@ -64,11 +64,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
SkillUtils.UpdateMenu(player, menuItems);
@ -126,7 +127,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));

View file

@ -0,0 +1,177 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class JumpCurse : ISkill
{
private const Skills skillName = Skills.JumpCurse;
private static readonly ConcurrentDictionary<uint, byte> cursedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerDisconnect(uint playerIndex)
{
cursedPlayers.TryRemove(playerIndex, out _);
playersToTarget.TryRemove(playerIndex, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
public static void NewRound()
{
cursedPlayers.Clear();
playersToTarget.Clear();
foreach (var player in PlayerManager.GetTickPlayers())
SkillUtils.CloseMenu(player);
}
public static void PlayerJump(EventPlayerJump @event)
{
if (cursedPlayers.IsEmpty) return;
var jumper = PlayerManager.GetPlayerEvent(@event.Userid);
if (jumper == null || !jumper.IsValid) return;
if (jumper.Team != CsTeam.Terrorist && jumper.Team != CsTeam.CounterTerrorist) return;
var jumperPawn = jumper.PlayerPawn?.Value;
if (jumperPawn == null || !jumperPawn.IsValid) return;
if (jumperPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || jumperPawn.Health <= 0) return;
float jumpVelocity = SkillsInfo.GetValue<float>(skillName, "jumpVelocity");
foreach (var cursedIndex in cursedPlayers.Keys)
{
if (cursedIndex == jumper.Index) continue;
var cursed = PlayerManager.GetPlayerEvent(Utilities.GetPlayerFromIndex((int)cursedIndex));
if (cursed == null || !cursed.IsValid) continue;
if (cursed.Index == jumper.Index || cursed.Team != jumper.Team) continue;
var pawn = cursed.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) continue;
if (pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || pawn.Health <= 0) continue;
if ((pawn.Flags & (uint)PlayerFlags.FL_ONGROUND) == 0) continue;
pawn.AbsVelocity.Z = jumpVelocity;
}
}
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 = [.. 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;
}
string enemyId = commands[0];
if (!uint.TryParse(enemyId, 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 || enemy.PlayerPawn.Value == null || !enemy.PlayerPawn.Value.IsValid)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;
}
cursedPlayers.TryAdd(enemy.Index, 0);
playersToTarget[player.Index] = enemy.Index;
playerInfo.SkillUsed = true;
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) return;
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("jumpcurse_player_info", enemy.PlayerName));
enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("jumpcurse_enemy_info"));
}
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 = [.. 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 (playersToTarget.TryRemove(player.Index, out uint targetIndex))
{
cursedPlayers.TryRemove(targetIndex, out _);
var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex));
if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime())
target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("jumpcurse_disable_info"));
}
SkillUtils.CloseMenu(player);
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return;
cursedPlayers.TryRemove(player.Index, out _);
SkillUtils.CloseMenu(player);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#7ad1c4", 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 = 301f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float JumpVelocity { get; set; } = jumpVelocity;
}
}
}

View file

@ -30,7 +30,7 @@ namespace src.player.skills
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire") && player!.Team == attacker!.Team) return;
if (attackerInfo?.Skill == skillName && playerInfo?.Skill != Skills.AntyFlash && player!.PlayerPawn.Value!.FlashDuration >= SkillsInfo.GetValue<float>(skillName, "flashDuration"))
SkillUtils.TakeHealth(player.PlayerPawn.Value, 9999);
SkillUtils.TakeHealth(player.PlayerPawn.Value, 9999, attacker, "weapon_flashbang");
}
public static void GrenadeThrown(EventGrenadeThrown @event)

View file

@ -0,0 +1,56 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
namespace src.player.skills
{
public class Knockback : ISkill
{
private const Skills skillName = Skills.Knockback;
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void WeaponFire(EventWeaponFire @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player.Index);
if (playerInfo?.Skill != skillName) return;
if (!SkillUtils.FiresBullets(@event.Weapon)) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.Health <= 0) return;
if ((pawn.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0) return;
float force = SkillsInfo.GetValue<float>(skillName, "knockbackUnits");
if (force <= 0) return;
Vector push = SkillUtils.GetForwardVector(pawn.EyeAngles) * -force;
pawn.AbsVelocity.X += push.X;
pawn.AbsVelocity.Y += push.Y;
pawn.AbsVelocity.Z += push.Z;
float maxSpeed = SkillsInfo.GetValue<float>(skillName, "maxSpeed");
if (maxSpeed <= 0) return;
float speed = pawn.AbsVelocity.Length();
if (speed <= maxSpeed) return;
float scale = maxSpeed / speed;
pawn.AbsVelocity.X *= scale;
pawn.AbsVelocity.Y *= scale;
pawn.AbsVelocity.Z *= scale;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff8c42", 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 knockbackUnits = 120f, float maxSpeed = 1200f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float KnockbackUnits { get; set; } = knockbackUnits;
public float MaxSpeed { get; set; } = maxSpeed;
}
}
}

View file

@ -26,11 +26,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e?.PlayerPawn?.Value?.Health ?? 0} HP", e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems);
@ -85,7 +86,7 @@ namespace src.player.skills
if (playerInfo == null) return;
playerInfo.SkillUsed = false;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e.PawnHealth} HP", e.Index.ToString())));

View file

@ -119,7 +119,7 @@ namespace src.player.skills
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire") && player.Team == target.Team) return;
target.PlayerPawn.Value.EmitSound("Player.DamageBody.Onlooker");
SkillUtils.TakeHealth(target.PlayerPawn.Value, heavyHit ? Instance.Random.Next(45, 55) : Instance.Random.Next(21, 34));
SkillUtils.TakeHealth(target.PlayerPawn.Value, heavyHit ? Instance.Random.Next(45, 55) : Instance.Random.Next(21, 34), player, "weapon_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)

View file

@ -39,7 +39,7 @@ namespace src.player.skills
if (target.Handle == player.Handle) return;
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire") && player.Team == target.Team) return;
SkillUtils.TakeHealth(target.PlayerPawn.Value, 9999);
SkillUtils.TakeHealth(target.PlayerPawn.Value, 9999, player, "weapon_taser");
}
public static void EnableSkill(CCSPlayerController player)

View file

@ -48,10 +48,11 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p.PawnIsAlive && p.Team != player.Team && p.IsValid && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
@ -127,7 +128,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));

View file

@ -26,11 +26,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {(e.InGameMoneyServices == null ? 0 : e.InGameMoneyServices.Account + e.InGameMoneyServices.CashSpentThisRound)}$", e.Index.ToString()))];
SkillUtils.UpdateMenu(player, menuItems);
@ -84,7 +85,7 @@ namespace src.player.skills
if (playerInfo == null) return;
playerInfo.SkillUsed = false;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {(e.InGameMoneyServices == null ? 0 : e.InGameMoneyServices.Account + e.InGameMoneyServices.CashSpentThisRound)}$", e.Index.ToString()))];

View file

@ -0,0 +1,236 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
namespace src.player.skills
{
public class Nightmare : ISkill
{
private const Skills skillName = Skills.Nightmare;
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetVolumes = [];
private static readonly object setLock = new();
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
jRandomSkills.Instance.AddToManifest(SkillsInfo.GetValue<string>(skillName, "postProcessing"));
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
RemoveVolume(playerIndex);
playersToTarget.TryRemove(playerIndex, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
}
public static void NewRound()
{
lock (setLock)
{
foreach (var targetIndex in targetVolumes.Keys.ToArray())
RemoveVolume(targetIndex);
foreach (var player in PlayerManager.GetTickPlayers())
SkillUtils.CloseMenu(player);
targetVolumes.Clear();
playersToTarget.Clear();
}
}
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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
SkillUtils.UpdateMenu(player, menuItems);
}
}
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 = [.. 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 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 (!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;
}
lock (setLock)
{
if (!ApplyVolume(player.Index, enemy))
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;
}
playersToTarget[player.Index] = enemy.Index;
}
playerInfo.SkillUsed = true;
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("nightmare_player_info", enemy.PlayerName));
if (enemyEvent != null && enemyEvent.IsValid)
enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("nightmare_enemy_info"));
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
lock (setLock)
{
if (playersToTarget.TryRemove(player.Index, out uint targetIndex))
{
RemoveVolume(targetIndex);
var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex));
if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime())
target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("nightmare_disable_info"));
}
SkillUtils.CloseMenu(player);
}
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return;
lock (setLock)
RemoveVolume(player.Index);
}
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
{
if (targetVolumes.IsEmpty) return;
foreach (var (info, viewer) in infoList)
{
if (viewer == null || !viewer.IsValid) continue;
foreach (var kvp in targetVolumes)
{
if (kvp.Key == viewer.Index) continue;
if (info.TransmitEntities.Contains(kvp.Value))
info.TransmitEntities.Remove(kvp.Value);
}
}
}
private static bool ApplyVolume(uint ownerIndex, CCSPlayerController target)
{
RemoveVolume(target.Index);
var targetPawn = target.PlayerPawn?.Value;
if (targetPawn == null || !targetPawn.IsValid || targetPawn.AbsOrigin == null) return false;
if (EntityManager.OverBudget()) return false;
var volume = Utilities.CreateEntityByName<CPostProcessingVolume>("post_processing_volume");
if (volume == null || !volume.IsValid || volume.Entity == null) return false;
var keys = new CEntityKeyValues();
try
{
keys.SetString("targetname", $"Nightmare_{target.Index}");
keys.SetString("postprocessing", SkillsInfo.GetValue<string>(skillName, "postProcessing"));
keys.SetBool("master", true);
keys.SetBool("enableexposure", true);
keys.SetFloat("fadetime", SkillsInfo.GetValue<float>(skillName, "fadeTime"));
keys.SetFloat("minexposure", SkillsInfo.GetValue<float>(skillName, "minExposure"));
keys.SetFloat("maxexposure", SkillsInfo.GetValue<float>(skillName, "maxExposure"));
keys.SetFloat("exposurespeedup", 1f);
keys.SetFloat("exposurespeeddown", 1f);
keys.SetBool("startdisabled", false);
keys.SetInt("spawnflags", 4097);
keys.SetVector("origin", targetPawn.AbsOrigin);
volume.DispatchSpawn(keys);
}
finally
{
keys.Dispose();
}
if (!volume.IsValid) return false;
volume.AcceptInput("SetParent", targetPawn, null, "!activator");
EntityManager.RegisterExisting(volume, ownerIndex, "post_processing_volume");
targetVolumes[target.Index] = volume.Index;
return true;
}
private static void RemoveVolume(uint targetIndex)
{
if (!targetVolumes.TryRemove(targetIndex, out uint volumeIndex)) return;
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 string PostProcessing { get; set; } = postProcessing;
public float FadeTime { get; set; } = fadeTime;
public float MinExposure { get; set; } = minExposure;
public float MaxExposure { get; set; } = maxExposure;
}
}
}

View file

@ -91,6 +91,8 @@ namespace src.player.skills
if (info.TransmitEntities.Contains(entity.Index))
info.TransmitEntities.Remove(entity.Index);
SkillUtils.HideCarriedEntities(info, playerPawn);
if (bomb == null || bombOwnerIndex != playerController.Index) continue;
if (info.TransmitEntities.Contains(bomb.Index))

View file

@ -15,18 +15,19 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerHurt(EventPlayerHurt @event)
private static readonly HashSet<string> blockedWeapons =
["hegrenade", "inferno", "decoy", "flashbang", "smokegrenade", "molotov"];
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var damage = @event.DmgHealth;
var player = PlayerManager.GetPlayerEvent(@event.Userid);
var weapon = @event.Weapon;
if (!Instance.IsPlayerValid(player)) return false;
if (!Instance.IsPlayerValid(player)) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
if (!blockedWeapons.Contains(@event.Weapon)) return false;
if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != skillName) return false;
if (weapon == "hegrenade" || weapon == "inferno" || weapon == "decoy" || weapon == "flashbang" || weapon == "smokegrenade" || weapon == "molotov")
SkillUtils.RestoreHealth(player);
SkillUtils.RestoreHealth(player);
return true;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#a38c1a", 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) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -1,5 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
@ -14,9 +15,12 @@ namespace src.player.skills
private static readonly ConcurrentDictionary<uint, byte> holders = [];
private static bool noSpreadActive;
private static bool defaultNoSpread;
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
defaultNoSpread = ConVar.Find("weapon_accuracy_nospread")?.GetPrimitiveValue<bool>() ?? false;
}
public static void NewRound()
@ -47,7 +51,8 @@ namespace src.player.skills
if (noSpreadActive == enabled) return;
noSpreadActive = enabled;
Server.ExecuteCommand($"weapon_accuracy_nospread {(enabled ? 1 : 0)}");
bool value = enabled || defaultNoSpread;
Server.ExecuteCommand($"weapon_accuracy_nospread {(value ? 1 : 0)}");
}
public static void OnTick()

View file

@ -15,20 +15,17 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerHurt(EventPlayerHurt @event)
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
int damage = @event.DmgHealth;
var hitgroup = (HitGroup_t)@event.Hitgroup;
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return;
var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index);
if (victimInfo == null || victimInfo.Skill != skillName) return;
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return false;
if ((HitGroup_t)@event.Hitgroup == HitGroup_t.HITGROUP_HEAD) return false;
if (PlayerManager.GetPlayerByIndex(victim!.Index)?.Skill != skillName) return false;
HitGroup_t[] disabledHitbox = [HitGroup_t.HITGROUP_LEFTARM, HitGroup_t.HITGROUP_RIGHTARM, HitGroup_t.HITGROUP_LEFTLEG, HitGroup_t.HITGROUP_RIGHTLEG];
if (hitgroup != HitGroup_t.HITGROUP_HEAD)
SkillUtils.RestoreHealth(victim);
SkillUtils.RestoreHealth(victim);
return true;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#3c47de", 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) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -19,6 +19,22 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"), false);
}
public static void NewRound()
{
phoenixTicks.Clear();
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
phoenixTicks.TryRemove(player.Index, out _);
}
public static void PlayerDisconnect(uint playerIndex)
{
phoenixTicks.TryRemove(playerIndex, out _);
}
public static void OnTakeDamage(DynamicHook h)
{
var victimEntity = h.GetParam<CEntityInstance>(0);

View file

@ -11,6 +11,7 @@ namespace src.player.skills
private const Skills skillName = Skills.Poison;
private static readonly ConcurrentDictionary<uint, byte> poisonedPlayers = [];
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetToPlayer = [];
private static readonly object setLock = new();
public static void LoadSkill()
@ -23,7 +24,10 @@ namespace src.player.skills
lock (setLock)
{
poisonedPlayers.TryRemove(playerIndex, out _);
playersToTarget.TryRemove(playerIndex, out _);
targetToPlayer.TryRemove(playerIndex, out _);
if (playersToTarget.TryRemove(playerIndex, out uint ownTarget))
targetToPlayer.TryRemove(ownTarget, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
@ -37,6 +41,7 @@ namespace src.player.skills
{
poisonedPlayers.Clear();
playersToTarget.Clear();
targetToPlayer.Clear();
}
}
@ -59,7 +64,8 @@ namespace src.player.skills
if (Jester.IsActiveJester(playerIndex)) continue;
if (pawn.Health <= SkillsInfo.GetValue<int>(skillName, "MinHealth")) continue;
SkillUtils.TakeHealth(pawn, SkillsInfo.GetValue<int>(skillName, "Damage"));
SkillUtils.TakeHealth(pawn, SkillsInfo.GetValue<int>(skillName, "Damage"), GetSkillOwner(playerIndex), "planted_c4");
if (Server.TickCount % cooldown2 == 0)
PlayerManager.GetPlayerFromEvent(player)?.ExecuteClientCommand($"play player/player_damagebody_0{jRandomSkills.Instance.Random.Next(4, 8)}");
@ -69,11 +75,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
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);
@ -113,6 +120,7 @@ namespace src.player.skills
poisonedPlayers.TryAdd(enemy.Index, 0);
playersToTarget[player.Index] = enemy.Index;
targetToPlayer[enemy.Index] = player.Index;
playerInfo.SkillUsed = true;
var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy);
@ -131,7 +139,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
@ -141,11 +149,22 @@ namespace src.player.skills
playerEvent.PrintToChat($" {ChatColors.Red}{player.GetTranslation("selectplayerskill_incorrect_enemy_index")}");
}
private static CCSPlayerController? GetSkillOwner(uint targetIndex)
{
if (!targetToPlayer.TryGetValue(targetIndex, out uint ownerIndex)) return null;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
return owner != null && owner.IsValid ? owner : null;
}
public static void DisableSkill(CCSPlayerController player)
{
targetToPlayer.TryRemove(player.Index, out _);
if (playersToTarget.TryRemove(player.Index, out uint targetIndex))
{
poisonedPlayers.TryRemove(targetIndex, out _);
targetToPlayer.TryRemove(targetIndex, out _);
var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex));
if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime())
@ -161,6 +180,8 @@ namespace src.player.skills
if (player == null || !player.IsValid) return;
poisonedPlayers.TryRemove(player.Index, out _);
targetToPlayer.TryRemove(player.Index, out _);
SkillUtils.CloseMenu(player);
}

View file

@ -70,11 +70,12 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
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);
@ -133,7 +134,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));

View file

@ -14,19 +14,20 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerHurt(EventPlayerHurt @event)
private static readonly HitGroup_t[] disabledHitbox =
[HitGroup_t.HITGROUP_LEFTARM, HitGroup_t.HITGROUP_RIGHTARM, HitGroup_t.HITGROUP_LEFTLEG, HitGroup_t.HITGROUP_RIGHTLEG];
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
var hitgroup = (HitGroup_t)@event.Hitgroup;
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return;
var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index);
if (victimInfo == null || victimInfo.Skill != skillName) return;
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return false;
if (Array.IndexOf(disabledHitbox, (HitGroup_t)@event.Hitgroup) < 0) return false;
if (PlayerManager.GetPlayerByIndex(victim!.Index)?.Skill != skillName) return false;
HitGroup_t[] disabledHitbox = [HitGroup_t.HITGROUP_LEFTARM, HitGroup_t.HITGROUP_RIGHTARM, HitGroup_t.HITGROUP_LEFTLEG, HitGroup_t.HITGROUP_RIGHTLEG];
if (disabledHitbox.Contains(hitgroup))
SkillUtils.RestoreHealth(victim);
SkillUtils.RestoreHealth(victim);
return true;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9c9c9c", 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) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -120,10 +120,26 @@ namespace src.player.skills
SetPlayerColor(victimPawn, false);
SkillUtils.SetHealth(victimPawn, zombieHealth, zombieHealth);
victim.ExecuteClientCommand("slot3");
ApplyTurnTint(victim);
return true;
}
}
private static void ApplyTurnTint(CCSPlayerController victim)
{
int alpha = SkillsInfo.GetValue<int>(skillName, "A");
if (alpha <= 0) return;
SkillUtils.ApplyScreenColor(victim,
r: SkillsInfo.GetValue<int>(skillName, "R"),
g: SkillsInfo.GetValue<int>(skillName, "G"),
b: SkillsInfo.GetValue<int>(skillName, "B"),
a: alpha,
duration: 200,
holdTime: 600);
}
private static float GetHeadshotMultiplier(CTakeDamageInfo info)
{
var ability = info.Ability?.Value;
@ -188,9 +204,13 @@ namespace src.player.skills
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff5C0A", 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 zombieHealth = 500) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff5C0A", 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 zombieHealth = 500, int r = 255, int g = 0, int b = 0, int a = 60) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int ZombieHealth { get; set; } = zombieHealth;
public int R { get; set; } = r;
public int G { get; set; } = g;
public int B { get; set; } = b;
public int A { get; set; } = a;
}
}
}

View file

@ -34,26 +34,25 @@ namespace src.player.skills
}
}
public static void PlayerHurt(EventPlayerHurt @event)
public static bool PlayerHurtPre(EventPlayerHurt @event)
{
var victim = @event.Userid;
if (victim == null || !victim.IsValid) return;
if (victim == null || !victim.IsValid || !victim.PawnIsAlive) return false;
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
if (attacker == null || !attacker.IsValid) return false;
var victimEvent = PlayerManager.GetPlayerEvent(victim);
if (victimEvent == null || !victimEvent.IsValid) return false;
if (attacker == null || !attacker.IsValid) return;
if (victimEvent == null || !victimEvent.IsValid || !victim.PawnIsAlive) return;
if (PlayerManager.GetPlayerByIndex(victimEvent.Index)?.Skill != skillName) return false;
if (PlayerManager.GetPlayerByIndex(victimEvent!.Index)?.Skill != skillName) return;
if (!SkillPlayerInfo.TryGetValue(victimEvent.Index, out var skillInfo) || !skillInfo.CanUse) return false;
if (SkillPlayerInfo.TryGetValue(victimEvent!.Index, out var skillInfo))
{
if (!skillInfo.CanUse) return;
skillInfo.CanUse = false;
skillInfo.Cooldown = DateTime.Now;
SkillUtils.RestoreHealth(victimEvent);
}
skillInfo.CanUse = false;
skillInfo.Cooldown = DateTime.Now;
SkillUtils.RestoreHealth(victimEvent);
return true;
}
public static void EnableSkill(CCSPlayerController player)

View file

@ -13,6 +13,7 @@ namespace src.player.skills
{
private const Skills skillName = Skills.Replicator;
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
private static readonly ConcurrentDictionary<uint, byte> consumedReplicas = [];
private static readonly object setLock = new();
public static void LoadSkill()
@ -24,7 +25,10 @@ namespace src.player.skills
{
ClearAllReplicas();
lock (setLock)
{
SkillPlayerInfo.Clear();
consumedReplicas.Clear();
}
}
private static void ClearAllReplicas()
@ -112,6 +116,8 @@ namespace src.player.skills
if (replica == null || !replica.IsValid)
return;
consumedReplicas.TryRemove(replica.Index, out _);
replica.Flags = playerPawn.Flags;
replica.Flags |= (uint)Flags_t.FL_DUCKING;
replica.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
@ -143,7 +149,12 @@ namespace src.player.skills
var replica = param.As<CPhysicsPropMultiplayer>();
if (replica == null || !replica.IsValid) return;
if (!consumedReplicas.TryAdd(replica.Index, 0)) return;
replica.EmitSound("GlassBottle.BulletImpact", volume: 1f);
var owner = GetReplicaOwner(replica.Index);
EntityManager.DestroyEntity(replica.Index);
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
@ -152,7 +163,17 @@ namespace src.player.skills
var attackerTeam = attackerPawn.TeamNum;
var replicaTeam = replica.Globalname.EndsWith("CT") ? 3 : 2;
SkillUtils.TakeHealth(attackerPawn, attackerTeam != replicaTeam ? SkillsInfo.GetValue<int>(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue<int>(skillName, "YourTeamDamage"));
SkillUtils.TakeHealth(attackerPawn, attackerTeam != replicaTeam ? SkillsInfo.GetValue<int>(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue<int>(skillName, "YourTeamDamage"), owner, "planted_c4");
}
private static CCSPlayerController? GetReplicaOwner(uint replicaIndex)
{
var ownerIndex = EntityManager.GetEntityOwner(replicaIndex);
if (ownerIndex == null) return null;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex.Value);
return owner != null && owner.IsValid ? owner : null;
}
public class PlayerSkillInfo

View file

@ -42,15 +42,11 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill == skillName)
{
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
if (bombEntities.Count != 0)
var bomb = PlayerManager.GetTickBomb();
if (bomb != null && bomb.IsValid)
{
var bomb = bombEntities.FirstOrDefault();
if (bomb != null)
{
bomb.BombPlacedAnimation = false;
bomb.ArmedTime = 0.0f;
}
bomb.BombPlacedAnimation = false;
bomb.ArmedTime = 0.0f;
}
}
}

View file

@ -31,6 +31,8 @@ namespace src.player.skills
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return;
if (attacker!.Index == victim!.Index) return;
var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index);
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);

View file

@ -11,11 +11,14 @@ namespace src.player.skills
public class Spectator : ISkill
{
private const Skills skillName = Skills.Spectator;
private const string cameraViewModel = "models/sprays/spray_plane.vmdl";
private static readonly ConcurrentDictionary<uint, (uint, uint, uint)> cameras = [];
private static readonly ConcurrentDictionary<uint, DateTime> lastUse = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
Instance.AddToManifest(cameraViewModel);
}
public static void NewRound()
@ -24,6 +27,15 @@ namespace src.player.skills
EntityManager.DestroyEntity(info.Value.Item2);
cameras.Clear();
lastUse.Clear();
}
public static void PlayerDisconnect(uint playerIndex)
{
if (cameras.TryRemove(playerIndex, out var cameraInfo) && cameraInfo.Item2 != 0)
EntityManager.DestroyEntity(cameraInfo.Item2);
lastUse.TryRemove(playerIndex, out _);
}
public static void WeaponPickup(EventItemPickup @event)
@ -43,6 +55,11 @@ namespace src.player.skills
{
var playerPawn = player.PlayerPawn.Value;
if (playerPawn?.CBodyComponent == null) return;
float cooldown = SkillsInfo.GetValue<float>(skillName, "useCooldown");
if (lastUse.TryGetValue(player.Index, out var last) && (DateTime.Now - last).TotalSeconds < cooldown) return;
lastUse[player.Index] = DateTime.Now;
ChangeCamera(player);
}
@ -52,6 +69,7 @@ namespace src.player.skills
ChangeCamera(player, true);
EntityManager.DestroyPlayerEntities(player.Index);
cameras.TryRemove(player.Index, out _);
lastUse.TryRemove(player.Index, out _);
}
public static void OnTick()
@ -139,12 +157,19 @@ namespace src.player.skills
p.PlayerPawn.Value.Health > 0).ToList();
if (enemies.Count == 0)
{
EntityManager.DestroyEntity(camera.Index);
return 0;
}
var enemy = enemies[Instance.Random.Next(enemies.Count)];
var pawn = enemy.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid || pawn.CameraServices == null || pawn.AbsOrigin == null) return 0;
if (pawn == null || !pawn.IsValid || pawn.CameraServices == null || pawn.AbsOrigin == null)
{
EntityManager.DestroyEntity(camera.Index);
return 0;
}
QAngle angle = new(0, pawn.EyeAngles.Y, 0);
@ -159,7 +184,7 @@ namespace src.player.skills
if (camNode != null)
camNode.Flags = (uint)(camNode.Flags & ~(1 << 2));
camera.SetModel("models/sprays/spray_plane.vmdl");
camera.SetModel(cameraViewModel);
camera.Render = Color.FromArgb(1, 255, 255, 255);
camera.Teleport(pos, angle);
camera.DispatchSpawn();
@ -202,9 +227,10 @@ namespace src.player.skills
}
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5da", 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 distance = 100f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5da", 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 distance = 100f, float useCooldown = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float Distance { get; set; } = distance;
public float UseCooldown { get; set; } = useCooldown;
}
}
}

View file

@ -25,21 +25,7 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p =>
p != null &&
p.IsValid)
.Select(p => PlayerManager.GetPlayerEvent(p))
.Where(p =>
p != null &&
p.IsValid &&
p.Team != player.Team &&
p.PlayerPawn?.Value != null &&
p.PlayerPawn.Value.IsValid &&
p.PlayerPawn.Value.Health > 0 &&
!p.IsHLTV &&
p.Team != CsTeam.Spectator
&& p.Team != CsTeam.None
).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = [];
foreach (var enemy in enemies)
@ -106,14 +92,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers()
.Where(p => p != null
&& p.IsValid
&& p.Team != player.Team
&& p.PlayerPawn?.Value?.Health > 0
&& p.Team != CsTeam.Spectator
&& p.Team != CsTeam.None)
.ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{

View file

@ -10,12 +10,14 @@ namespace src.player.skills
public class ThirdEye : ISkill
{
private const Skills skillName = Skills.ThirdEye;
private const string cameraViewModel = "models/sprays/spray_plane.vmdl";
private static readonly ConcurrentDictionary<uint, (uint, uint)> cameras = [];
private static readonly object setLock = new();
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
jRandomSkills.Instance.AddToManifest(cameraViewModel);
}
public static void NewRound()
@ -114,7 +116,7 @@ namespace src.player.skills
if (camNode != null)
camNode.Flags = (uint)(camNode.Flags & ~(1 << 2));
camera.SetModel("models/sprays/spray_plane.vmdl");
camera.SetModel(cameraViewModel);
camera.Render = Color.FromArgb(0, 255, 255, 255);
camera.Teleport(pawn.AbsOrigin, pawn.EyeAngles);
camera.DispatchSpawn();

View file

@ -38,7 +38,7 @@ namespace src.player.skills
int damage = (int)(@event.DmgHealth * SkillsInfo.GetValue<float>(skillName, "healthTakenScale"));
damage = Math.Min(damage, SkillsInfo.GetValue<int>(skillName, "maxTakenDamagePerShot"));
SkillUtils.TakeHealth(attackerEvent.PlayerPawn.Value, damage);
SkillUtils.TakeHealth(attackerEvent.PlayerPawn.Value, damage, victimEvent, "planted_c4");
attackerEvent.EmitSound("Player.DamageBody.Onlooker");
}
}

View file

@ -24,7 +24,21 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
private static bool roundEnded;
public static void NewRound()
{
roundEnded = false;
KillAllKnives();
}
public static void RoundEnd()
{
roundEnded = true;
KillAllKnives();
}
private static void KillAllKnives()
{
foreach (var knifeInfo in knivesInfo.Values.ToArray())
DisableKnifeSkill(knifeInfo);
@ -83,14 +97,16 @@ namespace src.player.skills
if (knifeInfo.Timer != null)
knifeInfo.Timer?.Kill();
uint knifeIndex = knife.Index;
knifeInfo.Timer = Instance.AddTimer(3f, () =>
{
if (knife == null || !knife.IsValid) return;
var droppedKnife = Utilities.GetEntityFromIndex<CBaseEntity>((int)knifeIndex);
if (droppedKnife == null || !droppedKnife.IsValid) return;
var player = Utilities.GetPlayerFromIndex((int)knifeInfo.PlayerIndex);
if (player != null && player.IsValid && CheckHasKnife(player)) return;
knife.AcceptInput("ClearParent");
droppedKnife.AcceptInput("ClearParent");
}, TimerFlags.STOP_ON_MAPCHANGE);
}
@ -109,7 +125,10 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return false;
if (knivesInfo.TryGetValue(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null && knifeInfo.KnifeId == econItem.ItemID)
if (!knivesInfo.TryGetValue(player.Index, out KnifeInfo? knifeInfo) || knifeInfo == null)
return false;
if (knifeInfo.KnifeId == econItem.ItemID)
{
knifeInfo.IsDropped = false;
if (knifeInfo.Timer != null)
@ -121,6 +140,8 @@ namespace src.player.skills
return false;
}
if (!knifeInfo.IsDropped) return false;
hook.SetReturn(AcquireResult.InvalidItem);
return true;
}
@ -162,12 +183,20 @@ namespace src.player.skills
SkillUtils.SafeKillEntity<CTriggerMultiple>(trigger);
SkillUtils.SafeKillEntity<CBaseEntity>(glow);
SkillUtils.SafeKillEntity<CBaseEntity>(relay);
if (knifeInfo.IsDropped)
{
knifeInfo.IsDropped = false;
EntityManager.DestroyEntity(knifeInfo.KnifeIndex, 0.25f);
}
}
public static void UseSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
if (roundEnded) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
@ -183,7 +212,7 @@ namespace src.player.skills
Instance.AddTickTimer(8, () =>
{
if (player == null || !player.IsValid) return;
if (player == null || !player.IsValid || roundEnded) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.WeaponServices == null) return;
@ -194,7 +223,7 @@ namespace src.player.skills
player.DropActiveWeapon();
Server.NextFrame(() =>
{
if (player == null || !player.IsValid) return;
if (player == null || !player.IsValid || roundEnded) return;
if (weapon == null || !weapon.IsValid) return;
ThrowKnife(player, weapon);
});
@ -331,7 +360,7 @@ namespace src.player.skills
if (CheckHasKnife(thrower!)) return;
SkillUtils.TakeHealth(victimPawn, SkillsInfo.GetValue<int>(skillName, "damage"));
SkillUtils.TakeHealth(victimPawn, SkillsInfo.GetValue<int>(skillName, "damage"), thrower, "weapon_knife");
}
}

View file

@ -10,7 +10,7 @@ namespace src.player.skills
public class ToxicSmoke : ISkill
{
private const Skills skillName = Skills.ToxicSmoke;
private static readonly ConcurrentDictionary<Vector, byte> smokes = [];
private static readonly ConcurrentDictionary<Vector, uint> smokes = [];
private readonly static ConcurrentDictionary<uint, int> playersWithSkill = [];
public static void LoadSkill()
@ -31,7 +31,7 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
smokes.TryAdd(new Vector(@event.X, @event.Y, @event.Z), 0);
smokes.TryAdd(new Vector(@event.X, @event.Y, @event.Z), player.Index);
}
public static void SmokegrenadeExpired(EventSmokegrenadeExpired @event)
@ -81,7 +81,11 @@ namespace src.player.skills
float smokeRadius = SkillsInfo.GetValue<float>(skillName, "smokeRadius");
int smokeDamage = SkillsInfo.GetValue<int>(skillName, "smokeDamage");
foreach (Vector smokePos in smokes.Keys)
foreach (var smoke in smokes)
{
var thrower = Utilities.GetPlayerFromIndex((int)smoke.Value);
if (thrower != null && !thrower.IsValid) thrower = null;
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p.IsValid))
{
var eventPlayer = PlayerManager.GetPlayerEvent(player);
@ -90,10 +94,11 @@ namespace src.player.skills
var pawn = eventPlayer.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) continue;
if (SkillUtils.GetDistance(smokePos, pawn.AbsOrigin) <= smokeRadius)
if (SkillUtils.TakeHealth(pawn, smokeDamage))
if (SkillUtils.GetDistance(smoke.Key, pawn.AbsOrigin) <= smokeRadius)
if (SkillUtils.TakeHealth(pawn, smokeDamage, thrower, "smokegrenade"))
player.EmitSound("Player.DamageBody.Onlooker");
}
}
}
public static void GrenadeThrown(EventGrenadeThrown @event)

View file

@ -0,0 +1,327 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using RayTraceAPI;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace src.player.skills
{
public class Tripwire : ISkill
{
private const Skills skillName = Skills.Tripwire;
private static readonly ConcurrentDictionary<uint, WireInfo> wires = [];
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
private static readonly ConcurrentDictionary<(uint Owner, uint Target), (int Slot, int ExpiryTick)> revealed = [];
private static readonly object setLock = new();
private static readonly Color terroristWire = Color.FromArgb(255, 255, 64, 64);
private static readonly Color counterTerroristWire = Color.FromArgb(255, 64, 128, 255);
private const double attemptCooldown = .5;
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
lock (setLock)
{
foreach (var wire in wires.Values)
DestroyWire(wire.BeamIndex);
wires.Clear();
SkillPlayerInfo.Clear();
revealed.Clear();
}
}
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 PlayerDisconnect(uint playerIndex)
{
RemovePlayerWires(playerIndex);
foreach (var key in revealed.Keys)
if (key.Owner == playerIndex || key.Target == playerIndex)
revealed.TryRemove(key, out _);
SkillPlayerInfo.TryRemove(playerIndex, out _);
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null) return;
ClearOwner(player.Index);
SkillUtils.ResetPrintHTML(player);
}
public static void PlayerDeath(EventPlayerDeath @event)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return;
ClearOwner(player.Index);
SkillUtils.ResetPrintHTML(player);
}
private static void ClearOwner(uint ownerIndex)
{
RemovePlayerWires(ownerIndex);
foreach (var key in revealed.Keys)
if (key.Owner == ownerIndex)
revealed.TryRemove(key, out _);
}
private static void RemovePlayerWires(uint ownerIndex)
{
foreach (var kvp in wires)
{
if (kvp.Value.OwnerIndex != ownerIndex) continue;
DestroyWire(kvp.Key);
wires.TryRemove(kvp.Key, out _);
}
SkillPlayerInfo.TryRemove(ownerIndex, out _);
}
private static void DestroyWire(uint beamIndex)
{
var beam = Utilities.GetEntityFromIndex<CBeam>((int)beamIndex);
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");
}
EntityManager.DestroyEntity(beamIndex);
}
public static void UseSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo) || !skillInfo.CanUse) return;
if ((DateTime.Now - skillInfo.LastAttempt).TotalSeconds < attemptCooldown) return;
skillInfo.LastAttempt = DateTime.Now;
if (!TryPlaceWire(player))
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("tripwire_no_wall_info"));
return;
}
skillInfo.CanUse = false;
skillInfo.Cooldown = DateTime.Now;
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("tripwire_placed_info"));
}
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 (cooldown == 0)
playerInfo.PrintHTML = null;
else
playerInfo.PrintHTML = $"{player.GetTranslation("hud_info", $"<font color='#FF0000'>{cooldown}</font>")}";
}
private static bool TryPlaceWire(CCSPlayerController player)
{
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return false;
if (!RayTrace.IsAvailable) return false;
float height = SkillsInfo.GetValue<float>(skillName, "wireHeight");
float maxDistance = SkillsInfo.GetValue<float>(skillName, "maxWallDistance");
Vector origin = new(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + height);
float yaw = pawn.EyeAngles.Y;
Vector rightDir = SkillUtils.GetForwardVector(new QAngle(0, yaw - 90, 0));
Vector leftDir = SkillUtils.GetForwardVector(new QAngle(0, yaw + 90, 0));
ulong wallMask = (ulong)InteractionLayers.MASK_WORLD_ONLY;
var rightHit = RayTrace.TraceShape(player, origin, origin + rightDir * maxDistance, wallMask, 0);
var leftHit = RayTrace.TraceShape(player, origin, origin + leftDir * maxDistance, wallMask, 0);
if (rightHit == null || leftHit == null) return false;
if (!rightHit.Value.DidHit || !leftHit.Value.DidHit) return false;
if (rightHit.Value.HitPlayer(out _) || leftHit.Value.HitPlayer(out _)) return false;
Vector start = new(rightHit.Value.EndPos.X, rightHit.Value.EndPos.Y, rightHit.Value.EndPos.Z);
Vector end = new(leftHit.Value.EndPos.X, leftHit.Value.EndPos.Y, leftHit.Value.EndPos.Z);
if (EntityManager.OverBudget()) return false;
Color wireColor = player.Team == CsTeam.Terrorist ? terroristWire : counterTerroristWire;
var beam = EntityManager.CreateTrackedBeam(player.Index, start, end, wireColor);
if (beam == null || !beam.IsValid) return false;
beam.Width = SkillsInfo.GetValue<float>(skillName, "wireWidth");
wires[beam.Index] = new WireInfo
{
OwnerIndex = player.Index,
BeamIndex = beam.Index,
Start = start,
End = end,
};
return true;
}
public static void OnTick()
{
if (!SkillPlayerInfo.IsEmpty && Server.TickCount % 8 == 0)
{
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)
UpdateHUD(player, skillInfo);
}
}
if (revealed.IsEmpty && wires.IsEmpty) return;
int tick = Server.TickCount;
foreach (var kvp in revealed)
{
if (kvp.Value.ExpiryTick <= tick)
{
revealed.TryRemove(kvp.Key, out _);
continue;
}
RevealOnRadar(kvp.Key.Target, kvp.Value.Slot);
}
if (wires.IsEmpty || tick % 4 != 0) return;
float radius = SkillsInfo.GetValue<float>(skillName, "triggerRadius");
int revealTicks = (int)(SkillsInfo.GetValue<float>(skillName, "radarDuration") * 64);
float bodyHeight = SkillsInfo.GetValue<float>(skillName, "wireHeight");
foreach (var wire in wires.Values)
{
var owner = Utilities.GetPlayerFromIndex((int)wire.OwnerIndex);
if (owner == null || !owner.IsValid) continue;
foreach (var enemy in PlayerManager.GetTickPlayers())
{
if (enemy == null || !enemy.IsValid || enemy.Team == owner.Team) continue;
var enemyEvent = PlayerManager.GetPlayerEvent(enemy);
if (enemyEvent == null || !enemyEvent.IsValid) continue;
var enemyPawn = enemyEvent.PlayerPawn?.Value;
if (enemyPawn == null || !enemyPawn.IsValid || enemyPawn.Health <= 0 || enemyPawn.AbsOrigin == null) continue;
var key = (wire.OwnerIndex, enemyEvent.Index);
if (revealed.ContainsKey(key)) continue;
Vector body = new(enemyPawn.AbsOrigin.X, enemyPawn.AbsOrigin.Y, enemyPawn.AbsOrigin.Z + bodyHeight);
if (DistanceToSegment(body, wire.Start, wire.End) > radius) continue;
revealed[key] = (owner.Slot, tick + revealTicks);
var ownerEvent = PlayerManager.GetPlayerFromEvent(owner);
if (ownerEvent != null && ownerEvent.IsValid)
{
ownerEvent.ExecuteClientCommand("play sounds/weapons/taser/taser_charge_ready");
ownerEvent.PrintToChat($" {ChatColors.Red}" + ownerEvent.GetTranslation("tripwire_triggered_info", enemyEvent.PlayerName));
}
}
}
}
private static void RevealOnRadar(uint targetIndex, int ownerSlot)
{
var target = Utilities.GetPlayerFromIndex((int)targetIndex);
if (target == null || !target.IsValid) return;
var targetPawn = target.PlayerPawn?.Value;
if (targetPawn == null || !targetPawn.IsValid || targetPawn.Health <= 0) return;
targetPawn.EntitySpottedState.SpottedByMask[0] |= (1u << (ownerSlot % 32));
}
private static float DistanceToSegment(Vector point, Vector start, Vector end)
{
Vector line = new(end.X - start.X, end.Y - start.Y, end.Z - start.Z);
float lengthSquared = line.X * line.X + line.Y * line.Y + line.Z * line.Z;
if (lengthSquared <= .0001f) return (float)SkillUtils.GetDistance(point, start);
Vector toPoint = new(point.X - start.X, point.Y - start.Y, point.Z - start.Z);
float t = (toPoint.X * line.X + toPoint.Y * line.Y + toPoint.Z * line.Z) / lengthSquared;
t = Math.Clamp(t, 0f, 1f);
Vector closest = new(start.X + line.X * t, start.Y + line.Y * t, start.Z + line.Z * t);
return (float)SkillUtils.GetDistance(point, closest);
}
private class WireInfo
{
public required uint OwnerIndex { get; set; }
public required uint BeamIndex { get; set; }
public required Vector Start { get; set; }
public required Vector End { get; set; }
}
public class PlayerSkillInfo
{
public bool CanUse { get; set; }
public DateTime Cooldown { 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 = 1.5f, float maxWallDistance = 400f, float cooldown = 20f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float RadarDuration { get; set; } = radarDuration;
public float TriggerRadius { get; set; } = triggerRadius;
public float WireHeight { get; set; } = wireHeight;
public float WireWidth { get; set; } = wireWidth;
public float MaxWallDistance { get; set; } = maxWallDistance;
public float Cooldown { get; set; } = cooldown;
}
}
}

View file

@ -28,6 +28,8 @@ namespace src.player.skills
temporaryBlockList.TryRemove(kvp.Key, out _);
}
if (glows.IsEmpty && temporaryBlockList.IsEmpty) return;
// One pawn->info map per frame instead of a GetPlayers scan per client.
var infoByPawnHandle = new Dictionary<nint, jSkill_PlayerInfo?>();
foreach (var p in PlayerManager.GetTickPlayers())
@ -37,6 +39,27 @@ namespace src.player.skills
infoByPawnHandle[h.Value] = PlayerManager.GetPlayerByIndex(p.Index);
}
var glowStates = new List<(uint Relay, uint Glow, CsTeam Team, bool Showable, bool EntitiesValid)>(glows.Count);
foreach (var kvp in glows)
{
var glowInfo = kvp.Value;
var enemy = Utilities.GetPlayerFromIndex((int)kvp.Key);
bool showable = false;
if (enemy != null && enemy.IsValid)
{
var enemyPawn = enemy.PlayerPawn?.Value;
bool pawnValid = enemyPawn != null && enemyPawn.IsValid;
showable = pawnValid && enemyPawn!.Health > 0 && enemyPawn!.Render.A != 102 && enemyPawn!.Render.A != 128;
}
var relayEntity = Utilities.GetEntityFromIndex<CBaseEntity>((int)glowInfo.RelayIndex);
var glowEntity = Utilities.GetEntityFromIndex<CBaseEntity>((int)glowInfo.GlowIndex);
bool entitiesValid = relayEntity != null && relayEntity.IsValid && glowEntity != null && glowEntity.IsValid;
glowStates.Add((glowInfo.RelayIndex, glowInfo.GlowIndex, glowInfo.Team, showable, entitiesValid));
}
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid) continue;
@ -47,40 +70,18 @@ namespace src.player.skills
if (targetHandle != nint.Zero)
infoByPawnHandle.TryGetValue(targetHandle, out observerInfo);
foreach (var kvp in glows)
bool seesGlows = playerInfo?.Skill == skillName || (observerInfo != null && observerInfo?.Skill == skillName);
foreach (var glow in glowStates)
{
var enemyIndex = kvp.Key;
var glowInfo = kvp.Value;
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (glow.Showable && seesGlows && glow.Team != player.Team) continue;
if (!glow.EntitiesValid) continue;
bool hasSkill = playerInfo?.Skill == skillName;
bool observerHasSkill = observerInfo != null && observerInfo?.Skill == skillName;
bool differentTeam = glowInfo.Team != player.Team;
if (info.TransmitEntities.Contains(glow.Relay))
info.TransmitEntities.Remove(glow.Relay);
if (enemy != null && enemy.IsValid)
{
var enemyPawn = enemy.PlayerPawn?.Value;
bool pawnValid = enemyPawn != null && enemyPawn.IsValid;
bool alive = pawnValid && enemyPawn!.Health > 0;
bool notInvisible = pawnValid && enemyPawn!.Render.A != 102 && enemyPawn!.Render.A != 128;
bool shouldShow = alive && notInvisible && differentTeam && (hasSkill || observerHasSkill);
if (shouldShow)
continue;
}
var glowEntity1 = Utilities.GetEntityFromIndex<CBaseEntity>((int)glowInfo.RelayIndex);
if (glowEntity1 == null || !glowEntity1.IsValid) continue;
var glowEntity2 = Utilities.GetEntityFromIndex<CBaseEntity>((int)glowInfo.GlowIndex);
if (glowEntity2 == null || !glowEntity2.IsValid) continue;
if (info.TransmitEntities.Contains(glowEntity1.Index))
info.TransmitEntities.Remove(glowEntity1.Index);
if (info.TransmitEntities.Contains(glowEntity2.Index))
info.TransmitEntities.Remove(glowEntity2.Index);
if (info.TransmitEntities.Contains(glow.Glow))
info.TransmitEntities.Remove(glow.Glow);
}
foreach (var kvp in temporaryBlockList)
@ -91,6 +92,8 @@ namespace src.player.skills
public static void NewRound()
{
buildInProgress = false;
foreach (var kvp in glows)
{
var relayIndex = kvp.Value.RelayIndex;
@ -136,7 +139,7 @@ namespace src.player.skills
Event.EnableTransmit();
playersInAction.TryAdd(player.Index, 0);
if (glows.IsEmpty)
if (glows.IsEmpty && !buildInProgress)
SetGlowEffectForAll();
SkillUtils.ForceFullUpdateToAll();
@ -153,6 +156,9 @@ namespace src.player.skills
}
}
private const int GlowsPerFrame = 4;
private static bool buildInProgress;
private static void SetGlowEffectForAll()
{
var enemies = PlayerManager.GetTickPlayers().Where(p =>
@ -163,60 +169,83 @@ namespace src.player.skills
p.PlayerPawn.Value.Health > 0 &&
(p.Team == CsTeam.Terrorist || p.Team == CsTeam.CounterTerrorist)).ToList();
foreach (var enemy in enemies)
buildInProgress = true;
SpawnGlowChunk(enemies, 0);
}
private static void SpawnGlowChunk(List<CCSPlayerController> enemies, int start)
{
if (!buildInProgress) return;
if (start >= enemies.Count)
{
var enemyInfo = PlayerManager.GetPlayerByIndex(enemy.Index);
if (enemyInfo?.Skill == Skills.Ghost)
continue;
var enemyPawn = enemy.PlayerPawn?.Value;
if (enemyPawn == null || !enemyPawn.IsValid) continue;
var skeleton = enemyPawn.CBodyComponent?.SceneNode?.GetSkeletonInstance();
var modelName = skeleton?.ModelState?.ModelName;
if (string.IsNullOrEmpty(modelName)) continue;
var modelGlow = EntityManager.CreateTrackedDynamicProp(enemy.Index);
var modelRelay = EntityManager.CreateTrackedDynamicProp(enemy.Index);
if (modelGlow == null || !modelGlow.IsValid || modelRelay == null || !modelRelay.IsValid)
continue;
// Register before spawn so the glow is filtered from its first snapshot.
glows.TryAdd(enemy.Index, (modelRelay.Index, modelGlow.Index, enemy.Team));
var relayEntity = modelRelay.CBodyComponent?.SceneNode?.Owner?.Entity;
if (relayEntity != null)
relayEntity.Flags = (uint)(relayEntity.Flags & ~(1 << 2));
modelRelay.SetModel(modelName);
modelRelay.Spawnflags = 256u;
modelRelay.RenderMode = RenderMode_t.kRenderNone;
modelRelay.DispatchSpawn();
var glowEntity = modelGlow.CBodyComponent?.SceneNode?.Owner?.Entity;
if (glowEntity != null)
glowEntity.Flags = (uint)(glowEntity.Flags & ~(1 << 2));
modelGlow.SetModel(modelName);
modelGlow.Spawnflags = 256u;
modelGlow.Render = Color.FromArgb(1, 255, 255, 255);
modelGlow.DispatchSpawn();
try
{
modelGlow.Glow.GlowColorOverride = enemy.Team == CsTeam.Terrorist ? Color.FromArgb(255, 255, 165, 0) : Color.FromArgb(255, 173, 216, 230);
modelGlow.Glow.GlowRange = 5000;
modelGlow.Glow.GlowTeam = -1;
modelGlow.Glow.GlowType = 3;
modelGlow.Glow.GlowRangeMin = 100;
}
catch
{ }
modelRelay.AcceptInput("FollowEntity", enemyPawn, modelRelay, "!activator");
modelGlow.AcceptInput("FollowEntity", modelRelay, modelGlow, "!activator");
buildInProgress = false;
return;
}
int end = Math.Min(start + GlowsPerFrame, enemies.Count);
for (int i = start; i < end; i++)
{
var enemy = enemies[i];
if (enemy == null || !enemy.IsValid) continue;
SpawnGlowFor(enemy);
}
Server.NextFrame(() => SpawnGlowChunk(enemies, end));
}
private static void SpawnGlowFor(CCSPlayerController enemy)
{
var enemyInfo = PlayerManager.GetPlayerByIndex(enemy.Index);
if (enemyInfo?.Skill == Skills.Ghost)
return;
var enemyPawn = enemy.PlayerPawn?.Value;
if (enemyPawn == null || !enemyPawn.IsValid) return;
var skeleton = enemyPawn.CBodyComponent?.SceneNode?.GetSkeletonInstance();
var modelName = skeleton?.ModelState?.ModelName;
if (string.IsNullOrEmpty(modelName)) return;
var modelGlow = EntityManager.CreateTrackedDynamicProp(enemy.Index);
var modelRelay = EntityManager.CreateTrackedDynamicProp(enemy.Index);
if (modelGlow == null || !modelGlow.IsValid || modelRelay == null || !modelRelay.IsValid)
return;
glows.TryAdd(enemy.Index, (modelRelay.Index, modelGlow.Index, enemy.Team));
var relayEntity = modelRelay.CBodyComponent?.SceneNode?.Owner?.Entity;
if (relayEntity != null)
relayEntity.Flags = (uint)(relayEntity.Flags & ~(1 << 2));
modelRelay.SetModel(modelName);
modelRelay.Spawnflags = 256u;
modelRelay.RenderMode = RenderMode_t.kRenderNone;
modelRelay.DispatchSpawn();
var glowEntity = modelGlow.CBodyComponent?.SceneNode?.Owner?.Entity;
if (glowEntity != null)
glowEntity.Flags = (uint)(glowEntity.Flags & ~(1 << 2));
modelGlow.SetModel(modelName);
modelGlow.Spawnflags = 256u;
modelGlow.Render = Color.FromArgb(1, 255, 255, 255);
modelGlow.DispatchSpawn();
try
{
modelGlow.Glow.GlowColorOverride = enemy.Team == CsTeam.Terrorist ? Color.FromArgb(255, 255, 165, 0) : Color.FromArgb(255, 173, 216, 230);
modelGlow.Glow.GlowRange = 5000;
modelGlow.Glow.GlowTeam = -1;
modelGlow.Glow.GlowType = 3;
modelGlow.Glow.GlowRangeMin = 100;
}
catch
{ }
modelRelay.AcceptInput("FollowEntity", enemyPawn, modelRelay, "!activator");
modelGlow.AcceptInput("FollowEntity", modelRelay, modelGlow, "!activator");
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5d00ff", 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)

View file

@ -137,10 +137,17 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) return;
if (enemy == null || !enemy.IsValid) return;
var playerGear = SnapshotGear(player);
var enemyGear = SnapshotGear(enemy);
player.RemoveWeapons();
enemy.RemoveWeapons();
GiveWeapons(player, enemyWeapon, playerC4);
GiveWeapons(enemy, playerWeapon, (enemyWeapon != null && enemyC4));
RestoreGear(player, playerGear);
RestoreGear(enemy, enemyGear);
});
}
else
@ -148,6 +155,49 @@ namespace src.player.skills
}
}
private static GearInfo? SnapshotGear(CCSPlayerController player)
{
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) return null;
var itemServices = pawn.ItemServices?.As<CCSPlayer_ItemServices>();
return new GearInfo
{
Armor = pawn.ArmorValue,
Health = pawn.Health,
HasHelmet = itemServices?.HasHelmet ?? false,
HasDefuser = itemServices?.HasDefuser ?? false,
};
}
private static void RestoreGear(CCSPlayerController player, GearInfo? gear)
{
if (gear == null) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid) return;
if (pawn.ArmorValue != gear.Armor)
{
pawn.ArmorValue = gear.Armor;
Utilities.SetStateChanged(pawn, "CCSPlayerPawn", "m_ArmorValue");
}
if (pawn.Health != gear.Health)
{
pawn.Health = gear.Health;
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
}
var itemServices = pawn.ItemServices?.As<CCSPlayer_ItemServices>();
if (itemServices == null) return;
itemServices.HasHelmet = gear.HasHelmet;
itemServices.HasDefuser = gear.HasDefuser;
Utilities.SetStateChanged(pawn, "CCSPlayerPawn", "m_pItemServices");
}
private static (WeaponInfo[]?, bool) GetWeapons(CCSPlayerController player)
{
bool haveC4 = false;
@ -247,6 +297,14 @@ namespace src.player.skills
public bool HaveWeapon { get; set; }
}
public class GearInfo
{
public required int Armor { get; set; }
public required int Health { get; set; }
public required bool HasHelmet { get; set; }
public required bool HasDefuser { get; set; }
}
public class WeaponInfo
{
public required string Name { get; set; }

View file

@ -42,7 +42,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
var enemies = SkillUtils.GetSelectableEnemies(player, true);
if (enemies.Length > 0)
{
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
@ -80,10 +80,11 @@ namespace src.player.skills
if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (!SkillUtils.HasMenu(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p =>
p != null &&
p.IsValid)

View file

@ -90,6 +90,7 @@ namespace src.utils
public bool TraceRayBeam { get; set; }
public string DisableHUDOnDeathPermission { get; set; }
public bool DisableSkillsOnRoundEnd { get; set; }
public int? CurseSkillPerPlayer { get; set; }
public LanguageSystem LanguageSystem { get; set; }
public HtmlHudCustomisation HtmlHudCustomisation { get; set; }
public ChatMessage ChatMessage { get; set; }
@ -119,6 +120,7 @@ namespace src.utils
DisableSpectateHUD = false;
DisableHUDOnDeathPermission = "@jRandomSkills/death";
DisableSkillsOnRoundEnd = false;
CurseSkillPerPlayer = null;
LanguageSystem = new LanguageSystem
{

View file

@ -65,6 +65,12 @@ namespace src.utils
public static int GetTrackedCount(uint playerIndex) => GetPlayerEntities(playerIndex).Count;
public static uint? GetEntityOwner(uint entityIndex)
{
if (!trackedEntities.TryGetValue(entityIndex, out var data)) return null;
return data.PlayerIndex == SystemOwnerIndex ? null : data.PlayerIndex;
}
public static (int totalTracked, int ownerCount) GetStatistics()
{
return (trackedEntities.Count, trackedEntities.Values.Select(e => e.PlayerIndex).Distinct().Count());
@ -117,44 +123,6 @@ namespace src.utils
return CreateTrackedDynamicProp(playerIndex, "prop_dynamic_override");
}
public static CEnvShake? CreateTrackedEnvShake(uint playerIndex)
{
try
{
if (OverBudget()) return null;
var shake = Utilities.CreateEntityByName<CEnvShake>("env_shake");
if (shake == null || !shake.IsValid) return null;
shake.DispatchSpawn();
RegisterEntity(shake.Index, playerIndex, "env_shake");
return shake;
}
catch (Exception ex)
{
Server.PrintToConsole($"[EntityManager] CreateTrackedEnvShake: {ex.Message}");
return null;
}
}
public static CChicken? CreateTrackedChicken(uint playerIndex)
{
try
{
if (OverBudget()) return null;
var chicken = Utilities.CreateEntityByName<CChicken>("chicken");
if (chicken == null || !chicken.IsValid) return null;
chicken.DispatchSpawn();
RegisterEntity(chicken.Index, playerIndex, "chicken");
return chicken;
}
catch (Exception ex)
{
Server.PrintToConsole($"[EntityManager] CreateTrackedChicken: {ex.Message}");
return null;
}
}
public static CPhysicsPropMultiplayer? CreateTrackedPhysicsProp(uint playerIndex)
{
try

View file

@ -22,10 +22,33 @@ namespace src.utils
private static string defaultLangCode = "en";
private static readonly Dictionary<Skills, string> skillKeys = BuildSkillKeys("");
private static readonly Dictionary<Skills, string> skillDescKeys = BuildSkillKeys("_desc");
private static readonly Dictionary<Skills, string> skillDesc2Keys = BuildSkillKeys("_desc2");
private static Dictionary<Skills, string> BuildSkillKeys(string suffix)
{
var map = new Dictionary<Skills, string>();
foreach (Skills skill in Enum.GetValues<Skills>())
map[skill] = skill.ToString().ToLowerInvariant() + suffix;
return map;
}
private static string SkillKey(Skills skill) => skillKeys.TryGetValue(skill, out var k) ? k : skill.ToString().ToLowerInvariant();
private static string SkillDescKey(Skills skill) => skillDescKeys.TryGetValue(skill, out var k) ? k : skill.ToString().ToLowerInvariant() + "_desc";
private static string SkillDesc2Key(Skills skill) => skillDesc2Keys.TryGetValue(skill, out var k) ? k : skill.ToString().ToLowerInvariant() + "_desc2";
private static readonly ConcurrentDictionary<(Skills Skill, string Lang, double Chance), string> _skillNameCache = [];
private static readonly ConcurrentDictionary<(Skills Skill, string Lang, double Chance), string> _skillDescCache = [];
private const double NoChance = double.NegativeInfinity;
public static void Load()
{
_translations.Clear();
_playersLanguage.Clear();
_skillNameCache.Clear();
_skillDescCache.Clear();
SetLangCode();
LoadAllLanguages();
LoadPlayersLanguage();
@ -77,9 +100,26 @@ namespace src.utils
public static string GetSkillName(this CCSPlayerController player, Skills skill, float? chance = null)
{
string langCode = GetLangCode(player);
bool cacheable = !Illiterate.CheckIlliterateSkill(player);
var cacheKey = (skill, langCode, chance == null ? NoChance : Math.Round((double)chance, 2));
if (cacheable && _skillNameCache.TryGetValue(cacheKey, out var cached))
return cached;
string result = BuildSkillName(player, skill, chance, langCode);
if (cacheable)
_skillNameCache[cacheKey] = result;
return result;
}
private static string BuildSkillName(CCSPlayerController player, Skills skill, float? chance, string langCode)
{
if (chance == null)
{
var translation = GetTranslation(skill.ToString().ToLowerInvariant(), langCode);
var translation = GetTranslation(SkillKey(skill), langCode);
if (!translation.Contains("{0}"))
return translation;
@ -92,7 +132,7 @@ namespace src.utils
}
var value = Math.Round((double)(chance ?? 1), 2);
var skillNameText = GetTranslation(skill.ToString().ToLowerInvariant(), langCode, player, value);
var skillNameText = GetTranslation(SkillKey(skill), langCode, player, value);
if (skillNameText.Contains('%')) skillNameText = skillNameText.Replace(value.ToString(), Math.Round(value * 100, 0).ToString());
return skillNameText;
}
@ -100,9 +140,26 @@ namespace src.utils
public static string GetSkillDescription(this CCSPlayerController player, Skills skill, float? chance = null)
{
string langCode = GetLangCode(player);
bool cacheable = !Illiterate.CheckIlliterateSkill(player);
var cacheKey = (skill, langCode, chance == null ? NoChance : Math.Round((double)chance, 2));
if (cacheable && _skillDescCache.TryGetValue(cacheKey, out var cached))
return cached;
string result = BuildSkillDescription(player, skill, chance, langCode);
if (cacheable)
_skillDescCache[cacheKey] = result;
return result;
}
private static string BuildSkillDescription(CCSPlayerController player, Skills skill, float? chance, string langCode)
{
if (chance == null)
{
var translation = GetTranslation($"{skill.ToString().ToLowerInvariant()}_desc", langCode);
var translation = GetTranslation(SkillDescKey(skill), langCode);
if (!translation.Contains("{0}"))
return translation;
@ -114,12 +171,12 @@ namespace src.utils
return string.Join(' ', filtered);
}
var skillName = $"{skill.ToString().ToLowerInvariant()}_desc2";
var skillName = SkillDesc2Key(skill);
var value = Math.Round((double)(chance ?? 1), 2);
var desc2 = GetTranslation(skillName, langCode, player, value);
var skilLDescription = desc2 == skillName
? player.GetTranslation($"{skill.ToString().ToLowerInvariant()}_desc")
? player.GetTranslation(SkillDescKey(skill))
: desc2.Contains('%') ? desc2.Replace(value.ToString(), Math.Round(value * 100, 0).ToString()) : desc2;
return skilLDescription;
}

View file

@ -12,6 +12,8 @@ namespace src.utils
private static int cachedTick = int.MinValue;
private static List<CCSPlayerController> cachedControllers = [];
private static int cachedBombTick = int.MinValue;
private static CC4? cachedBomb;
private static void EnsureTickCache()
@ -20,7 +22,6 @@ namespace src.utils
if (tick == cachedTick) return;
cachedTick = tick;
cachedControllers = Utilities.GetPlayers();
cachedBomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
}
public static List<CCSPlayerController> GetTickPlayers()
@ -31,7 +32,13 @@ namespace src.utils
public static CC4? GetTickBomb()
{
EnsureTickCache();
int tick = Server.TickCount;
if (tick != cachedBombTick)
{
cachedBombTick = tick;
cachedBomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
}
return cachedBomb != null && cachedBomb.IsValid ? cachedBomb : null;
}
@ -62,7 +69,7 @@ namespace src.utils
if (!player.ControllingBot)
return player;
return Utilities.GetPlayers().FirstOrDefault(p =>
return GetTickPlayers().FirstOrDefault(p =>
p != null &&
p.IsValid &&
p.IsBot &&
@ -78,7 +85,7 @@ namespace src.utils
if (!player.IsBot)
return player;
return Utilities.GetPlayers().FirstOrDefault(p =>
return GetTickPlayers().FirstOrDefault(p =>
p != null &&
p.IsValid &&
!p.IsBot &&
@ -91,59 +98,11 @@ namespace src.utils
return playersByIndex.Values;
}
public static IEnumerable<jSkill_PlayerInfo> GetAlivePlayers()
{
return playersByIndex.Values.Where(p =>
{
try
{
var controller = Utilities.GetPlayerFromIndex((int)p.PlayerIndex);
return controller != null && controller.IsValid && controller.PawnIsAlive;
}
catch
{
return false;
}
});
}
public static IEnumerable<jSkill_PlayerInfo> GetPlayersByTeam(CsTeam team)
{
return playersByIndex.Values.Where(p =>
{
try
{
var controller = Utilities.GetPlayerFromIndex((int)p.PlayerIndex);
return controller != null && controller.IsValid && controller.Team == team;
}
catch
{
return false;
}
});
}
public static int GetTeamPlayerCount(CsTeam team)
{
return GetPlayersByTeam(team).Count();
}
public static int GetPlayerCountBySkill(Skills skills)
{
return playersByIndex.Values.Count(p => p.Skill == skills);
}
public static bool UpdatePlayerSkill(uint playerIndex, Skills skill, Skills specialSkill = Skills.None)
{
if (playersByIndex.TryGetValue(playerIndex, out var playerInfo))
{
playerInfo.Skill = skill;
playerInfo.SpecialSkill = specialSkill;
return true;
}
return false;
}
public static void Clear()
{
playersByIndex.Clear();

View file

@ -275,9 +275,18 @@ namespace src.utils
return null;
}
public static bool IsBulletDamage(CTakeDamageInfo? info)
{
var ability = info?.Ability?.Value;
if (ability == null || !ability.IsValid) return false;
return FiresBullets(ability.DesignerName);
}
public static HitGroup_t GetHitGroup(CTakeDamageInfo? info)
{
if (info == null || info.Handle == nint.Zero) return HitGroup_t.HITGROUP_GENERIC;
if (!IsBulletDamage(info)) return HitGroup_t.HITGROUP_GENERIC;
int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup");
if (offset <= 0) return HitGroup_t.HITGROUP_GENERIC;
@ -322,7 +331,181 @@ namespace src.utils
return !ff && !tae; // same team + FF off -> engine will zero this damage
}
public static bool TakeHealth(CCSPlayerPawn? pawn, int damage)
private static readonly ConcurrentDictionary<uint, (uint AttackerIndex, string? Weapon, int ExpiryTick)> pendingKillCredits = [];
public static void RegisterKillCredit(uint victimIndex, uint attackerIndex, string? weapon = null)
{
pendingKillCredits[victimIndex] = (attackerIndex, weapon, Server.TickCount + 64);
}
public static bool TryConsumeKillCredit(uint victimIndex, out uint attackerIndex, out string? weapon)
{
attackerIndex = 0;
weapon = null;
if (!pendingKillCredits.TryRemove(victimIndex, out var credit)) return false;
if (credit.ExpiryTick < Server.TickCount) return false;
attackerIndex = credit.AttackerIndex;
weapon = credit.Weapon;
return true;
}
public static void ClearKillCredits()
{
pendingKillCredits.Clear();
}
private static readonly HashSet<string> bulletWeapons = new(StringComparer.Ordinal)
{
"deagle", "revolver", "glock", "usp_silencer", "cz75a",
"fiveseven", "p250", "tec9", "elite", "hkp2000",
"mp9", "mac10", "bizon", "mp7", "ump45", "p90", "mp5sd",
"famas", "galilar", "m4a1", "m4a1_silencer", "ak47", "aug", "sg553",
"ssg08", "awp", "scar20", "g3sg1",
"nova", "xm1014", "mag7", "sawedoff",
"m249", "negev"
};
public static bool FiresBullets(string? weapon)
{
if (string.IsNullOrEmpty(weapon)) return false;
if (weapon.StartsWith("weapon_", StringComparison.Ordinal))
weapon = weapon["weapon_".Length..];
return bulletWeapons.Contains(weapon);
}
private static readonly HashSet<Skills> curseSkills =
[
Skills.Bankrupt, Skills.CarefulBullets, Skills.Darkness, Skills.Deactivator,
Skills.Deaf, Skills.ExpensiveAmmo, Skills.Giant, Skills.Glitch,
Skills.Jammer, Skills.JumpBan, Skills.JumpCurse, Skills.LifeSwap,
Skills.Magnifier, Skills.MoneySwap, Skills.Nightmare, Skills.Poison,
Skills.PrimaryBan, Skills.Thief, Skills.WildThrow
];
private static readonly HashSet<string> curseSkillNames = new(curseSkills.Select(s => s.ToString()), StringComparer.Ordinal);
private static readonly Dictionary<uint, int> curseCounts = [];
private static readonly Dictionary<uint, uint> curserToVictim = [];
private static readonly object curseLock = new();
private static readonly Config.GameModes[] sharedSkillModes =
[Config.GameModes.TeamSkills, Config.GameModes.SameSkills, Config.GameModes.Debug];
public static bool CurseLimitEnabled
{
get
{
if (Config.LoadedConfig.CurseSkillPerPlayer is not int limit || limit <= 0) return false;
return Array.IndexOf(sharedSkillModes, (Config.GameModes)Config.LoadedConfig.GameMode) < 0;
}
}
public static bool IsCurseSkill(Skills skill) => curseSkills.Contains(skill);
public static bool IsCurseSkill(string skill) => curseSkillNames.Contains(skill);
public static void ClearCurses()
{
if (!CurseLimitEnabled) return;
lock (curseLock)
{
curseCounts.Clear();
curserToVictim.Clear();
}
}
public static bool CanCurse(uint victimIndex)
{
if (!CurseLimitEnabled) return true;
int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value;
lock (curseLock)
return !curseCounts.TryGetValue(victimIndex, out int used) || used < limit;
}
public static bool TryClaimCurse(uint curserIndex, uint victimIndex, bool force = false)
{
if (!CurseLimitEnabled) return true;
int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value;
lock (curseLock)
{
ReleaseCurseLocked(curserIndex);
curseCounts.TryGetValue(victimIndex, out int used);
if (!force && used >= limit) return false;
curseCounts[victimIndex] = used + 1;
curserToVictim[curserIndex] = victimIndex;
return true;
}
}
public static void ReleaseCurse(uint curserIndex)
{
if (!CurseLimitEnabled) return;
lock (curseLock) ReleaseCurseLocked(curserIndex);
}
public static void ClearCursesFor(uint playerIndex)
{
if (!CurseLimitEnabled) return;
lock (curseLock)
{
ReleaseCurseLocked(playerIndex);
curseCounts.Remove(playerIndex);
foreach (var curser in curserToVictim.Where(kvp => kvp.Value == playerIndex).Select(kvp => kvp.Key).ToList())
curserToVictim.Remove(curser);
}
}
private static void ReleaseCurseLocked(uint curserIndex)
{
if (!curserToVictim.Remove(curserIndex, out uint victimIndex)) return;
if (!curseCounts.TryGetValue(victimIndex, out int used)) return;
if (used <= 1) curseCounts.Remove(victimIndex);
else curseCounts[victimIndex] = used - 1;
}
public static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player, bool respectCurseLimit = false)
{
if (player == null || !player.IsValid) return [];
var enemies = GetAliveEnemies(player);
if (!respectCurseLimit || !CurseLimitEnabled || enemies.Length == 0) return enemies;
var withCapacity = enemies.Where(p => CanCurse(p.Index)).ToArray();
return withCapacity.Length > 0 ? withCapacity : enemies;
}
public static bool AnyCurseCapacity(CCSPlayerController player)
{
if (!CurseLimitEnabled) return true;
if (player == null || !player.IsValid) return true;
return GetAliveEnemies(player).Any(p => CanCurse(p.Index));
}
private static CCSPlayerController[] GetAliveEnemies(CCSPlayerController player)
{
return [.. PlayerManager.GetTickPlayers()
.Where(p => p != null && p.IsValid)
.Select(PlayerManager.GetPlayerEvent)
.Where(p => p != null && p.IsValid && p.Team != player.Team
&& p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0
&& !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None)
.Cast<CCSPlayerController>()];
}
public static bool TakeHealth(CCSPlayerPawn? pawn, int damage, CCSPlayerController? damageAttacker = null, string? damageWeapon = null)
{
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE)
return false;
@ -363,6 +546,10 @@ namespace src.utils
if (pawn.Health <= 0)
{
if (damageAttacker != null && damageAttacker.IsValid && victim != null && victim.IsValid
&& damageAttacker.Index != victim.Index)
RegisterKillCredit(victim.Index, damageAttacker.Index, damageWeapon);
Server.NextFrame(() =>
{
if (pawn == null || !pawn.IsValid) return;
@ -374,6 +561,29 @@ namespace src.utils
return true;
}
public static void HideCarriedEntities(CCheckTransmitInfo info, CCSPlayerPawn? pawn)
{
if (pawn == null || !pawn.IsValid) return;
var weaponServices = pawn.WeaponServices;
if (weaponServices == null) return;
var activeWeapon = weaponServices.ActiveWeapon?.Value;
if (activeWeapon != null && activeWeapon.IsValid && info.TransmitEntities.Contains(activeWeapon.Index))
info.TransmitEntities.Remove(activeWeapon.Index);
if (weaponServices.MyWeapons == null) return;
foreach (var handle in weaponServices.MyWeapons)
{
var weapon = handle?.Value;
if (weapon == null || !weapon.IsValid) continue;
if (info.TransmitEntities.Contains(weapon.Index))
info.TransmitEntities.Remove(weapon.Index);
}
}
public static void ResetPrintHTML(CCSPlayerController? player)
{
if (player == null || !player.IsValid) return;

View file

@ -20,6 +20,7 @@
"TraceRayBeam": false,
"DisableHUDOnDeathPermission": "@jRandomSkills/death",
"DisableSkillsOnRoundEnd": false,
"CurseSkillPerPlayer": null,
"LanguageSystem": {
"DefaultLangCode": "en",
"DisableGeoLite": false,

View file

@ -81,6 +81,25 @@
"MaxPerServer": 1,
"Rarity": "Epic"
},
{
"RadarDuration": 5.0,
"TriggerRadius": 24.0,
"WireHeight": 30.0,
"WireWidth": 1.5,
"MaxWallDistance": 400.0,
"Cooldown": 20.0,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
"Color": "#ff3b3b",
"Active": true,
"Name": "Tripwire",
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": -1,
"Rarity": "Rare"
},
{
"SmokeDamage": 2,
"SmokeRadius": 180.0,
@ -187,6 +206,7 @@
},
{
"Distance": 100.0,
"UseCooldown": 0.5,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
@ -371,6 +391,10 @@
},
{
"ZombieHealth": 500,
"R": 255,
"G": 0,
"B": 0,
"A": 60,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
@ -756,6 +780,23 @@
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"PostProcessing": "lighting/postprocessing/effects/death_cam_phase1_low_violence.vpost",
"FadeTime": 0.25,
"MinExposure": 0.5,
"MaxExposure": 2.0,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
"Color": "#5b2c6f",
"Active": true,
"Name": "Nightmare",
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": -1,
"Rarity": "Rare"
},
{
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
@ -887,6 +928,21 @@
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"KnockbackUnits": 120.0,
"MaxSpeed": 1200.0,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
"Color": "#ff8c42",
"Active": true,
"Name": "Knockback",
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"FlashDuration": 1.0,
"FriendlyFire": true,
@ -917,6 +973,20 @@
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"JumpVelocity": 301.0,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
"Color": "#7ad1c4",
"Active": true,
"Name": "JumpCurse",
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
@ -1094,6 +1164,7 @@
},
{
"HealthToAdd": 100,
"EffectDuration": 1.0,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
@ -1191,6 +1262,21 @@
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"MinScale": 1.1,
"MaxScale": 1.4,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
"Color": "#8ad3ff",
"Active": true,
"Name": "Giant",
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
@ -1337,6 +1423,38 @@
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"Cooldown": 20.0,
"ExplosionRadius": 210.0,
"ExplosionDamage": 50,
"PropModel": "models/props/de_train/hr_t/barrel_a/barrel_a.vmdl",
"DmgReductionForTeamates": 0.5,
"NeedsTeammates": false,
"DisableOnFreezeTime": true,
"OnlyTeam": 0,
"Color": "#c0392b",
"Active": true,
"Name": "ExplodingBarrel",
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": 2,
"Rarity": "Common"
},
{
"MoneyPerShot": 50,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
"Color": "#e0c341",
"Active": true,
"Name": "ExpensiveAmmo",
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": -1,
"Rarity": "Common"
},
{
"Cooldown": 15.0,
"CooldownBeforeUse": 10.0,
@ -1591,7 +1709,7 @@
"HudDuration": null,
"DescriptionHudDuration": null,
"RequiredPermission": "",
"MaxPerServer": -1,
"MaxPerServer": 1,
"Rarity": "Uncommon"
},
{

View file

@ -129,6 +129,16 @@
"enemyspawn": "Feindes-Spawn",
"enemyspawn_desc": "Klicke auf [css_useSkill], um zum Feindes-Spawn zu teleportieren",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Explosiver Schuss",
"explosiveshot_desc": "Zufällige Chance, beim Schießen eine explosive Kugel abzufeuern",
"explosiveshot_desc2": "Deine Chance, eine explosive Kugel abzufeuern: {0} %",
@ -165,6 +175,14 @@
"ghost": "Geist",
"ghost_desc": "Du bist vollständig unsichtbar",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Glaz",
"glaz_desc": "Du kannst durch Rauchgranaten hindurchsehen",
@ -241,12 +259,22 @@
"jumpban_player_info": "Der Spieler ‚{0}‘ kann nicht mehr springen.",
"jumpban_select_info": "Spieler vom Springen ausschließen:",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Jumping Jack",
"jumpingjack_desc": "Durch Springen wird die Gesundheit wiederhergestellt",
"killerflash": "Killer Flash",
"killerflash_desc": "Jeder, der durch deine Blendgranate vollständig geblendet wird, stirbt (einschließlich dir selbst)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Lebensaustausch",
"lifeswap_desc": "Wähle einen Spieler aus, mit dem du deine Gesundheit austauschen möchtest",
"lifeswap_enemy_info": "Jemand hat sich deine Gesundheit ausgeliehen",
@ -288,6 +316,13 @@
"deathbomb_desc": "Du explodierst beim Tod und tötest Spieler in deiner Nähe",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Wenn du stillstehst, erhöht sich deine Unsichtbarkeit um 33 %, wenn du dich duckst um 33 % und wenn du ein Messer hältst um 33 %",
@ -450,6 +485,12 @@
"toxicsmoke": "Giftiger Rauch",
"toxicsmoke_desc": "Deine Rauchgranaten verursachen Schaden",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen",
@ -491,6 +532,7 @@
"duplicate_player": "Mehr als ein Spieler mit demselben Namen gefunden.",
"selectplayerskill_command": "Gib /t ein",
"selectplayerskill_incorrect_enemy_index": "Es wurden keine Spieler gefunden, die ausgewählt werden können.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Keine solche CHATCOLORS.REDskill gefunden",
"player_not_found_setskill": "Kein solcher CHATCOLORS.REDplayer gefunden",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Enemy Spawn",
"enemyspawn_desc": "Click [css_useSkill] to teleport to the enemy spawn",
"expensiveammo": "Expensive Ammo",
"expensiveammo_desc": "A chosen enemy loses money with every shot",
"expensiveammo_disable_info": "Your bullets are free again.",
"expensiveammo_enemy_info": "Every shot you fire now costs you {0}$.",
"expensiveammo_player_info": "Player '{0}' now pays for every bullet.",
"expensiveammo_select_info": "Select player whose ammo becomes expensive:",
"explodingbarrel": "Exploding Barrel",
"explodingbarrel_desc": "Click [css_useSkill] to place a barrel that explodes when shot",
"explosiveshot": "Explosive Shot",
"explosiveshot_desc": "Random chance to fire an explosive bullet while shooting",
"explosiveshot_desc2": "Your chance to fire an explosive bullet: {0}%",
@ -165,6 +175,14 @@
"ghost": "Ghost",
"ghost_desc": "You are completely invisible",
"giant": "Giant",
"giant_desc": "Enlarge an enemy of your choice",
"giant_desc2": "Enlarge an enemy of your choice to {0}x",
"giant_disable_info": "You are back to your normal size.",
"giant_enemy_info": "You have been enlarged to {0}x.",
"giant_player_info": "You enlarged player '{0}' to {1}x.",
"giant_select_info": "Enlarge player:",
"glaz": "Glaz",
"glaz_desc": "You can see through smoke grenades",
@ -241,12 +259,22 @@
"jumpban_player_info": "Player '{0}' can no longer jump.",
"jumpban_select_info": "Ban player from jumping:",
"jumpcurse": "Jump Curse",
"jumpcurse_desc": "A chosen enemy jumps whenever their teammates jump",
"jumpcurse_disable_info": "Your legs are your own again.",
"jumpcurse_enemy_info": "You now jump whenever your teammates jump.",
"jumpcurse_player_info": "Player '{0}' now jumps with their team.",
"jumpcurse_select_info": "Select player for the jump curse:",
"jumpingjack": "Jumping Jack",
"jumpingjack_desc": "Jumping restores health",
"killerflash": "Killer Flash",
"killerflash_desc": "Anyone fully blinded by your flashbang dies (including you)",
"knockback": "Knockback",
"knockback_desc": "Firing while airborne pushes you backwards",
"lifeswap": "Life Swap",
"lifeswap_desc": "Choose a player to swap health with",
"lifeswap_enemy_info": "Someone borrowed your health.",
@ -288,6 +316,13 @@
"deathbomb_desc": "You explode upon death, killing nearby players",
"deathbomb_explosion": "Kaboom!",
"nightmare": "Nightmare",
"nightmare_desc": "Give a chosen enemy a nightmarish vision",
"nightmare_disable_info": "The nightmare has faded.",
"nightmare_enemy_info": "A nightmare clouds your vision.",
"nightmare_player_info": "You cast a nightmare on player '{0}'.",
"nightmare_select_info": "Select player for the nightmare:",
"ninja": "Ninja",
"ninja_desc": "Standing still increases your invisibility by 33%, crouching by 33%, and holding a knife by 33%",
@ -450,6 +485,12 @@
"toxicsmoke": "Toxic Smoke",
"toxicsmoke_desc": "Your smoke grenades deal damage",
"tripwire": "Tripwire",
"tripwire_desc": "Click [css_useSkill] to string a wire between two walls. Enemies touching it show on your radar",
"tripwire_no_wall_info": "No walls close enough on both sides.",
"tripwire_placed_info": "Tripwire placed.",
"tripwire_triggered_info": "'{0}' tripped your wire.",
"wallhack": "Wallhack",
"wallhack_desc": "You can see enemies through walls",
@ -491,6 +532,7 @@
"duplicate_player": "More than one player found with the same name.",
"selectplayerskill_command": "Type /t",
"selectplayerskill_incorrect_enemy_index": "No players were found to select.",
"curse_limit_info": "The curse limit for {0} has been reached",
"skill_not_found_setskill": "No such CHATCOLORS.REDskill found",
"player_not_found_setskill": "No such CHATCOLORS.REDplayer found",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Spawn Ennemi",
"enemyspawn_desc": "Appuyez sur [css_useSkill] pour vous téléporter au spawn ennemi",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Tir Explosif",
"explosiveshot_desc": "Chance aléatoire de tirer une balle explosive en tirant",
"explosiveshot_desc2": "Votre chance de tirer une balle explosive est : {0}%",
@ -165,6 +175,14 @@
"ghost": "Fantôme",
"ghost_desc": "Vous êtes complètement invisible",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Glaz",
"glaz_desc": "Vous pouvez voir à travers les fumigènes",
@ -241,12 +259,21 @@
"jumpban_player_info": "Le joueur « {0} » ne peut plus sauter.",
"jumpban_select_info": "Interdire de sauter au joueur :",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Sauteur",
"jumpingjack_desc": "Sauter vous rend de la vie",
"killerflash": "Flash Mortel",
"killerflash_desc": "Toute personne totalement aveuglée par votre flashbang meurt (y compris vous)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Échange de Vie",
"lifeswap_desc": "Choisissez un joueur avec qui échanger votre santé",
"lifeswap_enemy_info": "Quelqu’un a emprunté votre santé.",
@ -288,6 +315,13 @@
"deathbomb_desc": "Vous explosez à votre mort, tuant les joueurs proches",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Rester immobile augmente votre invisibilité de 33%, s’accroupir de 33%, et tenir un couteau de 33%",
@ -450,6 +484,12 @@
"toxicsmoke": "Fumée Toxique",
"toxicsmoke_desc": "Vos grenades fumigènes infligent des dégâts",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Vous pouvez voir les ennemis à travers les murs",
@ -491,6 +531,7 @@
"duplicate_player": "Plus d’un joueur trouvé avec le même nom.",
"selectplayerskill_command": "Tapez /t",
"selectplayerskill_incorrect_enemy_index": "Aucun joueur trouvé à sélectionner.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Aucune CHATCOLORS.REDcompétence trouvée",
"player_not_found_setskill": "Aucun CHATCOLORS.REDjoueur trouvé",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Resp Wroga",
"enemyspawn_desc": "Kliknij [css_useSkill], aby teleportować się na resp wroga",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Strzał Wybuchowy",
"explosiveshot_desc": "Losowa szansa wystrzelenia pocisku wybuchowego podczas strzelania",
"explosiveshot_desc2": "Twoja szansa na wystrzelenie pocisku wybuchowego: {0}%",
@ -165,6 +175,14 @@
"ghost": "Duszek",
"ghost_desc": "Jesteś całkowicie niewidzialny",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Glaz",
"glaz_desc": "Nie widzisz granatów dymnych",
@ -241,12 +259,21 @@
"jumpban_player_info": "Gracz '{0}' nie może teraz skakać.",
"jumpban_select_info": "Zablokuj skok dla gracza:",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Pajacyk",
"jumpingjack_desc": "Skakanie przywraca zdrowie",
"killerflash": "Zabójczy Flash",
"killerflash_desc": "Każdy całkowicie oślepiony twoim granatem umiera (również ty)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Zamiana Żyć",
"lifeswap_desc": "Wybierasz gracza, z którym chcesz wymienić się zdrowiem",
"lifeswap_enemy_info": "Ktoś pożyczył sobie twoje zdrowie.",
@ -288,6 +315,13 @@
"deathbomb_desc": "Po śmierci eksplodujesz i zabijasz graczy znajdujących się w zasięgu",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Stojąc nieruchomo zwiększasz swoją niewidzialność o 33%, kucając o 33%, a trzymając nóż o 33%",
@ -450,6 +484,12 @@
"toxicsmoke": "Toksyczny Dym",
"toxicsmoke_desc": "Twoje granaty dymny zadają obrażenia",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Widzisz wrogów przez ściany",
@ -491,6 +531,7 @@
"duplicate_player": "Znaleziono więcej niż jednego gracza o tej samej nazwie.",
"selectplayerskill_command": "Wpisz /t",
"selectplayerskill_incorrect_enemy_index": "Nie znaleziono graczy do wyboru.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Nie znaleziono takiej CHATCOLORS.REDsupermocy",
"player_not_found_setskill": "Nie znaleziono takiego CHATCOLORS.REDgracza",

View file

@ -129,6 +129,16 @@
"enemyspawn": "Spawn Inimigo",
"enemyspawn_desc": "Clique em [css_useSkill] para se teletransportar para a base inimiga",
"expensiveammo": "expensiveammo",
"expensiveammo_desc": "expensiveammo_desc",
"expensiveammo_disable_info": "expensiveammo_disable_info",
"expensiveammo_enemy_info": "expensiveammo_enemy_info: {0}",
"expensiveammo_player_info": "expensiveammo_player_info: {0}",
"expensiveammo_select_info": "expensiveammo_select_info",
"explodingbarrel": "explodingbarrel",
"explodingbarrel_desc": "explodingbarrel_desc",
"explosiveshot": "Tiro Explosivo",
"explosiveshot_desc": "Chance aleatória de disparar uma bala explosiva enquanto atira",
"explosiveshot_desc2": "Sua chance de disparar uma bala explosiva: {0}%",
@ -165,6 +175,14 @@
"ghost": "Fantasma",
"ghost_desc": "Você é completamente invisível",
"giant": "giant",
"giant_desc": "giant_desc",
"giant_desc2": "giant_desc2: {0}x",
"giant_disable_info": "giant_disable_info",
"giant_enemy_info": "giant_enemy_info: {0}x",
"giant_player_info": "giant_player_info: {0} {1}",
"giant_select_info": "giant_select_info",
"glaz": "Smoke Transparente",
"glaz_desc": "Smokes tornam-se transparentes para você",
@ -241,12 +259,22 @@
"jumpban_player_info": "O jogador '{0}' não pode mais pular.",
"jumpban_select_info": "Banir pulo do jogador:",
"jumpcurse": "jumpcurse",
"jumpcurse_desc": "jumpcurse_desc",
"jumpcurse_disable_info": "jumpcurse_disable_info",
"jumpcurse_enemy_info": "jumpcurse_enemy_info",
"jumpcurse_player_info": "jumpcurse_player_info: {0}",
"jumpcurse_select_info": "jumpcurse_select_info",
"jumpingjack": "Pula-Pula",
"jumpingjack_desc": "Pular restaura vida",
"killerflash": "Flash Mortal",
"killerflash_desc": "Qualquer pessoa cega pela sua flash morre (incluindo você)",
"knockback": "knockback",
"knockback_desc": "knockback_desc",
"lifeswap": "Troca de Vida",
"lifeswap_desc": "Escolha um jogador para trocar de vida",
"lifeswap_enemy_info": "Alguém pegou sua vida emprestada.",
@ -288,6 +316,13 @@
"deathbomb_desc": "Você explode ao morrer, matando os jogadores próximos",
"deathbomb_explosion": "Kaboom!",
"nightmare": "nightmare",
"nightmare_desc": "nightmare_desc",
"nightmare_disable_info": "nightmare_disable_info",
"nightmare_enemy_info": "nightmare_enemy_info",
"nightmare_player_info": "nightmare_player_info: {0}",
"nightmare_select_info": "nightmare_select_info",
"ninja": "Ninja",
"ninja_desc": "Ficar parado aumenta sua invisibilidade em 33%, agachar em 33%, e segurar uma faca em 33%",
@ -450,6 +485,12 @@
"toxicsmoke": "Smoke Tóxica",
"toxicsmoke_desc": "Suas smokes causam dano",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Wallhack",
"wallhack_desc": "Você pode ver inimigos através das paredes",
@ -491,6 +532,7 @@
"duplicate_player": "Mais de um jogador encontrado com o mesmo nome.",
"selectplayerskill_command": "Digite /t",
"selectplayerskill_incorrect_enemy_index": "Não foram encontrados jogadores para selecionar.",
"curse_limit_info": "curse_limit_info",
"skill_not_found_setskill": "Nenhuma habilidade CHATCOLORS.RED encontrada",
"player_not_found_setskill": "Nenhum jogador CHATCOLORS.RED encontrado",

Some files were not shown because too many files have changed in this diff Show more