QoE Update
## Added - **`CurseSkillPerPlayer`** (config.json) — limits how many curse skills (Darkness, JumpBan, PrimaryBan, Bankrupt and 14 others) can be stacked on a single player per round. `null` = unlimited (default), `1` = one curse per player, `2` = two. When the limit is reached the target is rejected and the curser gets a chat warning. Enforced centrally in `SkillAction`, so no per-skill code is needed. - **Tripwire cooldown mode** — replaces the fixed wire limit with a Replicator-style cooldown. Players can place as many wires as they want as long as the cooldown is up; remaining time is shown on the HUD. Configurable via `Cooldown` in skillsInfo.json (default 20s). - **Tripwire team colors** — T wires red (255, 64, 64), CT wires blue (64, 128, 255). ## Fixed - **ExpensiveAmmo** also charged money for knife swings and grenade throws. It now only charges when a bullet-firing weapon is fired. - **Knockback** also triggered on grenade throws; bound to the same allowlist. ## Removed - Dead code: `Earthquake.cs` and `HealingChicken.cs` (neither was registered in the `Skills` enum, so neither ever ran), `EntityManager.CreateTrackedEnvShake`, `EntityManager.CreateTrackedChicken`, `PlayerManager.UpdatePlayerSkill`. ## Changed - **Tripwire wires are now removed when the owner dies or loses the skill.** Previously they stayed on the map until the end of the round. - `PlayerEvents.cs` split into three partial files: player events in `PlayerEvents.cs`, round and skill dispatch in `RoundEvents.cs`, entity and weapon hooks in `EntityEvents.cs`. Behaviour is unchanged; all members stay in the same `Event` class. - 28 copy-pasted enemy-gathering LINQ chains reduced to the shared helper (Bankrupt, CarefulBullets, Darkness, Deactivator, Deaf, ExpensiveAmmo, Giant, Glitch, Jammer, JumpBan, JumpCurse, LifeSwap, Magnifier, MoneySwap, Nightmare, Poison, PrimaryBan, WildThrow).
This commit is contained in:
parent
98f6e65b01
commit
b0b50a224f
50 changed files with 1215 additions and 1279 deletions
|
|
@ -92,6 +92,23 @@ 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;
|
||||
|
||||
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)
|
||||
|
|
@ -105,6 +122,15 @@ namespace src
|
|||
SkillsUsedThisMap.TryAdd(skill, 0);
|
||||
}
|
||||
|
||||
if (Enum.TryParse<Skills>(skill, out var parsedSkill) && SkillUtils.IsCurseSkill(parsedSkill))
|
||||
{
|
||||
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}";
|
||||
|
|
|
|||
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "Tripwire",
|
||||
"tripwire_desc": "Click [css_useSkill] to string a wire between two walls. Enemies touching it show on your radar",
|
||||
"tripwire_limit_info": "You can only have {0} tripwires at a time.",
|
||||
"tripwire_no_wall_info": "No walls close enough on both sides.",
|
||||
"tripwire_placed_info": "Tripwire placed.",
|
||||
"tripwire_triggered_info": "'{0}' tripped your wire.",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -486,7 +486,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -532,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é",
|
||||
|
|
|
|||
|
|
@ -486,7 +486,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -532,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",
|
||||
|
|
|
|||
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -533,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": "Игрок не найден",
|
||||
|
|
|
|||
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "Tel Tuzağı",
|
||||
"tripwire_desc": "[css_useSkill] ile iki duvar arasına tel ger. Tele değen düşman radarında görünür",
|
||||
"tripwire_limit_info": "Aynı anda en fazla {0} tel gerebilirsin.",
|
||||
"tripwire_no_wall_info": "İki yanında da yeterince yakın duvar yok.",
|
||||
"tripwire_placed_info": "Tel gerildi.",
|
||||
"tripwire_triggered_info": "'{0}' teline takıldı.",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -484,7 +484,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -530,6 +529,7 @@
|
|||
"duplicate_player": "找到多个同名玩家。",
|
||||
"selectplayerskill_command": "输入 /t",
|
||||
"selectplayerskill_incorrect_enemy_index": "未找到可供选择的玩家。",
|
||||
"curse_limit_info": "curse_limit_info",
|
||||
|
||||
"skill_not_found_setskill": "未找到 CHATCOLORS.RED 技能",
|
||||
"player_not_found_setskill": "未找到 CHATCOLORS.RED 玩家",
|
||||
|
|
|
|||
249
jRandomSkills - SRC Files/src/player/EntityEvents.cs
Normal file
249
jRandomSkills - SRC Files/src/player/EntityEvents.cs
Normal file
|
|
@ -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<CBaseTrigger>(0);
|
||||
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
|
||||
|
||||
DispatchToActiveSkills("OnTriggerEnter", trigger, entity);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
private static HookResult OnTriggerExit(DynamicHook hook)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
CBaseTrigger trigger = hook.GetParam<CBaseTrigger>(0);
|
||||
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
|
||||
|
||||
DispatchToActiveSkills("OnTriggerExit", trigger, entity);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
private static HookResult OnWeaponCanAcquire(DynamicHook hook)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
CCSPlayer_ItemServices itemServices = hook.GetParam<CCSPlayer_ItemServices>(0);
|
||||
if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue;
|
||||
|
||||
CEconItemView econItem = hook.GetParam<CEconItemView>(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<CCSPlayerController>();
|
||||
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<CCSPlayerController>(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>(CheckTransmit);
|
||||
isTransmitRegistered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -107,55 +107,6 @@ namespace src.player
|
|||
catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] unhook failed: {ex.Message}"); }
|
||||
}
|
||||
|
||||
private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List<jSkill_SkillInfo> candidates, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
|
||||
{
|
||||
if (candidates == null || candidates.Count == 0) return noneSkill;
|
||||
|
||||
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 readonly Skills[] lateDamageSkills = [Skills.SecondLife, Skills.Phoenix, Skills.ReZombie];
|
||||
|
||||
private static readonly HashSet<Skills> tickFailuresLogged = [];
|
||||
|
|
@ -301,78 +252,6 @@ namespace src.player
|
|||
}
|
||||
}
|
||||
|
||||
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 PlayerHurtPre(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
lock (setLock)
|
||||
|
|
@ -466,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<CBaseTrigger>(0);
|
||||
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
|
||||
|
||||
DispatchToActiveSkills("OnTriggerEnter", trigger, entity);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
private static HookResult OnTriggerExit(DynamicHook hook)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
CBaseTrigger trigger = hook.GetParam<CBaseTrigger>(0);
|
||||
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
|
||||
|
||||
DispatchToActiveSkills("OnTriggerExit", trigger, entity);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
private static HookResult OnWeaponCanAcquire(DynamicHook hook)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
CCSPlayer_ItemServices itemServices = hook.GetParam<CCSPlayer_ItemServices>(0);
|
||||
if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue;
|
||||
|
||||
CEconItemView econItem = hook.GetParam<CEconItemView>(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<CCSPlayerController>();
|
||||
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<CCSPlayerController>(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<Skills, string> _skillNames =
|
||||
Enum.GetValues<Skills>().ToDictionary(s => s, s => s.ToString());
|
||||
private static readonly HashSet<Skills> _activeSkillsSet = [];
|
||||
|
|
@ -759,94 +531,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;
|
||||
SkillUtils.ClearKillCredits();
|
||||
Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
|
||||
foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist))
|
||||
{
|
||||
var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||
if (skillPlayer == null) continue;
|
||||
skillPlayer.IsDrawing = !isWarmup;
|
||||
skillPlayer.PrintHTML = null;
|
||||
}
|
||||
|
||||
Instance.RemoveListener<CheckTransmit>(CheckTransmit);
|
||||
int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue<Int32>() ?? 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;
|
||||
|
|
@ -863,90 +547,6 @@ namespace src.player
|
|||
Utilities.SetStateChanged(player, "CBasePlayerController", "m_iDesiredFOV");
|
||||
}
|
||||
|
||||
public static void OnMapChange()
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
isTransmitRegistered = false;
|
||||
Instance.RemoveListener<CheckTransmit>(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>(CheckTransmit);
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
private static HookResult PlayerDeathPre(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
try
|
||||
|
|
@ -1077,14 +677,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)
|
||||
|
|
@ -1094,483 +686,8 @@ namespace src.player
|
|||
}
|
||||
}
|
||||
|
||||
private static void SetSkill()
|
||||
{
|
||||
long perfStart = PerfLog.Start();
|
||||
SetSkillCore();
|
||||
PerfLog.End("SetSkill total", perfStart, 2.0);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<uint, jSkill_SkillInfo> nextRoundPicks = [];
|
||||
|
||||
private sealed class PickContext
|
||||
{
|
||||
public required List<jSkill_SkillInfo> BaseList { get; init; }
|
||||
public required Dictionary<Skills, string> RequiredPermissions { get; init; }
|
||||
public required HashSet<Skills> NeedsTeammates { get; init; }
|
||||
public required HashSet<Skills> CtOnly { get; init; }
|
||||
public required HashSet<Skills> TOnly { get; init; }
|
||||
public required int TerroristCount { get; init; }
|
||||
public required int CounterTerroristCount { get; init; }
|
||||
}
|
||||
|
||||
private static PickContext BuildPickContext(List<CCSPlayerController> validPlayers)
|
||||
{
|
||||
Dictionary<Skills, string> perms = [];
|
||||
foreach (var s in SkillData.Skills)
|
||||
{
|
||||
if (s == null || s.Skill == Skills.None) continue;
|
||||
string perm = SkillsInfo.GetValue<string>(s.Skill, "requiredPermission");
|
||||
if (!string.IsNullOrEmpty(perm)) perms[s.Skill] = perm;
|
||||
}
|
||||
|
||||
return new PickContext
|
||||
{
|
||||
BaseList = [.. SkillData.Skills.Where(s => s != null && s.Skill != Skills.None)],
|
||||
RequiredPermissions = perms,
|
||||
NeedsTeammates = ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates).Select(s => s.Name)),
|
||||
CtOnly = ToSkillSet(counterterroristSkills.Select(s => s.Name)),
|
||||
TOnly = ToSkillSet(terroristSkills.Select(s => s.Name)),
|
||||
TerroristCount = validPlayers.Count(p => p.Team == CsTeam.Terrorist),
|
||||
CounterTerroristCount = validPlayers.Count(p => p.Team == CsTeam.CounterTerrorist),
|
||||
};
|
||||
}
|
||||
|
||||
private static HashSet<Skills> ToSkillSet(IEnumerable<string> names)
|
||||
{
|
||||
HashSet<Skills> set = [];
|
||||
foreach (var name in names)
|
||||
if (Enum.TryParse<Skills>(name, out var skill)) set.Add(skill);
|
||||
return set;
|
||||
}
|
||||
|
||||
private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, PickContext ctx, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
|
||||
{
|
||||
List<jSkill_SkillInfo> skillList = [.. ctx.BaseList];
|
||||
|
||||
if (!player.IsBot && ctx.RequiredPermissions.Count != 0)
|
||||
skillList.RemoveAll(s => ctx.RequiredPermissions.TryGetValue(s.Skill, out var perm) && !AdminManager.PlayerHasPermissions(player, perm));
|
||||
|
||||
if (gameMode != Config.GameModes.FullRandom)
|
||||
skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill);
|
||||
|
||||
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<jSkill_SkillInfo>? 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<jSkill_SkillInfo>? value))
|
||||
value.Add(randomSkill);
|
||||
else
|
||||
playersSkills.TryAdd(player.Index, [randomSkill]);
|
||||
}
|
||||
|
||||
return randomSkill;
|
||||
}
|
||||
|
||||
private static bool IsPickStillValid(jSkill_SkillInfo pick, CCSPlayerController player, List<CCSPlayerController> validPlayers, Dictionary<Skills, int> 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<Skills, int> 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<float?>(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<float?>(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<jSkill_SkillInfo> 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<jSkill_SkillInfo> 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<jSkill_SkillInfo> 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<Skills, int> 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<bool>(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<jSkill_SkillInfo> 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<jSkill_SkillInfo> 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<jSkill_SkillInfo> skillList = [.. SkillData.Skills];
|
||||
skillList.RemoveAll(s => s?.Skill == Skills.None);
|
||||
if (!player.IsBot)
|
||||
skillList.RemoveAll(s => !string.IsNullOrEmpty(SkillsInfo.GetValue<string>(s.Skill, "requiredPermission")) && !AdminManager.PlayerHasPermissions(player, SkillsInfo.GetValue<string>(s.Skill, "requiredPermission")));
|
||||
|
||||
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<jSkill_SkillInfo>? skills))
|
||||
{
|
||||
skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill));
|
||||
if (skillList.Count == 0) skills.Clear();
|
||||
}
|
||||
|
||||
var assignmentCounts = new Dictionary<Skills, int>();
|
||||
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<bool>(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)
|
||||
|
|
@ -1604,15 +721,5 @@ namespace src.player
|
|||
}
|
||||
}
|
||||
|
||||
public static void EnableTransmit()
|
||||
{
|
||||
if (!isTransmitRegistered)
|
||||
{
|
||||
Instance?.RegisterListener<CheckTransmit>(CheckTransmit);
|
||||
isTransmitRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime GetFreezeTimeEnd() => freezeTimeEnd;
|
||||
}
|
||||
}
|
||||
692
jRandomSkills - SRC Files/src/player/RoundEvents.cs
Normal file
692
jRandomSkills - SRC Files/src/player/RoundEvents.cs
Normal file
|
|
@ -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<jSkill_SkillInfo> candidates, Dictionary<Skills, int> 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>(CheckTransmit);
|
||||
int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue<Int32>() ?? 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>(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>(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<jSkill_SkillInfo> BaseList { get; init; }
|
||||
public required Dictionary<Skills, string> RequiredPermissions { get; init; }
|
||||
public required HashSet<Skills> NeedsTeammates { get; init; }
|
||||
public required HashSet<Skills> CtOnly { get; init; }
|
||||
public required HashSet<Skills> TOnly { get; init; }
|
||||
public required int TerroristCount { get; init; }
|
||||
public required int CounterTerroristCount { get; init; }
|
||||
}
|
||||
|
||||
private static PickContext BuildPickContext(List<CCSPlayerController> validPlayers)
|
||||
{
|
||||
Dictionary<Skills, string> perms = [];
|
||||
foreach (var s in SkillData.Skills)
|
||||
{
|
||||
if (s == null || s.Skill == Skills.None) continue;
|
||||
string perm = SkillsInfo.GetValue<string>(s.Skill, "requiredPermission");
|
||||
if (!string.IsNullOrEmpty(perm)) perms[s.Skill] = perm;
|
||||
}
|
||||
|
||||
return new PickContext
|
||||
{
|
||||
BaseList = [.. SkillData.Skills.Where(s => s != null && s.Skill != Skills.None)],
|
||||
RequiredPermissions = perms,
|
||||
NeedsTeammates = ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates).Select(s => s.Name)),
|
||||
CtOnly = ToSkillSet(counterterroristSkills.Select(s => s.Name)),
|
||||
TOnly = ToSkillSet(terroristSkills.Select(s => s.Name)),
|
||||
TerroristCount = validPlayers.Count(p => p.Team == CsTeam.Terrorist),
|
||||
CounterTerroristCount = validPlayers.Count(p => p.Team == CsTeam.CounterTerrorist),
|
||||
};
|
||||
}
|
||||
|
||||
private static HashSet<Skills> ToSkillSet(IEnumerable<string> names)
|
||||
{
|
||||
HashSet<Skills> set = [];
|
||||
foreach (var name in names)
|
||||
if (Enum.TryParse<Skills>(name, out var skill)) set.Add(skill);
|
||||
return set;
|
||||
}
|
||||
|
||||
private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, PickContext ctx, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
|
||||
{
|
||||
List<jSkill_SkillInfo> skillList = [.. ctx.BaseList];
|
||||
|
||||
if (!player.IsBot && ctx.RequiredPermissions.Count != 0)
|
||||
skillList.RemoveAll(s => ctx.RequiredPermissions.TryGetValue(s.Skill, out var perm) && !AdminManager.PlayerHasPermissions(player, perm));
|
||||
|
||||
if (gameMode != Config.GameModes.FullRandom)
|
||||
skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill);
|
||||
|
||||
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<jSkill_SkillInfo>? 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<jSkill_SkillInfo>? value))
|
||||
value.Add(randomSkill);
|
||||
else
|
||||
playersSkills.TryAdd(player.Index, [randomSkill]);
|
||||
}
|
||||
|
||||
return randomSkill;
|
||||
}
|
||||
|
||||
private static bool IsPickStillValid(jSkill_SkillInfo pick, CCSPlayerController player, List<CCSPlayerController> validPlayers, Dictionary<Skills, int> 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<Skills, int> 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<float?>(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<float?>(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<jSkill_SkillInfo> 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<jSkill_SkillInfo> 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<jSkill_SkillInfo> 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<Skills, int> 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<bool>(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<jSkill_SkillInfo> 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<jSkill_SkillInfo> 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<jSkill_SkillInfo> skillList = [.. SkillData.Skills];
|
||||
skillList.RemoveAll(s => s?.Skill == Skills.None);
|
||||
if (!player.IsBot)
|
||||
skillList.RemoveAll(s => !string.IsNullOrEmpty(SkillsInfo.GetValue<string>(s.Skill, "requiredPermission")) && !AdminManager.PlayerHasPermissions(player, SkillsInfo.GetValue<string>(s.Skill, "requiredPermission")));
|
||||
|
||||
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<jSkill_SkillInfo>? skills))
|
||||
{
|
||||
skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill));
|
||||
if (skillList.Count == 0) skills.Clear();
|
||||
}
|
||||
|
||||
var assignmentCounts = new Dictionary<Skills, int>();
|
||||
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<bool>(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ namespace src.player.skills
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ namespace src.player.skills
|
|||
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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -110,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()))];
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ namespace src.player.skills
|
|||
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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -130,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()))];
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace src.player.skills
|
|||
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 enemy in enemies)
|
||||
|
|
@ -85,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 = [];
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace src.player.skills
|
|||
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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -122,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())));
|
||||
|
|
|
|||
|
|
@ -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<uint, PlayerSkillInfo> SkillPlayerInfo = [];
|
||||
//private static readonly object setLock = new();
|
||||
|
||||
//public static void LoadSkill()
|
||||
//{
|
||||
// SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(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<float>(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", $"<font color='#FF0000'>{cooldown}</font>")}";
|
||||
//}
|
||||
|
||||
//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<float>(skillName, "amplitude");
|
||||
// shake.Frequency = SkillsInfo.GetValue<float>(skillName, "frequency");
|
||||
// shake.Duration = SkillsInfo.GetValue<float>(skillName, "duration");
|
||||
// shake.Radius = SkillsInfo.GetValue<float>(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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ namespace src.player.skills
|
|||
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;
|
||||
|
|
@ -61,7 +62,7 @@ namespace src.player.skills
|
|||
if (playerInfo == null || playerInfo.Skill != skillName) continue;
|
||||
if (!SkillUtils.HasMenu(player)) continue;
|
||||
|
||||
var enemies = GetSelectableEnemies(player);
|
||||
var enemies = SkillUtils.GetSelectableEnemies(player, true);
|
||||
|
||||
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -119,7 +120,7 @@ namespace src.player.skills
|
|||
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
|
||||
if (playerEvent == null || !playerEvent.IsValid) return;
|
||||
|
||||
var enemies = GetSelectableEnemies(player);
|
||||
var enemies = SkillUtils.GetSelectableEnemies(player, true);
|
||||
if (enemies.Length > 0)
|
||||
{
|
||||
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
|
|
@ -154,15 +155,6 @@ namespace src.player.skills
|
|||
SkillUtils.CloseMenu(player);
|
||||
}
|
||||
|
||||
private static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player)
|
||||
{
|
||||
return [.. PlayerManager.GetTickPlayers()
|
||||
.Where(p => p != null && p.IsValid)
|
||||
.Select(PlayerManager.GetPlayerEvent)
|
||||
.Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None)
|
||||
.Cast<CCSPlayerController>()];
|
||||
}
|
||||
|
||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e0c341", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int moneyPerShot = 50) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
||||
{
|
||||
public int MoneyPerShot { get; set; } = moneyPerShot;
|
||||
|
|
|
|||
|
|
@ -175,14 +175,8 @@ namespace src.player.skills
|
|||
|
||||
private static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player)
|
||||
{
|
||||
return [.. PlayerManager.GetTickPlayers()
|
||||
.Where(p => p != null && p.IsValid)
|
||||
.Select(PlayerManager.GetPlayerEvent)
|
||||
.Where(p => p != null && p.IsValid && p.Team != player.Team && !p.IsHLTV
|
||||
&& p.Team != CsTeam.Spectator && p.Team != CsTeam.None
|
||||
&& p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0
|
||||
&& PlayerManager.GetPlayerByIndex(p.Index)?.Skill != Skills.Chicken)
|
||||
.Cast<CCSPlayerController>()];
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace src.player.skills
|
|||
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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -129,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())));
|
||||
|
|
|
|||
|
|
@ -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<uint, List<Timer>> playerSmokes = [];
|
||||
//private static readonly object setLock = new();
|
||||
|
||||
//public static void LoadSkill()
|
||||
//{
|
||||
// SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(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<int>(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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ namespace src.player.skills
|
|||
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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -151,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())));
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace src.player.skills
|
|||
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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -127,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())));
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ namespace src.player.skills
|
|||
if (playerInfo == null || playerInfo.Skill != skillName) continue;
|
||||
if (!SkillUtils.HasMenu(player)) continue;
|
||||
|
||||
var enemies = GetSelectableEnemies(player);
|
||||
var enemies = SkillUtils.GetSelectableEnemies(player, true);
|
||||
|
||||
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -134,7 +134,7 @@ namespace src.player.skills
|
|||
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
|
||||
if (playerEvent == null || !playerEvent.IsValid) return;
|
||||
|
||||
var enemies = GetSelectableEnemies(player);
|
||||
var enemies = SkillUtils.GetSelectableEnemies(player, true);
|
||||
if (enemies.Length > 0)
|
||||
{
|
||||
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
|
|
@ -169,15 +169,6 @@ namespace src.player.skills
|
|||
SkillUtils.CloseMenu(player);
|
||||
}
|
||||
|
||||
private static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player)
|
||||
{
|
||||
return [.. PlayerManager.GetTickPlayers()
|
||||
.Where(p => p != null && p.IsValid)
|
||||
.Select(PlayerManager.GetPlayerEvent)
|
||||
.Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None)
|
||||
.Cast<CCSPlayerController>()];
|
||||
}
|
||||
|
||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#7ad1c4", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float jumpVelocity = 301f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
||||
{
|
||||
public float JumpVelocity { get; set; } = jumpVelocity;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace src.player.skills
|
|||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace src.player.skills
|
|||
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 = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e?.PlayerPawn?.Value?.Health ?? 0} HP", e.Index.ToString())));
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -86,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())));
|
||||
|
|
|
|||
|
|
@ -128,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())));
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace src.player.skills
|
|||
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 = [.. enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {(e.InGameMoneyServices == null ? 0 : e.InGameMoneyServices.Account + e.InGameMoneyServices.CashSpentThisRound)}$", e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -85,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()))];
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ namespace src.player.skills
|
|||
if (playerInfo == null || playerInfo.Skill != skillName) continue;
|
||||
if (!SkillUtils.HasMenu(player)) continue;
|
||||
|
||||
var enemies = GetSelectableEnemies(player);
|
||||
var enemies = SkillUtils.GetSelectableEnemies(player, true);
|
||||
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ namespace src.player.skills
|
|||
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
|
||||
if (playerEvent == null || !playerEvent.IsValid) return;
|
||||
|
||||
var enemies = GetSelectableEnemies(player);
|
||||
var enemies = SkillUtils.GetSelectableEnemies(player, true);
|
||||
if (enemies.Length > 0)
|
||||
{
|
||||
ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))];
|
||||
|
|
@ -225,17 +225,6 @@ namespace src.player.skills
|
|||
EntityManager.DestroyEntity(volumeIndex);
|
||||
}
|
||||
|
||||
private static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player)
|
||||
{
|
||||
return [.. PlayerManager.GetTickPlayers()
|
||||
.Where(p => p != null && p.IsValid)
|
||||
.Select(PlayerManager.GetPlayerEvent)
|
||||
.Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null
|
||||
&& p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV
|
||||
&& p.Team != CsTeam.Spectator && p.Team != CsTeam.None)
|
||||
.Select(p => p!)];
|
||||
}
|
||||
|
||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5b2c6f", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, string postProcessing = "lighting/postprocessing/effects/death_cam_phase1_low_violence.vpost", float fadeTime = .25f, float minExposure = .5f, float maxExposure = 2f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
||||
{
|
||||
public string PostProcessing { get; set; } = postProcessing;
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ namespace src.player.skills
|
|||
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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -139,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())));
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace src.player.skills
|
|||
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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
|
||||
SkillUtils.UpdateMenu(player, menuItems);
|
||||
|
|
@ -134,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())));
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@ namespace src.player.skills
|
|||
private const Skills skillName = Skills.Tripwire;
|
||||
|
||||
private static readonly ConcurrentDictionary<uint, WireInfo> wires = [];
|
||||
private static readonly ConcurrentDictionary<uint, int> wireCount = [];
|
||||
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> 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);
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
|
|
@ -31,11 +34,22 @@ namespace src.player.skills
|
|||
DestroyWire(wire.BeamIndex);
|
||||
|
||||
wires.Clear();
|
||||
wireCount.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);
|
||||
|
|
@ -43,15 +57,32 @@ namespace src.player.skills
|
|||
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;
|
||||
RemovePlayerWires(player.Index);
|
||||
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 == player.Index)
|
||||
if (key.Owner == ownerIndex)
|
||||
revealed.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +96,7 @@ namespace src.player.skills
|
|||
wires.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
|
||||
wireCount.TryRemove(ownerIndex, out _);
|
||||
SkillPlayerInfo.TryRemove(ownerIndex, out _);
|
||||
}
|
||||
|
||||
private static void DestroyWire(uint beamIndex)
|
||||
|
|
@ -92,12 +123,7 @@ namespace src.player.skills
|
|||
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
|
||||
if (playerEvent == null || !playerEvent.IsValid) return;
|
||||
|
||||
int maxWires = SkillsInfo.GetValue<int>(skillName, "maxWires");
|
||||
if (wireCount.TryGetValue(player.Index, out int placed) && placed >= maxWires)
|
||||
{
|
||||
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("tripwire_limit_info", maxWires));
|
||||
return;
|
||||
}
|
||||
if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo) || !skillInfo.CanUse) return;
|
||||
|
||||
if (!TryPlaceWire(player))
|
||||
{
|
||||
|
|
@ -105,10 +131,29 @@ namespace src.player.skills
|
|||
return;
|
||||
}
|
||||
|
||||
wireCount.AddOrUpdate(player.Index, 1, (_, v) => v + 1);
|
||||
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<float>(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", $"<font color='#FF0000'>{cooldown}</font>")}";
|
||||
}
|
||||
|
||||
private static bool TryPlaceWire(CCSPlayerController player)
|
||||
{
|
||||
var pawn = player.PlayerPawn?.Value;
|
||||
|
|
@ -138,7 +183,9 @@ namespace src.player.skills
|
|||
|
||||
if (EntityManager.OverBudget()) return false;
|
||||
|
||||
var beam = EntityManager.CreateTrackedBeam(player.Index, start, end, Color.FromArgb(255, 255, 40, 40));
|
||||
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<float>(skillName, "wireWidth");
|
||||
|
|
@ -156,6 +203,18 @@ namespace src.player.skills
|
|||
|
||||
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;
|
||||
|
|
@ -243,14 +302,20 @@ namespace src.player.skills
|
|||
public required Vector End { get; set; }
|
||||
}
|
||||
|
||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff3b3b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, float radarDuration = 5f, float triggerRadius = 24f, float wireHeight = 30f, float wireWidth = 1.5f, float maxWallDistance = 400f, int maxWires = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
||||
public class PlayerSkillInfo
|
||||
{
|
||||
public bool CanUse { get; set; }
|
||||
public DateTime Cooldown { 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 int MaxWires { get; set; } = maxWires;
|
||||
public float Cooldown { get; set; } = cooldown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -123,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<CEnvShake>("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<CChicken>("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
|
||||
|
|
|
|||
|
|
@ -103,17 +103,6 @@ namespace src.utils
|
|||
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();
|
||||
|
|
|
|||
|
|
@ -346,6 +346,105 @@ namespace src.utils
|
|||
pendingKillCredits.Clear();
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> 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<Skills> 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.WildThrow
|
||||
];
|
||||
|
||||
private static readonly Dictionary<uint, int> curseCounts = [];
|
||||
private static readonly Dictionary<uint, uint> curserToVictim = [];
|
||||
private static readonly object curseLock = new();
|
||||
|
||||
public static bool IsCurseSkill(Skills skill) => curseSkills.Contains(skill);
|
||||
|
||||
public static void ClearCurses()
|
||||
{
|
||||
lock (curseLock)
|
||||
{
|
||||
curseCounts.Clear();
|
||||
curserToVictim.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CanCurse(uint victimIndex)
|
||||
{
|
||||
int? limit = Config.LoadedConfig.CurseSkillPerPlayer;
|
||||
if (limit == null || limit <= 0) return true;
|
||||
|
||||
lock (curseLock)
|
||||
return !curseCounts.TryGetValue(victimIndex, out int used) || used < limit;
|
||||
}
|
||||
|
||||
public static bool TryClaimCurse(uint curserIndex, uint victimIndex)
|
||||
{
|
||||
int? limit = Config.LoadedConfig.CurseSkillPerPlayer;
|
||||
|
||||
lock (curseLock)
|
||||
{
|
||||
ReleaseCurseLocked(curserIndex);
|
||||
|
||||
curseCounts.TryGetValue(victimIndex, out int used);
|
||||
if (limit != null && limit > 0 && used >= limit) return false;
|
||||
|
||||
curseCounts[victimIndex] = used + 1;
|
||||
curserToVictim[curserIndex] = victimIndex;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReleaseCurse(uint curserIndex)
|
||||
{
|
||||
lock (curseLock) ReleaseCurseLocked(curserIndex);
|
||||
}
|
||||
|
||||
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 [];
|
||||
|
||||
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
|
||||
&& (!respectCurseLimit || CanCurse(p.Index)))
|
||||
.Cast<CCSPlayerController>()];
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -20,6 +20,7 @@
|
|||
"TraceRayBeam": false,
|
||||
"DisableHUDOnDeathPermission": "@jRandomSkills/death",
|
||||
"DisableSkillsOnRoundEnd": false,
|
||||
"CurseSkillPerPlayer": null,
|
||||
"LanguageSystem": {
|
||||
"DefaultLangCode": "en",
|
||||
"DisableGeoLite": false,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
"WireHeight": 30.0,
|
||||
"WireWidth": 1.5,
|
||||
"MaxWallDistance": 400.0,
|
||||
"MaxWires": 2,
|
||||
"Cooldown": 20.0,
|
||||
"NeedsTeammates": false,
|
||||
"DisableOnFreezeTime": false,
|
||||
"OnlyTeam": 0,
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -486,8 +486,7 @@
|
|||
"toxicsmoke_desc": "Your smoke grenades deal damage",
|
||||
|
||||
"tripwire": "Tripwire",
|
||||
"tripwire_desc": "Click [css_useSkill] to string a tripwire between two walls",
|
||||
"tripwire_limit_info": "You can only have {0} tripwires at a time.",
|
||||
"tripwire_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.",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -486,7 +486,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -532,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é",
|
||||
|
|
|
|||
|
|
@ -486,7 +486,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -532,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",
|
||||
|
|
|
|||
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -487,7 +487,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -533,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": "Игрок не найден",
|
||||
|
|
|
|||
|
|
@ -486,8 +486,7 @@
|
|||
"toxicsmoke_desc": "Sis bombaların hasar verir",
|
||||
|
||||
"tripwire": "Tel Tuzağı",
|
||||
"tripwire_desc": "[css_useSkill] ile iki duvar arasına tel gerersin",
|
||||
"tripwire_limit_info": "Aynı anda en fazla {0} tel gerebilirsin.",
|
||||
"tripwire_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ı.",
|
||||
|
|
@ -533,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",
|
||||
|
|
|
|||
|
|
@ -484,7 +484,6 @@
|
|||
|
||||
"tripwire": "tripwire",
|
||||
"tripwire_desc": "tripwire_desc",
|
||||
"tripwire_limit_info": "tripwire_limit_info: {0}",
|
||||
"tripwire_no_wall_info": "tripwire_no_wall_info",
|
||||
"tripwire_placed_info": "tripwire_placed_info",
|
||||
"tripwire_triggered_info": "tripwire_triggered_info: {0}",
|
||||
|
|
@ -530,6 +529,7 @@
|
|||
"duplicate_player": "找到多个同名玩家。",
|
||||
"selectplayerskill_command": "输入 /t",
|
||||
"selectplayerskill_incorrect_enemy_index": "未找到可供选择的玩家。",
|
||||
"curse_limit_info": "curse_limit_info",
|
||||
|
||||
"skill_not_found_setskill": "未找到 CHATCOLORS.RED 技能",
|
||||
"player_not_found_setskill": "未找到 CHATCOLORS.RED 玩家",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue