Updates & Fixes

# Updates & Fixes
- Vote system: Timer now resets after each vote
- Automatic copying of gamedata files from source to server (Thanks to vinicius-trev for the help)
- Improved power assignment at round start
- Observers can now see invisible players.
- Added geolocation support for Turkey and Czechia
- New power: Illiterate
- In development: Smoker skill
- Baseball: Decoy now have the same collision as players (PlayerClip)
- Noclip: Reworked
- Pilot: Currently broken
- Replicator: Added crouching animations for replicas
- SoundMaker: Reworked (automatically play a sound every 2s (only for you))
- Wallhack: Glow disabled for dead players

# TODO
- Add/update descriptions for new/reworked skills
- Fix Pilot, SniperElite
This commit is contained in:
Juzlus 2026-02-14 05:33:17 +01:00
parent f76093ef6e
commit 09803748bc
28 changed files with 691 additions and 295 deletions

View file

@ -1,70 +1,86 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="src\command\Command.cs~RF4f34d1e7.TMP" />
<None Remove="src\player\PlayerOnTick.cs~RF38b6229.TMP" />
<None Remove="src\player\skills\RespWroga.cs~RF528d210.TMP" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.362" />
<PackageReference Include="CS2TraceRay" Version="1.0.9" />
<PackageReference Include="MaxMind.Db" Version="4.3.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
<!-- Relative path to the WASDMenuAPI source code -->
<ItemGroup>
<ProjectReference Include="../WASDMenuAPI - SRC Files/WASDMenuAPI.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup>
<SourceLangDir>$(MSBuildProjectDirectory)\src\lang</SourceLangDir>
<ServerLangDir>$(MSBuildProjectDirectory)\..\jRandomSkills - Server Files\plugins\jRandomSkills\languages</ServerLangDir>
<SourceDLL>$(MSBuildProjectDirectory)\bin\Debug\net8.0</SourceDLL>
<ServerDLL>$(MSBuildProjectDirectory)\..\jRandomSkills - Server Files\plugins\jRandomSkills</ServerDLL>
</PropertyGroup>
<!-- Copy language files to server directory -->
<Target Name="CopyLangFilesToServer" AfterTargets="Build">
<Message Text="Copying language files from $(SourceLangDir) to $(ServerLangDir)" Importance="High" />
<ItemGroup>
<LangFiles Include="$(SourceLangDir)\**\*.*" />
<None Remove="src\command\Command.cs~RF4f34d1e7.TMP" />
<None Remove="src\player\PlayerOnTick.cs~RF38b6229.TMP" />
<None Remove="src\player\skills\RespWroga.cs~RF528d210.TMP" />
</ItemGroup>
<MakeDir Directories="$(ServerLangDir)" Condition="!Exists('$(ServerLangDir)')" />
<Copy SourceFiles="@(LangFiles)" DestinationFiles="@(LangFiles->'$(ServerLangDir)\\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
</Target>
<!-- Copy compiled DLL to server directory -->
<Target Name="CopyDLLToServer" AfterTargets="Build">
<Message Text="Copying compiled DLL from $(SourceDLL) to $(ServerDLL)" Importance="High" />
<ItemGroup>
<CompiledDLL Include="$(SourceDLL)\jRandomSkills.dll" />
<CompiledDLL Include="$(SourceDLL)\WASDMenuAPI.dll" />
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.362" />
<PackageReference Include="CS2TraceRay" Version="1.0.9" />
<PackageReference Include="MaxMind.Db" Version="4.3.4">
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
<MakeDir Directories="$(ServerDLL)" Condition="!Exists('$(ServerDLL)')" />
<Copy SourceFiles="@(CompiledDLL)" DestinationFiles="@(CompiledDLL->'$(ServerDLL)\\%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
</Target>
<!-- Relative path to the WASDMenuAPI source code -->
<ItemGroup>
<ProjectReference Include="../WASDMenuAPI - SRC Files/WASDMenuAPI.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup>
<SourceLangDir>$(MSBuildProjectDirectory)\src\lang</SourceLangDir>
<ServerLangDir>$(MSBuildProjectDirectory)\..\jRandomSkills - Server Files\plugins\jRandomSkills\languages</ServerLangDir>
<SourceDLL>$(MSBuildProjectDirectory)\bin\Debug\net8.0</SourceDLL>
<ServerDLL>$(MSBuildProjectDirectory)\..\jRandomSkills - Server Files\plugins\jRandomSkills</ServerDLL>
<SourceGamedataDir>$(MSBuildProjectDirectory)\src\gamedata</SourceGamedataDir>
<ServerGamedataDir>$(MSBuildProjectDirectory)\..\jRandomSkills - Server Files\gamedata</ServerGamedataDir>
</PropertyGroup>
<!-- Copy language files to server directory -->
<Target Name="CopyLangFilesToServer" AfterTargets="Build">
<Message Text="Copying language files from $(SourceLangDir) to $(ServerLangDir)" Importance="High" />
<ItemGroup>
<LangFiles Include="$(SourceLangDir)\**\*.*" />
</ItemGroup>
<MakeDir Directories="$(ServerLangDir)" Condition="!Exists('$(ServerLangDir)')" />
<Copy SourceFiles="@(LangFiles)" DestinationFiles="@(LangFiles->'$(ServerLangDir)\\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
</Target>
<!-- Copy compiled DLL to server directory -->
<Target Name="CopyDLLToServer" AfterTargets="Build">
<Message Text="Copying compiled DLL from $(SourceDLL) to $(ServerDLL)" Importance="High" />
<ItemGroup>
<CompiledDLL Include="$(SourceDLL)\jRandomSkills.dll" />
<CompiledDLL Include="$(SourceDLL)\WASDMenuAPI.dll" />
<CompiledDLL Include="$(SourceDLL)\MaxMind.Db.dll" />
<CompiledDLL Include="$(SourceDLL)\Newtonsoft.Json.dll" />
</ItemGroup>
<MakeDir Directories="$(ServerDLL)" Condition="!Exists('$(ServerDLL)')" />
<Copy SourceFiles="@(CompiledDLL)" DestinationFiles="@(CompiledDLL->'$(ServerDLL)\\%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
</Target>
<!-- Copy gamedata files to server directory -->
<Target Name="CopyGamedataFilesToServer" AfterTargets="Build">
<Message Text="Copying gamedata files from $(SourceGamedataDir) to $(ServerGamedataDir)" Importance="High" />
<ItemGroup>
<GamedataFiles Include="$(SourceGamedataDir)\**\*.*" />
</ItemGroup>
<MakeDir Directories="$(ServerGamedataDir)" Condition="!Exists('$(ServerGamedataDir)')" />
<Copy SourceFiles="@(GamedataFiles)" DestinationFiles="@(GamedataFiles->'$(ServerGamedataDir)\\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
</Target>
</Project>

View file

