Skip to content

Commit 937822b

Browse files
authored
Support YRpp spawner's MP saves, port XNA CnCNet Client MP save handling (#1790)
Also see CnCNet/yrpp-spawner#48
1 parent 23dc9f9 commit 937822b

15 files changed

+310
-84
lines changed

CREDITS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ This page lists all the individual contributions to the project by their author.
4949
- Voxel light source position customization
5050
- `UseFixedVoxelLighting`
5151
- Warhead activation target health thresholds
52+
- MP saves support for quicksave command and savegame trigger action
53+
- Ported XNA CnCNet Client MP save handling
5254
- **Uranusian (Thrifinesma)**:
5355
- Mind Control enhancement
5456
- Custom warhead splash list

Phobos.vcxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,10 @@
197197
<ClCompile Include="src\Phobos.CRT.cpp" />
198198
<ClCompile Include="src\Phobos.Ext.cpp" />
199199
<ClCompile Include="src\Phobos.INI.cpp" />
200+
<ClCompile Include="src\Phobos.Save.cpp" />
200201
<ClCompile Include="src\Phobos.cpp" />
201202
<ClCompile Include="src\Utilities\ShapeTextPrinter.cpp" />
203+
<ClCompile Include="src\Utilities\SpawnerHelper.cpp" />
202204
<ClCompile Include="src\Utilities\Stream.cpp" />
203205
<ClCompile Include="src\Utilities\Constructs.cpp" />
204206
<ClCompile Include="src\Utilities\EnumFunctions.cpp" />
@@ -292,6 +294,7 @@
292294
<ClInclude Include="src\Utilities\Savegame.h" />
293295
<ClInclude Include="src\Utilities\SavegameDef.h" />
294296
<ClInclude Include="src\Utilities\ShapeTextPrinter.h" />
297+
<ClInclude Include="src\Utilities\SpawnerHelper.h" />
295298
<ClInclude Include="src\Utilities\Stream.h" />
296299
<ClInclude Include="src\Utilities\Swizzle.h" />
297300
<ClInclude Include="src\Phobos.COM.h" />

YRpp

docs/AI-Scripting-and-Mapping.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,12 @@ This category is empty for now.
498498

499499
### `500` Save Game
500500

501-
- Save the current game immediately (singleplayer game only).
501+
- Save the current game immediately.
502+
503+
```{note}
504+
For this action to work in multiplayer - you need to use a version of [YRpp spawner](https://github.com/CnCNet/yrpp-spawner) with multiplayer saves support.
505+
```
506+
502507
- These vanilla CSF entries will be used: `TXT_SAVING_GAME`, `TXT_GAME_WAS_SAVED` and `TXT_ERROR_SAVING_GAME`.
503508
- The save's description will look like `MapDescName - CSFText`.
504509
- For example: `Allied Mission 25: Esther's Money - Money Stolen`.

docs/Fixed-or-Improved-Logics.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,19 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
274274
- Fixed an issue where some units crashed after the deployment transformation.
275275
- Fixed the bug that AlphaImage remained after unit entered tunnel.
276276
- Fixed an issue where Ares' `Convert.Deploy` triggers repeatedly when the unit is turning or moving.
277+
- The game now automatically changes save file name from `SAVEGAME.NET` to `SVGM_XXX.NET` (where `XXX` is a number) when saving to prevent occasional overwriting of the save file when using Phobos with XNA CnCNet Client and saving too frequently.
278+
- 1000 save files are supported, from `SVGM_000.NET` to `SVGM_999.NET`. When the limit is reached, the game will overwrite the latest save file.
279+
- The previous `SVGM_XXX.NET` files are cleaned up before first copy if it's a new game, otherwise the highest numbered `SVGM_XXX.NET` file is found and the index is incremented, if possible.
280+
- The game also automatically copies `spawn.ini` to the save folder as `spawnSG.ini` when saving a game.
281+
282+
283+
```{note}
284+
The described behavior is a replica of and is compliant with XNA CnCNet Client's multiplayer save game support.
285+
```
286+
287+
```{note}
288+
At the moment this is only useful if you use a version of [YRpp Spawner](https://github.com/CnCNet/yrpp-spawner) with multiplayer saves support (along with [XNA CnCNet Client](https://github.com/CnCNet/xna-cncnet-client)).
289+
```
277290

