Skip to content

Commit 63a6c66

Browse files
committed
Initial draft of savegames
Sections save their runtime state into a SaveSect-<x>-<y>.c4g subgroup; x denotes an ascending counter for sorting purposes, y the section number. The exception to this is the first section, which saves into the main group as well as Game.txt for compatibility.
1 parent ea0cfe1 commit 63a6c66

14 files changed

+512
-104
lines changed

src/C4Components.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
#define C4CFN_Languages "Language.c4g"
3232

3333
#define C4CFN_ScenarioSections "Sect*.c4g"
34+
#define C4CFN_Section "Sect{}.c4g"
35+
#define C4CFN_SectionRuntimeData "Section.txt"
36+
#define C4CFN_SavedSection "SaveSect{}-{}.c4g"
37+
#define C4CFN_SavedSectionFiles "SaveSect*.c4g"
3438

3539
#define C4CFN_Mouse "Mouse.c4f"
3640
#define C4CFN_Keyboard "Keyboard.c4f"

src/C4Config.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,12 @@ const char *C4Config::AtTempPath(const char *szFilename)
664664
return AtPathFilename;
665665
}
666666

667+
const char *C4Config::AtTempPathWithPrefix(std::string_view prefix, std::string_view filename)
668+
{
669+
FormatWithNull(AtPathFilename, "{}{}{}", General.TempPath, prefix, filename);
670+
return AtPathFilename;
671+
}
672+
667673
#ifdef C4ENGINE
668674

669675
const char *C4Config::AtNetworkPath(const char *szFilename)

src/C4Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ class C4Config
329329
bool Load(bool forceWorkingDirectory = true, const char *szConfigFile = nullptr);
330330
bool Init();
331331
const char *AtExePath(const char *szFilename);
332+
const char *AtTempPathWithPrefix(std::string_view prefix, std::string_view filename);
332333
const char *AtTempPath(const char *szFilename);
333334
#ifdef C4ENGINE
334335
const char *AtNetworkPath(const char *szFilename);

src/C4Game.cpp

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,45 @@ bool C4Game::OpenScenario()
392392
LogFatal(C4ResStrTableKey::IDS_ERR_LOAD_RUNTIMEDATA); return false;
393393
}
394394

395+
if (C4S.Head.SaveGame)
396+
{
397+
// Sadly, the first section requires some rather convoluted section loading,
398+
// as its data gets deserialized from Game.txt for compatibility with older
399+
// save games.s
400+
if (!Sections.front()->AssumeGroupAsSaveGameGroup())
401+
{
402+
LogFatal(C4ResStrTableKey::IDS_ERR_SECTION); return false;
403+
}
404+
405+
std::array<char, _MAX_PATH + 1> filename;
406+
407+
ScenarioFile.ResetSearch();
408+
while (ScenarioFile.FindNextEntry(C4CFN_SavedSectionFiles, filename.data()))
409+
{
410+
auto section = C4Section::FromSaveGame(ScenarioFile, filename.data());
411+
if (!section)
412+
{
413+
LogFatal(C4ResStrTableKey::IDS_ERR_SECTION); return false;
414+
}
415+
416+
Sections.emplace_back(std::move(section));
417+
}
418+
419+
C4Section::AdjustEnumerationIndex(std::ranges::max(Sections | std::views::transform(&C4Section::Number)) + 1);
420+
421+
// C4Section::InitFromSaveGameAfterLoad potentially calls C4Section::InitFromTemplate, which calls
422+
// C4Group::OpenAsChild, which moves the group search pointer; therefore we can't combine it with the
423+
// loop above.
424+
425+
for (auto &section : Sections | std::views::drop(1))
426+
{
427+
if (!section->InitFromSaveGameAfterLoad(ScenarioFile))
428+
{
429+
LogFatal(C4ResStrTableKey::IDS_ERR_SECTION); return false;
430+
}
431+
}
432+
}
433+
395434
// If scenario is a directory: Watch for changes
396435
if (!ScenarioFile.IsPacked())
397436
{
@@ -1426,9 +1465,7 @@ void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp)
14261465