@ -3,6 +3,7 @@ using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace src.command
{
@ -10,6 +11,20 @@ namespace src.command
{
private static readonly ConcurrentDictionary<VoteData, byte> votes = [];
private static void StartVoteTimer(VoteData vote, string commandName)
{
vote.ActiveTimer?.Kill();
vote.ActiveTimer = jRandomSkills.Instance.AddTimer(vote.TimeToVote, () =>
{
if (!votes.ContainsKey(vote) || !vote.GetActive()) return;
vote.SetActive(false);
vote.TimeToNextSameVoting = vote.TimeToNextVoting;
Localization.PrintTranslationToChatAll($" {ChatColors.Red}{{0}}", ["vote_timeout"], [commandName]);
});
}
private static VoteData? CreateVote(VoteType voteType, string? args = null)
{
var vote = new VoteData(10,
@ -27,21 +42,15 @@ namespace src.command
foreach (var player in Utilities.GetPlayers())
player.EmitSound("UIPanorama.tab_mainmenu_news");
if (vote == null) return vote;
jRandomSkills.Instance.AddTimer(vote.TimeToVote, () =>
{
if (!votes.ContainsKey(vote) || !vote.GetActive()) return;
vote.SetActive(false);
vote.TimeToNextSameVoting = vote.TimeToNextVoting;
Localization.PrintTranslationToChatAll($" {ChatColors.Red}{{0}}", ["vote_timeout"], [commandName]);
});
StartVoteTimer(vote!, commandName);
float[] times = [vote.TimeToVote, vote.TimeToVote + vote.TimeToNextVoting, vote.TimeToVote + vote.TimeToNextSameVoting];
float[] times = [vote!.TimeToVote, vote.TimeToVote + vote.TimeToNextVoting, vote.TimeToVote + vote.TimeToNextSameVoting];
jRandomSkills.Instance.AddTimer(times.Max(), () =>
{
if (!votes.ContainsKey(vote)) return;
votes.TryRemove(vote, out _);
});
return vote;
}
@ -78,16 +87,21 @@ namespace src.command
private static void CheckVote(VoteData vote)
{
int voted = vote.PlayersVoted.Count;
int playerCount = Utilities.GetPlayers().Where(p => !p.IsBot).ToArray().Length;
int playerCount = Utilities.GetPlayers().Count(p => !p.IsBot);
int playersNeeded = (int)Math.Ceiling(playerCount * (vote.PercentagesToSuccess / 100f));
string commandName = $"!{VoteTypeCommands.GetCommand(vote.Type)?.Replace("css_", "")}{(!string.IsNullOrEmpty(vote?.Args) ? $" {vote?.Args}" : "")}";
if (voted >= playersNeeded)
{
vote!.ActiveTimer?.Kill();
vote.SuccessAction.Invoke();
vote.SetActive(false);
}
else
Localization.PrintTranslationToChatAll($" {ChatColors.Yellow}{{0}} '!{VoteTypeCommands.GetCommand(vote.Type)?.Replace("css_", "")}{(!string.IsNullOrEmpty(vote?.Args) ? $" {vote?.Args}" : "")}': {ChatColors.Green}{voted}/{playersNeeded}", ["vote_vote"]);
{
StartVoteTimer(vote!, commandName);
Localization.PrintTranslationToChatAll($" {ChatColors.Yellow}{{0}} '': {ChatColors.Green}{voted}/{playersNeeded}", ["vote_vote"]);
}
}
}
@ -103,7 +117,7 @@ namespace src.command
public VoteType Type { get; set; } = type;
public string? Args { get; set; } = args;
public ConcurrentDictionary<ulong, byte> PlayersVoted { get; set; } = [];
public Timer? ActiveTimer { get; set; }
private DateTime CreatedTime { get; set; } = DateTime.Now;
public void SetActive(bool active)

View file

@ -0,0 +1,23 @@
{
"SmokeGrenadeProjectile_CreateFunc": {
"signatures": {
"library": "server",
"windows": "48 8B C4 48 89 58 ? 48 89 68 ? 48 89 70 ? 57 41 56 41 57 48 81 EC ? ? ? ? 48 8B B4 24 ? ? ? ? 4D 8B F8",
"linux": "55 4C 89 C1 48 89 E5 41 57 49 89 FF 41 56 45 89 CE"
}
},
"HEGrenadeProjectile_CreateFunc": {
"signatures": {
"library": "server",
"windows": "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC ? 48 8B AC 24 ? ? ? ? 49 8B F8",
"linux": "55 4C 89 C1 48 89 E5 41 57 49 89 D7"
}
},
"Shoot_Secondary": {
"signatures": {
"library": "server",
"windows": "48 89 5C 24 ? 57 48 83 EC 20 44 0F BF C2",
"linux": "55 48 89 E5 41 54 53 48 89 FB 66 83 FE 65"
}
}
}

View file

@ -27,7 +27,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X, Juzlus";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.10.0";
public override string ModuleVersion => "1.2.1";
public override void Load(bool hotReload)
{

View file

@ -100,6 +100,7 @@ public enum Skills
Hermit,
HolyHandGrenade,
Iana,
Illiterate,
Impostor,
InfiniteAmmo,
Jackal,
@ -150,6 +151,7 @@ public enum Skills
Shade,
ShortBomb,
Silent,
Smoker,
SniperElite,
Soldier,
SoundMaker,

View file

@ -8,15 +8,18 @@ using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
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 Timer? setSkillTimer = null;
private static DateTime freezeTimeEnd = DateTime.MinValue;
private static bool isTransmitRegistered = false;
public static readonly jSkill_SkillInfo noneSkill = new(Skills.None, SkillsInfo.GetValue<string>(Skills.None, "color"), false);
@ -39,6 +42,7 @@ namespace src.player
Instance.RegisterEventHandler<EventPlayerConnectFull>(PlayerConnectFull);
Instance.RegisterEventHandler<EventPlayerDisconnect>(PlayerDisconnect);
// Instance.RegisterEventHandler<EventPlayerChat>(PlayerChat);
Instance.RegisterEventHandler<EventPlayerSpawned>(PlayerSpawned);
Instance.RegisterEventHandler<EventRoundStart>(RoundStart);
Instance.RegisterEventHandler<EventRoundEnd>(RoundEnd);
@ -384,6 +388,32 @@ namespace src.player
return HookResult.Continue;
}
private static HookResult PlayerSpawned(EventPlayerSpawned @event, GameEventInfo info)
{
lock (setLock)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return HookResult.Continue;
var skillPlayer = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID);
if (skillPlayer == null) return HookResult.Continue;
if (setSkillTimer != null)
{
skillPlayer.IsDrawing = true;
return HookResult.Continue;
}
skillPlayer.IsDrawing = false;
if (Instance?.GameRules != null &&
Instance?.GameRules.WarmupPeriod == false &&
skillPlayer.Skill == Skills.None &&
skillPlayer.SpecialSkill == Skills.None)
SetRandomSkill(player);
return HookResult.Continue;
}
}
private static HookResult RoundStart(EventRoundStart @event, GameEventInfo info)
{
lock (setLock)
@ -401,7 +431,11 @@ namespace src.player
Instance.RemoveListener<CheckTransmit>(CheckTransmit);
int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue<Int32>() ?? 0;
freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0));
Instance?.AddTimer((Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f, SetSkill);
setSkillTimer?.Kill();
float timeToDraw = (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f;
setSkillTimer = Instance?.AddTimer(timeToDraw, SetSkill);
return HookResult.Continue;
}
}
@ -447,6 +481,7 @@ namespace src.player
private static HookResult RoundEnd(EventRoundEnd @event, GameEventInfo info)
{
Illiterate.Disable();
lock (setLock)
{
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid))
@ -567,6 +602,7 @@ namespace src.player
private static void SetSkill()
{
setSkillTimer = null;
lock (setLock)
{
var validPlayers = Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).ToList();
@ -670,14 +706,20 @@ namespace src.player
skillPlayer.Skill = randomSkill.Skill;
skillPlayer.SpecialSkill = Skills.None;
if (SkillsInfo.GetValue<bool>(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime())
Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () =>
{
if (Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID && p.Skill == randomSkill.Skill) == null) return;
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 (Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID && p.Skill == randomSkill.Skill) == null) return;
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
});
else
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
});
else
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
});
Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\".");
skillPlayer.SkillHudExpired = DateTime.Now.AddSeconds(Config.LoadedConfig.SkillHudDuration);
@ -685,7 +727,7 @@ namespace src.player
if (Config.LoadedConfig.TeamMateSkillChatInfo)
{
Instance?.AddTimer(.5f, () =>
Instance?.AddTimer(.6f, () =>
{
foreach (var teammate in teammates)
{
@ -732,6 +774,7 @@ namespace src.player
var skillPlayer = Instance?.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID);
if (skillPlayer == null) return;
skillPlayer.IsDrawing = false;
if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid)
{
skillPlayer.Skill = Skills.None;
@ -792,13 +835,20 @@ namespace src.player
skillPlayer.Skill = randomSkill.Skill;
skillPlayer.SpecialSkill = Skills.None;
if (SkillsInfo.GetValue<bool>(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime())
Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => {
if (Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID && p.Skill == randomSkill.Skill) == null) return;
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 (Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID && p.Skill == randomSkill.Skill) == null) return;
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
});
else
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
});
else
Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
});
Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\".");
skillPlayer.SkillDescriptionHudExpired = DateTime.Now.AddSeconds(Config.LoadedConfig.SkillDescriptionDuration);
@ -820,6 +870,14 @@ namespace src.player
lock (setLock)
{
if (player == null || !player.IsValid) return;
if (Illiterate.CheckIlliterateSkill(player))
{
headerLine = Illiterate.GetRandomText(headerLine);
centerLine = Illiterate.GetRandomText(centerLine);
extraLine = Illiterate.GetRandomText(extraLine);
}
var config = Config.LoadedConfig.HtmlHudCustomisation;
var emptySymbol = $"<font class='fontSize-{(string.IsNullOrEmpty(headerLine) ? "l" : "ml")}'> </font>";
var emptySymbol2 = $"<font class='fontSize-ml'> </font>";

View file

@ -62,7 +62,7 @@ namespace src.player
infoLine = player.GetTranslation("your_skill");
skillLine = player.GetTranslation("none");
}
else if (skillPlayer.IsDrawing)
else if (skillPlayer.IsDrawing && player.PawnIsAlive)
{
var randomSkill = SkillData.Skills.ToArray()[Instance.Random.Next(SkillData.Skills.Count)];
infoLine = player.GetTranslation("drawing_skill");

View file

@ -51,6 +51,9 @@ namespace src.player.skills
var playerInfo = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID);
if (playerInfo?.Skill != skillName) return;
decoys.TryAdd(decoy, 0);
decoy.Collision.CollisionAttribute.InteractsWith = pawn.Collision.CollisionAttribute.InteractsWith;
decoy.Collision.CollisionGroup = pawn.Collision.CollisionGroup;
}
public static void DecoyStarted(EventDecoyStarted @event)