278291
## Aircraft
279292

docs/User-Interface.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,12 @@ DisplayIncome.Offset=0,0 ; X,Y, pixels relative to default
462462

463463
### `[ ]` Quicksave
464464

465-
- Save the current singleplayer game.
465+
- Saves the current game.
466+
467+
```{note}
468+
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.
469+
```
470+
466471
- For localization, add `TXT_QUICKSAVE`, `TXT_QUICKSAVE_DESC`, `TXT_QUICKSAVE_SUFFIX` and `MSG:NotAvailableInMultiplayer` into your `.csf` file.
467472
- These vanilla CSF entries will be used: `TXT_SAVING_GAME`, `TXT_GAME_WAS_SAVED` and `TXT_ERROR_SAVING_GAME`.
468473
- The save should be looks like `Allied Mission 25: Esther's Money - QuickSaved`.

docs/Whats-New.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,8 @@ Fixes / interactions with other extensions:
837837
- Fixed an issue where some units crashed after the deployment transformation (by ststl & FlyStar)
838838
- Fixed the bug that AlphaImage remained after unit entered tunnel (by NetsuNegi)
839839
- Fixed an issue where Ares' `Convert.Deploy` triggers repeatedly when the unit is turning or moving (by CrimRecya)
840+
- Fixed quicksave command and save game trigger action to work with YRpp spawner's multiplayer saves (by Kerbiter)
841+
- Ported XNA CnCNet Client multiplayer save handling to get rid of occasional multiplayer save file overwriting when saving too fast (by Kerbiter)
840842
```
841843

842844
### 0.3.0.1

src/Commands/QuickSave.cpp

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#include <ScenarioClass.h>
44
#include <HouseClass.h>
55
#include <SessionClass.h>
6+
#include <EventClass.h>
67
#include <Utilities/GeneralUtils.h>
8+
#include <Utilities/SpawnerHelper.h>
79

810
const char* QuickSaveCommandClass::GetName() const
911
{
@@ -22,7 +24,7 @@ const wchar_t* QuickSaveCommandClass::GetUICategory() const
2224

2325
const wchar_t* QuickSaveCommandClass::GetUIDescription() const
2426
{
25-
return GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_DESC", L"Save the current game (Singleplayer only).");
27+
return GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_DESC", L"Save the current game.");
2628
}
2729

2830
void QuickSaveCommandClass::Execute(WWKey eInput) const
@@ -39,15 +41,12 @@ void QuickSaveCommandClass::Execute(WWKey eInput) const
3941

4042
if (SessionClass::IsSingleplayer())
4143
{
42-
*reinterpret_cast<bool*>(0xABCE08) = false;
43-
Phobos::ShouldQuickSave = true;
44-
45-
if (SessionClass::IsCampaign())
46-
Phobos::CustomGameSaveDescription = ScenarioClass::Instance->UINameLoaded;
47-
else
48-
Phobos::CustomGameSaveDescription = ScenarioClass::Instance->Name;
49-
Phobos::CustomGameSaveDescription += L" - ";
50-
Phobos::CustomGameSaveDescription += GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_SUFFIX", L"Quicksaved");
44+
Phobos::ScheduleGameSave(GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_SUFFIX", L"Quicksaved"));
45+
}
46+
else if (SpawnerHelper::IsSaveGameEventHooked())
47+
{
48+
// Relinquish handling of the save game to spawner
49+
EventClass::OutList.Add(EventClass { HouseClass::CurrentPlayer->ArrayIndex, EventType::SaveGame });
5150
}
5251
else
5352
{

src/Ext/TAction/Body.cpp

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <New/Type/BannerTypeClass.h>
1111

1212
#include <Utilities/SavegameDef.h>
13+
#include <Utilities/SpawnerHelper.h>
1314
#include <Ext/House/Body.h>
1415

1516
//Static init
@@ -116,18 +117,8 @@ bool TActionExt::PlayAudioAtRandomWP(TActionClass* pThis, HouseClass* pHouse, Ob
116117

117118
bool TActionExt::SaveGame(TActionClass* pThis, HouseClass* pHouse, ObjectClass* pObject, TriggerClass* pTrigger, CellStruct const& location)
118119
{
119-
if (SessionClass::IsSingleplayer())
120-
{
121-
*reinterpret_cast<bool*>(0xABCE08) = false;
122-
Phobos::ShouldQuickSave = true;
123-
124-
if (SessionClass::IsCampaign())
125-
Phobos::CustomGameSaveDescription = ScenarioClass::Instance->UINameLoaded;
126-
else
127-
Phobos::CustomGameSaveDescription = ScenarioClass::Instance->Name;
128-
Phobos::CustomGameSaveDescription += L" - ";
129-
Phobos::CustomGameSaveDescription += StringTable::LoadString(pThis->Text);
130-
}
120+
if (SessionClass::IsSingleplayer() || SpawnerHelper::IsSaveGameEventHooked())
121+
Phobos::ScheduleGameSave(StringTable::LoadString(pThis->Text));
131122

132123
return true;
133124
}

src/Phobos.INI.cpp

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -277,56 +277,3 @@ DEFINE_HOOK(0x52D21F, InitRules_ThingsThatShouldntBeSerailized, 0x6)
277277

278278
return 0;
279279
}
280-
281-
282-
bool Phobos::ShouldQuickSave = false;
283-
std::wstring Phobos::CustomGameSaveDescription {};
284-
285-
void Phobos::PassiveSaveGame()
286-
{
287-
auto PrintMessage = [](const wchar_t* pMessage)
288-
{
289-
MessageListClass::Instance.PrintMessage(
290-
pMessage,
291-
RulesClass::Instance->MessageDelay,
292-
HouseClass::CurrentPlayer->ColorSchemeIndex,
293-
true
294-
);
295-
};
296-
297-
PrintMessage(StringTable::LoadString(GameStrings::TXT_SAVING_GAME));
298-
char fName[0x80];
299-
300-
SYSTEMTIME time;
301-
GetLocalTime(&time);
302-
303-
_snprintf_s(fName, 0x7F, "Map.%04u%02u%02u-%02u%02u%02u-%05u.sav",
304-
time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);
305-
306-
if (ScenarioClass::SaveGame(fName, Phobos::CustomGameSaveDescription.c_str()))
307-
PrintMessage(StringTable::LoadString(GameStrings::TXT_GAME_WAS_SAVED));
308-
else
309-
PrintMessage(StringTable::LoadString(GameStrings::TXT_ERROR_SAVING_GAME));
310-
}
311-
312-
DEFINE_HOOK(0x55DBCD, MainLoop_SaveGame, 0x6)
313-
{
314-
// This happens right before LogicClass::Update()
315-
enum { SkipSave = 0x55DC99, InitialSave = 0x55DBE6 };
316-
317-
bool& scenario_saved = *reinterpret_cast<bool*>(0xABCE08);
318-
if (SessionClass::IsSingleplayer() && !scenario_saved)
319-
{
320-
scenario_saved = true;
321-
if (Phobos::ShouldQuickSave)
322-
{
323-
Phobos::PassiveSaveGame();
324-
Phobos::ShouldQuickSave = false;
325-
Phobos::CustomGameSaveDescription.clear();
326-
}
327-
else if (Phobos::Config::SaveGameOnScenarioStart && SessionClass::IsCampaign())
328-
return InitialSave;
329-
}
330-
331-
return SkipSave;
332-
}

0 commit comments

Comments
 (0)