mirror of
https://github.com/creazy231/cs2-css-flashlight.git
synced 2026-09-27 20:17:04 +02:00
Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b43f9fdea3 |
5 changed files with 217 additions and 48 deletions
|
|
@ -70,6 +70,79 @@ public class FlashlightLogicTests
|
||||||
Assert.Equal(0f, forward.Z, 3);
|
Assert.Equal(0f, forward.Z, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForwardFromAnglesDegrees_PitchDrivesVerticalComponent()
|
||||||
|
{
|
||||||
|
// Source angles are inverted on pitch: positive pitch looks down.
|
||||||
|
Assert.True(FlashlightLogic.ForwardFromAnglesDegrees(45f, 0f).Z < 0f);
|
||||||
|
Assert.True(FlashlightLogic.ForwardFromAnglesDegrees(-45f, 0f).Z > 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HorizontalForwardFromYawDegrees_HasNoVerticalComponent()
|
||||||
|
{
|
||||||
|
foreach (var yaw in new[] { -180f, -90f, -45f, 0f, 45f, 90f, 180f })
|
||||||
|
{
|
||||||
|
Assert.Equal(0f, FlashlightLogic.HorizontalForwardFromYawDegrees(yaw).Z, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateLightTransform_AnglesCarryFullViewRotation()
|
||||||
|
{
|
||||||
|
var transform = FlashlightLogic.CalculateLightTransform(
|
||||||
|
pawnOrigin: new Vector3(0f, 0f, 0f),
|
||||||
|
pitch: -35f,
|
||||||
|
yaw: 90f,
|
||||||
|
roll: 12f,
|
||||||
|
eyeOffsetZ: 64f,
|
||||||
|
forwardDistance: 54f);
|
||||||
|
|
||||||
|
// Pitch must survive into the light's angles, otherwise the beam only tracks yaw.
|
||||||
|
Assert.Equal(-35f, transform.Angles.X, 3);
|
||||||
|
Assert.Equal(90f, transform.Angles.Y, 3);
|
||||||
|
Assert.Equal(12f, transform.Angles.Z, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(-89f)]
|
||||||
|
[InlineData(-45f)]
|
||||||
|
[InlineData(0f)]
|
||||||
|
[InlineData(45f)]
|
||||||
|
[InlineData(89f)]
|
||||||
|
public void CalculateLightTransform_PitchNeverMovesTheOrigin(float pitch)
|
||||||
|
{
|
||||||
|
var transform = FlashlightLogic.CalculateLightTransform(
|
||||||
|
new Vector3(0f, 0f, 0f),
|
||||||
|
pitch,
|
||||||
|
yaw: 0f,
|
||||||
|
roll: 0f,
|
||||||
|
eyeOffsetZ: 64f,
|
||||||
|
forwardDistance: 54f);
|
||||||
|
|
||||||
|
// The muzzle stays at eye height and eye-forward regardless of pitch, so
|
||||||
|
// looking up or down can never shove the light through the ceiling or floor.
|
||||||
|
Assert.Equal(54f, transform.Origin.X, 3);
|
||||||
|
Assert.Equal(0f, transform.Origin.Y, 3);
|
||||||
|
Assert.Equal(64f, transform.Origin.Z, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateLightTransform_YawDrivesHorizontalOffset()
|
||||||
|
{
|
||||||
|
var transform = FlashlightLogic.CalculateLightTransform(
|
||||||
|
new Vector3(10f, 20f, 30f),
|
||||||
|
pitch: 0f,
|
||||||
|
yaw: 90f,
|
||||||
|
roll: 0f,
|
||||||
|
eyeOffsetZ: 64f,
|
||||||
|
forwardDistance: 54f);
|
||||||
|
|
||||||
|
Assert.Equal(10f, transform.Origin.X, 3);
|
||||||
|
Assert.Equal(74f, transform.Origin.Y, 3);
|
||||||
|
Assert.Equal(94f, transform.Origin.Z, 3);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ShouldCreateAndDestroyLight_Policies()
|
public void ShouldCreateAndDestroyLight_Policies()
|
||||||
{
|
{
|
||||||
|
|
@ -98,7 +171,6 @@ public class FlashlightLogicTests
|
||||||
ForwardDistance = -10f,
|
ForwardDistance = -10f,
|
||||||
StandEyeOffsetZ = -1f,
|
StandEyeOffsetZ = -1f,
|
||||||
CrouchEyeOffsetZ = -1f,
|
CrouchEyeOffsetZ = -1f,
|
||||||
AttachmentName = " ",
|
|
||||||
LightCookie = ""
|
LightCookie = ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -118,7 +190,6 @@ public class FlashlightLogicTests
|
||||||
Assert.Equal(0f, config.ForwardDistance);
|
Assert.Equal(0f, config.ForwardDistance);
|
||||||
Assert.Equal(0f, config.StandEyeOffsetZ);
|
Assert.Equal(0f, config.StandEyeOffsetZ);
|
||||||
Assert.Equal(0f, config.CrouchEyeOffsetZ);
|
Assert.Equal(0f, config.CrouchEyeOffsetZ);
|
||||||
Assert.Equal("axis_of_intent", config.AttachmentName);
|
|
||||||
Assert.Equal("materials/effects/lightcookies/flashlight.vtex", config.LightCookie);
|
Assert.Equal("materials/effects/lightcookies/flashlight.vtex", config.LightCookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
public override string ModuleAuthor => "creazy.eth";
|
public override string ModuleAuthor => "creazy.eth";
|
||||||
public override string ModuleName => "Flashlight";
|
public override string ModuleName => "Flashlight";
|
||||||
public override string ModuleDescription => "Flashlight for Counter-Strike 2";
|
public override string ModuleDescription => "Flashlight for Counter-Strike 2";
|
||||||
public override string ModuleVersion => "0.1.1";
|
public override string ModuleVersion => "0.1.2";
|
||||||
|
|
||||||
public FlashlightConfig Config { get; set; } = new();
|
public FlashlightConfig Config { get; set; } = new();
|
||||||
|
|
||||||
|
|
@ -57,19 +57,35 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
|
|
||||||
private void OnTick()
|
private void OnTick()
|
||||||
{
|
{
|
||||||
if (!Config.Enabled || !Config.AllowUseKey)
|
if (!Config.Enabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var player in Utilities.GetPlayers())
|
foreach (var player in Utilities.GetPlayers())
|
||||||
{
|
{
|
||||||
if (!IsEligiblePlayer(player) || !player.PawnIsAlive)
|
if (!IsEligiblePlayer(player))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!player.PawnIsAlive)
|
||||||
|
{
|
||||||
|
// An un-parented light no longer dies together with the pawn, so sweep it up here
|
||||||
|
// in case a death or round-end never reached the event handlers.
|
||||||
|
if (_playerStates.TryGetValue(player.Slot, out var deadState) && deadState.IsOn)
|
||||||
|
{
|
||||||
|
deadState.IsOn = false;
|
||||||
|
DestroyLight(player.Slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var state = EnsureState(player);
|
var state = EnsureState(player);
|
||||||
|
|
||||||
|
if (Config.AllowUseKey)
|
||||||
|
{
|
||||||
var usePressed = (player.Buttons & PlayerButtons.Use) != 0;
|
var usePressed = (player.Buttons & PlayerButtons.Use) != 0;
|
||||||
|
|
||||||
if (FlashlightLogic.IsUsePressedEdge(usePressed, state.WasUsePressed))
|
if (FlashlightLogic.IsUsePressedEdge(usePressed, state.WasUsePressed))
|
||||||
|
|
@ -79,6 +95,12 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
|
|
||||||
state.WasUsePressed = usePressed;
|
state.WasUsePressed = usePressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.IsOn)
|
||||||
|
{
|
||||||
|
UpdateLight(player, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GameEventHandler]
|
[GameEventHandler]
|
||||||
|
|
@ -188,7 +210,7 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
|
|
||||||
if (state.IsOn)
|
if (state.IsOn)
|
||||||
{
|
{
|
||||||
CreateAndParentLight(player, state);
|
CreateLight(player, state);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -205,7 +227,7 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateAndParentLight(CCSPlayerController player, PlayerFlashlightState state)
|
private void CreateLight(CCSPlayerController player, PlayerFlashlightState state)
|
||||||
{
|
{
|
||||||
DestroyLight(player.Slot);
|
DestroyLight(player.Slot);
|
||||||
|
|
||||||
|
|
@ -225,20 +247,6 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isCrouching = (player.Buttons & PlayerButtons.Duck) != 0;
|
|
||||||
var eyeOffsetZ = FlashlightLogic.GetEyeOffsetZ(
|
|
||||||
isCrouching,
|
|
||||||
Config.StandEyeOffsetZ,
|
|
||||||
Config.CrouchEyeOffsetZ);
|
|
||||||
|
|
||||||
var angles = pawn.V_angle;
|
|
||||||
var forward = FlashlightLogic.ForwardFromAnglesDegrees(angles.X, angles.Y);
|
|
||||||
var origin = FlashlightLogic.CalculateLightOrigin(
|
|
||||||
new Vector3(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z),
|
|
||||||
forward,
|
|
||||||
eyeOffsetZ,
|
|
||||||
Config.ForwardDistance);
|
|
||||||
|
|
||||||
light.Enabled = true;
|
light.Enabled = true;
|
||||||
light.Color = Color.FromArgb(255, Config.ColorR, Config.ColorG, Config.ColorB);
|
light.Color = Color.FromArgb(255, Config.ColorR, Config.ColorG, Config.ColorB);
|
||||||
light.ColorTemperature = Config.ColorTemperature;
|
light.ColorTemperature = Config.ColorTemperature;
|
||||||
|
|
@ -254,10 +262,7 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
light.SizeParams.Y = Config.SizeY;
|
light.SizeParams.Y = Config.SizeY;
|
||||||
light.SizeParams.Z = Config.SizeZ;
|
light.SizeParams.Z = Config.SizeZ;
|
||||||
|
|
||||||
light.Teleport(
|
ApplyTransform(light, player, pawn);
|
||||||
origin,
|
|
||||||
new Vector3(angles.X, angles.Y, angles.Z),
|
|
||||||
null);
|
|
||||||
|
|
||||||
using (var keyValues = new CEntityKeyValues())
|
using (var keyValues = new CEntityKeyValues())
|
||||||
{
|
{
|
||||||
|
|
@ -265,13 +270,69 @@ public class FlashlightPlugin : BasePlugin, IPluginConfig<FlashlightConfig>
|
||||||
light.DispatchSpawn(keyValues);
|
light.DispatchSpawn(keyValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
light.AcceptInput("SetParent", pawn, light, "!activator");
|
|
||||||
light.AcceptInput("SetParentAttachmentMaintainOffset", null, null, Config.AttachmentName);
|
|
||||||
|
|
||||||
state.Light = light;
|
state.Light = light;
|
||||||
state.IsOn = true;
|
state.IsOn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps the light glued to the player's eye every tick.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The light is deliberately not parented to the pawn. Handing it to the engine via
|
||||||
|
/// <c>SetParent</c> / <c>SetParentAttachmentMaintainOffset</c> locks its orientation to a model
|
||||||
|
/// attachment, and those attachments only carry the body's yaw, so the beam could never follow
|
||||||
|
/// the player looking up or down. Driving the transform ourselves keeps pitch and yaw in sync.
|
||||||
|
/// </remarks>
|
||||||
|
private void UpdateLight(CCSPlayerController player, PlayerFlashlightState state)
|
||||||
|
{
|
||||||
|
var light = state.Light;
|
||||||
|
|
||||||
|
if (light is null || !light.IsValid)
|
||||||
|
{
|
||||||
|
// The engine can reap the entity underneath us (round restart, cleanup); rebuild it.
|
||||||
|
state.Light = null;
|
||||||
|
CreateLight(player, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pawn = player.PlayerPawn.Value;
|
||||||
|
if (pawn is null || !pawn.IsValid || pawn.AbsOrigin is null || pawn.V_angle is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyTransform(light, player, pawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTransform(CBarnLight light, CCSPlayerController player, CCSPlayerPawn pawn)
|
||||||
|
{
|
||||||
|
var isCrouching = (player.Buttons & PlayerButtons.Duck) != 0;
|
||||||
|
var eyeOffsetZ = FlashlightLogic.GetEyeOffsetZ(
|
||||||
|
isCrouching,
|
||||||
|
Config.StandEyeOffsetZ,
|
||||||
|
Config.CrouchEyeOffsetZ);
|
||||||
|
|
||||||
|
var origin = pawn.AbsOrigin!;
|
||||||
|
var angles = pawn.V_angle;
|
||||||
|
|
||||||
|
var transform = FlashlightLogic.CalculateLightTransform(
|
||||||
|
new Vector3(origin.X, origin.Y, origin.Z),
|
||||||
|
angles.X,
|
||||||
|
angles.Y,
|
||||||
|
angles.Z,
|
||||||
|
eyeOffsetZ,
|
||||||
|
Config.ForwardDistance);
|
||||||
|
|
||||||
|
// Handing the pawn's velocity over lets clients interpolate the light between ticks
|
||||||
|
// instead of visibly stepping it.
|
||||||
|
var velocity = pawn.AbsVelocity;
|
||||||
|
|
||||||
|
light.Teleport(
|
||||||
|
transform.Origin,
|
||||||
|
transform.Angles,
|
||||||
|
velocity is null ? null : new Vector3(velocity.X, velocity.Y, velocity.Z));
|
||||||
|
}
|
||||||
|
|
||||||
private void DestroyLight(int slot)
|
private void DestroyLight(int slot)
|
||||||
{
|
{
|
||||||
if (!_playerStates.TryGetValue(slot, out var state))
|
if (!_playerStates.TryGetValue(slot, out var state))
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,6 @@ public class FlashlightConfig : BasePluginConfig
|
||||||
[JsonPropertyName("CrouchEyeOffsetZ")]
|
[JsonPropertyName("CrouchEyeOffsetZ")]
|
||||||
public float CrouchEyeOffsetZ { get; set; } = 46f;
|
public float CrouchEyeOffsetZ { get; set; } = 46f;
|
||||||
|
|
||||||
[JsonPropertyName("AttachmentName")]
|
|
||||||
public string AttachmentName { get; set; } = "axis_of_intent";
|
|
||||||
|
|
||||||
[JsonPropertyName("LightCookie")]
|
[JsonPropertyName("LightCookie")]
|
||||||
public string LightCookie { get; set; } = "materials/effects/lightcookies/flashlight.vtex";
|
public string LightCookie { get; set; } = "materials/effects/lightcookies/flashlight.vtex";
|
||||||
|
|
||||||
|
|
@ -94,11 +91,6 @@ public class FlashlightConfig : BasePluginConfig
|
||||||
StandEyeOffsetZ = Math.Max(0f, StandEyeOffsetZ);
|
StandEyeOffsetZ = Math.Max(0f, StandEyeOffsetZ);
|
||||||
CrouchEyeOffsetZ = Math.Max(0f, CrouchEyeOffsetZ);
|
CrouchEyeOffsetZ = Math.Max(0f, CrouchEyeOffsetZ);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(AttachmentName))
|
|
||||||
{
|
|
||||||
AttachmentName = "axis_of_intent";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(LightCookie))
|
if (string.IsNullOrWhiteSpace(LightCookie))
|
||||||
{
|
{
|
||||||
LightCookie = "materials/effects/lightcookies/flashlight.vtex";
|
LightCookie = "materials/effects/lightcookies/flashlight.vtex";
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Flashlight;
|
namespace Flashlight;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position and orientation to apply to the flashlight entity.
|
||||||
|
/// <paramref name="Angles"/> is a Source QAngle laid out as (pitch, yaw, roll).
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct LightTransform(Vector3 Origin, Vector3 Angles);
|
||||||
|
|
||||||
public static class FlashlightLogic
|
public static class FlashlightLogic
|
||||||
{
|
{
|
||||||
public static bool TryToggle(ref bool isOn, ref bool canToggle)
|
public static bool TryToggle(ref bool isOn, ref bool canToggle)
|
||||||
|
|
@ -50,6 +56,39 @@ public static class FlashlightLogic
|
||||||
-MathF.Sin(pitchRad));
|
-MathF.Sin(pitchRad));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forward vector on the horizontal plane only, ignoring pitch.
|
||||||
|
/// </summary>
|
||||||
|
public static Vector3 HorizontalForwardFromYawDegrees(float yaw)
|
||||||
|
{
|
||||||
|
var yawRad = yaw * (MathF.PI / 180f);
|
||||||
|
|
||||||
|
return new Vector3(MathF.Cos(yawRad), MathF.Sin(yawRad), 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// World transform for the flashlight given the player's current pawn origin and view angles.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The angles carry the full view rotation so the beam tracks pitch as well as yaw, while the
|
||||||
|
/// origin is only pushed forward on the horizontal plane. Offsetting the origin along the full
|
||||||
|
/// pitched forward vector would drop the light through the floor when looking straight down
|
||||||
|
/// (a crouched eye height of 46 minus a 54 unit offset ends up below the ground).
|
||||||
|
/// </remarks>
|
||||||
|
public static LightTransform CalculateLightTransform(
|
||||||
|
Vector3 pawnOrigin,
|
||||||
|
float pitch,
|
||||||
|
float yaw,
|
||||||
|
float roll,
|
||||||
|
float eyeOffsetZ,
|
||||||
|
float forwardDistance)
|
||||||
|
{
|
||||||
|
var forward = HorizontalForwardFromYawDegrees(yaw);
|
||||||
|
var origin = CalculateLightOrigin(pawnOrigin, forward, eyeOffsetZ, forwardDistance);
|
||||||
|
|
||||||
|
return new LightTransform(origin, new Vector3(pitch, yaw, roll));
|
||||||
|
}
|
||||||
|
|
||||||
public static bool ShouldCreateLight(bool isOn, bool hasValidLight)
|
public static bool ShouldCreateLight(bool isOn, bool hasValidLight)
|
||||||
{
|
{
|
||||||
return isOn && !hasValidLight;
|
return isOn && !hasValidLight;
|
||||||
|
|
|
||||||
18
README.md
18
README.md
|
|
@ -1,12 +1,12 @@
|
||||||
# Flashlight
|
# Flashlight
|
||||||
|
|
||||||
Flashlight is a Counter-Strike 2 server plugin written in C# with [CounterStrikeSharp](https://docs.cssharp.dev). It gives human players a toggleable flashlight using a parented `light_barn` entity.
|
Flashlight is a Counter-Strike 2 server plugin written in C# with [CounterStrikeSharp](https://docs.cssharp.dev). It gives human players a toggleable flashlight using a `light_barn` entity.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Toggle with the Use key (`E` by default) or `css_fl_toggle`
|
- Toggle with the Use key (`E` by default) or `css_fl_toggle`
|
||||||
- One `light_barn` per player, parented to the pawn attachment (no per-tick spawn/teleport)
|
- One `light_barn` per player, re-aimed each tick so the beam tracks both pitch and yaw
|
||||||
- Configurable brightness, range, color, shadows, offsets, and attachment
|
- Configurable brightness, range, color, shadows, and offsets
|
||||||
- Optional team restriction (`Any`, `CT`, or `T`)
|
- Optional team restriction (`Any`, `CT`, or `T`)
|
||||||
- Automatically turns off on death, spawn, and team change
|
- Automatically turns off on death, spawn, and team change
|
||||||
- Bots ignored
|
- Bots ignored
|
||||||
|
|
@ -49,10 +49,9 @@ On first load, CounterStrikeSharp writes a JSON config for the plugin. Defaults:
|
||||||
| `SoftX` / `SoftY` | `1.0` | Softness |
|
| `SoftX` / `SoftY` | `1.0` | Softness |
|
||||||
| `Skirt` / `SkirtNear` | `0.5` / `1.0` | Skirt falloff |
|
| `Skirt` / `SkirtNear` | `0.5` / `1.0` | Skirt falloff |
|
||||||
| `SizeX` / `SizeY` / `SizeZ` | `45` / `45` / `0.03` | Beam size params |
|
| `SizeX` / `SizeY` / `SizeZ` | `45` / `45` / `0.03` | Beam size params |
|
||||||
| `ForwardDistance` | `54` | Spawn offset along view forward |
|
| `ForwardDistance` | `54` | Horizontal offset in front of the eye, so the beam clears the player model |
|
||||||
| `StandEyeOffsetZ` | `64` | Standing eye height offset |
|
| `StandEyeOffsetZ` | `64` | Standing eye height offset |
|
||||||
| `CrouchEyeOffsetZ` | `46` | Crouching eye height offset |
|
| `CrouchEyeOffsetZ` | `46` | Crouching eye height offset |
|
||||||
| `AttachmentName` | `axis_of_intent` | Parent attachment |
|
|
||||||
| `LightCookie` | `materials/effects/lightcookies/flashlight.vtex` | Flashlight cookie texture |
|
| `LightCookie` | `materials/effects/lightcookies/flashlight.vtex` | Flashlight cookie texture |
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
@ -75,10 +74,17 @@ dotnet build
|
||||||
dotnet test
|
dotnet test
|
||||||
```
|
```
|
||||||
|
|
||||||
Unit tests cover toggle/cooldown logic, Use-key edge detection, origin math, create/destroy policy, and config clamping. Entity parenting requires a live CS2 server.
|
Unit tests cover toggle/cooldown logic, Use-key edge detection, transform math (origin and pitch/yaw angles), create/destroy policy, and config clamping. Entity behaviour itself requires a live CS2 server.
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v0.1.2
|
||||||
|
|
||||||
|
- Fixed the beam only following horizontal aim: the light was parented to the pawn's `axis_of_intent` attachment, which carries body yaw but not view pitch, so looking up or down never moved it. The light is now un-parented and re-aimed every tick from the pawn's live `V_angle`.
|
||||||
|
- Fixed the flashlight never updating when `AllowUseKey` was `false`, which previously short-circuited the whole tick loop.
|
||||||
|
- `ForwardDistance` now offsets the light horizontally only, so looking straight down no longer pushes it through the floor.
|
||||||
|
- Removed the obsolete `AttachmentName` config key (leaving it in an existing config file is harmless and ignored).
|
||||||
|
|
||||||
### v0.1.1
|
### v0.1.1
|
||||||
|
|
||||||
- Updated to .NET 10 and CounterStrikeSharp.API 1.0.371
|
- Updated to .NET 10 and CounterStrikeSharp.API 1.0.371
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue