Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Bold & Italics = being worked on.
- [ ] Zoning
- [X] Start/End trigger touch hooks
- [X] Load zone information automatically from standardised triggers: https://github.com/CS2Surf/Timer/wiki/CS2-Surf-Mapping
- [ ] _**Support for stages (`/rs`, teleporting with `/s`)**_
- [X] _**Support for stages (`/rs`, teleporting with `/s`)**_
- [ ] _**Support for bonuses (`/rs`, teleporting with `/b #`)**_
- [ ] _**Start/End touch hooks implemented for all zones**_
- [ ] Surf configs
Expand Down
18 changes: 14 additions & 4 deletions cfg/SurfTimer/server_settings.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ sv_deadtalk 1
sv_full_alltalk 1

// Movement Settings
sv_airaccelerate 150
// some pussy shit = sv_airaccelerate 150
sv_airaccelerate 2000
sv_gravity 800
sv_friction 5.2
sv_maxspeed 350
Expand All @@ -26,6 +27,16 @@ sv_enablebunnyhopping 1
sv_autobunnyhopping 1
sv_staminajumpcost 0
sv_staminalandcost 0
sv_timebetweenducks 0

// Some replay bot shit (took so fucking long to debug)
bot_quota 1 // This is gonna be used to change the amount of bots allowed (per stages/bonuses/etc) when stages/bonuses/etc added
bot_quota_mode "normal"
bot_join_after_player 1
bot_join_team CT
bot_zombie 1
bot_stop 1
bot_freeze 1

// Player Settings
mp_spectators_max 64
Expand All @@ -36,8 +47,8 @@ mp_respawn_on_death_ct 1
mp_respawn_on_death_t 1
mp_ct_default_secondary weapon_usp_silencer
mp_t_default_secondary weapon_usp_silencer
mp_autoteambalance 0
mp_limitteams 0
mp_autoteambalance 0
mp_playercashawards 0
mp_teamcashawards 0
mp_death_drop_c4 1
Expand All @@ -63,8 +74,7 @@ mp_freezetime 0
mp_team_intro_time 0
mp_warmup_end
mp_warmuptime 0
bot_quota 0
sv_holiday_mode 0
sv_party_mode 0

sv_cheats 0
sv_cheats 0
2 changes: 1 addition & 1 deletion src/ST-Commands/MapCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void MapTier(CCSPlayerController? player, CommandInfo command)
if (player == null)
return;

if (CurrentMap.Stages > 0)
if (CurrentMap.Stages > 1)
player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Staged {ChatColors.Yellow}{CurrentMap.Stages} Stages{ChatColors.Default}");
else
player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Linear {ChatColors.Yellow}{CurrentMap.Checkpoints} Checkpoints{ChatColors.Default}");
Expand Down
51 changes: 50 additions & 1 deletion src/ST-Commands/PlayerCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command)
player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s <stage>");
return;
}

else if (CurrentMap.Stages <= 0)
{
player.PrintToChat($"{PluginPrefix} {ChatColors.Red}This map has no stages.");
Expand All @@ -84,4 +83,54 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command)
else
player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s <stage>");
}

// // Test command
// [ConsoleCommand("css_savereplay", "Test")]
// public void SaveReplay(CCSPlayerController? player, CommandInfo command) {
// if(player == null)
// return;

// foreach(var p in playerList.Values) {
// if(p.Replay.Frames.Count() > 0) {
// p.Replay.StopRecording();
// p.Replay.SaveReplayData(p, DB!);
// break;
// }
// }
// }

// Test command
[ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")]
public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo command) {
if(player == null || player.Team == CsTeam.Spectator)
return;

player.ChangeTeam(CsTeam.Spectator);
}

[ConsoleCommand("css_replaybotpause", "Pause the replay bot playback")]
[ConsoleCommand("css_rbpause", "Pause the replay bot playback")]
public void PauseReplay(CCSPlayerController? player, CommandInfo command) {
if(player == null
|| player.Team != CsTeam.Spectator
|| CurrentMap.ReplayBot.Controller == null
|| !CurrentMap.ReplayBot.IsPlaying
|| CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum)
return;

CurrentMap.ReplayBot.Pause();
}

[ConsoleCommand("css_replaybotflip", "Flips the replay bot between Forward/Backward playback")]
[ConsoleCommand("css_rbflip", "Flips the replay bot between Forward/Backward playback")]
public void ReverseReplay(CCSPlayerController? player, CommandInfo command) {
if(player == null
|| player.Team != CsTeam.Spectator
|| CurrentMap.ReplayBot.Controller == null
|| !CurrentMap.ReplayBot.IsPlaying
|| CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum)
return;

CurrentMap.ReplayBot.FrameTickIncrement *= -1;
}
}
35 changes: 32 additions & 3 deletions src/ST-Events/Players.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,41 @@ namespace SurfTimer;

public partial class SurfTimer
{
[GameEventHandler] // Player Connect Event
public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo info)
[GameEventHandler(HookMode.Post)]
public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info)
{
var controller = @event.Userid;
if(!controller.IsValid)
return HookResult.Continue;

if (controller.IsBot && CurrentMap.ReplayBot.Controller == null)
{
CurrentMap.ReplayBot.Controller = controller;
// CurrentMap.ReplayBot.Controller.PlayerName = $"[REPLAY] {CurrentMap.Name}";

Server.PrintToChatAll($"{ChatColors.Lime} Loading replay data..."); // WHY COLORS NOT WORKING AHHHHH!!!!!
AddTimer(2f, () => {
CurrentMap.ReplayBot.Controller.RemoveWeapons();

CurrentMap.ReplayBot.LoadReplayData(DB!, CurrentMap);

CurrentMap.ReplayBot.Start();
});
}

return HookResult.Continue;
}

[GameEventHandler]
public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo info)
{
var player = @event.Userid;
#if DEBUG
Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> {player.PlayerName} / {player.UserId} / {player.SteamID}");
Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> {player.PlayerName} / {player.UserId} / Bot Diff: {player.PawnBotDifficulty}");
#endif

if (player.IsBot || !player.IsValid)
if (player.IsBot || !player.IsValid) // IsBot might be broken so we can check for PawnBotDifficulty which is `-1` for real players
{
return HookResult.Continue;
}
Expand Down Expand Up @@ -128,6 +154,9 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo
{
var player = @event.Userid;

if (CurrentMap.ReplayBot.Controller != null&& CurrentMap.ReplayBot.Controller.Equals(player))
CurrentMap.ReplayBot.Reset();

if (player.IsBot || !player.IsValid)
{
return HookResult.Continue;
Expand Down
7 changes: 7 additions & 0 deletions src/ST-Events/Tick.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;

namespace SurfTimer;

public partial class SurfTimer
Expand All @@ -7,7 +10,11 @@ public void OnTick()
foreach (var player in playerList.Values)
{
player.Timer.Tick();
player.ReplayRecorder.Tick(player);
player.HUD.Display();
}

// Replay BOT Ticks
CurrentMap?.ReplayBot.Tick(); // When CurrentMap null the ? operator will terminate safely the operation
}
}
9 changes: 8 additions & 1 deletion src/ST-Events/TriggerEndTouch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler)
CBaseTrigger trigger = handler.GetParam<CBaseTrigger>(0);
CBaseEntity entity = handler.GetParam<CBaseEntity>(1);
CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle);
if (!client.IsValid || client.UserId == -1 || !client.PawnIsAlive) // `client.IsBot` throws error in server console when going to spectator?
if (!client.IsValid || client.UserId == -1 || !client.PawnIsAlive || !playerList.ContainsKey((int)client.UserId!)) // `client.IsBot` throws error in server console when going to spectator? + !playerList.ContainsKey((int)client.UserId!) make sure to not check for user_id that doesnt exists
{
return HookResult.Continue;
}
Expand All @@ -41,6 +41,13 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler)
trigger.Entity.Name.Contains("s1_start") ||
trigger.Entity.Name.Contains("stage1_start"))
{
// Replay
if(player.ReplayRecorder.IsRecording)
{
// Saveing 2 seconds before leaving the start zone
player.ReplayRecorder.Frames.RemoveRange(0, Math.Max(0, player.ReplayRecorder.Frames.Count - (64*2))); // Would like for someone to fact check the math :)
}

// MAP START ZONE
player.Timer.Start();

Expand Down
18 changes: 14 additions & 4 deletions src/ST-Events/TriggerStartTouch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler)
CBaseTrigger trigger = handler.GetParam<CBaseTrigger>(0);
CBaseEntity entity = handler.GetParam<CBaseEntity>(1);
CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle);
if (client.IsBot || !client.IsValid || !client.PawnIsAlive)
if (!client.IsValid || !client.PawnIsAlive || !playerList.ContainsKey((int)client.UserId!)) // !playerList.ContainsKey((int)client.UserId!) make sure to not check for user_id that doesnt exists
{
return HookResult.Continue;
}
Expand Down Expand Up @@ -51,6 +51,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler)
if (player.Timer.IsRunning)
{
player.Timer.Stop();

player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the run
player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run
player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run
Expand All @@ -59,15 +60,15 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler)
// To-do: make Style (currently 0) be dynamic
if (player.Stats.PB[style].Ticks <= 0) // Player first ever PersonalBest for the map
{
Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!");
Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!");
}
else if (player.Timer.Ticks < player.Stats.PB[style].Ticks) // Player beating their existing PersonalBest for the map
{
Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[style].Ticks)}{ChatColors.Default})!");
Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[style].Ticks)}{ChatColors.Default})!");
}
else // Player did not beat their existing PersonalBest for the map
{
player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!");
player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!");
return HookResult.Continue; // Exit here so we don't write to DB
}

