Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions Source/diablo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*
* Implementation of the main game initialization functions.
*/
#include <algorithm>
#include <array>
#include <cstdint>
#include <string_view>
Expand All @@ -19,7 +20,7 @@
#endif
#endif

#include <fmt/format.h>

Check warning on line 23 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:23:1 [misc-include-cleaner]

included header format.h is not used directly

#include <config.h>

Expand All @@ -42,7 +43,7 @@
#include "diablo_msg.hpp"
#include "discord/discord.h"
#include "doom.h"
#include "encrypt.h"

Check warning on line 46 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:46:1 [misc-include-cleaner]

included header encrypt.h is not used directly
#include "engine/backbuffer_state.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/demomode.h"
Expand All @@ -61,10 +62,10 @@
#include "hwcursor.hpp"
#include "init.hpp"
#include "inv.h"
#include "levels/drlg_l1.h"

Check warning on line 65 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:65:1 [misc-include-cleaner]

included header drlg_l1.h is not used directly
#include "levels/drlg_l2.h"

Check warning on line 66 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:66:1 [misc-include-cleaner]

included header drlg_l2.h is not used directly
#include "levels/drlg_l3.h"

Check warning on line 67 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:67:1 [misc-include-cleaner]

included header drlg_l3.h is not used directly
#include "levels/drlg_l4.h"

Check warning on line 68 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:68:1 [misc-include-cleaner]

included header drlg_l4.h is not used directly
#include "levels/gendung.h"
#include "levels/setmaps.h"
#include "levels/themes.h"
Expand Down Expand Up @@ -112,10 +113,10 @@
#include "utils/paths.h"
#include "utils/screen_reader.hpp"
#include "utils/sdl_compat.h"
#include "utils/sdl_thread.h"

Check warning on line 116 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:116:1 [misc-include-cleaner]

included header sdl_thread.h is not used directly
#include "utils/status_macros.hpp"
#include "utils/str_cat.hpp"
#include "utils/utf8.hpp"

Check warning on line 119 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:119:1 [misc-include-cleaner]

included header utf8.hpp is not used directly