View file

@ -1,7 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
@ -62,8 +61,19 @@ namespace src.player.skills
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
bool isObservingC4Camouflage = false;
if (targetHandle != nint.Zero)
{
var target = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == targetHandle);
var targetInfo = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == target?.SteamID);
if (targetInfo?.Skill == skillName) isObservingC4Camouflage = true;
}
foreach (var _player in invisiblePlayers.Keys)
if (player.SteamID != _player.SteamID)
if (player.SteamID != _player.SteamID && !isObservingC4Camouflage)
{
var playerPawn = _player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;

View file

@ -151,12 +151,13 @@ namespace src.player.skills
pawn!.CameraServices!.ViewEntity.Raw = player.CameraView.EntityHandle.Raw;
SkillUtils.ApplyScreenColor(player.Player, 0, 0, 255, 20, 100, 1020);
Timer? cameraTimer = null;
ulong playerSteamID = player.Player.SteamID;
ulong? playerSteamID = player.Player?.SteamID;
if (playerSteamID == null) return;
Timer? cameraTimer = null;
cameraTimer = jRandomSkills.Instance.AddTimer(2f, () =>
{
var target = Utilities.GetPlayerFromSteamId(playerSteamID);
var target = Utilities.GetPlayers().FirstOrDefault(p => p.IsValid && p.SteamID == playerSteamID);
if (target == null || !target.IsValid || !target.PawnIsAlive)
{
cameraTimer?.Kill();

View file

@ -112,24 +112,26 @@ namespace src.player.skills
private static void SetUpPostProcessing(CCSPlayerController player, bool turnOff = false)
{
if (player == null || !player.IsValid) return;
ulong playerSteamID = player.SteamID;
ulong? playerSteamID = player?.SteamID;
if (playerSteamID == null) return;
lock (setLock)
{
if (!turnOff)
{
playersInDark.TryAdd(playerSteamID, 0);
playersInDark.TryAdd((ulong)playerSteamID, 0);
ApplyColor(player);
Timer? darkTimer = null;
darkTimer = Instance.AddTimer(5f, () => {
if (!playersInDark.ContainsKey(playerSteamID))
if (!playersInDark.ContainsKey((ulong)playerSteamID))
{
darkTimer?.Kill();
return;
}
var target = Utilities.GetPlayerFromSteamId(playerSteamID);
var target = Utilities.GetPlayers().FirstOrDefault(p => p.IsValid && p.SteamID == playerSteamID);
if (target == null || !target.IsValid)
{
darkTimer?.Kill();
@ -137,18 +139,18 @@ namespace src.player.skills
}
if (target.PawnIsAlive)
ApplyColor(player);
ApplyColor(target);
}, TimerFlags.STOP_ON_MAPCHANGE | TimerFlags.REPEAT);
}
else
{
SkillUtils.ApplyScreenColor(player, r: 0, g: 0, b: 0, a: 0, duration: 200, holdTime: 0);
playersInDark.TryRemove(player.SteamID, out _);
playersInDark.TryRemove((ulong)playerSteamID, out _);
}
}
}
private static void ApplyColor(CCSPlayerController player)
private static void ApplyColor(CCSPlayerController? player)
{
SkillUtils.ApplyScreenColor(player,
r: SkillsInfo.GetValue<int>(skillName, "R"),

View file

@ -1,7 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
@ -65,9 +64,20 @@ namespace src.player.skills
{
foreach (var (info, player) in infoList)
{
if (player == null) continue;
if (player == null || !player.IsValid) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
bool isObservingGhost = false;
if (targetHandle != nint.Zero)
{
var target = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == targetHandle);
var targetInfo = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == target?.SteamID);
if (targetInfo?.Skill == skillName) isObservingGhost = true;
}
foreach (var _player in invisiblePlayers.Keys)
if (player.SteamID != _player.SteamID)
if (player.SteamID != _player.SteamID && !isObservingGhost)
{
var playerPawn = _player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;

View file

@ -154,12 +154,13 @@ namespace src.player.skills
KillClone(playerSkill);
});
Timer? cloneTimer = null;
ulong playerSteamID = player.SteamID;
ulong? playerSteamID = player?.SteamID;
if (playerSteamID == null) return;
Timer? cloneTimer = null;
cloneTimer = jRandomSkills.Instance.AddTimer(2f, () =>
{
var target = Utilities.GetPlayerFromSteamId(playerSteamID);
var target = Utilities.GetPlayers().FirstOrDefault(p => p.IsValid && p.SteamID == playerSteamID);
if (target == null || !target.IsValid || !target.PawnIsAlive)
{
cloneTimer?.Kill();

View file

@ -0,0 +1,77 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
namespace src.player.skills
{
public class Illiterate : ISkill
{
private const Skills skillName = Skills.Illiterate;
private static bool isActive = false;
private static int offset = jRandomSkills.Instance.Random.Next(0, 26);
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
isActive = false;
}
public static void EnableSkill(CCSPlayerController player)
{
isActive = true;
}
public static void Enable()
{
isActive = true;
}
public static void Disable()
{
isActive = false;
}
public static bool CheckIlliterateSkill(CCSPlayerController? player)
{
if (!isActive || player == null || !player.IsValid) return false;
if (player.Team == CsTeam.Spectator) return false;
var playersWithSkill = jRandomSkills.Instance.SkillPlayer.Where(p => p.Skill == skillName).Select(p => p.SteamID);
if (!playersWithSkill.Any()) return false;
return Utilities.GetPlayers().Any(
p => p.IsValid &&
p.PawnIsAlive &&
p.Team != player.Team
&& playersWithSkill.Contains(p.SteamID));
}
public static string? GetRandomText(string? input)
{
if (string.IsNullOrEmpty(input)) return null;
if (Server.TickCount % 64 == 0)
offset = jRandomSkills.Instance.Random.Next(1, 26);
return new string([.. input.Select(c =>
{
if (char.IsDigit(c)) return '?';
if (!char.IsLetter(c)) return c;
char baseChar = char.IsUpper(c) ? 'A' : 'a';
return (char)(baseChar + (c - baseChar + offset) % 26);
})]);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1466F5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float maximumFuel = 150f, float fuelConsumption = .64f, float refuelling = .1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
public float MaximumFuel { get; set; } = maximumFuel;
public float FuelConsumption { get; set; } = fuelConsumption;
public float Refuelling { get; set; } = refuelling;
}
}
}

View file

@ -107,9 +107,9 @@ namespace src.player.skills
if (Config.LoadedConfig.CS2TraceRayDebug)
{
CreateLine(eyePos, endPos, Color.FromArgb(255, 255, 255, 0));
CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.EndPos.X, trace.EndPos.Y, trace.EndPos.Z), Color.FromArgb(255, 255, 0, 0));
CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.Position.X, trace.Position.Y, trace.Position.Z), Color.FromArgb(255, 0, 0, 255));
SkillUtils.CreateLine(eyePos, endPos, Color.FromArgb(255, 255, 255, 0));
SkillUtils.CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.EndPos.X, trace.EndPos.Y, trace.EndPos.Z), Color.FromArgb(255, 255, 0, 0));
SkillUtils.CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.Position.X, trace.Position.Y, trace.Position.Z), Color.FromArgb(255, 0, 0, 255));
if (trace.DidHit())
{
@ -133,24 +133,6 @@ namespace src.player.skills
SkillUtils.TakeHealth(target.PlayerPawn.Value, heavyHit ? Instance.Random.Next(45, 55) : Instance.Random.Next(21, 34));
}
private static void CreateLine(Vector start, Vector end, Color color)
{
CBeam beam = Utilities.CreateEntityByName<CBeam>("beam")!;
if (beam == null) return;
beam.Render = color;
beam.Width = 2.0f;
beam.EndWidth = 2.0f;
beam.Teleport(start);
beam.EndPos.X = end.X;
beam.EndPos.Y = end.Y;
beam.EndPos.Z = end.Z;
beam.DispatchSpawn();
beam.AcceptInput("FollowEntity", beam, null!, "");
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c9f8ff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float maxDistance = 4096f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
public float MaxDistance { get; set; } = maxDistance;

View file

@ -45,9 +45,9 @@ namespace src.player.skills
if (Config.LoadedConfig.CS2TraceRayDebug)
{
CreateLine(eyePos, endPos, Color.FromArgb(255, 255, 255, 0));
CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.EndPos.X, trace.EndPos.Y, trace.EndPos.Z), Color.FromArgb(255, 255, 0, 0));
CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.Position.X, trace.Position.Y, trace.Position.Z), Color.FromArgb(255, 0, 0, 255));
SkillUtils.CreateLine(eyePos, endPos, Color.FromArgb(255, 255, 255, 0));
SkillUtils.CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.EndPos.X, trace.EndPos.Y, trace.EndPos.Z), Color.FromArgb(255, 255, 0, 0));
SkillUtils.CreateLine(new Vector(trace.StartPos.X, trace.StartPos.Y, trace.StartPos.Z), new Vector(trace.Position.X, trace.Position.Y, trace.Position.Z), Color.FromArgb(255, 0, 0, 255));
if (trace.DidHit())
{
@ -70,24 +70,6 @@ namespace src.player.skills
SkillUtils.TryGiveWeapon(player, CsItem.Zeus);
}
private static void CreateLine(Vector start, Vector end, Color color)
{
CBeam beam = Utilities.CreateEntityByName<CBeam>("beam")!;
if (beam == null) return;
beam.Render = color;
beam.Width = 2.0f;
beam.EndWidth = 2.0f;
beam.Teleport(start);
beam.EndPos.X = end.X;
beam.EndPos.Y = end.Y;
beam.EndPos.Z = end.Z;
beam.DispatchSpawn();
beam.AcceptInput("FollowEntity", beam, null!, "");
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#6effc7", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float maxDistance = 4096f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
public float MaxDistance { get; set; } = maxDistance;

View file

@ -1,7 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
@ -57,9 +56,20 @@ namespace src.player.skills
{
foreach (var (info, player) in infoList)
{
if (player == null) continue;
if (player == null || !player.IsValid) continue;
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
bool isObservingNinja = false;
if (targetHandle != nint.Zero)
{
var target = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == targetHandle);
var targetInfo = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == target?.SteamID);
if (targetInfo?.Skill == skillName) isObservingNinja = true;
}
foreach (var _player in invisiblePlayers.Keys)
if (player.SteamID != _player.SteamID)
if (player.SteamID != _player.SteamID && !isObservingNinja)
{
var playerPawn = _player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) continue;

View file

@ -1,9 +1,13 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using static src.jRandomSkills;
using System.Collections.Concurrent;
using CS2TraceRay.Class;
using CS2TraceRay.Struct;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace src.player.skills
{
@ -87,52 +91,139 @@ namespace src.player.skills
public static void UseSkill(CCSPlayerController player)
{
var playerPawn = player.PlayerPawn.Value;
var duration = SkillsInfo.GetValue<float>(skillName, "duration");
if (playerPawn?.CBodyComponent == null) return;
if (SkillPlayerInfo.TryGetValue(player.SteamID, out var skillInfo))
{
if (!player.IsValid || !player.PawnIsAlive) return;
if (skillInfo.IsFlying)
{
StopFlying(player, skillInfo);
return;
}
if (skillInfo.CanUse)
{
var duration = SkillsInfo.GetValue<float>(skillName, "duration");
skillInfo.CanUse = false;
skillInfo.IsFlying = true;
skillInfo.Cooldown = DateTime.Now;
skillInfo.LastPosition = playerPawn.AbsOrigin == null ? null : new Vector(playerPawn.AbsOrigin.X, playerPawn.AbsOrigin.Y, playerPawn.AbsOrigin.Z);
SetNoclip(player, true);
skillInfo.Timer?.Kill();
playerPawn.ActualMoveType = MoveType_t.MOVETYPE_NOCLIP;
Instance.AddTimer(duration, () => {
if (playerPawn == null || !playerPawn.IsValid || !skillInfo.IsFlying) return;
skillInfo.IsFlying = false;
playerPawn.ActualMoveType = MoveType_t.MOVETYPE_WALK;
});
Instance.AddTimer(duration + 4, () => {
if (playerPawn == null || !playerPawn.IsValid || !player.PawnIsAlive || skillInfo.IsFlying) return;
if (skillInfo.LastPosition == null || playerPawn.AbsOrigin == null) return;
skillInfo.IsFlying = false;
var diff = Math.Abs(playerPawn.AbsOrigin.Z - skillInfo.LastPosition.Z);
if (diff > 3000 && playerPawn.AbsOrigin.Z < skillInfo.LastPosition.Z)
playerPawn.Teleport(skillInfo.LastPosition, null, new Vector(0,0,0));
});
}
else if (skillInfo.IsFlying)
{
skillInfo.IsFlying = false;
playerPawn.ActualMoveType = MoveType_t.MOVETYPE_WALK;
Instance.AddTimer(4, () => {
if (playerPawn == null || !playerPawn.IsValid || !player.PawnIsAlive || skillInfo.IsFlying) return;
if (skillInfo.LastPosition == null || playerPawn.AbsOrigin == null) return;
skillInfo.IsFlying = false;
var diff = Math.Abs(playerPawn.AbsOrigin.Z - skillInfo.LastPosition.Z);
if (diff > 3000 && playerPawn.AbsOrigin.Z < skillInfo.LastPosition.Z)
playerPawn.Teleport(skillInfo.LastPosition, null, new Vector(0, 0, 0));
skillInfo.Timer = Instance.AddTimer(duration, () =>
{
StopFlying(player, skillInfo);
});
}
}
}
private static void SetNoclip(CCSPlayerController player, bool noclip = true)
{
if (player == null || !player.IsValid) return;
var playerPawn = player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid || !player.PawnIsAlive) return;
playerPawn.MoveType = noclip ? MoveType_t.MOVETYPE_NOCLIP : MoveType_t.MOVETYPE_WALK;
Schema.SetSchemaValue(playerPawn.Handle, "CBaseEntity", "m_nActualMoveType", (int)playerPawn.MoveType);
Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_MoveType");
}
private static void StopFlying(CCSPlayerController player, PlayerSkillInfo skillInfo)
{
skillInfo.Timer?.Kill();
skillInfo.Timer = null;
if (!skillInfo.IsFlying) return;
skillInfo.IsFlying = false;
if (player == null || !player.IsValid || !player.PawnIsAlive) return;
SetNoclip(player, false);
var playerPawn = player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid || skillInfo.IsFlying) return;
Vector? safePoint = GetCorrectPosition(player, skillInfo);
playerPawn.Teleport(safePoint ?? skillInfo.LastPosition, null, new Vector(0, 0, 0));
}
private static Vector? GetCorrectPosition(CCSPlayerController player, PlayerSkillInfo skillInfo)
{
var playerPawn = player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid || playerPawn.AbsOrigin == null) return null;
Vector currentPos = playerPawn.AbsOrigin;
float offset = 50;
Vector[] checkOffsets =
{
currentPos,
currentPos + new Vector(offset, 0, 10),
currentPos + new Vector(-offset, 0, 10),
currentPos + new Vector(0, offset, 10),
currentPos + new Vector(0, -offset, 10),
currentPos + new Vector(offset, 0, 60),
currentPos + new Vector(-offset, 0, 60),
currentPos + new Vector(0, offset, 60),
currentPos + new Vector(0, -offset, 60),
};
ulong mask = playerPawn.Collision.CollisionAttribute.InteractsWith;
ulong contents = playerPawn.Collision.CollisionGroup;
bool hasGround = false;
foreach (Vector targetPos in checkOffsets)
{
Vector start = new(targetPos.X, targetPos.Y, targetPos.Z + 70);
Vector end = new(targetPos.X, targetPos.Y, targetPos.Z - 1000);
CGameTrace groundTrace = TraceRay.TraceShape(start, end, mask, contents, player);
if (!groundTrace.DidHit()) continue;
hasGround = true;
Vector newPos =
groundTrace.EndPos.Z > targetPos.Z
? new(groundTrace.EndPos.X, groundTrace.EndPos.Y, groundTrace.EndPos.Z)
: targetPos;
if (IsPositionSafe(newPos, player))
return newPos;
}
if (hasGround)
skillInfo.Cooldown = DateTime.Now.AddSeconds(-SkillsInfo.GetValue<float>(skillName, "cooldown") + SkillsInfo.GetValue<float>(skillName, "cooldownWhenStuck"));
return hasGround ? currentPos : null;
}
private static bool IsPositionSafe(Vector pos, CCSPlayerController player)
{
var playerPawn = player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid || playerPawn.AbsOrigin == null) return false;
float footHeight = 0;
float headHeight = 70;
float innerDist = 12;
ulong mask = playerPawn.Collision.CollisionAttribute.InteractsWith;
ulong contents = playerPawn.Collision.CollisionGroup;
Vector s1 = new(pos.X - innerDist, pos.Y - innerDist, pos.Z + footHeight);
Vector e1 = new(pos.X + innerDist, pos.Y + innerDist, pos.Z + headHeight);
CGameTrace t1 = TraceRay.TraceShape(s1, e1, mask, contents, player);
if (t1.DidHit() || t1.AllSolid) return false;
Vector s2 = new(pos.X + innerDist, pos.Y - innerDist, pos.Z + footHeight);
Vector e2 = new(pos.X - innerDist, pos.Y + innerDist, pos.Z + headHeight);
CGameTrace t2 = TraceRay.TraceShape(s2, e2, mask, contents, player);
if (t2.DidHit() || t2.AllSolid) return false;
return true;
}
public class PlayerSkillInfo
{
public ulong SteamID { get; set; }
@ -140,11 +231,13 @@ namespace src.player.skills
public bool IsFlying { get; set; }
public DateTime Cooldown { get; set; }
public Vector? LastPosition { get; set; }
public Timer? Timer { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#44ebd4", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float cooldown = 30f, float duration = 2f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#44ebd4", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float cooldown = 30f, float duration = 2f, float cooldownWhenStuck = 5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
public float Cooldown { get; set; } = cooldown;
public float CooldownWhenStuck { get; set; } = cooldownWhenStuck;
public float Duration { get; set; } = duration;
}
}

