Refactor markers

* Replace drawables in public with interfaces
* Add service for tracking access to markers
* Add separate listener for placing markers
This commit is contained in:
Mooshua
2024-02-07 18:34:06 -08:00
committed by Mooshua
parent 0b40f00865
commit 1267ad6443
23 changed files with 418 additions and 139 deletions

View File

@@ -28,6 +28,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jailbreak.Logs", "mod\Jailb
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jailbreak.Debug", "mod\Jailbreak.Debug\Jailbreak.Debug.csproj", "{4F10E2B5-E8BB-4253-9BE6-9187575ADB13}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jailbreak.Lasers", "mod\Jailbreak.Lasers\Jailbreak.Lasers.csproj", "{FFF49F66-44EE-4BB2-8232-7002718DD3E5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -74,6 +76,10 @@ Global
{4F10E2B5-E8BB-4253-9BE6-9187575ADB13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F10E2B5-E8BB-4253-9BE6-9187575ADB13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F10E2B5-E8BB-4253-9BE6-9187575ADB13}.Release|Any CPU.Build.0 = Release|Any CPU
{FFF49F66-44EE-4BB2-8232-7002718DD3E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFF49F66-44EE-4BB2-8232-7002718DD3E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFF49F66-44EE-4BB2-8232-7002718DD3E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFF49F66-44EE-4BB2-8232-7002718DD3E5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9135CCC9-66C5-4A9C-AE3C-91475B5F0437} = {177DA48D-8306-4102-918D-992569878581}
@@ -86,5 +92,6 @@ Global
{CB2391A1-6CDD-496F-B8D6-674FD6268038} = {36BA84C0-291C-4930-A7C6-97CDF8F7F0D7}
{CE6EC648-E7F9-4CE7-B28F-8C7995830F35} = {36BA84C0-291C-4930-A7C6-97CDF8F7F0D7}
{4F10E2B5-E8BB-4253-9BE6-9187575ADB13} = {36BA84C0-291C-4930-A7C6-97CDF8F7F0D7}
{FFF49F66-44EE-4BB2-8232-7002718DD3E5} = {36BA84C0-291C-4930-A7C6-97CDF8F7F0D7}
EndGlobalSection
EndGlobal

View File

@@ -3,7 +3,7 @@ using CounterStrikeSharp.API.Modules.Utils;
namespace Jailbreak.Public.Mod.Draw;
public class BeamCircle : BeamedShape
public class BeamCircle : BeamedShape, IMarker
{
private readonly BeamLine?[] _lines;
private Vector[] _offsets;
@@ -37,7 +37,8 @@ public class BeamCircle : BeamedShape
return offsets;
}
public override void Draw()
protected override void DrawInternal()
{
for (var i = 0; i < _lines.Length; i++)
{
@@ -53,15 +54,16 @@ public class BeamCircle : BeamedShape
}
else
{
line.Move(start, end);
line.Update();
line.SetPoints(start, end);
}
}
}
public float Radius => _radius;
public void SetRadius(float radius)
{
_radius = radius;
_offsets = GenerateOffsets();
}
}
}

View File

@@ -5,7 +5,7 @@ using CounterStrikeSharp.API.Modules.Utils;
namespace Jailbreak.Public.Mod.Draw;
public class BeamLine : DrawableShape, IColorable
public class BeamLine : DrawableShape, ILine
{
private CEnvBeam? _beam;
private Color _color = Color.White;
@@ -17,30 +17,20 @@ public class BeamLine : DrawableShape, IColorable
_end = end;
}
public void SetColor(Color color)
{
_color = color;
}
public Color GetColor()
{
return _color;
}
public void Move(Vector start, Vector end)
public void SetPoints(Vector start, Vector end)
{
Position = start;
_end = end;
}
public override void Draw()
protected override void DrawInternal()
{
Remove();
var beam = Utilities.CreateEntityByName<CEnvBeam>("env_beam");
if (beam == null) return;
beam.RenderMode = RenderMode_t.kRenderTransColor;
beam.Width = _width;
beam.Render = GetColor();
beam.Render = _color;
beam.Teleport(Position, new QAngle(), new Vector());
beam.EndPos.X = _end.X;
@@ -51,9 +41,8 @@ public class BeamLine : DrawableShape, IColorable
Utilities.SetStateChanged(beam, "CBeam", "m_vecEndPos");
}
public override void Remove()
protected override void RemoveInternal()
{
KillTimer?.Kill();
_beam?.Remove();
_beam = null;
}
@@ -67,4 +56,6 @@ public class BeamLine : DrawableShape, IColorable
{
return _width;
}
}
public Vector EndPosition => _end;
}

