This commit is contained in:
ByDexter 2026-07-30 01:15:32 +03:00
parent 0e86f54e0a
commit 98f6e65b01
98 changed files with 3391 additions and 397 deletions

38
.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 |
@ -366,6 +373,33 @@ This plugin uses content from the following projects:
## 📋 Changelog
<details>
<summary><b>v1.2.3.b1</b></summary>
- #### General (Performance and Stability)
- ###### `CServerSideClient.ForceFullUpdate` now bails on a null client handle.
- ###### Added a per-tick player/bomb cache.
- ###### Migrated 82 skills from per-skill `Utilities.GetPlayers()` to the one shared per-tick scan, and the HUD loop reuses it. Much lower per-tick cost at high player counts (verified 10v10).
- ###### Print a single notice when the RayTrace is not installed.
- #### Skill improvements:
- ###### Second Chance / Phoenix / Re-Zombie:
- ###### Intercept the lethal hit before it is applied. Previously they reacted after the death was already committed, so one-shots, fall damage and skill-based kills were missed.
- ###### Killer Flash:
- ###### Kills through real damage instead of forcing a suicide, so revive skills can intercept.
- ###### Baseball:
- ###### Fixed a rare server crash from decoy grenades (decoy's entity index could be reused by another entity).
- ###### Aimbot:
- ###### Guard the hit-group native pointer before writing (avoids writing to freed/null memory).
- ###### Wallhack:
- ###### Detach the glow when the target dies or disconnects.
- ###### Spectator / Cypher / FalconEye / ThirdEye:
- ###### null-guard the camera SceneNode walk and re-validate the target before teleport/parent.
**Full update contributed by [@ByDexterTR](https://github.com/ByDexterTR) in pull request [#44](https://github.com/Juzlus/jRandomSkills/pull/44). Thanks to ByDexterTR!**
</details>
<details>
<summary><b>v1.2.2.b9</b></summary>
@ -399,7 +433,7 @@ This plugin uses content from the following projects:
- ###### Ghost / Ninja / C4 Camouflage:
- ###### Improved hidden-player processing for better performance.
**Full update contributed by @ByDexterTR in pull request #41. Thanks to ByDexterTR!**
**Full update contributed by [@ByDexterTR](https://github.com/ByDexterTR) in pull request [#41](https://github.com/Juzlus/jRandomSkills/pull/41). Thanks to ByDexterTR!**
</details>

29
.github/pl.md vendored
View file

@ -364,6 +364,33 @@ Plugin korzysta z zawartości następujących projektów:
## 📋 Lista Zmian
<details>
<summary><b>v1.2.3.b1</b></summary>
- #### Ogólne (Wydajność i stabilność)
- ###### `CServerSideClient.ForceFullUpdate` przerywa teraz działanie, jeśli klient handle jest pusty.
- ###### Dodano współdzieloną pamięć podręczną graczy i bomby odświeżaną co tick.
- ###### 82 moce zostały przeniesione z własnych wywołań `Utilities.GetPlayers()` na jedno współdzielone skanowanie wykonywane raz na tick. Pętla HUD również z niego korzysta, co znacznie zmniejsza obciążenie serwera przy większej liczbie graczy (potwierdzone w 10v10).
- ###### Wyświetlanie informacji o braku zainstalowanego RayTrace.
- #### Poprawki mocy:
- ###### Druga Szansa / Feniks / Re-Zombie:
- ###### Przechwytują śmiertelne obrażenia przed ich zastosowaniem. Wcześniej reagowały dopiero po śmierci gracza, przez co nie działały przy natychmiastowych zabójstwach, obrażeniach od upadku oraz zabójstwach zadawanych przez moce.
- ###### Zabójczy Flash:
- ###### Zabija teraz poprzez zadanie rzeczywistych obrażeń zamiast wymuszania samobójstwa, dzięki czemu moce wskrzeszające mogą zadziałać.
- ###### Bejsbolista:
- ###### Naprawiono rzadki crash serwera związany z granatem wabikiem (indeks encji wabika mógł zostać ponownie wykorzystany przez inną encję).
- ###### Aimbot:
- ###### Dodano sprawdzanie poprawności wskaźnika hit-group przed zapisem, co zapobiega zapisywaniu do zwolnionej lub pustej pamięci.
- ###### Wallhack:
- ###### Efekt poświaty jest teraz usuwany, gdy cel zginie lub opuści serwer.
- ###### Obserwator / Kamera / Oko Sokoła / Trzecie Oko:
- ###### Dodano zabezpieczenia przed pustymi wskaźnikami kamery oraz ponowną weryfikację celu przed teleportacją lub przypisaniem kamery.
**Pełna aktualizacja została przygotowana przez [@ByDexterTR](https://github.com/ByDexterTR) w ramach pull requesta [#44](https://github.com/Juzlus/jRandomSkills/pull/44). Dziękujemy ByDexterTR!**
</details>
<details>
<summary><b>v1.2.2.b8</b></summary>
@ -397,7 +424,7 @@ Plugin korzysta z zawartości następujących projektów:
- ###### Duszek / Ninja / C4 Kamuflaż:
- ###### Zoptymalizowano przetwarzanie ukrytych graczy, poprawiając wydajność.
**Pełna aktualizacja została przygotowana przez @ByDexterTR w ramach pull requesta #41. Dziękujemy ByDexterTR!**
**Pełna aktualizacja została przygotowana przez [@ByDexterTR](https://github.com/ByDexterTR) w ramach pull requesta [#44](https://github.com/Juzlus/jRandomSkills/pull/44). Dziękujemy ByDexterTR!**
</details>

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

@ -381,6 +381,33 @@ Plugin korzysta z zawartości następujących projektów:
## 📋 Lista Zmian
<details>
<summary><b>v1.2.3.b1</b></summary>
- #### Ogólne (Wydajność i stabilność)
- ###### `CServerSideClient.ForceFullUpdate` przerywa teraz działanie, jeśli klient handle jest pusty.
- ###### Dodano współdzieloną pamięć podręczną graczy i bomby odświeżaną co tick.
- ###### 82 moce zostały przeniesione z własnych wywołań `Utilities.GetPlayers()` na jedno współdzielone skanowanie wykonywane raz na tick. Pętla HUD również z niego korzysta, co znacznie zmniejsza obciążenie serwera przy większej liczbie graczy (potwierdzone w 10v10).
- ###### Wyświetlanie informacji o braku zainstalowanego RayTrace.
- #### Poprawki mocy:
- ###### Druga Szansa / Feniks / Re-Zombie:
- ###### Przechwytują śmiertelne obrażenia przed ich zastosowaniem. Wcześniej reagowały dopiero po śmierci gracza, przez co nie działały przy natychmiastowych zabójstwach, obrażeniach od upadku oraz zabójstwach zadawanych przez moce.
- ###### Zabójczy Flash:
- ###### Zabija teraz poprzez zadanie rzeczywistych obrażeń zamiast wymuszania samobójstwa, dzięki czemu moce wskrzeszające mogą zadziałać.
- ###### Bejsbolista:
- ###### Naprawiono rzadki crash serwera związany z granatem wabikiem (indeks encji wabika mógł zostać ponownie wykorzystany przez inną encję).
- ###### Aimbot:
- ###### Dodano sprawdzanie poprawności wskaźnika hit-group przed zapisem, co zapobiega zapisywaniu do zwolnionej lub pustej pamięci.
- ###### Wallhack:
- ###### Efekt poświaty jest teraz usuwany, gdy cel zginie lub opuści serwer.
- ###### Obserwator / Kamera / Oko Sokoła / Trzecie Oko:
- ###### Dodano zabezpieczenia przed pustymi wskaźnikami kamery oraz ponowną weryfikację celu przed teleportacją lub przypisaniem kamery.
**Pełna aktualizacja została przygotowana przez [@ByDexterTR](https://github.com/ByDexterTR) w ramach pull requesta [#44](https://github.com/Juzlus/jRandomSkills/pull/44). Dziękujemy ByDexterTR!**
</details>
<details>
<summary><b>v1.2.2.b8</b></summary>
@ -414,7 +441,7 @@ Plugin korzysta z zawartości następujących projektów:
- ###### Duszek / Ninja / C4 Kamuflaż:
- ###### Zoptymalizowano przetwarzanie ukrytych graczy, poprawiając wydajność.
**Pełna aktualizacja została przygotowana przez @ByDexterTR w ramach pull requesta #41. Dziękujemy ByDexterTR!**
**Pełna aktualizacja została przygotowana przez [@ByDexterTR](https://github.com/ByDexterTR) w ramach pull requesta [#44](https://github.com/Juzlus/jRandomSkills/pull/44). Dziękujemy ByDexterTR!**
</details>

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 |
@ -383,6 +390,33 @@ This plugin uses content from the following projects:
## 📋 Changelog
<details>
<summary><b>v1.2.3.b1</b></summary>
- #### General (Performance and Stability)
- ###### `CServerSideClient.ForceFullUpdate` now bails on a null client handle.
- ###### Added a per-tick player/bomb cache.
- ###### Migrated 82 skills from per-skill `Utilities.GetPlayers()` to the one shared per-tick scan, and the HUD loop reuses it. Much lower per-tick cost at high player counts (verified 10v10).
- ###### Print a single notice when the RayTrace is not installed.
- #### Skill improvements:
- ###### Second Chance / Phoenix / Re-Zombie:
- ###### Intercept the lethal hit before it is applied. Previously they reacted after the death was already committed, so one-shots, fall damage and skill-based kills were missed.
- ###### Killer Flash:
- ###### Kills through real damage instead of forcing a suicide, so revive skills can intercept.
- ###### Baseball:
- ###### Fixed a rare server crash from decoy grenades (decoy's entity index could be reused by another entity).
- ###### Aimbot:
- ###### Guard the hit-group native pointer before writing (avoids writing to freed/null memory).
- ###### Wallhack:
- ###### Detach the glow when the target dies or disconnects.
- ###### Spectator / Cypher / FalconEye / ThirdEye:
- ###### null-guard the camera SceneNode walk and re-validate the target before teleport/parent.
**Full update contributed by [@ByDexterTR](https://github.com/ByDexterTR) in pull request [#44](https://github.com/Juzlus/jRandomSkills/pull/44). Thanks to ByDexterTR!**
</details>
<details>
<summary><b>v1.2.2.b9</b></summary>
@ -416,7 +450,7 @@ This plugin uses content from the following projects:
- ###### Ghost / Ninja / C4 Camouflage:
- ###### Improved hidden-player processing for better performance.
**Full update contributed by @ByDexterTR in pull request #41. Thanks to ByDexterTR!**
**Full update contributed by [@ByDexterTR](https://github.com/ByDexterTR) in pull request [#41](https://github.com/Juzlus/jRandomSkills/pull/41). Thanks to ByDexterTR!**
</details>

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);
}
@ -333,9 +337,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,13 @@
"toxicsmoke": "Giftiger Rauch",
"toxicsmoke_desc": "Deine Rauchgranaten verursachen Schaden",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"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_limit_info": "You can only have {0} tripwires at a time.",
"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",

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,13 @@
"toxicsmoke": "Fumée Toxique",
"toxicsmoke_desc": "Vos grenades fumigènes infligent des dégâts",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"toxicsmoke": "Toksyczny Dym",
"toxicsmoke_desc": "Twoje granaty dymny zadają obrażenia",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"toxicsmoke": "Smoke Tóxica",
"toxicsmoke_desc": "Suas smokes causam dano",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"toxicsmoke": "Токсичный дым",
"toxicsmoke_desc": "Ваши дымовые гранаты наносят урон",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Воллхак",
"wallhack_desc": "Вы видите врагов сквозь стены",

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",
@ -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",
@ -450,6 +485,13 @@
"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_limit_info": "Aynı anda en fazla {0} tel gerebilirsin.",
"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",

View file

@ -106,6 +106,24 @@ namespace src.player
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre);
}
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 +147,10 @@ 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}.");
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={SkillUtils.GetHitGroup(param2)} " +
$"takes={victimPawn.TakesDamage} vskill={PlayerManager.GetPlayerByIndex(victim.Index)?.Skill} askill={playerInfo.Skill}" +
$"{DescribeIdentitySplit(victim)}]");
return HookResult.Continue;
}

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,

View file

@ -47,8 +47,10 @@ namespace src.player
Instance.RegisterEventHandler<EventRoundStart>(RoundStart);
Instance.RegisterEventHandler<EventRoundEnd>(RoundEnd);
Instance.RegisterEventHandler<EventPlayerDeath>(PlayerDeathPre, HookMode.Pre);
Instance.RegisterEventHandler<EventPlayerDeath>(PlayerDeath);
Instance.RegisterEventHandler<EventPlayerBlind>(PlayerBlind);
Instance.RegisterEventHandler<EventPlayerHurt>(PlayerHurtPre, HookMode.Pre);
Instance.RegisterEventHandler<EventPlayerHurt>(PlayerHurt);
Instance.RegisterEventHandler<EventPlayerJump>(PlayerJump);
Instance.RegisterEventHandler<EventBotTakeover>(BotTakeover);
@ -89,6 +91,22 @@ namespace src.player
// Keeping the plugin alive is safer than installing a stale global hook at load time.
}
public static void Unload()
{
TryUnhook(() => VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre));
TryUnhook(() => VirtualFunctions.CBaseTrigger_StartTouchFunc.Unhook(OnTriggerEnter, HookMode.Post));
TryUnhook(() => VirtualFunctions.CBaseTrigger_EndTouchFunc.Unhook(OnTriggerExit, HookMode.Pre));
TryUnhook(() => VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnWeaponCanAcquire, HookMode.Pre));
TryUnhook(() => Instance.UnhookUserMessage(208, PlayerMakeSound));
TryUnhook(() => Instance.RemoveListener<CheckTransmit>(CheckTransmit));
}
private static void TryUnhook(Action unhook)
{
try { unhook(); }
catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] unhook failed: {ex.Message}"); }
}
private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List<jSkill_SkillInfo> candidates, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
{
if (candidates == null || candidates.Count == 0) return noneSkill;
@ -180,12 +198,53 @@ namespace src.player
continue;
}
InvokeSkill(p.Skill, "OnTakeDamage", args);
InvokeOnTakeDamage(p.Skill, h, args);
}
if (deferred == null) return;
foreach (var skill in deferred)
InvokeOnTakeDamage(skill, h, args);
}
private static void InvokeOnTakeDamage(Skills skill, DynamicHook h, object[] args)
{
if (Config.LoadedConfig.DebugMode != true)
{
InvokeSkill(skill, "OnTakeDamage", args);
return;
}
var info = h.GetParam<CTakeDamageInfo>(1);
float before = info == null ? 0f : info.Damage;
InvokeSkill(skill, "OnTakeDamage", args);
float after = info == null ? 0f : info.Damage;
if (Math.Abs(before - after) > 0.01f)
Debug.WriteToDebug($"[DMG] {skill} changed damage {before:0.#} -> {after:0.#}{DescribeDamageTarget(h)}");
}
private static string DescribeDamageTarget(DynamicHook h)
{
try
{
var victimEntity = h.GetParam<CEntityInstance>(0);
if (victimEntity == null || !victimEntity.IsValid) return string.Empty;
var pawn = victimEntity.As<CCSPlayerPawn>();
if (pawn == null || !pawn.IsValid || pawn.DesignerName != "player") return string.Empty;
var controller = pawn.Controller.Value?.As<CCSPlayerController>();
if (controller == null || !controller.IsValid) return string.Empty;
uint routedIndex = PlayerManager.GetPlayerEvent(controller)?.Index ?? controller.Index;
return $" on {controller.PlayerName} [idx={controller.Index} skill={PlayerManager.GetPlayerByIndex(controller.Index)?.Skill}" +
$" routedIdx={routedIndex} routedSkill={PlayerManager.GetPlayerByIndex(routedIndex)?.Skill}]";
}
catch
{
return string.Empty;
}
}
private static HookResult PlayerMakeSound(UserMessage um)
@ -314,6 +373,63 @@ namespace src.player
}
}
private static HookResult PlayerHurtPre(EventPlayerHurt @event, GameEventInfo info)
{
lock (setLock)
{
try
{
if (@event.DmgHealth <= 0 && @event.DmgArmor <= 0) return HookResult.Continue;
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return HookResult.Continue;
var victimInfo = PlayerManager.GetPlayerByIndex(victim.Index);
if (victimInfo == null || victimInfo.IsDrawing) return HookResult.Continue;
bool suppressed = AskSkillSuppressesHit(victimInfo.Skill, @event);
if (!suppressed)
{
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
if (attacker != null && attacker.IsValid && attacker.Index != victim.Index)
{
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (attackerInfo != null && !attackerInfo.IsDrawing && attackerInfo.Skill != victimInfo.Skill)
suppressed = AskSkillSuppressesHit(attackerInfo.Skill, @event);
}
}
if (!suppressed) return HookResult.Continue;
if (@event.DmgArmor > 0)
{
var pawn = victim.PlayerPawn?.Value;
if (pawn != null && pawn.IsValid)
{
pawn.ArmorValue += @event.DmgArmor;
Utilities.SetStateChanged(pawn, "CCSPlayerPawn", "m_ArmorValue");
}
}
@event.DmgHealth = 0;
@event.DmgArmor = 0;
}
catch (Exception ex)
{
Debug.WriteToDebug($"PlayerHurtPre failed: {ex.Message}");
}
return HookResult.Continue;
}
}
private static bool AskSkillSuppressesHit(Skills skill, EventPlayerHurt @event)
{
if (skill == Skills.None) return false;
return (bool?)Instance.SkillAction(skill.ToString(), "PlayerHurtPre", [@event]) == true;
}
private static HookResult PlayerHurt(EventPlayerHurt @event, GameEventInfo info)
{
lock (setLock)
@ -546,6 +662,9 @@ namespace src.player
DisplayHUD = true,
SkillUsed = false,
};
UpdateSkillHudExpired(playerInfo, Skills.None);
PlayerManager.Register(playerInfo);
}
}
@ -609,14 +728,7 @@ namespace src.player
var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index);
if (skillPlayer == null) return HookResult.Continue;
var temp = skillPlayer.SkillHudExpired;
skillPlayer.SkillHudExpired = DateTime.MinValue;
Instance.AddTimer(5f, () =>
{
if (skillPlayer.SkillHudExpired == DateTime.MinValue)
skillPlayer.SkillHudExpired = temp;
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
skillPlayer.HudSuppressedUntil = DateTime.Now.AddSeconds(5);
return HookResult.Continue;
}
@ -653,6 +765,7 @@ namespace src.player
{
bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true;
isTransmitRegistered = false;
SkillUtils.ClearKillCredits();
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))
@ -785,6 +898,7 @@ namespace src.player
private static HookResult RoundEnd(EventRoundEnd @event, GameEventInfo info)
{
Illiterate.Disable();
DispatchToActiveSkills("RoundEnd");
lock (setLock)
{
@ -833,6 +947,40 @@ namespace src.player
}
}
private static HookResult PlayerDeathPre(EventPlayerDeath @event, GameEventInfo info)
{
try
{
var victim = @event.Userid;
if (victim == null || !victim.IsValid) return HookResult.Continue;
if (!SkillUtils.TryConsumeKillCredit(victim.Index, out uint attackerIndex, out string? weapon))
return HookResult.Continue;
var attacker = Utilities.GetPlayerFromIndex((int)attackerIndex);
if (attacker == null || !attacker.IsValid || attacker.Index == victim.Index)
return HookResult.Continue;
@event.Attacker = attacker;
if (!string.IsNullOrEmpty(weapon))
@event.Weapon = weapon;
var matchStats = attacker.ActionTrackingServices?.MatchStats;
if (matchStats != null)
{
matchStats.Kills++;
Utilities.SetStateChanged(attacker, "CCSPlayerController", "m_pActionTrackingServices");
}
}
catch (Exception ex)
{
Debug.WriteToDebug($"PlayerDeathPre kill credit failed: {ex.Message}");
}
return HookResult.Continue;
}
private static HookResult PlayerDeath(EventPlayerDeath @event, GameEventInfo info)
{
long perfStart = PerfLog.Start();
@ -955,26 +1103,65 @@ namespace src.player
private static readonly Dictionary<uint, jSkill_SkillInfo> nextRoundPicks = [];
private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, List<CCSPlayerController> validPlayers, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
private sealed class PickContext
{
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")));
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);
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()));
}
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 => counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
skillList.RemoveAll(s => ctx.CtOnly.Contains(s.Skill));
else
skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString()));
skillList.RemoveAll(s => ctx.TOnly.Contains(s.Skill));
if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag<jSkill_SkillInfo>? skills))
{
@ -1029,13 +1216,15 @@ namespace src.player
.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, validPlayers, assignmentCounts, gameMode);
var pick = PickSkillForPlayer(player, skillPlayer, ctx, assignmentCounts, gameMode);
nextRoundPicks[player.Index] = pick;
if (pick.Skill != Skills.None)
@ -1116,6 +1305,8 @@ namespace src.player
else assignmentCounts[sp.Skill] = 1;
}
PickContext? pickContext = null;
foreach (var player in validPlayers)
{
if (player == null) continue;
@ -1126,6 +1317,7 @@ namespace src.player
if (skillPlayer == null) continue;
skillPlayer.IsDrawing = false;
skillPlayer.HudOnDeathBlocked = null;
if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid)
{
skillPlayer.Skill = Skills.None;
@ -1142,7 +1334,10 @@ namespace src.player
if (nextRoundPicks.TryGetValue(player.Index, out var pre) && IsPickStillValid(pre, player, validPlayers, assignmentCounts))
randomSkill = pre;
else
randomSkill = PickSkillForPlayer(player, skillPlayer, validPlayers, assignmentCounts, gameMode);
{
pickContext ??= BuildPickContext(validPlayers);
randomSkill = PickSkillForPlayer(player, skillPlayer, pickContext, assignmentCounts, gameMode);
}
}
else if (gameMode == Config.GameModes.TeamSkills)
randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill;

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