#ifndef USE_SDL1
#include "controls/touch/gamepad.h"
Expand All @@ -133,8 +134,8 @@
namespace devilution {

uint32_t DungeonSeeds[NUMLEVELS];
std::optional<uint32_t> LevelSeeds[NUMLEVELS];

Check warning on line 137 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:137:6 [misc-include-cleaner]

no header providing "std::optional" is directly included
Point MousePosition;

Check warning on line 138 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:138:1 [misc-include-cleaner]

no header providing "devilution::Point" is directly included
bool gbRunGameResult;
bool ReturnToMainMenu;
/** Enable updating of player character, set to false once Diablo dies */
Expand Down Expand Up @@ -175,6 +176,72 @@
/** To know if surfaces have been initialized or not */
bool was_window_init = false;
bool was_ui_init = false;
using TickCount = decltype(SDL_GetTicks());

TickCount autoSaveNextTimerDueAt = 0;
AutoSaveReason pendingAutoSaveReason = AutoSaveReason::None;
/** Prevent autosave from running immediately after session start before player interaction. */
bool hasEnteredActiveGameplay = false;
TickCount autoSaveCooldownUntil = 0;
TickCount autoSaveCombatCooldownUntil = 0;
bool wasAutoSaveEnabled = false;
constexpr TickCount AutoSaveCooldownMilliseconds = 5000;
constexpr TickCount AutoSaveCombatCooldownMilliseconds = 4000;

bool HaveTicksPassed(TickCount now, TickCount due)
{
#ifdef USE_SDL3
return now >= due;
#else
return SDL_TICKS_PASSED(now, due);
#endif
}

bool HaveTicksPassed(TickCount due)
{
return HaveTicksPassed(SDL_GetTicks(), due);
}

TickCount GetAutoSaveIntervalMilliseconds()
{
return static_cast<TickCount>(std::max(1, *GetOptions().Gameplay.autoSaveIntervalSeconds)) * 1000;
}

int GetAutoSavePriority(AutoSaveReason reason)
{
switch (reason) {
case AutoSaveReason::BossKill:
return 4;
case AutoSaveReason::TownEntry:
return 3;
case AutoSaveReason::UniquePickup:
return 2;
case AutoSaveReason::Timer:
return 1;
case AutoSaveReason::None:
return 0;
}

return 0;
}

const char *GetAutoSaveReasonName(AutoSaveReason reason)
{
switch (reason) {
case AutoSaveReason::None:
return "None";
case AutoSaveReason::Timer:
return "Timer";
case AutoSaveReason::TownEntry:
return "TownEntry";
case AutoSaveReason::BossKill:
return "BossKill";
case AutoSaveReason::UniquePickup:
return "UniquePickup";
}

return "Unknown";
}

void StartGame(interface_mode uMsg)
{
Expand All @@ -194,6 +261,12 @@
sgnTimeoutCurs = CURSOR_NONE;
sgbMouseDown = CLICK_NONE;
LastPlayerAction = PlayerActionType::None;
hasEnteredActiveGameplay = false;
autoSaveCooldownUntil = 0;
autoSaveCombatCooldownUntil = 0;
pendingAutoSaveReason = AutoSaveReason::None;
autoSaveNextTimerDueAt = SDL_GetTicks() + GetAutoSaveIntervalMilliseconds();
wasAutoSaveEnabled = *GetOptions().Gameplay.autoSaveEnabled;
}

void FreeGame()
Expand Down Expand Up @@ -1553,6 +1626,32 @@
RedrawViewport();
pfile_update(false);

if (!hasEnteredActiveGameplay && LastPlayerAction != PlayerActionType::None)
hasEnteredActiveGameplay = true;

const bool autoSaveEnabled = *GetOptions().Gameplay.autoSaveEnabled;
if (autoSaveEnabled != wasAutoSaveEnabled) {
if (!autoSaveEnabled)
pendingAutoSaveReason = AutoSaveReason::None;

autoSaveNextTimerDueAt = SDL_GetTicks() + GetAutoSaveIntervalMilliseconds();
wasAutoSaveEnabled = autoSaveEnabled;
}

if (autoSaveEnabled) {
const TickCount now = SDL_GetTicks();
if (HaveTicksPassed(now, autoSaveNextTimerDueAt)) {
QueueAutoSave(AutoSaveReason::Timer);
}
}

if (HasPendingAutoSave() && IsAutoSaveSafe()) {
if (AttemptAutoSave(pendingAutoSaveReason)) {
pendingAutoSaveReason = AutoSaveReason::None;
autoSaveNextTimerDueAt = SDL_GetTicks() + GetAutoSaveIntervalMilliseconds();
}
}

plrctrls_after_game_logic();
}

Expand Down Expand Up @@ -1802,6 +1901,127 @@

} // namespace

bool HasActiveMonstersForAutoSave();

bool IsAutoSaveSafe()
{
if (gbIsMultiplayer || !gbRunGame)
return false;

if (!hasEnteredActiveGameplay)
return false;

if (!HaveTicksPassed(autoSaveCooldownUntil))
return false;

if (!HaveTicksPassed(autoSaveCombatCooldownUntil))
return false;

if (movie_playing || PauseMode != 0 || gmenu_is_active() || IsPlayerInStore())
return false;

if (MyPlayer == nullptr || IsPlayerDead() || MyPlayer->_pLvlChanging || LoadingMapObjects)
return false;

if (qtextflag || DropGoldFlag || IsWithdrawGoldOpen || pcurs != CURSOR_HAND)
return false;

if (leveltype != DTYPE_TOWN && HasActiveMonstersForAutoSave())
return false;

return true;
}

void MarkCombatActivity()
{
autoSaveCombatCooldownUntil = SDL_GetTicks() + AutoSaveCombatCooldownMilliseconds;
}