View File

@@ -10,7 +10,6 @@ namespace Jailbreak.Public.Mod.Draw;
public abstract class BeamedShape : DrawableShape, IColorable
{
protected BeamLine?[] Beams;
protected Color Color = Color.White;
protected int Resolution;
protected BeamedShape(BasePlugin plugin, Vector position, int resolution) : base(plugin, position)
@@ -20,17 +19,15 @@ public abstract class BeamedShape : DrawableShape, IColorable
// TODO: Add support for rotation across arbitrary axis
public Color GetColor()
{
return Color;
}
public void SetColor(Color color)
{
Color = color;
}
public override void Remove()
public Color Color { get; protected set; }
protected override void RemoveInternal()
{
for (var i = 0; i < Beams.Length; i++)
{
@@ -38,4 +35,4 @@ public abstract class BeamedShape : DrawableShape, IColorable
Beams[i] = null;
}
}
}
}

View File

@@ -0,0 +1,17 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
namespace Jailbreak.Public.Mod.Draw;
public class DrawableFactory : IDrawableFactory
{
public ILine LineFor(CCSPlayerController? player, Vector from, Vector to)
{
throw new NotImplementedException();
}
public IMarker MarkerFor(CCSPlayerController? player, Vector position, float radius)
{
throw new NotImplementedException();
}
}

View File

@@ -1,3 +1,5 @@
using System.Drawing;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
@@ -8,13 +10,12 @@ namespace Jailbreak.Public.Mod.Draw;
/// <summary>
/// Represents a drawable shape
/// </summary>
public abstract class DrawableShape
public abstract class DrawableShape : IDrawable, IColorable
{
protected Timer? KillTimer; // Internal timer used to remove the shape after a certain amount of time
protected BasePlugin Plugin;
protected Vector Position; // Represents the origin of the shape
// Note that this can mean different things for different shapes
protected DateTime StartTime = DateTime.Now;
@@ -25,28 +26,41 @@ public abstract class DrawableShape
Position = position;
}
public abstract void Draw();
public Vector Position { get; protected set; }
public virtual void Update()
{
Remove();
Draw();
}
public virtual void Tick()
{
}
public void Draw(float lifetime)
{
Draw();
KillTimer = Plugin.AddTimer(lifetime, Remove, TimerFlags.STOP_ON_MAPCHANGE);
}
public virtual void Move(Vector position)
public void SetPosition(Vector position)
{
Position = position;
}
public abstract void Remove();
}
public Color Color { get; protected set; }
public void SetColor(Color color)
{
Color = color;
}
public bool Drawn { get; protected set; }
protected abstract void DrawInternal();
public void Draw()
{
if (Drawn)
Remove();
DrawInternal();
Drawn = true;
}
protected abstract void RemoveInternal();
public void Remove()
{
if (!Drawn)
return;
RemoveInternal();
Drawn = false;
}
}

View File

@@ -0,0 +1,37 @@
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.Public.Mod.Draw;
using NodaTime;
namespace Jailbreak.Drawable.Markers;
public class CurrentMarkerState : IDisposable
{
public bool HasMarker { get; set; } = false;
public IMarker? CurrentMarker { get; set; } = null;
/// <summary>
/// The time that the marker was placed.
/// Used for resize operations, to avoid making every operation a resize.
/// </summary>
public Instant? PlacedOn { get; set; } = null;
/// <summary>
/// The position of the last ping, for resize purposes.
/// </summary>
public Vector? PlacedAt { get; set; } = null;
/// <summary>
/// Invoked when the player dies or leaves.
/// </summary>
public void Dispose()
{
if (CurrentMarker != null)
{
CurrentMarker.Remove();
CurrentMarker.Dispose();
}
}
}

