Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ This page lists all the individual contributions to the project by their author.
- Voxel light source position customization
- `UseFixedVoxelLighting`
- Warhead activation target health thresholds
- MP saves support for quicksave command and savegame trigger action
- **Uranusian (Thrifinesma)**:
- Mind Control enhancement
- Custom warhead splash list
Expand Down
2 changes: 2 additions & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
<ClCompile Include="src\Phobos.INI.cpp" />
<ClCompile Include="src\Phobos.cpp" />
<ClCompile Include="src\Utilities\ShapeTextPrinter.cpp" />
<ClCompile Include="src\Utilities\SpawnerHelper.cpp" />
<ClCompile Include="src\Utilities\Stream.cpp" />
<ClCompile Include="src\Utilities\Constructs.cpp" />
<ClCompile Include="src\Utilities\EnumFunctions.cpp" />
Expand Down Expand Up @@ -293,6 +294,7 @@
<ClInclude Include="src\Utilities\Savegame.h" />
<ClInclude Include="src\Utilities\SavegameDef.h" />
<ClInclude Include="src\Utilities\ShapeTextPrinter.h" />
<ClInclude Include="src\Utilities\SpawnerHelper.h" />
<ClInclude Include="src\Utilities\Stream.h" />
<ClInclude Include="src\Utilities\Swizzle.h" />
<ClInclude Include="src\Phobos.COM.h" />
Expand Down
2 changes: 1 addition & 1 deletion YRpp
Submodule YRpp updated 2 files
+3 −0 CellClass.h
+2 −0 GameStrings.h
7 changes: 6 additions & 1 deletion docs/User-Interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,12 @@ DisplayIncome.Offset=0,0 ; X,Y, pixels relative to default

### `[ ]` Quicksave

- Save the current singleplayer game.
- Saves the current game.

```{note}
For this command to work in multiplayer - you need to use a version of [YRpp spawner](https://github.com/CnCNet/yrpp-spawner) with multiplayer saves support.
```