bool HasActiveMonstersForAutoSave()
{
for (const Monster &monster : Monsters) {
if (monster.hitPoints <= 0 || monster.mode == MonsterMode::Death || monster.mode == MonsterMode::Petrified)
continue;

if (monster.activeForTicks == 0)
continue;

return true;
}

return false;
}

int GetSecondsUntilNextAutoSave()
{
if (!*GetOptions().Gameplay.autoSaveEnabled)
return -1;

if (HasPendingAutoSave())
return 0;

const TickCount now = SDL_GetTicks();
if (HaveTicksPassed(now, autoSaveNextTimerDueAt))
return 0;

const TickCount remainingMilliseconds = autoSaveNextTimerDueAt - now;
return static_cast<int>((remainingMilliseconds + 999) / 1000);
}

bool HasPendingAutoSave()
{
return pendingAutoSaveReason != AutoSaveReason::None;
}

void RequestAutoSave(AutoSaveReason reason)
{
if (!*GetOptions().Gameplay.autoSaveEnabled)
return;

if (gbIsMultiplayer)
return;

QueueAutoSave(reason);
}

void QueueAutoSave(AutoSaveReason reason)
{
if (gbIsMultiplayer)
return;

if (!*GetOptions().Gameplay.autoSaveEnabled)
return;

if (GetAutoSavePriority(reason) > GetAutoSavePriority(pendingAutoSaveReason)) {
pendingAutoSaveReason = reason;
LogVerbose("Autosave queued: {}", GetAutoSaveReasonName(reason));
}
}

bool AttemptAutoSave(AutoSaveReason reason)
{
if (!IsAutoSaveSafe())
return false;

const EventHandler saveProc = SetEventHandler(DisableInputEventHandler);
const TickCount currentTime = SDL_GetTicks();
const SaveResult saveResult = SaveGame(SaveKind::Auto);
const TickCount afterSaveTime = SDL_GetTicks();
const bool saveSucceeded = saveResult == SaveResult::Success;

autoSaveCooldownUntil = afterSaveTime + AutoSaveCooldownMilliseconds;
if (saveSucceeded) {
autoSaveNextTimerDueAt = afterSaveTime + GetAutoSaveIntervalMilliseconds();
if (reason != AutoSaveReason::Timer) {
const int timeElapsed = static_cast<int>(afterSaveTime - currentTime);
const int displayTime = std::max(500, 1000 - timeElapsed);
InitDiabloMsg(EMSG_GAME_SAVED, displayTime);
}
}
SetEventHandler(saveProc);
return saveSucceeded;
}

void InitKeymapActions()
{
Options &options = GetOptions();
Expand Down Expand Up @@ -3434,6 +3654,8 @@
CompleteProgress();

LoadGameLevelCalculateCursor();
if (leveltype == DTYPE_TOWN && lvldir != ENTRY_LOAD && !firstflag)
::devilution::RequestAutoSave(AutoSaveReason::TownEntry);
return {};
}

Expand Down
15 changes: 15 additions & 0 deletions Source/diablo.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ enum class PlayerActionType : uint8_t {
OperateObject,
};

enum class AutoSaveReason : uint8_t {
None,
Timer,
TownEntry,
BossKill,
UniquePickup,
};

extern uint32_t DungeonSeeds[NUMLEVELS];
extern DVL_API_FOR_TEST std::optional<uint32_t> LevelSeeds[NUMLEVELS];
extern Point MousePosition;
Expand Down Expand Up @@ -101,6 +109,13 @@ bool PressEscKey();
void DisableInputEventHandler(const SDL_Event &event, uint16_t modState);
tl::expected<void, std::string> LoadGameLevel(bool firstflag, lvl_entry lvldir);
bool IsDiabloAlive(bool playSFX);
void MarkCombatActivity();
bool IsAutoSaveSafe();
int GetSecondsUntilNextAutoSave();
bool HasPendingAutoSave();
void RequestAutoSave(AutoSaveReason reason);
void QueueAutoSave(AutoSaveReason reason);
bool AttemptAutoSave(AutoSaveReason reason);
void PrintScreen(SDL_Keycode vkey);

/**
Expand Down
Loading
Loading