using System.Drawing; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Core.Attributes.Registration; using CounterStrikeSharp.API.Modules.Commands; using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; namespace Flashlight; [MinimumApiVersion(363)] public class Flashlight : BasePlugin { public override string ModuleAuthor => "creazy.eth"; public override string ModuleName => "Flashlight"; public override string ModuleDescription => "Flashlight for Counter-Strike 2"; public override string ModuleVersion => "0.0.6"; private static string ModuleDisplayName => "Flashlight"; // TODO: Change crouch-tracking to a more elegant solution // TODO: Add config and make light entity values configurable // TODO: Maybe replace light_omni2 with light_rect or something else // FIXED: EyeAngles -> V_angle for CSS API v1.0.363+ compatibility public static Flashlight? Instance { get; private set; } private readonly List _connectedPlayers = new(); private readonly Dictionary _playerUsingFlashlight = new(); private readonly Dictionary _playerIsCrouching = new(); private readonly Dictionary _playerCanToggle = new(); private readonly Dictionary _playerFlashlight = new(); public override void Load(bool hotReload) { Instance = this; LogHelper.LogToConsole(ConsoleColor.Green, $"{ModuleName} v{ModuleVersion} loading..."); RegisterListener(() => { foreach (var player in _connectedPlayers.Where(player => player is { IsValid: true, IsBot: false, PawnIsAlive: true })) { ToggleFlashlight(player); if (_playerCanToggle[player] == false) continue; if ((player.Buttons & PlayerButtons.Use) != 0) { _playerCanToggle[player] = false; var currentPlayer = player; // Capture for closure if (_playerUsingFlashlight[player] == false) { _playerUsingFlashlight[player] = true; Instance?.AddTimer(0.25f, () => { _playerCanToggle[currentPlayer] = true; }); } else { _playerUsingFlashlight[player] = false; Instance?.AddTimer(0.25f, () => { _playerCanToggle[currentPlayer] = true; }); } } if ((player.Buttons & PlayerButtons.Duck) != 0) { _playerIsCrouching[player] = true; } else { _playerIsCrouching[player] = false; } } }); LogHelper.LogToConsole($"{ModuleDisplayName} v{ModuleVersion} loaded!"); } [GameEventHandler] public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo info) { var player = @event.Userid; if (!player.IsValid || player.IsBot) return HookResult.Continue; _connectedPlayers.Add(player); _playerUsingFlashlight[player] = false; _playerIsCrouching[player] = false; _playerCanToggle[player] = true; LogHelper.LogToConsole(ConsoleColor.Green, $"{player.PlayerName} connected"); return HookResult.Continue; } [GameEventHandler] public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info) { var player = @event.Userid; if (!player.IsValid || player.IsBot) return HookResult.Continue; _connectedPlayers.Remove(player); _playerUsingFlashlight.Remove(player); _playerIsCrouching.Remove(player); _playerCanToggle.Remove(player); _playerFlashlight.TryGetValue(player, out var flashlight); flashlight?.Remove(); _playerFlashlight.Remove(player); LogHelper.LogToConsole(ConsoleColor.Green, $"{player.PlayerName} disconnected"); return HookResult.Continue; } [GameEventHandler] public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) { var player = @event.Userid; if (!player.IsValid || player.IsBot) return HookResult.Continue; if (_connectedPlayers.Contains(player) == false) { _connectedPlayers.Add(player); _playerUsingFlashlight[player] = false; _playerIsCrouching[player] = false; _playerCanToggle[player] = true; } _playerUsingFlashlight[player] = false; return HookResult.Continue; } [GameEventHandler] public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info) { var player = @event.Userid; if (!player.IsValid || player.IsBot) return HookResult.Continue; _playerUsingFlashlight[player] = false; return HookResult.Continue; } public void ToggleFlashlight(CCSPlayerController player) { if (_playerUsingFlashlight[player] == false) { if (_playerFlashlight.TryGetValue(player, out var value)) { value.Remove(); _playerFlashlight.Remove(player); } return; } var entity = _playerFlashlight.TryGetValue(player, out var flashlight) ? flashlight : Utilities.CreateEntityByName("light_omni2"); if (entity == null || !entity.IsValid) { LogHelper.LogToConsole("Failed to create entity!"); return; } entity.DirectLight = 3; var pawn = player.PlayerPawn.Value; if (pawn?.AbsOrigin == null || pawn.V_angle == null) { LogHelper.LogToConsole("Failed to get player pawn data!"); return; } entity.Teleport( new Vector( pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + (_playerIsCrouching[player] ? 46.03f : 64.03f) ), pawn.V_angle, pawn.AbsVelocity ); entity.OuterAngle = 45f; entity.Enabled = true; entity.Color = Color.White; entity.ColorTemperature = 6500; entity.Brightness = 1f; entity.Range = 5000f; entity.DispatchSpawn(); _playerFlashlight[player] = entity; } [ConsoleCommand("css_fl_toggle", "Toggles the flashlight")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void ToggleFlashlight(CCSPlayerController caller, CommandInfo? info) { if (!caller.IsValid || !caller.PawnIsAlive) return; if (_playerCanToggle[caller] == false) return; _playerUsingFlashlight[caller] = !_playerUsingFlashlight[caller]; _playerCanToggle[caller] = false; var currentCaller = caller; // Capture for closure Instance?.AddTimer(0.25f, () => { _playerCanToggle[currentCaller] = true; }); } }