@ -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,10 +30,11 @@ 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();
ConcurrentBag<(string, string)> menuItems = [];

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,10 +46,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.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();
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
@ -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);
@ -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,10 +54,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.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();
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];

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.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();
ConcurrentBag<(string, string)> menuItems = [];

View file

@ -59,10 +59,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.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();
ConcurrentBag<(string, string)> menuItems = [.. 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

@ -0,0 +1,171 @@
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;
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 = 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;
}
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 = 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 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);
}
private static CCSPlayerController[] GetSelectableEnemies(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 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,194 @@
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 [.. PlayerManager.GetTickPlayers()
.Where(p => p != null && p.IsValid)
.Select(PlayerManager.GetPlayerEvent)
.Where(p => p != null && p.IsValid && p.Team != player.Team && !p.IsHLTV
&& p.Team != CsTeam.Spectator && p.Team != CsTeam.None
&& p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0
&& PlayerManager.GetPlayerByIndex(p.Index)?.Skill != Skills.Chicken)
.Cast<CCSPlayerController>()];
}
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,10 +51,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.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();
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

@ -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,10 +59,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.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();
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
@ -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"));
}

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,10 +64,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.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();
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];

View file

@ -0,0 +1,186 @@
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 = 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;
}
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 = 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 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);
}
private static CCSPlayerController[] GetSelectableEnemies(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 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,55 @@
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;
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,10 +26,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.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();
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e?.PlayerPawn?.Value?.Health ?? 0} 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())));