View file

@ -9,7 +9,6 @@ namespace src.player.skills
public class PawelJumper : ISkill
{
private const Skills skillName = Skills.PawelJumper;
private static readonly PlayerFlags[] LF = new PlayerFlags[64];
private static readonly int?[] J = new int?[64];
private static readonly PlayerButtons[] LB = new PlayerButtons[64];
@ -71,7 +70,6 @@ namespace src.player.skills
playerPawn.AbsVelocity.Z = 300;
}
LF[player.Slot] = flags;
LB[player.Slot] = buttons;
}

View file

@ -38,6 +38,7 @@ namespace src.player.skills
{
SteamID = player.SteamID,
Fuel = SkillsInfo.GetValue<float>(skillName, "maximumFuel"),
LastButtons = 0
});
}
@ -49,16 +50,37 @@ namespace src.player.skills
private static void HandlePilot(CCSPlayerController player)
{
var playerPawn = player.PlayerPawn.Value;
if (playerPawn == null || !playerPawn.IsValid) return;
if (!PlayerPilotInfo.TryGetValue(player.SteamID, out var pilotInfo)) return;
var flags = (PlayerFlags)playerPawn.Flags;
var buttons = player.Buttons;
bool isJumpDown = (playerPawn.MovementServices?.Buttons?.ButtonStates[0] & (ulong)PlayerButtons.Jump) != 0 || (buttons & PlayerButtons.Jump) != 0;
bool wasJumpDown = (pilotInfo.LastButtons & PlayerButtons.Jump) != 0;
bool jumpPressed = (isJumpDown && !wasJumpDown)
|| (playerPawn.MovementServices?.QueuedButtonChangeMask & (ulong)PlayerButtons.Jump) != 0;
bool isOnGround = (flags & PlayerFlags.FL_ONGROUND) != 0;
bool inUse = jumpPressed && !isOnGround;
var maximumFuel = SkillsInfo.GetValue<float>(skillName, "maximumFuel");
if (PlayerPilotInfo.TryGetValue(player.SteamID, out var pilotInfo))
{
pilotInfo.Fuel = Math.Min(Math.Max(0, pilotInfo.Fuel - (buttons.HasFlag(PlayerButtons.Use) ? SkillsInfo.GetValue<float>(skillName, "fuelConsumption") : -SkillsInfo.GetValue<float>(skillName, "refuelling"))), maximumFuel);
if (buttons.HasFlag(PlayerButtons.Use))
if (pilotInfo.Fuel > 0 && player.PlayerPawn.Value != null && player.PlayerPawn.Value.IsValid && !player.PlayerPawn.Value.IsDefusing)
ApplyPilotEffect(player);
UpdateHUD(player, pilotInfo);
}
pilotInfo.Fuel = Math.Min(
Math.Max(
0,
pilotInfo.Fuel - (inUse
? SkillsInfo.GetValue<float>(skillName, "fuelConsumption")
: -SkillsInfo.GetValue<float>(skillName, "refuelling"))),
maximumFuel);
pilotInfo.LastButtons = buttons;
if (inUse && pilotInfo.Fuel > 0)
ApplyPilotEffect(player);
UpdateHUD(player, pilotInfo);
}
private static void UpdateHUD(CCSPlayerController player, Pilot_PlayerInfo pilotInfo)
@ -121,6 +143,7 @@ namespace src.player.skills
{
public ulong SteamID { get; set; }
public float Fuel { get; set; }
public PlayerButtons LastButtons { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#1466F5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float maximumFuel = 150f, float fuelConsumption = .64f, float refuelling = .1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)

View file

@ -99,9 +99,6 @@ namespace src.player.skills
float distance = 40;
Vector pos = playerPawn.AbsOrigin + SkillUtils.GetForwardVector(playerPawn.AbsRotation) * distance;
if (((PlayerFlags)playerPawn.Flags).HasFlag(PlayerFlags.FL_DUCKING))
pos.Z -= 19;
replica.Flags = playerPawn.Flags;
replica.Flags |= (uint)Flags_t.FL_DUCKING;
@ -109,6 +106,13 @@ namespace src.player.skills
replica.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(replica.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
replica.SetModel(playerPawn!.CBodyComponent!.SceneNode!.GetSkeletonInstance().ModelState.ModelName);
replica.Entity!.Name = replica.Globalname = $"Replica_{Server.TickCount}_{(player.Team == CsTeam.CounterTerrorist ? "CT" : "TT")}";
replica.UseAnimGraph = false;
string animName = "idle_for_turns_stand_pistol";
if (((PlayerFlags)playerPawn.Flags).HasFlag(PlayerFlags.FL_DUCKING))
animName = "idle_for_turns_crouch_pistol";
replica.AcceptInput("SetAnimation", value: animName);
replica.Teleport(pos, playerPawn.AbsRotation, null);
replica.DispatchSpawn();
}

View file

@ -0,0 +1,75 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
namespace src.player.skills
{
public class Smoker : ISkill
{
private const Skills skillName = Skills.Smoker;
private readonly static ConcurrentDictionary<uint, int> smokes = [];
private static readonly object setLock = new();
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void NewRound()
{
lock (setLock)
smokes.Clear();
}
public static void EnableSkill(CCSPlayerController player)
{
SkillUtils.TryGiveWeapon(player, CsItem.SmokeGrenade);
}
public static void SmokegrenadeDetonate(EventSmokegrenadeDetonate @event)
{
Server.PrintToChatAll("BOOM");
var player = @event.Userid;
if (player == null || !player.IsValid) return;
var playerInfo = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID);
if (playerInfo?.Skill != skillName) return;
var smoke = Utilities.GetEntityFromIndex<CSmokeGrenadeProjectile>(@event.Entityid);
if (smoke == null || !smoke.IsValid) return;
smokes.TryAdd(smoke.Index, Server.TickCount);
// smoke.SmokeEffectTickBegin = Server.TickCount - ((19 - 15) * 64);
smoke.NextThinkTick = 0;
// Utilities.SetStateChanged(smoke, "CSmokeGrenadeProjectile", "m_nSmokeEffectTickBegin");
Utilities.SetStateChanged(smoke, "CBaseEntity", "m_nNextThinkTick");
}
public static void SmokegrenadeExpired(EventSmokegrenadeExpired @event)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return;
var playerInfo = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID);
if (playerInfo?.Skill != skillName) return;
Vector pos = new(@event.X, @event.Y, @event.Z);
SkillUtils.CreateSmokeGrenadeProjectile(pos, new QAngle(0, 0, 0), new Vector(0, 0, 0), player.TeamNum);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#b5ab8f", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "") : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
}
}
}