View File

@@ -0,0 +1,39 @@
using CounterStrikeSharp.API.Core;
using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Generic;
using Jailbreak.Public.Mod.Draw;
namespace Jailbreak.Drawable.Markers;
public class MarkerBehavior : IPluginBehavior, IMarkerService
{
private IPlayerState<MarkerState> _state;
public MarkerBehavior(IPlayerStateFactory playerStateFactory)
{
_state = playerStateFactory.Global<MarkerState>();
}
public bool TryEnable(CCSPlayerController player)
{
_state.Get(player)
.Enabled = true;
return true;
}
public bool TryDisable(CCSPlayerController player)
{
_state.Get(player)
.Enabled = false;
return true;
}
public bool IsEnabled(CCSPlayerController player)
{
return _state.Get(player)
.Enabled;
}
}

View File

@@ -0,0 +1,77 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Generic;
using Jailbreak.Public.Mod.Draw;
using NodaTime;
namespace Jailbreak.Drawable.Markers;
public class MarkerListener : IPluginBehavior
{
public const int DEFAULT_SIZE = 150;
private IMarkerService _markers;
private IPlayerState<CurrentMarkerState> _current;
private IDrawableFactory _drawable;
public MarkerListener(IMarkerService markers, IPlayerStateFactory factory, IDrawableFactory drawable)
{
_markers = markers;
_drawable = drawable;
// Use alive so the state tracker will dispose
// of the current state when the player dies.
_current = factory.Alive<CurrentMarkerState>();
}
protected void HandlePing(CCSPlayerController player, CurrentMarkerState markerState, Vector position)
{
if (markerState.HasMarker
&& markerState.CurrentMarker != null
&& markerState.PlacedOn != null
&& markerState.PlacedAt != null)
{
// The player already has a marker down.
// Do we qualify for a resize operation?
var elapsed = SystemClock.Instance.GetCurrentInstant() - markerState.PlacedOn;
if (elapsed <= Duration.FromMilliseconds(750))
{
// Resize the existing marker
var distance = Math.Clamp(markerState.PlacedAt.Distance(position), 100, 500);
markerState.CurrentMarker.SetRadius(distance);
markerState.CurrentMarker.Draw();
return;
}
}
// Create the marker if it does not exist
markerState.HasMarker = true;
markerState.CurrentMarker ??= _drawable.MarkerFor(player, position, DEFAULT_SIZE);
markerState.CurrentMarker.SetPosition(position);
markerState.CurrentMarker.Draw();
markerState.PlacedAt = position;
markerState.PlacedOn = SystemClock.Instance.GetCurrentInstant();
}
[GameEventHandler]
public HookResult OnPing(EventPlayerPing ev, GameEventInfo info)
{
var player = ev.Userid;
if (!_markers.IsEnabled(player))
return HookResult.Handled;
var vec = new Vector(ev.X, ev.Y, ev.Z);
var current = _current.Get(player);
this.HandlePing(player, current, vec);
return HookResult.Handled;
}
}

View File

@@ -0,0 +1,12 @@
using System.Drawing;
using Jailbreak.Public.Mod.Draw;
namespace Jailbreak.Drawable.Markers;
public class MarkerState
{
public bool Enabled { get; set; } = false;
public Color Color { get; set; }= Color.White;
}

View File

