Finished Trainer Rankings database calls.

This commit is contained in:
Greg Edwards 2022-06-02 01:48:49 -04:00
parent 11697ac96e
commit 2975c5e10a
6 changed files with 297 additions and 34 deletions

View File

@ -362,6 +362,10 @@ namespace PkmnFoundations.GlobalTerminalService
}
var recordTypes = Database.Instance.TrainerRankingsGetActiveRecordTypes();
// todo: If we can be sure the player has already sent us their up to date records,
// then we can lie here about the active records and collect more complete trainer
// stats for better trainer profiles
response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
// The game will give error 10609 if the response is longer than 4 bytes.
@ -640,6 +644,7 @@ namespace PkmnFoundations.GlobalTerminalService
0x88, 0x01, 0x1a, 0x00, 0xfe, 0x00, 0xf9, 0x00
});*/
/*
// fake replay data:
TrainerRankingsReport lastWeek = GenerateFakeReport(
new DateTime(2014, 04, 27),
@ -657,6 +662,17 @@ namespace PkmnFoundations.GlobalTerminalService
TrainerRankingsRecordTypes.CompletedGtsPokemonTrades,
TrainerRankingsRecordTypes.FacilitiesChallengedAtTheBattleFrontier
});
*/
TrainerRankingsReport thisWeek = Database.Instance.TrainerRankingsGetPendingReport();
TrainerRankingsReport lastWeek = Database.Instance.TrainerRankingsGetReport(DateTime.MinValue, thisWeek.StartDate.AddDays(-1), 1).FirstOrDefault();
if (lastWeek == null) lastWeek = GenerateFakeReport(thisWeek.StartDate.AddDays(-7),
new TrainerRankingsRecordTypes[]
{
TrainerRankingsRecordTypes.HallOfFameEntries,
TrainerRankingsRecordTypes.CompletedGtsPokemonTrades,
TrainerRankingsRecordTypes.FacilitiesChallengedAtTheBattleFrontier
});
// Last week:
foreach (var lbg in lastWeek.Leaderboards)
@ -768,8 +784,8 @@ namespace PkmnFoundations.GlobalTerminalService
private static TrainerRankingsReport GenerateFakeReport(DateTime startDate, TrainerRankingsRecordTypes[] recordTypes)
{
var leaderboards = recordTypes.Select(r => new TrainerRankingsLeaderboardGroup(r,
new TrainerRankingsLeaderboard(TrainerRankingsTeamCategories.BirthMonth, GenerateFakeData(12, 1, 12, 100000)),
new TrainerRankingsLeaderboard(TrainerRankingsTeamCategories.TrainerClass, GenerateFakeData(16, 0, 16, 100000)),
new TrainerRankingsLeaderboard(TrainerRankingsTeamCategories.BirthMonth, GenerateFakeData(12, 1, 12, 100000)),
new TrainerRankingsLeaderboard(TrainerRankingsTeamCategories.FavouritePokemon, GenerateFakeData(20, 1, 493, 100000)))).ToArray();
return new TrainerRankingsReport(startDate, startDate.AddDays(7), leaderboards);

View File

@ -2684,11 +2684,12 @@ namespace PkmnFoundations.Data
// outer join enum values to include those recordtypes that have never been used
var combined = enumValues
.Join(tblRecordTypes.AsEnumerable(),
i => i,
row => (TrainerRankingsRecordTypes)DatabaseExtender.Cast<int>(row["RecordType"]),
(i, row) => new { RecordType = i, LastUsed = row == null ? DateTime.MinValue : DatabaseExtender.Cast<DateTime>(row["LastUsed"]) })
.OrderBy(r => r.LastUsed).ToList(); // oldest first
.GroupJoin(tblRecordTypes.AsEnumerable(),
i => i,
row => (TrainerRankingsRecordTypes)DatabaseExtender.Cast<int>(row["RecordType"]),
(i, rows) => new { RecordType = i, LastUsed = rows.Count() == 0 ? DateTime.MinValue : DatabaseExtender.Cast<DateTime>(rows.First()["LastUsed"]) }
)
.OrderBy(r => r.LastUsed).ToList();
List<TrainerRankingsRecordTypes> chosen = new List<TrainerRankingsRecordTypes>(3);
Random rand = new Random(); // todo: use better rng here. Maybe when the framework gets good RNGs
@ -2734,22 +2735,218 @@ namespace PkmnFoundations.Data
public override IList<TrainerRankingsRecordTypes> TrainerRankingsGetActiveRecordTypes()
{
throw new NotImplementedException();
return WithTransaction(tran => TrainerRankingsGetActiveRecordTypes(tran));
}
public override bool TrainerRankingsSubmit(TrainerRankingsSubmission submission)
public IList<TrainerRankingsRecordTypes> TrainerRankingsGetActiveRecordTypes(MySqlTransaction tran)
{
throw new NotImplementedException();
DateTime now = DateTime.UtcNow;
DataTable tbl = tran.ExecuteDataTable(
"SELECT RecordType1, RecordType2, RecordType3 FROM pkmncf_terminal_trainer_rankings_reports ORDER BY EndDate DESC LIMIT 1",
new MySqlParameter("@now", now));
if (tbl.Rows.Count == 0) return new List<TrainerRankingsRecordTypes>();
DataRow row = tbl.Rows[0];
return new List<TrainerRankingsRecordTypes>()
{
(TrainerRankingsRecordTypes)DatabaseExtender.Cast<int>(row["RecordType1"]),
(TrainerRankingsRecordTypes)DatabaseExtender.Cast<int>(row["RecordType2"]),
(TrainerRankingsRecordTypes)DatabaseExtender.Cast<int>(row["RecordType3"]),
};
}
public override TrainerRankingsReport[] TrainerRankingsGetReport(DateTime start, DateTime end)
public override void TrainerRankingsSubmit(TrainerRankingsSubmission submission)
{
throw new NotImplementedException();
WithTransaction(tran => TrainerRankingsSubmit(tran, submission));
}
public void TrainerRankingsSubmit(MySqlTransaction tran, TrainerRankingsSubmission submission)
{
tran.ExecuteNonQuery("IF (SELECT EXISTS(SELECT * FROM pkmncf_terminal_trainer_rankings_teams WHERE pid = @pid)) THEN " +
"UPDATE pkmncf_terminal_trainer_rankings_teams SET " +
"TrainerClass = @trainer_class, BirthMonth = @birth_month, " +
"FavouritePokemon = @favourite_pokemon, " +
"Unknown1 = @unknown1, Unknown2 = @unknown2, " +
"Unknown3 = @unknown3, LastUpdated = UTC_TIMESTAMP() " +
"WHERE pid = @pid; " +
"ELSE " +
"INSERT INTO pkmncf_terminal_trainer_rankings_teams " +
"(pid, TrainerClass, BirthMonth, FavouritePokemon, Unknown1, Unknown2, Unknown3, LastUpdated) " +
"VALUES (@pid, @trainer_class, @birth_month, @favourite_pokemon, @unknown1, @unknown2, @unknown3, UTC_TIMESTAMP()); " +
"END IF;",
new MySqlParameter("@pid", submission.PID),
new MySqlParameter("@trainer_class", submission.TrainerClass),
new MySqlParameter("@birth_month", submission.BirthMonth),
new MySqlParameter("@favourite_pokemon", submission.FavouritePokemon),
new MySqlParameter("@unknown1", submission.Unknown1),
new MySqlParameter("@unknown2", submission.Unknown2),
new MySqlParameter("@unknown3", submission.Unknown3)
);
foreach (var entry in submission.Entries)
{
tran.ExecuteNonQuery("DELETE FROM pkmncf_terminal_trainer_rankings_records WHERE pid = @pid AND RecordType = @record_type; " +
"INSERT INTO pkmncf_terminal_trainer_rankings_records (pid, RecordType, Score, LastUpdated) " +
"VALUES (@pid, @record_type, @score, UTC_TIMESTAMP())",
new MySqlParameter("@pid", submission.PID),
new MySqlParameter("@record_type", entry.RecordType),
new MySqlParameter("@score", entry.Score));
}
}
public override TrainerRankingsReport[] TrainerRankingsGetReport(DateTime start, DateTime end, int limit)
{
return WithTransaction(tran => TrainerRankingsGetReport(tran, start, end, limit));
}
public TrainerRankingsReport[] TrainerRankingsGetReport(MySqlTransaction tran, DateTime start, DateTime end, int limit)
{
DataTable tblReports = tran.ExecuteDataTable(
"SELECT report_id, StartDate, EndDate, RecordType1, RecordType2, RecordType3 " +
"FROM pkmncf_terminal_trainer_rankings_reports " +
"WHERE EndDate >= @start_date AND StartDate <= @end_date " +
"ORDER BY StartDate DESC" + (limit > 0 ? " LIMIT @limit" : ""),
new MySqlParameter("@start_date", start),
new MySqlParameter("@end_date", end),
new MySqlParameter("@limit", limit));
var result = new TrainerRankingsReport[tblReports.Rows.Count];
for (int res = 0; res < result.Length; res++)
{
DataRow row = tblReports.Rows[res];
var groups = new TrainerRankingsLeaderboardGroup[3];
var groupCols = new[] { "RecordType1", "RecordType2", "RecordType3" };
var reportId = DatabaseExtender.Cast<int>(row["report_id"]);
for (int grp = 0; grp < 3; grp++)
{
var recordType = (TrainerRankingsRecordTypes)DatabaseExtender.Cast<int>(row[groupCols[grp]]);
groups[grp] = new TrainerRankingsLeaderboardGroup
(
recordType,
GetSpecificLeaderboard(tran, reportId, recordType, TrainerRankingsTeamCategories.TrainerClass),
GetSpecificLeaderboard(tran, reportId, recordType, TrainerRankingsTeamCategories.BirthMonth),
GetSpecificLeaderboard(tran, reportId, recordType, TrainerRankingsTeamCategories.FavouritePokemon)
);
}
result[res] = new TrainerRankingsReport(DatabaseExtender.Cast<DateTime>(row["StartDate"]), DatabaseExtender.Cast<DateTime>(row["EndDate"]), groups);
}
return result;
}
private TrainerRankingsLeaderboard GetSpecificLeaderboard(MySqlTransaction tran, int reportId,
TrainerRankingsRecordTypes recordType, TrainerRankingsTeamCategories teamCategory)
{
string tableName, teamColumnName;
switch (teamCategory)
{
case TrainerRankingsTeamCategories.TrainerClass:
tableName = "pkmncf_terminal_trainer_rankings_leaderboards_class";
teamColumnName = "TrainerClass";
break;
case TrainerRankingsTeamCategories.BirthMonth:
tableName = "pkmncf_terminal_trainer_rankings_leaderboards_month";
teamColumnName = "Month";
break;
case TrainerRankingsTeamCategories.FavouritePokemon:
tableName = "pkmncf_terminal_trainer_rankings_leaderboards_pokemon";
teamColumnName = "pokemon_id";
break;
default:
throw new ArgumentOutOfRangeException("teamCategory");
}
var tblLeaderboard = tran.ExecuteDataTable("SELECT " + teamColumnName + " AS Team, Score FROM " + tableName +
" WHERE report_id = @report_id AND RecordType = @record_type",
new MySqlParameter("@report_id", reportId),
new MySqlParameter("@record_type", recordType));
var entries = tblLeaderboard.AsEnumerable()
.Select(rowEntry => new TrainerRankingsLeaderboardEntry(
DatabaseExtender.Cast<int>(rowEntry["Team"]),
DatabaseExtender.Cast<long>(rowEntry["Score"]))).ToArray();
return new TrainerRankingsLeaderboard(teamCategory, entries);
}
public override TrainerRankingsReport TrainerRankingsGetPendingReport()
{
throw new NotImplementedException();
return WithTransaction(tran => TrainerRankingsGetPendingReport(tran));
}
public TrainerRankingsReport TrainerRankingsGetPendingReport(MySqlTransaction tran)
{
DataTable tblReports = tran.ExecuteDataTable(
"SELECT report_id, StartDate, EndDate, RecordType1, RecordType2, RecordType3 " +
"FROM pkmncf_terminal_trainer_rankings_reports " +
"ORDER BY StartDate DESC LIMIT 1");
if (tblReports.Rows.Count < 1) return null;
DataRow row = tblReports.Rows[0];
var groups = new TrainerRankingsLeaderboardGroup[3];
var groupCols = new[] { "RecordType1", "RecordType2", "RecordType3" };
var startDate = DatabaseExtender.Cast<DateTime>(row["StartDate"]);
for (int grp = 0; grp < 3; grp++)
{
var recordType = (TrainerRankingsRecordTypes)DatabaseExtender.Cast<int>(row[groupCols[grp]]);
groups[grp] = new TrainerRankingsLeaderboardGroup
(
recordType,
GetSpecificPendingLeaderboard(tran, recordType, TrainerRankingsTeamCategories.TrainerClass, startDate),
GetSpecificPendingLeaderboard(tran, recordType, TrainerRankingsTeamCategories.BirthMonth, startDate),
GetSpecificPendingLeaderboard(tran, recordType, TrainerRankingsTeamCategories.FavouritePokemon, startDate)
);
}
return new TrainerRankingsReport(startDate, DatabaseExtender.Cast<DateTime>(row["EndDate"]), groups);
}
private TrainerRankingsLeaderboard GetSpecificPendingLeaderboard(MySqlTransaction tran,
TrainerRankingsRecordTypes recordType, TrainerRankingsTeamCategories teamCategory, DateTime startDate)
{
string teamColumnName; // different column names than above...
switch (teamCategory)
{
case TrainerRankingsTeamCategories.TrainerClass:
teamColumnName = "TrainerClass";
break;
case TrainerRankingsTeamCategories.BirthMonth:
teamColumnName = "BirthMonth";
break;
case TrainerRankingsTeamCategories.FavouritePokemon:
teamColumnName = "FavouritePokemon";
break;
default:
throw new ArgumentOutOfRangeException("teamCategory");
}
var tblLeaderboard = tran.ExecuteDataTable("SELECT " + teamColumnName + " AS Team, SUM(Score) AS Score " +
"FROM pkmncf_terminal_trainer_rankings_records " +
"INNER JOIN pkmncf_terminal_trainer_rankings_teams " +
"ON pkmncf_terminal_trainer_rankings_records.pid = pkmncf_terminal_trainer_rankings_teams.pid " +
"WHERE pkmncf_terminal_trainer_rankings_records.LastUpdated >= @start_date " +
"AND RecordType = @record_type " +
"GROUP BY TrainerClass;",
new MySqlParameter("@record_type", recordType),
new MySqlParameter("@start_date", startDate));
var entries = tblLeaderboard.AsEnumerable()
.Select(rowEntry => new TrainerRankingsLeaderboardEntry(
DatabaseExtender.Cast<int>(rowEntry["Team"]),
Convert.ToInt64(rowEntry["Score"]))).ToArray();
return new TrainerRankingsLeaderboard(teamCategory, entries);
}
#endregion

View File

@ -179,25 +179,45 @@ namespace PkmnFoundations.Data
/// Submits trainer rankings data for one player and populates the active leaderboard with it.
/// </summary>
/// <param name="submission"></param>
/// <returns>True if the data was processed</returns>
public abstract bool TrainerRankingsSubmit(TrainerRankingsSubmission submission);
public abstract void TrainerRankingsSubmit(TrainerRankingsSubmission submission);
/// <summary>
/// Gets past reports falling within a specified date range.
/// Gets past reports, sorted descending, falling within a specified date range.
/// </summary>
/// <param name="start">Datetime during which the oldest returned leaderboard was active</param>
/// <param name="end">Datetime during which the newest returned leaderboard was active</param>
/// <param name="limit">Limit on the number of results or less than 1 for unlimited</param>
/// <returns>Reports</returns>
public abstract TrainerRankingsReport[] TrainerRankingsGetReport(DateTime start, DateTime end, int limit);
/// <summary>
/// Gets past reports, sorted descending, falling within a specified date range.
/// </summary>
/// <param name="start">Datetime during which the oldest returned leaderboard was active</param>
/// <param name="end">Datetime during which the newest returned leaderboard was active</param>
/// <returns>Reports</returns>
public abstract TrainerRankingsReport[] TrainerRankingsGetReport(DateTime start, DateTime end);
public TrainerRankingsReport[] TrainerRankingsGetReport(DateTime start, DateTime end)
{
return TrainerRankingsGetReport(start, end, 0);
}
/// <summary>
/// Gets the single report which was active during the specified date.
/// </summary>
/// <param name="during"></param>
/// <returns></returns>
/// <returns>Report</returns>
public TrainerRankingsReport TrainerRankingsGetReport(DateTime during)
{
return TrainerRankingsGetReport(during, during).FirstOrDefault();
return TrainerRankingsGetReport(during, during, 1).FirstOrDefault();
}
/// <summary>
/// Gets the most recently finished report
/// </summary>
/// <returns>Report</returns>
public TrainerRankingsReport TrainerRankingsGetReport()
{
return TrainerRankingsGetReport(DateTime.MinValue, DateTime.UtcNow, 1).FirstOrDefault();
}
/// <summary>

View File

@ -299,5 +299,32 @@ namespace PkmnFoundations.Data
if (value is DBNull) value = null;
return (T)value; // Allow InvalidCastException to escape.
}
public static List<T> Collect<T>(this IDataReader reader, Func<IDataRecord, T> collector)
{
List<T> result = new List<T>();
while (reader.Read())
{
result.Add(collector(reader));
}
return result;
}
public static List<T> ExecuteCollection<T>(this DbConnection conn, string sqlstr, Func<IDataRecord, T> collector, params IDataParameter[] _params)
{
return conn.ExecuteReader(sqlstr, _params).Collect(collector);
}
public static List<T> ExecuteCollection<T>(this DbTransaction tran, string sqlstr, Func<IDataRecord, T> collector, params IDataParameter[] _params)
{
return tran.ExecuteReader(sqlstr, _params).Collect(collector);
}
public static List<T> ExecuteCollection<T>(this DbCommand cmd, Func<IDataRecord, T> collector)
{
return cmd.ExecuteReader().Collect(collector);
}
}
}

