diff --git a/README.md b/README.md
index b2de2e8..30601aa 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
> [!NOTE]
-> The original creator of SharpTimer is deafps, who discontinued support for the project after version 0.2.6. This fork is now maintaned by the community, mainly [rcnoob](https://github.com/rcnoob).
+> The original creator of SharpTimer is dea_bb, who discontinued support for the project after version 0.2.6. This fork is now maintaned by the community, mainly [rcnoob](https://github.com/rcnoob).
@@ -8,6 +8,10 @@
+
+
+
+
# SharpTimer
@@ -80,20 +84,24 @@ SharpTimer is a "simple" Surf/KZ/Bhop/MG/Deathrun/etc. CS2 Timer plugin using Co
[**MetaMod**](https://cs2.poggu.me/metamod/installation/)
-[**CounterStrikeSharp** *(v215 and up)*](https://github.com/roflmuffin/CounterStrikeSharp/releases)
+[**CounterStrikeSharp** *(v281 and up)*](https://github.com/roflmuffin/CounterStrikeSharp/releases)
[**SharpTimerModelSetter** *(optional but recommended for custom player models)*](https://github.com/johandrevwyk/STCustomModels)
-[**MovementUnlocker** *(optional but recommended)*](https://github.com/Source2ZE/MovementUnlocker)
+[**MovementUnlocker** *(optional but recommended for surf and bhop servers)*](https://github.com/Source2ZE/MovementUnlocker)
[**RampBugFix** *(optional but recommended for surf servers)*](https://github.com/Interesting-exe/CS2Fixes-RampbugFix/)
[**Web panel** *(optional but recommended)*](https://github.com/Letaryat/sharptimer-web-panel)
-[**SharpTimer-WallLists** *(optional but recommended)*](https://github.com/M-archand/SharpTimer-WallLists/tree/PointsList)
+[**SharpTimer-WallLists** *(optional but recommended)*](https://github.com/M-archand/SharpTimer-WallLists)
[**CS2-TeleportAnglesFix** *(optional but recommended)*](https://github.com/M-archand/CS2-TeleportAnglesFix)
+[**STFixes** *(optional but recommended)*](https://github.com/rcnoob/STFixes)
+
+[**Flashing HUD Fix** *(optional but recommended)*](https://github.com/deabb/CS2FlashingHtmlHudFix)
+
## Install
* Download the [latest release](https://github.com/Letaryat/poor-sharptimer/releases),
@@ -146,7 +154,7 @@ SharpTimer is a "simple" Surf/KZ/Bhop/MG/Deathrun/etc. CS2 Timer plugin using Co
- [x] Max
- [x] Height
- [x] Width
- - [x] Sync
+ - [ ] Sync
- [ ] Jump Types
- [x] Long Jump
- [x] BunnyHop
@@ -162,4 +170,7 @@ SharpTimer is a "simple" Surf/KZ/Bhop/MG/Deathrun/etc. CS2 Timer plugin using Co
- [ ] Strafe Sync Bar on HUD
-## Author: [@DEA_BB](https://twitter.com/dea_bb)
+## Authors:
+[DEA_BB](https://twitter.com/dea_bb)
+[Letaryat](https://github.com/Letaryat)
+[rcnoob](https://github.com/rcnoob)
diff --git a/cfg/SharpTimer/config.cfg b/cfg/SharpTimer/config.cfg
index 5187121..fab50bc 100644
--- a/cfg/SharpTimer/config.cfg
+++ b/cfg/SharpTimer/config.cfg
@@ -23,8 +23,6 @@ sharptimer_remove_legs true
sharptimer_remove_collision true // Whether player collision should be removed or not. Default value: true
sharptimer_remove_damage true // Whether dealing damage should be disabled or not (will crash with cs2fixes since they hook/detour damage regardless of you using their feature). Default value: true
sharptimer_remove_crouch_fatigue true // Whether the player should get no crouch fatigue or not. Default value: true
-sharptimer_trigger_push_fix false // When enabled all trigger_push ents will only push once OnStartTouch. Default value: false
-sharptimer_hide_all_players false // Whether all players should be hidden or not. Default value: false
sharptimer_bhop_block_ticks 16 // Ticks allowed on bhop_block. Default value: 16
sharptimer_spawn_on_respawnpos false // Teleports player to respawnpos on spawn. Default value: false
sharptimer_stage_times_enabled true // Whether stage time records are enabled by default or not. Default value: true
@@ -101,9 +99,8 @@ sharptimer_enable_noclip false
sharptimer_global_rank_points_enabled false // Whether the plugin should reward players with global points for completing maps. MySQL Requierd. Default value: false
sharptimer_display_rank_tags_chat true // Whether the plugin should display rank tags infront of players names in chat or not. Default value: true
sharptimer_display_rank_tags_scoreboard true // Whether the plugin should display rank tags infront of players names in scoreboard or not. Default value: true
-sharptimer_global_rank_free_points_enabled true // Whether the plugin should reward players with free points for completing maps without beating their PB (31xMapTier). Default value: true
-sharptimer_global_rank_max_free_rewards 20 // How many times the player should recieve free 'participation' points for finishing the map without a new PB. Default value: 20
-sharptimer_global_rank_min_points_threshold 1000 // Players with Points below this amount will be treated as Unranked. Default value: 1000
+sharptimer_global_rank_min_points_threshold 1 // Players with Points below this amount will be treated as Unranked. Default value: 1
+sharptimer_global_rank_bonus_points_multiplier 0.5 // Multiplier for bonus course completion points. Default value: 0.5
//REPLAYS
diff --git a/src/Commands/ChatCommands.cs b/src/Commands/ChatCommands.cs
index 8c22cf2..3d34a32 100644
--- a/src/Commands/ChatCommands.cs
+++ b/src/Commands/ChatCommands.cs
@@ -673,7 +673,7 @@ public async Task RankCommandHandler(CCSPlayerController? player, string steamId
foreach (var bonusRespawnPose in bonusRespawnPoses)
{
var bonusNumber = bonusRespawnPose.Key;
- var bonusPbTicks = enableDb ? await GetPreviousPlayerRecordFromDatabase(player, steamId, currentMapName!, playerName, bonusNumber, style) : await GetPreviousPlayerRecord(player, steamId, bonusNumber);
+ var bonusPbTicks = enableDb ? await GetPreviousPlayerRecordFromDatabase(steamId, currentMapName!, playerName, bonusNumber, style) : await GetPreviousPlayerRecord(steamId, bonusNumber);
/// Skip this bonus since the player doesn't have a saved time
if (bonusPbTicks <= 0) continue;
@@ -697,7 +697,7 @@ public async Task RankCommandHandler(CCSPlayerController? player, string steamId
serverPlacement = await GetPlayerServerPlacement(player, steamId, playerName, false, true, false);
}
- int pbTicks = enableDb ? await GetPreviousPlayerRecordFromDatabase(player, steamId, currentMapName!, playerName, 0, style) : await GetPreviousPlayerRecord(player, steamId, 0);
+ int pbTicks = enableDb ? await GetPreviousPlayerRecordFromDatabase(steamId, currentMapName!, playerName, 0, style) : await GetPreviousPlayerRecord(steamId, 0);
Server.NextFrame(() =>
{
@@ -889,6 +889,7 @@ public void RespawnBonusPlayer(CCSPlayerController? player, CommandInfo command)
[ConsoleCommand("css_startpos", "Saves a custom respawn point within the start trigger")]
[ConsoleCommand("css_setresp", "Saves a custom respawn point within the start trigger")]
+ [ConsoleCommand("css_ssp", "Saves a custom respawn point within the start trigger")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void SetRespawnCommand(CCSPlayerController? player, CommandInfo command)
{
@@ -1054,14 +1055,6 @@ public void RespawnPlayerCommand(CCSPlayerController? player, CommandInfo comman
}
}
-
- [ConsoleCommand("say !r", "Alias for css_r to teleport to start")]
- [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
- public void RespawnPlayerAliasCommand(CCSPlayerController? player, CommandInfo command)
- {
- RespawnPlayerCommand(player, command);
- }
-
[ConsoleCommand("css_end", "Teleports you to end")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void EndPlayerCommand(CCSPlayerController? player, CommandInfo command)
diff --git a/src/Commands/ConfigConvars.cs b/src/Commands/ConfigConvars.cs
index e56aea1..26718b7 100644
--- a/src/Commands/ConfigConvars.cs
+++ b/src/Commands/ConfigConvars.cs
@@ -84,46 +84,37 @@ public void SharpTimerGlobalRanksConvar(CCSPlayerController? player, CommandInfo
globalRanksEnabled = bool.TryParse(args, out bool globalRanksEnabledValue) ? globalRanksEnabledValue : args != "0" && globalRanksEnabled;
}
- [ConsoleCommand("sharptimer_global_rank_free_points_enabled", "Whether the plugin should reward players with free points for completing maps without beating their PB (31xMapTier). Default value: true")]
+ [ConsoleCommand("sharptimer_global_rank_min_points_threshold", "Players with Points below this amount will be treated as Unranked. Default value: 1")]
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
- public void SharpTimerGlobalRanksEnableFreeRewardsConvar(CCSPlayerController? player, CommandInfo command)
- {
- string args = command.ArgString;
-
- globalRanksFreePointsEnabled = bool.TryParse(args, out bool globalRanksFreePointsEnabledValue) ? globalRanksFreePointsEnabledValue : args != "0" && globalRanksFreePointsEnabled;
- }
-
- [ConsoleCommand("sharptimer_global_rank_max_free_rewards", "How many times the player should recieve free 'participation' points for finishing the map without a new PB. Default value: 20")]
- [CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
- public void SharpTimerGlobalRanksMaxFreeRewardsConvar(CCSPlayerController? player, CommandInfo command)
+ public void SharpTimerGlobalRanksMinPointsConvar(CCSPlayerController? player, CommandInfo command)
{
string args = command.ArgString;
- if (int.TryParse(args, out int maxFreePoints) && maxFreePoints > 0)
+ if (int.TryParse(args, out int minPoints) && minPoints > 0)
{
- maxGlobalFreePoints = maxFreePoints;
- SharpTimerConPrint($"SharpTimer free 'participation' rewards set to {maxFreePoints} times.");
+ minGlobalPointsForRank = minPoints;
+ SharpTimerConPrint($"SharpTimer min points for rank set to {minPoints} points.");
}
else
{
- SharpTimerConPrint("Invalid free 'participation' rewards value. Please provide a positive float.");
+ SharpTimerConPrint("Invalid min points for rank value. Please provide a positive integer.");
}
}
- [ConsoleCommand("sharptimer_global_rank_min_points_threshold", "Players with Points below this amount will be treated as Unranked. Default value: 1000")]
+ [ConsoleCommand("sharptimer_global_rank_bonus_points_multiplier", "Multiplier for bonus course completion points. Default value: 0.5")]
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
- public void SharpTimerGlobalRanksMinPointsConvar(CCSPlayerController? player, CommandInfo command)
+ public void SharpTimerGlobalRanksBonusPointsConvar(CCSPlayerController? player, CommandInfo command)
{
string args = command.ArgString;
- if (int.TryParse(args, out int minPoints) && minPoints > 0)
+ if (double.TryParse(args, out double bonusMultiplier) && bonusMultiplier > 0)
{
- minGlobalPointsForRank = minPoints;
- SharpTimerConPrint($"SharpTimer min points for rank set to {minPoints} points.");
+ globalPointsBonusMultiplier = bonusMultiplier;
+ SharpTimerConPrint($"SharpTimer bonus points multiplier set to {bonusMultiplier}");
}
else
{
- SharpTimerConPrint("Invalid min points for rank value. Please provide a positive integer.");
+ SharpTimerConPrint("Invalid bonus points multipler. Please provide a positive integer.");
}
}
@@ -1078,7 +1069,7 @@ public void SharpTimerOnlyAMultiplierConvar(CCSPlayerController? player, Command
}
}
- [ConsoleCommand("sharptimer_style_multiplier_onlyw", "Point modifier for onlys. Default value: 1.33")]
+ [ConsoleCommand("sharptimer_style_multiplier_onlys", "Point modifier for onlys. Default value: 1.33")]
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
public void SharpTimerOnlySMultiplierConvar(CCSPlayerController? player, CommandInfo command)
{
@@ -1095,7 +1086,7 @@ public void SharpTimerOnlySMultiplierConvar(CCSPlayerController? player, Command
}
}
- [ConsoleCommand("sharptimer_style_multiplier_onlyw", "Point modifier for onlyd. Default value: 1.33")]
+ [ConsoleCommand("sharptimer_style_multiplier_onlyd", "Point modifier for onlyd. Default value: 1.33")]
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
public void SharpTimerOnlyDMultiplierConvar(CCSPlayerController? player, CommandInfo command)
{
diff --git a/src/DB/DatabaseUtils.cs b/src/DB/DatabaseUtils.cs
index d55f7c1..9969176 100644
--- a/src/DB/DatabaseUtils.cs
+++ b/src/DB/DatabaseUtils.cs
@@ -761,10 +761,10 @@ DO UPDATE SET
upsertCommand!.AddParameterWithValue("@UnixStamp", dBunixStamp);
upsertCommand!.AddParameterWithValue("@SteamID", steamId);
upsertCommand!.AddParameterWithValue("@Style", style);
- if (globalRanksEnabled == true && ((dBtimesFinished <= maxGlobalFreePoints && globalRanksFreePointsEnabled == true) || beatPB)) await SavePlayerPoints(steamId, playerName, playerSlot, playerPoints, dBtimerTicks, beatPB, bonusX, style);
+ if (globalRanksEnabled == true) await SavePlayerPoints(steamId, playerName, playerSlot, playerPoints, dBtimerTicks, beatPB, bonusX, style, dBtimesFinished);
if (style == 0 && (stageTriggerCount != 0 || cpTriggerCount != 0) && bonusX == 0 && enableDb && timerTicks < dBtimerTicks) Server.NextFrame(() => _ = Task.Run(async () => await DumpPlayerStageTimesToJson(player, steamId, playerSlot)));
var prevSRID = await GetMapRecordSteamIDFromDatabase(bonusX, 0, style);
- var prevSR = await GetPreviousPlayerRecordFromDatabase(player, prevSRID.Item1, currentMapNamee, prevSRID.Item2, bonusX, style);
+ var prevSR = await GetPreviousPlayerRecordFromDatabase(prevSRID.Item1, currentMapNamee, prevSRID.Item2, bonusX, style);
await upsertCommand!.ExecuteNonQueryAsync();
Server.NextFrame(() => SharpTimerDebug($"Saved player {(bonusX != 0 ? $"bonus {bonusX} time" : "time")} to database for {playerName} {timerTicks} {DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"));
if (enableDb && IsAllowedPlayer(player)) await RankCommandHandler(player, steamId, playerSlot, playerName, true, style);
@@ -812,9 +812,9 @@ DO UPDATE SET
upsertCommand!.AddParameterWithValue("@SteamID", steamId);
upsertCommand!.AddParameterWithValue("@Style", style);
var prevSRID = await GetMapRecordSteamIDFromDatabase(bonusX, 0, style);
- var prevSR = await GetPreviousPlayerRecordFromDatabase(player, prevSRID.Item1, currentMapNamee, prevSRID.Item2, bonusX, style);
+ var prevSR = await GetPreviousPlayerRecordFromDatabase(prevSRID.Item1, currentMapNamee, prevSRID.Item2, bonusX, style);
await upsertCommand!.ExecuteNonQueryAsync();
- if (globalRanksEnabled == true) await SavePlayerPoints(steamId, playerName, playerSlot, timerTicks, dBtimerTicks, beatPB, bonusX, style);
+ if (globalRanksEnabled == true) await SavePlayerPoints(steamId, playerName, playerSlot, timerTicks, dBtimerTicks, beatPB, bonusX, style, dBtimesFinished);
if (style == 0 && (stageTriggerCount != 0 || cpTriggerCount != 0) && bonusX == 0) Server.NextFrame(() => _ = Task.Run(async () => await DumpPlayerStageTimesToJson(player, steamId, playerSlot)));
Server.NextFrame(() => SharpTimerDebug($"Saved player {(bonusX != 0 ? $"bonus {bonusX} time" : "time")} to database for {playerName} {timerTicks} {DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"));
if (IsAllowedPlayer(player)) await RankCommandHandler(player, steamId, playerSlot, playerName, true, style);
@@ -1487,12 +1487,12 @@ DO UPDATE SET
}
}
- public void GainPointsMessage(string playerName, int newPoints, int playerPoints)
+ public void GainPointsMessage(string playerName, double newPoints, double playerPoints)
{
PrintToChatAll(Localizer["gained_points", playerName, Convert.ToInt32(newPoints - playerPoints), newPoints]);
}
- public async Task SavePlayerPoints(string steamId, string playerName, int playerSlot, int timerTicks, int oldTicks, bool beatPB = false, int bonusX = 0, int style = 0)
+ public async Task SavePlayerPoints(string steamId, string playerName, int playerSlot, int timerTicks, int oldTicks, bool beatPB = false, int bonusX = 0, int style = 0, int completions = 0)
{
SharpTimerDebug($"Trying to set player points in database for {playerName}");
try
@@ -1509,7 +1509,6 @@ public async Task SavePlayerPoints(string steamId, string playerName, int player
bool isVip = false;
string bigGif = "x";
int playerPoints = 0;
- float mapTier = 0.1f;
using (var connection = await OpenConnectionAsync())
{
@@ -1573,13 +1572,49 @@ public async Task SavePlayerPoints(string steamId, string playerName, int player
break;
}
- int newPoints;
+ double newPoints;
- if (!enableStylePoints && style == 0) newPoints = (int)(beatPB == false ? Convert.ToInt32(CalculatePoints(timerTicks, style)! * globalPointsMultiplier!) + playerPoints
- : Convert.ToInt32(CalculatePoints(timerTicks, style)! - CalculatePoints(oldTicks, style) * globalPointsMultiplier! + playerPoints + (310 * (bonusX == 0 ? mapTier : mapTier * 0.5))));
- else if (enableStylePoints) newPoints = (int)(beatPB == false ? Convert.ToInt32(CalculatePoints(timerTicks, style)! * globalPointsMultiplier!) + playerPoints
- : Convert.ToInt32(CalculatePoints(timerTicks, style)! - CalculatePoints(oldTicks, style) * globalPointsMultiplier! + playerPoints + (310 * (bonusX == 0 ? mapTier : mapTier * 0.5))));
- else newPoints = playerPoints;
+ // First calculate basic map completion points based on tier
+ newPoints = CalculateCompletion();
+
+ // Then calculate max points based on times finished
+ double maxPoints = CalculateTier(completions);
+
+ // Then grab the top 10 and calculate distributed points
+ Dictionary sortedRecords = await GetSortedRecordsFromDatabase(10, bonusX, "", style);
+ int rank = 1;
+ bool isTop10 = false;
+ foreach (var kvp in sortedRecords.Take(10))
+ {
+ if (steamId == kvp.Key)
+ {
+ newPoints += CalculateTop10(maxPoints, rank);
+ isTop10 = true;
+ }
+ rank++;
+ }
+
+ // If not in top 10, calculate groups based on percentile
+ if(!isTop10)
+ newPoints += CalculateGroups(maxPoints, await GetPlayerMapPercentile(steamId, playerName, bonusX, style));
+
+ // Apply style multiplier if enabled
+ if(enableStylePoints)
+ newPoints *= GetStyleMultiplier(style);
+
+ // Apply bonus multiplier if bonus completion
+ if(bonusX != 0)
+ newPoints *= globalPointsBonusMultiplier;
+
+ // Hastily round the new points to prevent 123.4567890123456789 points
+ newPoints = Math.Round(newPoints);
+
+ // Zero out new points if style points are disabled and player is using styles
+ if(!enableStylePoints && style != 0)
+ newPoints = 0;
+
+ // Add final calculation to players points
+ newPoints += playerPoints;
await row.CloseAsync();
// Update or insert the record
@@ -1660,13 +1695,49 @@ DO UPDATE SET
Server.NextFrame(() => SharpTimerDebug($"No player stats yet"));
- int newPoints;
+ double newPoints;
+
+ // First calculate basic map completion points based on tier
+ newPoints = CalculateCompletion();
- if (!enableStylePoints && style == 0) newPoints = (int)(beatPB == false ? Convert.ToInt32(CalculatePoints(timerTicks, style)! * globalPointsMultiplier!) + playerPoints
- : Convert.ToInt32(CalculatePoints(timerTicks, style)! - CalculatePoints(oldTicks, style) * globalPointsMultiplier! + playerPoints + (310 * (bonusX == 0 ? mapTier : mapTier * 0.5))));
- else if (enableStylePoints) newPoints = (int)(beatPB == false ? Convert.ToInt32(CalculatePoints(timerTicks, style)! * globalPointsMultiplier!) + playerPoints
- : Convert.ToInt32(CalculatePoints(timerTicks, style)! - CalculatePoints(oldTicks, style) * globalPointsMultiplier! + playerPoints + (310 * (bonusX == 0 ? mapTier : mapTier * 0.5))));
- else newPoints = playerPoints;
+ // Then calculate max points based on times finished
+ double maxPoints = CalculateTier(completions);
+
+ // Then grab the top 10 and calculate distributed points
+ Dictionary sortedRecords = await GetSortedRecordsFromDatabase(10, bonusX, "", style);
+ int rank = 1;
+ bool isTop10 = false;
+ foreach (var kvp in sortedRecords.Take(10))
+ {
+ if (steamId == kvp.Key)
+ {
+ newPoints += CalculateTop10(maxPoints, rank);
+ isTop10 = true;
+ }
+ rank++;
+ }
+
+ // If not in top 10, calculate groups based on percentile
+ if(!isTop10)
+ newPoints += CalculateGroups(maxPoints, await GetPlayerMapPercentile(steamId, playerName, bonusX, style));
+
+ // Apply style multiplier if enabled
+ if(enableStylePoints)
+ newPoints *= GetStyleMultiplier(style);
+
+ // Apply bonus multiplier if bonus completion
+ if(bonusX != 0)
+ newPoints *= globalPointsBonusMultiplier;
+
+ // Hastily round the new points to prevent 123.4567890123456789 points
+ newPoints = Math.Round(newPoints);
+
+ // Zero out new points if style points are disabled and player is using styles
+ if(!enableStylePoints && style != 0)
+ newPoints = 0;
+
+ // Add final calculation to players points
+ newPoints += playerPoints;
await row.CloseAsync();
@@ -1732,6 +1803,58 @@ DO UPDATE SET
}
}
+ public async Task PlayerCompletions(string steamId, int bonusX = 0, int style = 0)
+ {
+ try
+ {
+ //if ((bonusX == 0 && !playerTimers[playerSlot].IsTimerRunning) || (bonusX != 0 && !playerTimers[playerSlot].IsBonusTimerRunning)) return;
+ string currentMapNamee = bonusX == 0 ? currentMapName! : $"{currentMapName}_bonus{bonusX}";
+
+ using (var connection = await OpenConnectionAsync())
+ {
+ await CreatePlayerRecordsTableAsync(connection);
+
+ string? selectQuery;
+ DbCommand? selectCommand;
+ switch (dbType)
+ {
+ case DatabaseType.MySQL:
+ selectQuery = @"SELECT TimesFinished FROM PlayerRecords WHERE MapName = @MapName AND SteamID = @SteamID AND Style = @Style";
+ selectCommand = new MySqlCommand(selectQuery, (MySqlConnection)connection);
+ break;
+ case DatabaseType.PostgreSQL:
+ selectQuery = @"SELECT ""TimesFinished"" FROM ""PlayerRecords"" WHERE ""MapName"" = @MapName AND ""SteamID"" = @SteamID AND ""Style"" = @Style";
+ selectCommand = new NpgsqlCommand(selectQuery, (NpgsqlConnection)connection);
+ break;
+ case DatabaseType.SQLite:
+ selectQuery = @"SELECT TimesFinished FROM PlayerRecords WHERE MapName = @MapName AND SteamID = @SteamID AND Style = @Style";
+ selectCommand = new SQLiteCommand(selectQuery, (SQLiteConnection)connection);
+ break;
+ default:
+ selectQuery = null;
+ selectCommand = null;
+ break;
+ }
+ // Check if the record already exists or has a higher timer value
+ selectCommand!.AddParameterWithValue("@MapName", currentMapNamee);
+ selectCommand!.AddParameterWithValue("@SteamID", steamId);
+ selectCommand!.AddParameterWithValue("@Style", style);
+
+ var row = await selectCommand!.ExecuteReaderAsync();
+
+ if (row.Read())
+ {
+ return row.GetInt32("TimesFinished");
+ }
+ }
+ }
+ catch(Exception ex)
+ {
+ Server.NextFrame(() => SharpTimerError($"Error getting player completions from database for id:{steamId}: {ex}"));
+ }
+ return 0;
+ }
+
public async Task PrintTop10PlayerPoints(CCSPlayerController player)
{
try
@@ -2184,16 +2307,11 @@ public async Task GetReplayVIPGif(string steamId, int playerSlot)
}
}
- public async Task GetPreviousPlayerRecordFromDatabase(CCSPlayerController? player, string steamId, string currentMapName, string playerName, int bonusX = 0, int style = 0)
+ public async Task GetPreviousPlayerRecordFromDatabase(string steamId, string currentMapName, string playerName, int bonusX = 0, int style = 0)
{
SharpTimerDebug($"Trying to get Previous {(bonusX != 0 ? $"bonus {bonusX} time" : "time")} from database for {playerName}");
try
{
- if (!IsAllowedClient(player))
- {
- return 0;
- }
-
string currentMapNamee = bonusX == 0 ? currentMapName : $"{currentMapName}_bonus{bonusX}";
using (IDbConnection connection = await OpenConnectionAsync())
@@ -2471,6 +2589,106 @@ public async Task> GetSortedRecordsFromDatabase
}
return [];
}
+ public async Task> GetAllSortedRecordsFromDatabase(int limit = 0, int bonusX = 0, int style = 0)
+ {
+ SharpTimerDebug($"Trying GetSortedRecords {(bonusX != 0 ? $"bonus {bonusX}" : "")} from database");
+ using (var connection = await OpenConnectionAsync())
+ {
+ try
+ {
+ await CreatePlayerRecordsTableAsync(connection);
+
+ // Retrieve and sort records for the current map
+ string? selectQuery;
+ DbCommand? selectCommand;
+ if (limit != 0)
+ {
+ switch (dbType)
+ {
+ case DatabaseType.MySQL:
+ selectQuery = $@"SELECT SteamID, PlayerName, TimerTicks, MapName FROM PlayerRecords WHERE Style = @Style ORDER BY TimerTicks ASC LIMIT {limit}";
+ selectCommand = new MySqlCommand(selectQuery, (MySqlConnection)connection);
+ break;
+ case DatabaseType.PostgreSQL:
+ selectQuery = $@"SELECT ""SteamID"", ""PlayerName"", ""TimerTicks"", ""MapName"" FROM ""PlayerRecords"" WHERE ""Style"" = @Style ORDER BY ""TimerTicks"" ASC LIMIT {limit}";
+ selectCommand = new NpgsqlCommand(selectQuery, (NpgsqlConnection)connection);
+ break;
+ case DatabaseType.SQLite:
+ selectQuery = $@"SELECT SteamID, PlayerName, TimerTicks, MapName FROM PlayerRecords WHERE Style = @Style ORDER BY TimerTicks ASC LIMIT {limit}";
+ selectCommand = new SQLiteCommand(selectQuery, (SQLiteConnection)connection);
+ break;
+ default:
+ selectQuery = null;
+ selectCommand = null;
+ break;
+ }
+ }
+ else
+ {
+ switch (dbType)
+ {
+ case DatabaseType.MySQL:
+ selectQuery = @"SELECT SteamID, PlayerName, TimerTicks, MapName FROM PlayerRecords WHERE Style = @Style";
+ selectCommand = new MySqlCommand(selectQuery, (MySqlConnection)connection);
+ break;
+ case DatabaseType.PostgreSQL:
+ selectQuery = @"SELECT ""SteamID"", ""PlayerName"", ""TimerTicks"", ""MapName"" FROM ""PlayerRecords"" WHERE ""Style"" = @Style";
+ selectCommand = new NpgsqlCommand(selectQuery, (NpgsqlConnection)connection);
+ break;
+ case DatabaseType.SQLite:
+ selectQuery = @"SELECT SteamID, PlayerName, TimerTicks, MapName FROM PlayerRecords WHERE Style = @Style";
+ selectCommand = new SQLiteCommand(selectQuery, (SQLiteConnection)connection);
+ break;
+ default:
+ selectQuery = null;
+ selectCommand = null;
+ break;
+ }
+ }
+ using (selectCommand)
+ {
+ selectCommand!.AddParameterWithValue("@Style", style);
+ using (var reader = await selectCommand!.ExecuteReaderAsync())
+ {
+ Dictionary> sortedRecords = new Dictionary>();
+ while (await reader.ReadAsync())
+ {
+ string steamId = reader.GetString(0);
+ string playerName = reader.IsDBNull(1) ? "Unknown" : reader.GetString(1);
+ int timerTicks = reader.GetInt32(2);
+ string mapname = reader.GetString(3);
+ if (!sortedRecords.ContainsKey(steamId))
+ {
+ // If steamId doesn't exist, create a new list for the steamId
+ sortedRecords[steamId] = new List();
+ }
+ sortedRecords[steamId].Add(new PlayerRecord
+ {
+ PlayerName = playerName,
+ SteamID = steamId,
+ TimerTicks = timerTicks,
+ MapName = mapname
+ });
+ }
+
+ var sortedList = sortedRecords
+ .SelectMany(recordEntry => recordEntry.Value)
+ .OrderBy(record => record.TimerTicks)
+ .ToList();
+
+ SharpTimerDebug($"Got GetSortedRecords {(bonusX != 0 ? $"bonus {bonusX}" : "")} from database");
+
+ return sortedList;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ SharpTimerError($"Error getting all sorted records from database: {ex.Message}");
+ }
+ }
+ return [];
+ }
public async Task> GetSortedStageRecordsFromDatabase(int stage, int limit = 0, int bonusX = 0, string mapName = "")
{
SharpTimerDebug($"Trying GetSortedStageRecords {(bonusX != 0 ? $"bonus {bonusX}" : "")} from database");
@@ -2650,17 +2868,17 @@ public async Task ImportPlayerPoints()
{
try
{
- var sortedRecords = await GetSortedRecordsFromDatabase();
+ var sortedRecords = await GetAllSortedRecordsFromDatabase();
- foreach (var kvp in sortedRecords)
+ foreach (var record in sortedRecords)
{
- string playerSteamID = kvp.Key;
- string playerName = kvp.Value.PlayerName!;
- int timerTicks = kvp.Value.TimerTicks;
+ string playerSteamID = record.SteamID!;
+ string playerName = record.PlayerName!;
+ int timerTicks = record.TimerTicks;
if (enableDb && globalRanksEnabled == true)
{
- _ = Task.Run(async () => await SavePlayerPoints(playerSteamID, playerName, -1, timerTicks, 0, false, 0, 0));
+ _ = Task.Run(async () => await SavePlayerPoints(playerSteamID, playerName, -1, timerTicks, 0, false, 0, 0, await PlayerCompletions(playerSteamID, 0, 0)));
await Task.Delay(100);
}
}
@@ -2898,74 +3116,5 @@ ON CONFLICT (MapName, SteamID, Style) DO UPDATE
SharpTimerError($"Error adding JSON times to the database: {ex.Message}");
}
}
-
- [ConsoleCommand("css_databasetojson", " ")]
- [RequiresPermissions("@css/root")]
- [CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
- public void ExportDatabaseToJsonCommand(CCSPlayerController? player, CommandInfo command)
- {
- _ = Task.Run(ExportDatabaseToJsonAsync);
- }
-
- public async Task ExportDatabaseToJsonAsync()
- {
- string recordsDirectoryNamee = "SharpTimer/PlayerRecords";
- string playerRecordsPathh = Path.Combine(gameDir!, "csgo", "cfg", recordsDirectoryNamee);
-
- try
- {
- string connectionString = await GetConnectionStringFromConfigFile();
-
- using (var connection = new MySqlConnection(connectionString))
- {
- await connection.OpenAsync();
-
- string selectQuery = "SELECT SteamID, PlayerName, TimerTicks, MapName FROM PlayerRecords";
- using (var selectCommand = new MySqlCommand(selectQuery, connection))
- {
- using (var reader = await selectCommand.ExecuteReaderAsync())
- {
- while (await reader.ReadAsync())
- {
- string steamId = reader.GetString(0);
- string playerName = reader.GetString(1);
- int timerTicks = reader.GetInt32(2);
- string mapName = reader.GetString(3);
-
- Directory.CreateDirectory(playerRecordsPathh);
-
- Dictionary records;
- string filePath = Path.Combine(playerRecordsPathh, $"{mapName}.json");
- if (File.Exists(filePath))
- {
- string existingJson = await File.ReadAllTextAsync(filePath);
- records = JsonSerializer.Deserialize>(existingJson) ?? [];
- }
- else
- {
- records = [];
- }
-
- records[steamId] = new PlayerRecord
- {
- PlayerName = playerName,
- TimerTicks = timerTicks
- };
-
- string updatedJson = JsonSerializer.Serialize(records, jsonSerializerOptions);
-
- await File.WriteAllTextAsync(filePath, updatedJson);
-
- SharpTimerDebug($"Player records for map {mapName} successfully exported to JSON.");
- }
- }
- }
- }
- }
- catch (Exception ex)
- {
- SharpTimerError($"Error exporting player records to JSON: {ex.Message}");
- }
- }
}
}
\ No newline at end of file
diff --git a/src/Features/JumpStats.cs b/src/Features/JumpStats.cs
index 2317d71..08fd704 100644
--- a/src/Features/JumpStats.cs
+++ b/src/Features/JumpStats.cs
@@ -220,7 +220,7 @@ public void OnSyncTick(CCSPlayerController player, PlayerButtons? buttons, QAngl
bool left = false;
bool right = false;
- #pragma warning disable CS0219 // annoyence
+ #pragma warning disable CS0219 // annoyance
bool leftRight = false;
#pragma warning restore CS0219
@@ -236,14 +236,15 @@ public void OnSyncTick(CCSPlayerController player, PlayerButtons? buttons, QAngl
playerTimer.Rotation.Add(newEyeAngle);
playerTimer.TotalSync++;
- //Check left goodsync
- if (playerTimer.Rotation != null && playerTimer.Rotation.Count > 1 && eyeangle.Y > playerTimer.Rotation[playerTimer.TotalSync - 2].Y && left)
- playerTimer.GoodSync++;
-
- //Check right goodsync
- if (playerTimer.Rotation != null && playerTimer.Rotation.Count > 1 && eyeangle.Y < playerTimer.Rotation[playerTimer.TotalSync - 2].Y && right)
- playerTimer.GoodSync++;
-
+ if (playerTimer.Rotation != null && playerTimer.Rotation.Count > 1 && playerTimer.TotalSync >= 2)
+ {
+ if (eyeangle.Y > playerTimer.Rotation[playerTimer.TotalSync - 2].Y && left)
+ playerTimer.GoodSync++;
+
+ if (eyeangle.Y < playerTimer.Rotation[playerTimer.TotalSync - 2].Y && right)
+ playerTimer.GoodSync++;
+ }
+
playerTimer.Sync = Math.Round((float)playerTimers[player.Slot].GoodSync / playerTimers[player.Slot].TotalSync * 100, 0);
}
catch (Exception ex)
@@ -443,4 +444,4 @@ public void PrintJS(CCSPlayerController player, PlayerJumpStats playerJumpStat,
player.PrintToConsole($" {Localizer["js_msg2", Math.Round(playerJumpStat.jumpFrames.Last().MaxHeight, 2), GetMaxWidth(playerpos, playerJumpStat), playerJumpStat.WTicks, sync]}");
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Features/Points.cs b/src/Features/Points.cs
new file mode 100644
index 0000000..b77b323
--- /dev/null
+++ b/src/Features/Points.cs
@@ -0,0 +1,145 @@
+using System.Diagnostics.Eventing.Reader;
+using System.Net;
+using System.Text;
+using System.Text.Json;
+using System.Xml;
+using CounterStrikeSharp.API.Core;
+
+namespace SharpTimer
+{
+ public partial class SharpTimer
+ {
+
+ // Step 1
+ // This function calculates basic map completion points
+ // Start at 25 for T1, then 50, then 100, etc..
+ public int CalculateCompletion()
+ {
+ if (currentMapTier is not null)
+ {
+ switch (currentMapTier)
+ {
+ case 1:
+ return 25;
+ case 2:
+ return 50;
+ case 3:
+ return 100;
+ case 4:
+ return 200;
+ case 5:
+ return 400;
+ case 6:
+ return 600;
+ case 7:
+ return 800;
+ case 8:
+ return 1000;
+
+ default:
+ return 0;
+ }
+ }else{
+ return 25;
+ }
+ }
+
+ // Step 2
+ // This function calculates tier ranking
+ // This is the first step in calculating the players specific ranking on the map
+ public double CalculateTier(int completions)
+ {
+ // Define max WR points for each tier (fallback to t1)
+ int maxWR;
+ int tier;
+ if (currentMapTier is not null)
+ {
+ maxWR = 250 * (int)currentMapTier;
+ tier = (int)currentMapTier;
+ }
+ else
+ {
+ maxWR = 250;
+ tier = 1;
+ }
+
+ switch (tier)
+ {
+ case 1:
+ return Math.Max(maxWR, 58.5 + (1.75 * completions) / 6);
+ case 2:
+ return Math.Max(maxWR, 82.15 + (2.8 * completions) / 5);
+ case 3:
+ return Math.Max(maxWR, 117 + (3.5 * completions) / 4);
+ case 4:
+ return Math.Max(maxWR, 164.25 + (5.74 * completions) / 4);
+ case 5:
+ return Math.Max(maxWR, 234 + (7 * completions) / 4);
+ case 6:
+ return Math.Max(maxWR, 328 + (14 * completions) / 4);
+ case 7:
+ return Math.Max(maxWR, 420 + (21 * completions) / 4);
+ case 8:
+ return Math.Max(maxWR, 560 + (30 * completions) / 4);
+
+ default:
+ return 0;
+ }
+ }
+
+ // Step 3
+ // This function takes the WR points from above and distributes them among the top 10
+ public double CalculateTop10(double points, int position)
+ {
+ switch(position)
+ {
+ case 1:
+ return points;
+ case 2:
+ return points * 0.8;
+ case 3:
+ return points * 0.75;
+ case 4:
+ return points * 0.7;
+ case 5:
+ return points * 0.65;
+ case 6:
+ return points * 0.6;
+ case 7:
+ return points * 0.55;
+ case 8:
+ return points * 0.5;
+ case 9:
+ return points * 0.45;
+ case 10:
+ return points * 0.4;
+
+ default:
+ return 0;
+ }
+ }
+
+ // Step 4
+ // This function sorts players below top10, but above 50th percentile, into groups
+ // These groups get less points than top 10, but still get points!
+ public double CalculateGroups(double points, double percentile)
+ {
+ switch(percentile)
+ {
+ case double p when p <= 3.125:
+ return points * 0.25; // Group 1
+ case double p when p <= 6.25:
+ return (points * 0.25) / 1.5; // Group 2
+ case double p when p <= 12.5:
+ return ((points * 0.25) / 1.5) / 1.5; // Group 3
+ case double p when p <= 25:
+ return (((points * 0.25) / 1.5) / 1.5) / 1.5; // Group 4
+ case double p when p <= 50:
+ return (((((points * 0.25) / 1.5) / 1.5) / 1.5) / 1.5); // Group 5
+
+ default:
+ return 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/ReplayUtils.cs b/src/Features/ReplayUtils.cs
index bd849b5..05e6d77 100644
--- a/src/Features/ReplayUtils.cs
+++ b/src/Features/ReplayUtils.cs
@@ -289,7 +289,7 @@ private async Task SpawnReplayBot()
Server.ExecuteCommand("bot_zombie 1");
Server.ExecuteCommand("sv_cheats 0");
- AddTimer(0.5f, () =>
+ AddTimer(3.0f, () =>
{
foundReplayBot = false;
SharpTimerDebug($"Trying to find replay bot!");
@@ -339,7 +339,7 @@ private void OnReplayBotConnect(CCSPlayerController bot)
var botSlot = bot.Slot;
var botName = bot.PlayerName;
- AddTimer(5.0f, () =>
+ AddTimer(3.0f, () =>
{
OnPlayerConnect(bot, true);
connectedReplayBots[botSlot] = new CCSPlayerController(bot.Handle);
diff --git a/src/Player/PlayerOnTick.cs b/src/Player/PlayerOnTick.cs
index 768c8bb..1447eb2 100644
--- a/src/Player/PlayerOnTick.cs
+++ b/src/Player/PlayerOnTick.cs
@@ -86,15 +86,13 @@ public void PlayerOnTick()
string formattedPlayerPre = Math.Round(ParseVector(playerTimer.PreSpeed ?? "0 0 0").Length2D()).ToString("000");
string playerTime = FormatTime(timerTicks);
string playerBonusTime = FormatTime(playerTimer.BonusTimerTicks);
-
string timerLine = isBonusTimerRunning
- ? $" Bonus #{playerTimer.BonusStage} Timer: {playerBonusTime}
"
- : isTimerRunning
- ? $" Timer: {playerTime} {GetPlayerPlacement(player)}{((playerTimer.CurrentMapStage != 0 && useStageTriggers == true) ? $" {playerTimer.CurrentMapStage}/{stageTriggerCount}" : "")}
"
- : playerTimer.IsReplaying
- ? $" ◉ REPLAY {FormatTime(playerReplays[playerSlot].CurrentPlaybackFrame)}
"
- : "";
-
+ ? $" Bonus #{playerTimer.BonusStage} Timer: {playerBonusTime}
"
+ : isTimerRunning
+ ? $" Timer: {playerTime} ({GetPlayerPlacement(player)}){((playerTimer.CurrentMapStage != 0 && useStageTriggers == true) ? $" {playerTimer.CurrentMapStage}/{stageTriggerCount}" : "")}
"
+ : playerTimer.IsReplaying
+ ? $" ◉ REPLAY {FormatTime(playerReplays[playerSlot].CurrentPlaybackFrame)}
"
+ : "";
string veloLine;
if (playerVel <= 349)
@@ -141,9 +139,7 @@ public void PlayerOnTick()
{
veloLine = $" {(playerTimer.IsTester ? playerTimer.TesterSmolGif : "")}Speed: {(playerTimer.IsReplaying ? "{formattedPlayerVel} ({formattedPlayerPre}){(playerTimer.IsTester ? playerTimer.TesterSmolGif : "")}
";
}
-
-
-
+
string syncLine = $"Sync: {playerTimer.Sync}%
";
string infoLine = "";
@@ -173,41 +169,34 @@ public void PlayerOnTick()
if (playerTimer.MovementService!.OldJumpPressed == true) playerTimer.MovementService.OldJumpPressed = false;
- // enable hud if isTimerRunning, i added this because of the Weapon Paints Menu update
- string hudContent = "";
- bool previousTimerState = false; // Track the previous state of isTimerRunning
-
- // Check if the timer is running
- if (isTimerRunning)
- {
- // Build the HUD content if the timer is running
- hudContent = (hudEnabled ? timerLine +
- (VelocityHudEnabled ? veloLine : "") +
- (StrafeHudEnabled && !playerTimer.IsReplaying ? syncLine : "") +
- infoLine : "") +
- (keyEnabled && !playerTimer.IsReplaying ? keysLineNoHtml : "") +
- ((playerTimer.IsTester && !playerTimer.IsReplaying) ? $"{(!keyEnabled ? "
" : "")}" + playerTimer.TesterBigGif : "") +
- ((playerTimer.IsVip && !playerTimer.IsTester && !playerTimer.IsReplaying) ? $"{(!keyEnabled ? "
" : "")}" + $"
" : "") +
- ((playerTimer.IsReplaying && playerTimer.VipReplayGif != "x") ? playerTimer.VipReplayGif : "");
-
- // Print the HUD content when timer is running
- player.PrintToCenterHtml(hudContent);
- }
- else
- {
- // Clear the HUD when the timer stops
- if (previousTimerState == true) // Only clear if it was previously running
- {
- player.PrintToCenterHtml(""); // Clear HUD content
- }
- }
-
- // Update the previous timer state
- previousTimerState = isTimerRunning;
-
-
+// enable hud if isTimerRunning, updated logic based on Weapon Paints Menu update
+string hudContent = "";
+bool previousTimerState = false; // Track the previous state of isTimerRunning
+// Check if the timer is running
+if (isTimerRunning)
+{
+ // Build the HUD content if the timer is running
+ hudContent = (hudEnabled ? timerLine + veloLine + infoLine : "") +
+ (keyEnabled ? keysLineNoHtml : "") +
+ ((playerTimer.IsTester && !playerTimer.IsReplaying) ? $"{(!keyEnabled ? "
" : "")}" + playerTimer.TesterBigGif : "") +
+ ((playerTimer.IsVip && !playerTimer.IsTester && !playerTimer.IsReplaying) ? $"{(!keyEnabled ? "
" : "")}" + $"
" : "") +
+ ((playerTimer.IsReplaying && playerTimer.VipReplayGif != "x") ? playerTimer.VipReplayGif : "");
+
+ // Print the HUD content when timer is running
+ player.PrintToCenterHtml(hudContent);
+}
+else
+{
+ // Clear the HUD when the timer stops
+ if (previousTimerState == true) // Only clear if it was previously running
+ {
+ player.PrintToCenterHtml(""); // Clear HUD content
+ }
+}
+// Update the previous timer state
+previousTimerState = isTimerRunning;
if (isTimerRunning)
{
@@ -364,16 +353,19 @@ public void PlayerOnTick()
private string GetMainMapInfoLine(PlayerTimerInfo playerTimer)
{
- return !playerTimer.IsReplaying
- ? $"" +
-
- $"{playerTimer.CachedPB} " +
- $"({playerTimer.CachedMapPlacement})" +
- $"{(RankIconsEnabled ? $" | " : "")}" +
- $"{((MapNameHudEnabled && currentMapType == null && currentMapTier == null) ? $" | {currentMapName}" : "")}" +
- $""
+return !playerTimer.IsReplaying
+ ? $"" +
+ $"{playerTimer.CachedPB} " +
+ $"({playerTimer.CachedMapPlacement})" +
+ $"{(RankIconsEnabled ? $" | " : "")}" +
+ $"{(enableStyles ? $" | {GetNamedStyle(playerTimer.currentStyle)}" : "")}" +
+ $"{((MapTierHudEnabled && currentMapTier != null) ? $" | Tier: {currentMapTier}" : "")}" +
+ $" | {playerTimer.CachedRank}" +
+ $"{((MapTypeHudEnabled && currentMapType != null) ? $" | {currentMapType}" : "")}" +
+ $"{((MapNameHudEnabled && currentMapType == null && currentMapTier == null) ? $" | {currentMapName}" : "")}" +
+ $"
[CS2SURF.PRO]"
+ : $"{playerTimer.ReplayHUDString}";
- : $" {playerTimer.ReplayHUDString}";
}
private string GetBonusInfoLine(PlayerTimerInfo playerTimer)
@@ -426,7 +418,7 @@ public void SpectatorOnTick(CCSPlayerController player)
string timerLine = isBonusTimerRunning
? $" Bonus #{playerTimer.BonusStage} Timer: {playerBonusTime}
"
: isTimerRunning
- ? $" Timer: {playerTime} ({GetPlayerPlacement(target)}){((playerTimer.CurrentMapStage != 0 && useStageTriggers == true) ? $" {playerTimer.CurrentMapStage}/{stageTriggerCount}" : "")}
"
+ ? $" Timer: {playerTime} ({GetPlayerPlacement(target)}){((playerTimer.CurrentMapStage != 0 && useStageTriggers == true) ? $" {playerTimer.CurrentMapStage}/{stageTriggerCount}" : "")}
"
: playerTimer.IsReplaying
? $" ◉ REPLAY {FormatTime(playerReplays[target.Slot].CurrentPlaybackFrame)}
"
: "";
diff --git a/src/Player/PlayerUtils.cs b/src/Player/PlayerUtils.cs
index a6cdfb5..e49d054 100644
--- a/src/Player/PlayerUtils.cs
+++ b/src/Player/PlayerUtils.cs
@@ -229,14 +229,8 @@ private void RemovePlayerCollision(CCSPlayerController? player)
return (0, string.Empty);
}
- public async Task GetPreviousPlayerRecord(CCSPlayerController? player, string steamId, int bonusX = 0)
+ public async Task GetPreviousPlayerRecord(string steamId, int bonusX = 0)
{
- if (!IsAllowedPlayer(player))
- {
- SharpTimerDebug("Player not allowed.");
- return 0;
- }
-
string currentMapNamee = bonusX == 0 ? currentMapName! : $"{currentMapName}_bonus{bonusX}";
string mapRecordsFileName = $"{currentMapNamee}.json";
string mapRecordsPath = Path.Combine(playerRecordsPath!, mapRecordsFileName);
@@ -297,7 +291,7 @@ public string GetPlayerPlacement(CCSPlayerController? player)
}
}
- public async Task GetPlayerMapPlacementWithTotal(CCSPlayerController? player, string steamId, string playerName, bool getRankImg = false, bool getPlacementOnly = false, int bonusX = 0, int style = 0)
+ public async Task GetPlayerMapPlacementWithTotal(CCSPlayerController? player, string steamId, string playerName, bool getRankImg = false, bool getPlacementOnly = false, int bonusX = 0, int style = 0, bool getPercentileOnly = false)
{
try
{
@@ -306,7 +300,7 @@ public async Task GetPlayerMapPlacementWithTotal(CCSPlayerController? pl
string currentMapNamee = bonusX == 0 ? currentMapName! : $"{currentMapName}_bonus{bonusX}";
- int savedPlayerTime = enableDb ? await GetPreviousPlayerRecordFromDatabase(player, steamId, currentMapName!, playerName, bonusX, style) : await GetPreviousPlayerRecord(player, steamId, bonusX);
+ int savedPlayerTime = enableDb ? await GetPreviousPlayerRecordFromDatabase(steamId, currentMapName!, playerName, bonusX, style) : await GetPreviousPlayerRecord(steamId, bonusX);
if (savedPlayerTime == 0)
return getRankImg ? UnrankedIcon : UnrankedTitle;
@@ -325,6 +319,28 @@ public async Task GetPlayerMapPlacementWithTotal(CCSPlayerController? pl
return UnrankedTitle;
}
}
+ public async Task GetPlayerMapPercentile(string steamId, string playerName, int bonusX = 0, int style = 0)
+ {
+ try
+ {
+ string currentMapNamee = bonusX == 0 ? currentMapName! : $"{currentMapName}_bonus{bonusX}";
+
+ int savedPlayerTime = enableDb ? await GetPreviousPlayerRecordFromDatabase(steamId, currentMapName!, playerName, bonusX, style) : await GetPreviousPlayerRecord(steamId, bonusX);
+
+ Dictionary sortedRecords = enableDb ? await GetSortedRecordsFromDatabase(0, bonusX, "", style) : await GetSortedRecords();
+
+ int placement = sortedRecords.Count(kv => kv.Value.TimerTicks < savedPlayerTime) + 1;
+ int totalPlayers = sortedRecords.Count;
+ double percentage = (double)placement / totalPlayers * 100;
+
+ return percentage;
+ }
+ catch (Exception ex)
+ {
+ SharpTimerError($"Error in GetPlayerMapPercentile: {ex}");
+ return 0;
+ }
+ }
public async Task GetPlayerStagePlacementWithTotal(CCSPlayerController? player, string steamId, string playerName, int stage, bool getRankImg = false, bool getPlacementOnly = false, int bonusX = 0)
{
try
@@ -463,7 +479,7 @@ public async Task PrintMapTimeToChat(CCSPlayerController player, string steamID,
Server.NextFrame(() =>
{
if (IsAllowedPlayer(player) && timesFinished > maxGlobalFreePoints && globalRanksFreePointsEnabled == true && oldticks < newticks)
- PrintToChat(player, Localizer["reached_max_free", maxGlobalFreePoints]);
+ //PrintToChat(player, Localizer["reached_max_free", maxGlobalFreePoints]);
if (newSR)
{
diff --git a/src/Plugin/Classes.cs b/src/Plugin/Classes.cs
index 97298c7..4055234 100644
--- a/src/Plugin/Classes.cs
+++ b/src/Plugin/Classes.cs
@@ -274,6 +274,8 @@ public class IndexedReplayFrames
public class PlayerRecord
{
public string? PlayerName { get; set; }
+ public string? SteamID { get; set; }
+ public string? MapName { get; set; }
public int TimerTicks { get; set; }
}
diff --git a/src/Plugin/Globals.cs b/src/Plugin/Globals.cs
index 9b7d2ae..daf0222 100644
--- a/src/Plugin/Globals.cs
+++ b/src/Plugin/Globals.cs
@@ -27,7 +27,7 @@ public partial class SharpTimer
public string compileTimeStamp = new DateTime(CompileTimeStamp.CompileTime, DateTimeKind.Utc).ToString();
public override string ModuleName => "SharpTimer";
- public override string ModuleVersion => $"0.3.0t";
+ public override string ModuleVersion => $"0.3.1c";
public override string ModuleAuthor => "dea https://github.com/deabb/";
public override string ModuleDescription => "A CS2 Timer Plugin";
@@ -125,10 +125,11 @@ public partial class SharpTimer
public bool globalRanksFreePointsEnabled = true;
public int maxGlobalFreePoints = 20;
public float? globalPointsMultiplier = 1.0f;
- public int minGlobalPointsForRank = 1000;
+ public int minGlobalPointsForRank = 1;
+ public double globalPointsBonusMultiplier = 0.5;
public bool displayChatTags = true;
public bool displayScoreboardTags = true;
- public string customVIPTag = "[VIP]";
+ public string customVIPTag = "[VIP] ";
//public string vipGifHost = "https://files.catbox.moe";
public bool useTriggers = true;
diff --git a/src/Plugin/Utils.cs b/src/Plugin/Utils.cs
index 7486651..78d86fa 100644
--- a/src/Plugin/Utils.cs
+++ b/src/Plugin/Utils.cs
@@ -287,38 +287,6 @@ private static string FormatSpeedDifferenceFromString(string currentSpeed, strin
}
}
- public double CalculatePoints(int timerTicks, int style)
- {
- double basePoints = 10000.0;
- double timeFactor = 0.0001;
- double tierMult = 0.1;
- double styleMult = GetStyleMultiplier(style);
-
- if (currentMapTier != null)
- {
- tierMult = (double)(currentMapTier * 0.1);
- }
-
- double points = basePoints / (timerTicks * timeFactor);
- return points * tierMult * styleMult;
- }
-
- public double CalculatePBPoints(int timerTicks, int style)
- {
- double basePoints = 10000.0;
- double timeFactor = 0.01;
- double tierMult = 0.1;
- double styleMult = GetStyleMultiplier(style);
-
- if (currentMapTier != null)
- {
- tierMult = (double)(currentMapTier * 0.1);
- }
-
- double points = basePoints / (timerTicks * timeFactor);
- return points * tierMult * styleMult;
- }
-
string ParseColorToSymbol(string input)
{
Dictionary colorNameSymbolMap = new(StringComparer.OrdinalIgnoreCase)
@@ -842,7 +810,7 @@ private void OnMapStartHandler(string mapName)
if (enableReplays && enableSRreplayBot)
{
- AddTimer(8.0f, () =>
+ AddTimer(10.0f, () =>
{
if (ConVar.Find("mp_force_pick_time")!.GetPrimitiveValue() == 1.0)
_ = Task.Run(async () => await SpawnReplayBot());