@@ -7,6 +7,7 @@ using Jailbreak.Formatting.Extensions;
using Jailbreak.Formatting.Views;
using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Mod.Draw;
using Jailbreak.Public.Mod.Logs;
using Jailbreak.Public.Mod.Warden;
using Microsoft.Extensions.Logging;
@@ -16,18 +17,20 @@ namespace Jailbreak.Warden.Global;
public class WardenBehavior : IPluginBehavior, IWardenService
{
private ILogger<WardenBehavior> _logger;
private IRichLogService logs;
private IRichLogService _logs;
private IMarkerService _markers;
private IWardenNotifications _notifications;
private bool _hasWarden;
private CCSPlayerController? _warden;
public WardenBehavior(ILogger<WardenBehavior> logger, IWardenNotifications notifications, IRichLogService logs)
public WardenBehavior(ILogger<WardenBehavior> logger, IWardenNotifications notifications, IRichLogService logs, IMarkerService markers)
{
_logger = logger;
_notifications = notifications;
logs = logs;
_logs = logs;
_markers = markers;
}
/// <summary>
@@ -61,11 +64,14 @@ public class WardenBehavior : IPluginBehavior, IWardenService
Utilities.SetStateChanged(_warden.Pawn.Value, "CBaseModelEntity", "m_clrRender");
}
// Grant marker perms
_markers.TryEnable(_warden);
_notifications.NEW_WARDEN(_warden)
.ToAllChat()
.ToAllCenter();
logs.Append( logs.Player(_warden), "is now the warden.");
_logs.Append( _logs.Player(_warden), "is now the warden.");
return true;
}
@@ -81,7 +87,10 @@ public class WardenBehavior : IPluginBehavior, IWardenService
_warden.Pawn.Value.RenderMode = RenderMode_t.kRenderTransColor;
_warden.Pawn.Value.Render = Color.FromArgb(254, 255, 255, 255);
Utilities.SetStateChanged(_warden.Pawn.Value, "CBaseModelEntity", "m_clrRender");
logs.Append( logs.Player(_warden), "is no longer the warden.");
_logs.Append( _logs.Player(_warden), "is no longer the warden.");
// Remove marker privileges.
_markers.TryDisable(_warden!);
}
_warden = null;

View File

@@ -11,4 +11,8 @@
<ProjectReference Include="..\..\public\Jailbreak.Public\Jailbreak.Public.csproj"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Markers\" />
</ItemGroup>
</Project>

View File

@@ -1,78 +0,0 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Mod.Draw;
using Jailbreak.Public.Mod.Warden;
namespace Jailbreak.Warden.Markers;
public class WardenMarkerBehavior : IPluginBehavior
{
private const float MinRadius = 60f, MaxRadius = 360f;
private readonly IWardenService _warden;
private BeamCircle? _marker;
private Vector? _currentPos;
private long _placementTime;
private float _radius = MinRadius;
public WardenMarkerBehavior(IWardenService warden)
{
_warden = warden;
}
public void Start(BasePlugin plugin)
{
_marker = new BeamCircle(plugin, new Vector(), 60f, (int)Math.PI * 15);
plugin.AddCommandListener("player_ping", CommandListener_PlayerPing);
}
[GameEventHandler]
public HookResult OnPing(EventPlayerPing @event, GameEventInfo info)
{
var player = @event.Userid;
if (!_warden.IsWarden(player))
return HookResult.Handled;
var vec = new Vector(@event.X, @event.Y, @event.Z);
if (_currentPos != null)
{
var distance = _currentPos.Distance(vec);
var timeElapsed = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - _placementTime;
if (timeElapsed < 500)
{
if (distance <= MaxRadius * 1.5)
{
distance = Math.Clamp(distance, MinRadius, MaxRadius);
_marker?.SetRadius(distance);
_marker?.Update();
_radius = distance;
return HookResult.Handled;
}
}
else if (distance <= _radius)
{
_marker?.Remove();
return HookResult.Handled;
}
}
_radius = MinRadius;
_currentPos = vec;
_placementTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
_marker?.Move(vec);
_marker?.SetRadius(_radius);
_marker?.Update();
return HookResult.Handled;
}
private HookResult CommandListener_PlayerPing(CCSPlayerController? player, CommandInfo info)
{
return _warden.IsWarden(player) ? HookResult.Continue : HookResult.Handled;
}
}

View File

@@ -8,6 +8,7 @@ using Jailbreak.Formatting.Views;
using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Generic;
using Jailbreak.Public.Mod.Draw;
using Jailbreak.Public.Mod.Warden;
using Microsoft.Extensions.Logging;

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.142"/>
<PackageReference Include="NodaTime" Version="3.1.10" />
<PackageReference Include="System.Text.Json" Version="8.0.0"/>
</ItemGroup>