- For localization, add `TXT_QUICKSAVE`, `TXT_QUICKSAVE_DESC`, `TXT_QUICKSAVE_SUFFIX` and `MSG:NotAvailableInMultiplayer` into your `.csf` file.
- These vanilla CSF entries will be used: `TXT_SAVING_GAME`, `TXT_GAME_WAS_SAVED` and `TXT_ERROR_SAVING_GAME`.
- The save should be looks like `Allied Mission 25: Esther's Money - QuickSaved`.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ Fixes / interactions with other extensions:
- Fixed an issue where some units crashed after the deployment transformation (by ststl & FlyStar)
- Fixed the bug that AlphaImage remained after unit entered tunnel (by NetsuNegi)
- Fixed an issue where Ares' `Convert.Deploy` triggers repeatedly when the unit is turning or moving (by CrimRecya)
- Fixed quicksave command and save game trigger action to work with YRpp spawner's multiplayer saves (by Kerbiter)
```

### 0.3.0.1
Expand Down
12 changes: 10 additions & 2 deletions src/Commands/QuickSave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include <ScenarioClass.h>
#include <HouseClass.h>
#include <SessionClass.h>
#include <EventClass.h>
#include <Utilities/GeneralUtils.h>
#include <Utilities/SpawnerHelper.h>

const char* QuickSaveCommandClass::GetName() const
{
Expand All @@ -22,7 +24,7 @@ const wchar_t* QuickSaveCommandClass::GetUICategory() const

const wchar_t* QuickSaveCommandClass::GetUIDescription() const
{
return GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_DESC", L"Save the current game (Singleplayer only).");
return GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_DESC", L"Save the current game.");
}

void QuickSaveCommandClass::Execute(WWKey eInput) const
Expand All @@ -40,7 +42,7 @@ void QuickSaveCommandClass::Execute(WWKey eInput) const
if (SessionClass::IsSingleplayer())
{
*reinterpret_cast<bool*>(0xABCE08) = false;
Phobos::ShouldQuickSave = true;
Phobos::ShouldSave = true;

if (SessionClass::IsCampaign())
Phobos::CustomGameSaveDescription = ScenarioClass::Instance->UINameLoaded;
Expand All @@ -49,6 +51,12 @@ void QuickSaveCommandClass::Execute(WWKey eInput) const
Phobos::CustomGameSaveDescription += L" - ";
Phobos::CustomGameSaveDescription += GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_SUFFIX", L"Quicksaved");
}
else if (SpawnerHelper::SaveGameEventHooked())
{
// Relinquish handling of the save game to spawner
EventClass event { HouseClass::CurrentPlayer->ArrayIndex, EventType::SaveGame };
EventClass::AddEvent(event);
}
else
{
PrintMessage(GeneralUtils::LoadStringUnlessMissing("MSG:NotAvailableInMultiplayer", L"QuickSave is not available in multiplayer"));
Expand Down
5 changes: 3 additions & 2 deletions src/Ext/TAction/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <New/Type/BannerTypeClass.h>

#include <Utilities/SavegameDef.h>
#include <Utilities/SpawnerHelper.h>
#include <Ext/House/Body.h>

//Static init
Expand Down Expand Up @@ -116,10 +117,10 @@ bool TActionExt::PlayAudioAtRandomWP(TActionClass* pThis, HouseClass* pHouse, Ob

bool TActionExt::SaveGame(TActionClass* pThis, HouseClass* pHouse, ObjectClass* pObject, TriggerClass* pTrigger, CellStruct const& location)
{
if (SessionClass::IsSingleplayer())
if (SessionClass::IsSingleplayer() || SpawnerHelper::SaveGameEventHooked())
{
*reinterpret_cast<bool*>(0xABCE08) = false;
Phobos::ShouldQuickSave = true;
Phobos::ShouldSave = true;

if (SessionClass::IsCampaign())
Phobos::CustomGameSaveDescription = ScenarioClass::Instance->UINameLoaded;
Expand Down
32 changes: 24 additions & 8 deletions src/Phobos.INI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ DEFINE_HOOK(0x52D21F, InitRules_ThingsThatShouldntBeSerailized, 0x6)
}


bool Phobos::ShouldQuickSave = false;
bool Phobos::ShouldSave = false;
std::wstring Phobos::CustomGameSaveDescription {};

void Phobos::PassiveSaveGame()
Expand All @@ -290,18 +290,34 @@ void Phobos::PassiveSaveGame()
pMessage,
RulesClass::Instance->MessageDelay,
HouseClass::CurrentPlayer->ColorSchemeIndex,
true
/* bSilent: */ true
);

// Force a redraw so that our message gets printed.
if (Game::SpecialDialog == 0)
{
MapClass::Instance.MarkNeedsRedraw(2);
MapClass::Instance.Render();
}
};

PrintMessage(StringTable::LoadString(GameStrings::TXT_SAVING_GAME));
char fName[0x80];

SYSTEMTIME time;
GetLocalTime(&time);
if (SessionClass::IsSingleplayer())
{
SYSTEMTIME time;
GetLocalTime(&time);

_snprintf_s(fName, sizeof(fName), "Map.%04u%02u%02u-%02u%02u%02u-%05u.sav",
time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);
}
else if (SessionClass::IsMultiplayer())
{
// Support for this is in the YRpp Spawner, be sure to read the respective comments

_snprintf_s(fName, 0x7F, "Map.%04u%02u%02u-%02u%02u%02u-%05u.sav",
time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);
_snprintf_s(fName, sizeof(fName), GameStrings::SAVEGAME_NET);
}

if (ScenarioClass::SaveGame(fName, Phobos::CustomGameSaveDescription.c_str()))
PrintMessage(StringTable::LoadString(GameStrings::TXT_GAME_WAS_SAVED));
Expand All @@ -318,10 +334,10 @@ DEFINE_HOOK(0x55DBCD, MainLoop_SaveGame, 0x6)
if (SessionClass::IsSingleplayer() && !scenario_saved)
{
scenario_saved = true;
if (Phobos::ShouldQuickSave)
if (Phobos::ShouldSave)
{
Phobos::PassiveSaveGame();
Phobos::ShouldQuickSave = false;
Phobos::ShouldSave = false;
Phobos::CustomGameSaveDescription.clear();
}
else if (Phobos::Config::SaveGameOnScenarioStart && SessionClass::IsCampaign())
Expand Down
2 changes: 1 addition & 1 deletion src/Phobos.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Phobos
static const wchar_t* VersionDescription;
static bool DisplayDamageNumbers;
static bool IsLoadingSaveGame;
static bool ShouldQuickSave;
static bool ShouldSave;
static std::wstring CustomGameSaveDescription;
static void PassiveSaveGame();
#ifdef DEBUG
Expand Down
6 changes: 6 additions & 0 deletions src/Utilities/SpawnerHelper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "SpawnerHelper.h"

bool SpawnerHelper::SaveGameEventHooked()
{
return SaveGameHookStart == 0xE9;
}
22 changes: 22 additions & 0 deletions src/Utilities/SpawnerHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <Windows.h>

#include <Helpers/CompileTime.h>

class SpawnerHelper
{
private:
DEFINE_REFERENCE(const char, SaveGameHookStart, 0x4C7A14u);

public:
// Spawner hooks 0x4C7A14 and places an LJMP there. We check that memory address on whether it is a valid LJMP opcode
// and assume (unreliable, but I am open for better ideas) that if it is, the save game event hook is active.
//
// This doesn't account for Spawner::Active, so in a case where the spawner is loaded but not active this will fail,
// but oh well I am not engeneering a complicated solution just to fix that niche case which wouldn't happen 99% of the time.
//
// To read more about this mess and possibly engineer a better solution, look up the comments mentioning 0x4C7A14 in spawner.
// - Kerbiter
static bool SaveGameEventHooked();
};