14271466
if (comp.fExact)
14281467
{
1429-
pComp->Value(mkNamingAdapt(Sections.front()->Weather, "Weather")); // FIXME: sections
1430-
pComp->Value(mkNamingAdapt(Sections.front()->Landscape, "Landscape"));
1431-
pComp->Value(mkNamingAdapt(Sections.front()->Landscape.Sky, "Sky"));
1468+
pComp->Value(mkParAdapt(*Sections.front(), true));
14321469
}
14331470

14341471
pComp->Value(mkNamingAdapt(mkNamingPtrAdapt(Sections.front()->GlobalEffects, "GlobalEffects"), "Effects"));
@@ -1734,6 +1771,7 @@ bool C4Game::QuickSave(const char *strFilename, const char *strTitle, bool fForc
17341771
{
17351772
Log(C4ResStrTableKey::IDS_GAME_FAILSAVEGAME); delete pGameSave; return false;
17361773
}
1774+
17371775
delete pGameSave;
17381776

17391777
// Success
@@ -2892,6 +2930,13 @@ void C4Game::SectionLoadProc(std::stop_token stopToken)
28922930

28932931
{
28942932
const std::lock_guard lock{SectionLoadMutex};
2933+
2934+
if (SectionLoadQueue.empty())
2935+
{
2936+
spdlog::error("SectionLoadProc: Semaphore acquired, but queue is empty!");
2937+
continue;
2938+
}
2939+
28952940
sectionLoadArgs = std::move(SectionLoadQueue.front());
28962941
SectionLoadQueue.pop();
28972942
}

src/C4GameObjects.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,11 +684,11 @@ int C4GameObjects::Load(C4Section &section, C4Group &hGroup, bool fKeepInactive)
684684
bool C4GameObjects::Save(C4Section &section, C4Group &hGroup, bool fSaveGame, bool fSaveInactive)
685685
{
686686
// Save to temp file
687-
char szFilename[_MAX_PATH + 1]; SCopy(Config.AtTempPath(C4CFN_ScenarioObjects), szFilename);
687+
char szFilename[_MAX_PATH + 1]; SCopy(Config.AtTempPathWithPrefix(section.GetNumberAsString(), C4CFN_ScenarioObjects), szFilename);
688688
if (!Save(section, szFilename, fSaveGame, fSaveInactive)) return false;
689689

690690
// Move temp file to group
691-
hGroup.Move(szFilename, nullptr); // check?
691+
hGroup.Move(szFilename, C4CFN_ScenarioObjects); // check?
692692
// Success
693693
return true;
694694
}

src/C4GameSave.cpp

Lines changed: 112 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ bool C4GameSave::SaveScenarioSections()
136136
return true;
137137
}
138138

