mirror of
https://github.com/creazy231/cs2-css-flashlight.git
synced 2026-09-27 20:17:04 +02:00
Add IsValid checks before calling Remove() on flashlight entities to prevent InvalidOperationException when players switch teams. Changes: - Add validity check in ToggleFlashlight() before entity.Remove() - Add validity check in OnPlayerDisconnect() before flashlight.Remove() - Add OnPlayerTeam event handler to clean up flashlight on team switch - Bump version to 0.0.7 This prevents the 'Entity is not valid' crash reported in Issue #2 when players switch teams and their entities become invalid.
258 lines
No EOL
8.7 KiB
C#
258 lines
No EOL
8.7 KiB
C#
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.7";
|
|
|
|
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
|
|
// FIXED: Entity validity check before Remove() to prevent crashes on team switch (Issue #2)
|
|
|
|
public static Flashlight? Instance { get; private set; }
|
|
|
|
private readonly List<CCSPlayerController> _connectedPlayers = new();
|
|
private readonly Dictionary<CCSPlayerController, bool> _playerUsingFlashlight = new();
|
|
private readonly Dictionary<CCSPlayerController, bool> _playerIsCrouching = new();
|
|
private readonly Dictionary<CCSPlayerController, bool> _playerCanToggle = new();
|
|
private readonly Dictionary<CCSPlayerController, COmniLight> _playerFlashlight = new();
|
|
|
|
public override void Load(bool hotReload)
|
|
{
|
|
Instance = this;
|
|
|
|
LogHelper.LogToConsole(ConsoleColor.Green, $"{ModuleName} v{ModuleVersion} loading...");
|
|
|
|
RegisterListener<Listeners.OnTick>(() =>
|
|
{
|
|
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);
|
|
// Fix #2: Check entity validity before removing
|
|
if (flashlight != null && flashlight.IsValid)
|
|
{
|
|
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;
|
|
}
|
|
|
|
[GameEventHandler]
|
|
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
|
|
{
|
|
// Fix #2: Handle team switch - clean up flashlight entity
|
|
var player = @event.Userid;
|
|
|
|
if (!player.IsValid || player.IsBot) return HookResult.Continue;
|
|
|
|
// Turn off flashlight and clean up entity when switching teams
|
|
_playerUsingFlashlight[player] = false;
|
|
|
|
if (_playerFlashlight.TryGetValue(player, out var flashlight))
|
|
{
|
|
if (flashlight != null && flashlight.IsValid)
|
|
{
|
|
flashlight.Remove();
|
|
}
|
|
_playerFlashlight.Remove(player);
|
|
}
|
|
|
|
return HookResult.Continue;
|
|
}
|
|
|
|
public void ToggleFlashlight(CCSPlayerController player)
|
|
{
|
|
if (_playerUsingFlashlight[player] == false)
|
|
{
|
|
if (_playerFlashlight.TryGetValue(player, out var value))
|
|
{
|
|
// Fix #2: Check entity validity before removing
|
|
if (value.IsValid)
|
|
{
|
|
value.Remove();
|
|
}
|
|
_playerFlashlight.Remove(player);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var entity = _playerFlashlight.TryGetValue(player, out var flashlight) ? flashlight : Utilities.CreateEntityByName<COmniLight>("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;
|
|
});
|
|
}
|
|
} |