feat(Core/Player): restore additional saves behind a config bitmask (#26825)

This commit is contained in:
Andrew
2026-07-27 21:31:00 -03:00
committed by GitHub
parent 6f0ba8e896
commit b7387f653d
7 changed files with 81 additions and 1 deletions

View File

@@ -1838,6 +1838,20 @@ PlayerSave.Stats.MinLevel = 0
PlayerSave.Stats.SaveOnlyOnLogout = 1
#
# PlayerSave.AdditionalSaves
# Description: Bitmask of player data to save to the database a few seconds after
# important changes instead of waiting for the next periodic save
# (PlayerSaveInterval). Reduces the progress lost on a server crash.
# Combine the values to enable multiple triggers.
# Values: 1 - Inventory and gold (after looting an item of rare or better quality)
# 2 - Quest status (after quest status changes)
# 4 - Achievements (after completing an achievement)
# Default: 0 - (Disabled)
# 7 - (All of the above)
PlayerSave.AdditionalSaves = 0
#
# CleanCharacterDB
# Description: Clean out deprecated achievements, skills, spells and talents from the db.

View File

@@ -2333,6 +2333,8 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement)
}
}
_player->AdditionalSavingAddMask(ADDITIONAL_SAVING_ACHIEVEMENTS);
if (achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL) && !_player->GetSession()->HasPermission(rbac::RBAC_PERM_CANNOT_EARN_REALM_FIRST_ACHIEVEMENTS))
sAchievementMgr->SetRealmCompleted(achievement);

View File

@@ -988,6 +988,7 @@ enum AdditionalSaving
ADDITIONAL_SAVING_NONE = 0x00,
ADDITIONAL_SAVING_INVENTORY_AND_GOLD = 0x01,
ADDITIONAL_SAVING_QUEST_STATUS = 0x02,
ADDITIONAL_SAVING_ACHIEVEMENTS = 0x04,
};
enum PlayerCommandStates
@@ -2618,7 +2619,7 @@ public:
bool IsFreeFlying() const { return HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || HasAuraType(SPELL_AURA_FLY); }
// saving
void AdditionalSavingAddMask(uint8 mask) { m_additionalSaveTimer = 2000; m_additionalSaveMask |= mask; }
void AdditionalSavingAddMask(uint8 mask);
// arena spectator
[[nodiscard]] bool IsSpectator() const { return m_ExtraFlags & PLAYER_EXTRA_SPECTATOR_ON; }
void SetIsSpectator(bool on);
@@ -2814,6 +2815,7 @@ protected:
void _SaveCharacter(bool create, CharacterDatabaseTransaction trans);
void _SaveInstanceTimeRestrictions(CharacterDatabaseTransaction trans);
void _SavePlayerSettings(CharacterDatabaseTransaction trans);
void UpdateAdditionalSaves(uint32 p_time);
/*********************************************************/
/*** ENVIRONMENTAL SYSTEM ***/

View File

@@ -7254,6 +7254,18 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create, bool logo
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
}
// flag data to be saved by UpdateAdditionalSaves a moment after an important change,
// filtered by the PlayerSave.AdditionalSaves config mask
void Player::AdditionalSavingAddMask(uint8 mask)
{
mask &= sWorld->getIntConfig(CONFIG_ADDITIONAL_SAVES);
if (!mask)
return;
m_additionalSaveTimer = 2000;
m_additionalSaveMask |= mask;
}
// fast save function for item/money cheating preventing - save only inventory and money state
void Player::SaveInventoryAndGoldToDB(CharacterDatabaseTransaction trans)
{

View File

@@ -332,6 +332,8 @@ void Player::Update(uint32 p_time)
}
}
UpdateAdditionalSaves(p_time);
// Handle Water/drowning
HandleDrowning(p_time);
@@ -2399,3 +2401,49 @@ void Player::ProcessSpellQueue()
break;
}
}
// save only the data flagged by AdditionalSavingAddMask shortly after
// important changes, so a crash loses at most a few seconds of them
void Player::UpdateAdditionalSaves(uint32 p_time)
{
if (!m_additionalSaveTimer || GetSession()->isLogingOut())
return;
if (m_additionalSaveTimer > p_time)
{
m_additionalSaveTimer -= p_time;
return;
}
uint8 mask = m_additionalSaveMask;
m_additionalSaveTimer = 0;
m_additionalSaveMask = 0;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
if (mask & ADDITIONAL_SAVING_INVENTORY_AND_GOLD)
SaveInventoryAndGoldToDB(trans);
if (mask & ADDITIONAL_SAVING_QUEST_STATUS)
{
_SaveQuestStatus(trans);
// if nothing changed, nothing will happen
_SaveDailyQuestStatus(trans);
_SaveWeeklyQuestStatus(trans);
_SaveSeasonalQuestStatus(trans);
_SaveMonthlyQuestStatus(trans);
}
if (mask & ADDITIONAL_SAVING_ACHIEVEMENTS)
{
m_achievementMgr->SaveToDB(trans);
// achievements are often earned together with skill or gold changes
// (professions, riding, wealth), save those too to keep the DB consistent
_SaveSkills(trans);
SaveGoldToDB(trans);
}
CharacterDatabase.CommitTransaction(trans);
}

View File

@@ -168,6 +168,7 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_INTERVAL_SAVE, "PlayerSaveInterval", 900000);
SetConfigValue<uint32>(CONFIG_INTERVAL_DISCONNECT_TOLERANCE, "DisconnectToleranceInterval", 0);
SetConfigValue<bool>(CONFIG_STATS_SAVE_ONLY_ON_LOGOUT, "PlayerSave.Stats.SaveOnlyOnLogout", true);
SetConfigValue<uint32>(CONFIG_ADDITIONAL_SAVES, "PlayerSave.AdditionalSaves", 0);
SetConfigValue<bool>(CONFIG_VALIDATE_SKILL_LEARNED_BY_SPELLS, "ValidateSkillLearnedBySpells", true);
SetConfigValue<uint32>(CONFIG_MIN_LEVEL_STAT_SAVE, "PlayerSave.Stats.MinLevel", 0, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value < MAX_LEVEL; }, "< MAX_LEVEL");

View File

@@ -27,6 +27,7 @@ enum ServerConfigs
CONFIG_ALLOW_PLAYER_COMMANDS,
CONFIG_CLEAN_CHARACTER_DB,
CONFIG_STATS_SAVE_ONLY_ON_LOGOUT,
CONFIG_ADDITIONAL_SAVES,
CONFIG_ALLOW_TWO_SIDE_ACCOUNTS,
CONFIG_ALLOW_TWO_SIDE_INTERACTION_CALENDAR,
CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT,