Merge pull request #6 from edgegamers/dev

Update ranking system
This commit is contained in:
Isaac
2025-03-06 19:39:18 -08:00
committed by GitHub
9 changed files with 243 additions and 25 deletions

View File

@@ -49,23 +49,52 @@ jobs:
mkdir -p publish/addons/counterstrikesharp/plugins/SharpTimer/
find ./publish -mindepth 1 -maxdepth 1 ! -name 'cfg' ! -name 'gamedata' -exec mv {} publish/addons/counterstrikesharp/plugins/SharpTimer/ \;
mv gamedata/ ./publish/addons/counterstrikesharp/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: SharpTimer
path: publish/*
if-no-files-found: error
- name: Set version tag and zip file name
id: version
run: |
VERSION="v1.0.${{ github.run_number }}"
if [[ $GITHUB_REF == 'refs/heads/dev' ]]; then
TAG="Prerelease $VERSION"
else
TAG="$VERSION"
fi
ZIP_NAME="SharpTimer_${VERSION}.zip"
echo "TAG=$TAG" >> $GITHUB_ENV
echo "ZIP_NAME=$ZIP_NAME" >> $GITHUB_ENV
- name: Zip published files
run: |
cd publish
zip -r ${{ env.ZIP_NAME }} .
- name: Create GitHub Release
uses: ncipollo/release-action@v1.16.0
with:
name: ${{ env.TAG }}
artifacts: publish/${{ env.ZIP_NAME }}
prerelease: ${{ contains(github.ref, 'dev') }}
tag: v1.0.${{ github.run_number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
post_webhook:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/dev'
steps:
- name: POST Webhook
run: |
curl -X POST \
--fail \
-F token=${{ secrets.GITLAB_SECRET_TOKEN }} \
-F ref=dev \
https://gitlab.edgegamers.io/api/v4/projects/2683/trigger/pipeline
- name: POST Webhook
run: |
curl -X POST \
--fail \
-F token=${{ secrets.GITLAB_SECRET_TOKEN }} \
-F ref=dev \
https://gitlab.edgegamers.io/api/v4/projects/2683/trigger/pipeline

View File

@@ -16,7 +16,7 @@
<PackageReference Include="System.Data.SQLite" Version="1.0.118"/>
</ItemGroup>
<ItemGroup>
<Content Include=".github\workflows\build.yml"/>
<Content Include=".github\workflows\nightly.yml" />
<Content Include="lang\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>lang\%(RecursiveDir)%(Filename)%(Extension)</Link>

View File

@@ -24,6 +24,7 @@
"map_finish": "{lime}{0} {white}finished the map!",
"map_finish_bonus": "{lime}{0} {white}finished the Bonus {1}!",
"map_finish_rank": "Rank: [{lime}{0}{default}] Times Finished: [{lime}{1}{default}]",
"map_finish_group": "Group: {orchid}{0}",
"top10_records": "Top 10 {0} Records for {1}:",
"top10_records_bonus": "Top 10 {0} Records for Bonus {1} on {2}:",
@@ -39,7 +40,7 @@
//rank
"current_rank": "You are currently {0}{1}{lime}",
"current_rank_points": "{default}({lime}{0}{default}) [{lime}{1}{default}]",
"current_pb": "Your current PB on {lime}{0}{default}: {lime}{1}{default} [{lime}{2}{default}]",
"current_pb": "Your current PB on {lime}{0}{default}: {lime}{1}{default} [{lime}{2}{default}] Group: {lime}{3}",
"current_bonus_pb": "Your current PB on {lime}Bonus {0}{default}: {lime}{1}{default} [{lime}{2}{default}]",
"current_sr": "Current Server Record on {lime}{0}{white}:",
"current_sr_player": "{lime}{0} {white}- {lime}{1}",

View File

@@ -838,6 +838,11 @@ namespace SharpTimer
ranking = useGlobalRanks ? await GetPlayerServerPlacement(steamId, playerName) : await GetPlayerMapPlacementWithTotal(player, steamId, playerName, false, false, 0, style);
rankIcon = useGlobalRanks ? await GetPlayerServerPlacement(steamId, playerName, true) : await GetPlayerMapPlacementWithTotal(player, steamId, playerName, true, false, 0, style);
mapPlacement = await GetPlayerMapPlacementWithTotal(player, steamId, playerName, false, true, 0, style);
var percentile =
await GetPlayerMapPercentile(steamId, playerName);
SharpTimerDebug("Ranking: " + mapPlacement);
int.TryParse(mapPlacement[..Math.Max(mapPlacement.IndexOf('/'), 0)], out var position);
foreach (var bonusRespawnPose in bonusRespawnPoses)
{
@@ -900,8 +905,9 @@ namespace SharpTimer
PrintToChat(player, rankMessage);
if (pbTicks != 0)
PrintToChat(player, Localizer["current_pb", currentMapName!, FormatTime(pbTicks), mapPlacement]);
if (pbTicks != 0) {
PrintToChat(player, Localizer["current_pb", currentMapName!, FormatTime(pbTicks), mapPlacement, FormatGroup(position, percentile)]);
}
if (playerTimers[playerSlot].CachedBonusInfo.Any())
{
@@ -915,7 +921,7 @@ namespace SharpTimer
}
catch (Exception ex)
{
SharpTimerError($"Error in RankCommandHandler: {ex}");
SharpTimerError($"Error in RankCommandHandler: {ex} {ex.StackTrace}");
}
}

View File

@@ -1643,7 +1643,7 @@ namespace SharpTimer
public void GainPointsMessage(string playerName, double newPoints, double playerPoints)
{
PrintToChatAll(Localizer["gained_points", playerName, Convert.ToInt32(newPoints - playerPoints), newPoints]);
// PrintToChatAll(Localizer["gained_points", playerName, Convert.ToInt32(newPoints - playerPoints), newPoints]);
}
public (string, int) FixMapAndBonus(string mapName)
@@ -2048,7 +2048,8 @@ namespace SharpTimer
switch (dbType)
{
case DatabaseType.MySQL:
query = $@"SELECT PlayerName, GlobalPoints FROM {PlayerStatsTable} ORDER BY GlobalPoints DESC LIMIT 10";
// query = $@"SELECT PlayerName, GlobalPoints FROM {PlayerStatsTable} ORDER BY GlobalPoints DESC LIMIT 10";
query = $@"SELECT PlayerName, GlobalPoints FROM PlayerLeaderboard ORDER BY GlobalPoints DESC LIMIT 10";
command = new MySqlCommand(query, (MySqlConnection)connection);
break;
case DatabaseType.PostgreSQL:
@@ -2625,7 +2626,7 @@ namespace SharpTimer
switch (dbType)
{
case DatabaseType.MySQL:
selectQuery = $"SELECT GlobalPoints FROM {PlayerStatsTable} WHERE SteamID = @SteamID";
selectQuery = $"SELECT GlobalPoints FROM PlayerLeaderboard WHERE SteamID = @SteamID";
selectCommand = new MySqlCommand(selectQuery, (MySqlConnection)connection);
break;
case DatabaseType.PostgreSQL:
@@ -2979,7 +2980,7 @@ namespace SharpTimer
switch (dbType)
{
case DatabaseType.MySQL:
selectQuery = $@"SELECT SteamID, PlayerName, GlobalPoints FROM {PlayerStatsTable}";
selectQuery = $@"SELECT SteamID, PlayerName, GlobalPoints FROM PlayerLeaderboard ORDER BY GlobalPoints DESC";
selectCommand = new MySqlCommand(selectQuery, (MySqlConnection)connection);
break;
case DatabaseType.PostgreSQL:
@@ -3005,7 +3006,7 @@ namespace SharpTimer
{
string steamId = reader.GetString(0);
string playerName = reader.IsDBNull(1) ? "Unknown" : reader.GetString(1);
int globalPoints = reader.GetInt32(2);
int globalPoints = Convert.ToInt32(reader["GlobalPoints"]);
if (globalPoints >= minGlobalPointsForRank) // Only add if GlobalPoints is above or equal to minGlobalPointsForRank
{
@@ -3017,11 +3018,8 @@ namespace SharpTimer
}
}
sortedPoints = sortedPoints.OrderByDescending(record => record.Value.GlobalPoints)
.ToDictionary(record => record.Key, record => record.Value);
// sortedPoints = sortedPoints.OrderByDescending(record => record.Value.GlobalPoints)
// .ToDictionary(record => record.Key, record => record.Value);
return sortedPoints;
}
}

View File

@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS TierPoints
(
Tier INT NOT NULL
PRIMARY KEY,
Points INT NULL,
CompletionPoints FLOAT NULL,
BasePoints FLOAT NULL,
Divider FLOAT NULL,
MaxPoints INT NULL
);

View File

@@ -0,0 +1,112 @@
CREATE VIEW IF NOT EXISTS cs2_surf.MapCompletions AS
SELECT `PlayerRecords`.`MapName` AS `MapName`,
SUM(`PlayerRecords`.`TimesFinished`) AS `Completions`
FROM `PlayerRecords`
GROUP BY `PlayerRecords`.`MapName`;
CREATE TABLE IF NOT EXISTS MapTiers
(
MapName VARCHAR(255) NOT NULL
PRIMARY KEY,
Tier INT DEFAULT 1 NOT NULL
);
CREATE VIEW IF NOT EXISTS cs2_surf.MapWRs AS
SELECT `Map`.`MapName` AS `MapName`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) AS `WR`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.8 AS `Rank2`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.75 AS `Rank3`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.7 AS `Rank4`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.65 AS `Rank5`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.6 AS `Rank6`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.55 AS `Rank7`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.5 AS `Rank8`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.45 AS `Rank9`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.4 AS `Rank10`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.25 AS `Group1`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.25 / 1.5 AS `Group2`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.25 / 2.25 AS `Group3`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.25 / 3.375 AS `Group4`,
GREATEST(`TP`.`MaxPoints`,
COALESCE(`TP`.`BasePoints` + `TP`.`CompletionPoints` * `Map`.`Completions` / `TP`.`Divider`,
`TP`.`MaxPoints`)) * 0.25 / 5.0625 AS `Group5`
FROM ((`MapCompletions` `Map` JOIN `MapTiers` `MT`
ON (`Map`.`MapName` = `MT`.`MapName`)) JOIN `TierPoints` `TP` ON (`MT`.`Tier` = `TP`.`Tier`));
CREATE VIEW IF NOT EXISTS cs2_surf.PlayerRanks AS
SELECT `PlayerRecords`.`MapName` AS `MapName`,
`PlayerRecords`.`SteamID` AS `SteamID`,
RANK() OVER ( PARTITION BY `PlayerRecords`.`MapName` ORDER BY `PlayerRecords`.`TimerTicks`) AS `Rank`,
PERCENT_RANK() OVER ( PARTITION BY `PlayerRecords`.`MapName` ORDER BY `PlayerRecords`.`TimerTicks`) AS `Percentile`
FROM `PlayerRecords`
WHERE `PlayerRecords`.`Style` = 0;
CREATE VIEW IF NOT EXISTS cs2_surf.PlayerPoints AS
SELECT `Ranks`.`SteamID` AS `SteamID`,
`MT`.`MapName` AS `MapName`,
`Ranks`.`Percentile` AS `Percentile`,
CASE
WHEN `Ranks`.`Percentile` < 0.5 THEN CASE `Ranks`.`Rank`
WHEN 1 THEN `MWR`.`WR`
WHEN 2 THEN `MWR`.`Rank2`
WHEN 3 THEN `MWR`.`Rank3`
WHEN 4 THEN `MWR`.`Rank4`
WHEN 5 THEN `MWR`.`Rank5`
WHEN 6 THEN `MWR`.`Rank6`
WHEN 7 THEN `MWR`.`Rank7`
WHEN 8 THEN `MWR`.`Rank8`
WHEN 9 THEN `MWR`.`Rank9`
WHEN 10 THEN `MWR`.`Rank10` END
ELSE CASE
WHEN `Ranks`.`Percentile` < 0.03125 THEN `MWR`.`Group1`
WHEN `Ranks`.`Percentile` < 0.06250 THEN `MWR`.`Group2`
WHEN `Ranks`.`Percentile` < 0.12500 THEN `MWR`.`Group3`
WHEN `Ranks`.`Percentile` < 0.25000 THEN `MWR`.`Group4`
WHEN `Ranks`.`Percentile` < 0.50000 THEN `MWR`.`Group5`
ELSE 0 END END + CASE `MT`.`Tier`
WHEN 1 THEN 25
WHEN 2 THEN 50
WHEN 3 THEN 100
WHEN 4 THEN 200
WHEN 5 THEN 400
WHEN 6 THEN 600
WHEN 7 THEN 800
WHEN 8 THEN 1000 END AS `Points`
FROM ((`PlayerRanks` `Ranks` JOIN `MapWRs` `MWR`
ON (`Ranks`.`MapName` = `MWR`.`MapName`)) JOIN `MapTiers` `MT` ON (`MWR`.`MapName` = `MT`.`MapName`));
CREATE VIEW IF NOT EXISTS cs2_surf.PlayerLeaderboard AS
SELECT `PP`.`SteamID` AS `SteamID`, `PR`.`PlayerName` AS `PlayerName`, SUM(`PP`.`Points`) AS `GlobalPoints`
FROM (`PlayerPoints` `PP` JOIN `PlayerRecords` `PR` ON (`PP`.`SteamID` = `PR`.`SteamID`))
GROUP BY `PP`.`SteamID`;

View File

@@ -126,5 +126,62 @@ namespace SharpTimer
_ => 0,
};
}
public string FormatGroup(int placement, double percentile) {
percentile *= 100;
if (placement <= 10)
return placement switch {
1 => "First",
2 => "Second",
3 => "Third",
> 0 => placement + "th",
_ => "N/A"
};
if (percentile > 0.5) return "N/A";
return percentile switch {
double p when p <= 3.125 => "Group 1",
double p when p <= 6.25 => "Group 2",
double p when p <= 12.5 => "Group 3",
double p when p <= 25 => "Group 4",
double p when p <= 50 => "Group 5",
_ => "N/A"
};
}
public int GetGroupIndex(double percentile, bool forGlobal = false)
{
// double baseMultiplier = points * 0.25;
double divisor = 1.5;
double threshold1, threshold2, threshold3, threshold4, threshold5;
if (forGlobal)
{
threshold1 = 3.125;
threshold2 = 6.25;
threshold3 = 12.5;
threshold4 = 25;
threshold5 = 50;
}
else
{
threshold1 = group1;
threshold2 = group2;
threshold3 = group3;
threshold4 = group4;
threshold5 = group5;
}
return percentile switch
{
double p when p <= threshold1 => 1,
double p when p <= threshold2 => 2,
double p when p <= threshold3 => 3,
double p when p <= threshold4 => 4,
double p when p <= threshold5 => 5,
_ => 0,
};
}
}
}

View File

@@ -526,6 +526,9 @@ namespace SharpTimer
}
string ranking = await GetPlayerMapPlacementWithTotal(player, steamID, playerName, false, true, bonusX, style);
SharpTimerDebug($"Player {playerName} finished map with time {newticks} ticks, placing {ranking}");
var percentile = await GetPlayerMapPercentile(steamID, playerName, currentMapName!, bonusX, style, false, newticks);
int.TryParse(ranking[..ranking.IndexOf('/')], out int position);
bool newSR = GetNumberBeforeSlash(ranking) == 1 && (oldticks > newticks || oldticks == 0);
bool beatPB = oldticks > newticks;
@@ -570,11 +573,12 @@ namespace SharpTimer
PlaySound(player, timerSound);
}
if (enableDb || bonusX != 0)
if (enableDb || bonusX != 0) {
PrintToChatAll(Localizer["map_finish_rank", ranking, timesFinished]);
PrintToChatAll(Localizer["map_finish_group", FormatGroup(position, percentile)]);
}
PrintToChatAll(Localizer["timer_time", newTime, timeDifference]);
// if (enableStyles) PrintToChatAll(Localizer["timer_style", GetNamedStyle(style)]);
if (enableReplays == true && enableSRreplayBot == true && newSR && (oldticks > newticks || oldticks == 0))
{
_ = Task.Run(async () => await SpawnReplayBot());