diff --git a/.github/en.md b/.github/en.md index 6580e43..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 | 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.md b/README.md index 05a005a..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 | diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs index f24938e..eb93e6c 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); } @@ -88,6 +92,26 @@ namespace src Debug.WriteToDebug($"Loaded: {skill.Skill}"); } + private static bool TryClaimCurseTarget(object[]? param) + { + if (param == null || param.Length < 2) return true; + if (param[0] is not CCSPlayerController curser || !curser.IsValid) return true; + if (param[1] is not string[] commands || commands.Length < 1) return true; + if (!uint.TryParse(commands[0], out uint victimIndex)) return true; + + var victim = Utilities.GetPlayerFromIndex((int)victimIndex); + if (victim == null || !victim.IsValid) return true; + + if (SkillUtils.TryClaimCurse(curser.Index, victimIndex)) return true; + + if (!SkillUtils.AnyCurseCapacity(curser)) + return SkillUtils.TryClaimCurse(curser.Index, victimIndex, true); + + var curserEvent = PlayerManager.GetPlayerFromEvent(curser); + curserEvent?.PrintToChat($" {ChatColors.Red}{curserEvent.GetTranslation("curse_limit_info", victim.PlayerName)}"); + return false; + } + private static readonly ConcurrentDictionary<(string Skill, string Method), MethodInfo?> _skillMethodCache = new(); internal object? SkillAction(string skill, string methodName, object[]? param = null) @@ -101,6 +125,15 @@ namespace src SkillsUsedThisMap.TryAdd(skill, 0); } + if (SkillUtils.CurseLimitEnabled && SkillUtils.IsCurseSkill(skill)) + { + if (methodName == "DisableSkill" && param?.Length > 0 && param[0] is CCSPlayerController curser && curser.IsValid) + SkillUtils.ReleaseCurse(curser.Index); + + if (methodName == "TypeSkill" && !TryClaimCurseTarget(param)) + return null; + } + var method = _skillMethodCache.GetOrAdd((skill, methodName), key => { string className = $"src.player.skills.{key.Skill}"; @@ -333,9 +366,11 @@ namespace src public bool IsDrawing { get; set; } public DateTime SkillHudExpired { get; set; } public DateTime SkillDescriptionHudExpired { get; set; } + public DateTime HudSuppressedUntil { get; set; } public string? PrintHTML { get; set; } public bool DisplayHUD { get; set; } public bool SkillUsed = false; + public bool? HudOnDeathBlocked { get; set; } } public class jSkill_SkillInfo(Skills skill, string color, bool display) diff --git a/jRandomSkills - SRC Files/src/lang/de.json b/jRandomSkills - SRC Files/src/lang/de.json index a300f14..bebaa5d 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,12 @@ "toxicsmoke": "Giftiger Rauch", "toxicsmoke_desc": "Deine Rauchgranaten verursachen Schaden", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen", @@ -491,6 +532,7 @@ "duplicate_player": "Mehr als ein Spieler mit demselben Namen gefunden.", "selectplayerskill_command": "Gib /t ein", "selectplayerskill_incorrect_enemy_index": "Es wurden keine Spieler gefunden, die ausgewählt werden können.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Keine solche CHATCOLORS.REDskill gefunden", "player_not_found_setskill": "Kein solcher CHATCOLORS.REDplayer gefunden", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S – Scrollen", "menu_controlls_padding": " | ", "menu_controlls_select": "E – Auswählen" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/en.json b/jRandomSkills - SRC Files/src/lang/en.json index 54d6f6c..172540e 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,12 @@ "toxicsmoke": "Toxic Smoke", "toxicsmoke_desc": "Your smoke grenades deal damage", + "tripwire": "Tripwire", + "tripwire_desc": "Click [css_useSkill] to string a wire between two walls. Enemies touching it show on your radar", + "tripwire_no_wall_info": "No walls close enough on both sides.", + "tripwire_placed_info": "Tripwire placed.", + "tripwire_triggered_info": "'{0}' tripped your wire.", + "wallhack": "Wallhack", "wallhack_desc": "You can see enemies through walls", @@ -491,6 +532,7 @@ "duplicate_player": "More than one player found with the same name.", "selectplayerskill_command": "Type /t", "selectplayerskill_incorrect_enemy_index": "No players were found to select.", + "curse_limit_info": "The curse limit for {0} has been reached", "skill_not_found_setskill": "No such CHATCOLORS.REDskill found", "player_not_found_setskill": "No such CHATCOLORS.REDplayer found", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S - Scroll", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Select" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/fr.json b/jRandomSkills - SRC Files/src/lang/fr.json index e916741..19a008a 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,12 @@ "toxicsmoke": "Fumée Toxique", "toxicsmoke_desc": "Vos grenades fumigènes infligent des dégâts", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Vous pouvez voir les ennemis à travers les murs", @@ -491,6 +531,7 @@ "duplicate_player": "Plus d’un joueur trouvé avec le même nom.", "selectplayerskill_command": "Tapez /t", "selectplayerskill_incorrect_enemy_index": "Aucun joueur trouvé à sélectionner.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Aucune CHATCOLORS.REDcompétence trouvée", "player_not_found_setskill": "Aucun CHATCOLORS.REDjoueur trouvé", @@ -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..c8958da 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,12 @@ "toxicsmoke": "Toksyczny Dym", "toxicsmoke_desc": "Twoje granaty dymny zadają obrażenia", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Widzisz wrogów przez ściany", @@ -491,6 +531,7 @@ "duplicate_player": "Znaleziono więcej niż jednego gracza o tej samej nazwie.", "selectplayerskill_command": "Wpisz /t", "selectplayerskill_incorrect_enemy_index": "Nie znaleziono graczy do wyboru.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nie znaleziono takiej CHATCOLORS.REDsupermocy", "player_not_found_setskill": "Nie znaleziono takiego CHATCOLORS.REDgracza", @@ -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..907e797 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,12 @@ "toxicsmoke": "Smoke Tóxica", "toxicsmoke_desc": "Suas smokes causam dano", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Você pode ver inimigos através das paredes", @@ -491,6 +532,7 @@ "duplicate_player": "Mais de um jogador encontrado com o mesmo nome.", "selectplayerskill_command": "Digite /t", "selectplayerskill_incorrect_enemy_index": "Não foram encontrados jogadores para selecionar.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nenhuma habilidade CHATCOLORS.RED encontrada", "player_not_found_setskill": "Nenhum jogador CHATCOLORS.RED encontrado", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S - Rolar", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Selecionar" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/ru.json b/jRandomSkills - SRC Files/src/lang/ru.json index dd0196c..f77d7cc 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,12 @@ "toxicsmoke": "Токсичный дым", "toxicsmoke_desc": "Ваши дымовые гранаты наносят урон", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Воллхак", "wallhack_desc": "Вы видите врагов сквозь стены", @@ -491,6 +532,7 @@ "duplicate_player": "Найдено несколько игроков с таким именем.", "selectplayerskill_command": "Введите /t", "selectplayerskill_incorrect_enemy_index": "Игроки для выбора не найдены.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Навык не найден", "player_not_found_setskill": "Игрок не найден", @@ -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..18a1ceb 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", @@ -190,7 +208,7 @@ "healingsmoke_desc": "Sis bombaların iyileştirir", "hermit": "Münzevi", - "hermit_desc": "Öldüğünde mernin ve canının biraz yenilenir", + "hermit_desc": "Öldürdüğünde mermi ve canın yenilenir", "holyhandgrenade": "Kutsal El Bombası", "holyhandgrenade_desc": "El bomban iki kat hasara ve menzile sahip", @@ -241,12 +259,22 @@ "jumpban_player_info": "'{0}' artık zıplayamaz.", "jumpban_select_info": "Zıplaması yasaklanacak oyuncu:", + "jumpcurse": "Zıplama Laneti", + "jumpcurse_desc": "Seçtiğin rakip, takım arkadaşları her zıpladığında zıplar", + "jumpcurse_disable_info": "Bacakların yeniden senin.", + "jumpcurse_enemy_info": "Artık takım arkadaşların zıpladıkça sen de zıplıyorsun.", + "jumpcurse_player_info": "'{0}' artık takımıyla birlikte zıplıyor.", + "jumpcurse_select_info": "Zıplama laneti verilecek oyuncu:", + "jumpingjack": "Zıpzıp Jack", "jumpingjack_desc": "Zıplamak canını yeniler", "killerflash": "Ölümcül Flaş", "killerflash_desc": "Flaşına tamamen kör olan herkes ölür (sen dahil)", + "knockback": "Savurma", + "knockback_desc": "Havadayken ateş etmek seni geriye savurur", + "lifeswap": "Can Takası", "lifeswap_desc": "Can değerlerini takas etmek için bir oyuncu seç", "lifeswap_enemy_info": "Birisi canını çaldı", @@ -288,6 +316,13 @@ "deathbomb_desc": "Öldüğünde patlayarak etraftaki oyuncuları öldürürsün", "deathbomb_explosion": "BOOM!!!", + "nightmare": "Kabus", + "nightmare_desc": "Seçtiğin bir rakibe kabus görüntüsü verir", + "nightmare_disable_info": "Kabus sona erdi.", + "nightmare_enemy_info": "Gözlerini bir kabus bürüdü.", + "nightmare_player_info": "'{0}' oyuncusuna kabus gördürdün.", + "nightmare_select_info": "Kabus görecek oyuncu:", + "ninja": "Ninja", "ninja_desc": "Görünmezliğini sabit durmak %33, eğilmek %33 ve bıçak tutmak %33 artırır", @@ -408,7 +443,7 @@ "silent_desc": "Ayak seslerini ve zıplamalarını DİĞER oyuncular duyamaz", "smoker": "Tiryaki", - "smoker_desc": "Sis bombalarınız asla bitmez", + "smoker_desc": "Attığın Sis asla bitmez", "sniperelite": "Keskin Nişancı", "sniperelite_customname": "Keskin Nişancı", @@ -450,6 +485,12 @@ "toxicsmoke": "Zehirli Sis", "toxicsmoke_desc": "Sis bombaların hasar verir", + "tripwire": "Tel Tuzağı", + "tripwire_desc": "[css_useSkill] ile iki duvar arasına tel ger. Tele değen düşman radarında görünür", + "tripwire_no_wall_info": "İki yanında da yeterince yakın duvar yok.", + "tripwire_placed_info": "Tel gerildi.", + "tripwire_triggered_info": "'{0}' teline takıldı.", + "wallhack": "Wallhack", "wallhack_desc": "Düşmanları duvarların arkasından görebilirsin", @@ -491,6 +532,7 @@ "duplicate_player": "Aynı isimde birden fazla oyuncu bulundu.", "selectplayerskill_command": "Sohbete /t yazın", "selectplayerskill_incorrect_enemy_index": "Seçilecek oyuncu yok.", + "curse_limit_info": "'{0}' üzerindeki lanet sınırı doldu, başka bir oyuncu seç", "skill_not_found_setskill": "Böyle bir yetenek yok", "player_not_found_setskill": "Böyle bir oyuncu yok", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S - Kaydır", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Seç" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/zh.json b/jRandomSkills - SRC Files/src/lang/zh.json index c922535..7564a60 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..285f837 100644 --- a/jRandomSkills - SRC Files/src/player/Debug.cs +++ b/jRandomSkills - SRC Files/src/player/Debug.cs @@ -4,6 +4,7 @@ using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Utils; using src.utils; +using System.Collections.Concurrent; using static CounterStrikeSharp.API.Core.Listeners; using static src.jRandomSkills; @@ -103,9 +104,43 @@ namespace src.player return HookResult.Continue; }); + Instance.RegisterEventHandler((@event, info) => + { + var victim = PlayerManager.GetPlayerEvent(@event.Userid); + if (victim == null || !victim.IsValid) return HookResult.Continue; + + if (!lastNativeHitGroup.TryRemove(victim.Index, out var native)) return HookResult.Continue; + if (native.HitGroup == @event.Hitgroup) return HookResult.Continue; + + WriteToDebug($"[HITGROUP] mismatch on {victim.PlayerName}: native={(HitGroup_t)native.HitGroup} event={(HitGroup_t)@event.Hitgroup} " + + $"weapon={@event.Weapon} rawDmg={native.Damage:0.#} appliedDmg={@event.DmgHealth}"); + + return HookResult.Continue; + }); + VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre); } + private static readonly ConcurrentDictionary lastNativeHitGroup = []; + + public static void Unload() + { + try { VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); } + catch { } + + lock (_writeLock) { _writer?.Dispose(); _writer = null; } + } + + private static string DescribeIdentitySplit(CCSPlayerController victim) + { + var routed = PlayerManager.GetPlayerEvent(victim); + uint routedIndex = routed?.Index ?? victim.Index; + if (routedIndex == victim.Index) return string.Empty; + + return $" SPLIT(idx {victim.Index}->{routedIndex}, skill {PlayerManager.GetPlayerByIndex(victim.Index)?.Skill}" + + $"->{PlayerManager.GetPlayerByIndex(routedIndex)?.Skill}, controllingBot={victim.ControllingBot})"; + } + private static HookResult OnTakeDamage(DynamicHook h) { CEntityInstance param = h.GetParam(0); @@ -129,7 +164,13 @@ namespace src.player var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); if (playerInfo == null) return HookResult.Continue; - WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} took damage from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}."); + var nativeHitGroup = SkillUtils.GetHitGroup(param2); + lastNativeHitGroup[victim.Index] = ((int)nativeHitGroup, param2.Damage); + + WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} took damage from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}. " + + $"[dmg={param2.Damage:0.#} hp={victimPawn.Health}/{victimPawn.MaxHealth} armor={victimPawn.ArmorValue} hitgroup={nativeHitGroup} " + + $"takes={victimPawn.TakesDamage} vskill={PlayerManager.GetPlayerByIndex(victim.Index)?.Skill} askill={playerInfo.Skill}" + + $"{DescribeIdentitySplit(victim)}]"); return HookResult.Continue; } diff --git a/jRandomSkills - SRC Files/src/player/EntityEvents.cs b/jRandomSkills - SRC Files/src/player/EntityEvents.cs new file mode 100644 index 0000000..51c36bc --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/EntityEvents.cs @@ -0,0 +1,249 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Events; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API.Modules.UserMessages; +using CounterStrikeSharp.API.Modules.Utils; +using RayTraceAPI; +using src.player.skills; +using src.utils; +using System.Collections.Concurrent; +using static CounterStrikeSharp.API.Core.Listeners; +using static src.jRandomSkills; +using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; + +namespace src.player +{ + public static partial class Event + { + private static HookResult BombBeginplant(EventBombBeginplant @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombBeginplant", @event); + return HookResult.Continue; + } + } + + private static HookResult BombAbortplant(EventBombAbortplant @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombAbortplant", @event); + return HookResult.Continue; + } + } + + private static HookResult BombPlanted(EventBombPlanted @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombPlanted", @event); + return HookResult.Continue; + } + } + + private static HookResult BombBegindefuse(EventBombBegindefuse @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombBegindefuse", @event); + return HookResult.Continue; + } + } + + private static HookResult DecoyStarted(EventDecoyStarted @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("DecoyStarted", @event); + return HookResult.Continue; + } + } + + private static HookResult DecoyDetonate(EventDecoyDetonate @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("DecoyDetonate", @event); + return HookResult.Continue; + } + } + + private static HookResult SmokegrenadeDetonate(EventSmokegrenadeDetonate @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("SmokegrenadeDetonate", @event); + return HookResult.Continue; + } + } + + private static HookResult SmokegrenadeExpired(EventSmokegrenadeExpired @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("SmokegrenadeExpired", @event); + return HookResult.Continue; + } + } + + private static HookResult OnTakeDamage(DynamicHook h) + { + lock (setLock) + { + DispatchOnTakeDamage(h); + + if (Fortnite.skillInThisRound == true && + !Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite)) + Instance.SkillAction("Fortnite", "OnTakeDamage", [h]); + + return HookResult.Continue; + } + } + + private static HookResult OnTriggerEnter(DynamicHook hook) + { + lock (setLock) + { + CBaseTrigger trigger = hook.GetParam(0); + CBaseEntity entity = hook.GetParam(1); + + DispatchToActiveSkills("OnTriggerEnter", trigger, entity); + return HookResult.Continue; + } + } + + private static HookResult OnTriggerExit(DynamicHook hook) + { + lock (setLock) + { + CBaseTrigger trigger = hook.GetParam(0); + CBaseEntity entity = hook.GetParam(1); + + DispatchToActiveSkills("OnTriggerExit", trigger, entity); + return HookResult.Continue; + } + } + + private static HookResult OnWeaponCanAcquire(DynamicHook hook) + { + lock (setLock) + { + CCSPlayer_ItemServices itemServices = hook.GetParam(0); + if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue; + + CEconItemView econItem = hook.GetParam(1); + if (econItem == null) return HookResult.Continue; + + CBasePlayerPawn pawn = itemServices.Pawn.Value; + if (pawn == null || !pawn.IsValid || pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return HookResult.Continue; + + CCSPlayerController player = pawn.Controller.Value.As(); + if (player == null || !player.IsValid) return HookResult.Continue; + + var playerInfo = PlayerManager.GetPlayerByIndex(player.Index); + if (playerInfo == null) return HookResult.Continue; + + CCSWeaponBaseVData vdata = VirtualFunctions.GetCSWeaponDataFromKeyFunc.Invoke(-1, econItem.ItemDefinitionIndex.ToString()); + if (vdata == null || vdata.Handle == IntPtr.Zero) return HookResult.Continue; + + var activeSkills = Instance.SkillPlayer + .Where(p => !p.IsDrawing) + .Select(p => p.Skill.ToString()) + .Distinct(); + + bool block = false; + foreach (string skillName in activeSkills) + { + bool? result = (bool?)Instance.SkillAction(skillName, "OnWeaponCanAcquire", [hook, player, econItem, vdata]); + if (result == true) + { + block = true; + break; + } + } + + return block ? HookResult.Handled : HookResult.Continue; + } + } + + private static HookResult WeaponDrop(DynamicHook hook) + { + lock (setLock) + { + CCSPlayerController player = hook.GetParam(0); + if (player == null || !player.IsValid) + return HookResult.Continue; + + var activeSkills = Instance.SkillPlayer + .Where(p => !p.IsDrawing) + .Select(p => p.Skill.ToString()) + .Distinct(); + + bool block = false; + foreach (string skillName in activeSkills) + { + bool? result = (bool?)Instance.SkillAction(skillName, "WeaponDrop", [hook, player]); + if (result == true) + { + block = true; + break; + } + } + return block ? HookResult.Handled : HookResult.Continue; + } + } + + private static void EntitySpawned(CEntityInstance entity) + { + lock (setLock) + { + DispatchToActiveSkills("OnEntitySpawned", entity); + } + } + + public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList) + { + long perfStart = PerfLog.Start(); + lock (setLock) + { + try + { + // Keep dying entities out of snapshots until the engine processes the kill. + var dying = EntityManager.GetRecentlyDestroyedSnapshot(); + if (dying.Count > 0) + { + foreach (var (info, player) in infoList) + { + if (player == null || !player.IsValid) continue; + foreach (var entityIndex in dying) + if (info.TransmitEntities.Contains(entityIndex)) + info.TransmitEntities.Remove(entityIndex); + } + } + } + catch (Exception ex) + { + Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}"); + } + + DispatchToActiveSkills("CheckTransmit", infoList); + } + PerfLog.Sample("CheckTransmit", perfStart); + } + + public static void EnableTransmit() + { + if (!isTransmitRegistered) + { + Instance?.RegisterListener(CheckTransmit); + isTransmitRegistered = true; + } + } + } +} 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..fe62a16 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,53 +91,20 @@ namespace src.player // Keeping the plugin alive is safer than installing a stale global hook at load time. } - private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List candidates, Dictionary assignmentCounts, Config.GameModes gameMode) + public static void Unload() { - if (candidates == null || candidates.Count == 0) return noneSkill; + 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)); + } - bool ignoreMax = gameMode == Config.GameModes.SameSkills || gameMode == Config.GameModes.TeamSkills; - - const int attempts = 6; - for (int attempt = 0; attempt < attempts; attempt++) - { - var (roll, rolled) = RarityManager.RollRarity(); - - var filtered = candidates.Where(s => - { - if (s == null) return false; - var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); - if (def == null) return false; - - if (!string.Equals(def.Rarity ?? string.Empty, rolled.ToString(), StringComparison.OrdinalIgnoreCase)) - return false; - - if (!ignoreMax && def.MaxPerServer >= 0) - { - int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; - if (current >= def.MaxPerServer) return false; - } - - return true; - }).ToList(); - - if (filtered.Count > 0) - return filtered[Random.Shared.Next(filtered.Count)]; - } - - var fallback = candidates.Where(s => - { - var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); - if (def == null) return true; - if (ignoreMax) return true; - if (def.MaxPerServer < 0) return true; - int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; - return current < def.MaxPerServer; - }).ToList(); - - if (fallback.Count > 0) - return fallback[Random.Shared.Next(fallback.Count)]; - - return candidates[Random.Shared.Next(candidates.Count)]; + private static void TryUnhook(Action unhook) + { + try { unhook(); } + catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] unhook failed: {ex.Message}"); } } private static readonly Skills[] lateDamageSkills = [Skills.SecondLife, Skills.Phoenix, Skills.ReZombie]; @@ -180,12 +149,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) @@ -242,76 +252,61 @@ namespace src.player } } - private static HookResult BombBeginplant(EventBombBeginplant @event, GameEventInfo info) + private static HookResult PlayerHurtPre(EventPlayerHurt @event, GameEventInfo info) { lock (setLock) { - DispatchToActiveSkills("BombBeginplant", @event); + 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 HookResult BombAbortplant(EventBombAbortplant @event, GameEventInfo info) + private static bool AskSkillSuppressesHit(Skills skill, EventPlayerHurt @event) { - lock (setLock) - { - DispatchToActiveSkills("BombAbortplant", @event); - return HookResult.Continue; - } - } - - private static HookResult BombPlanted(EventBombPlanted @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("BombPlanted", @event); - return HookResult.Continue; - } - } - - private static HookResult BombBegindefuse(EventBombBegindefuse @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("BombBegindefuse", @event); - return HookResult.Continue; - } - } - - private static HookResult DecoyStarted(EventDecoyStarted @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("DecoyStarted", @event); - return HookResult.Continue; - } - } - - private static HookResult DecoyDetonate(EventDecoyDetonate @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("DecoyDetonate", @event); - return HookResult.Continue; - } - } - - private static HookResult SmokegrenadeDetonate(EventSmokegrenadeDetonate @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("SmokegrenadeDetonate", @event); - return HookResult.Continue; - } - } - - private static HookResult SmokegrenadeExpired(EventSmokegrenadeExpired @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("SmokegrenadeExpired", @event); - return HookResult.Continue; - } + if (skill == Skills.None) return false; + return (bool?)Instance.SkillAction(skill.ToString(), "PlayerHurtPre", [@event]) == true; } private static HookResult PlayerHurt(EventPlayerHurt @event, GameEventInfo info) @@ -350,113 +345,6 @@ namespace src.player } } - private static HookResult OnTakeDamage(DynamicHook h) - { - lock (setLock) - { - DispatchOnTakeDamage(h); - - if (Fortnite.skillInThisRound == true && - !Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite)) - Instance.SkillAction("Fortnite", "OnTakeDamage", [h]); - - return HookResult.Continue; - } - } - - private static HookResult OnTriggerEnter(DynamicHook hook) - { - lock (setLock) - { - CBaseTrigger trigger = hook.GetParam(0); - CBaseEntity entity = hook.GetParam(1); - - DispatchToActiveSkills("OnTriggerEnter", trigger, entity); - return HookResult.Continue; - } - } - - private static HookResult OnTriggerExit(DynamicHook hook) - { - lock (setLock) - { - CBaseTrigger trigger = hook.GetParam(0); - CBaseEntity entity = hook.GetParam(1); - - DispatchToActiveSkills("OnTriggerExit", trigger, entity); - return HookResult.Continue; - } - } - - private static HookResult OnWeaponCanAcquire(DynamicHook hook) - { - lock (setLock) - { - CCSPlayer_ItemServices itemServices = hook.GetParam(0); - if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue; - - CEconItemView econItem = hook.GetParam(1); - if (econItem == null) return HookResult.Continue; - - CBasePlayerPawn pawn = itemServices.Pawn.Value; - if (pawn == null || !pawn.IsValid || pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return HookResult.Continue; - - CCSPlayerController player = pawn.Controller.Value.As(); - if (player == null || !player.IsValid) return HookResult.Continue; - - var playerInfo = PlayerManager.GetPlayerByIndex(player.Index); - if (playerInfo == null) return HookResult.Continue; - - CCSWeaponBaseVData vdata = VirtualFunctions.GetCSWeaponDataFromKeyFunc.Invoke(-1, econItem.ItemDefinitionIndex.ToString()); - if (vdata == null || vdata.Handle == IntPtr.Zero) return HookResult.Continue; - - var activeSkills = Instance.SkillPlayer - .Where(p => !p.IsDrawing) - .Select(p => p.Skill.ToString()) - .Distinct(); - - bool block = false; - foreach (string skillName in activeSkills) - { - bool? result = (bool?)Instance.SkillAction(skillName, "OnWeaponCanAcquire", [hook, player, econItem, vdata]); - if (result == true) - { - block = true; - break; - } - } - - return block ? HookResult.Handled : HookResult.Continue; - } - } - - private static HookResult WeaponDrop(DynamicHook hook) - { - lock (setLock) - { - CCSPlayerController player = hook.GetParam(0); - if (player == null || !player.IsValid) - return HookResult.Continue; - - var activeSkills = Instance.SkillPlayer - .Where(p => !p.IsDrawing) - .Select(p => p.Skill.ToString()) - .Distinct(); - - bool block = false; - foreach (string skillName in activeSkills) - { - bool? result = (bool?)Instance.SkillAction(skillName, "WeaponDrop", [hook, player]); - if (result == true) - { - block = true; - break; - } - } - return block ? HookResult.Handled : HookResult.Continue; - } - } - private static readonly Dictionary _skillNames = Enum.GetValues().ToDictionary(s => s, s => s.ToString()); private static readonly HashSet _activeSkillsSet = []; @@ -546,6 +434,9 @@ namespace src.player DisplayHUD = true, SkillUsed = false, }; + + UpdateSkillHudExpired(playerInfo, Skills.None); + PlayerManager.Register(playerInfo); } } @@ -587,6 +478,8 @@ namespace src.player foreach (var skill in SkillData.Skills) Instance.SkillAction(skill.Skill.ToString(), "PlayerDisconnect", [leavingIndex]); + SkillUtils.ClearCursesFor(leavingIndex); + PlayerManager.UnregisterPlayer(player.Index); EntityManager.DestroyPlayerEntities(player.Index); @@ -609,14 +502,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; } @@ -647,93 +533,6 @@ namespace src.player } } - private static HookResult RoundStart(EventRoundStart @event, GameEventInfo info) - { - lock (setLock) - { - bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true; - isTransmitRegistered = false; - Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist)) - { - var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); - if (skillPlayer == null) continue; - skillPlayer.IsDrawing = !isWarmup; - skillPlayer.PrintHTML = null; - } - - Instance.RemoveListener(CheckTransmit); - int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue() ?? 0; - freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0)); - - setSkillTimer?.Kill(); - - if (isWarmup) - { - setSkillTimer = Instance?.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - return HookResult.Continue; - } - - float timeToDraw = (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f; - setSkillTimer = Instance?.AddTimer(timeToDraw, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - return HookResult.Continue; - } - } - - private static void DisableAll() - { - long perfStart = PerfLog.Start(); - DisableAllCore(); - PerfLog.End("DisableAll total", perfStart, 2.0); - } - - private static void DisableAllCore() - { - lock (setLock) - { - // Re-register CheckTransmit so the dying-entity filter covers the kills below. - EnableTransmit(); - - Fortnite.skillInThisRound = false; - EntityManager.DestroyAllTracked(); - - foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid)) - { - if (player == null || !player.IsValid) continue; - - var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - if (playerInfo == null) continue; - - ActiveSkillsThisRound.TryAdd(playerInfo.Skill.ToString(), 0); - SkillsUsedThisMap.TryAdd(playerInfo.Skill.ToString(), 0); - if (playerInfo.SpecialSkill != noneSkill.Skill) - { - ActiveSkillsThisRound.TryAdd(playerInfo.SpecialSkill.ToString(), 0); - SkillsUsedThisMap.TryAdd(playerInfo.SpecialSkill.ToString(), 0); - } - - Instance.SkillAction(playerInfo.Skill.ToString(), "DisableSkill", [player]); - - playerInfo.Skill = noneSkill.Skill; - playerInfo.SpecialSkill = noneSkill.Skill; - playerInfo.PrintHTML = null; - playerInfo.SkillChance = 1; - playerInfo.SkillUsed = false; - - RestorePlayer(player); - } - - // Reset every skill used so far on this map, not only the ones held this round: a skill - // nobody drew now would otherwise never clear state left over from an earlier round. - // Skills that never ran cannot hold state, so they stay out of the sweep. - foreach (var skillName in SkillsUsedThisMap.Keys) - Instance.SkillAction(skillName, "NewRound"); - ActiveSkillsThisRound.Clear(); - tickFailuresLogged.Clear(); - } - } - public static void RestorePlayer(CCSPlayerController? player) { if (player == null || !player.IsValid) return; @@ -750,87 +549,38 @@ namespace src.player Utilities.SetStateChanged(player, "CBasePlayerController", "m_iDesiredFOV"); } - public static void OnMapChange() + private static HookResult PlayerDeathPre(EventPlayerDeath @event, GameEventInfo info) { - lock (setLock) + try { - isTransmitRegistered = false; - Instance.RemoveListener(CheckTransmit); + var victim = @event.Userid; + if (victim == null || !victim.IsValid) return HookResult.Continue; - Fortnite.skillInThisRound = false; + if (!SkillUtils.TryConsumeKillCredit(victim.Index, out uint attackerIndex, out string? weapon)) + return HookResult.Continue; - EntityManager.SuppressKills = true; - EntityManager.DestroyAllTracked(); - foreach (var skill in SkillData.Skills) - Instance.SkillAction(skill.Skill.ToString(), "NewRound"); - EntityManager.SuppressKills = false; + var attacker = Utilities.GetPlayerFromIndex((int)attackerIndex); + if (attacker == null || !attacker.IsValid || attacker.Index == victim.Index) + return HookResult.Continue; - ActiveSkillsThisRound.Clear(); - SkillsUsedThisMap.Clear(); - nextRoundPicks.Clear(); + @event.Attacker = attacker; - playersSkills.Clear(); - staticSkills.Clear(); + if (!string.IsNullOrEmpty(weapon)) + @event.Weapon = weapon; - ctSkill = noneSkill; - tSkill = noneSkill; - allSkill = noneSkill; - - PlayerManager.Clear(); - - ConVar.Find("sv_legacy_jump")?.SetValue("1"); - } - } - - private static HookResult RoundEnd(EventRoundEnd @event, GameEventInfo info) - { - Illiterate.Disable(); - - lock (setLock) - { - Instance.AddTimer(.5f, () => + var matchStats = attacker.ActionTrackingServices?.MatchStats; + if (matchStats != null) { - if (!Config.LoadedConfig.SummaryAfterTheRound) return; - - var _players = Utilities.GetPlayers().Where(p => p.IsValid && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).OrderBy(p => p.Team).ToList(); - - foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid)) - { - string skillsText = ""; - foreach (var _player in _players) - { - var _playerSkill = PlayerManager.GetPlayerByIndex(_player.Index); - if (_playerSkill == null) continue; - - var skillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.Skill); - var specialSkillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.SpecialSkill); - if (skillInfo == null) continue; - - skillsText += $" {ChatColors.DarkRed}\u202A{_player.PlayerName}\u202C{ChatColors.Lime}: {(_playerSkill.SpecialSkill == Skills.None || specialSkillInfo == null ? player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance) : $"{player.GetSkillName(specialSkillInfo.Skill)} -> {player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance)}")}\n"; - } - - if (string.IsNullOrEmpty(skillsText)) continue; - - SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("summary"), border: "t"); - foreach (string text in skillsText.Split("\n")) - if (!string.IsNullOrEmpty(text)) - SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); - SkillUtils.PrintToChat(player, string.Empty, border: "b"); - } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - // Before the optional disable below, so the "don't repeat the current skill" - // exclusion still sees this round's skills. - Instance.AddTimer(.6f, PrecomputeNextRoundSkills, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - if (Config.LoadedConfig.DisableSkillsOnRoundEnd) - { - isTransmitRegistered = false; - Instance.AddTimer(1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - Instance.RemoveListener(CheckTransmit); + matchStats.Kills++; + Utilities.SetStateChanged(attacker, "CCSPlayerController", "m_pActionTrackingServices"); } - return HookResult.Continue; } + catch (Exception ex) + { + Debug.WriteToDebug($"PlayerDeathPre kill credit failed: {ex.Message}"); + } + + return HookResult.Continue; } private static HookResult PlayerDeath(EventPlayerDeath @event, GameEventInfo info) @@ -929,14 +679,6 @@ namespace src.player } } - private static void EntitySpawned(CEntityInstance entity) - { - lock (setLock) - { - DispatchToActiveSkills("OnEntitySpawned", entity); - } - } - private static HookResult BulletImpact(EventBulletImpact @event, GameEventInfo info) { lock (setLock) @@ -946,436 +688,8 @@ namespace src.player } } - private static void SetSkill() - { - long perfStart = PerfLog.Start(); - SetSkillCore(); - PerfLog.End("SetSkill total", perfStart, 2.0); - } - private static readonly Dictionary nextRoundPicks = []; - private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, List validPlayers, Dictionary assignmentCounts, Config.GameModes gameMode) - { - 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"))); - - if (gameMode != Config.GameModes.FullRandom) - skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill); - - if (validPlayers.Count(p => p.Team == player.Team) == 1) - { - SkillsInfo.DefaultSkillInfo[] skillsNeedsTeammates = [.. SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates)]; - skillList.RemoveAll(s => skillsNeedsTeammates.Any(s2 => s2.Name == s.Skill.ToString())); - } - - if (player.Team == CsTeam.Terrorist) - skillList.RemoveAll(s => counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - else - skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - - if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) - { - skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); - if (skillList.Count == 0) skills.Clear(); - } - - var randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); - - if (gameMode == Config.GameModes.NoRepeat) - { - if (playersSkills.TryGetValue(player.Index, out ConcurrentBag? value)) - value.Add(randomSkill); - else - playersSkills.TryAdd(player.Index, [randomSkill]); - } - - return randomSkill; - } - - private static bool IsPickStillValid(jSkill_SkillInfo pick, CCSPlayerController player, List validPlayers, Dictionary assignmentCounts) - { - if (pick.Skill == Skills.None) return true; - if (!SkillData.Skills.Any(s => s.Skill == pick.Skill)) return false; - - string name = pick.Skill.ToString(); - if (player.Team == CsTeam.Terrorist && counterterroristSkills.Any(s => s.Name == name)) return false; - if (player.Team == CsTeam.CounterTerrorist && terroristSkills.Any(s => s.Name == name)) return false; - - var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == name); - if (def == null) return false; - if (def.NeedsTeammates && validPlayers.Count(p => p.Team == player.Team) == 1) return false; - if (def.MaxPerServer >= 0 && assignmentCounts.TryGetValue(pick.Skill, out var c) && c >= def.MaxPerServer) return false; - - return true; - } - - // Runs at round end so the expensive skill selection is off the round-start hot path; - // SetSkillCore then only applies the picks. - private static void PrecomputeNextRoundSkills() - { - long perfStart = PerfLog.Start(); - lock (setLock) - { - nextRoundPicks.Clear(); - - var gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; - if (gameMode is not (Config.GameModes.Normal or Config.GameModes.FullRandom or Config.GameModes.NoRepeat)) return; - if (Instance?.GameRules == null || Instance.GameRules.WarmupPeriod == true) return; - - var validPlayers = Utilities.GetPlayers() - .Where(p => p != null && p.IsValid && !p.IsHLTV) - .Where(p => { try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } catch { return false; } }).ToList(); - - 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); - nextRoundPicks[player.Index] = pick; - - if (pick.Skill != Skills.None) - assignmentCounts[pick.Skill] = assignmentCounts.TryGetValue(pick.Skill, out var c) ? c + 1 : 1; - } - } - PerfLog.End("PrecomputeSkills total", perfStart, 2.0); - } - - public static void UpdateSkillHudExpired(jSkill_PlayerInfo skillPlayer, Skills skill) - { - float globalHudExpired = Config.LoadedConfig.SkillHudDuration; - float? skillHudExpired = SkillsInfo.GetValue(skill, "hudDuration"); - - skillPlayer.SkillHudExpired = - !skillHudExpired.HasValue ? - (globalHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalHudExpired)) - : skillHudExpired.Value == -1 ? DateTime.MaxValue - : DateTime.Now.AddSeconds(skillHudExpired.Value); - - float globalDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration; - float? skillDescriptionHudExpired = SkillsInfo.GetValue(skill, "descriptionHudDuration"); - - skillPlayer.SkillDescriptionHudExpired = - !skillDescriptionHudExpired.HasValue ? - (globalDescriptionHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalDescriptionHudExpired)) - : skillDescriptionHudExpired.Value == -1 ? DateTime.MaxValue - : DateTime.Now.AddSeconds(skillDescriptionHudExpired.Value); - } - - private static void SetSkillCore() - { - setSkillTimer = null; - lock (setLock) - { - if (Instance == null) return; - - // GameRules null = not ready; keep polling so skills land right after warmup ends. - if (Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true) - { - setSkillTimer?.Kill(); - setSkillTimer = Instance.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - return; - } - - var validPlayers = Utilities.GetPlayers() - .Where(p => p != null && p.IsValid && !p.IsHLTV) - .Where(p => - { - try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } - catch { return false; } - }).ToList(); - - if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) - { - List tSkills = [.. SkillData.Skills]; - tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - tSkill = tSkills.Count == 0 ? noneSkill : tSkills[Instance.Random.Next(tSkills.Count)]; - - List ctSkills = [.. SkillData.Skills]; - ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[Instance.Random.Next(ctSkills.Count)]; - } - else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.SameSkills) - { - List allSkills = [.. SkillData.Skills]; - allSkills.RemoveAll(s => s.Skill == allSkill.Skill || s.Skill == Skills.None || !allTeamsSkills.Any(s2 => s2.Name == s.Skill.ToString())); - allSkill = allSkills.Count == 0 ? noneSkill : allSkills[Instance.Random.Next(allSkills.Count)]; - } - else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.Debug && debugSkills.Count == 0) - debugSkills = [.. SkillData.Skills]; - - Dictionary assignmentCounts = new(); - foreach (var sp in Instance.SkillPlayer) - { - if (sp == null) continue; - if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; - else assignmentCounts[sp.Skill] = 1; - } - - foreach (var player in validPlayers) - { - if (player == null) continue; - var teammates = validPlayers.Where(p => p != null && p.IsValid && p.Team == player.Team && p != player).ToList(); - string teammateSkills = ""; - - var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); - if (skillPlayer == null) continue; - - skillPlayer.IsDrawing = false; - if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) - { - skillPlayer.Skill = Skills.None; - continue; - } - - jSkill_SkillInfo randomSkill = noneSkill; - - Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; - if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) - { - // Prefer the pick made at the end of the previous round; re-pick only when - // it no longer fits (team change, missing player, max reached). - if (nextRoundPicks.TryGetValue(player.Index, out var pre) && IsPickStillValid(pre, player, validPlayers, assignmentCounts)) - randomSkill = pre; - else - randomSkill = PickSkillForPlayer(player, skillPlayer, validPlayers, assignmentCounts, gameMode); - } - else if (gameMode == Config.GameModes.TeamSkills) - randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; - else if (gameMode == Config.GameModes.SameSkills) - randomSkill = allSkill; - else if (gameMode == Config.GameModes.Debug) - { - if (debugSkills.Count == 0) - debugSkills = [.. SkillData.Skills]; - randomSkill = debugSkills[0]; - debugSkills.RemoveAt(0); - player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); - } - - Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); - skillPlayer.Skill = randomSkill.Skill; - skillPlayer.SpecialSkill = Skills.None; - - if (randomSkill.Skill != Skills.None) - { - if (assignmentCounts.TryGetValue(randomSkill.Skill, out var cnt)) assignmentCounts[randomSkill.Skill] = cnt + 1; - else assignmentCounts[randomSkill.Skill] = 1; - } - - if (randomSkill.Skill == Skills.Illiterate) - Illiterate.Enable(); - - var playerIndex = player.Index; - Instance?.AddTimer(.2f, () => - { - var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); - if (playerTarget == null || !playerTarget.IsValid) return; - - if (randomSkill.Display) - SkillUtils.PrintToChat(playerTarget, $"{ChatColors.DarkRed}{playerTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {playerTarget.GetSkillDescription(randomSkill.Skill)}", - border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == playerTarget.Team && p != playerTarget) ? "tb" : "t"); - - if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) - Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => - { - var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); - if (playerTarget == null || !playerTarget.IsValid) return; - - if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; - Debug.WriteToDebug("Enabling skill after freeze time: " + randomSkill.Skill); - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - else - { - if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; - Debug.WriteToDebug("Enabling skill: " + randomSkill.Skill); - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); - } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); - UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); - - if (Config.LoadedConfig.TeamMateSkillChatInfo) - { - Instance?.AddTimer(.6f, () => - { - if (player == null || !player.IsValid) return; - - foreach (var teammate in teammates) - { - var teammateInfo = PlayerManager.GetPlayerByIndex(teammate.Index); - if (teammateInfo != null && teammateInfo?.Skill != null) - { - var skillInfo = SkillData.Skills.FirstOrDefault(p => p.Skill == teammateInfo.Skill); - teammateSkills += $" {ChatColors.DarkRed}\u202A{teammate.PlayerName}\u202C{ChatColors.Lime}: {(skillInfo == null ? player.GetSkillName(Skills.None) : player.GetSkillName(skillInfo.Skill, teammateInfo.SkillChance))}\n"; - } - } - - if (!string.IsNullOrEmpty(teammateSkills)) - { - SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "t"); - foreach (string text in teammateSkills.Split("\n")) - if (!string.IsNullOrEmpty(text)) - SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); - SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "b"); - } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - } - } - - nextRoundPicks.Clear(); - } - } - - public static void SetRandomSkill(CCSPlayerController player) - { - lock (setLock) - { - var validPlayers = Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).ToList(); - - if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) - { - List tSkills = [.. SkillData.Skills]; - tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - tSkill = tSkills.Count == 0 ? noneSkill : tSkills[0]; - - List ctSkills = [.. SkillData.Skills]; - ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[0]; - } - - if (player == null) return; - var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); - if (skillPlayer == null) return; - - skillPlayer.IsDrawing = false; - if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) - { - skillPlayer.Skill = Skills.None; - return; - } - - jSkill_SkillInfo randomSkill = noneSkill; - if (Instance?.GameRules != null && Instance?.GameRules.WarmupPeriod == false) - { - Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; - if (staticSkills.TryGetValue(player.Index, out var staticSkill)) - randomSkill = staticSkill; - else if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) - { - List 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"))); - - if (gameMode != Config.GameModes.FullRandom) - skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill); - - if (validPlayers.Count(p => p.Team == player.Team) == 1) - { - SkillsInfo.DefaultSkillInfo[] skillsNeedsTeammates = [.. SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates)]; - skillList.RemoveAll(s => skillsNeedsTeammates.Any(s2 => s2.Name == s.Skill.ToString())); - } - - if (player.Team == CsTeam.Terrorist) - skillList.RemoveAll(s => counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - else - skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - - if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) - { - skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); - if (skillList.Count == 0) skills.Clear(); - } - - var assignmentCounts = new Dictionary(); - foreach (var sp in Instance.SkillPlayer) - { - if (sp == null) continue; - if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; - else assignmentCounts[sp.Skill] = 1; - } - - randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); - } - else if (gameMode == Config.GameModes.TeamSkills) - randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; - else if (gameMode == Config.GameModes.SameSkills) - randomSkill = allSkill; - else if (gameMode == Config.GameModes.Debug) - { - if (debugSkills.Count == 0) - debugSkills = [.. SkillData.Skills]; - randomSkill = debugSkills[0]; - debugSkills.RemoveAt(0); - player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); - } - } - - Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); - skillPlayer.Skill = randomSkill.Skill; - skillPlayer.SpecialSkill = Skills.None; - - if (randomSkill.Display && Config.LoadedConfig.YourSkillChatInfo) - SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}", - border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t"); - - if (randomSkill.Skill == Skills.Illiterate) - Illiterate.Enable(); - - Instance?.AddTimer(.2f, () => - { - if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) - Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => - { - if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != randomSkill.Skill) return; - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - else - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); - UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); - } - } - - public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList) - { - long perfStart = PerfLog.Start(); - lock (setLock) - { - try - { - // Keep dying entities out of snapshots until the engine processes the kill. - var dying = EntityManager.GetRecentlyDestroyedSnapshot(); - if (dying.Count > 0) - { - foreach (var (info, player) in infoList) - { - if (player == null || !player.IsValid) continue; - foreach (var entityIndex in dying) - if (info.TransmitEntities.Contains(entityIndex)) - info.TransmitEntities.Remove(entityIndex); - } - } - } - catch (Exception ex) - { - Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}"); - } - - DispatchToActiveSkills("CheckTransmit", infoList); - } - PerfLog.Sample("CheckTransmit", perfStart); - } - public static void UpdateSkillHUD(CCSPlayerController? player, string? headerLine, string? centerLine, string? extraLine, bool isDescription) { lock (setLock) @@ -1409,15 +723,5 @@ namespace src.player } } - public static void EnableTransmit() - { - if (!isTransmitRegistered) - { - Instance?.RegisterListener(CheckTransmit); - isTransmitRegistered = true; - } - } - - public static DateTime GetFreezeTimeEnd() => freezeTimeEnd; } -} \ No newline at end of file +} 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/RoundEvents.cs b/jRandomSkills - SRC Files/src/player/RoundEvents.cs new file mode 100644 index 0000000..71d2d4b --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/RoundEvents.cs @@ -0,0 +1,692 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Events; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API.Modules.UserMessages; +using CounterStrikeSharp.API.Modules.Utils; +using RayTraceAPI; +using src.player.skills; +using src.utils; +using System.Collections.Concurrent; +using static CounterStrikeSharp.API.Core.Listeners; +using static src.jRandomSkills; +using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; + +namespace src.player +{ + public static partial class Event + { + private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List candidates, Dictionary assignmentCounts, Config.GameModes gameMode) + { + if (candidates == null || candidates.Count == 0) return noneSkill; + + bool ignoreMax = gameMode == Config.GameModes.SameSkills || gameMode == Config.GameModes.TeamSkills; + + const int attempts = 6; + for (int attempt = 0; attempt < attempts; attempt++) + { + var (roll, rolled) = RarityManager.RollRarity(); + + var filtered = candidates.Where(s => + { + if (s == null) return false; + var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); + if (def == null) return false; + + if (!string.Equals(def.Rarity ?? string.Empty, rolled.ToString(), StringComparison.OrdinalIgnoreCase)) + return false; + + if (!ignoreMax && def.MaxPerServer >= 0) + { + int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; + if (current >= def.MaxPerServer) return false; + } + + return true; + }).ToList(); + + if (filtered.Count > 0) + return filtered[Random.Shared.Next(filtered.Count)]; + } + + var fallback = candidates.Where(s => + { + var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); + if (def == null) return true; + if (ignoreMax) return true; + if (def.MaxPerServer < 0) return true; + int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; + return current < def.MaxPerServer; + }).ToList(); + + if (fallback.Count > 0) + return fallback[Random.Shared.Next(fallback.Count)]; + + return candidates[Random.Shared.Next(candidates.Count)]; + } + + private static HookResult RoundStart(EventRoundStart @event, GameEventInfo info) + { + lock (setLock) + { + bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true; + isTransmitRegistered = false; + SkillUtils.ClearKillCredits(); + SkillUtils.ClearCurses(); + Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist)) + { + var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); + if (skillPlayer == null) continue; + skillPlayer.IsDrawing = !isWarmup; + skillPlayer.PrintHTML = null; + } + + Instance.RemoveListener(CheckTransmit); + int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue() ?? 0; + freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0)); + + setSkillTimer?.Kill(); + + if (isWarmup) + { + setSkillTimer = Instance?.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + return HookResult.Continue; + } + + float timeToDraw = (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f; + setSkillTimer = Instance?.AddTimer(timeToDraw, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + return HookResult.Continue; + } + } + + private static void DisableAll() + { + long perfStart = PerfLog.Start(); + DisableAllCore(); + PerfLog.End("DisableAll total", perfStart, 2.0); + } + + private static void DisableAllCore() + { + lock (setLock) + { + // Re-register CheckTransmit so the dying-entity filter covers the kills below. + EnableTransmit(); + + Fortnite.skillInThisRound = false; + EntityManager.DestroyAllTracked(); + + foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid)) + { + if (player == null || !player.IsValid) continue; + + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo == null) continue; + + ActiveSkillsThisRound.TryAdd(playerInfo.Skill.ToString(), 0); + SkillsUsedThisMap.TryAdd(playerInfo.Skill.ToString(), 0); + if (playerInfo.SpecialSkill != noneSkill.Skill) + { + ActiveSkillsThisRound.TryAdd(playerInfo.SpecialSkill.ToString(), 0); + SkillsUsedThisMap.TryAdd(playerInfo.SpecialSkill.ToString(), 0); + } + + Instance.SkillAction(playerInfo.Skill.ToString(), "DisableSkill", [player]); + + playerInfo.Skill = noneSkill.Skill; + playerInfo.SpecialSkill = noneSkill.Skill; + playerInfo.PrintHTML = null; + playerInfo.SkillChance = 1; + playerInfo.SkillUsed = false; + + RestorePlayer(player); + } + + // Reset every skill used so far on this map, not only the ones held this round: a skill + // nobody drew now would otherwise never clear state left over from an earlier round. + // Skills that never ran cannot hold state, so they stay out of the sweep. + foreach (var skillName in SkillsUsedThisMap.Keys) + Instance.SkillAction(skillName, "NewRound"); + ActiveSkillsThisRound.Clear(); + tickFailuresLogged.Clear(); + } + } + + public static void OnMapChange() + { + lock (setLock) + { + isTransmitRegistered = false; + Instance.RemoveListener(CheckTransmit); + + Fortnite.skillInThisRound = false; + + EntityManager.SuppressKills = true; + EntityManager.DestroyAllTracked(); + foreach (var skill in SkillData.Skills) + Instance.SkillAction(skill.Skill.ToString(), "NewRound"); + EntityManager.SuppressKills = false; + + ActiveSkillsThisRound.Clear(); + SkillsUsedThisMap.Clear(); + nextRoundPicks.Clear(); + + playersSkills.Clear(); + staticSkills.Clear(); + + ctSkill = noneSkill; + tSkill = noneSkill; + allSkill = noneSkill; + + PlayerManager.Clear(); + + ConVar.Find("sv_legacy_jump")?.SetValue("1"); + } + } + + private static HookResult RoundEnd(EventRoundEnd @event, GameEventInfo info) + { + Illiterate.Disable(); + DispatchToActiveSkills("RoundEnd"); + + lock (setLock) + { + Instance.AddTimer(.5f, () => + { + if (!Config.LoadedConfig.SummaryAfterTheRound) return; + + var _players = Utilities.GetPlayers().Where(p => p.IsValid && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).OrderBy(p => p.Team).ToList(); + + foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid)) + { + string skillsText = ""; + foreach (var _player in _players) + { + var _playerSkill = PlayerManager.GetPlayerByIndex(_player.Index); + if (_playerSkill == null) continue; + + var skillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.Skill); + var specialSkillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.SpecialSkill); + if (skillInfo == null) continue; + + skillsText += $" {ChatColors.DarkRed}\u202A{_player.PlayerName}\u202C{ChatColors.Lime}: {(_playerSkill.SpecialSkill == Skills.None || specialSkillInfo == null ? player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance) : $"{player.GetSkillName(specialSkillInfo.Skill)} -> {player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance)}")}\n"; + } + + if (string.IsNullOrEmpty(skillsText)) continue; + + SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("summary"), border: "t"); + foreach (string text in skillsText.Split("\n")) + if (!string.IsNullOrEmpty(text)) + SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); + SkillUtils.PrintToChat(player, string.Empty, border: "b"); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + // Before the optional disable below, so the "don't repeat the current skill" + // exclusion still sees this round's skills. + Instance.AddTimer(.6f, PrecomputeNextRoundSkills, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + if (Config.LoadedConfig.DisableSkillsOnRoundEnd) + { + isTransmitRegistered = false; + Instance.AddTimer(1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + Instance.RemoveListener(CheckTransmit); + } + return HookResult.Continue; + } + } + + private static void SetSkill() + { + long perfStart = PerfLog.Start(); + SetSkillCore(); + PerfLog.End("SetSkill total", perfStart, 2.0); + } + + private sealed class PickContext + { + public required List 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); + + int teamCount = player.Team == CsTeam.Terrorist ? ctx.TerroristCount : ctx.CounterTerroristCount; + if (teamCount == 1) + skillList.RemoveAll(s => ctx.NeedsTeammates.Contains(s.Skill)); + + if (player.Team == CsTeam.Terrorist) + skillList.RemoveAll(s => ctx.CtOnly.Contains(s.Skill)); + else + skillList.RemoveAll(s => ctx.TOnly.Contains(s.Skill)); + + if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) + { + skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); + if (skillList.Count == 0) skills.Clear(); + } + + var randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); + + if (gameMode == Config.GameModes.NoRepeat) + { + if (playersSkills.TryGetValue(player.Index, out ConcurrentBag? value)) + value.Add(randomSkill); + else + playersSkills.TryAdd(player.Index, [randomSkill]); + } + + return randomSkill; + } + + private static bool IsPickStillValid(jSkill_SkillInfo pick, CCSPlayerController player, List validPlayers, Dictionary assignmentCounts) + { + if (pick.Skill == Skills.None) return true; + if (!SkillData.Skills.Any(s => s.Skill == pick.Skill)) return false; + + string name = pick.Skill.ToString(); + if (player.Team == CsTeam.Terrorist && counterterroristSkills.Any(s => s.Name == name)) return false; + if (player.Team == CsTeam.CounterTerrorist && terroristSkills.Any(s => s.Name == name)) return false; + + var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == name); + if (def == null) return false; + if (def.NeedsTeammates && validPlayers.Count(p => p.Team == player.Team) == 1) return false; + if (def.MaxPerServer >= 0 && assignmentCounts.TryGetValue(pick.Skill, out var c) && c >= def.MaxPerServer) return false; + + return true; + } + + // Runs at round end so the expensive skill selection is off the round-start hot path; + // SetSkillCore then only applies the picks. + private static void PrecomputeNextRoundSkills() + { + long perfStart = PerfLog.Start(); + lock (setLock) + { + nextRoundPicks.Clear(); + + var gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; + if (gameMode is not (Config.GameModes.Normal or Config.GameModes.FullRandom or Config.GameModes.NoRepeat)) return; + if (Instance?.GameRules == null || Instance.GameRules.WarmupPeriod == true) return; + + var validPlayers = Utilities.GetPlayers() + .Where(p => p != null && p.IsValid && !p.IsHLTV) + .Where(p => { try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } catch { return false; } }).ToList(); + + var ctx = BuildPickContext(validPlayers); + + Dictionary assignmentCounts = []; + foreach (var player in validPlayers) + { + var skillPlayer = PlayerManager.GetPlayerByIndex(player.Index); + if (skillPlayer == null) continue; + + var pick = PickSkillForPlayer(player, skillPlayer, ctx, assignmentCounts, gameMode); + nextRoundPicks[player.Index] = pick; + + if (pick.Skill != Skills.None) + assignmentCounts[pick.Skill] = assignmentCounts.TryGetValue(pick.Skill, out var c) ? c + 1 : 1; + } + } + PerfLog.End("PrecomputeSkills total", perfStart, 2.0); + } + + public static void UpdateSkillHudExpired(jSkill_PlayerInfo skillPlayer, Skills skill) + { + float globalHudExpired = Config.LoadedConfig.SkillHudDuration; + float? skillHudExpired = SkillsInfo.GetValue(skill, "hudDuration"); + + skillPlayer.SkillHudExpired = + !skillHudExpired.HasValue ? + (globalHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalHudExpired)) + : skillHudExpired.Value == -1 ? DateTime.MaxValue + : DateTime.Now.AddSeconds(skillHudExpired.Value); + + float globalDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration; + float? skillDescriptionHudExpired = SkillsInfo.GetValue(skill, "descriptionHudDuration"); + + skillPlayer.SkillDescriptionHudExpired = + !skillDescriptionHudExpired.HasValue ? + (globalDescriptionHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalDescriptionHudExpired)) + : skillDescriptionHudExpired.Value == -1 ? DateTime.MaxValue + : DateTime.Now.AddSeconds(skillDescriptionHudExpired.Value); + } + + private static void SetSkillCore() + { + setSkillTimer = null; + lock (setLock) + { + if (Instance == null) return; + + // GameRules null = not ready; keep polling so skills land right after warmup ends. + if (Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true) + { + setSkillTimer?.Kill(); + setSkillTimer = Instance.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + return; + } + + var validPlayers = Utilities.GetPlayers() + .Where(p => p != null && p.IsValid && !p.IsHLTV) + .Where(p => + { + try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } + catch { return false; } + }).ToList(); + + if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) + { + List tSkills = [.. SkillData.Skills]; + tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + tSkill = tSkills.Count == 0 ? noneSkill : tSkills[Instance.Random.Next(tSkills.Count)]; + + List ctSkills = [.. SkillData.Skills]; + ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[Instance.Random.Next(ctSkills.Count)]; + } + else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.SameSkills) + { + List allSkills = [.. SkillData.Skills]; + allSkills.RemoveAll(s => s.Skill == allSkill.Skill || s.Skill == Skills.None || !allTeamsSkills.Any(s2 => s2.Name == s.Skill.ToString())); + allSkill = allSkills.Count == 0 ? noneSkill : allSkills[Instance.Random.Next(allSkills.Count)]; + } + else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.Debug && debugSkills.Count == 0) + debugSkills = [.. SkillData.Skills]; + + Dictionary assignmentCounts = new(); + foreach (var sp in Instance.SkillPlayer) + { + if (sp == null) continue; + if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; + else assignmentCounts[sp.Skill] = 1; + } + + PickContext? pickContext = null; + + foreach (var player in validPlayers) + { + if (player == null) continue; + var teammates = validPlayers.Where(p => p != null && p.IsValid && p.Team == player.Team && p != player).ToList(); + string teammateSkills = ""; + + var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); + if (skillPlayer == null) continue; + + skillPlayer.IsDrawing = false; + skillPlayer.HudOnDeathBlocked = null; + if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) + { + skillPlayer.Skill = Skills.None; + continue; + } + + jSkill_SkillInfo randomSkill = noneSkill; + + Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; + if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) + { + // Prefer the pick made at the end of the previous round; re-pick only when + // it no longer fits (team change, missing player, max reached). + if (nextRoundPicks.TryGetValue(player.Index, out var pre) && IsPickStillValid(pre, player, validPlayers, assignmentCounts)) + randomSkill = pre; + else + { + pickContext ??= BuildPickContext(validPlayers); + randomSkill = PickSkillForPlayer(player, skillPlayer, pickContext, assignmentCounts, gameMode); + } + } + else if (gameMode == Config.GameModes.TeamSkills) + randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; + else if (gameMode == Config.GameModes.SameSkills) + randomSkill = allSkill; + else if (gameMode == Config.GameModes.Debug) + { + if (debugSkills.Count == 0) + debugSkills = [.. SkillData.Skills]; + randomSkill = debugSkills[0]; + debugSkills.RemoveAt(0); + player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); + } + + Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); + skillPlayer.Skill = randomSkill.Skill; + skillPlayer.SpecialSkill = Skills.None; + + if (randomSkill.Skill != Skills.None) + { + if (assignmentCounts.TryGetValue(randomSkill.Skill, out var cnt)) assignmentCounts[randomSkill.Skill] = cnt + 1; + else assignmentCounts[randomSkill.Skill] = 1; + } + + if (randomSkill.Skill == Skills.Illiterate) + Illiterate.Enable(); + + var playerIndex = player.Index; + Instance?.AddTimer(.2f, () => + { + var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); + if (playerTarget == null || !playerTarget.IsValid) return; + + if (randomSkill.Display) + SkillUtils.PrintToChat(playerTarget, $"{ChatColors.DarkRed}{playerTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {playerTarget.GetSkillDescription(randomSkill.Skill)}", + border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == playerTarget.Team && p != playerTarget) ? "tb" : "t"); + + if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) + Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => + { + var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); + if (playerTarget == null || !playerTarget.IsValid) return; + + if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; + Debug.WriteToDebug("Enabling skill after freeze time: " + randomSkill.Skill); + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + else + { + if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; + Debug.WriteToDebug("Enabling skill: " + randomSkill.Skill); + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); + UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); + + if (Config.LoadedConfig.TeamMateSkillChatInfo) + { + Instance?.AddTimer(.6f, () => + { + if (player == null || !player.IsValid) return; + + foreach (var teammate in teammates) + { + var teammateInfo = PlayerManager.GetPlayerByIndex(teammate.Index); + if (teammateInfo != null && teammateInfo?.Skill != null) + { + var skillInfo = SkillData.Skills.FirstOrDefault(p => p.Skill == teammateInfo.Skill); + teammateSkills += $" {ChatColors.DarkRed}\u202A{teammate.PlayerName}\u202C{ChatColors.Lime}: {(skillInfo == null ? player.GetSkillName(Skills.None) : player.GetSkillName(skillInfo.Skill, teammateInfo.SkillChance))}\n"; + } + } + + if (!string.IsNullOrEmpty(teammateSkills)) + { + SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "t"); + foreach (string text in teammateSkills.Split("\n")) + if (!string.IsNullOrEmpty(text)) + SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); + SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "b"); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + } + + nextRoundPicks.Clear(); + } + } + + public static void SetRandomSkill(CCSPlayerController player) + { + lock (setLock) + { + var validPlayers = Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).ToList(); + + if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) + { + List tSkills = [.. SkillData.Skills]; + tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + tSkill = tSkills.Count == 0 ? noneSkill : tSkills[0]; + + List ctSkills = [.. SkillData.Skills]; + ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[0]; + } + + if (player == null) return; + var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); + if (skillPlayer == null) return; + + skillPlayer.IsDrawing = false; + if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) + { + skillPlayer.Skill = Skills.None; + return; + } + + jSkill_SkillInfo randomSkill = noneSkill; + if (Instance?.GameRules != null && Instance?.GameRules.WarmupPeriod == false) + { + Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; + if (staticSkills.TryGetValue(player.Index, out var staticSkill)) + randomSkill = staticSkill; + else if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) + { + List 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"))); + + if (gameMode != Config.GameModes.FullRandom) + skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill); + + if (validPlayers.Count(p => p.Team == player.Team) == 1) + { + SkillsInfo.DefaultSkillInfo[] skillsNeedsTeammates = [.. SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates)]; + skillList.RemoveAll(s => skillsNeedsTeammates.Any(s2 => s2.Name == s.Skill.ToString())); + } + + if (player.Team == CsTeam.Terrorist) + skillList.RemoveAll(s => counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + else + skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + + if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) + { + skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); + if (skillList.Count == 0) skills.Clear(); + } + + var assignmentCounts = new Dictionary(); + foreach (var sp in Instance.SkillPlayer) + { + if (sp == null) continue; + if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; + else assignmentCounts[sp.Skill] = 1; + } + + randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); + } + else if (gameMode == Config.GameModes.TeamSkills) + randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; + else if (gameMode == Config.GameModes.SameSkills) + randomSkill = allSkill; + else if (gameMode == Config.GameModes.Debug) + { + if (debugSkills.Count == 0) + debugSkills = [.. SkillData.Skills]; + randomSkill = debugSkills[0]; + debugSkills.RemoveAt(0); + player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); + } + } + + Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); + skillPlayer.Skill = randomSkill.Skill; + skillPlayer.SpecialSkill = Skills.None; + + if (randomSkill.Display && Config.LoadedConfig.YourSkillChatInfo) + SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}", + border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t"); + + if (randomSkill.Skill == Skills.Illiterate) + Illiterate.Enable(); + + Instance?.AddTimer(.2f, () => + { + if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) + Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => + { + if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != randomSkill.Skill) return; + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + else + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); + UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); + } + } + + public static DateTime GetFreezeTimeEnd() => freezeTimeEnd; + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Aimbot.cs b/jRandomSkills - SRC Files/src/player/skills/Aimbot.cs index 0d12b1e..d2670f4 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Aimbot.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Aimbot.cs @@ -39,6 +39,8 @@ namespace src.player.skills var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index); if (playerInfo == null || playerInfo.Skill != skillName) return; + if (!SkillUtils.IsBulletDamage(param2)) return; + int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup"); if (offset <= 0) return; 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..8f8ac6c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs @@ -30,12 +30,13 @@ namespace src.player.skills foreach (var player in PlayerManager.GetTickPlayers()) { - if (player == null || !player.IsValid || !SkillUtils.HasMenu(player)) continue; + if (player == null || !player.IsValid) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; + if (!SkillUtils.HasMenu(player)) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = []; foreach (var e in enemies) 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..867d3a3 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,11 +46,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -88,6 +91,7 @@ namespace src.player.skills targetPlayers.TryAdd(enemy.Index, 0); playersToTarget[player.Index] = enemy.Index; + targetToPlayer[enemy.Index] = player.Index; playerInfo.SkillUsed = true; var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy); @@ -106,7 +110,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; @@ -131,13 +135,14 @@ namespace src.player.skills return; uint playerIndex = player.Index; + int impactTick = Server.TickCount; Server.NextFrame(() => { if (player == null || !player.IsValid) return; if (playerPawn == null || !playerPawn.IsValid) return; - bool ishitPlayer = hitPlayer.TryGetValue(playerIndex, out int tick) && tick + 10 >= Server.TickCount; + bool ishitPlayer = hitPlayer.TryGetValue(playerIndex, out int tick) && tick + 1 >= impactTick; if (!lastShot.ContainsKey(playerIndex)) { @@ -148,7 +153,8 @@ namespace src.player.skills if (lastShot.TryRemove(playerIndex, out bool didHit) && !didHit) { eventPlayer.ExecuteClientCommand($"play player/player_damagebody_0{Instance.Random.Next(4, 8)}"); - SkillUtils.TakeHealth(playerPawn, SkillsInfo.GetValue(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..66216c0 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Darkness.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Darkness.cs @@ -54,11 +54,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -129,7 +130,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; @@ -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..869f9f6 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs @@ -27,11 +27,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = []; foreach (var enemy in enemies) @@ -84,7 +85,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = []; @@ -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..9576610 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Deaf.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Deaf.cs @@ -59,11 +59,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -121,7 +122,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -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/Earthquake.cs b/jRandomSkills - SRC Files/src/player/skills/Earthquake.cs deleted file mode 100644 index 89a8b5c..0000000 --- a/jRandomSkills - SRC Files/src/player/skills/Earthquake.cs +++ /dev/null @@ -1,141 +0,0 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Utils; -using static src.jRandomSkills; -using System.Collections.Concurrent; -using src.utils; - -namespace src.player.skills -{ - public class Earthquake : ISkill - { - //private const Skills skillName = Skills.Earthquake; - //private static readonly ConcurrentDictionary SkillPlayerInfo = []; - //private static readonly object setLock = new(); - - //public static void LoadSkill() - //{ - // SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); - //} - - //public static void NewRound() - //{ - // lock (setLock) - // SkillPlayerInfo.Clear(); - //} - - //public static void EnableSkill(CCSPlayerController player) - //{ - // SkillPlayerInfo.TryAdd(player.Index, new PlayerSkillInfo - // { - // SteamID = player.Index, - // CanUse = true, - // Cooldown = DateTime.MinValue, - // }); - //} - - //public static void DisableSkill(CCSPlayerController player) - //{ - // SkillPlayerInfo.TryRemove(player.Index, out _); - // SkillUtils.ResetPrintHTML(player); - //} - - //public static void PlayerDeath(EventPlayerDeath @event) - //{ - // var player = PlayerManager.GetPlayerEvent(@event.Userid); - // if (player == null || !player.IsValid) return; - // var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - // if (playerInfo?.Skill == skillName) - // SkillPlayerInfo.TryRemove(player.Index, out _); - //} - - //public static void OnTick() - //{ - // foreach (var player in PlayerManager.GetTickPlayers()) - // { - // var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - // if (playerInfo?.Skill == skillName) - // if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) - // UpdateHUD(player, skillInfo); - // } - //} - - //private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo) - //{ - // float cooldown = 0; - // if (skillInfo != null) - // { - // float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue(skillName, "cooldown")) - DateTime.Now).TotalSeconds); - // cooldown = Math.Max(time, 0); - - // if (cooldown == 0 && skillInfo?.CanUse == false) - // skillInfo.CanUse = true; - // } - - // if (cooldown == 0) - // return; - - // var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - // if (playerInfo == null) return; - - // string remainingLine = $"{player.GetTranslation("hud_info", $"{cooldown}")}"; - //} - - //public static void UseSkill(CCSPlayerController player) - //{ - // var playerPawn = player.PlayerPawn.Value; - // if (playerPawn?.CBodyComponent == null) return; - - // if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) - // { - // if (!player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; - // if (skillInfo.CanUse) - // { - // skillInfo.CanUse = false; - // skillInfo.Cooldown = DateTime.Now; - // MakeShake(player); - // } - // } - //} - - //private static void MakeShake(CCSPlayerController player) - //{ - // foreach (var enemy in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.PawnIsAlive)) - // CreateShake(player); - //} - - //private static void CreateShake(CCSPlayerController player) - //{ - // var pawn = player.PlayerPawn.Value; - // if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return; - - // var shake = EntityManager.CreateTrackedEnvShake(player.Index); - // if (shake == null || !shake.IsValid) return; - - // shake.Amplitude = SkillsInfo.GetValue(skillName, "amplitude"); - // shake.Frequency = SkillsInfo.GetValue(skillName, "frequency"); - // shake.Duration = SkillsInfo.GetValue(skillName, "duration"); - // shake.Radius = SkillsInfo.GetValue(skillName, "radius"); - - // shake.Teleport(new Vector(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z)); - // shake.AcceptInput("SetParent", pawn, pawn, "!activator"); - // shake.AcceptInput("StartShake"); - //} - - //public class PlayerSkillInfo - //{ - // public ulong SteamID { get; set; } - // public bool CanUse { get; set; } - // public DateTime Cooldown { get; set; } - //} - - //public class SkillConfig(Skills skill = skillName, bool active = false, string color = "#42f59b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 16f, float amplitude = 15f, float frequency = 500f, float duration = 8f, float radius = 50f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) - //{ - // public float Cooldown { get; set; } = cooldown; - // public float Amplitude { get; set; } = amplitude; - // public float Frequency { get; set; } = frequency; - // public float Duration { get; set; } = duration; - // public float Radius { get; set; } = radius; - //} - } -} \ 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..232aecb --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs @@ -0,0 +1,163 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using src.utils; +using System.Collections.Concurrent; + +namespace src.player.skills +{ + public class ExpensiveAmmo : ISkill + { + private const Skills skillName = Skills.ExpensiveAmmo; + private static readonly ConcurrentDictionary 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; + if (!SkillUtils.FiresBullets(@event.Weapon)) 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 = SkillUtils.GetSelectableEnemies(player, true); + + ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; + SkillUtils.UpdateMenu(player, menuItems); + } + } + + public static void TypeSkill(CCSPlayerController player, string[] commands) + { + if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo?.Skill != skillName) return; + + var playerEvent = PlayerManager.GetPlayerFromEvent(player); + if (playerEvent == null || !playerEvent.IsValid) return; + + if (playerInfo.SkillUsed) + { + playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("areareaper_used_info")}"); + return; + } + + string enemyId = commands[0]; + + if (!uint.TryParse(enemyId, out uint enemyIndex)) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")); + return; + } + + var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex); + + if (enemy == null || !enemy.IsValid || enemy.PlayerPawn.Value == null || !enemy.PlayerPawn.Value.IsValid) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")); + return; + } + + cursedPlayers.TryAdd(enemy.Index, 0); + playersToTarget[player.Index] = enemy.Index; + playerInfo.SkillUsed = true; + + var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy); + if (enemyEvent == null || !enemyEvent.IsValid) return; + + playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("expensiveammo_player_info", enemy.PlayerName)); + enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("expensiveammo_enemy_info", SkillsInfo.GetValue(skillName, "moneyPerShot"))); + } + + public static void EnableSkill(CCSPlayerController player) + { + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo == null) return; + playerInfo.SkillUsed = false; + + var playerEvent = PlayerManager.GetPlayerFromEvent(player); + if (playerEvent == null || !playerEvent.IsValid) return; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); + if (enemies.Length > 0) + { + ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; + SkillUtils.CreateMenu(player, menuItems); + } + else + playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}"); + } + + public static void DisableSkill(CCSPlayerController player) + { + if (player == null || !player.IsValid) return; + + if (playersToTarget.TryRemove(player.Index, out uint targetIndex)) + { + cursedPlayers.TryRemove(targetIndex, out _); + + var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex)); + if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime()) + target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("expensiveammo_disable_info")); + } + + SkillUtils.CloseMenu(player); + } + + public static void PlayerDeath(EventPlayerDeath @event) + { + var player = @event.Userid; + if (player == null || !player.IsValid) return; + + cursedPlayers.TryRemove(player.Index, out _); + SkillUtils.CloseMenu(player); + } + + public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e0c341", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int moneyPerShot = 50) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) + { + public int MoneyPerShot { get; set; } = moneyPerShot; + } + } +} 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..ac6eb3c --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Giant.cs @@ -0,0 +1,188 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using src.utils; +using System.Collections.Concurrent; +using static src.jRandomSkills; + +namespace src.player.skills +{ + public class Giant : ISkill + { + private const Skills skillName = Skills.Giant; + private static readonly ConcurrentDictionary 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 [.. SkillUtils.GetSelectableEnemies(player, true) + .Where(p => PlayerManager.GetPlayerByIndex(p.Index)?.Skill != Skills.Chicken)]; + } + + public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8ad3ff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float minScale = 1.1f, float maxScale = 1.4f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) + { + public float MinScale { get; set; } = minScale; + public float MaxScale { get; set; } = maxScale; + } + } +} 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..c8fe078 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Glitch.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Glitch.cs @@ -51,11 +51,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -128,7 +129,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -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/HealingChicken.cs b/jRandomSkills - SRC Files/src/player/skills/HealingChicken.cs deleted file mode 100644 index 6eb9e53..0000000 --- a/jRandomSkills - SRC Files/src/player/skills/HealingChicken.cs +++ /dev/null @@ -1,82 +0,0 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Timers; -using CounterStrikeSharp.API.Modules.Utils; -using src.utils; -using System.Collections.Concurrent; -using System.Drawing; -using static src.jRandomSkills; -using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; - -namespace src.player.skills -{ - public class HealingChicken : ISkill - { - //private const Skills skillName = Skills.HealingChicken; - //private readonly static ConcurrentDictionary> playerSmokes = []; - //private static readonly object setLock = new(); - - //public static void LoadSkill() - //{ - // SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); - //} - - //public static void NewRound() - //{ - - //} - - //public static void EnableSkill(CCSPlayerController player) - //{ - // SpawnChicken(player); - //} - - //public static void DisableSkill(CCSPlayerController player) - //{ - - //} - - //private static void SpawnChicken(CCSPlayerController player) - //{ - // if (player == null || !player.IsValid) return; - - // var pawn = player.PlayerPawn.Value; - // if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return; - - // int amount = SkillsInfo.GetValue(skillName, "amount"); - // for (int i = 0; i < 1; i++) - // { - // CChicken? chicken = EntityManager.CreateTrackedChicken(player.Index); - // if (chicken == null || !chicken.IsValid) continue; - - // chicken.Render = Color.Green; - // Vector offset = new ( - // (float)(100 * Math.Cos(2 * Math.PI * i / amount)), - // (float)(100 * Math.Sin(2 * Math.PI * i / amount)), - // 0 - // ); - - // chicken.Teleport(pawn.AbsOrigin + offset); - - // // Schema.SetSchemaValue(chicken.Handle, "CChicken", "m_leader", player.PlayerPawn.Raw); - // Vector? spawn = SkillUtils.GetSpawnPointVector(player); - // Schema.SetSchemaValue(chicken.Handle, "CChicken", "m_vecPathGoal", spawn); - - // Instance.AddTickTimer(1, () => - // { - // if (chicken == null || !chicken.IsValid) return; - - // Server.PrintToChatAll($"{chicken.PathGoal}, {chicken.UpdateTimer.Duration}, {chicken.UpdateTimer.Timescale}"); - // }, TimerFlags.REPEAT | TimerFlags.STOP_ON_MAPCHANGE); - // } - //} - - //public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#b5ab8f", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int amount = 3, int heal = 2, int tickCooldown = 16) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) - //{ - // public int Amount { get; set; } = amount; - // public int Heal { get; set; } = heal; - // public int TickCooldown { get; set; } = tickCooldown; - //} - } -} \ No newline at end of file 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..3e2e511 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Jammer.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Jammer.cs @@ -59,11 +59,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -111,7 +112,7 @@ namespace src.player.skills SetCrosshair(enemyEvent, false); playerInfo.SkillUsed = true; - playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("jammer_player_info", player.PlayerName)); + playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("jammer_player_info", enemy.PlayerName)); enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("jammer_enemy_info")); } @@ -150,7 +151,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -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..bffa306 100644 --- a/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs +++ b/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs @@ -64,11 +64,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -126,7 +127,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -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..74e8310 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs @@ -0,0 +1,177 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using src.utils; +using System.Collections.Concurrent; + +namespace src.player.skills +{ + public class JumpCurse : ISkill + { + private const Skills skillName = Skills.JumpCurse; + private static readonly ConcurrentDictionary 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 = SkillUtils.GetSelectableEnemies(player, true); + + ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; + SkillUtils.UpdateMenu(player, menuItems); + } + } + + public static void TypeSkill(CCSPlayerController player, string[] commands) + { + if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo?.Skill != skillName) return; + + var playerEvent = PlayerManager.GetPlayerFromEvent(player); + if (playerEvent == null || !playerEvent.IsValid) return; + + if (playerInfo.SkillUsed) + { + playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("areareaper_used_info")}"); + return; + } + + string enemyId = commands[0]; + + if (!uint.TryParse(enemyId, out uint enemyIndex)) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")); + return; + } + + var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex); + + if (enemy == null || !enemy.IsValid || enemy.PlayerPawn.Value == null || !enemy.PlayerPawn.Value.IsValid) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")); + return; + } + + cursedPlayers.TryAdd(enemy.Index, 0); + playersToTarget[player.Index] = enemy.Index; + playerInfo.SkillUsed = true; + + var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy); + if (enemyEvent == null || !enemyEvent.IsValid) return; + + playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("jumpcurse_player_info", enemy.PlayerName)); + enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("jumpcurse_enemy_info")); + } + + public static void EnableSkill(CCSPlayerController player) + { + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo == null) return; + playerInfo.SkillUsed = false; + + var playerEvent = PlayerManager.GetPlayerFromEvent(player); + if (playerEvent == null || !playerEvent.IsValid) return; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); + if (enemies.Length > 0) + { + ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; + SkillUtils.CreateMenu(player, menuItems); + } + else + playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}"); + } + + public static void DisableSkill(CCSPlayerController player) + { + if (player == null || !player.IsValid) return; + + if (playersToTarget.TryRemove(player.Index, out uint targetIndex)) + { + cursedPlayers.TryRemove(targetIndex, out _); + + var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex)); + if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime()) + target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("jumpcurse_disable_info")); + } + + SkillUtils.CloseMenu(player); + } + + public static void PlayerDeath(EventPlayerDeath @event) + { + var player = @event.Userid; + if (player == null || !player.IsValid) return; + + cursedPlayers.TryRemove(player.Index, out _); + SkillUtils.CloseMenu(player); + } + + public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#7ad1c4", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float jumpVelocity = 301f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) + { + public float JumpVelocity { get; set; } = jumpVelocity; + } + } +} 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..4fb7081 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Knockback.cs @@ -0,0 +1,56 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using src.utils; + +namespace src.player.skills +{ + public class Knockback : ISkill + { + private const Skills skillName = Skills.Knockback; + + public static void LoadSkill() + { + SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); + } + + public static void WeaponFire(EventWeaponFire @event) + { + var player = PlayerManager.GetPlayerEvent(@event.Userid); + if (player == null || !player.IsValid) return; + + var playerInfo = PlayerManager.GetPlayerByIndex(player.Index); + if (playerInfo?.Skill != skillName) return; + if (!SkillUtils.FiresBullets(@event.Weapon)) return; + + var pawn = player.PlayerPawn?.Value; + if (pawn == null || !pawn.IsValid || pawn.Health <= 0) return; + if ((pawn.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0) return; + + float force = SkillsInfo.GetValue(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..29cc08f 100644 --- a/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs @@ -26,11 +26,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e?.PlayerPawn?.Value?.Health ?? 0} HP", e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -85,7 +86,7 @@ namespace src.player.skills if (playerInfo == null) return; playerInfo.SkillUsed = false; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e.PawnHealth} HP", e.Index.ToString()))); @@ -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..78eafd5 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()))); @@ -127,7 +128,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -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..a26deed 100644 --- a/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs @@ -26,11 +26,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {(e.InGameMoneyServices == null ? 0 : e.InGameMoneyServices.Account + e.InGameMoneyServices.CashSpentThisRound)}$", e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -84,7 +85,7 @@ namespace src.player.skills if (playerInfo == null) return; playerInfo.SkillUsed = false; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {(e.InGameMoneyServices == null ? 0 : e.InGameMoneyServices.Account + e.InGameMoneyServices.CashSpentThisRound)}$", e.Index.ToString()))]; @@ -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..c650155 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs @@ -0,0 +1,236 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes; +using CounterStrikeSharp.API.Modules.Utils; +using src.utils; +using System.Collections.Concurrent; + +namespace src.player.skills +{ + public class Nightmare : ISkill + { + private const Skills skillName = Skills.Nightmare; + private static readonly ConcurrentDictionary 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 = SkillUtils.GetSelectableEnemies(player, true); + ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; + SkillUtils.UpdateMenu(player, menuItems); + } + } + + public static void EnableSkill(CCSPlayerController player) + { + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo == null) return; + playerInfo.SkillUsed = false; + + var playerEvent = PlayerManager.GetPlayerFromEvent(player); + if (playerEvent == null || !playerEvent.IsValid) return; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); + if (enemies.Length > 0) + { + ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; + SkillUtils.CreateMenu(player, menuItems); + } + else + playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")}"); + } + + public static void TypeSkill(CCSPlayerController player, string[] commands) + { + if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo?.Skill != skillName) return; + + var playerEvent = PlayerManager.GetPlayerFromEvent(player); + if (playerEvent == null || !playerEvent.IsValid) return; + + if (playerInfo.SkillUsed) + { + playerEvent.PrintToChat($" {ChatColors.Red}{playerEvent.GetTranslation("areareaper_used_info")}"); + return; + } + + if (!uint.TryParse(commands[0], out uint enemyIndex)) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")); + return; + } + + var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex); + if (enemy == null || !enemy.IsValid) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")); + return; + } + + lock (setLock) + { + if (!ApplyVolume(player.Index, enemy)) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index")); + return; + } + + playersToTarget[player.Index] = enemy.Index; + } + + playerInfo.SkillUsed = true; + + var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy); + playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("nightmare_player_info", enemy.PlayerName)); + + if (enemyEvent != null && enemyEvent.IsValid) + enemyEvent.PrintToChat($" {ChatColors.Red}" + enemyEvent.GetTranslation("nightmare_enemy_info")); + } + + public static void DisableSkill(CCSPlayerController player) + { + if (player == null || !player.IsValid) return; + + lock (setLock) + { + if (playersToTarget.TryRemove(player.Index, out uint targetIndex)) + { + RemoveVolume(targetIndex); + + var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex)); + if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime()) + target.PrintToChat($" {ChatColors.Green}" + target.GetTranslation("nightmare_disable_info")); + } + + SkillUtils.CloseMenu(player); + } + } + + public static void PlayerDeath(EventPlayerDeath @event) + { + var player = @event.Userid; + if (player == null || !player.IsValid) return; + + lock (setLock) + RemoveVolume(player.Index); + } + + public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList) + { + if (targetVolumes.IsEmpty) return; + + foreach (var (info, viewer) in infoList) + { + if (viewer == null || !viewer.IsValid) continue; + + foreach (var kvp in targetVolumes) + { + if (kvp.Key == viewer.Index) continue; + if (info.TransmitEntities.Contains(kvp.Value)) + info.TransmitEntities.Remove(kvp.Value); + } + } + } + + private static bool ApplyVolume(uint ownerIndex, CCSPlayerController target) + { + RemoveVolume(target.Index); + + var targetPawn = target.PlayerPawn?.Value; + if (targetPawn == null || !targetPawn.IsValid || targetPawn.AbsOrigin == null) return false; + + if (EntityManager.OverBudget()) return false; + + var volume = Utilities.CreateEntityByName("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); + } + + 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..c7b8e8c 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,11 +75,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -113,6 +120,7 @@ namespace src.player.skills poisonedPlayers.TryAdd(enemy.Index, 0); playersToTarget[player.Index] = enemy.Index; + targetToPlayer[enemy.Index] = player.Index; playerInfo.SkillUsed = true; var enemyEvent = PlayerManager.GetPlayerFromEvent(enemy); @@ -131,7 +139,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -141,11 +149,22 @@ namespace src.player.skills playerEvent.PrintToChat($" {ChatColors.Red}{player.GetTranslation("selectplayerskill_incorrect_enemy_index")}"); } + private static CCSPlayerController? GetSkillOwner(uint targetIndex) + { + if (!targetToPlayer.TryGetValue(targetIndex, out uint ownerIndex)) return null; + + var owner = Utilities.GetPlayerFromIndex((int)ownerIndex); + return owner != null && owner.IsValid ? owner : null; + } + public static void DisableSkill(CCSPlayerController player) { + targetToPlayer.TryRemove(player.Index, out _); + if (playersToTarget.TryRemove(player.Index, out uint targetIndex)) { poisonedPlayers.TryRemove(targetIndex, out _); + targetToPlayer.TryRemove(targetIndex, out _); var target = PlayerManager.GetPlayerFromEvent(Utilities.GetPlayerFromIndex((int)targetIndex)); if (target != null && target.IsValid && target.PawnIsAlive && !SkillUtils.IsFreezeTime()) @@ -161,6 +180,8 @@ namespace src.player.skills if (player == null || !player.IsValid) return; poisonedPlayers.TryRemove(player.Index, out _); + targetToPlayer.TryRemove(player.Index, out _); + SkillUtils.CloseMenu(player); } @@ -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..d123d4c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs +++ b/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs @@ -70,11 +70,12 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + if (!SkillUtils.HasMenu(player)) continue; + + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -133,7 +134,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -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..3c90713 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Spectator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Spectator.cs @@ -11,11 +11,14 @@ namespace src.player.skills public class Spectator : ISkill { private const Skills skillName = Skills.Spectator; + private const string cameraViewModel = "models/sprays/spray_plane.vmdl"; private static readonly ConcurrentDictionary cameras = []; + private static readonly ConcurrentDictionary lastUse = []; public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); + Instance.AddToManifest(cameraViewModel); } public static void NewRound() @@ -24,6 +27,15 @@ namespace src.player.skills EntityManager.DestroyEntity(info.Value.Item2); cameras.Clear(); + lastUse.Clear(); + } + + public static void PlayerDisconnect(uint playerIndex) + { + if (cameras.TryRemove(playerIndex, out var cameraInfo) && cameraInfo.Item2 != 0) + EntityManager.DestroyEntity(cameraInfo.Item2); + + lastUse.TryRemove(playerIndex, out _); } public static void WeaponPickup(EventItemPickup @event) @@ -43,6 +55,11 @@ namespace src.player.skills { var playerPawn = player.PlayerPawn.Value; if (playerPawn?.CBodyComponent == null) return; + + float cooldown = SkillsInfo.GetValue(skillName, "useCooldown"); + if (lastUse.TryGetValue(player.Index, out var last) && (DateTime.Now - last).TotalSeconds < cooldown) return; + lastUse[player.Index] = DateTime.Now; + ChangeCamera(player); } @@ -52,6 +69,7 @@ namespace src.player.skills ChangeCamera(player, true); EntityManager.DestroyPlayerEntities(player.Index); cameras.TryRemove(player.Index, out _); + lastUse.TryRemove(player.Index, out _); } public static void OnTick() @@ -139,12 +157,19 @@ namespace src.player.skills p.PlayerPawn.Value.Health > 0).ToList(); if (enemies.Count == 0) + { + EntityManager.DestroyEntity(camera.Index); return 0; + } var enemy = enemies[Instance.Random.Next(enemies.Count)]; var pawn = enemy.PlayerPawn.Value; - if (pawn == null || !pawn.IsValid || pawn.CameraServices == null || pawn.AbsOrigin == null) return 0; + if (pawn == null || !pawn.IsValid || pawn.CameraServices == null || pawn.AbsOrigin == null) + { + EntityManager.DestroyEntity(camera.Index); + return 0; + } QAngle angle = new(0, pawn.EyeAngles.Y, 0); @@ -159,7 +184,7 @@ namespace src.player.skills if (camNode != null) camNode.Flags = (uint)(camNode.Flags & ~(1 << 2)); - camera.SetModel("models/sprays/spray_plane.vmdl"); + camera.SetModel(cameraViewModel); camera.Render = Color.FromArgb(1, 255, 255, 255); camera.Teleport(pos, angle); camera.DispatchSpawn(); @@ -202,9 +227,10 @@ namespace src.player.skills } } - public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5da", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float distance = 100f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) + public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5da", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float distance = 100f, float useCooldown = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) { public float Distance { get; set; } = distance; + public float UseCooldown { get; set; } = useCooldown; } } } \ No newline at end of file diff --git a/jRandomSkills - SRC Files/src/player/skills/Thief.cs b/jRandomSkills - SRC Files/src/player/skills/Thief.cs index 536d276..246c3d4 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Thief.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Thief.cs @@ -25,21 +25,7 @@ namespace src.player.skills var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo?.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => - p != null && - p.IsValid) - .Select(p => PlayerManager.GetPlayerEvent(p)) - .Where(p => - p != null && - p.IsValid && - p.Team != player.Team && - p.PlayerPawn?.Value != null && - p.PlayerPawn.Value.IsValid && - p.PlayerPawn.Value.Health > 0 && - !p.IsHLTV && - p.Team != CsTeam.Spectator - && p.Team != CsTeam.None - ).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = []; foreach (var enemy in enemies) @@ -106,14 +92,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers() - .Where(p => p != null - && p.IsValid - && p.Team != player.Team - && p.PlayerPawn?.Value?.Health > 0 - && p.Team != CsTeam.Spectator - && p.Team != CsTeam.None) - .ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { 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..0edd992 --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs @@ -0,0 +1,327 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using RayTraceAPI; +using src.utils; +using System.Collections.Concurrent; +using System.Drawing; +using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; + +namespace src.player.skills +{ + public class Tripwire : ISkill + { + private const Skills skillName = Skills.Tripwire; + + private static readonly ConcurrentDictionary wires = []; + private static readonly ConcurrentDictionary SkillPlayerInfo = []; + private static readonly ConcurrentDictionary<(uint Owner, uint Target), (int Slot, int ExpiryTick)> revealed = []; + private static readonly object setLock = new(); + + private static readonly Color terroristWire = Color.FromArgb(255, 255, 64, 64); + private static readonly Color counterTerroristWire = Color.FromArgb(255, 64, 128, 255); + + private const double attemptCooldown = .5; + + public static void LoadSkill() + { + SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); + } + + public static void NewRound() + { + lock (setLock) + { + foreach (var wire in wires.Values) + DestroyWire(wire.BeamIndex); + + wires.Clear(); + SkillPlayerInfo.Clear(); + revealed.Clear(); + } + } + + public static void EnableSkill(CCSPlayerController player) + { + if (player == null || !player.IsValid) return; + + SkillPlayerInfo[player.Index] = new PlayerSkillInfo + { + CanUse = true, + Cooldown = DateTime.MinValue, + }; + } + + public static void PlayerDisconnect(uint playerIndex) + { + RemovePlayerWires(playerIndex); + + foreach (var key in revealed.Keys) + if (key.Owner == playerIndex || key.Target == playerIndex) + revealed.TryRemove(key, out _); + + SkillPlayerInfo.TryRemove(playerIndex, out _); + } + + public static void DisableSkill(CCSPlayerController player) + { + if (player == null) return; + ClearOwner(player.Index); + SkillUtils.ResetPrintHTML(player); + } + + public static void PlayerDeath(EventPlayerDeath @event) + { + var player = @event.Userid; + if (player == null || !player.IsValid) return; + + ClearOwner(player.Index); + SkillUtils.ResetPrintHTML(player); + } + + private static void ClearOwner(uint ownerIndex) + { + RemovePlayerWires(ownerIndex); + + foreach (var key in revealed.Keys) + if (key.Owner == ownerIndex) + revealed.TryRemove(key, out _); + } + + private static void RemovePlayerWires(uint ownerIndex) + { + foreach (var kvp in wires) + { + if (kvp.Value.OwnerIndex != ownerIndex) continue; + + DestroyWire(kvp.Key); + wires.TryRemove(kvp.Key, out _); + } + + SkillPlayerInfo.TryRemove(ownerIndex, out _); + } + + private static void DestroyWire(uint beamIndex) + { + var beam = Utilities.GetEntityFromIndex((int)beamIndex); + if (beam != null && beam.IsValid) + { + beam.Width = 0; + beam.EndWidth = 0; + beam.Render = Color.FromArgb(0, 0, 0, 0); + + Utilities.SetStateChanged(beam, "CBeam", "m_fWidth"); + Utilities.SetStateChanged(beam, "CBeam", "m_fEndWidth"); + Utilities.SetStateChanged(beam, "CBaseModelEntity", "m_clrRender"); + } + + EntityManager.DestroyEntity(beamIndex); + } + + public static void UseSkill(CCSPlayerController player) + { + if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; + + var playerEvent = PlayerManager.GetPlayerFromEvent(player); + if (playerEvent == null || !playerEvent.IsValid) return; + + if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo) || !skillInfo.CanUse) return; + + if ((DateTime.Now - skillInfo.LastAttempt).TotalSeconds < attemptCooldown) return; + skillInfo.LastAttempt = DateTime.Now; + + if (!TryPlaceWire(player)) + { + playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("tripwire_no_wall_info")); + return; + } + + skillInfo.CanUse = false; + skillInfo.Cooldown = DateTime.Now; + + playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("tripwire_placed_info")); + } + + private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo) + { + float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue(skillName, "cooldown")) - DateTime.Now).TotalSeconds); + float cooldown = Math.Max(time, 0); + + if (cooldown == 0 && !skillInfo.CanUse) + skillInfo.CanUse = true; + + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo == null) return; + + if (cooldown == 0) + playerInfo.PrintHTML = null; + else + playerInfo.PrintHTML = $"{player.GetTranslation("hud_info", $"{cooldown}")}"; + } + + 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; + + Color wireColor = player.Team == CsTeam.Terrorist ? terroristWire : counterTerroristWire; + + var beam = EntityManager.CreateTrackedBeam(player.Index, start, end, wireColor); + if (beam == null || !beam.IsValid) return false; + + beam.Width = SkillsInfo.GetValue(skillName, "wireWidth"); + + wires[beam.Index] = new WireInfo + { + OwnerIndex = player.Index, + BeamIndex = beam.Index, + Start = start, + End = end, + }; + + return true; + } + + public static void OnTick() + { + if (!SkillPlayerInfo.IsEmpty && Server.TickCount % 8 == 0) + { + foreach (var player in PlayerManager.GetTickPlayers()) + { + if (player == null || !player.IsValid) continue; + if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) continue; + + if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill == skillName) + UpdateHUD(player, skillInfo); + } + } + + if (revealed.IsEmpty && wires.IsEmpty) return; + + int tick = Server.TickCount; + + foreach (var kvp in revealed) + { + if (kvp.Value.ExpiryTick <= tick) + { + revealed.TryRemove(kvp.Key, out _); + continue; + } + + RevealOnRadar(kvp.Key.Target, kvp.Value.Slot); + } + + if (wires.IsEmpty || tick % 4 != 0) return; + + float radius = SkillsInfo.GetValue(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 PlayerSkillInfo + { + public bool CanUse { get; set; } + public DateTime Cooldown { get; set; } + public DateTime LastAttempt { get; set; } + } + + public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff3b3b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, float radarDuration = 5f, float triggerRadius = 24f, float wireHeight = 30f, float wireWidth = 1.5f, float maxWallDistance = 400f, float cooldown = 20f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) + { + public float RadarDuration { get; set; } = radarDuration; + public float TriggerRadius { get; set; } = triggerRadius; + public float WireHeight { get; set; } = wireHeight; + public float WireWidth { get; set; } = wireWidth; + public float MaxWallDistance { get; set; } = maxWallDistance; + public float Cooldown { get; set; } = cooldown; + } + } +} 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..a2aa998 100644 --- a/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs +++ b/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs @@ -42,7 +42,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); @@ -80,10 +80,11 @@ namespace src.player.skills if (Server.TickCount % 32 != 0) return; foreach (var player in PlayerManager.GetTickPlayers()) { - if (!SkillUtils.HasMenu(player)) continue; var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo == null || playerInfo.Skill != skillName) continue; + if (!SkillUtils.HasMenu(player)) continue; + var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid) @@ -200,4 +201,4 @@ namespace src.player.skills { } } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/utils/Config.cs b/jRandomSkills - SRC Files/src/utils/Config.cs index 9d1a873..6f7acc4 100644 --- a/jRandomSkills - SRC Files/src/utils/Config.cs +++ b/jRandomSkills - SRC Files/src/utils/Config.cs @@ -90,6 +90,7 @@ namespace src.utils public bool TraceRayBeam { get; set; } public string DisableHUDOnDeathPermission { get; set; } public bool DisableSkillsOnRoundEnd { get; set; } + public int? CurseSkillPerPlayer { get; set; } public LanguageSystem LanguageSystem { get; set; } public HtmlHudCustomisation HtmlHudCustomisation { get; set; } public ChatMessage ChatMessage { get; set; } @@ -119,6 +120,7 @@ namespace src.utils DisableSpectateHUD = false; DisableHUDOnDeathPermission = "@jRandomSkills/death"; DisableSkillsOnRoundEnd = false; + CurseSkillPerPlayer = null; LanguageSystem = new LanguageSystem { diff --git a/jRandomSkills - SRC Files/src/utils/EntityManager.cs b/jRandomSkills - SRC Files/src/utils/EntityManager.cs index 7a0cdb6..5675e6a 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()); @@ -117,44 +123,6 @@ namespace src.utils return CreateTrackedDynamicProp(playerIndex, "prop_dynamic_override"); } - public static CEnvShake? CreateTrackedEnvShake(uint playerIndex) - { - try - { - if (OverBudget()) return null; - var shake = Utilities.CreateEntityByName("env_shake"); - if (shake == null || !shake.IsValid) return null; - - shake.DispatchSpawn(); - RegisterEntity(shake.Index, playerIndex, "env_shake"); - return shake; - } - catch (Exception ex) - { - Server.PrintToConsole($"[EntityManager] CreateTrackedEnvShake: {ex.Message}"); - return null; - } - } - - public static CChicken? CreateTrackedChicken(uint playerIndex) - { - try - { - if (OverBudget()) return null; - var chicken = Utilities.CreateEntityByName("chicken"); - if (chicken == null || !chicken.IsValid) return null; - - chicken.DispatchSpawn(); - RegisterEntity(chicken.Index, playerIndex, "chicken"); - return chicken; - } - catch (Exception ex) - { - Server.PrintToConsole($"[EntityManager] CreateTrackedChicken: {ex.Message}"); - return null; - } - } - public static CPhysicsPropMultiplayer? CreateTrackedPhysicsProp(uint playerIndex) { try 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..35ef3a0 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,59 +98,11 @@ 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); } - public static bool UpdatePlayerSkill(uint playerIndex, Skills skill, Skills specialSkill = Skills.None) - { - if (playersByIndex.TryGetValue(playerIndex, out var playerInfo)) - { - playerInfo.Skill = skill; - playerInfo.SpecialSkill = specialSkill; - return true; - } - return false; - } - public static void Clear() { playersByIndex.Clear(); diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs index cfd55b1..783cd9c 100644 --- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs +++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs @@ -275,9 +275,18 @@ namespace src.utils return null; } + public static bool IsBulletDamage(CTakeDamageInfo? info) + { + var ability = info?.Ability?.Value; + if (ability == null || !ability.IsValid) return false; + + return FiresBullets(ability.DesignerName); + } + public static HitGroup_t GetHitGroup(CTakeDamageInfo? info) { if (info == null || info.Handle == nint.Zero) return HitGroup_t.HITGROUP_GENERIC; + if (!IsBulletDamage(info)) return HitGroup_t.HITGROUP_GENERIC; int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup"); if (offset <= 0) return HitGroup_t.HITGROUP_GENERIC; @@ -322,7 +331,181 @@ namespace src.utils return !ff && !tae; // same team + FF off -> engine will zero this damage } - public static bool TakeHealth(CCSPlayerPawn? pawn, int damage) + private static readonly ConcurrentDictionary pendingKillCredits = []; + + public static void RegisterKillCredit(uint victimIndex, uint attackerIndex, string? weapon = null) + { + pendingKillCredits[victimIndex] = (attackerIndex, weapon, Server.TickCount + 64); + } + + public static bool TryConsumeKillCredit(uint victimIndex, out uint attackerIndex, out string? weapon) + { + attackerIndex = 0; + weapon = null; + if (!pendingKillCredits.TryRemove(victimIndex, out var credit)) return false; + if (credit.ExpiryTick < Server.TickCount) return false; + + attackerIndex = credit.AttackerIndex; + weapon = credit.Weapon; + return true; + } + + public static void ClearKillCredits() + { + pendingKillCredits.Clear(); + } + + private static readonly HashSet bulletWeapons = new(StringComparer.Ordinal) + { + "deagle", "revolver", "glock", "usp_silencer", "cz75a", + "fiveseven", "p250", "tec9", "elite", "hkp2000", + "mp9", "mac10", "bizon", "mp7", "ump45", "p90", "mp5sd", + "famas", "galilar", "m4a1", "m4a1_silencer", "ak47", "aug", "sg553", + "ssg08", "awp", "scar20", "g3sg1", + "nova", "xm1014", "mag7", "sawedoff", + "m249", "negev" + }; + + public static bool FiresBullets(string? weapon) + { + if (string.IsNullOrEmpty(weapon)) return false; + + if (weapon.StartsWith("weapon_", StringComparison.Ordinal)) + weapon = weapon["weapon_".Length..]; + + return bulletWeapons.Contains(weapon); + } + + private static readonly HashSet curseSkills = + [ + Skills.Bankrupt, Skills.CarefulBullets, Skills.Darkness, Skills.Deactivator, + Skills.Deaf, Skills.ExpensiveAmmo, Skills.Giant, Skills.Glitch, + Skills.Jammer, Skills.JumpBan, Skills.JumpCurse, Skills.LifeSwap, + Skills.Magnifier, Skills.MoneySwap, Skills.Nightmare, Skills.Poison, + Skills.PrimaryBan, Skills.Thief, Skills.WildThrow + ]; + + private static readonly HashSet curseSkillNames = new(curseSkills.Select(s => s.ToString()), StringComparer.Ordinal); + + private static readonly Dictionary curseCounts = []; + private static readonly Dictionary curserToVictim = []; + private static readonly object curseLock = new(); + + private static readonly Config.GameModes[] sharedSkillModes = + [Config.GameModes.TeamSkills, Config.GameModes.SameSkills, Config.GameModes.Debug]; + + public static bool CurseLimitEnabled + { + get + { + if (Config.LoadedConfig.CurseSkillPerPlayer is not int limit || limit <= 0) return false; + return Array.IndexOf(sharedSkillModes, (Config.GameModes)Config.LoadedConfig.GameMode) < 0; + } + } + + public static bool IsCurseSkill(Skills skill) => curseSkills.Contains(skill); + + public static bool IsCurseSkill(string skill) => curseSkillNames.Contains(skill); + + public static void ClearCurses() + { + if (!CurseLimitEnabled) return; + + lock (curseLock) + { + curseCounts.Clear(); + curserToVictim.Clear(); + } + } + + public static bool CanCurse(uint victimIndex) + { + if (!CurseLimitEnabled) return true; + int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value; + + lock (curseLock) + return !curseCounts.TryGetValue(victimIndex, out int used) || used < limit; + } + + public static bool TryClaimCurse(uint curserIndex, uint victimIndex, bool force = false) + { + if (!CurseLimitEnabled) return true; + int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value; + + lock (curseLock) + { + ReleaseCurseLocked(curserIndex); + + curseCounts.TryGetValue(victimIndex, out int used); + if (!force && used >= limit) return false; + + curseCounts[victimIndex] = used + 1; + curserToVictim[curserIndex] = victimIndex; + return true; + } + } + + public static void ReleaseCurse(uint curserIndex) + { + if (!CurseLimitEnabled) return; + + lock (curseLock) ReleaseCurseLocked(curserIndex); + } + + public static void ClearCursesFor(uint playerIndex) + { + if (!CurseLimitEnabled) return; + + lock (curseLock) + { + ReleaseCurseLocked(playerIndex); + curseCounts.Remove(playerIndex); + + foreach (var curser in curserToVictim.Where(kvp => kvp.Value == playerIndex).Select(kvp => kvp.Key).ToList()) + curserToVictim.Remove(curser); + } + } + + private static void ReleaseCurseLocked(uint curserIndex) + { + if (!curserToVictim.Remove(curserIndex, out uint victimIndex)) return; + if (!curseCounts.TryGetValue(victimIndex, out int used)) return; + + if (used <= 1) curseCounts.Remove(victimIndex); + else curseCounts[victimIndex] = used - 1; + } + + public static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player, bool respectCurseLimit = false) + { + if (player == null || !player.IsValid) return []; + + var enemies = GetAliveEnemies(player); + if (!respectCurseLimit || !CurseLimitEnabled || enemies.Length == 0) return enemies; + + var withCapacity = enemies.Where(p => CanCurse(p.Index)).ToArray(); + return withCapacity.Length > 0 ? withCapacity : enemies; + } + + public static bool AnyCurseCapacity(CCSPlayerController player) + { + if (!CurseLimitEnabled) return true; + if (player == null || !player.IsValid) return true; + + return GetAliveEnemies(player).Any(p => CanCurse(p.Index)); + } + + private static CCSPlayerController[] GetAliveEnemies(CCSPlayerController player) + { + return [.. PlayerManager.GetTickPlayers() + .Where(p => p != null && p.IsValid) + .Select(PlayerManager.GetPlayerEvent) + .Where(p => p != null && p.IsValid && p.Team != player.Team + && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 + && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None) + .Cast()]; + } + + public static bool TakeHealth(CCSPlayerPawn? pawn, int damage, CCSPlayerController? damageAttacker = null, string? damageWeapon = null) { if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return false; @@ -363,6 +546,10 @@ namespace src.utils if (pawn.Health <= 0) { + if (damageAttacker != null && damageAttacker.IsValid && victim != null && victim.IsValid + && damageAttacker.Index != victim.Index) + RegisterKillCredit(victim.Index, damageAttacker.Index, damageWeapon); + Server.NextFrame(() => { if (pawn == null || !pawn.IsValid) return; @@ -374,6 +561,29 @@ namespace src.utils return true; } + public static void HideCarriedEntities(CCheckTransmitInfo info, CCSPlayerPawn? pawn) + { + if (pawn == null || !pawn.IsValid) return; + + var weaponServices = pawn.WeaponServices; + if (weaponServices == null) return; + + var activeWeapon = weaponServices.ActiveWeapon?.Value; + if (activeWeapon != null && activeWeapon.IsValid && info.TransmitEntities.Contains(activeWeapon.Index)) + info.TransmitEntities.Remove(activeWeapon.Index); + + if (weaponServices.MyWeapons == null) return; + + foreach (var handle in weaponServices.MyWeapons) + { + var weapon = handle?.Value; + if (weapon == null || !weapon.IsValid) continue; + + if (info.TransmitEntities.Contains(weapon.Index)) + info.TransmitEntities.Remove(weapon.Index); + } + } + public static void ResetPrintHTML(CCSPlayerController? player) { if (player == null || !player.IsValid) return; diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll index ba4d9ab..9fb186b 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/config.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json index 03dbe24..22fa6b8 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json @@ -20,6 +20,7 @@ "TraceRayBeam": false, "DisableHUDOnDeathPermission": "@jRandomSkills/death", "DisableSkillsOnRoundEnd": false, + "CurseSkillPerPlayer": null, "LanguageSystem": { "DefaultLangCode": "en", "DisableGeoLite": false, @@ -208,4 +209,4 @@ "Permissions": "@jRandomSkills/owner" } } -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json index 5018c99..b0f1c45 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, + "Cooldown": 20.0, + "NeedsTeammates": false, + "DisableOnFreezeTime": false, + "OnlyTeam": 0, + "Color": "#ff3b3b", + "Active": true, + "Name": "Tripwire", + "HudDuration": null, + "DescriptionHudDuration": null, + "RequiredPermission": "", + "MaxPerServer": -1, + "Rarity": "Rare" + }, { "SmokeDamage": 2, "SmokeRadius": 180.0, @@ -187,6 +206,7 @@ }, { "Distance": 100.0, + "UseCooldown": 0.5, "NeedsTeammates": false, "DisableOnFreezeTime": false, "OnlyTeam": 0, @@ -371,6 +391,10 @@ }, { "ZombieHealth": 500, + "R": 255, + "G": 0, + "B": 0, + "A": 60, "NeedsTeammates": false, "DisableOnFreezeTime": false, "OnlyTeam": 0, @@ -756,6 +780,23 @@ "MaxPerServer": -1, "Rarity": "Common" }, + { + "PostProcessing": "lighting/postprocessing/effects/death_cam_phase1_low_violence.vpost", + "FadeTime": 0.25, + "MinExposure": 0.5, + "MaxExposure": 2.0, + "NeedsTeammates": false, + "DisableOnFreezeTime": false, + "OnlyTeam": 0, + "Color": "#5b2c6f", + "Active": true, + "Name": "Nightmare", + "HudDuration": null, + "DescriptionHudDuration": null, + "RequiredPermission": "", + "MaxPerServer": -1, + "Rarity": "Rare" + }, { "NeedsTeammates": false, "DisableOnFreezeTime": false, @@ -887,6 +928,21 @@ "MaxPerServer": -1, "Rarity": "Common" }, + { + "KnockbackUnits": 120.0, + "MaxSpeed": 1200.0, + "NeedsTeammates": false, + "DisableOnFreezeTime": false, + "OnlyTeam": 0, + "Color": "#ff8c42", + "Active": true, + "Name": "Knockback", + "HudDuration": null, + "DescriptionHudDuration": null, + "RequiredPermission": "", + "MaxPerServer": -1, + "Rarity": "Common" + }, { "FlashDuration": 1.0, "FriendlyFire": true, @@ -917,6 +973,20 @@ "MaxPerServer": -1, "Rarity": "Common" }, + { + "JumpVelocity": 301.0, + "NeedsTeammates": false, + "DisableOnFreezeTime": false, + "OnlyTeam": 0, + "Color": "#7ad1c4", + "Active": true, + "Name": "JumpCurse", + "HudDuration": null, + "DescriptionHudDuration": null, + "RequiredPermission": "", + "MaxPerServer": -1, + "Rarity": "Common" + }, { "NeedsTeammates": false, "DisableOnFreezeTime": false, @@ -1094,6 +1164,7 @@ }, { "HealthToAdd": 100, + "EffectDuration": 1.0, "NeedsTeammates": false, "DisableOnFreezeTime": false, "OnlyTeam": 0, @@ -1191,6 +1262,21 @@ "MaxPerServer": -1, "Rarity": "Common" }, + { + "MinScale": 1.1, + "MaxScale": 1.4, + "NeedsTeammates": false, + "DisableOnFreezeTime": false, + "OnlyTeam": 0, + "Color": "#8ad3ff", + "Active": true, + "Name": "Giant", + "HudDuration": null, + "DescriptionHudDuration": null, + "RequiredPermission": "", + "MaxPerServer": -1, + "Rarity": "Common" + }, { "NeedsTeammates": false, "DisableOnFreezeTime": false, @@ -1337,6 +1423,38 @@ "MaxPerServer": -1, "Rarity": "Common" }, + { + "Cooldown": 20.0, + "ExplosionRadius": 210.0, + "ExplosionDamage": 50, + "PropModel": "models/props/de_train/hr_t/barrel_a/barrel_a.vmdl", + "DmgReductionForTeamates": 0.5, + "NeedsTeammates": false, + "DisableOnFreezeTime": true, + "OnlyTeam": 0, + "Color": "#c0392b", + "Active": true, + "Name": "ExplodingBarrel", + "HudDuration": null, + "DescriptionHudDuration": null, + "RequiredPermission": "", + "MaxPerServer": 2, + "Rarity": "Common" + }, + { + "MoneyPerShot": 50, + "NeedsTeammates": false, + "DisableOnFreezeTime": false, + "OnlyTeam": 0, + "Color": "#e0c341", + "Active": true, + "Name": "ExpensiveAmmo", + "HudDuration": null, + "DescriptionHudDuration": null, + "RequiredPermission": "", + "MaxPerServer": -1, + "Rarity": "Common" + }, { "Cooldown": 15.0, "CooldownBeforeUse": 10.0, @@ -1591,7 +1709,7 @@ "HudDuration": null, "DescriptionHudDuration": null, "RequiredPermission": "", - "MaxPerServer": -1, + "MaxPerServer": 1, "Rarity": "Uncommon" }, { diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll index fc47e69..51e4268 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..bebaa5d 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,12 @@ "toxicsmoke": "Giftiger Rauch", "toxicsmoke_desc": "Deine Rauchgranaten verursachen Schaden", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Du kannst Feinde durch Wände hindurch sehen", @@ -491,6 +532,7 @@ "duplicate_player": "Mehr als ein Spieler mit demselben Namen gefunden.", "selectplayerskill_command": "Gib /t ein", "selectplayerskill_incorrect_enemy_index": "Es wurden keine Spieler gefunden, die ausgewählt werden können.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Keine solche CHATCOLORS.REDskill gefunden", "player_not_found_setskill": "Kein solcher CHATCOLORS.REDplayer gefunden", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S – Scrollen", "menu_controlls_padding": " | ", "menu_controlls_select": "E – Auswählen" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json index 54d6f6c..172540e 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,12 @@ "toxicsmoke": "Toxic Smoke", "toxicsmoke_desc": "Your smoke grenades deal damage", + "tripwire": "Tripwire", + "tripwire_desc": "Click [css_useSkill] to string a wire between two walls. Enemies touching it show on your radar", + "tripwire_no_wall_info": "No walls close enough on both sides.", + "tripwire_placed_info": "Tripwire placed.", + "tripwire_triggered_info": "'{0}' tripped your wire.", + "wallhack": "Wallhack", "wallhack_desc": "You can see enemies through walls", @@ -491,6 +532,7 @@ "duplicate_player": "More than one player found with the same name.", "selectplayerskill_command": "Type /t", "selectplayerskill_incorrect_enemy_index": "No players were found to select.", + "curse_limit_info": "The curse limit for {0} has been reached", "skill_not_found_setskill": "No such CHATCOLORS.REDskill found", "player_not_found_setskill": "No such CHATCOLORS.REDplayer found", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S - Scroll", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Select" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json index e916741..19a008a 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,12 @@ "toxicsmoke": "Fumée Toxique", "toxicsmoke_desc": "Vos grenades fumigènes infligent des dégâts", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Vous pouvez voir les ennemis à travers les murs", @@ -491,6 +531,7 @@ "duplicate_player": "Plus d’un joueur trouvé avec le même nom.", "selectplayerskill_command": "Tapez /t", "selectplayerskill_incorrect_enemy_index": "Aucun joueur trouvé à sélectionner.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Aucune CHATCOLORS.REDcompétence trouvée", "player_not_found_setskill": "Aucun CHATCOLORS.REDjoueur trouvé", @@ -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..c8958da 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,12 @@ "toxicsmoke": "Toksyczny Dym", "toxicsmoke_desc": "Twoje granaty dymny zadają obrażenia", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Widzisz wrogów przez ściany", @@ -491,6 +531,7 @@ "duplicate_player": "Znaleziono więcej niż jednego gracza o tej samej nazwie.", "selectplayerskill_command": "Wpisz /t", "selectplayerskill_incorrect_enemy_index": "Nie znaleziono graczy do wyboru.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nie znaleziono takiej CHATCOLORS.REDsupermocy", "player_not_found_setskill": "Nie znaleziono takiego CHATCOLORS.REDgracza", @@ -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..907e797 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,12 @@ "toxicsmoke": "Smoke Tóxica", "toxicsmoke_desc": "Suas smokes causam dano", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Wallhack", "wallhack_desc": "Você pode ver inimigos através das paredes", @@ -491,6 +532,7 @@ "duplicate_player": "Mais de um jogador encontrado com o mesmo nome.", "selectplayerskill_command": "Digite /t", "selectplayerskill_incorrect_enemy_index": "Não foram encontrados jogadores para selecionar.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nenhuma habilidade CHATCOLORS.RED encontrada", "player_not_found_setskill": "Nenhum jogador CHATCOLORS.RED encontrado", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S - Rolar", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Selecionar" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json index dd0196c..f77d7cc 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,12 @@ "toxicsmoke": "Токсичный дым", "toxicsmoke_desc": "Ваши дымовые гранаты наносят урон", + "tripwire": "tripwire", + "tripwire_desc": "tripwire_desc", + "tripwire_no_wall_info": "tripwire_no_wall_info", + "tripwire_placed_info": "tripwire_placed_info", + "tripwire_triggered_info": "tripwire_triggered_info: {0}", + "wallhack": "Воллхак", "wallhack_desc": "Вы видите врагов сквозь стены", @@ -491,6 +532,7 @@ "duplicate_player": "Найдено несколько игроков с таким именем.", "selectplayerskill_command": "Введите /t", "selectplayerskill_incorrect_enemy_index": "Игроки для выбора не найдены.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Навык не найден", "player_not_found_setskill": "Игрок не найден", @@ -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..18a1ceb 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", @@ -190,7 +208,7 @@ "healingsmoke_desc": "Sis bombaların iyileştirir", "hermit": "Münzevi", - "hermit_desc": "Öldüğünde mernin ve canının biraz yenilenir", + "hermit_desc": "Öldürdüğünde mermi ve canın yenilenir", "holyhandgrenade": "Kutsal El Bombası", "holyhandgrenade_desc": "El bomban iki kat hasara ve menzile sahip", @@ -241,12 +259,22 @@ "jumpban_player_info": "'{0}' artık zıplayamaz.", "jumpban_select_info": "Zıplaması yasaklanacak oyuncu:", + "jumpcurse": "Zıplama Laneti", + "jumpcurse_desc": "Seçtiğin rakip, takım arkadaşları her zıpladığında zıplar", + "jumpcurse_disable_info": "Bacakların yeniden senin.", + "jumpcurse_enemy_info": "Artık takım arkadaşların zıpladıkça sen de zıplıyorsun.", + "jumpcurse_player_info": "'{0}' artık takımıyla birlikte zıplıyor.", + "jumpcurse_select_info": "Zıplama laneti verilecek oyuncu:", + "jumpingjack": "Zıpzıp Jack", "jumpingjack_desc": "Zıplamak canını yeniler", "killerflash": "Ölümcül Flaş", "killerflash_desc": "Flaşına tamamen kör olan herkes ölür (sen dahil)", + "knockback": "Savurma", + "knockback_desc": "Havadayken ateş etmek seni geriye savurur", + "lifeswap": "Can Takası", "lifeswap_desc": "Can değerlerini takas etmek için bir oyuncu seç", "lifeswap_enemy_info": "Birisi canını çaldı", @@ -288,6 +316,13 @@ "deathbomb_desc": "Öldüğünde patlayarak etraftaki oyuncuları öldürürsün", "deathbomb_explosion": "BOOM!!!", + "nightmare": "Kabus", + "nightmare_desc": "Seçtiğin bir rakibe kabus görüntüsü verir", + "nightmare_disable_info": "Kabus sona erdi.", + "nightmare_enemy_info": "Gözlerini bir kabus bürüdü.", + "nightmare_player_info": "'{0}' oyuncusuna kabus gördürdün.", + "nightmare_select_info": "Kabus görecek oyuncu:", + "ninja": "Ninja", "ninja_desc": "Görünmezliğini sabit durmak %33, eğilmek %33 ve bıçak tutmak %33 artırır", @@ -408,7 +443,7 @@ "silent_desc": "Ayak seslerini ve zıplamalarını DİĞER oyuncular duyamaz", "smoker": "Tiryaki", - "smoker_desc": "Sis bombalarınız asla bitmez", + "smoker_desc": "Attığın Sis asla bitmez", "sniperelite": "Keskin Nişancı", "sniperelite_customname": "Keskin Nişancı", @@ -450,6 +485,12 @@ "toxicsmoke": "Zehirli Sis", "toxicsmoke_desc": "Sis bombaların hasar verir", + "tripwire": "Tel Tuzağı", + "tripwire_desc": "[css_useSkill] ile iki duvar arasına tel ger. Tele değen düşman radarında görünür", + "tripwire_no_wall_info": "İki yanında da yeterince yakın duvar yok.", + "tripwire_placed_info": "Tel gerildi.", + "tripwire_triggered_info": "'{0}' teline takıldı.", + "wallhack": "Wallhack", "wallhack_desc": "Düşmanları duvarların arkasından görebilirsin", @@ -491,6 +532,7 @@ "duplicate_player": "Aynı isimde birden fazla oyuncu bulundu.", "selectplayerskill_command": "Sohbete /t yazın", "selectplayerskill_incorrect_enemy_index": "Seçilecek oyuncu yok.", + "curse_limit_info": "'{0}' üzerindeki lanet sınırı doldu, başka bir oyuncu seç", "skill_not_found_setskill": "Böyle bir yetenek yok", "player_not_found_setskill": "Böyle bir oyuncu yok", @@ -529,4 +571,4 @@ "menu_controlls_scroll": "W/S - Kaydır", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Seç" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json index c922535..7564a60 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json and b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json differ