139-
bool C4GameSave::SaveLandscape(C4Section &section)
139+
bool C4GameSave::SaveLandscape(C4Section &section, C4Group &group)
140140
{
141141
// exact?
142142
if (section.Landscape.Mode == C4LSC_Exact || GetForceExactLandscape())
@@ -146,20 +146,20 @@ bool C4GameSave::SaveLandscape(C4Section &section)
146146
section.Objects.RemoveSolidMasks();
147147
bool fSuccess;
148148
if (section.Landscape.Mode == C4LSC_Exact)
149-
fSuccess = !!section.Landscape.Save(*pSaveGroup);
149+
fSuccess = !!section.Landscape.SaveExact(group);
150150
else
151-
fSuccess = !!section.Landscape.SaveDiff(*pSaveGroup, !IsSynced());
151+
fSuccess = !!section.Landscape.SaveDiff(group, !IsSynced());
152152
section.Objects.PutSolidMasks();
153153
if (!fSuccess) return false;
154154
DBGRECOFF.Clear();
155155
// PXS
156-
if (!section.PXS.Save(*pSaveGroup)) return false;
156+
if (!section.PXS.Save(group)) return false;
157157
// MassMover (create copy, may not modify running data)
158158
C4MassMoverSet MassMoverSet{section};
159159
MassMoverSet.Copy(section.MassMover);
160-
if (!MassMoverSet.Save(*pSaveGroup)) return false;
160+
if (!MassMoverSet.Save(group)) return false;
161161
// Material enumeration
162-
if (!section.Material.SaveEnumeration(*pSaveGroup)) return false;
162+
if (!section.Material.SaveEnumeration(group)) return false;
163163
}
164164
// static / dynamic
165165
if (section.Landscape.Mode == C4LSC_Static)
@@ -171,9 +171,9 @@ bool C4GameSave::SaveLandscape(C4Section &section)
171171
if (!GetForceExactLandscape())
172172
{
173173
// save map
174-
if (!section.Landscape.SaveMap(*pSaveGroup)) return false;
174+
if (!section.Landscape.SaveMap(group)) return false;
175175
// save textures (if changed)
176-
if (!section.Landscape.SaveTextures(*pSaveGroup)) return false;
176+
if (!section.Landscape.SaveTextures(group)) return false;
177177
}
178178
}
179179
else if (section.Landscape.Mode != C4LSC_Exact)
@@ -193,11 +193,46 @@ bool C4GameSave::SaveRuntimeData()
193193
Log(C4ResStrTableKey::IDS_ERR_SAVE_SCENSECTIONS); return false;
194194
}
195195

196-
for (const auto &section : Game.Sections)
196+
if (!SaveSection(*Game.Sections.front(), *pSaveGroup, false))
197197
{
198-
if (!SaveSection(*section))
198+
return false;
199+
}
200+
201+
if (IsExact())
202+
{
203+
std::size_t counter{1};
204+
205+
for (auto it = std::next(Game.Sections.begin()); it != Game.Sections.end(); ++it)
199206
{
200-
return false;
207+
C4Section &section{**it};
208+
if (!SaveSection(section, std::format(C4CFN_SavedSection, counter++, section.Number), true))
209+
{
210+
return false;
211+
}
212+
}
213+
}
214+
else
215+
{
216+
for (auto it = std::next(Game.Sections.begin()); it != Game.Sections.end(); ++it)
217+
{
218+
C4Section &section{**it};
219+
220+
C4Group sectGroup;
221+
const std::string filename{std::format(C4CFN_Section, section.GetNameForSaveGame())};
222+
if (sectGroup.OpenAsChild(pSaveGroup, filename.c_str()))
223+
{
224+
if (!SaveSection(section, sectGroup, true))
225+
{
226+
return false;
227+
}
228+
}
229+
else
230+
{
231+
if (!SaveSection(section, filename, true))
232+
{
233+
return false;
234+
}
235+
}
201236
}
202237
}
203238

@@ -263,22 +298,77 @@ bool C4GameSave::SaveRuntimeData()
263298
return true;
264299
}
265300