Expand All @@ -93,6 +94,13 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler)
player.Stats.ThisRun.SaveCurrentRunCheckpoints(player, DB); // Save this run's checkpoints
player.Stats.LoadCheckpointsData(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here
CurrentMap.GetMapRecordAndTotals(DB); // Reload the Map record and totals for the HUD

// Replay - Add end buffer for replay
AddTimer(1.5f, () => player.ReplayRecorder.SaveReplayData(player, DB));
AddTimer(2f, () => {
CurrentMap.ReplayBot.LoadReplayData(DB!, CurrentMap);
CurrentMap.ReplayBot.ResetReplay();
});
}

#if DEBUG
Expand All @@ -105,6 +113,8 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler)
trigger.Entity.Name.Contains("s1_start") ||
trigger.Entity.Name.Contains("stage1_start"))
{
player.ReplayRecorder.Start(); // Start replay recording

player.Timer.Reset();
player.Stats.ThisRun.Checkpoint.Clear(); // I have the suspicion that the `Timer.Reset()` does not properly reset this object :thonk:
player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})");
Expand Down
1 change: 1 addition & 0 deletions src/ST-Map/Map.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal class Map
public int LastPlayed {get; set;} = 0;
public int TotalCompletions {get; set;} = 0;
public Dictionary<int, PersonalBest> WR { get; set; } = new Dictionary<int, PersonalBest>();
public ReplayPlayer ReplayBot { get; set; } = new ReplayPlayer();

// Zone Origin Information
// Map start/end zones
Expand Down
2 changes: 2 additions & 0 deletions src/ST-Player/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal class Player
public PlayerTimer Timer {get; set;}
public PlayerStats Stats {get; set;}
public PlayerHUD HUD {get; set;}
public ReplayRecorder ReplayRecorder { get; set; }

// Player information
public PlayerProfile Profile {get; set;}
Expand All @@ -28,6 +29,7 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen

this.Timer = new PlayerTimer();
this.Stats = new PlayerStats();
this.ReplayRecorder = new ReplayRecorder();