View File

@@ -5,5 +5,6 @@ namespace Jailbreak.Public.Mod.Draw;
public interface IColorable
{
void SetColor(Color color);
Color GetColor();
}
Color Color { get; }
}

View File

@@ -0,0 +1,36 @@

using CounterStrikeSharp.API.Modules.Utils;
namespace Jailbreak.Public.Mod.Draw;
public interface IDrawable : IDisposable
{
/// <summary>
/// The position of the drawable object
/// </summary>
Vector Position { get; }
void SetPosition(Vector position);
/// <summary>
/// Has this object been written to the world?
/// </summary>
bool Drawn { get; }
/// <summary>
/// Draw this item
/// The drawable should remove itself if it is currently drawn
/// </summary>
void Draw();
/// <summary>
/// Remove this item
/// </summary>
void Remove();
void IDisposable.Dispose()
{
this.Remove();
}
}

View File

@@ -0,0 +1,45 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
namespace Jailbreak.Public.Mod.Draw;
public interface IDrawableFactory
{
/// <summary>
/// Draw a line beginning at from and ending at to.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
ILine Line(Vector from, Vector to)
=> this.LineFor(null, from, to);
/// <summary>
/// Draw a line beginning at from and ending at to,
/// using the specified player's style configurations.
/// </summary>
/// <param name="player"></param>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
ILine LineFor(CCSPlayerController? player, Vector from, Vector to);
/// <summary>
/// Draw a marker at the specified position with size "radius".
/// </summary>
/// <param name="position"></param>
/// <param name="radius"></param>
/// <returns></returns>
IMarker Marker(Vector position, float radius)
=> MarkerFor(null, position, radius);
/// <summary>
/// Draw a marker at the position with size radius,
/// using the specified player's style configurations.
/// </summary>
/// <param name="player"></param>
/// <param name="position"></param>
/// <param name="radius"></param>
/// <returns></returns>
IMarker MarkerFor(CCSPlayerController? player, Vector position, float radius);
}

View File

@@ -0,0 +1,12 @@
using CounterStrikeSharp.API.Modules.Utils;
namespace Jailbreak.Public.Mod.Draw;
public interface ILine : IDrawable, IColorable
{
/// <summary>
/// The second position that makes up this line.
/// The line will terminate here.
/// </summary>
Vector EndPosition { get; }
}

View File

@@ -0,0 +1,8 @@
namespace Jailbreak.Public.Mod.Draw;
public interface IMarker : IDrawable, IColorable
{
float Radius { get; }
void SetRadius(float radius);
}

View File

@@ -0,0 +1,34 @@
using System.Drawing;
using CounterStrikeSharp.API.Core;
namespace Jailbreak.Public.Mod.Draw;
public interface IMarkerService
{
/// <summary>
/// Grant this player the ability to use a marker
/// This persists until <see cref="TryDisable"/> is called.
/// Succeeds if the player already has marker permissions.
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
bool TryEnable(CCSPlayerController player);
/// <summary>
/// Try to remove marker permissions from this player.
/// Succeeds if the player does not have marker permissions in the first place.
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
bool TryDisable(CCSPlayerController player);
/// <summary>
/// Returns true if the specified player has marker privileges.
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
bool IsEnabled(CCSPlayerController player);
}

View File

@@ -18,11 +18,24 @@ public class PlayerStateImpl<TState> : IPlayerState<TState>, ITrackedPlayerState
public void Reset(CCSPlayerController controller)
{
_states.Remove(controller.Slot);
ResetInternal(controller.Slot);
}
public void Drop()
{
_states.Clear();
foreach (var (key, _) in _states)
ResetInternal(key);
}
}
private void ResetInternal(int slot)
{
var entry = _states[slot];
// If the state is disposable,
// give the plugin a nice clean place to cleanup the state here.
if (entry is IDisposable disposable)
disposable.Dispose();
_states.Remove(slot);
}
}