View file

@ -26,10 +26,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.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();
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,247 @@
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 = GetSelectableEnemies(player);
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 = 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 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);
}
private static CCSPlayerController[] GetSelectableEnemies(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)
.Select(p => p!)];
}
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,10 +75,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.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();
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
@ -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);
@ -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,10 +70,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.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();
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,13 @@ 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 = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
Instance.AddToManifest(cameraViewModel);
}
public static void NewRound()
@ -159,7 +161,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();

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,256 @@
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, int> wireCount = [];
private static readonly ConcurrentDictionary<(uint Owner, uint Target), (int Slot, int ExpiryTick)> revealed = [];
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 wire in wires.Values)
DestroyWire(wire.BeamIndex);
wires.Clear();
wireCount.Clear();
revealed.Clear();
}
}
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 _);
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null) return;
RemovePlayerWires(player.Index);
foreach (var key in revealed.Keys)
if (key.Owner == player.Index)
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 _);
}
wireCount.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;
int maxWires = SkillsInfo.GetValue<int>(skillName, "maxWires");
if (wireCount.TryGetValue(player.Index, out int placed) && placed >= maxWires)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("tripwire_limit_info", maxWires));
return;
}
if (!TryPlaceWire(player))
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("tripwire_no_wall_info"));
return;
}
wireCount.AddOrUpdate(player.Index, 1, (_, v) => v + 1);
playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("tripwire_placed_info"));
}
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;
var beam = EntityManager.CreateTrackedBeam(player.Index, start, end, Color.FromArgb(255, 255, 40, 40));
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 (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 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, int maxWires = 2) : 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 int MaxWires { get; set; } = maxWires;
}
}
}

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