this.HUD = new PlayerHUD(this);
this.CurrMap = CurrMap;
Expand Down
26 changes: 19 additions & 7 deletions src/ST-Player/PlayerHUD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private string FormatHUDElementHTML(string title, string body, string color, str
/// Unless specified differently, the default formatting will be `Compact`.
/// Check <see cref="PlayerTimer.TimeFormatStyle"/> for all formatting types.
/// </summary>
public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTimer.TimeFormatStyle.Compact)
public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTimer.TimeFormatStyle.Compact)
{
TimeSpan time = TimeSpan.FromSeconds(ticks / 64.0);
int millis = (int)(ticks % 64 * (1000.0 / 64.0));
Expand All @@ -57,7 +57,10 @@ public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTi

public void Display()
{
if (_player.Controller.IsValid && _player.Controller.PawnIsAlive)
if(!_player.Controller.IsValid)
return;

if (_player.Controller.PawnIsAlive)
{
int style = _player.Timer.Style;
// Timer Module
Expand Down Expand Up @@ -97,6 +100,15 @@ public void Display()
// Display HUD
_player.Controller.PrintToCenterHtml(hud);
}
else if (_player.Controller.Team == CsTeam.Spectator)
{
if (_player.CurrMap.ReplayBot.Controller?.Pawn.SerialNum == _player.Controller.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum)
{
string elapsed_ticks = FormatHUDElementHTML("Tick", $"{_player.CurrMap.ReplayBot.CurrentFrameTick}/{_player.CurrMap.ReplayBot.Frames.Count}", "#7882dd");
string hud = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}<br>{elapsed_ticks}";
_player.Controller.PrintToCenterHtml(hud);
}
}
}

/// <summary>
Expand Down Expand Up @@ -163,11 +175,11 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi
// Calculate the time difference
if (pbTime - currentTime < 0.0)
{
strPbDifference += ChatColors.Red + "+" + _player.HUD.FormatTime((pbTime - currentTime) * -1); // We multiply by -1 to get the positive value
strPbDifference += ChatColors.Red + "+" + FormatTime((pbTime - currentTime) * -1); // We multiply by -1 to get the positive value
}
else if (pbTime - currentTime >= 0.0)
{
strPbDifference += ChatColors.Green + "-" + _player.HUD.FormatTime(pbTime - currentTime);
strPbDifference += ChatColors.Green + "-" + FormatTime(pbTime - currentTime);
}
strPbDifference += ChatColors.Default + " ";

Expand Down Expand Up @@ -200,11 +212,11 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi
// Calculate the WR time difference
if (wrTime - currentTime < 0.0)
{
strWrDifference += ChatColors.Red + "+" + _player.HUD.FormatTime((wrTime - currentTime) * -1); // We multiply by -1 to get the positive value
strWrDifference += ChatColors.Red + "+" + FormatTime((wrTime - currentTime) * -1); // We multiply by -1 to get the positive value
}
else if (wrTime - currentTime >= 0.0)
{
strWrDifference += ChatColors.Green + "-" + _player.HUD.FormatTime(wrTime - currentTime);
strWrDifference += ChatColors.Green + "-" + FormatTime(wrTime - currentTime);
}
strWrDifference += ChatColors.Default + " ";

Expand All @@ -223,7 +235,7 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi
// Print checkpoint message
_player.Controller.PrintToChat(
$"{PluginPrefix} CP [{ChatColors.Yellow}{_player.Timer.Checkpoint}{ChatColors.Default}]: " +
$"{ChatColors.Yellow}{_player.HUD.FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " +
$"{ChatColors.Yellow}{FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " +
$"{ChatColors.Yellow}({currentSpeed.ToString("0")}){ChatColors.Default} " +
$"[PB: {strPbDifference} | " +
$"WR: {strWrDifference}]");
Expand Down
14 changes: 14 additions & 0 deletions src/ST-Player/Replay/ReplayFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace SurfTimer;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Core;


[Serializable]
internal class ReplayFrame
{
public Vector Pos { get; set; } = new Vector(0, 0, 0);
public QAngle Ang { get; set; } = new QAngle(0, 0, 0);
public ulong Button { get; set; }
public uint Flags { get; set; }
public MoveType_t MoveType { get; set; }
}
Loading