View file

@ -1,18 +1,22 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
using static src.jRandomSkills;
using System.Collections.Concurrent;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
namespace src.player.skills
{
public class SoundMaker : ISkill
{
private const Skills skillName = Skills.SoundMaker;
private static readonly ConcurrentDictionary<ulong, PlayerSkillInfo> SkillPlayerInfo = [];
private static readonly ConcurrentDictionary<ulong, byte> SkillPlayerInfo = [];
private static readonly object setLock = new();
private const string soundEventName = "Hostage.Pain";
private const uint soundEventHash = 1876781570;
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
@ -26,12 +30,7 @@ namespace src.player.skills
public static void EnableSkill(CCSPlayerController player)
{
SkillPlayerInfo.TryAdd(player.SteamID, new PlayerSkillInfo
{
SteamID = player.SteamID,
CanUse = true,
Cooldown = DateTime.MinValue,
});
SkillPlayerInfo.TryAdd(player.SteamID, 0);
}
public static void DisableSkill(CCSPlayerController player)
@ -49,78 +48,40 @@ namespace src.player.skills
SkillPlayerInfo.TryRemove(player.SteamID, out _);
}
public static void PlayerMakeSound(UserMessage um)
{
var soundevent = um.ReadUInt("soundevent_hash");
if (soundevent != soundEventHash) return;
var userIndex = um.ReadUInt("source_entity_index");
if (userIndex == 0) return;
var sourcePlayer = Utilities.GetPlayers().FirstOrDefault(p => p.Pawn?.Value != null && p.Pawn.Value.IsValid && p.Pawn.Value.Index == userIndex);
if (sourcePlayer == null || !sourcePlayer.IsValid) return;
var toRemove = um.Recipients.Where(r =>
{
if (r.Team == sourcePlayer.Team) return true;
if (SkillPlayerInfo.ContainsKey(r.SteamID)) return false;
return true;
}).ToList();
foreach (var player in toRemove)
um.Recipients.Remove(player);
}
public static void OnTick()
{
foreach (var player in Utilities.GetPlayers())
{
var playerInfo = Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID);
if (playerInfo?.Skill == skillName)
if (SkillPlayerInfo.TryGetValue(player.SteamID, out var skillInfo))
UpdateHUD(player, skillInfo);
}
if (Server.TickCount % (60 * SkillsInfo.GetValue<int>(skillName, "cooldown")) != 0) return;
foreach (var player in Utilities.GetPlayers()
.Where(p => p != null && p.IsValid && p.PawnIsAlive && p.PlayerPawn.Value != null && p.PlayerPawn.Value.IsValid))
player.PlayerPawn.Value!.EmitSound(soundEventName, volume: 1f);
}
private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e3ed8c", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int cooldown = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
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;
}
var playerInfo = Instance.SkillPlayer.FirstOrDefault(s => s.SteamID == player?.SteamID);
if (playerInfo == null) return;
if (cooldown == 0)
playerInfo.PrintHTML = null;
else
playerInfo.PrintHTML = $"{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.SteamID, out var skillInfo))
{
if (!player.IsValid || !player.PawnIsAlive) return;
if (skillInfo.CanUse)
{
skillInfo.CanUse = false;
skillInfo.Cooldown = DateTime.Now;
MakeSound(player);
}
}
}
private static void MakeSound(CCSPlayerController player)
{
foreach (var enemy in Utilities.GetPlayers().Where(p => p.Team != player.Team))
if (enemy != null && enemy.IsValid && enemy.PawnIsAlive && enemy.PlayerPawn.Value != null && enemy.PlayerPawn.Value.IsValid)
enemy.PlayerPawn.Value.EmitSound(
enemy.Team == CsTeam.CounterTerrorist
? SkillsInfo.GetValue<string>(skillName, "CTSoundEvent")
: SkillsInfo.GetValue<string>(skillName, "TSoundEvent")
, volume: 1f);
}
public class PlayerSkillInfo
{
public ulong SteamID { get; set; }
public bool CanUse { get; set; }
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e3ed8c", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float cooldown = 5f, string ctSoundEvent = "c4.disarmstart", string tSoundEvent = "C4.PlantSoundB") : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
public float Cooldown { get; set; } = cooldown;
public string CTSoundEvent { get; set; } = ctSoundEvent;
public string TSoundEvent { get; set; } = tSoundEvent;
public int Cooldown { get; set; } = cooldown;
}
}
}

