From d8b5cc73aec5e1bb959e0e35c99b17909c7b5a2a Mon Sep 17 00:00:00 2001 From: Doriel Rivalet <100863878+DorielRivalet@users.noreply.github.com> Date: Mon, 19 Feb 2024 01:58:41 -0300 Subject: [PATCH] fix: incorrect run buffs values closes DorielRivalet/mhfz-overlay#281 closes DorielRivalet/mhfz-overlay#282 --- .../Models/Collections/QuestVariants.cs | 2 +- MHFZ_Overlay/Services/DatabaseService.cs | 225 ++++++++++++++---- .../ViewModels/Windows/AddressModel.cs | 117 ++++++++- 3 files changed, 296 insertions(+), 48 deletions(-) diff --git a/MHFZ_Overlay/Models/Collections/QuestVariants.cs b/MHFZ_Overlay/Models/Collections/QuestVariants.cs index 75b69634..da55f7e3 100644 --- a/MHFZ_Overlay/Models/Collections/QuestVariants.cs +++ b/MHFZ_Overlay/Models/Collections/QuestVariants.cs @@ -18,7 +18,7 @@ public static class QuestVariants /// /// Defaulty for GRank is 8,0,0,0 /// - public static ReadOnlyDictionary QuestIDVariant { get; } = new(new Dictionary + public static ReadOnlyDictionary QuestIDVariant { get; } = new(new Dictionary { { Numbers.QuestIDStarvingDeviljhoHistoric20m, new QuestsQuestVariant{QuestVariant1 = 8, QuestVariant2 = 32, QuestVariant3 = 3, QuestVariant4 = 0} diff --git a/MHFZ_Overlay/Services/DatabaseService.cs b/MHFZ_Overlay/Services/DatabaseService.cs index e0e44ff8..ebb52d43 100644 --- a/MHFZ_Overlay/Services/DatabaseService.cs +++ b/MHFZ_Overlay/Services/DatabaseService.cs @@ -3441,12 +3441,14 @@ private static void HandleError(SQLiteTransaction? transaction, Exception ex) LoggingService.WriteCrashLog(ex, $"SQLite error (version: {serverVersion})"); } - public void FillRunBuffs(SQLiteConnection conn, DataLoader dataLoader) + public int UpsertRunBuffs(SQLiteConnection conn, DataLoader dataLoader) { + var updatedRows = 0; + if (string.IsNullOrEmpty(this.dataSource)) { - Logger.Warn(CultureInfo.InvariantCulture, "Cannot update run buffs. dataSource: {0}", this.dataSource); - return; + Logger.Warn(CultureInfo.InvariantCulture, "Cannot upsert run buffs. dataSource: {0}", this.dataSource); + return updatedRows; } // Start a transaction @@ -3454,9 +3456,19 @@ public void FillRunBuffs(SQLiteConnection conn, DataLoader dataLoader) { try { + + using (var cmd0 = new SQLiteCommand(conn)) + { + cmd0.CommandText = @"DROP TRIGGER IF EXISTS prevent_quests_run_buffs_updates"; + cmd0.ExecuteNonQuery(); + } + using (var cmd = new SQLiteCommand(conn)) { - cmd.CommandText = "SELECT * FROM Quests"; + cmd.CommandText = @"SELECT + q.*, qrb.RunBuffs + FROM Quests q + LEFT JOIN QuestsRunBuffs qrb ON q.RunID = qrb.RunID"; using (var reader = cmd.ExecuteReader()) { @@ -3464,18 +3476,35 @@ public void FillRunBuffs(SQLiteConnection conn, DataLoader dataLoader) { var actualOverlayMode = reader["ActualOverlayMode"].ToString(); var runID = long.Parse(reader["RunID"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + var questID = long.Parse(reader["QuestID"]?.ToString() ?? "0", CultureInfo.InvariantCulture); if (actualOverlayMode == null || runID == 0) { continue; } - // Separate command for the insert operation + string? runBuffsString = reader["RunBuffs"].ToString(); + RunBuff runBuffs = RunBuff.None; + using (var cmd2 = new SQLiteCommand(conn)) { - cmd2.CommandText = "INSERT INTO QuestsRunBuffs(RunBuffs, RunBuffsTag, RunID) VALUES (@RunBuffs, @RunBuffsTag, @RunID)"; - cmd2.Parameters.AddWithValue("@RunBuffs", dataLoader.Model.GetRunBuffs(actualOverlayMode)); - cmd2.Parameters.AddWithValue("@RunBuffsTag", dataLoader.Model.GetRunBuffsTag(dataLoader.Model.GetRunBuffs(actualOverlayMode), (QuestVariant2)dataLoader.Model.QuestVariant2(), (QuestVariant3)dataLoader.Model.QuestVariant3())); + // Check if the row exists + if (runBuffsString != null && runBuffsString != string.Empty) // >=v0.35 + { + // Row exists, so update it + cmd2.CommandText = @"UPDATE QuestsRunBuffs SET RunBuffs = @RunBuffs, RunBuffsTag = @RunBuffsTag WHERE RunID = @RunID"; + updatedRows++; + + runBuffs = (RunBuff)long.Parse(reader["RunBuffs"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + } + else // @@ -3515,7 +3556,7 @@ public void UpdatePersonalBestsRunBuffs(SQLiteConnection conn, DataLoader dataLo try { string sql = @" - SELECT pb.*, q.ActualOverlayMode + SELECT pb.*, q.ActualOverlayMode, q.QuestID FROM PersonalBests pb LEFT JOIN Quests q ON pb.RunID = q.RunID"; @@ -3526,7 +3567,9 @@ FROM PersonalBests pb while (reader.Read()) { var runID = long.Parse(reader["RunID"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + var questID = long.Parse(reader["QuestID"]?.ToString() ?? "0", CultureInfo.InvariantCulture); var actualOverlayMode = reader["ActualOverlayMode"].ToString(); + var runBuffs = long.Parse(reader["RunBuffs"]?.ToString() ?? "0", CultureInfo.InvariantCulture); if (runID == 0 || actualOverlayMode == null) { @@ -3537,7 +3580,7 @@ FROM PersonalBests pb using (var cmd2 = new SQLiteCommand(conn)) { cmd2.CommandText = "UPDATE PersonalBests SET RunBuffs = @RunBuffs WHERE RunID = @RunID"; - cmd2.Parameters.AddWithValue("@RunBuffs", dataLoader.Model.GetRunBuffs(actualOverlayMode)); + cmd2.Parameters.AddWithValue("@RunBuffs", dataLoader.Model.GetRunBuffs(questID, actualOverlayMode, GetBaseRunBuffs(actualOverlayMode, runBuffs))); cmd2.Parameters.AddWithValue("@RunID", runID); cmd2.ExecuteNonQuery(); @@ -3558,6 +3601,17 @@ FROM PersonalBests pb Logger.Debug("Updated run buffs for PersonalBests table"); } + private RunBuff GetBaseRunBuffs(string mode, long buffs) + { + return mode switch + { + Messages.OverlayModeTimeAttack => RunBuff.TimeAttack, + Messages.OverlayModeFreestyleNoSecretTech => RunBuff.FreestyleNoSecretTech, + Messages.OverlayModeFreestyleWithSecretTech => RunBuff.FreestyleWithSecretTech, + _ => (RunBuff)buffs, + }; + } + public void UpdateTableRunBuffs(string tableName, SQLiteConnection conn, DataLoader dataLoader) { if (string.IsNullOrEmpty(this.dataSource)) @@ -3572,6 +3626,7 @@ public void UpdateTableRunBuffs(string tableName, SQLiteConnection conn, DataLoa try { string sql = $"SELECT * FROM {tableName}"; + HashSet processedRowIds = new HashSet(); using (var cmd = new SQLiteCommand(sql, conn)) { @@ -3579,22 +3634,121 @@ public void UpdateTableRunBuffs(string tableName, SQLiteConnection conn, DataLoa { while (reader.Read()) { - var actualOverlayMode = reader["ActualOverlayMode"].ToString(); + var oldActualOverlayMode = reader["ActualOverlayMode"].ToString(); var rowID = long.Parse(reader[$"{tableName}ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + var oldQuestID = long.Parse(reader["QuestID"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + var oldWeaponTypeID = long.Parse(reader["WeaponTypeID"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + var oldPartySize = long.Parse(reader["PartySize"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + var oldRunBuffs = long.Parse(reader["RunBuffs"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + var oldAttempts = long.Parse(reader["Attempts"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + + // Skip if the row has already been processed + if (processedRowIds.Contains(rowID)) + { + continue; + } - if (actualOverlayMode == null || rowID == 0) + if (oldActualOverlayMode == null || rowID == 0) { + processedRowIds.Add(rowID); continue; } - using (var cmd2 = new SQLiteCommand(conn)) + if (oldRunBuffs == 0 && oldActualOverlayMode != Messages.OverlayModeFreestyleNoSecretTech && oldActualOverlayMode != Messages.OverlayModeFreestyleWithSecretTech && oldActualOverlayMode != Messages.OverlayModeTimeAttack && oldActualOverlayMode != Messages.OverlayModeSpeedrun) { - cmd2.CommandText = $"UPDATE {tableName} SET RunBuffs = @RunBuffs WHERE {tableName}ID = @rowID"; - cmd2.Parameters.AddWithValue("@RunBuffs", dataLoader.Model.GetRunBuffs(actualOverlayMode)); - cmd2.Parameters.AddWithValue("@rowID", rowID); + // After successful processing, add the row ID to the list of processed IDs + processedRowIds.Add(rowID); + continue; + } - cmd2.ExecuteNonQuery(); + long newRunBuffs = (long)dataLoader.Model.GetRunBuffs(oldQuestID, oldActualOverlayMode, GetBaseRunBuffs(oldActualOverlayMode, oldRunBuffs)); + + if (oldRunBuffs == newRunBuffs || (oldRunBuffs > 0 && newRunBuffs == 0)) + { + processedRowIds.Add(rowID); + continue; + } + + // Step 2: Search for a row with the same field values except for RunBuffs + using (var cmdSearch = new SQLiteCommand(conn)) + { + cmdSearch.CommandText = $@" + SELECT * FROM {tableName} + WHERE QuestID = @QuestID AND WeaponTypeID = @WeaponTypeID AND ActualOverlayMode = @ActualOverlayMode AND PartySize = @PartySize AND RunBuffs = @NewRunBuffs"; + cmdSearch.Parameters.AddWithValue("@QuestID", oldQuestID); + cmdSearch.Parameters.AddWithValue("@WeaponTypeID", oldWeaponTypeID); + cmdSearch.Parameters.AddWithValue("@ActualOverlayMode", oldActualOverlayMode); + cmdSearch.Parameters.AddWithValue("@PartySize", oldPartySize); + cmdSearch.Parameters.AddWithValue("@NewRunBuffs", newRunBuffs); + + using (var searchReader = cmdSearch.ExecuteReader()) + { + if (searchReader.HasRows) + { + Logger.Debug($"Found potential unique constraint violation. oldRunBuffs: {oldRunBuffs}, newRunBuffs: {newRunBuffs}"); + + // Step 3: Potential unique constraint violation detected + while (searchReader.Read()) + { + var newAttempts = long.Parse(searchReader["Attempts"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + + // Step 4: Delete the old row and insert a new one + using (var cmdDelete = new SQLiteCommand(conn)) + { + cmdDelete.CommandText = $"DELETE FROM {tableName} WHERE {tableName}ID = @OldRowID"; + cmdDelete.Parameters.AddWithValue("@OldRowID", rowID); + cmdDelete.ExecuteNonQuery(); + } + + Logger.Debug($"Deleted old row {rowID} from {tableName}"); + + using (var cmdInsert = new SQLiteCommand(conn)) + { + cmdInsert.CommandText = $@" + INSERT INTO {tableName} (QuestID, WeaponTypeID, ActualOverlayMode, PartySize, RunBuffs, Attempts) + VALUES (@QuestID, @WeaponTypeID, @ActualOverlayMode, @PartySize, @NewRunBuffs, @NewAttempts)"; + cmdInsert.Parameters.AddWithValue("@QuestID", oldQuestID); + cmdInsert.Parameters.AddWithValue("@WeaponTypeID", oldWeaponTypeID); + cmdInsert.Parameters.AddWithValue("@ActualOverlayMode", oldActualOverlayMode); + cmdInsert.Parameters.AddWithValue("@PartySize", oldPartySize); + cmdInsert.Parameters.AddWithValue("@NewRunBuffs", newRunBuffs); + cmdInsert.Parameters.AddWithValue("@NewAttempts", Math.Max(oldAttempts, newAttempts)); + cmdInsert.ExecuteNonQuery(); + } + + var lastInsertSql = "SELECT LAST_INSERT_ROWID()"; + long newRowID = 0; + + using (var lastInsertCmd = new SQLiteCommand(lastInsertSql, conn)) + { + newRowID = Convert.ToInt32(lastInsertCmd.ExecuteScalar(), CultureInfo.InvariantCulture); + } + + // Add the new row ID to the processed IDs set + processedRowIds.Add(newRowID); + + Logger.Debug($"Inserted into {tableName}. oldAttempts: {oldAttempts}, newAttempts: {newAttempts}, newRowID: {newRowID}."); + + } + } + else + { + // No conflict, just update the row + using (var cmdUpdate = new SQLiteCommand(conn)) + { + cmdUpdate.CommandText = $"UPDATE {tableName} SET RunBuffs = @NewRunBuffs WHERE {tableName}ID = @rowID"; + cmdUpdate.Parameters.AddWithValue("@NewRunBuffs", newRunBuffs); + cmdUpdate.Parameters.AddWithValue("@rowID", rowID); + cmdUpdate.ExecuteNonQuery(); + } + + Logger.Debug($"Updated row {rowID} of {tableName}. oldRunBuffs: {oldRunBuffs}, newRunBuffs: {newRunBuffs}."); + } + } } + + // After successful processing, add the row ID to the list of processed IDs + processedRowIds.Add(rowID); } } } @@ -16116,7 +16270,7 @@ private void MigrateToSchemaFromVersion(SQLiteConnection conn, int fromVersion, // sqlite3_exec(db, "ALTER TABLE entries ADD COLUMN touched_at TEXT;", NULL, NULL, NULL); { this.PerformUpdateToVersion_0_25_0(conn); - this.EnforceForeignKeys(conn); + // this.EnforceForeignKeys(conn); newVersion++; Logger.Info(CultureInfo.InvariantCulture, "Updated schema to version v0.25.0. newVersion {0}", newVersion); goto case 2; @@ -16124,16 +16278,24 @@ private void MigrateToSchemaFromVersion(SQLiteConnection conn, int fromVersion, case 2: // 0.34.0 // fix attempts and pb attempts, set partysize default to 1 for the extra attempts from 2p/3p/4p. this.PerformUpdateToVersion_0_34_0(conn); - this.EnforceForeignKeys(conn); + // this.EnforceForeignKeys(conn); newVersion++; Logger.Info(CultureInfo.InvariantCulture, "Updated schema to version v0.34.0. newVersion {0}", newVersion); goto case 3; case 3: // 0.35.0 { this.PerformUpdateToVersion_0_35_0(conn, dataLoader); - this.EnforceForeignKeys(conn); + // this.EnforceForeignKeys(conn); newVersion++; Logger.Info(CultureInfo.InvariantCulture, "Updated schema to version v0.35.0. newVersion {0}", newVersion); + goto case 4; + } + case 4:// 0.37.0 + { + this.PerformUpdateToVersion_0_37_0(conn, dataLoader); + this.EnforceForeignKeys(conn); + newVersion++; + Logger.Info(CultureInfo.InvariantCulture, "Updated schema to version v0.37.0. newVersion {0}", newVersion); break; } // case 2://v0.24.0 @@ -18091,29 +18253,10 @@ private void UpdateTableActualOverlayMode(string tableName, SQLiteConnection con /// private void UpdateRunBuffs(SQLiteConnection connection, DataLoader dataLoader) { - // 1. recreate runbuffs table - var sql = @"DROP TABLE IF EXISTS QuestsRunBuffs"; - using (var cmd = new SQLiteCommand(sql, connection)) - { - cmd.ExecuteNonQuery(); - } - - sql = @"CREATE TABLE IF NOT EXISTS QuestsRunBuffs( - QuestsRunBuffsID INTEGER PRIMARY KEY AUTOINCREMENT, - RunBuffs INTEGER NOT NULL DEFAULT 0, - RunBuffsTag TEXT NOT NULL DEFAULT '', - RunID INTEGER NOT NULL, - FOREIGN KEY(RunID) REFERENCES Quests(RunID) - )"; - using (var cmd = new SQLiteCommand(sql, connection)) - { - cmd.ExecuteNonQuery(); - } - // 2. for every row in quests table, add a row to runbuffs table: // runid taken from quests table // runbuffs from GetRunBuffs(string actualoverlaymode from quests table) - FillRunBuffs(connection, dataLoader); + var updatedRows = UpsertRunBuffs(connection, dataLoader); // 3. for every row in personalbests, update personalbests.runbuffs: // get actualoverlaymode by doing personalbests.RunID = quests.RunID, then quests.ActualOverlayMode. @@ -18134,6 +18277,8 @@ FOREIGN KEY(RunID) REFERENCES Quests(RunID) // else if questattempts.ActualOverlayMode = "TimeAttack" then update questattempts.runbuffs to (Enum) RunBuff.TimeAttack. UpdateTableRunBuffs("QuestAttempts", connection, dataLoader); + + Logger.Debug($"Updated QuestsRunBuffs rows: {updatedRows}"); } // TODO: should i put this in FileManager? diff --git a/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs b/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs index 843c3d71..98becfed 100644 --- a/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs +++ b/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs @@ -2862,33 +2862,136 @@ public string CalculateRunBuffsTag(RunBuff runBuffs, QuestVariant2 questVariant2 return value.ToString(); } + public QuestsQuestVariant GetQuestVariants(long questID) + { + QuestsQuestVariant questVariants = new(); + + if (QuestVariants.QuestIDVariant.ContainsKey(questID)) + { + questVariants.QuestVariant1 = QuestVariants.QuestIDVariant[questID].QuestVariant1; + questVariants.QuestVariant2 = QuestVariants.QuestIDVariant[questID].QuestVariant2; + questVariants.QuestVariant3 = QuestVariants.QuestIDVariant[questID].QuestVariant3; + questVariants.QuestVariant4 = QuestVariants.QuestIDVariant[questID].QuestVariant4; + } + + return questVariants; + } + /// - /// Gets the run buffs + /// Decrements the run buffs input if the quest variants disallow it. + /// + /// + /// + /// + public RunBuff GetRunBuffs(RunBuff runBuffs, QuestsQuestVariant questVariants) + { + var questVariant2 = (QuestVariant2?)questVariants.QuestVariant2 ?? Models.Structures.QuestVariant2.None; + var questVariant3 = (QuestVariant3?)questVariants.QuestVariant3 ?? Models.Structures.QuestVariant3.None; + + if (runBuffs.HasFlag(RunBuff.Halk) && (questVariant2.HasFlag(Models.Structures.QuestVariant2.DisableHalkPoogieCuff) || questVariant2.HasFlag(Models.Structures.QuestVariant2.Road))) + { + runBuffs -= RunBuff.Halk; + } + + //if (PoogieItemUseID() > 0) + //{ + // runBuffs |= RunBuff.PoogieItem; + //} + + //if (DivaSongActive) + //{ + // runBuffs |= RunBuff.DivaSong; + //} + + if (runBuffs.HasFlag(RunBuff.HalkPotEffect) && (questVariant2.HasFlag(Models.Structures.QuestVariant2.DisableHalkPotionCourseAttack) || questVariant2.HasFlag(Models.Structures.QuestVariant2.Level9999) || questVariant2.HasFlag(Models.Structures.QuestVariant2.Road))) + { + runBuffs -= RunBuff.HalkPotEffect; + } + + // TODO bento + //if (true == true) + //{ + // runBuffs |= RunBuff.Bento; + //} + + //if (GuildPoogie1Skill() > 0 || GuildPoogie2Skill() > 0 || GuildPoogie3Skill() > 0) + //{ + // runBuffs |= RunBuff.GuildPoogie; + //} + + if (runBuffs.HasFlag(RunBuff.ActiveFeature) && questVariant2.HasFlag(Models.Structures.QuestVariant2.DisableActiveFeature)) + { + runBuffs -= RunBuff.ActiveFeature; + } + + //if (GuildFoodSkill() > 0) + //{ + // runBuffs |= RunBuff.GuildFood; + //} + + if (runBuffs.HasFlag(RunBuff.DivaSkill) && (questVariant2.HasFlag(Models.Structures.QuestVariant2.Road) || questVariant3.HasFlag(Models.Structures.QuestVariant3.NoGPSkills))) + { + runBuffs -= RunBuff.DivaSkill; + } + + if (runBuffs.HasFlag(RunBuff.SecretTechnique) && questVariant2.HasFlag(Models.Structures.QuestVariant2.Level9999)) + { + runBuffs -= RunBuff.SecretTechnique; + } + + if (runBuffs.HasFlag(RunBuff.DivaPrayerGem) && (questVariant2.HasFlag(Models.Structures.QuestVariant2.Road) || questVariant2.HasFlag(Models.Structures.QuestVariant2.Level9999))) + { + runBuffs -= RunBuff.DivaPrayerGem; + } + + if (runBuffs.HasFlag(RunBuff.CourseAttackBoost) && (questVariant2.HasFlag(Models.Structures.QuestVariant2.DisableHalkPotionCourseAttack) || questVariant2.HasFlag(Models.Structures.QuestVariant2.Level9999))) + { + runBuffs -= RunBuff.CourseAttackBoost; + } + + return runBuffs; + } + + /// + /// Gets the run buffs. The runBuffs parameter should be given by doing GetBaseRunBuffs. /// /// /// - public RunBuff GetRunBuffs(string overlayMode = "") + public RunBuff GetRunBuffs(long questID, string overlayMode, RunBuff runBuffs) { - if (overlayMode != string.Empty) + if (overlayMode != string.Empty && questID > 0) { + QuestsQuestVariant questVariants = GetQuestVariants(questID); + switch (overlayMode) { case Messages.OverlayModeFreestyleNoSecretTech: - return RunBuff.FreestyleNoSecretTech; + return GetRunBuffs(RunBuff.FreestyleNoSecretTech, questVariants); case Messages.OverlayModeFreestyleWithSecretTech: - return RunBuff.FreestyleWithSecretTech; + return GetRunBuffs(RunBuff.FreestyleWithSecretTech, questVariants); case Messages.OverlayModeTimeAttack: - return RunBuff.TimeAttack; + return GetRunBuffs(RunBuff.TimeAttack, questVariants); + case Messages.OverlayModeSpeedrun: + return GetRunBuffs(runBuffs, questVariants); default: + // todo update // we do not know the quest variants in 0.34, so we set as none. // if we do not take into account quest variants, calculating the run buffs // may be wrong because, for example, if we detect that halk was on we increase by 1 but // in quests where halk is disabled the value in db is still positive, although in-game - // halk is off. + // halk is off. Also setting this to TA tags would not take into account that the runs may have used HP bars etc. return RunBuff.None; } } + else + { + LoggerInstance.Error($"Wrong argument values for GetRunBuffs. QuestID {questID} OverlayMode {overlayMode}"); + return RunBuff.None; + } + } + public RunBuff GetRunBuffs() + { var runBuffs = RunBuff.None; var questVariant2 = (QuestVariant2)QuestVariant2(); var questVariant3 = (QuestVariant3)QuestVariant3();