1.0.4c-zio1: CSS 1.0.375 / net10.0, fixes, SimpleAdmin ranks
- Target net10.0 and CounterStrikeSharp 1.0.375 (compile-only reference), System.Text.Json instead of Newtonsoft.Json. - Config moves to configs/plugins/CS2-Tags/tags.json, defaulting to our #rank/... SimpleAdmin groups. Comments and trailing commas allowed; a bad file is logged and the previous tags kept. - Scoreboard tags call SetStateChanged on m_szClan, only when changed. - Colour tags are expanded only in config strings, not in players' names or messages. Empty nick_color means team colour. - A tag with no prefix or colours leaves chat to the game; drop team_chat. - css_tags_reload works for @css/root too and reapplies scoreboard tags; hot reload tags connected players. Timers stop on map change. No dead icon for spectators. - Remove css_tag_mute/unmute and "@" team admin chat: CS2-SimpleAdmin does both, and its catch-all command listener runs before these. - Add release.sh, drop the .sln and GitHub workflow, README for the fork.
This commit is contained in:
parent
0d5c8ec061
commit
cd90d1bf82
7 changed files with 361 additions and 553 deletions
67
.github/workflows/build.yml
vendored
67
.github/workflows/build.yml
vendored
|
|
@ -1,67 +0,0 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
PROJECT_PATH: "CS2-Tags.csproj"
|
||||
PROJECT_NAME: "CS2-Tags"
|
||||
OUTPUT_PATH: "./CS2-Tags"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build ${{ env.PROJECT_PATH }} -c WeaponPaints -o ${{ env.OUTPUT_PATH }}
|
||||
|
||||
publish:
|
||||
if: github.event_name == 'push'
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build ${{ env.PROJECT_PATH }} -c WeaponPaints -o ${{ env.OUTPUT_PATH }}
|
||||
- name: Clean files
|
||||
run: |
|
||||
rm -f \
|
||||
${{ env.OUTPUT_PATH }}/CounterStrikeSharp.API.dll \
|
||||
${{ env.OUTPUT_PATH }}/McMaster.NETCore.Plugins.dll \
|
||||
${{ env.OUTPUT_PATH }}/Microsoft.DotNet.PlatformAbstractions.dll \
|
||||
${{ env.OUTPUT_PATH }}/Microsoft.Extensions.DependencyModel.dll \
|
||||
- name: Zip
|
||||
uses: thedoctor0/zip-release@0.7.5
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ env.PROJECT_NAME }}.zip'
|
||||
path: ${{ env.OUTPUT_PATH }}
|
||||
- name: CS2-Tags
|
||||
uses: ncipollo/release-action@v1.12.0
|
||||
with:
|
||||
artifacts: "${{ env.PROJECT_NAME }}.zip"
|
||||
name: "Build ${{ env.BUILD_NUMBER }}"
|
||||
tag: "build-${{ env.BUILD_NUMBER }}"
|
||||
body: |
|
||||
Place the plugin in game/csgo/addons/counterstrikesharp/plugins/CS2-Tags
|
||||
After first server start, tags.json be created
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
.git
|
||||
.vs
|
||||
bin/
|
||||
obj/
|
||||
bin/
|
||||
release-out/
|
||||
|
|
|
|||
571
CS2-Tags.cs
571
CS2-Tags.cs
|
|
@ -1,497 +1,278 @@
|
|||
using CounterStrikeSharp.API;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Timers;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CS2_Tags;
|
||||
|
||||
[MinimumApiVersion(159)]
|
||||
public class Tag
|
||||
{
|
||||
[JsonPropertyName("prefix")] public string Prefix { get; set; } = "";
|
||||
[JsonPropertyName("nick_color")] public string NickColor { get; set; } = "";
|
||||
[JsonPropertyName("message_color")] public string MessageColor { get; set; } = "";
|
||||
[JsonPropertyName("scoreboard")] public string Scoreboard { get; set; } = "";
|
||||
|
||||
// A tag with nothing to show in chat leaves the player's messages to the game.
|
||||
[JsonIgnore]
|
||||
public bool AffectsChat => Prefix != "" || NickColor != "" || MessageColor != "";
|
||||
}
|
||||
|
||||
[MinimumApiVersion(375)]
|
||||
public class CS2_Tags : BasePlugin
|
||||
{
|
||||
private HashSet<string> GaggedIds = new HashSet<string>();
|
||||
public static JObject? JsonTags { get; private set; }
|
||||
public override string ModuleName => "CS2-Tags";
|
||||
public override string ModuleDescription => "Add player tags easily in cs2 game";
|
||||
public override string ModuleAuthor => "daffyy";
|
||||
public override string ModuleVersion => "1.0.4c";
|
||||
public override string ModuleVersion => "1.0.4c-zio1";
|
||||
|
||||
private static string TagsPath => Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp",
|
||||
"configs", "plugins", "CS2-Tags", "tags.json");
|
||||
|
||||
// Checked in file order after the SteamID64 entries, first match wins, so list higher ranks first.
|
||||
private List<KeyValuePair<string, Tag>> _tags = new();
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
CreateOrLoadJsonFile(ModuleDirectory + "/tags.json");
|
||||
LoadTags();
|
||||
|
||||
RegisterListener<Listeners.OnMapStart>(OnMapStart);
|
||||
RegisterListener<Listeners.OnClientAuthorized>(OnClientAuthorized);
|
||||
RegisterListener<Listeners.OnClientDisconnect>(OnClientDisconnect);
|
||||
RegisterEventHandler<EventPlayerConnectFull>(OnPlayerConnectFull);
|
||||
RegisterEventHandler<EventPlayerSpawn>(OnPlayerSpawn);
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
AddCommandListener("say", OnPlayerChat);
|
||||
AddCommandListener("say_team", OnPlayerChatTeam);
|
||||
|
||||
if (hotReload)
|
||||
ApplyAllClanTags();
|
||||
}
|
||||
|
||||
private void OnMapStart(string mapName)
|
||||
private void LoadTags()
|
||||
{
|
||||
GaggedIds.Clear();
|
||||
}
|
||||
|
||||
private static void CreateOrLoadJsonFile(string filepath)
|
||||
{
|
||||
if (!File.Exists(filepath))
|
||||
try
|
||||
{
|
||||
var templateData = new JObject
|
||||
if (!File.Exists(TagsPath))
|
||||
{
|
||||
["tags"] = new JObject
|
||||
{
|
||||
["#css/admin"] = new JObject
|
||||
{
|
||||
["prefix"] = "{RED}[ADMIN]",
|
||||
["nick_color"] = "{RED}",
|
||||
["message_color"] = "{GOLD}",
|
||||
["scoreboard"] = "[ADMIN]"
|
||||
},
|
||||
["@css/chat"] = new JObject
|
||||
{
|
||||
["prefix"] = "{GREEN}[CHAT]",
|
||||
["nick_color"] = "{RED}",
|
||||
["message_color"] = "{GOLD}",
|
||||
["scoreboard"] = "[CHAT]"
|
||||
},
|
||||
["76561197961430531"] = new JObject
|
||||
{
|
||||
["prefix"] = "{RED}[ADMIN]",
|
||||
["nick_color"] = "{RED}",
|
||||
["message_color"] = "{GOLD}",
|
||||
["scoreboard"] = "[ADMIN]"
|
||||
},
|
||||
["everyone"] = new JObject
|
||||
{
|
||||
["team_chat"] = false,
|
||||
["prefix"] = "{Grey}[Player]",
|
||||
["nick_color"] = "",
|
||||
["message_color"] = "",
|
||||
["scoreboard"] = "[Player]"
|
||||
},
|
||||
}
|
||||
};
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(TagsPath)!);
|
||||
File.WriteAllText(TagsPath, DefaultTagsJson);
|
||||
}
|
||||
|
||||
File.WriteAllText(filepath, templateData.ToString());
|
||||
var jsonData = File.ReadAllText(filepath);
|
||||
JsonTags = JObject.Parse(jsonData);
|
||||
var root = JsonNode.Parse(File.ReadAllText(TagsPath),
|
||||
documentOptions: new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true });
|
||||
var tags = new List<KeyValuePair<string, Tag>>();
|
||||
if (root?["tags"] is JsonObject tagsObject)
|
||||
{
|
||||
foreach (var (key, value) in tagsObject)
|
||||
{
|
||||
var tag = value?.Deserialize<Tag>();
|
||||
if (tag != null)
|
||||
tags.Add(new(key, tag));
|
||||
}
|
||||
}
|
||||
|
||||
_tags = tags;
|
||||
Logger.LogInformation("Loaded {Count} tags from {Path}", _tags.Count, TagsPath);
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
var jsonData = File.ReadAllText(filepath);
|
||||
JsonTags = JObject.Parse(jsonData);
|
||||
// Keep the previous tags, so a typo in the file doesn't strip everyone's tags.
|
||||
Logger.LogError(ex, "Failed to load {Path}", TagsPath);
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_tags_reload")]
|
||||
[ConsoleCommand("css_tags_reload", "Reload tags.json and reapply scoreboard tags")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/root")]
|
||||
public void OnReloadConfig(CCSPlayerController? player, CommandInfo info)
|
||||
{
|
||||
if (player != null) return;
|
||||
CreateOrLoadJsonFile(ModuleDirectory + "/tags.json");
|
||||
|
||||
Server.PrintToConsole("[CS2-Tags] Config reloaded!");
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_tag_mute")]
|
||||
[CommandHelper(minArgs: 1, usage: "<SteamID>", whoCanExecute: CommandUsage.SERVER_ONLY)]
|
||||
public void OnTagMuteCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
string? steamid = command.GetArg(1);
|
||||
|
||||
if (steamid.Length == 17)
|
||||
{
|
||||
if (!GaggedIds.Contains(steamid))
|
||||
GaggedIds.Add(steamid);
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_tag_unmute")]
|
||||
[CommandHelper(minArgs: 1, usage: "<SteamID>", whoCanExecute: CommandUsage.SERVER_ONLY)]
|
||||
public void OnTagUnMuteCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
string? steamid = command.GetArg(1);
|
||||
|
||||
if (steamid.Length == 17)
|
||||
{
|
||||
if (GaggedIds.Contains(steamid))
|
||||
GaggedIds.Remove(steamid);
|
||||
}
|
||||
LoadTags();
|
||||
ApplyAllClanTags();
|
||||
info.ReplyToCommand($"[CS2-Tags] Reloaded {_tags.Count} tags.");
|
||||
}
|
||||
|
||||
private void OnClientAuthorized(int playerSlot, SteamID steamId)
|
||||
{
|
||||
CCSPlayerController? player = Utilities.GetPlayerFromSlot(playerSlot);
|
||||
|
||||
var player = Utilities.GetPlayerFromSlot(playerSlot);
|
||||
if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) return;
|
||||
|
||||
AddTimer(2.0f, () => SetPlayerClanTag(player));
|
||||
AddTimer(2.0f, () => SetPlayerClanTag(player), TimerFlags.STOP_ON_MAPCHANGE);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo info)
|
||||
{
|
||||
CCSPlayerController? player = @event.Userid;
|
||||
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) return HookResult.Continue;
|
||||
|
||||
AddTimer(2.0f, () => SetPlayerClanTag(player));
|
||||
|
||||
AddTimer(2.0f, () => SetPlayerClanTag(player), TimerFlags.STOP_ON_MAPCHANGE);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void OnClientDisconnect(int playerSlot)
|
||||
{
|
||||
CCSPlayerController? player = Utilities.GetPlayerFromSlot(playerSlot);
|
||||
|
||||
if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) return;
|
||||
|
||||
GaggedIds.Remove(player.SteamID.ToString()!);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info)
|
||||
{
|
||||
CCSPlayerController? player = @event.Userid;
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid || player.IsBot) return HookResult.Continue;
|
||||
|
||||
AddTimer(1.5f, () => SetPlayerClanTag(player));
|
||||
|
||||
AddTimer(1.5f, () => SetPlayerClanTag(player), TimerFlags.STOP_ON_MAPCHANGE);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
CCSPlayerController? player = @event.Userid;
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid || player.IsBot) return HookResult.Continue;
|
||||
|
||||
AddTimer(1.5f, () => SetPlayerClanTag(player));
|
||||
|
||||
AddTimer(1.5f, () => SetPlayerClanTag(player), TimerFlags.STOP_ON_MAPCHANGE);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Gags and "@" admin chat are CS2-SimpleAdmin's job: its catch-all command listener runs before
|
||||
// these and stops the command, so a gagged player's message never reaches here.
|
||||
private static bool IsCommandOrTrigger(string message) =>
|
||||
message.StartsWith('!') || message.StartsWith('@') || message.StartsWith('/') || message.StartsWith('.') || message == "rtv";
|
||||
|
||||
private HookResult OnPlayerChat(CCSPlayerController? player, CommandInfo info)
|
||||
{
|
||||
if (player == null || !player.IsValid || info.GetArg(1).Length == 0 || player.AuthorizedSteamID == null) return HookResult.Continue;
|
||||
string steamid = player.AuthorizedSteamID.SteamId64.ToString();
|
||||
var message = info.GetArg(1);
|
||||
if (player == null || !player.IsValid || message.Length == 0 || IsCommandOrTrigger(message)) return HookResult.Continue;
|
||||
|
||||
if (player.SteamID.ToString() != "" && GaggedIds.Contains(player.SteamID.ToString())) return HookResult.Handled;
|
||||
var tag = FindTag(player);
|
||||
if (tag == null || !tag.AffectsChat) return HookResult.Continue;
|
||||
|
||||
if (info.GetArg(1).StartsWith("!") || info.GetArg(1).StartsWith("@") || info.GetArg(1).StartsWith("/") || info.GetArg(1).StartsWith(".") || info.GetArg(1) == "rtv") return HookResult.Continue;
|
||||
|
||||
if (JsonTags != null && JsonTags.TryGetValue("tags", out var tags) && tags is JObject tagsObject)
|
||||
{
|
||||
string deadIcon = !player.PawnIsAlive ? $"{ChatColors.White}☠ {ChatColors.Default}" : "";
|
||||
|
||||
if (tagsObject.TryGetValue(steamid, out var playerTag) && playerTag is JObject)
|
||||
{
|
||||
string prefix = playerTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = playerTag?["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = playerTag?["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
Server.PrintToChatAll(ReplaceTags($" {deadIcon}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}", player.TeamNum));
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
|
||||
foreach (var tagKey in tagsObject.Properties())
|
||||
{
|
||||
if (tagKey.Name.StartsWith("#"))
|
||||
{
|
||||
string group = tagKey.Name;
|
||||
bool inGroup = AdminManager.PlayerInGroup(player, group);
|
||||
|
||||
if (inGroup)
|
||||
{
|
||||
if (tagsObject.TryGetValue(group, out var groupTag) && groupTag is JObject)
|
||||
{
|
||||
string prefix = groupTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = groupTag?["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = groupTag?["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
Server.PrintToChatAll(ReplaceTags($" {deadIcon}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}", player.TeamNum));
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tagKey.Name.StartsWith("@"))
|
||||
{
|
||||
string permission = tagKey.Name;
|
||||
bool hasPermission = AdminManager.PlayerHasPermissions(player, permission);
|
||||
|
||||
if (hasPermission)
|
||||
{
|
||||
if (tagsObject.TryGetValue(permission, out var permissionTag) && permissionTag is JObject)
|
||||
{
|
||||
string prefix = permissionTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = permissionTag?["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = permissionTag?["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
Server.PrintToChatAll(ReplaceTags($" {deadIcon}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}", player.TeamNum));
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tagsObject.TryGetValue("everyone", out var everyoneTag) && everyoneTag is JObject && everyoneTag?["team_chat"]?.Value<bool>() == true)
|
||||
{
|
||||
string prefix = everyoneTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = everyoneTag?["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = everyoneTag?["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
Server.PrintToChatAll(ReplaceTags($" {deadIcon}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}", player.TeamNum));
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
var line = FormatChat(player, tag, message, player.TeamNum);
|
||||
Server.PrintToChatAll($" {line}");
|
||||
return HookResult.Handled;
|
||||
}
|
||||
|
||||
private HookResult OnPlayerChatTeam(CCSPlayerController? player, CommandInfo info)
|
||||
{
|
||||
if (player == null || !player.IsValid || info.GetArg(1).Length == 0 || player.AuthorizedSteamID == null) return HookResult.Continue;
|
||||
var message = info.GetArg(1);
|
||||
if (player == null || !player.IsValid || message.Length == 0 || IsCommandOrTrigger(message)) return HookResult.Continue;
|
||||
|
||||
var tag = FindTag(player);
|
||||
if (tag == null || !tag.AffectsChat) return HookResult.Continue;
|
||||
|
||||
var line = $"{TeamName(player.TeamNum)} {ChatColors.Default}{FormatChat(player, tag, message, player.TeamNum)}";
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false } && p.TeamNum == player.TeamNum))
|
||||
{
|
||||
p.PrintToChat($" {line}");
|
||||
}
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
|
||||
private string FormatChat(CCSPlayerController player, Tag tag, string message, int teamNum)
|
||||
{
|
||||
string deadIcon = player.TeamNum > (int)CsTeam.Spectator && !player.PawnIsAlive ? $"{ChatColors.White}☠ {ChatColors.Default}" : "";
|
||||
string prefix = tag.Prefix == "" ? "" : ReplaceTags(tag.Prefix, teamNum).TrimEnd() + " ";
|
||||
string nickColor = tag.NickColor == "" ? TeamColor(teamNum) : ReplaceTags(tag.NickColor, teamNum);
|
||||
string messageColor = tag.MessageColor == "" ? ChatColors.Default.ToString() : ReplaceTags(tag.MessageColor, teamNum);
|
||||
|
||||
// Colour tags are only replaced in the config's strings, never in the player's name or message.
|
||||
return $"{deadIcon}{prefix}{ChatColors.Default}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{message}";
|
||||
}
|
||||
|
||||
// SteamID64 entries win, then groups ("#...") and permissions ("@...") in file order, then "everyone".
|
||||
private Tag? FindTag(CCSPlayerController player)
|
||||
{
|
||||
if (player.IsBot || player.IsHLTV || player.AuthorizedSteamID == null) return null;
|
||||
|
||||
string steamid = player.AuthorizedSteamID.SteamId64.ToString();
|
||||
|
||||
if (player.SteamID.ToString() != "" && GaggedIds.Contains(player.SteamID.ToString())) return HookResult.Handled;
|
||||
|
||||
if (info.GetArg(1).StartsWith("@") && AdminManager.PlayerHasPermissions(player, "@css/chat"))
|
||||
foreach (var (key, tag) in _tags)
|
||||
{
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot && !p.IsHLTV && AdminManager.PlayerHasPermissions(p, "@css/chat")))
|
||||
{
|
||||
p.PrintToChat($" {ChatColors.Lime}(ADMIN) {ChatColors.Default}{player.PlayerName}: {info.GetArg(1).Remove(0, 1)}");
|
||||
}
|
||||
|
||||
return HookResult.Handled;
|
||||
if (key == steamid) return tag;
|
||||
}
|
||||
|
||||
if (info.GetArg(1).StartsWith("!") || info.GetArg(1).StartsWith("@") || info.GetArg(1).StartsWith("/") || info.GetArg(1).StartsWith(".") || info.GetArg(1) == "rtv") return HookResult.Continue;
|
||||
|
||||
if (JsonTags != null && JsonTags.TryGetValue("tags", out var tags) && tags is JObject tagsObject)
|
||||
foreach (var (key, tag) in _tags)
|
||||
{
|
||||
string deadIcon = !player.PawnIsAlive ? $"{ChatColors.White}☠ {ChatColors.Default}" : "";
|
||||
if (tagsObject.TryGetValue(steamid, out var playerTag) && playerTag is JObject)
|
||||
{
|
||||
string prefix = playerTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = playerTag?["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = playerTag?["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.TeamNum == player.TeamNum && p.IsValid && !p.IsBot))
|
||||
{
|
||||
string messageToSend = $"{deadIcon}{TeamName(player.TeamNum)} {ChatColors.Default}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}";
|
||||
p.PrintToChat($" {ReplaceTags(messageToSend, p.TeamNum)}");
|
||||
}
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
|
||||
foreach (var tagKey in tagsObject.Properties())
|
||||
{
|
||||
if (tagKey.Name.StartsWith("#"))
|
||||
{
|
||||
string group = tagKey.Name;
|
||||
bool inGroup = AdminManager.PlayerInGroup(player, group);
|
||||
|
||||
if (inGroup && tagsObject.TryGetValue(group, out var groupTag) && groupTag is JObject)
|
||||
{
|
||||
string prefix = groupTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = groupTag["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = groupTag["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.TeamNum == player.TeamNum && p.IsValid && !p.IsBot))
|
||||
{
|
||||
string messageToSend = $"{deadIcon}{TeamName(player.TeamNum)} {ChatColors.Default}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}";
|
||||
p.PrintToChat($" {ReplaceTags(messageToSend, p.TeamNum)}");
|
||||
}
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
}
|
||||
|
||||
if (tagKey.Name.StartsWith("@"))
|
||||
{
|
||||
string permission = tagKey.Name;
|
||||
bool hasPermission = AdminManager.PlayerHasPermissions(player, permission);
|
||||
|
||||
if (hasPermission && tagsObject.TryGetValue(permission, out var permissionTag) && permissionTag is JObject)
|
||||
{
|
||||
string prefix = permissionTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = permissionTag["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = permissionTag["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.TeamNum == player.TeamNum && p.IsValid && !p.IsBot))
|
||||
{
|
||||
string messageToSend = $"{deadIcon}{TeamName(player.TeamNum)} {ChatColors.Default}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}";
|
||||
p.PrintToChat($" {ReplaceTags(messageToSend, p.TeamNum)}");
|
||||
}
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tagsObject.TryGetValue("everyone", out var everyoneTag) && everyoneTag is JObject)
|
||||
{
|
||||
string prefix = everyoneTag["prefix"]?.ToString() ?? "";
|
||||
string nickColor = everyoneTag["nick_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
string messageColor = everyoneTag["message_color"]?.ToString() ?? ChatColors.Default.ToString();
|
||||
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.TeamNum == player.TeamNum && p.IsValid && !p.IsBot))
|
||||
{
|
||||
string messageToSend = $"{deadIcon}{TeamName(player.TeamNum)} {ChatColors.Default}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}";
|
||||
p.PrintToChat($" {ReplaceTags(messageToSend, p.TeamNum)}");
|
||||
}
|
||||
//p.PrintToChat(ReplaceTags($" {TeamName(player.TeamNum)} {ChatColors.Default}{prefix}{nickColor}{player.PlayerName}{ChatColors.Default}: {messageColor}{info.GetArg(1)}", p.TeamNum));
|
||||
|
||||
return HookResult.Handled;
|
||||
}
|
||||
if (key.StartsWith('#') && AdminManager.PlayerInGroup(player, key)) return tag;
|
||||
if (key.StartsWith('@') && AdminManager.PlayerHasPermissions(player, key)) return tag;
|
||||
}
|
||||
|
||||
foreach (var (key, tag) in _tags)
|
||||
{
|
||||
if (key == "everyone") return tag;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ApplyAllClanTags()
|
||||
{
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
SetPlayerClanTag(player);
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void SetPlayerClanTag(CCSPlayerController? player)
|
||||
{
|
||||
if (player == null || !player.IsValid || player.IsBot || player.IsHLTV || player.AuthorizedSteamID == null) return;
|
||||
if (player == null || !player.IsValid) return;
|
||||
|
||||
string steamid = player.SteamID!.ToString();
|
||||
var tag = FindTag(player);
|
||||
if (tag == null || tag.Scoreboard == "" || player.Clan == tag.Scoreboard) return;
|
||||
|
||||
if (JsonTags != null && JsonTags.TryGetValue("tags", out var tags) && tags is JObject tagsObject)
|
||||
{
|
||||
if (tagsObject.TryGetValue(steamid, out var playerTag) && playerTag is JObject)
|
||||
{
|
||||
var scoreboardValue = playerTag["scoreboard"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(scoreboardValue))
|
||||
{
|
||||
player.Clan = scoreboardValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tagKey in tagsObject.Properties())
|
||||
{
|
||||
if (tagKey.Name.StartsWith("#"))
|
||||
{
|
||||
string group = tagKey.Name;
|
||||
bool inGroup = AdminManager.PlayerInGroup(player, group);
|
||||
|
||||
if (inGroup)
|
||||
{
|
||||
if (tagsObject.TryGetValue(group, out var groupTag) && groupTag is JObject)
|
||||
{
|
||||
var scoreboardValue = groupTag["scoreboard"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(scoreboardValue))
|
||||
{
|
||||
player.Clan = scoreboardValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tagKey.Name.StartsWith("@"))
|
||||
{
|
||||
string permission = tagKey.Name;
|
||||
bool hasPermission = AdminManager.PlayerHasPermissions(player, permission);
|
||||
|
||||
if (hasPermission)
|
||||
{
|
||||
if (tagsObject.TryGetValue(permission, out var permissionTag) && permissionTag is JObject)
|
||||
{
|
||||
var scoreboardValue = permissionTag["scoreboard"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(scoreboardValue))
|
||||
{
|
||||
player.Clan = scoreboardValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tagsObject.TryGetValue("everyone", out var everyone) && everyone is JObject everyoneObject)
|
||||
{
|
||||
var scoreboardValue = everyoneObject["scoreboard"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(scoreboardValue))
|
||||
{
|
||||
player.Clan = scoreboardValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
player.Clan = tag.Scoreboard;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController", "m_szClan");
|
||||
}
|
||||
|
||||
private string TeamName(int teamNum)
|
||||
private static string TeamName(int teamNum)
|
||||
{
|
||||
string teamName = "";
|
||||
|
||||
switch (teamNum)
|
||||
return teamNum switch
|
||||
{
|
||||
case 0:
|
||||
teamName = $"(NONE)";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
teamName = $"(SPEC)";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
teamName = $"{ChatColors.Yellow}(T)";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
teamName = $"{ChatColors.Blue}(CT)";
|
||||
break;
|
||||
}
|
||||
|
||||
return teamName;
|
||||
(int)CsTeam.Spectator => "(SPEC)",
|
||||
(int)CsTeam.Terrorist => $"{ChatColors.Yellow}(T)",
|
||||
(int)CsTeam.CounterTerrorist => $"{ChatColors.Blue}(CT)",
|
||||
_ => "(NONE)",
|
||||
};
|
||||
}
|
||||
|
||||
private string TeamColor(int teamNum)
|
||||
private static string TeamColor(int teamNum)
|
||||
{
|
||||
string teamColor;
|
||||
|
||||
switch (teamNum)
|
||||
return teamNum switch
|
||||
{
|
||||
case 2:
|
||||
teamColor = $"{ChatColors.Gold}";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
teamColor = $"{ChatColors.Blue}";
|
||||
break;
|
||||
|
||||
default:
|
||||
teamColor = "";
|
||||
break;
|
||||
}
|
||||
|
||||
return teamColor;
|
||||
(int)CsTeam.Terrorist => ChatColors.Gold.ToString(),
|
||||
(int)CsTeam.CounterTerrorist => ChatColors.Blue.ToString(),
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
|
||||
private string ReplaceTags(string message, int teamNum = 0)
|
||||
private static string ReplaceTags(string text, int teamNum = 0)
|
||||
{
|
||||
if (message.Contains('{'))
|
||||
if (!text.Contains('{')) return text;
|
||||
|
||||
foreach (FieldInfo field in typeof(ChatColors).GetFields(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
string modifiedValue = message;
|
||||
foreach (FieldInfo field in typeof(ChatColors).GetFields())
|
||||
{
|
||||
string pattern = $"{{{field.Name}}}";
|
||||
if (message.Contains(pattern, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modifiedValue = modifiedValue.Replace(pattern, field.GetValue(null)!.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
return modifiedValue.Replace("{TEAMCOLOR}", TeamColor(teamNum));
|
||||
if (field.FieldType == typeof(char))
|
||||
text = text.Replace($"{{{field.Name}}}", field.GetValue(null)!.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return message;
|
||||
return text.Replace("{TEAMCOLOR}", TeamColor(teamNum), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
// Written to configs/plugins/CS2-Tags/tags.json on first load. Our CS2-SimpleAdmin rank groups,
|
||||
// highest first; guardian (VIP) comes after staff so a staff member's rank tag wins.
|
||||
private const string DefaultTagsJson = """
|
||||
{
|
||||
"tags": {
|
||||
"#rank/owner": { "prefix": "{DarkRed}[Owner]", "nick_color": "{LightRed}", "message_color": "", "scoreboard": "[Owner]" },
|
||||
"#rank/senioradmin": { "prefix": "{Red}[Senior Admin]", "nick_color": "{LightRed}", "message_color": "", "scoreboard": "[Sr. Admin]" },
|
||||
"#rank/admin": { "prefix": "{Red}[Admin]", "nick_color": "{LightRed}", "message_color": "", "scoreboard": "[Admin]" },
|
||||
"#rank/trialadmin": { "prefix": "{LightRed}[Trial Admin]", "nick_color": "", "message_color": "", "scoreboard": "[T. Admin]" },
|
||||
"#rank/mod": { "prefix": "{Blue}[Mod]", "nick_color": "", "message_color": "", "scoreboard": "[Mod]" },
|
||||
"#rank/trialmod": { "prefix": "{LightBlue}[Trial Mod]", "nick_color": "", "message_color": "", "scoreboard": "[T. Mod]" },
|
||||
"#rank/helper": { "prefix": "{Green}[Helper]", "nick_color": "", "message_color": "", "scoreboard": "[Helper]" },
|
||||
"#rank/guardian": { "prefix": "{Gold}[Guardian]", "nick_color": "", "message_color": "", "scoreboard": "[Guardian]" },
|
||||
"everyone": { "prefix": "", "nick_color": "", "message_color": "", "scoreboard": "" }
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>CS2_Tags</RootNamespace>
|
||||
<AssemblyName>CS2-Tags</AssemblyName>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.130" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<!-- Compile-time only: the server already has CounterStrikeSharp.API.dll -->
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.375">
|
||||
<PrivateAssets>none</PrivateAssets>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
22
CS2-Tags.sln
22
CS2-Tags.sln
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS2-Tags", "CS2-Tags.csproj", "{06A7649D-5CC9-4A37-9AB8-FEFB5DE69668}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{06A7649D-5CC9-4A37-9AB8-FEFB5DE69668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{06A7649D-5CC9-4A37-9AB8-FEFB5DE69668}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{06A7649D-5CC9-4A37-9AB8-FEFB5DE69668}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{06A7649D-5CC9-4A37-9AB8-FEFB5DE69668}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
121
README.md
121
README.md
|
|
@ -1,78 +1,75 @@
|
|||
# CS2-Tags
|
||||
|
||||
### Do you appreciate what I do? Buy me a cup of tea ❤️
|
||||
[](https://ko-fi.com/Y8Y4THKXG)
|
||||
Our fork of [daffyyyy/CS2-Tags](https://github.com/daffyyyy/CS2-Tags): chat prefixes, name/message
|
||||
colours and scoreboard (clan) tags, assigned by SteamID64, admin group or permission. Built for
|
||||
CounterStrikeSharp 1.0.375 (net10.0) and meant to run alongside CS2-SimpleAdmin.
|
||||
|
||||

|
||||

|
||||
## Changes from upstream 1.0.4c
|
||||
|
||||
### Description
|
||||
Adds tags to the server that can be easily edited, tags can be assigned via permission or steamid64
|
||||
- Targets net10.0 / CSS 1.0.375. Uses System.Text.Json, so Newtonsoft.Json no longer ships with it.
|
||||
- Config moved to `configs/plugins/CS2-Tags/tags.json`. The default file uses our SimpleAdmin rank
|
||||
groups. Comments and trailing commas are allowed. A broken file is logged and the previous
|
||||
tags stay in use.
|
||||
- Scoreboard tags call `SetStateChanged` on `m_szClan` so clients are told about the change.
|
||||
- Colour tags like `{RED}` are only expanded in the config's strings. Players can no longer colour
|
||||
their own chat by typing them.
|
||||
- An empty `nick_color` means the team colour (like normal chat), not whatever colour the prefix
|
||||
ended on.
|
||||
- A tag with an empty `prefix`, `nick_color` and `message_color` leaves that player's chat alone
|
||||
(the game prints it), in both all and team chat. The old `team_chat` key is gone.
|
||||
- `css_tags_reload` works from the server console and for `@css/root` admins, and reapplies
|
||||
scoreboard tags straight away. Hot reload applies tags to players already connected.
|
||||
- Removed `css_tag_mute`/`css_tag_unmute` and the `@` team-chat admin chat. CS2-SimpleAdmin
|
||||
already does both, and its catch-all command listener runs first. CSS always runs those before
|
||||
per-command listeners, so gagged players' messages never reach this plugin.
|
||||
|
||||
### Commands
|
||||
- css_tags_reload - Reload tags config
|
||||
## Configuration
|
||||
|
||||
### Configuration
|
||||
```
|
||||
`addons/counterstrikesharp/configs/plugins/CS2-Tags/tags.json`, written with defaults on first load:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"tags": {
|
||||
"#css/admin": { // Group
|
||||
"prefix": "{GREEN}[ADMIN]", // Chat prefix
|
||||
"nick_color": "{RED}", // Nick color
|
||||
"message_color": "{GOLD}", // Message nick color
|
||||
"scoreboard": "[ADMIN]" // Scoreboard tag
|
||||
},
|
||||
"@css/chat": { // Permission
|
||||
"prefix": "{GREEN}[ADMIN]", // Chat prefix
|
||||
"nick_color": "{RED}", // Nick color
|
||||
"message_color": "{GOLD}", // Message nick color
|
||||
"scoreboard": "[ADMIN]" // Scoreboard tag
|
||||
},
|
||||
"76561198202892670": { // SteamID64
|
||||
"prefix": "{GREEN}[ADMIN]",
|
||||
"nick_color": "{RED}",
|
||||
"message_color": "{GOLD}",
|
||||
"scoreboard": "[ADMIN]"
|
||||
},
|
||||
"everyone": { // Tag for everyone, bots excluded
|
||||
"prefix": "",
|
||||
"nick_color": "",
|
||||
"message_color": "",
|
||||
"scoreboard": "[Player]"
|
||||
}
|
||||
"76561198000000000": { "prefix": "{Magenta}[Dev]", "nick_color": "", "message_color": "", "scoreboard": "[Dev]" },
|
||||
"#rank/owner": { "prefix": "{DarkRed}[Owner]", "nick_color": "{LightRed}", "message_color": "", "scoreboard": "[Owner]" },
|
||||
"#rank/admin": { "prefix": "{Red}[Admin]", "nick_color": "{LightRed}", "message_color": "", "scoreboard": "[Admin]" },
|
||||
"@css/vip": { "prefix": "{Gold}[VIP]", "nick_color": "", "message_color": "", "scoreboard": "[VIP]" },
|
||||
"everyone": { "prefix": "", "nick_color": "", "message_color": "", "scoreboard": "" }
|
||||
}
|
||||
}
|
||||
```
|
||||
In addons/counterstrikesharp/plugins/CS2-Tags/tags.json
|
||||
|
||||
### Requirments
|
||||
[CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/) **tested on v142**
|
||||
Each player gets exactly one tag, picked in this order:
|
||||
|
||||
1. A SteamID64 key matching the player.
|
||||
2. The first `#group` or `@permission` key the player matches, **in file order**. Put higher
|
||||
ranks first. `@css/root` passes every `@css/...` check, so owners match any permission key.
|
||||
3. `everyone`, if present.
|
||||
|
||||
Fields (all optional, empty means "not set"):
|
||||
|
||||
- `prefix` - shown before the name in chat.
|
||||
- `nick_color` / `message_color` - colour tags; empty means team colour / default.
|
||||
- `scoreboard` - clan tag on the scoreboard; empty leaves the player's own clan tag.
|
||||
|
||||
Colours: any `ChatColors` field name in braces, case-insensitive, e.g. `{Red}`, `{LightBlue}`,
|
||||
`{Gold}`, `{Grey}`, plus `{TEAMCOLOR}`.
|
||||
|
||||
Group membership comes from CS2-SimpleAdmin (`css_addadmin ... -g`, `css_addgroup`, then
|
||||
`css_reloadadmins`). After changing ranks or `tags.json`, run `css_tags_reload` to reapply
|
||||
scoreboard tags without waiting for a respawn.
|
||||
|
||||
## Commands
|
||||
|
||||
- `css_tags_reload` (`@css/root` or server console) - reload `tags.json` and reapply scoreboard tags.
|
||||
|
||||
## Building and releasing
|
||||
|
||||
### Colors
|
||||
```
|
||||
public static char Default = '\x01';
|
||||
public static char White = '\x01';
|
||||
public static char Darkred = '\x02';
|
||||
public static char Green = '\x04';
|
||||
public static char LightYellow = '\x03';
|
||||
public static char LightBlue = '\x03';
|
||||
public static char Olive = '\x05';
|
||||
public static char Lime = '\x06';
|
||||
public static char Red = '\x07';
|
||||
public static char Purple = '\x03';
|
||||
public static char Grey = '\x08';
|
||||
public static char Yellow = '\x09';
|
||||
public static char Gold = '\x10';
|
||||
public static char Silver = '\x0A';
|
||||
public static char Blue = '\x0B';
|
||||
public static char DarkBlue = '\x0C';
|
||||
public static char BlueGrey = '\x0D';
|
||||
public static char Magenta = '\x0E';
|
||||
public static char LightRed = '\x0F';
|
||||
cd plugins/CS2-Tags && ../../build.sh # output in compiled/CS2-Tags/
|
||||
FORGEJO_TOKEN=... ./release.sh v<ModuleVersion>
|
||||
```
|
||||
|
||||
```{TEAMCOLOR} - Team color```
|
||||
Use color name for e.g. {LightRed}
|
||||
|
||||
### TODO
|
||||
- Thinking about better fix for commands handling
|
||||
`release.sh` rebuilds from the committed source and publishes `CS2-Tags-<tag>.tar.gz`, laid out
|
||||
like `game/csgo/`, to https://git.zio.sh/cs2/CS2-Tags. The tag must equal `v<ModuleVersion>`
|
||||
(`v1.0.4c-zio1` now).
|
||||
|
|
|
|||
115
release.sh
Executable file
115
release.sh
Executable file
|
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build CS2-Tags, package it as one tarball and publish it as a Forgejo release.
|
||||
#
|
||||
# ./release.sh <tag> e.g. ./release.sh v0.14.0
|
||||
#
|
||||
# Commit first: the plugin is rebuilt here from the committed source, so the release always matches
|
||||
# the tag. The tag is created on HEAD and pushed to origin if it doesn't exist yet. Re-running with
|
||||
# the same tag replaces that release's attachment.
|
||||
#
|
||||
# Asset: CS2-Tags-<tag>.tar.gz, laid out like the server's game/csgo/ - extract it there:
|
||||
# tar -xzf CS2-Tags-<tag>.tar.gz -C /srv/cs2/game/csgo
|
||||
#
|
||||
# addons/counterstrikesharp/plugins/CS2-Tags/ CS2-Tags.dll, .pdb, .deps.json
|
||||
#
|
||||
# No configs/: the plugin writes configs/plugins/CS2-Tags/tags.json itself when it is missing, so an
|
||||
# upgrade never touches the server's tags.
|
||||
set -euo pipefail
|
||||
|
||||
FORGEJO_URL="https://git.zio.sh"
|
||||
OWNER="cs2"
|
||||
REPO="CS2-Tags"
|
||||
# Placeholder - replace, or set FORGEJO_TOKEN in the environment instead. Needs write:repository.
|
||||
# Only uploading needs it; downloading from the public repo doesn't. Don't commit a real token.
|
||||
FORGEJO_TOKEN="${FORGEJO_TOKEN:-CHANGE_ME_FORGEJO_TOKEN}"
|
||||
|
||||
# Only these ship from the publish output. CounterStrikeSharp.API.dll is excluded by the csproj
|
||||
# (ExcludeAssets=runtime); the server already provides it.
|
||||
PLUGIN_FILES=(
|
||||
CS2-Tags.dll
|
||||
CS2-Tags.pdb
|
||||
CS2-Tags.deps.json
|
||||
)
|
||||
|
||||
TAG="${1:-}"
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "usage: $0 <tag> (e.g. v1.0.4c-zio1)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$FORGEJO_TOKEN" == "CHANGE_ME_FORGEJO_TOKEN" ]]; then
|
||||
echo "Set FORGEJO_TOKEN (edit release.sh or export it) first." >&2
|
||||
exit 1
|
||||
fi
|
||||
for tool in podman jq curl git; do
|
||||
command -v "$tool" >/dev/null || { echo "'$tool' is required." >&2; exit 1; }
|
||||
done
|
||||
|
||||
cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
|
||||
if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
|
||||
echo "Tracked files have uncommitted changes; commit them so the tag matches the release." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The tag should name the version the plugin reports in `css_plugins list`.
|
||||
version="$(sed -n 's/.*ModuleVersion => "\(.*\)";.*/\1/p' CS2-Tags.cs)"
|
||||
if [[ "${TAG#v}" != "$version" ]]; then
|
||||
echo "Tag $TAG doesn't match ModuleVersion $version in CS2-Tags.cs." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Build -----------------------------------------------------------------------
|
||||
OUT="release-out"
|
||||
PUBLISH="$OUT/publish"
|
||||
rm -rf bin obj "$OUT"
|
||||
podman run --rm -v "$(pwd)":/src:Z -w /src \
|
||||
mcr.microsoft.com/dotnet/sdk:10.0 dotnet publish -c Release -o "/src/$PUBLISH"
|
||||
|
||||
# --- Stage (layout of game/csgo/) and package ----------------------------------------
|
||||
STAGE="$OUT/stage"
|
||||
PLUGIN_DIR="$STAGE/addons/counterstrikesharp/plugins/CS2-Tags"
|
||||
mkdir -p "$PLUGIN_DIR"
|
||||
for file in "${PLUGIN_FILES[@]}"; do
|
||||
cp "$PUBLISH/$file" "$PLUGIN_DIR/"
|
||||
done
|
||||
|
||||
TARBALL="CS2-Tags-${TAG}.tar.gz"
|
||||
tar -czf "$OUT/$TARBALL" -C "$STAGE" addons
|
||||
rm -rf "$STAGE" "$PUBLISH"
|
||||
|
||||
echo "Packaged:"
|
||||
tar -tzf "$OUT/$TARBALL"
|
||||
|
||||
# --- Tag ---------------------------------------------------------------------------
|
||||
if ! git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
|
||||
git tag -a "$TAG" -m "$TAG"
|
||||
fi
|
||||
git push origin "refs/tags/$TAG"
|
||||
|
||||
# --- Release -----------------------------------------------------------------------
|
||||
API="$FORGEJO_URL/api/v1/repos/$OWNER/$REPO"
|
||||
AUTH=(-H "Authorization: token $FORGEJO_TOKEN")
|
||||
|
||||
release_json="$(curl -fsS "${AUTH[@]}" "$API/releases/tags/$TAG" 2>/dev/null || true)"
|
||||
if [[ -z "$release_json" ]]; then
|
||||
body="$(git log -1 --format=%B "$TAG")"
|
||||
release_json="$(jq -n --arg tag "$TAG" --arg body "$body" \
|
||||
'{tag_name: $tag, name: $tag, body: $body, draft: false, prerelease: false}' |
|
||||
curl -fsS "${AUTH[@]}" -H "Content-Type: application/json" -X POST --data @- "$API/releases")"
|
||||
echo "Created release $TAG"
|
||||
else
|
||||
echo "Release $TAG already exists, replacing its attachment"
|
||||
fi
|
||||
release_id="$(jq -r '.id' <<<"$release_json")"
|
||||
|
||||
existing_id="$(jq -r --arg n "$TARBALL" '.assets[]? | select(.name == $n) | .id' <<<"$release_json")"
|
||||
if [[ -n "$existing_id" ]]; then
|
||||
curl -fsS "${AUTH[@]}" -X DELETE "$API/releases/$release_id/assets/$existing_id" >/dev/null
|
||||
fi
|
||||
curl -fsS "${AUTH[@]}" -X POST -F "attachment=@$OUT/$TARBALL" \
|
||||
"$API/releases/$release_id/assets?name=$TARBALL" >/dev/null
|
||||
echo "Uploaded $TARBALL"
|
||||
|
||||
echo
|
||||
echo "Download URL:"
|
||||
echo " $FORGEJO_URL/$OWNER/$REPO/releases/download/$TAG/$TARBALL"
|
||||
Loading…
Add table
Add a link
Reference in a new issue