@ -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

@ -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());

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,43 +98,6 @@ 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);

View file

@ -322,7 +322,31 @@ 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();
}
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 +387,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 +402,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

@ -81,6 +81,25 @@
"MaxPerServer": 1,
"Rarity": "Epic"
},
{
"RadarDuration": 5.0,
"TriggerRadius": 24.0,
"WireHeight": 30.0,
"WireWidth": 1.5,
"MaxWallDistance": 400.0,
"MaxWires": 2,
"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,
@ -371,6 +390,10 @@
},
{
"ZombieHealth": 500,
"R": 255,
"G": 0,
"B": 0,
"A": 60,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
@ -756,6 +779,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 +927,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 +972,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 +1163,7 @@
},
{
"HealthToAdd": 100,
"EffectDuration": 1.0,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
@ -1191,6 +1261,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 +1422,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,

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,13 @@
"toxicsmoke": "Giftiger Rauch",
"toxicsmoke_desc": "Deine Rauchgranaten verursachen Schaden",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"toxicsmoke": "Toxic Smoke",
"toxicsmoke_desc": "Your smoke grenades deal damage",
"tripwire": "Tripwire",
"tripwire_desc": "Click [css_useSkill] to string a tripwire between two walls",
"tripwire_limit_info": "You can only have {0} tripwires at a time.",
"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",

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,13 @@
"toxicsmoke": "Fumée Toxique",
"toxicsmoke_desc": "Vos grenades fumigènes infligent des dégâts",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"toxicsmoke": "Toksyczny Dym",
"toxicsmoke_desc": "Twoje granaty dymny zadają obrażenia",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"toxicsmoke": "Smoke Tóxica",
"toxicsmoke_desc": "Suas smokes causam dano",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"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",

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,13 @@
"toxicsmoke": "Токсичный дым",
"toxicsmoke_desc": "Ваши дымовые гранаты наносят урон",
"tripwire": "tripwire",
"tripwire_desc": "tripwire_desc",
"tripwire_limit_info": "tripwire_limit_info: {0}",
"tripwire_no_wall_info": "tripwire_no_wall_info",
"tripwire_placed_info": "tripwire_placed_info",
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
"wallhack": "Воллхак",
"wallhack_desc": "Вы видите врагов сквозь стены",

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",
@ -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",
@ -450,6 +485,13 @@
"toxicsmoke": "Zehirli Sis",
"toxicsmoke_desc": "Sis bombaların hasar verir",
"tripwire": "Tel Tuzağı",
"tripwire_desc": "[css_useSkill] ile iki duvar arasına tel gerersin",
"tripwire_limit_info": "Aynı anda en fazla {0} tel gerebilirsin.",
"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",

View file

@ -1,7 +1,19 @@
{
"name": "jRandomSkills",
"current_version": "v1.2.2.b8",
"current_version": "v1.2.3.b1",
"releases": [
{
"version": "v1.2.3.b1",
"release_date": "2026-07-27",
"download": "https://github.com/Juzlus/jRandomSkills/releases/download/v1.2.3.b1/jRandomSkills.v1.2.3.b1.zip",
"output_folder": "addons/counterstrikesharp/"
}
{
"version": "v1.2.2.b9",
"release_date": "2026-07-23",
"download": "https://github.com/Juzlus/jRandomSkills/releases/download/v1.2.2.b9/jRandomSkills.v1.2.2.b9.zip",
"output_folder": "addons/counterstrikesharp/"
}
{
"version": "v1.2.2.b8",
"release_date": "2026-07-14",