1096 lines
No EOL
48 KiB
C#
1096 lines
No EOL
48 KiB
C#
using CounterStrikeSharp.API;
|
|
using CounterStrikeSharp.API.Core;
|
|
using CounterStrikeSharp.API.Modules.Cvars;
|
|
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
|
using CounterStrikeSharp.API.Modules.Memory;
|
|
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
|
using CounterStrikeSharp.API.Modules.Timers;
|
|
using CounterStrikeSharp.API.Modules.UserMessages;
|
|
using CounterStrikeSharp.API.Modules.Utils;
|
|
using jRandomSkills.src.utils;
|
|
using src.player;
|
|
using src.player.skills;
|
|
using System.Collections.Concurrent;
|
|
using System.Drawing;
|
|
using System.Globalization;
|
|
using System.Runtime.InteropServices;
|
|
using WASDMenuAPI.Classes;
|
|
using WASDSharedAPI;
|
|
|
|
namespace src.utils
|
|
{
|
|
public static class SkillUtils
|
|
{
|
|
private static Lazy<T?> LazySig<T>(string name, Func<string, T> factory) where T : class =>
|
|
new(() =>
|
|
{
|
|
try { return factory(GameData.GetSignature(name)); }
|
|
catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] gamedata signature '{name}' could not be resolved: {ex.Message}"); return null; }
|
|
});
|
|
|
|
private static readonly Lazy<MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int>?> HEGrenadeProjectile_CreateFunc =
|
|
LazySig<MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int>>("HEGrenadeProjectile_CreateFunc", s => new(s));
|
|
private static readonly Lazy<MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int, int, CSmokeGrenadeProjectile>?> SmokeGrenadeProjectile_CreateFunc =
|
|
LazySig<MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int, int, CSmokeGrenadeProjectile>>("SmokeGrenadeProjectile_CreateFunc", s => new(s));
|
|
private static readonly Lazy<MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int>?> CMolotovProjectile_CreateFunc =
|
|
LazySig<MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int>>("CMolotovProjectile_CreateFunc", s => new(s));
|
|
private static readonly Lazy<MemoryFunctionVoid<nint, float, RoundEndReason, nint, nint>?> TerminateRoundFunc =
|
|
LazySig<MemoryFunctionVoid<nint, float, RoundEndReason, nint, nint>>("CCSGameRules_TerminateRound", s => new(s));
|
|
private static readonly Lazy<MemoryFunctionVoid<CBasePlayerPawn, QAngle>?> SnapViewAngles =
|
|
LazySig<MemoryFunctionVoid<CBasePlayerPawn, QAngle>>("SnapViewAngles", s => new(s));
|
|
// private static readonly int collisionRulesChangedOffset = GameData.GetOffset("CBaseEntity_CollisionRulesChanged");
|
|
|
|
public static void PrintToChat(CCSPlayerController player, string? msg, string border = "tb", string? title = null)
|
|
{
|
|
if (!player.IsValid) return;
|
|
|
|
var config = Config.LoadedConfig.ChatMessage;
|
|
float maxWidth = config.MaxWidth;
|
|
char symbol = config.LineSymbol;
|
|
if (string.IsNullOrEmpty(title)) title = player.GetTranslation("jRandomSkills");
|
|
|
|
if (Illiterate.CheckIlliterateSkill(player))
|
|
msg = Illiterate.GetRandomText(msg);
|
|
|
|
if (border.Contains('t') && config.LineShow)
|
|
player.PrintToChat($" {MeansureString.GetTextDashed($"{(config.TagFormat.Contains("{TAG}") ? config.TagFormat.Replace("{TAG}", title) : $"\u0002◢◆◤ {title} ◥◆◣")}", maxWidth, symbol, config.LineColor)}");
|
|
if (!string.IsNullOrEmpty(msg) && config.InfoMessageShow)
|
|
player.PrintToChat($" {config.InfoSkillColor} {msg.Replace("\x02", config.InfoPlayerNameColor).Replace("\x06", config.InfoSkillColor)}");
|
|
if (border.Contains('b') && config.LineShow)
|
|
player.PrintToChat($" {MeansureString.GetTextDashed("", maxWidth, symbol, config.LineColor)}");
|
|
}
|
|
|
|
public static bool IsFreezeTime()
|
|
{
|
|
return jRandomSkills.Instance?.GameRules?.FreezePeriod == true;
|
|
}
|
|
|
|
public static void RegisterSkill(Skills skill, string color, bool display = true)
|
|
{
|
|
if (!SkillData.Skills.Any(s => s.Skill == skill))
|
|
SkillData.Skills.Add(new jSkill_SkillInfo(skill, color, display));
|
|
}
|
|
|
|
public static void UpdateGrenadeCount(CCSPlayerController player, CsItem item, int ammo)
|
|
{
|
|
string? itemString = EnumUtils.GetEnumMemberAttributeValue(item);
|
|
if (string.IsNullOrWhiteSpace(itemString)) return;
|
|
|
|
if (player == null || !player.IsValid || player.PlayerPawn.Value == null || !player.PlayerPawn.Value.IsValid) return;
|
|
if (player.PlayerPawn.Value.WeaponServices == null) return;
|
|
|
|
var weapon = player.PlayerPawn.Value.WeaponServices.MyWeapons
|
|
.FirstOrDefault(w => w != null && w.IsValid && w.Value != null && w.Value.IsValid && !string.IsNullOrEmpty(w.Value.DesignerName) && w.Value.DesignerName == itemString);
|
|
|
|
if (weapon == null || !weapon.IsValid || weapon.Value == null || !weapon.Value.IsValid) return;
|
|
|
|
weapon.Value.Clip1 = ammo;
|
|
Utilities.SetStateChanged(weapon.Value, "CBasePlayerWeapon", "m_iClip1");
|
|
|
|
if (ammo == 1) return;
|
|
|
|
jRandomSkills.Instance.AddTimer(.1f, () =>
|
|
{
|
|
if (weapon == null || !weapon.IsValid || weapon.Value == null || !weapon.Value.IsValid) return;
|
|
weapon.Value.Clip1 = 1;
|
|
}, TimerFlags.STOP_ON_MAPCHANGE);
|
|
}
|
|
|
|
public static void TryGiveWeapon(CCSPlayerController player, CsItem item, int count = 1, bool existValidator = true)
|
|
{
|
|
string? itemString = EnumUtils.GetEnumMemberAttributeValue(item);
|
|
if (string.IsNullOrWhiteSpace(itemString)) return;
|
|
|
|
if (player == null || !player.IsValid || player.PlayerPawn.Value == null || !player.PlayerPawn.Value.IsValid) return;
|
|
if (player.PlayerPawn.Value.WeaponServices == null) return;
|
|
|
|
var exists = player.PlayerPawn.Value.WeaponServices.MyWeapons
|
|
.FirstOrDefault(w => w != null && w.IsValid && w.Value != null && w.Value.IsValid && w.Value.DesignerName == itemString);
|
|
|
|
if (exists == null || !existValidator)
|
|
for (int i = 0; i < count; i++)
|
|
player.GiveNamedItem(item);
|
|
}
|
|
|
|
public static double GetDistance(Vector vector1, Vector vector2)
|
|
{
|
|
return Math.Sqrt(Math.Pow(vector2.X - vector1.X, 2) + Math.Pow(vector2.Y - vector1.Y, 2) + Math.Pow(vector2.Z - vector1.Z, 2));
|
|
}
|
|
|
|
public static float Distance(this Vector vector1, Vector vector2)
|
|
{
|
|
return (float)GetDistance(vector1, vector2);
|
|
}
|
|
|
|
public static float Dot(this Vector vector1, Vector vector2)
|
|
{
|
|
return (vector1.X * vector2.X) + (vector1.Y * vector2.Y) + (vector1.Z * vector2.Z);
|
|
}
|
|
|
|
public static Vector Normalize(this Vector vector)
|
|
{
|
|
float length = vector.Length();
|
|
if (length > 0)
|
|
return new Vector(vector.X / length, vector.Y / length, vector.Z / length);
|
|
return Vector.Zero;
|
|
}
|
|
|
|
public static string SecondsToTimer(int totalSeconds)
|
|
{
|
|
if (totalSeconds <= 0) return "00:00";
|
|
int minutes = totalSeconds / 60;
|
|
int seconds = totalSeconds % 60;
|
|
return $"{minutes:D2}:{seconds:D2}";
|
|
}
|
|
|
|
public static void SafeKillEntity<T>(uint? index) where T : CBaseEntity
|
|
{
|
|
if (index == null) return;
|
|
EntityManager.DestroyEntity(index.Value);
|
|
}
|
|
|
|
public static bool IsValid<T>(this CHandle<T>? handle) where T : NativeEntity
|
|
{
|
|
return handle != null && handle.IsValid && handle.Value != null;
|
|
}
|
|
|
|
public static bool IsValid(this CBaseEntity? ent)
|
|
{
|
|
return ent != null && ent.IsValid;
|
|
}
|
|
|
|
public static bool CheckPlayer(this CCSPlayerController? player)
|
|
{
|
|
return player != null
|
|
&& player.IsValid
|
|
&& player.PlayerPawn?.Value?.IsValid() == true
|
|
&& player.PawnIsAlive
|
|
&& (player?.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist);
|
|
}
|
|
|
|
|
|
public static Vector GetForwardVector(QAngle angles)
|
|
{
|
|
float pitch = -angles.X * (float)(Math.PI / 180);
|
|
float yaw = angles.Y * (float)(Math.PI / 180);
|
|
|
|
float x = (float)(Math.Cos(pitch) * Math.Cos(yaw));
|
|
float y = (float)(Math.Cos(pitch) * Math.Sin(yaw));
|
|
float z = (float)Math.Sin(pitch);
|
|
|
|
return new Vector(x, y, z);
|
|
}
|
|
|
|
public static void Look(this CBasePlayerPawn pawn, QAngle angle)
|
|
{
|
|
if (pawn == null || !pawn.IsValid) return;
|
|
SnapViewAngles.Value?.Invoke(pawn, angle);
|
|
}
|
|
|
|
public static CBeam? CreateLine(Vector start, Vector end, Color color, uint ownerPlayerIndex = EntityManager.SystemOwnerIndex)
|
|
{
|
|
return EntityManager.CreateTrackedBeam(ownerPlayerIndex, start, end, color);
|
|
}
|
|
|
|
public static void SetPlayerCollisions(CCSPlayerController? player, bool enable)
|
|
{
|
|
return;
|
|
|
|
if (player == null || !player.IsValid) return;
|
|
|
|
var pawn = player.PlayerPawn.Value;
|
|
if (pawn == null || !pawn.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE || pawn.CBodyComponent == null) return;
|
|
|
|
var collision = pawn.Collision;
|
|
if (collision == null) return;
|
|
|
|
var collisionGroup = (byte)(enable ? CollisionGroup.COLLISION_GROUP_PLAYER : CollisionGroup.COLLISION_GROUP_DEBRIS);
|
|
|
|
collision.CollisionGroup = collisionGroup;
|
|
collision.CollisionAttribute.CollisionGroup = collisionGroup;
|
|
Utilities.SetStateChanged(pawn, "CCollisionProperty", "m_collisionAttribute");
|
|
|
|
// CollisionRulesChanged(pawn);
|
|
}
|
|
|
|
//public static void CollisionRulesChanged(CBaseEntity? entity)
|
|
//{
|
|
// if (entity == null || !entity.IsValid || collisionRulesChangedOffset <= 0) return;
|
|
|
|
// var collisionRulesChanged = new VirtualFunctionVoid<nint>(entity.Handle, collisionRulesChangedOffset);
|
|
// collisionRulesChanged.Invoke(entity.Handle);
|
|
//}
|
|
|
|
public static void ApplyScreenColor(CCSPlayerController? player, int r, int g, int b, int a, int duration, int holdTime, int flags = 1)
|
|
{
|
|
if (player == null || !player.IsValid) return;
|
|
|
|
using var msg = UserMessage.FromPartialName("Fade");
|
|
if (msg == null) return;
|
|
int packageColor = (a << 24) | (b << 16) | (g << 8) | r;
|
|
|
|
msg.SetInt("duration", duration);
|
|
msg.SetInt("hold_time", holdTime);
|
|
|
|
msg.SetInt("flags", flags);
|
|
msg.SetInt("color", packageColor);
|
|
|
|
msg.Send(player);
|
|
}
|
|
|
|
public static void ChangePlayerScale(CCSPlayerController? player, float scale)
|
|
{
|
|
if (player == null || !player.IsValid) return;
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
if (playerPawn == null || !playerPawn.IsValid || playerPawn.CBodyComponent == null || playerPawn.CBodyComponent.SceneNode == null) return;
|
|
var skeleton = playerPawn.CBodyComponent.SceneNode.GetSkeletonInstance();
|
|
if (scale <= 0 || skeleton == null) return;
|
|
|
|
skeleton.Scale = scale;
|
|
playerPawn.AcceptInput("SetScale", null, null, scale.ToString(CultureInfo.InvariantCulture));
|
|
|
|
Server.NextWorldUpdate(() =>
|
|
{
|
|
if (playerPawn == null || !playerPawn.IsValid) return;
|
|
Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_CBodyComponent");
|
|
});
|
|
}
|
|
|
|
public static Vector? GetSpawnPointVector(CCSPlayerController player, bool enemySpawn = false)
|
|
{
|
|
if (player == null || player.Team is CsTeam.None or CsTeam.Spectator) return null;
|
|
|
|
CsTeam targetTeam = enemySpawn
|
|
? (player.Team == CsTeam.CounterTerrorist ? CsTeam.Terrorist : CsTeam.CounterTerrorist)
|
|
: player.Team;
|
|
|
|
string spawnPointName = targetTeam == CsTeam.CounterTerrorist
|
|
? "info_player_counterterrorist"
|
|
: "info_player_terrorist";
|
|
|
|
var spawns = Utilities.FindAllEntitiesByDesignerName<SpawnPoint>(spawnPointName).Where(s => s.IsValid && s.Enabled).ToList();
|
|
if (spawns.Count != 0)
|
|
{
|
|
var randomSpawn = spawns[jRandomSkills.Instance.Random.Next(spawns.Count)];
|
|
if (randomSpawn != null && randomSpawn.IsValid && randomSpawn.AbsOrigin != null)
|
|
return new Vector(randomSpawn.AbsOrigin.X, randomSpawn.AbsOrigin.Y, randomSpawn.AbsOrigin.Z);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static bool IsBulletDamage(CTakeDamageInfo? info)
|
|
{
|
|
var ability = info?.Ability?.Value;
|
|
if (ability == null || !ability.IsValid) return false;
|
|
|
|
return FiresBullets(ability.DesignerName);
|
|
}
|
|
|
|
public static HitGroup_t GetHitGroup(CTakeDamageInfo? info)
|
|
{
|
|
if (info == null || info.Handle == nint.Zero) return HitGroup_t.HITGROUP_GENERIC;
|
|
if (!IsBulletDamage(info)) return HitGroup_t.HITGROUP_GENERIC;
|
|
|
|
int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup");
|
|
if (offset <= 0) return HitGroup_t.HITGROUP_GENERIC;
|
|
|
|
nint hitGroupPointer = Marshal.ReadIntPtr(info.Handle, offset);
|
|
if (hitGroupPointer == nint.Zero) return HitGroup_t.HITGROUP_GENERIC;
|
|
|
|
nint hitGroupData = Marshal.ReadIntPtr(hitGroupPointer, 16);
|
|
if (hitGroupData == nint.Zero) return HitGroup_t.HITGROUP_GENERIC;
|
|
|
|
return (HitGroup_t)Marshal.ReadInt32(hitGroupData, 56);
|
|
}
|
|
|
|
public static void CreateHEGrenadeProjectile(Vector pos, QAngle angle, Vector vel, int teamNum)
|
|
{
|
|
HEGrenadeProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 44, teamNum);
|
|
}
|
|
|
|
public static void CreateSmokeGrenadeProjectile(Vector pos, QAngle angle, Vector vel, int teamNum)
|
|
{
|
|
SmokeGrenadeProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 45, teamNum);
|
|
}
|
|
|
|
public static void CreateMolotovProjectile(Vector pos, QAngle angle, Vector vel, int teamNum)
|
|
{
|
|
CMolotovProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 46, teamNum);
|
|
}
|
|
|
|
// True when this hit would be nullified by the server's friendly-fire rules (same-team hit,
|
|
// mp_friendlyfire 0 and mp_teammates_are_enemies 0). The TakeDamage pre-hook still sees the raw
|
|
// damage, so lethal victim-side skills (SecondLife/Phoenix) must skip it — otherwise they "revive"
|
|
// a teammate that was never going to take damage.
|
|
public static bool IsFriendlyFireBlocked(CTakeDamageInfo? info, CCSPlayerPawn? victimPawn)
|
|
{
|
|
if (info == null || victimPawn == null || !victimPawn.IsValid) return false;
|
|
|
|
var attackerEnt = info.Attacker?.Value;
|
|
if (attackerEnt == null || !attackerEnt.IsValid) return false; // world/no attacker -> real damage
|
|
if (attackerEnt.Handle == victimPawn.Handle) return false; // self damage applies
|
|
|
|
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
|
|
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return false; // non-player inflictor
|
|
if (attackerPawn.TeamNum != victimPawn.TeamNum) return false; // enemy -> real damage
|
|
|
|
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
|
|
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
|
|
return !ff && !tae; // same team + FF off -> engine will zero this damage
|
|
}
|
|
|
|
public static float GetTeamDamageMultiplier(Skills skill)
|
|
{
|
|
float reduction = SkillsInfo.GetValue<float>(skill, "dmgReductionForTeamates");
|
|
return 1f - Math.Clamp(reduction, 0f, 1f);
|
|
}
|
|
|
|
private static readonly ConcurrentDictionary<uint, (uint AttackerIndex, string? Weapon, int ExpiryTick)> pendingKillCredits = [];
|
|
|
|
public static void RegisterKillCredit(uint victimIndex, uint attackerIndex, KillfeedIcons? killfeedIcon = null)
|
|
{
|
|
pendingKillCredits[victimIndex] = (attackerIndex, killfeedIcon == null ? null : KillfeedIconsExtensions.ToIcon((KillfeedIcons)killfeedIcon), Server.TickCount + 64);
|
|
}
|
|
|
|
public static bool TryConsumeKillCredit(uint victimIndex, out uint attackerIndex, out string? weapon)
|
|
{
|
|
attackerIndex = 0;
|
|
weapon = null;
|
|
if (!pendingKillCredits.TryRemove(victimIndex, out var credit)) return false;
|
|
if (credit.ExpiryTick < Server.TickCount) return false;
|
|
|
|
attackerIndex = credit.AttackerIndex;
|
|
weapon = credit.Weapon;
|
|
return true;
|
|
}
|
|
|
|
public static void ClearKillCredits()
|
|
{
|
|
pendingKillCredits.Clear();
|
|
}
|
|
|
|
private static readonly HashSet<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", "sg556",
|
|
"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.JetKick, Skills.PrimaryBan, Skills.Thief, Skills.WildThrow
|
|
];
|
|
|
|
private static readonly HashSet<string> curseSkillNames = new(curseSkills.Select(s => s.ToString()), StringComparer.Ordinal);
|
|
|
|
private static readonly Dictionary<uint, int> curseCounts = [];
|
|
private static readonly Dictionary<uint, uint> curserToVictim = [];
|
|
private static readonly object curseLock = new();
|
|
|
|
private static readonly Config.GameModes[] sharedSkillModes =
|
|
[Config.GameModes.TeamSkills, Config.GameModes.SameSkills, Config.GameModes.Debug];
|
|
|
|
public static bool CurseLimitEnabled
|
|
{
|
|
get
|
|
{
|
|
if (Config.LoadedConfig.CurseSkillPerPlayer is not int limit || limit <= 0) return false;
|
|
return Array.IndexOf(sharedSkillModes, (Config.GameModes)Config.LoadedConfig.GameMode) < 0;
|
|
}
|
|
}
|
|
|
|
public static bool IsCurseSkill(Skills skill) => curseSkills.Contains(skill);
|
|
|
|
public static bool IsCurseSkill(string skill) => curseSkillNames.Contains(skill);
|
|
|
|
public static void ClearCurses()
|
|
{
|
|
if (!CurseLimitEnabled) return;
|
|
|
|
lock (curseLock)
|
|
{
|
|
curseCounts.Clear();
|
|
curserToVictim.Clear();
|
|
}
|
|
}
|
|
|
|
public static bool CanCurse(uint victimIndex)
|
|
{
|
|
if (!CurseLimitEnabled) return true;
|
|
int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value;
|
|
|
|
lock (curseLock)
|
|
return !curseCounts.TryGetValue(victimIndex, out int used) || used < limit;
|
|
}
|
|
|
|
public static bool TryClaimCurse(uint curserIndex, uint victimIndex, bool force = false)
|
|
{
|
|
lock (curseLock)
|
|
{
|
|
ReleaseCurseLocked(curserIndex);
|
|
|
|
curseCounts.TryGetValue(victimIndex, out int used);
|
|
|
|
if (CurseLimitEnabled && !force && used >= Config.LoadedConfig.CurseSkillPerPlayer!.Value)
|
|
return false;
|
|
|
|
curseCounts[victimIndex] = used + 1;
|
|
curserToVictim[curserIndex] = victimIndex;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public static void ReleaseCurse(uint curserIndex)
|
|
{
|
|
lock (curseLock) ReleaseCurseLocked(curserIndex);
|
|
}
|
|
|
|
public static uint[] GetCursersOf(uint victimIndex)
|
|
{
|
|
lock (curseLock)
|
|
return [.. curserToVictim.Where(kvp => kvp.Value == victimIndex).Select(kvp => kvp.Key)];
|
|
}
|
|
|
|
public static void ClearCursesFor(uint playerIndex)
|
|
{
|
|
lock (curseLock)
|
|
{
|
|
ReleaseCurseLocked(playerIndex);
|
|
curseCounts.Remove(playerIndex);
|
|
|
|
foreach (var curser in curserToVictim.Where(kvp => kvp.Value == playerIndex).Select(kvp => kvp.Key).ToList())
|
|
curserToVictim.Remove(curser);
|
|
}
|
|
}
|
|
|
|
private static void ReleaseCurseLocked(uint curserIndex)
|
|
{
|
|
if (!curserToVictim.Remove(curserIndex, out uint victimIndex)) return;
|
|
if (!curseCounts.TryGetValue(victimIndex, out int used)) return;
|
|
|
|
if (used <= 1) curseCounts.Remove(victimIndex);
|
|
else curseCounts[victimIndex] = used - 1;
|
|
}
|
|
|
|
public static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player, bool respectCurseLimit = false)
|
|
{
|
|
if (player == null || !player.IsValid) return [];
|
|
|
|
var enemies = GetAliveEnemies(player);
|
|
if (!respectCurseLimit || !CurseLimitEnabled || enemies.Length == 0) return enemies;
|
|
|
|
var withCapacity = enemies.Where(p => CanCurse(p.Index)).ToArray();
|
|
return withCapacity.Length > 0 ? withCapacity : enemies;
|
|
}
|
|
|
|
public static bool AnyCurseCapacity(CCSPlayerController player)
|
|
{
|
|
if (!CurseLimitEnabled) return true;
|
|
if (player == null || !player.IsValid) return true;
|
|
|
|
return GetAliveEnemies(player).Any(p => CanCurse(p.Index));
|
|
}
|
|
|
|
private static CCSPlayerController[] GetAliveEnemies(CCSPlayerController player)
|
|
{
|
|
return [.. PlayerManager.GetTickPlayers()
|
|
.Where(p => p != null && p.IsValid)
|
|
.Select(PlayerManager.GetPlayerEvent)
|
|
.Where(p => p != null && p.IsValid && p.Team != player.Team
|
|
&& p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0
|
|
&& !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None)
|
|
.Cast<CCSPlayerController>()];
|
|
}
|
|
|
|
public static bool TakeHealth(CCSPlayerPawn? pawn, int damage, CCSPlayerController? damageAttacker = null, KillfeedIcons? killfeedIcon = null)
|
|
{
|
|
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE)
|
|
return false;
|
|
|
|
CCSPlayerController? victim = null;
|
|
jSkill_PlayerInfo? playerInfo = null;
|
|
|
|
var player = pawn.Controller.Value;
|
|
if (player != null && player.IsValid)
|
|
{
|
|
victim = player.As<CCSPlayerController>();
|
|
playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
|
if (playerInfo == null) return false;
|
|
|
|
if (playerInfo.Skill == Skills.Jester && Jester.GetJesterInfo(player.Index)?.Active == true)
|
|
return false;
|
|
|
|
if (playerInfo.Skill == Skills.GodMode && GodMode.HaveHodMode(player.Index))
|
|
return false;
|
|
|
|
if (playerInfo.Skill == Skills.Armored)
|
|
damage = (int)Math.Round(damage * (playerInfo.SkillChance ?? 1f));
|
|
}
|
|
|
|
int newHealth = (int)(pawn.Health - damage);
|
|
if (newHealth <= 0 && playerInfo != null)
|
|
{
|
|
if (playerInfo.Skill == Skills.SecondLife && SecondLife.TryConsumeRevive(victim, pawn))
|
|
return true;
|
|
if (playerInfo.Skill == Skills.Phoenix && Phoenix.TryConsumeRevive(victim, pawn))
|
|
return true;
|
|
if (playerInfo.Skill == Skills.ReZombie && ReZombie.TryBecomeZombie(victim, pawn))
|
|
return true;
|
|
}
|
|
|
|
pawn.Health = newHealth;
|
|
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
|
|
|
if (pawn.Health <= 0)
|
|
{
|
|
if (damageAttacker != null && damageAttacker.IsValid && victim != null && victim.IsValid && damageAttacker.Index != victim.Index)
|
|
RegisterKillCredit(victim.Index, damageAttacker.Index, killfeedIcon);
|
|
|
|
Server.NextFrame(() =>
|
|
{
|
|
if (pawn == null || !pawn.IsValid) return;
|
|
pawn?.CommitSuicide(false, true);
|
|
});
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void HideCarriedEntities(CCheckTransmitInfo info, CCSPlayerPawn? pawn)
|
|
{
|
|
if (pawn == null || !pawn.IsValid) return;
|
|
|
|
var weaponServices = pawn.WeaponServices;
|
|
if (weaponServices == null) return;
|
|
|
|
var activeWeapon = weaponServices.ActiveWeapon?.Value;
|
|
if (activeWeapon != null && activeWeapon.IsValid && info.TransmitEntities.Contains(activeWeapon.Index))
|
|
info.TransmitEntities.Remove(activeWeapon.Index);
|
|
|
|
if (weaponServices.MyWeapons == null) return;
|
|
|
|
foreach (var handle in weaponServices.MyWeapons)
|
|
{
|
|
var weapon = handle?.Value;
|
|
if (weapon == null || !weapon.IsValid) continue;
|
|
|
|
if (info.TransmitEntities.Contains(weapon.Index))
|
|
info.TransmitEntities.Remove(weapon.Index);
|
|
}
|
|
}
|
|
|
|
public static void ResetPrintHTML(CCSPlayerController? player)
|
|
{
|
|
if (player == null || !player.IsValid) return;
|
|
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
|
if (playerInfo == null) return;
|
|
playerInfo.PrintHTML = null;
|
|
}
|
|
|
|
public static CTriggerMultiple? CreateTrigger(string name, float radius, Vector pos, uint ownerPlayerIndex = EntityManager.SystemOwnerIndex)
|
|
{
|
|
return EntityManager.CreateTrackedTrigger(ownerPlayerIndex, name, radius, pos);
|
|
}
|
|
|
|
public static void ForceFullUpdate(CCSPlayerController player, List<(uint PlayerIndex, QAngle LastAngle)>? batchList = null, INetworkGameServer? networkGameServer = null)
|
|
{
|
|
if (!Config.LoadedConfig.EnableFullForceUpdate) return;
|
|
if (player == null || !player.IsValid || player.IsBot) return;
|
|
|
|
var pawn = player.PlayerPawn?.Value;
|
|
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
|
|
|
|
QAngle lastAngle = new(pawn.V_angle.X, pawn.V_angle.Y, pawn.V_angle.Z);
|
|
|
|
networkGameServer ??= new INetworkServerService().GetIGameServer();
|
|
|
|
var client = networkGameServer.GetClientBySlot(player.Slot);
|
|
if (client == null) return;
|
|
|
|
client.ForceFullUpdate();
|
|
// Only skip the angle restore when the captured view is a spawn-time (0,0,0) placeholder;
|
|
// a genuine angle with a single zero component (e.g. yaw exactly 0) must still be restored.
|
|
if (lastAngle.X == 0 && lastAngle.Y == 0 && lastAngle.Z == 0) return;
|
|
|
|
uint playerIndex = player.Index;
|
|
|
|
if (batchList != null)
|
|
{
|
|
batchList.Add((playerIndex, lastAngle));
|
|
return;
|
|
}
|
|
|
|
jRandomSkills.Instance.AddTickTimer(3, () =>
|
|
{
|
|
var target = Utilities.GetPlayerFromIndex((int)playerIndex);
|
|
if (target == null || !target.IsValid) return;
|
|
|
|
var targetPawn = target.PlayerPawn?.Value;
|
|
if (targetPawn == null || !targetPawn.IsValid || targetPawn.AbsOrigin == null) return;
|
|
|
|
targetPawn.Look(lastAngle);
|
|
});
|
|
}
|
|
|
|
private static int lastForceFullUpdateAll = int.MinValue;
|
|
|
|
public static void ForceFullUpdateToAll()
|
|
{
|
|
if (!Config.LoadedConfig.EnableFullForceUpdate) return;
|
|
|
|
int tickCount = Server.TickCount;
|
|
if (tickCount == lastForceFullUpdateAll) return;
|
|
|
|
lastForceFullUpdateAll = tickCount;
|
|
var playersToRestore = new List<(uint PlayerIndex, QAngle LastAngle)>();
|
|
|
|
INetworkGameServer networkGameServer = new INetworkServerService().GetIGameServer();
|
|
foreach (var player in Utilities.GetPlayers())
|
|
ForceFullUpdate(player, playersToRestore, networkGameServer);
|
|
|
|
if (playersToRestore.Count <= 0) return;
|
|
|
|
jRandomSkills.Instance.AddTickTimer(3, () =>
|
|
{
|
|
foreach (var item in playersToRestore)
|
|
{
|
|
var target = Utilities.GetPlayerFromIndex((int)item.PlayerIndex);
|
|
if (target == null || !target.IsValid) continue;
|
|
|
|
var targetPawn = target.PlayerPawn?.Value;
|
|
if (targetPawn == null || !targetPawn.IsValid || targetPawn.AbsOrigin == null) continue;
|
|
|
|
targetPawn.Look(item.LastAngle);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static bool SetHealth(CCSPlayerPawn? pawn, int newHealth, int? maxHealth = null)
|
|
{
|
|
if (pawn == null || !pawn.IsValid)
|
|
return false;
|
|
|
|
maxHealth ??= pawn.MaxHealth;
|
|
|
|
if (pawn.Health == maxHealth)
|
|
return false;
|
|
|
|
pawn.Health = Math.Min(newHealth, (int)maxHealth);
|
|
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
|
|
|
pawn.MaxHealth = (int)maxHealth;
|
|
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iMaxHealth");
|
|
|
|
return true;
|
|
}
|
|
|
|
public static bool AddHealth(CCSPlayerPawn? pawn, int extraHealth, int? maxHealth = null)
|
|
{
|
|
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || pawn.Health <= 0)
|
|
return false;
|
|
|
|
maxHealth ??= pawn.MaxHealth;
|
|
|
|
if (pawn.Health == maxHealth)
|
|
return false;
|
|
|
|
int newHealth = (int)(pawn.Health + extraHealth);
|
|
pawn.Health = Math.Min(newHealth, (int)maxHealth);
|
|
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
|
|
|
pawn.MaxHealth = (int)maxHealth;
|
|
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iMaxHealth");
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void RestoreHealth(CCSPlayerController? player)
|
|
{
|
|
if (player == null || !player.IsValid || player.PlayerPawn == null)
|
|
return;
|
|
|
|
CBasePlayerPawn? pawn = player.PlayerPawn.Value;
|
|
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE)
|
|
return;
|
|
|
|
var p = PlayerManager.GetPlayerFromEvent(player);
|
|
if (p == null || !p.IsValid)
|
|
return;
|
|
|
|
pawn.Health = (int)p.PawnHealth;
|
|
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
|
}
|
|
|
|
public static void SetPlayerInvisibility(CCSPlayerController player, float percentInvisibility)
|
|
{
|
|
if (player == null || !player.IsValid || player.PlayerPawn == null)
|
|
return;
|
|
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
if (playerPawn != null)
|
|
{
|
|
var color = Color.FromArgb(Math.Max(255 - (int)(255 * percentInvisibility), 0), 255, 255, 255);
|
|
playerPawn.Render = color;
|
|
Utilities.SetStateChanged(playerPawn, "CBaseModelEntity", "m_clrRender");
|
|
}
|
|
}
|
|
|
|
public static string GetDesignerName(CBasePlayerWeapon? weapon)
|
|
{
|
|
if (weapon == null || !weapon.IsValid) return string.Empty;
|
|
string designerName = weapon.DesignerName;
|
|
ushort index = weapon.AttributeManager.Item.ItemDefinitionIndex;
|
|
|
|
designerName = (designerName, index) switch
|
|
{
|
|
var (name, _) when name.Contains("bayonet") => "weapon_knife",
|
|
("weapon_m4a1", 60) => "weapon_m4a1_silencer",
|
|
("weapon_hkp2000", 61) => "weapon_usp_silencer",
|
|
("weapon_deagle", 64) => "weapon_revolver",
|
|
("weapon_mp7", 23) => "weapon_mp5sd",
|
|
_ => designerName
|
|
};
|
|
|
|
return designerName;
|
|
}
|
|
|
|
private static IWasdMenuManager? GetMenuManager()
|
|
{
|
|
if (jRandomSkills.Instance.MenuManager == null)
|
|
jRandomSkills.Instance.MenuManager = new WasdManager();
|
|
return jRandomSkills.Instance.MenuManager;
|
|
}
|
|
|
|
public static void CloseMenu(CCSPlayerController? player)
|
|
{
|
|
var manager = GetMenuManager();
|
|
if (manager == null) return;
|
|
manager.CloseMenu(player);
|
|
}
|
|
|
|
public static bool HasMenu(CCSPlayerController? player)
|
|
{
|
|
var manager = GetMenuManager();
|
|
if (manager == null) return false;
|
|
return manager.HasMenu(player);
|
|
}
|
|
|
|
public static bool SetMenuPaused(CCSPlayerController? player, bool pause)
|
|
{
|
|
var manager = GetMenuManager();
|
|
if (manager == null) return false;
|
|
return manager.SetMenuPaused(player, pause);
|
|
}
|
|
|
|
private static string GetInvisibleSignature(string id)
|
|
{
|
|
if (string.IsNullOrEmpty(id)) return "";
|
|
var sb = new System.Text.StringBuilder();
|
|
|
|
foreach (char c in id)
|
|
for (int i = 0; i < 8; i++)
|
|
sb.Append(((c >> i) & 1) == 1 ? "\u200B" : "\u200C");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static void UpdateMenu(CCSPlayerController? player, ConcurrentBag<(string, string)> items)
|
|
{
|
|
if (player == null) return;
|
|
|
|
var manager = GetMenuManager();
|
|
if (manager == null) return;
|
|
|
|
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
|
if (playerInfo == null) return;
|
|
|
|
bool isIlliterate = Illiterate.CheckIlliterateSkill(player);
|
|
|
|
Dictionary<string, Action<CCSPlayerController, IWasdMenuOption>> list = [];
|
|
foreach (var item in items)
|
|
{
|
|
string encodedText = isIlliterate
|
|
? System.Net.WebUtility.HtmlEncode(Illiterate.GetRandomText(item.Item1)!)
|
|
: System.Net.WebUtility.HtmlEncode(item.Item1);
|
|
|
|
string uniqueKey = GetInvisibleSignature(item.Item2) + $"\u202A{encodedText}\u202C";
|
|
|
|
list.TryAdd(uniqueKey, (p, option) =>
|
|
{
|
|
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { item.Item2 }]);
|
|
manager.CloseMenu(p);
|
|
});
|
|
}
|
|
|
|
manager.UpdateActiveMenu(player, list);
|
|
}
|
|
|
|
public static void CreateMenu(CCSPlayerController? player, ConcurrentBag<(string, string)> enemies, (string, string, bool)? lastElement = null)
|
|
{
|
|
if (player == null || !player.IsValid) return;
|
|
|
|
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
|
if (playerInfo == null || playerInfo.HideHUD >= Server.TickCount) return;
|
|
|
|
if (player.IsBot)
|
|
{
|
|
var pool = new List<string>();
|
|
|
|
foreach (var enemy in enemies)
|
|
if (!string.IsNullOrEmpty(enemy.Item2))
|
|
pool.Add(enemy.Item2);
|
|
|
|
if (lastElement != null && !string.IsNullOrEmpty(lastElement.Value.Item2))
|
|
pool.Add(lastElement.Value.Item2);
|
|
|
|
if (pool.Count > 0)
|
|
{
|
|
string randomTarget = pool[Random.Shared.Next(pool.Count)];
|
|
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [player, new[] { randomTarget }]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var skillData = SkillData.Skills.FirstOrDefault(s => s.Skill == playerInfo.Skill);
|
|
if (skillData == null) return;
|
|
|
|
var manager = GetMenuManager();
|
|
if (manager == null) return;
|
|
|
|
var config = Config.LoadedConfig.HtmlHudCustomisation;
|
|
var your_skill = player.GetTranslationWithoutIlliterate("your_skill");
|
|
var emptySymbol = $"<font class='fontSize-{(string.IsNullOrEmpty(your_skill) ? "l" : "ml")}'> </font>";
|
|
|
|
string infoLine = string.IsNullOrEmpty(your_skill) || string.IsNullOrEmpty(config.HeaderLineSize)
|
|
? ""
|
|
: $"<font class='fontWeight-Bold fontSize-{config.HeaderLineSize}' color='{config.HeaderLineColor}'>\u202A{your_skill}:\u202C</font><br>";
|
|
|
|
string skillLine = Illiterate.CheckIlliterateSkill(player)
|
|
? $"<font class='fontWeight-Bold fontSize-{config.SkillLineSize}'>\u202A{Illiterate.GetRandomText(player.GetSkillName(skillData.Skill))}\u202C</font><br>"
|
|
: $"<font class='fontWeight-Bold fontSize-{config.SkillLineSize}' color='{skillData.Color}'>\u202A{player.GetSkillName(skillData.Skill)}\u202C</font><br>";
|
|
|
|
var skill_select_info = player.GetTranslation($"{playerInfo.Skill.ToString().ToLowerInvariant()}_select_info");
|
|
string remainingLine = string.IsNullOrWhiteSpace(skill_select_info)
|
|
? ""
|
|
: $"<font class='fontSize-{config.WSADMenuSelectInfoLineSize}' color='{config.WSADMenuSelectInfoLineColor}'>{skill_select_info}</font><br>";
|
|
|
|
var hudContent = infoLine + skillLine + remainingLine;
|
|
|
|
string controllsLine =
|
|
$"{emptySymbol}<font class='fontSize-{config.WSADMenuControllsLineSize}' color='{config.WSADMenuControllsLineColor1}'>{player.GetTranslationWithoutIlliterate($"menu_controlls_scroll")}</font>"
|
|
+ $"<font class='fontSize-{config.WSADMenuControllsLineSize}' color='{config.WSADMenuControllsLineColor2}'>{player.GetTranslationWithoutIlliterate($"menu_controlls_padding")}</font>"
|
|
+ $"<font class='fontSize-{config.WSADMenuControllsLineSize}' color='{config.WSADMenuControllsLineColor3}'>{player.GetTranslationWithoutIlliterate($"menu_controlls_select")}</font>{emptySymbol}";
|
|
|
|
string itemText = $"<font class='fontSize-{config.WSADMenuItemLineSize}' color='{config.WSADMenuItemLineColor}'>{{0}}</font><br>";
|
|
string itemHoverText = $"<font class='fontSize-{config.WSADMenuItemLineSize}'><font color='purple'>[ </font><font color='{config.WSADMenuItemHoverLineColor}'>{{0}}</font><font color='purple'> ]</font></font><br>";
|
|
|
|
bool isIlliterate = Illiterate.CheckIlliterateSkill(player);
|
|
|
|
IWasdMenu menu = manager.CreateMenu(hudContent, itemText, itemHoverText, controllsLine);
|
|
foreach (var enemy in enemies)
|
|
{
|
|
string encodedEnemyName = isIlliterate
|
|
? System.Net.WebUtility.HtmlEncode(Illiterate.GetRandomText(enemy.Item1)!)
|
|
: System.Net.WebUtility.HtmlEncode(enemy.Item1);
|
|
|
|
string uniqueKey = GetInvisibleSignature(enemy.Item2) + $"\u202A{encodedEnemyName}\u202C";
|
|
|
|
menu.Add(uniqueKey, (p, option) =>
|
|
{
|
|
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { enemy.Item2 }]);
|
|
manager.CloseMenu(p);
|
|
});
|
|
}
|
|
|
|
if (lastElement != null)
|
|
{
|
|
string encodedLastElement = isIlliterate
|
|
? System.Net.WebUtility.HtmlEncode(Illiterate.GetRandomText(lastElement.Value.Item1)!)
|
|
: System.Net.WebUtility.HtmlEncode(lastElement.Value.Item1);
|
|
|
|
menu.Add($"\u202A{encodedLastElement}\u202C", (p, option) =>
|
|
{
|
|
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { lastElement.Value.Item2 }]);
|
|
if (lastElement.Value.Item3)
|
|
manager.CloseMenu(p);
|
|
});
|
|
}
|
|
|
|
manager.OpenMainMenu(player, menu);
|
|
}
|
|
|
|
public static void SetTeamScores(short ctScore, short tScore, RoundEndReason roundEndReason)
|
|
{
|
|
if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return;
|
|
UpdateServerTeamScores(ctScore, tScore);
|
|
TerminateRoundFunc.Value?.Invoke(jRandomSkills.Instance.GameRules.Handle, 5f, roundEndReason, 0, 0);
|
|
}
|
|
|
|
public static void TerminateRound(CsTeam winnerTeam)
|
|
{
|
|
if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return;
|
|
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager");
|
|
var ctTeam = teams.FirstOrDefault(t => t.IsValid && (CsTeam)t.TeamNum == CsTeam.CounterTerrorist);
|
|
var tTeams = teams.FirstOrDefault(t => t.IsValid && (CsTeam)t.TeamNum == CsTeam.Terrorist);
|
|
if (ctTeam == null || tTeams == null) return;
|
|
|
|
short ctScore = (short)(winnerTeam == CsTeam.CounterTerrorist ? ctTeam.Score + 1 : ctTeam.Score);
|
|
short tScore = (short)(winnerTeam == CsTeam.Terrorist ? tTeams.Score + 1 : tTeams.Score);
|
|
|
|
UpdateServerTeamScores(ctScore, tScore);
|
|
jRandomSkills.Instance.GameRules?.TerminateRound(5f, winnerTeam == CsTeam.CounterTerrorist ? RoundEndReason.BombDefused : RoundEndReason.TargetBombed);
|
|
}
|
|
|
|
private static void UpdateServerTeamScores(short ctScore, short tScore)
|
|
{
|
|
if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return;
|
|
int totalRoundsPlayed = ctScore + tScore;
|
|
int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue<int>() ?? 24;
|
|
int halfRounds = maxRounds / 2;
|
|
int overtimeMaxRounds = ConVar.Find("mp_overtime_maxrounds")?.GetPrimitiveValue<int>() ?? 6;
|
|
int overtimeLimit = ConVar.Find("mp_overtime_limit")?.GetPrimitiveValue<int>() ?? 1;
|
|
|
|
var gameRulesProxy = jRandomSkills.Instance.GameRules;
|
|
gameRulesProxy.TotalRoundsPlayed = totalRoundsPlayed;
|
|
gameRulesProxy.ITotalRoundsPlayed = totalRoundsPlayed;
|
|
gameRulesProxy.RoundsPlayedThisPhase = totalRoundsPlayed;
|
|
|
|
gameRulesProxy.TeamIntroPeriod = false;
|
|
if (gameRulesProxy.GamePhase == 1 && totalRoundsPlayed < halfRounds)
|
|
{
|
|
gameRulesProxy.GamePhase = 0;
|
|
gameRulesProxy.SwapTeamsOnRestart = true;
|
|
gameRulesProxy.SwitchingTeamsAtRoundReset = true;
|
|
gameRulesProxy.RoundsPlayedThisPhase = 0;
|
|
gameRulesProxy.TeamIntroPeriod = true;
|
|
}
|
|
|
|
if (totalRoundsPlayed < halfRounds)
|
|
gameRulesProxy.GamePhase = 0;
|
|
else if (gameRulesProxy.GamePhase == 0)
|
|
{
|
|
gameRulesProxy.GamePhase = 1;
|
|
gameRulesProxy.SwapTeamsOnRestart = true;
|
|
gameRulesProxy.SwitchingTeamsAtRoundReset = true;
|
|
gameRulesProxy.RoundsPlayedThisPhase = 0;
|
|
gameRulesProxy.TeamIntroPeriod = true;
|
|
}
|
|
|
|
var structOffset = jRandomSkills.Instance.GameRules.Handle + Schema.GetSchemaOffset("CCSGameRules", "m_bMapHasBombZone") + 0x02;
|
|
var matchStruct = Marshal.PtrToStructure<MCCSMatch>(structOffset);
|
|
|
|
matchStruct.m_totalScore = (short)totalRoundsPlayed;
|
|
matchStruct.m_actualRoundsPlayed = (short)totalRoundsPlayed;
|
|
gameRulesProxy.MatchInfoDecidedTime = Server.CurrentTime;
|
|
|
|
matchStruct.m_ctScoreTotal = ctScore;
|
|
gameRulesProxy.AccountCT = ctScore;
|
|
matchStruct.m_terroristScoreTotal = tScore;
|
|
gameRulesProxy.AccountTerrorist = tScore;
|
|
|
|
if (gameRulesProxy.GamePhase == 0)
|
|
{
|
|
matchStruct.m_ctScoreFirstHalf = ctScore;
|
|
matchStruct.m_terroristScoreFirstHalf = tScore;
|
|
}
|
|
else
|
|
{
|
|
matchStruct.m_ctScoreSecondHalf = ctScore;
|
|
matchStruct.m_terroristScoreSecondHalf = tScore;
|
|
}
|
|
|
|
if (totalRoundsPlayed >= maxRounds)
|
|
{
|
|
if (gameRulesProxy.OvertimePlaying == 0)
|
|
{
|
|
gameRulesProxy.OvertimePlaying = 1;
|
|
gameRulesProxy.SwapTeamsOnRestart = true;
|
|
gameRulesProxy.SwitchingTeamsAtRoundReset = true;
|
|
}
|
|
else
|
|
{
|
|
int roundsInOvertime = totalRoundsPlayed - maxRounds;
|
|
if (roundsInOvertime % overtimeMaxRounds == 0)
|
|
{
|
|
int currentOvertime = roundsInOvertime / overtimeMaxRounds;
|
|
if (currentOvertime < overtimeLimit)
|
|
{
|
|
gameRulesProxy.SwapTeamsOnRestart = true;
|
|
gameRulesProxy.SwitchingTeamsAtRoundReset = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
gameRulesProxy.OvertimePlaying = 0;
|
|
|
|
Marshal.StructureToPtr(matchStruct, structOffset, true);
|
|
UpdateClientTeamScores(matchStruct);
|
|
}
|
|
|
|
private static void UpdateClientTeamScores(MCCSMatch match)
|
|
{
|
|
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager");
|
|
var ctTeam = teams.FirstOrDefault(t => t.IsValid && (CsTeam)t.TeamNum == CsTeam.CounterTerrorist);
|
|
var tTeams = teams.FirstOrDefault(t => t.IsValid && (CsTeam)t.TeamNum == CsTeam.Terrorist);
|
|
|
|
if (ctTeam != null && tTeams != null)
|
|
{
|
|
ctTeam.Score = match.m_ctScoreTotal;
|
|
ctTeam.ScoreFirstHalf = match.m_ctScoreFirstHalf;
|
|
ctTeam.ScoreSecondHalf = match.m_ctScoreSecondHalf;
|
|
ctTeam.ScoreOvertime = match.m_ctScoreOvertime;
|
|
Utilities.SetStateChanged(ctTeam, "CTeam", "m_iScore");
|
|
Utilities.SetStateChanged(ctTeam, "CCSTeam", "m_scoreFirstHalf");
|
|
Utilities.SetStateChanged(ctTeam, "CCSTeam", "m_scoreSecondHalf");
|
|
Utilities.SetStateChanged(ctTeam, "CCSTeam", "m_scoreOvertime");
|
|
|
|
tTeams.Score = match.m_terroristScoreTotal;
|
|
tTeams.ScoreFirstHalf = match.m_terroristScoreFirstHalf;
|
|
tTeams.ScoreSecondHalf = match.m_terroristScoreSecondHalf;
|
|
tTeams.ScoreOvertime = match.m_terroristScoreOvertime;
|
|
Utilities.SetStateChanged(tTeams, "CTeam", "m_iScore");
|
|
Utilities.SetStateChanged(tTeams, "CCSTeam", "m_scoreFirstHalf");
|
|
Utilities.SetStateChanged(tTeams, "CCSTeam", "m_scoreSecondHalf");
|
|
Utilities.SetStateChanged(tTeams, "CCSTeam", "m_scoreOvertime");
|
|
}
|
|
}
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct MCCSMatch
|
|
{
|
|
public short m_totalScore;
|
|
public short m_actualRoundsPlayed;
|
|
public short m_nOvertimePlaying;
|
|
public short m_ctScoreFirstHalf;
|
|
public short m_ctScoreSecondHalf;
|
|
public short m_ctScoreOvertime;
|
|
public short m_ctScoreTotal;
|
|
public short m_terroristScoreFirstHalf;
|
|
public short m_terroristScoreSecondHalf;
|
|
public short m_terroristScoreOvertime;
|
|
public short m_terroristScoreTotal;
|
|
public short unknown;
|
|
public int m_phase;
|
|
}
|
|
} |