setskill: @me/@all/@ct/@t/@random/@randomct/@randomt targets, respect team-only skills

css_setskill takes CSS's target selectors plus @random, @randomct and @randomt
(one random alive player the skill is allowed for). Players on the wrong team
for a team-only skill (e.g. ChillOut) are skipped and keep their skill.
Bump ModuleVersion to 1.2.4.b3-panorama3.
This commit is contained in:
Astra 2026-09-29 23:30:53 +01:00
parent 8a514255c1
commit b16e6fb213
3 changed files with 92 additions and 44 deletions

View file

@ -2,6 +2,7 @@ using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands; using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.menu; using src.menu;
@ -138,29 +139,16 @@ namespace src.command
{ {
Debug.WriteToDebug($"Player {player?.PlayerName} used the css_setskill {command.ArgString} command."); Debug.WriteToDebug($"Player {player?.PlayerName} used the css_setskill {command.ArgString} command.");
if (!string.IsNullOrEmpty(config.NormalCommands.SetSkillCommand.Permissions) && !AdminManager.PlayerHasPermissions(player, config.NormalCommands.SetSkillCommand.Permissions)) return; if (!string.IsNullOrEmpty(config.NormalCommands.SetSkillCommand.Permissions) && !AdminManager.PlayerHasPermissions(player, config.NormalCommands.SetSkillCommand.Permissions)) return;
var targetPlayer = Utilities.GetPlayers().FirstOrDefault(p => p != null && p.IsValid
&& (p.SteamID.ToString().Equals(command.GetArg(1), StringComparison.CurrentCultureIgnoreCase) void Reply(string key)
|| p.PlayerName.Equals(command.GetArg(1), StringComparison.OrdinalIgnoreCase))); {
if (player == null) Server.PrintToConsole(Localization.GetTranslationWithoutIlliterate(key));
else SkillUtils.PrintToChat(player, player.GetTranslationWithoutIlliterate(key));
}
if (command.ArgCount < 2) if (command.ArgCount < 2)
{ {
if (player == null) Reply("correct_form_setskill");
{
Server.PrintToConsole(Localization.GetTranslationWithoutIlliterate("correct_form_setskill"));
return;
}
SkillUtils.PrintToChat(player, player.GetTranslationWithoutIlliterate("correct_form_setskill"));
return;
}
if (targetPlayer == null)
{
if (player == null)
{
Server.PrintToConsole(Localization.GetTranslationWithoutIlliterate("player_not_found_setskill"));
return;
}
SkillUtils.PrintToChat(player, player.GetTranslationWithoutIlliterate("player_not_found_setskill"));
return; return;
} }
@ -169,45 +157,105 @@ namespace src.command
if (skill == null) if (skill == null)
{ {
if (player == null) Reply("skill_not_found_setskill");
{
Server.PrintToConsole(Localization.GetTranslationWithoutIlliterate("skill_not_found_setskill"));
return;
}
SkillUtils.PrintToChat(player, player.GetTranslationWithoutIlliterate("skill_not_found_setskill"));
return; return;
} }
var skillPlayer = PlayerManager.GetPlayerByIndex(targetPlayer.Index); // zio: team-only skills (e.g. ChillOut for CTs) are never given to the other team; those players keep their skill.
if (skillPlayer != null) int onlyTeam = SkillsInfo.GetValue<int>(skill.Skill, "onlyTeam");
bool TeamAllowed(CCSPlayerController p) => onlyTeam == 0 || (int)p.Team == onlyTeam;
var targets = GetSetSkillTargets(player, command.GetArg(1), TeamAllowed);
if (targets.Count == 0)
{ {
Reply("player_not_found_setskill");
return;
}
var done = new List<CCSPlayerController>();
var wrongTeam = new List<CCSPlayerController>();
foreach (var targetPlayer in targets)
{
if (!TeamAllowed(targetPlayer))
{
wrongTeam.Add(targetPlayer);
continue;
}
var skillPlayer = PlayerManager.GetPlayerByIndex(targetPlayer.Index);
if (skillPlayer == null) continue;
Instance.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [targetPlayer]); Instance.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [targetPlayer]);
skillPlayer.Skill = skill.Skill; skillPlayer.Skill = skill.Skill;
skillPlayer.SpecialSkill = Skills.None; skillPlayer.SpecialSkill = Skills.None;
Instance.SkillAction(skill.Skill.ToString(), "EnableSkill", [targetPlayer]); Instance.SkillAction(skill.Skill.ToString(), "EnableSkill", [targetPlayer]);
Event.UpdateSkillHudExpired(skillPlayer, skill.Skill); Event.UpdateSkillHudExpired(skillPlayer, skill.Skill);
done.Add(targetPlayer);
if (player == null)
{
Server.PrintToConsole(Localization.GetTranslationWithoutIlliterate("done_setskill"));
return;
} }
SkillUtils.PrintToChat(player, $"{player.GetTranslationWithoutIlliterate("done_setskill")}: {ChatColors.LightRed}{player.GetSkillName(skill.Skill)} {ChatColors.Lime}{player.GetTranslationWithoutIlliterate("for_setskill")} {ChatColors.LightRed}\u202A{targetPlayer.PlayerName}\u202C"); string teamName = onlyTeam == (int)CsTeam.Terrorist ? "T" : "CT";
if (skill.Display)
SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(skill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(skill.Skill)}", border: "b");
}
else
{
if (player == null) if (player == null)
{ {
if (done.Count > 0)
Server.PrintToConsole($"{Localization.GetTranslationWithoutIlliterate("done_setskill")}: {skill.Skill} -> {string.Join(", ", done.Select(p => p.PlayerName))}");
if (wrongTeam.Count > 0)
Server.PrintToConsole($"{skill.Skill} is {teamName}-only, skipped: {string.Join(", ", wrongTeam.Select(p => p.PlayerName))}");
if (done.Count == 0 && wrongTeam.Count == 0)
Server.PrintToConsole(Localization.GetTranslationWithoutIlliterate("error_setskill")); Server.PrintToConsole(Localization.GetTranslationWithoutIlliterate("error_setskill"));
return; return;
} }
SkillUtils.PrintToChat(player, player.GetTranslationWithoutIlliterate("error_setskill")); if (done.Count > 0)
{
string who = done.Count == 1 ? done[0].PlayerName : $"{done.Count} players";
SkillUtils.PrintToChat(player, $"{player.GetTranslationWithoutIlliterate("done_setskill")}: {ChatColors.LightRed}{player.GetSkillName(skill.Skill)} {ChatColors.Lime}{player.GetTranslationWithoutIlliterate("for_setskill")} {ChatColors.LightRed}\u202A{who}\u202C");
if (skill.Display)
SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(skill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(skill.Skill)}", border: "b");
} }
if (wrongTeam.Count > 0)
{
string who = wrongTeam.Count == 1 ? wrongTeam[0].PlayerName : $"{wrongTeam.Count} players";
SkillUtils.PrintToChat(player, $"{ChatColors.LightRed}{player.GetSkillName(skill.Skill)}{ChatColors.Default} is {teamName}-only, not given to {ChatColors.LightRed}\u202A{who}\u202C");
}
if (done.Count == 0 && wrongTeam.Count == 0)
Reply("error_setskill");
}
// zio: css_setskill targets. An exact name or SteamID64 as before, CSS's own selectors
// (@me, @all, @ct, @t, @alive, #userid, ...), or @random/@randomct/@randomt: one random
// alive player, drawn only from players the skill's team restriction allows.
private static List<CCSPlayerController> GetSetSkillTargets(CCSPlayerController? caller, string arg, Func<CCSPlayerController, bool> teamAllowed)
{
var players = Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV).ToList();
CsTeam? randomTeam = arg.ToLowerInvariant() switch
{
"@random" => CsTeam.None,
"@randomct" => CsTeam.CounterTerrorist,
"@randomt" => CsTeam.Terrorist,
_ => null
};
if (randomTeam != null)
{
var pool = players.Where(p => (p.Team == CsTeam.Terrorist || p.Team == CsTeam.CounterTerrorist)
&& (randomTeam == CsTeam.None || p.Team == randomTeam)
&& p.PawnIsAlive && teamAllowed(p)).ToList();
return pool.Count == 0 ? [] : [pool[Random.Shared.Next(pool.Count)]];
}
var exact = players.FirstOrDefault(p => p.SteamID.ToString().Equals(arg, StringComparison.CurrentCultureIgnoreCase)
|| p.PlayerName.Equals(arg, StringComparison.OrdinalIgnoreCase));
if (exact != null) return [exact];
var matched = new Target(arg).GetTarget(caller).Players.Where(p => p != null && p.IsValid && !p.IsHLTV).ToList();
if (arg.StartsWith('@'))
return [.. matched.Where(p => p.Team == CsTeam.Terrorist || p.Team == CsTeam.CounterTerrorist)];
// A partial name only counts when it matches one player, so a typo can't hit several.
return matched.Count == 1 ? matched : [];
} }
[CommandHelper(minArgs: 0, whoCanExecute: CommandUsage.CLIENT_ONLY)] [CommandHelper(minArgs: 0, whoCanExecute: CommandUsage.CLIENT_ONLY)]

View file

@ -44,7 +44,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]"; public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)"; public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus."; public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.2.4.b3-panorama2"; public override string ModuleVersion => "1.2.4.b3-panorama3";
public override void Load(bool hotReload) public override void Load(bool hotReload)
{ {

View file

@ -633,7 +633,7 @@
"skill_not_found_setskill": "No such CHATCOLORS.REDskill found", "skill_not_found_setskill": "No such CHATCOLORS.REDskill found",
"player_not_found_setskill": "No such CHATCOLORS.REDplayer found", "player_not_found_setskill": "No such CHATCOLORS.REDplayer found",
"correct_form_setskill": "Correct usage: CHATCOLORS.RED!setskill <nickname> <skill>", "correct_form_setskill": "Correct usage: CHATCOLORS.RED!setskill <nickname/@me/@all/@ct/@t/@random/@randomct/@randomt> <skill>",
"correct_form_setscore": "Correct usage: CHATCOLORS.RED!setscore <CT> <TT>", "correct_form_setscore": "Correct usage: CHATCOLORS.RED!setscore <CT> <TT>",
"done_setskill": "skill set", "done_setskill": "skill set",
"error_setskill": "Failed to set CHATCOLORS.REDskill", "error_setskill": "Failed to set CHATCOLORS.REDskill",