266-
bool C4GameSave::SaveSection(C4Section &section)
301+
bool C4GameSave::SaveSection(C4Section &section, const std::string &filename, const bool saveRuntimeData)
302+
{
303+
pSaveGroup->SetStdOutput(true);
304+
std::string tmpFile{Config.AtTempPath(filename.c_str())};
305+
MakeTempFilename(tmpFile.data());
306+
307+
{
308+
C4Group tmpGroup;
309+
if (!tmpGroup.Open(tmpFile.c_str(), true))
310+
{
311+
return false;
312+
}
313+
314+
if (!SaveSection(section, tmpGroup, saveRuntimeData))
315+
{
316+
return false;
317+
}
318+
319+
tmpGroup.Close();
320+
}
321+
322+
if (!pSaveGroup->Move(tmpFile.c_str(), filename.c_str()))
323+
{
324+
return false;
325+
}
326+
327+
return true;
328+
}
329+
330+
bool C4GameSave::SaveSection(C4Section &section, C4Group &group, const bool saveRuntimeData)
267331
{
268332
// landscape
269-
if (!SaveLandscape(section))
333+
if (!SaveLandscape(section, group))
270334
{
271335
Log(C4ResStrTableKey::IDS_ERR_SAVE_LANDSCAPE);
272336
return false;
273337
}
274338

275339
// Objects
276-
if (!section.Objects.Save(section, (*pSaveGroup), IsExact(), true))
340+
if (!section.Objects.Save(section, group, IsExact(), true))
277341
{
278342
Log(C4ResStrTableKey::IDS_ERR_SAVE_OBJECTS);
279343
return false;
280344
}
281345

346+
// Save core for non-scenarios
347+
if (!IsExact())
348+
{
349+
C4Scenario c4s;
350+
c4s.Animals = rC4S.Animals;
351+
c4s.Disasters = rC4S.Disasters;
352+
c4s.Environment = rC4S.Environment;
353+
c4s.Game = rC4S.Game;
354+
c4s.Landscape = rC4S.Landscape;
355+
std::ranges::copy(rC4S.PlrStart, c4s.PlrStart);
356+
c4s.Weather = rC4S.Weather;
357+
358+
if (!c4s.Save(group))
359+
{
360+
return false;
361+
}
362+
}
363+
364+
if (saveRuntimeData)
365+
{
366+
if (!section.SaveRuntimeData(group))
367+
{
368+
return false;
369+
}
370+
}
371+
282372
return true;
283373
}
284374

@@ -308,7 +398,7 @@ bool C4GameSave::SaveDesc(C4Group &hToGroup)
308398

309399
// Save to file
310400
StdStrBuf buf{desc.c_str(), desc.size()};
311-
return !!hToGroup.Add(fmt::sprintf(C4CFN_ScenarioDesc, szLang).c_str(), buf, false, true);
401+
return !!hToGroup.Add(std::format(C4CFN_ScenarioDesc, szLang).c_str(), buf, false, true);
312402
}
313403

314404
void C4GameSave::WriteDescLineFeed(std::string &desc)
@@ -500,7 +590,7 @@ bool C4GameSave::Save(C4Group &hToGroup, bool fKeepGroup)
500590
{
501591
pSaveGroup->Delete(C4CFN_ScenarioTitle);
502592
pSaveGroup->Delete(C4CFN_ScenarioIcon);
503-
pSaveGroup->Delete(fmt::sprintf(C4CFN_ScenarioDesc, "*").c_str());
593+
pSaveGroup->Delete(std::format(C4CFN_ScenarioDesc, "*").c_str());
504594
pSaveGroup->Delete(C4CFN_Titles);
505595
pSaveGroup->Delete(C4CFN_Info);
506596
}
@@ -533,7 +623,12 @@ bool C4GameSave::Close()
533623
// close if owned group
534624
if (fOwnGroup)
535625
{
536-
fSuccess = !!pSaveGroup->Close();
626+
fSuccess = !!pSaveGroup->Save(false);
627+
if (!fSuccess)
628+
{
629+
LogNTr(spdlog::level::err, "Error closing save file group: {}", pSaveGroup->GetError());
630+
}
631+
pSaveGroup->Close();
537632
delete pSaveGroup;
538633
fOwnGroup = false;
539634
}

src/C4GameSave.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@ class C4GameSave
100100
bool SaveCreateGroup(const char *szFilename, C4Group &hUseGroup); // create/copy group at target filename
101101
bool SaveCore(); // save C4S core
102102
bool SaveScenarioSections(); // save scenario sections
103-
bool SaveLandscape(C4Section &section); // save current landscape
103+
bool SaveLandscape(C4Section &section, C4Group &group); // save current landscape
104104
bool SaveRuntimeData(); // save any runtime data
105-
bool SaveSection(C4Section &section);
105+
bool SaveSection(C4Section &section, const std::string &filename, bool saveRuntimeData);
106+
bool SaveSection(C4Section &section, C4Group &group, bool saveRuntimeData);
106107

107108
public:
108109
virtual ~C4GameSave() { Close(); } // dtor: close group

0 commit comments

Comments
 (0)