View file

@ -95,10 +95,6 @@ namespace src.player.skills
var trigger = SkillUtils.CreateTrigger(triggerName, SkillsInfo.GetValue<float>(skillName, "smokeRadius"), new Vector(@event.X, @event.Y, @event.Z));
if (trigger == null) return;
triggers.TryAdd(trigger, 0);
new VirtualFunctionVoid<CBaseEntity>(trigger.Handle, 153);
}
public static void SmokegrenadeExpired(EventSmokegrenadeExpired @event)
@ -138,7 +134,7 @@ namespace src.player.skills
}
}
public class SkillConfig(Skills skill = skillName, bool active = false, string color = "#507529", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int smokeDamage = 2, float smokeRadius = 180) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int smokeDamage = 2, float smokeRadius = 180) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission)
{
public int SmokeDamage { get; set; } = smokeDamage;
public float SmokeRadius { get; set; } = smokeRadius;

View file

@ -13,7 +13,7 @@ namespace src.player.skills
{
private const Skills skillName = Skills.Wallhack;
private static readonly ConcurrentDictionary<ulong, byte> playersInAction = [];
private static readonly ConcurrentBag<(CDynamicProp, CDynamicProp, CsTeam)> glows = [];
private static readonly ConcurrentBag<(CDynamicProp, CDynamicProp, CsTeam, uint)> glows = [];
public static void LoadSkill()
{
@ -32,8 +32,11 @@ namespace src.player.skills
foreach (var glow in glows)
{
if (glow.Item3 != player.Team && (playerInfo?.Skill == skillName || (observerInfo != null && observerInfo?.Skill == skillName)))
continue;
var enemy = Utilities.GetPlayers().FirstOrDefault(e => e.IsValid && e.Index == glow.Item4);
if (enemy != null && enemy.PawnIsAlive)
if (glow.Item3 != player.Team && (playerInfo?.Skill == skillName || (observerInfo != null && observerInfo?.Skill == skillName)))
continue;
var glowEntity1 = Utilities.GetEntityFromIndex<CBaseEntity>((int)glow.Item1.Index);
if (glowEntity1 == null || !glowEntity1.IsValid) continue;
@ -110,7 +113,7 @@ namespace src.player.skills
modelRelay.AcceptInput("FollowEntity", enemyPawn, modelRelay, "!activator");
modelGlow.AcceptInput("FollowEntity", modelRelay, modelGlow, "!activator");
glows.Add((modelRelay, modelGlow, enemy.Team));
glows.Add((modelRelay, modelGlow, enemy.Team, enemy.Index));
}
}

View file

@ -1,5 +1,7 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using static src.jRandomSkills;
namespace src.utils
@ -69,6 +71,9 @@ namespace src.utils
public class SettingsModel
{
[DisplayName("Game Mode")]
[Description("TESTfafw..")]
[Range(0, 5)]
public int GameMode { get; set; }
public bool YourSkillChatInfo { get; set; }
public bool KillerSkillChatInfo { get; set; }
@ -120,6 +125,8 @@ namespace src.utils
new LanguageInfo("PT-BR, PT, BR, AO, CV, GW, MZ, ST, TL", "pt-br"),
new LanguageInfo("FR, MC, HT", "fr"),
new LanguageInfo("DE, AT, CH, LI, LU, BE", "de"),
new LanguageInfo("TR", "tr"),
new LanguageInfo("CZ", "cs"),
new LanguageInfo("PL", "pl"),
new LanguageInfo("EN, GB, US", "en")
]

View file

@ -6,6 +6,7 @@ using Newtonsoft.Json;
using System.Net;
using System.Collections.Concurrent;
using src.player;
using src.player.skills;
namespace src.utils
{
@ -80,7 +81,7 @@ namespace src.utils
}
var value = Math.Round((double)(chance ?? 1), 2);
var skillNameText = GetTranslation(skill.ToString().ToLower(), langCode, value);
var skillNameText = GetTranslation(skill.ToString().ToLower(), langCode, player, value);
if (skillNameText.Contains('%')) skillNameText = skillNameText.Replace(value.ToString(), Math.Round(value * 100, 0).ToString());
return skillNameText;
}
@ -104,7 +105,7 @@ namespace src.utils
var skillName = $"{skill.ToString().ToLower()}_desc2";
var value = Math.Round((double)(chance ?? 1), 2);
var desc2 = GetTranslation(skillName, langCode, value);
var desc2 = GetTranslation(skillName, langCode, player, value);
var skilLDescription = desc2 == skillName
? player.GetTranslation($"{skill.ToString().ToLower()}_desc")
@ -127,7 +128,7 @@ namespace src.utils
for (int i = 0; i < key.Length; i++)
{
object[] currentArgs = args != null && i < args.Length ? args[i] : [];
string translation = GetTranslation(key[i], langCode, currentArgs);
string translation = GetTranslation(key[i], langCode, null, currentArgs);
translations.Add(translation);
}
player.PrintToChat(string.Format(message, [.. translations]));
@ -137,10 +138,10 @@ namespace src.utils
public static string GetTranslation(this CCSPlayerController player, string key, params object[] args)
{
string langCode = GetLangCode(player);
return GetTranslation(key, langCode, args);
return GetTranslation(key, langCode, player, args);
}
public static string GetTranslation(string key, string? langCode = null, params object[] args)
public static string GetTranslation(string key, string? langCode = null, CCSPlayerController? player = null, params object[] args)
{
langCode ??= defaultLangCode;
if (_translations.TryGetValue(langCode, out var langDict) && langDict.TryGetValue(key, out var translation))
@ -152,9 +153,14 @@ namespace src.utils
output = output.Replace("CHATCOLORS.RED", ChatColors.Red.ToString());
if (Config.LoadedConfig.AlternativeSkillButton != null)
output = output.Replace("css_useSkill", $"css_useSkill/{Config.LoadedConfig.AlternativeSkillButton}");
if (Illiterate.CheckIlliterateSkill(player))
return Illiterate.GetRandomText(output);
return output;
}
if (langCode != defaultLangCode)
return GetTranslation(key, null, player, args);
return key;
}
@ -188,7 +194,10 @@ namespace src.utils
if (string.IsNullOrEmpty(playerIP)) return null;
if (!File.Exists(geoliteFilePath)) return null;
using var reader = new Reader(geoliteFilePath);
var ip = IPAddress.Parse(playerIP);
if (!IPAddress.TryParse(playerIP, out var ip))
return null;
var data = reader.Find<ConcurrentDictionary<string, object>>(ip);
if (data == null || data.IsEmpty) return null;