View File

@ -23,19 +23,19 @@ namespace PkmnFoundations.Structures
public class TrainerRankingsLeaderboardGroup
{
public TrainerRankingsLeaderboardGroup(TrainerRankingsRecordTypes record_type, TrainerRankingsLeaderboard leaderboard_birth_month,
TrainerRankingsLeaderboard leaderboard_trainer_class, TrainerRankingsLeaderboard leaderboard_favourite_pokemon)
public TrainerRankingsLeaderboardGroup(TrainerRankingsRecordTypes record_type, TrainerRankingsLeaderboard leaderboard_trainer_class,
TrainerRankingsLeaderboard leaderboard_birth_month, TrainerRankingsLeaderboard leaderboard_favourite_pokemon)
{
RecordType = record_type;
LeaderboardBirthMonth = leaderboard_birth_month;
LeaderboardTrainerClass = leaderboard_trainer_class;
LeaderboardBirthMonth = leaderboard_birth_month;
LeaderboardFavouritePokemon = leaderboard_favourite_pokemon;
}
public TrainerRankingsRecordTypes RecordType { get; set; }
public TrainerRankingsLeaderboard LeaderboardBirthMonth { get; set; }
public TrainerRankingsLeaderboard LeaderboardTrainerClass { get; set; }
public TrainerRankingsLeaderboard LeaderboardBirthMonth { get; set; }
public TrainerRankingsLeaderboard LeaderboardFavouritePokemon { get; set; }
}
@ -191,8 +191,8 @@ namespace PkmnFoundations.Structures
public class TrainerRankingsSubmission : BinarySerializableBase
{
public TrainerRankingsSubmission(int pid, Versions version,
Languages language, byte birth_month, byte trainer_class,
ushort favourite_pokemon, ushort unknown1, ushort unknown2,
Languages language, byte birth_month, byte trainer_class,
ushort favourite_pokemon, ushort unknown1, ushort unknown2,
ushort unknown3, TrainerRankingsSubmissionEntry[] entries)
{
PID = pid;
@ -299,11 +299,11 @@ namespace PkmnFoundations.Structures
public ushort Unknown3 { get; set; }
private TrainerRankingsSubmissionEntry[] m_entries;
public TrainerRankingsSubmissionEntry[] Entries
public TrainerRankingsSubmissionEntry[] Entries
{
get
{
return m_entries;
get
{
return m_entries;
}
set
{

View File

@ -2,7 +2,7 @@
-- Host: 127.0.0.1
-- Server version: 10.3.28-MariaDB - mariadb.org binary distribution
-- Server OS: Win64
-- HeidiSQL Version: 11.3.0.6449
-- HeidiSQL Version: 12.0.0.6468
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
@ -820,7 +820,7 @@ BEGIN
FROM pkmncf_terminal_trainer_rankings_reports WHERE report_id = @report_id;
INSERT INTO pkmncf_terminal_trainer_rankings_leaderboards_class
SELECT @report_id AS report_id, TrainerClass, @record_type AS RecordType, SUM(VALUE) AS Score
SELECT @report_id AS report_id, TrainerClass, @record_type AS RecordType, SUM(Score) AS Score
FROM pkmncf_terminal_trainer_rankings_records
INNER JOIN pkmncf_terminal_trainer_rankings_teams
ON pkmncf_terminal_trainer_rankings_records.pid = pkmncf_terminal_trainer_rankings_teams.pid
@ -829,7 +829,7 @@ BEGIN
GROUP BY TrainerClass;
INSERT INTO pkmncf_terminal_trainer_rankings_leaderboards_month
SELECT @report_id AS report_id, TrainerClass, @record_type AS RecordType, SUM(VALUE) AS Score
SELECT @report_id AS report_id, TrainerClass, @record_type AS RecordType, SUM(Score) AS Score
FROM pkmncf_terminal_trainer_rankings_records
INNER JOIN pkmncf_terminal_trainer_rankings_teams
ON pkmncf_terminal_trainer_rankings_records.pid = pkmncf_terminal_trainer_rankings_teams.pid
@ -838,7 +838,7 @@ BEGIN
GROUP BY BirthMonth;
INSERT INTO pkmncf_terminal_trainer_rankings_leaderboards_pokemon
SELECT @report_id AS report_id, TrainerClass, @record_type AS RecordType, SUM(VALUE) AS Score
SELECT @report_id AS report_id, TrainerClass, @record_type AS RecordType, SUM(Score) AS Score
FROM pkmncf_terminal_trainer_rankings_records
INNER JOIN pkmncf_terminal_trainer_rankings_teams
ON pkmncf_terminal_trainer_rankings_records.pid = pkmncf_terminal_trainer_rankings_teams.pid
@ -912,7 +912,7 @@ CREATE TABLE IF NOT EXISTS `pkmncf_terminal_trainer_rankings_leaderboards_pokemo
CREATE TABLE IF NOT EXISTS `pkmncf_terminal_trainer_rankings_records` (
`pid` int(11) NOT NULL,
`RecordType` int(11) NOT NULL,
`Value` bigint(20) NOT NULL DEFAULT 0,
`Score` bigint(20) NOT NULL DEFAULT 0,
`LastUpdated` datetime NOT NULL,
PRIMARY KEY (`pid`,`RecordType`),
KEY `LastUpdated` (`LastUpdated`) USING BTREE
@ -940,7 +940,10 @@ CREATE TABLE IF NOT EXISTS `pkmncf_terminal_trainer_rankings_teams` (
`pid` int(11) NOT NULL,
`TrainerClass` int(11) NOT NULL,
`BirthMonth` int(11) NOT NULL,
`FavouritePokemon` int(10) unsigned NOT NULL,
`FavouritePokemon` int(11) NOT NULL,
`Unknown1` smallint(5) unsigned NOT NULL DEFAULT 0,
`Unknown2` smallint(5) unsigned NOT NULL DEFAULT 0,
`Unknown3` smallint(5) unsigned NOT NULL DEFAULT 0,
`LastUpdated` datetime NOT NULL,
PRIMARY KEY (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;