diff --git a/.github/en.md b/.github/en.md index 3da2cb6..cedda4b 100644 --- a/.github/en.md +++ b/.github/en.md @@ -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)
The table below lists all available skills in the game, along with their descriptions. @@ -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 +
+v1.2.3.b1 + +- #### 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!** + +
+
v1.2.2.b9 @@ -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!**
diff --git a/.github/pl.md b/.github/pl.md index dbb8624..acf143a 100644 --- a/.github/pl.md +++ b/.github/pl.md @@ -364,6 +364,33 @@ Plugin korzysta z zawartości następujących projektów: ## 📋 Lista Zmian +
+v1.2.3.b1 + +- #### 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!** + +
+
v1.2.2.b8 @@ -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!**
diff --git a/.gitignore b/.gitignore index 543ae5a..1841365 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ project.fragment.lock.json # Logs, caches, temp *.log +debug_*.txt +perf_*.txt *.cache *.tmp *.temp diff --git a/README-PL.md b/README-PL.md index e5ee118..cae1b43 100644 --- a/README-PL.md +++ b/README-PL.md @@ -381,6 +381,33 @@ Plugin korzysta z zawartości następujących projektów: ## 📋 Lista Zmian +
+v1.2.3.b1 + +- #### 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!** + +
+
v1.2.2.b8 @@ -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!**
diff --git a/README.md b/README.md index c7f1aae..f514555 100644 --- a/README.md +++ b/README.md @@ -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)
The table below lists all available skills in the game, along with their descriptions. @@ -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 +
+v1.2.3.b1 + +- #### 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!** + +
+
v1.2.2.b9 @@ -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!**
diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs index f24938e..0349dab 100644 --- a/jRandomSkills - SRC Files/src/jRandomSkills.cs +++ b/jRandomSkills - SRC Files/src/jRandomSkills.cs @@ -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) diff --git a/jRandomSkills - SRC Files/src/lang/de.json b/jRandomSkills - SRC Files/src/lang/de.json index a300f14..0d0d15f 100644 --- a/jRandomSkills - SRC Files/src/lang/de.json +++ b/jRandomSkills - SRC Files/src/lang/de.json @@ -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", diff --git a/jRandomSkills - SRC Files/src/lang/en.json b/jRandomSkills - SRC Files/src/lang/en.json index 54d6f6c..8663de9 100644 --- a/jRandomSkills - SRC Files/src/lang/en.json +++ b/jRandomSkills - SRC Files/src/lang/en.json @@ -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", diff --git a/jRandomSkills - SRC Files/src/lang/fr.json b/jRandomSkills - SRC Files/src/lang/fr.json index e916741..64fa588 100644 --- a/jRandomSkills - SRC Files/src/lang/fr.json +++ b/jRandomSkills - SRC Files/src/lang/fr.json @@ -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", @@ -529,4 +570,4 @@ "menu_controlls_scroll": "W/S - Défilement", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Sélectionner" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/pl.json b/jRandomSkills - SRC Files/src/lang/pl.json index 16d4bbf..9dd510d 100644 --- a/jRandomSkills - SRC Files/src/lang/pl.json +++ b/jRandomSkills - SRC Files/src/lang/pl.json @@ -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", @@ -529,4 +570,4 @@ "menu_controlls_scroll": "W/S – Przewijanie", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Wybierz" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/pt-br.json b/jRandomSkills - SRC Files/src/lang/pt-br.json index c2cddfd..e3b4a3b 100644 --- a/jRandomSkills - SRC Files/src/lang/pt-br.json +++ b/jRandomSkills - SRC Files/src/lang/pt-br.json @@ -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", diff --git a/jRandomSkills - SRC Files/src/lang/ru.json b/jRandomSkills - SRC Files/src/lang/ru.json index dd0196c..57f0118 100644 --- a/jRandomSkills - SRC Files/src/lang/ru.json +++ b/jRandomSkills - SRC Files/src/lang/ru.json @@ -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": "Вы видите врагов сквозь стены", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S — Прокрутка", "menu_controlls_padding": " | ", "menu_controlls_select": "E — Выбрать" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/tr.json b/jRandomSkills - SRC Files/src/lang/tr.json index e8697da..6ccf3a3 100644 --- a/jRandomSkills - SRC Files/src/lang/tr.json +++ b/jRandomSkills - SRC Files/src/lang/tr.json @@ -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", diff --git a/jRandomSkills - SRC Files/src/lang/zh.json b/jRandomSkills - SRC Files/src/lang/zh.json index c922535..bb09467 100644 Binary files a/jRandomSkills - SRC Files/src/lang/zh.json and b/jRandomSkills - SRC Files/src/lang/zh.json differ diff --git a/jRandomSkills - SRC Files/src/player/Debug.cs b/jRandomSkills - SRC Files/src/player/Debug.cs index 70dcc0e..c1721fb 100644 --- a/jRandomSkills - SRC Files/src/player/Debug.cs +++ b/jRandomSkills - SRC Files/src/player/Debug.cs @@ -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(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; } diff --git a/jRandomSkills - SRC Files/src/player/ISkill.cs b/jRandomSkills - SRC Files/src/player/ISkill.cs index 6b2019a..7cc9a65 100644 --- a/jRandomSkills - SRC Files/src/player/ISkill.cs +++ b/jRandomSkills - SRC Files/src/player/ISkill.cs @@ -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, diff --git a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs index c119c53..e5e4ca0 100644 --- a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs +++ b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs @@ -47,8 +47,10 @@ namespace src.player Instance.RegisterEventHandler(RoundStart); Instance.RegisterEventHandler(RoundEnd); + Instance.RegisterEventHandler(PlayerDeathPre, HookMode.Pre); Instance.RegisterEventHandler(PlayerDeath); Instance.RegisterEventHandler(PlayerBlind); + Instance.RegisterEventHandler(PlayerHurtPre, HookMode.Pre); Instance.RegisterEventHandler(PlayerHurt); Instance.RegisterEventHandler(PlayerJump); Instance.RegisterEventHandler(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)); + } + + private static void TryUnhook(Action unhook) + { + try { unhook(); } + catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] unhook failed: {ex.Message}"); } + } + private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List candidates, Dictionary 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(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(0); + if (victimEntity == null || !victimEntity.IsValid) return string.Empty; + + var pawn = victimEntity.As(); + if (pawn == null || !pawn.IsValid || pawn.DesignerName != "player") return string.Empty; + + var controller = pawn.Controller.Value?.As(); + 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 nextRoundPicks = []; - private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, List validPlayers, Dictionary assignmentCounts, Config.GameModes gameMode) + private sealed class PickContext { - List skillList = [.. SkillData.Skills]; - skillList.RemoveAll(s => s?.Skill == Skills.None); - if (!player.IsBot) - skillList.RemoveAll(s => !string.IsNullOrEmpty(SkillsInfo.GetValue(s.Skill, "requiredPermission")) && !AdminManager.PlayerHasPermissions(player, SkillsInfo.GetValue(s.Skill, "requiredPermission"))); + public required List BaseList { get; init; } + public required Dictionary RequiredPermissions { get; init; } + public required HashSet NeedsTeammates { get; init; } + public required HashSet CtOnly { get; init; } + public required HashSet TOnly { get; init; } + public required int TerroristCount { get; init; } + public required int CounterTerroristCount { get; init; } + } + + private static PickContext BuildPickContext(List validPlayers) + { + Dictionary perms = []; + foreach (var s in SkillData.Skills) + { + if (s == null || s.Skill == Skills.None) continue; + string perm = SkillsInfo.GetValue(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 ToSkillSet(IEnumerable names) + { + HashSet set = []; + foreach (var name in names) + if (Enum.TryParse(name, out var skill)) set.Add(skill); + return set; + } + + private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, PickContext ctx, Dictionary assignmentCounts, Config.GameModes gameMode) + { + List 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? 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 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; diff --git a/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs b/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs index 520b593..cb960bd 100644 --- a/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs +++ b/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs @@ -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); } diff --git a/jRandomSkills - SRC Files/src/player/skills/AntyHead.cs b/jRandomSkills - SRC Files/src/player/skills/AntyHead.cs index 6fb1900..7a70a4d 100644 --- a/jRandomSkills - SRC Files/src/player/skills/AntyHead.cs +++ b/jRandomSkills - SRC Files/src/player/skills/AntyHead.cs @@ -13,17 +13,17 @@ namespace src.player.skills SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Assassin.cs b/jRandomSkills - SRC Files/src/player/skills/Assassin.cs index 4b2f08f..fb170c3 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Assassin.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Assassin.cs @@ -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(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(0); + CTakeDamageInfo param2 = h.GetParam(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()); + var victim = PlayerManager.GetPlayerEvent(victimPawn.Controller.Value.As()); + 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(skillName, "damageMultiplier") - 1))); - } + param2.Damage *= SkillsInfo.GetValue(skillName, "damageMultiplier"); } private static bool IsBehind(CCSPlayerController attacker, CCSPlayerController victim) diff --git a/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs b/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs index 67b009d..aeffe90 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs @@ -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 = []; diff --git a/jRandomSkills - SRC Files/src/player/skills/Baseball.cs b/jRandomSkills - SRC Files/src/player/skills/Baseball.cs index 774343c..9551ecf 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Baseball.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Baseball.cs @@ -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((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(skillName, "damageDeal")); + SkillUtils.TakeHealth(victim!.PlayerPawn.Value, SkillsInfo.GetValue(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) { diff --git a/jRandomSkills - SRC Files/src/player/skills/BladeMaster.cs b/jRandomSkills - SRC Files/src/player/skills/BladeMaster.cs index b828798..96143d5 100644 --- a/jRandomSkills - SRC Files/src/player/skills/BladeMaster.cs +++ b/jRandomSkills - SRC Files/src/player/skills/BladeMaster.cs @@ -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(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs b/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs index a096b1c..904c04e 100644 --- a/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs +++ b/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs @@ -11,6 +11,16 @@ namespace src.player.skills private const Skills skillName = Skills.BunnyHop; private static readonly ConcurrentDictionary 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(skillName, "color")); diff --git a/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs b/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs index f76803b..3a6ab09 100644 --- a/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs +++ b/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs @@ -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)) diff --git a/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs b/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs index 89a5c09..a89fe09 100644 --- a/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs +++ b/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs @@ -15,6 +15,7 @@ namespace src.player.skills private static readonly ConcurrentDictionary lastShot = []; private static readonly ConcurrentDictionary hitPlayer = []; private static readonly ConcurrentDictionary playersToTarget = []; + private static readonly ConcurrentDictionary 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(skillName, "damageAfterMiss")); + + SkillUtils.TakeHealth(playerPawn, SkillsInfo.GetValue(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(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); } @@ -220,4 +238,4 @@ namespace src.player.skills public int DamageAfterMiss { get; set; } = damageAfterMiss; } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Chicken.cs b/jRandomSkills - SRC Files/src/player/skills/Chicken.cs index c90be66..e999b17 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Chicken.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Chicken.cs @@ -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 chickens = []; public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(); diff --git a/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs b/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs index 1c88ed1..5bd73a8 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs @@ -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(skillName, "bombArmedTime"); + } + + private static bool IsAnyOwnerAlive() + { + foreach (var player in PlayerManager.GetTickPlayers()) { - var bombEntities = Utilities.FindAllEntitiesByDesignerName("weapon_c4").ToList(); - if (bombEntities.Count != 0) - { - var bomb = bombEntities.FirstOrDefault(); - if (bomb != null) - bomb.ArmedTime = Server.CurrentTime + SkillsInfo.GetValue(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(skillName, "bombArmedTime"); diff --git a/jRandomSkills - SRC Files/src/player/skills/Cutter.cs b/jRandomSkills - SRC Files/src/player/skills/Cutter.cs index 24e5816..32d1d06 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Cutter.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Cutter.cs @@ -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(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(0); + CTakeDamageInfo param2 = h.GetParam(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()); + 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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Darkness.cs b/jRandomSkills - SRC Files/src/player/skills/Darkness.cs index 5c6bde7..84aa377 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Darkness.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Darkness.cs @@ -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()))]; @@ -230,4 +231,4 @@ namespace src.player.skills public int A { get; set; } = a; } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs b/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs index 6817938..66e04a3 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs @@ -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 = []; @@ -144,4 +145,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Deaf.cs b/jRandomSkills - SRC Files/src/player/skills/Deaf.cs index 6b57b6e..d0a9d4c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Deaf.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Deaf.cs @@ -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()))]; @@ -158,4 +159,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs b/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs index 425c028..11c723e 100644 --- a/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs +++ b/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs @@ -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 nades = []; + private static readonly ConcurrentDictionary 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(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(skillName, "dmgReductionForTeamates"); - param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f); + var victim = victimPawn.Controller.Value.As(); + 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(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() ?? false; + bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue() ?? 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; diff --git a/jRandomSkills - SRC Files/src/player/skills/Dracula.cs b/jRandomSkills - SRC Files/src/player/skills/Dracula.cs index 47e9264..4f082ba 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Dracula.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Dracula.cs @@ -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(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(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Duplicator.cs b/jRandomSkills - SRC Files/src/player/skills/Duplicator.cs index 7d01917..a9623ba 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Duplicator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Duplicator.cs @@ -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 = []; @@ -179,4 +180,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs b/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs new file mode 100644 index 0000000..5571b14 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs @@ -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 cursedPlayers = []; + private static readonly ConcurrentDictionary playersToTarget = []; + + public static void LoadSkill() + { + SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(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(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()]; + } + + 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; + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/ExplodingBarrel.cs b/jRandomSkills - SRC Files/src/player/skills/ExplodingBarrel.cs new file mode 100644 index 0000000..0c7df63 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/ExplodingBarrel.cs @@ -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 SkillPlayerInfo = []; + private static readonly ConcurrentDictionary consumedBarrels = []; + private static readonly ConcurrentDictionary nades = []; + private static readonly object setLock = new(); + + public static void LoadSkill() + { + SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); + jRandomSkills.Instance.AddToManifest(SkillsInfo.GetValue(skillName, "propModel")); + } + + public static void NewRound() + { + ClearAllBarrels(); + lock (setLock) + { + SkillPlayerInfo.Clear(); + consumedBarrels.Clear(); + nades.Clear(); + } + } + + private static void ClearAllBarrels() + { + var entities = Utilities.FindAllEntitiesByDesignerName("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(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", $"{cooldown}")}"; + } + + 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(skillName, "propModel")); + barrel.Teleport(pos, rotation, null); + }); + } + + public static void OnEntitySpawned(CEntityInstance entity) + { + if (entity.DesignerName != "hegrenade_projectile") return; + + var heProjectile = entity.As(); + 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(skillName, "explosionDamage"); + heProjectile.DmgRadius = SkillsInfo.GetValue(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(0); + CTakeDamageInfo param2 = h.GetParam(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(); + 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(); + 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(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() ?? false; + bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue() ?? 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; + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs b/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs index 68d29f2..f08078b 100644 --- a/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs +++ b/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs @@ -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 cameras = []; public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(); diff --git a/jRandomSkills - SRC Files/src/player/skills/Ghost.cs b/jRandomSkills - SRC Files/src/player/skills/Ghost.cs index 7d5079a..46f7900 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Ghost.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Ghost.cs @@ -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; diff --git a/jRandomSkills - SRC Files/src/player/skills/Giant.cs b/jRandomSkills - SRC Files/src/player/skills/Giant.cs new file mode 100644 index 0000000..f58ee6d --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Giant.cs @@ -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 enlargedEnemies = []; + private static readonly object setLock = new(); + + public static void LoadSkill() + { + SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(skillName, "minScale"); + float maxScale = SkillsInfo.GetValue(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()]; + } + + 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; + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Glaz.cs b/jRandomSkills - SRC Files/src/player/skills/Glaz.cs index 37bb474..45ebcb8 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Glaz.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Glaz.cs @@ -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; diff --git a/jRandomSkills - SRC Files/src/player/skills/Glitch.cs b/jRandomSkills - SRC Files/src/player/skills/Glitch.cs index 6cf7b34..becc4e4 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Glitch.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Glitch.cs @@ -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()))); @@ -169,4 +170,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/GodMode.cs b/jRandomSkills - SRC Files/src/player/skills/GodMode.cs index 1206331..4087bbd 100644 --- a/jRandomSkills - SRC Files/src/player/skills/GodMode.cs +++ b/jRandomSkills - SRC Files/src/player/skills/GodMode.cs @@ -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(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Hermit.cs b/jRandomSkills - SRC Files/src/player/skills/Hermit.cs index eaba3d4..c8430de 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Hermit.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Hermit.cs @@ -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(skillName, "healthToAdd")); + + float effectDuration = SkillsInfo.GetValue(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; } } } \ No newline at end of file diff --git a/jRandomSkills - SRC Files/src/player/skills/HotBomb.cs b/jRandomSkills - SRC Files/src/player/skills/HotBomb.cs index 28bd3fa..1ca4069 100644 --- a/jRandomSkills - SRC Files/src/player/skills/HotBomb.cs +++ b/jRandomSkills - SRC Files/src/player/skills/HotBomb.cs @@ -26,6 +26,8 @@ namespace src.player.skills if (players.IsEmpty || jRandomSkills.Instance.GameRules?.FreezePeriod == true) return; int damage = SkillsInfo.GetValue(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("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("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"); diff --git a/jRandomSkills - SRC Files/src/player/skills/Iana.cs b/jRandomSkills - SRC Files/src/player/skills/Iana.cs index 432f323..ae7d758 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Iana.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Iana.cs @@ -14,6 +14,7 @@ namespace src.player.skills { private const Skills skillName = Skills.Iana; private static readonly ConcurrentDictionary playersInfo = []; + private static readonly ConcurrentDictionary 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(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()); + return shooter != null && shooter.IsValid ? shooter : null; + } + public static void OnTakeDamage(DynamicHook h) { CEntityInstance param = h.GetParam(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; diff --git a/jRandomSkills - SRC Files/src/player/skills/Illiterate.cs b/jRandomSkills - SRC Files/src/player/skills/Illiterate.cs index 9704203..9e23579 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Illiterate.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Illiterate.cs @@ -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 holders = []; + public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Illusionist.cs b/jRandomSkills - SRC Files/src/player/skills/Illusionist.cs index ac68ad7..c541ce8 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Illusionist.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Illusionist.cs @@ -15,6 +15,7 @@ namespace src.player.skills private const Skills skillName = Skills.Illusionist; private static readonly ConcurrentDictionary SkillPlayerInfo = []; private static readonly ConcurrentDictionary ActiveTimers = []; + private static readonly ConcurrentDictionary 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(); 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(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue(skillName, "YourTeamDamage")); + + SkillUtils.TakeHealth(attackerPawn, attackerTeam != replicaTeam ? SkillsInfo.GetValue(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue(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 diff --git a/jRandomSkills - SRC Files/src/player/skills/Jammer.cs b/jRandomSkills - SRC Files/src/player/skills/Jammer.cs index 3471ab7..458f878 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Jammer.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Jammer.cs @@ -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")); } @@ -199,4 +200,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Jester.cs b/jRandomSkills - SRC Files/src/player/skills/Jester.cs index ed4aa7d..6851cae 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Jester.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Jester.cs @@ -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(skillName, "minTime"); diff --git a/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs b/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs index 7dd38ed..c485922 100644 --- a/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs +++ b/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs @@ -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()))]; @@ -166,4 +167,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs b/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs new file mode 100644 index 0000000..e3de2ca --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs @@ -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 cursedPlayers = []; + private static readonly ConcurrentDictionary playersToTarget = []; + + public static void LoadSkill() + { + SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(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()]; + } + + 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; + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs b/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs index c88d8d5..44cf795 100644 --- a/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs +++ b/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs @@ -30,7 +30,7 @@ namespace src.player.skills if (!SkillsInfo.GetValue(skillName, "friendlyFire") && player!.Team == attacker!.Team) return; if (attackerInfo?.Skill == skillName && playerInfo?.Skill != Skills.AntyFlash && player!.PlayerPawn.Value!.FlashDuration >= SkillsInfo.GetValue(skillName, "flashDuration")) - SkillUtils.TakeHealth(player.PlayerPawn.Value, 9999); + SkillUtils.TakeHealth(player.PlayerPawn.Value, 9999, attacker, "weapon_flashbang"); } public static void GrenadeThrown(EventGrenadeThrown @event) diff --git a/jRandomSkills - SRC Files/src/player/skills/Knockback.cs b/jRandomSkills - SRC Files/src/player/skills/Knockback.cs new file mode 100644 index 0000000..2e53c29 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Knockback.cs @@ -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(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(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(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; + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs b/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs index eed8587..d7a104e 100644 --- a/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs @@ -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()))); @@ -117,4 +118,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs b/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs index c0ecbf8..32d128f 100644 --- a/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs +++ b/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs @@ -119,7 +119,7 @@ namespace src.player.skills if (!SkillsInfo.GetValue(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs b/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs index c3dbe8c..77b26d0 100644 --- a/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs +++ b/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs @@ -39,7 +39,7 @@ namespace src.player.skills if (target.Handle == player.Handle) return; if (!SkillsInfo.GetValue(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs b/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs index 6cf331d..0f772cc 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs @@ -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()))); @@ -176,4 +177,4 @@ namespace src.player.skills public uint CustomFOV { get; set; } = customFOV; } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs b/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs index ef811af..8583415 100644 --- a/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs @@ -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()))]; @@ -119,4 +120,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs b/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs new file mode 100644 index 0000000..11c0b2d --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs @@ -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 playersToTarget = []; + private static readonly ConcurrentDictionary targetVolumes = []; + private static readonly object setLock = new(); + + public static void LoadSkill() + { + SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); + jRandomSkills.Instance.AddToManifest(SkillsInfo.GetValue(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("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(skillName, "postProcessing")); + keys.SetBool("master", true); + keys.SetBool("enableexposure", true); + keys.SetFloat("fadetime", SkillsInfo.GetValue(skillName, "fadeTime")); + keys.SetFloat("minexposure", SkillsInfo.GetValue(skillName, "minExposure")); + keys.SetFloat("maxexposure", SkillsInfo.GetValue(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; + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs index 1144946..604c32a 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs @@ -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)) diff --git a/jRandomSkills - SRC Files/src/player/skills/NoNades.cs b/jRandomSkills - SRC Files/src/player/skills/NoNades.cs index 68d0707..ead6b21 100644 --- a/jRandomSkills - SRC Files/src/player/skills/NoNades.cs +++ b/jRandomSkills - SRC Files/src/player/skills/NoNades.cs @@ -15,18 +15,19 @@ namespace src.player.skills SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); } - public static void PlayerHurt(EventPlayerHurt @event) + private static readonly HashSet 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) diff --git a/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs b/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs index 8c47cc5..90ddde7 100644 --- a/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs +++ b/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs @@ -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 holders = []; private static bool noSpreadActive; + private static bool defaultNoSpread; + public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); + defaultNoSpread = ConVar.Find("weapon_accuracy_nospread")?.GetPrimitiveValue() ?? 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() diff --git a/jRandomSkills - SRC Files/src/player/skills/OnlyHead.cs b/jRandomSkills - SRC Files/src/player/skills/OnlyHead.cs index 38fde05..5bc540d 100644 --- a/jRandomSkills - SRC Files/src/player/skills/OnlyHead.cs +++ b/jRandomSkills - SRC Files/src/player/skills/OnlyHead.cs @@ -15,20 +15,17 @@ namespace src.player.skills SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs b/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs index fe7b8ad..b3a39b5 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs @@ -19,6 +19,22 @@ namespace src.player.skills SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(0); diff --git a/jRandomSkills - SRC Files/src/player/skills/Poison.cs b/jRandomSkills - SRC Files/src/player/skills/Poison.cs index ad1f883..40ff6b6 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Poison.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Poison.cs @@ -11,6 +11,7 @@ namespace src.player.skills private const Skills skillName = Skills.Poison; private static readonly ConcurrentDictionary poisonedPlayers = []; private static readonly ConcurrentDictionary playersToTarget = []; + private static readonly ConcurrentDictionary 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(skillName, "MinHealth")) continue; - SkillUtils.TakeHealth(pawn, SkillsInfo.GetValue(skillName, "Damage")); + + SkillUtils.TakeHealth(pawn, SkillsInfo.GetValue(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); } @@ -171,4 +192,4 @@ namespace src.player.skills public int MinHealth { get; set; } = minHealth; } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs b/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs index e7cb2a6..6f6a74f 100644 --- a/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs +++ b/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs @@ -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()))); @@ -196,4 +197,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Prosthesis.cs b/jRandomSkills - SRC Files/src/player/skills/Prosthesis.cs index 5c1d0c5..b5652cf 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Prosthesis.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Prosthesis.cs @@ -14,19 +14,20 @@ namespace src.player.skills SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs b/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs index 0b14a89..03bca00 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs @@ -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(skillName, "A"); + if (alpha <= 0) return; + + SkillUtils.ApplyScreenColor(victim, + r: SkillsInfo.GetValue(skillName, "R"), + g: SkillsInfo.GetValue(skillName, "G"), + b: SkillsInfo.GetValue(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; } } } \ No newline at end of file diff --git a/jRandomSkills - SRC Files/src/player/skills/ReactiveArmor.cs b/jRandomSkills - SRC Files/src/player/skills/ReactiveArmor.cs index 4f0f282..f3ac463 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ReactiveArmor.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ReactiveArmor.cs @@ -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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Replicator.cs b/jRandomSkills - SRC Files/src/player/skills/Replicator.cs index 2ed1b03..d0b6e9c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Replicator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Replicator.cs @@ -13,6 +13,7 @@ namespace src.player.skills { private const Skills skillName = Skills.Replicator; private static readonly ConcurrentDictionary SkillPlayerInfo = []; + private static readonly ConcurrentDictionary 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(); 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(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue(skillName, "YourTeamDamage")); + + SkillUtils.TakeHealth(attackerPawn, attackerTeam != replicaTeam ? SkillsInfo.GetValue(skillName, "EnemyTeamDamage") : SkillsInfo.GetValue(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 diff --git a/jRandomSkills - SRC Files/src/player/skills/Saper.cs b/jRandomSkills - SRC Files/src/player/skills/Saper.cs index 3aece77..f75135c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Saper.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Saper.cs @@ -42,15 +42,11 @@ namespace src.player.skills var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo?.Skill == skillName) { - var bombEntities = Utilities.FindAllEntitiesByDesignerName("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; } } } diff --git a/jRandomSkills - SRC Files/src/player/skills/Shade.cs b/jRandomSkills - SRC Files/src/player/skills/Shade.cs index 9f13ffc..36984f3 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Shade.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Shade.cs @@ -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); diff --git a/jRandomSkills - SRC Files/src/player/skills/Spectator.cs b/jRandomSkills - SRC Files/src/player/skills/Spectator.cs index 273b776..046b2e7 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Spectator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Spectator.cs @@ -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 cameras = []; public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(); diff --git a/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs b/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs index 2a24519..8f649af 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs @@ -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 cameras = []; private static readonly object setLock = new(); public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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(); diff --git a/jRandomSkills - SRC Files/src/player/skills/Thorns.cs b/jRandomSkills - SRC Files/src/player/skills/Thorns.cs index bcac47f..d9433b5 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Thorns.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Thorns.cs @@ -38,7 +38,7 @@ namespace src.player.skills int damage = (int)(@event.DmgHealth * SkillsInfo.GetValue(skillName, "healthTakenScale")); damage = Math.Min(damage, SkillsInfo.GetValue(skillName, "maxTakenDamagePerShot")); - SkillUtils.TakeHealth(attackerEvent.PlayerPawn.Value, damage); + SkillUtils.TakeHealth(attackerEvent.PlayerPawn.Value, damage, victimEvent, "planted_c4"); attackerEvent.EmitSound("Player.DamageBody.Onlooker"); } } diff --git a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs index 5057631..59610a2 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs @@ -24,7 +24,21 @@ namespace src.player.skills SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(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((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(trigger); SkillUtils.SafeKillEntity(glow); SkillUtils.SafeKillEntity(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(skillName, "damage")); + SkillUtils.TakeHealth(victimPawn, SkillsInfo.GetValue(skillName, "damage"), thrower, "weapon_knife"); } } diff --git a/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs b/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs index 847ddae..4697a19 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs @@ -10,7 +10,7 @@ namespace src.player.skills public class ToxicSmoke : ISkill { private const Skills skillName = Skills.ToxicSmoke; - private static readonly ConcurrentDictionary smokes = []; + private static readonly ConcurrentDictionary smokes = []; private readonly static ConcurrentDictionary 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(skillName, "smokeRadius"); int smokeDamage = SkillsInfo.GetValue(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) diff --git a/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs new file mode 100644 index 0000000..98ebac3 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs @@ -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 wires = []; + private static readonly ConcurrentDictionary 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(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((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(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(skillName, "wireHeight"); + float maxDistance = SkillsInfo.GetValue(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(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(skillName, "triggerRadius"); + int revealTicks = (int)(SkillsInfo.GetValue(skillName, "radarDuration") * 64); + float bodyHeight = SkillsInfo.GetValue(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; + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs b/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs index 95655f1..e931bcc 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs @@ -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(); 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((int)glowInfo.RelayIndex); + var glowEntity = Utilities.GetEntityFromIndex((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((int)glowInfo.RelayIndex); - if (glowEntity1 == null || !glowEntity1.IsValid) continue; - - var glowEntity2 = Utilities.GetEntityFromIndex((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 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) diff --git a/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs b/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs index fadf1b5..3637777 100644 --- a/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs @@ -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(); + + 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(); + 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; } diff --git a/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs b/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs index 3cadbe4..1ffd167 100644 --- a/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs +++ b/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs @@ -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) @@ -200,4 +201,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/utils/EntityManager.cs b/jRandomSkills - SRC Files/src/utils/EntityManager.cs index 7a0cdb6..6f775e7 100644 --- a/jRandomSkills - SRC Files/src/utils/EntityManager.cs +++ b/jRandomSkills - SRC Files/src/utils/EntityManager.cs @@ -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()); diff --git a/jRandomSkills - SRC Files/src/utils/Localization.cs b/jRandomSkills - SRC Files/src/utils/Localization.cs index 5047116..362e9c0 100644 --- a/jRandomSkills - SRC Files/src/utils/Localization.cs +++ b/jRandomSkills - SRC Files/src/utils/Localization.cs @@ -22,10 +22,33 @@ namespace src.utils private static string defaultLangCode = "en"; + private static readonly Dictionary skillKeys = BuildSkillKeys(""); + private static readonly Dictionary skillDescKeys = BuildSkillKeys("_desc"); + private static readonly Dictionary skillDesc2Keys = BuildSkillKeys("_desc2"); + + private static Dictionary BuildSkillKeys(string suffix) + { + var map = new Dictionary(); + foreach (Skills skill in Enum.GetValues()) + 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; } diff --git a/jRandomSkills - SRC Files/src/utils/PlayerManager.cs b/jRandomSkills - SRC Files/src/utils/PlayerManager.cs index ccd971c..97e661d 100644 --- a/jRandomSkills - SRC Files/src/utils/PlayerManager.cs +++ b/jRandomSkills - SRC Files/src/utils/PlayerManager.cs @@ -12,6 +12,8 @@ namespace src.utils private static int cachedTick = int.MinValue; private static List 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("weapon_c4").FirstOrDefault(); } public static List 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("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 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 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); diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs index cfd55b1..25ac717 100644 --- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs +++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs @@ -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 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; diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll index ba4d9ab..ed3424a 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll differ diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json index 5018c99..5617b60 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json @@ -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, diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll index fc47e69..e6e3986 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll differ diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json index a300f14..0d0d15f 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json @@ -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", diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json index 54d6f6c..0866036 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json @@ -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", diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json index e916741..64fa588 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json @@ -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", @@ -529,4 +570,4 @@ "menu_controlls_scroll": "W/S - Défilement", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Sélectionner" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json index 16d4bbf..9dd510d 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json @@ -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", @@ -529,4 +570,4 @@ "menu_controlls_scroll": "W/S – Przewijanie", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Wybierz" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json index c2cddfd..e3b4a3b 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json @@ -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", diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json index dd0196c..57f0118 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json @@ -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": "Вы видите врагов сквозь стены", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S — Прокрутка", "menu_controlls_padding": " | ", "menu_controlls_select": "E — Выбрать" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json index e8697da..0b6fc08 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json @@ -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", diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json index c922535..bb09467 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json and b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json differ diff --git a/updater.json b/updater.json index 778bd84..8832adf 100644 --- a/updater.json +++ b/updater.json @@ -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",