View file

@ -9,6 +9,7 @@ using CounterStrikeSharp.API.Modules.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;
@ -19,15 +20,21 @@ namespace src.utils
public static class SkillUtils
{
private static readonly MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int> HEGrenadeProjectile_CreateFunc = new(GameData.GetSignature("HEGrenadeProjectile_CreateFunc"));
private static readonly MemoryFunctionWithReturn<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int> SmokeGrenadeProjectile_CreateFunc = new(GameData.GetSignature("SmokeGrenadeProjectile_CreateFunc"));
private static readonly MemoryFunctionVoid<nint, float, RoundEndReason, nint, nint> TerminateRoundFunc = new(GameData.GetSignature("CCSGameRules_TerminateRound"));
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)
@ -86,9 +93,29 @@ namespace src.utils
return new Vector(x, y, z);
}
public static void ApplyScreenColor(CCSPlayerController player, int r, int g, int b, int a, int duration, int holdTime, int flags = 1)
public static CBeam? CreateLine(Vector start, Vector end, Color color)
{
CBeam beam = Utilities.CreateEntityByName<CBeam>("beam")!;
if (beam == null) return null;
beam.Render = color;
beam.Width = 2.0f;
beam.EndWidth = 2.0f;
beam.Teleport(start);
beam.EndPos.X = end.X;
beam.EndPos.Y = end.Y;
beam.EndPos.Z = end.Z;
beam.DispatchSpawn();
return beam;
}
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;
@ -142,6 +169,11 @@ namespace src.utils
HEGrenadeProjectile_CreateFunc.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.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 45, teamNum);
}
public static void TakeHealth(CCSPlayerPawn? pawn, int damage)
{
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE)
@ -280,9 +312,11 @@ namespace src.utils
var playerInfo = jRandomSkills.Instance.SkillPlayer.FirstOrDefault(p => p.SteamID == player.SteamID);
if (playerInfo == null) return;
bool isIlliterate = Illiterate.CheckIlliterateSkill(player);
Dictionary<string, Action<CCSPlayerController, IWasdMenuOption>> list = [];
foreach (var item in items)
list.TryAdd(item.Item1, (p, option) =>
list.TryAdd(isIlliterate ? Illiterate.GetRandomText(item.Item1) : item.Item1, (p, option) =>
{
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { item.Item2 }]);
manager.CloseMenu(p);
@ -329,15 +363,17 @@ namespace src.utils
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)
menu.Add(enemy.Item1, (p, option) =>
menu.Add(isIlliterate ? Illiterate.GetRandomText(enemy.Item1) : enemy.Item1, (p, option) =>
{
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { enemy.Item2 }]);
manager.CloseMenu(p);
});
if (lastElement != null)
menu.Add(lastElement.Value.Item1, (p, option) =>
menu.Add(isIlliterate ? Illiterate.GetRandomText(lastElement.Value.Item1) : lastElement.Value.Item1, (p, option) =>
{
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { lastElement.Value.Item2 }]);
if (lastElement.Value.Item3)