|
38 | 38 | #include <Unsorted.h> |
39 | 39 | #include <WWMouseClass.h> |
40 | 40 |
|
| 41 | +#include <cassert> |
| 42 | + |
41 | 43 | bool Spawner::Enabled = false; |
42 | 44 | bool Spawner::Active = false; |
43 | 45 | std::unique_ptr<SpawnerConfig> Spawner::Config = nullptr; |
@@ -570,69 +572,116 @@ void Spawner::RespondToSaveGame() |
570 | 572 | * |
571 | 573 | * Original author: Rampastring, ZivDero |
572 | 574 | * Migration: TaranDahl |
| 575 | + * Further changes: Kerbiter |
573 | 576 | */ |
574 | 577 | void Spawner::After_Main_Loop() |
575 | 578 | { |
576 | 579 | auto pConfig = Spawner::GetConfig(); |
577 | 580 |
|
578 | | - const bool doSaveCampaign = SessionClass::Instance.GameMode == GameMode::Campaign && pConfig->AutoSaveCount > 0 && pConfig->AutoSaveInterval > 0; |
579 | | - const bool doSaveMP = Spawner::Active && SessionClass::Instance.GameMode == GameMode::LAN && pConfig->AutoSaveInterval > 0; |
| 581 | + const bool doSaveSP = |
| 582 | + SessionClass::IsSingleplayer() |
| 583 | + && pConfig->AutoSaveCount > 0 |
| 584 | + && pConfig->AutoSaveInterval > 0; |
| 585 | + |
| 586 | + const bool doSaveMP = |
| 587 | + Spawner::Active |
| 588 | + && SessionClass::Instance.GameMode == GameMode::LAN |
| 589 | + && pConfig->AutoSaveInterval > 0; |
| 590 | + |
| 591 | + const bool isAutoSaving = (doSaveSP || doSaveMP) |
| 592 | + && Unsorted::CurrentFrame == Spawner::NextAutoSaveFrame; |
580 | 593 |
|
581 | 594 | // Schedule to make a save if it's time to autosave. |
582 | | - if (doSaveCampaign || doSaveMP) |
583 | | - { |
584 | | - if (Unsorted::CurrentFrame == Spawner::NextAutoSaveFrame) |
585 | | - { |
586 | | - Spawner::DoSave = true; |
587 | | - } |
588 | | - } |
| 595 | + // The save might be triggered manually, so we have to OR it. |
| 596 | + Spawner::DoSave |= isAutoSaving; |
589 | 597 |
|
590 | 598 | if (Spawner::DoSave) |
591 | 599 | { |
592 | | - // Send the message. |
593 | | - const auto TXT_AUTOSAVE_MESSAGE = StringTable::TryFetchString("TXT_AUTOSAVE_MESSAGE", L"Saving game..."); |
594 | | - MessageListClass::Instance.PrintMessage(TXT_AUTOSAVE_MESSAGE, (int)(RulesClass::Instance->MessageDelay * 900), ColorScheme::White, true); |
| 600 | + auto PrintMessage = [](const wchar_t* pMessage) |
| 601 | + { |
| 602 | + MessageListClass::Instance.PrintMessage( |
| 603 | + pMessage, |
| 604 | + RulesClass::Instance->MessageDelay, |
| 605 | + HouseClass::CurrentPlayer->ColorSchemeIndex, |
| 606 | + /* bSilent: */ true |
| 607 | + ); |
| 608 | + |
| 609 | + // Force a redraw so that our message gets printed. |
| 610 | + if (Game::SpecialDialog == 0) |
| 611 | + { |
| 612 | + MapClass::Instance.MarkNeedsRedraw(2); |
| 613 | + MapClass::Instance.Render(); |
| 614 | + } |
| 615 | + }; |
595 | 616 |
|
596 | | - // Force a redraw so that our message gets printed. |
597 | | - if (Game::SpecialDialog == 0) |
| 617 | + auto SaveGame = [PrintMessage](const char* fName, const wchar_t* description) |
598 | 618 | { |
599 | | - MapClass::Instance.MarkNeedsRedraw(2); |
600 | | - MapClass::Instance.Render(); |
601 | | - } |
| 619 | + if (ScenarioClass::SaveGame(fName, description)) |
| 620 | + PrintMessage(StringTable::LoadString(GameStrings::TXT_GAME_WAS_SAVED)); |
| 621 | + else |
| 622 | + PrintMessage(StringTable::LoadString(GameStrings::TXT_ERROR_SAVING_GAME)); |
| 623 | + }; |
| 624 | + |
| 625 | + // Send the message. |
| 626 | + PrintMessage(StringTable::LoadString(GameStrings::TXT_SAVING_GAME)); |
| 627 | + |
| 628 | + std::wstring saveGameDescription; |
| 629 | + if (SessionClass::IsCampaign()) |
| 630 | + saveGameDescription = ScenarioClass::Instance->UINameLoaded; |
| 631 | + else |
| 632 | + saveGameDescription = ScenarioClass::Instance->Name; |
| 633 | + saveGameDescription += L" - "; |
| 634 | + |
| 635 | + // This whole situation is a mess, but basically there's a myriad of ways to save |
| 636 | + // scattered across Phobos (quicksave hotkey and save trigger action) and spawner |
| 637 | + // (autosave), all in different conditions (multi- or singleplayer). |
| 638 | + |
| 639 | + // Previously everything only supported singleplayer, so Phobos didn't have to |
| 640 | + // account for multiplayer. Now we have to support both singleplayer and multiplayer, |
| 641 | + // but only spawner can do proper multiplayer saves on-demand, *and* also without |
| 642 | + // spawner there is no point in doing multiplayer saves at all. |
| 643 | + |
| 644 | + // What I came up with is: for synced situations (trigger action) we save on Phobos |
| 645 | + // side only if save event code (0x4C7A14) is patched (heuristic, any better ideas |
| 646 | + // are welcome), and for unsynced situations (quicksave) we also check for that patch |
| 647 | + // and emit the event that uses it. |
| 648 | + |
| 649 | + // If anyone wants to untangle that mess in a nice way - be my guest. |
| 650 | + // - Kerbiter |
602 | 651 |
|
603 | | - // Campaign autosave. |
604 | | - if (SessionClass::Instance.GameMode == GameMode::Campaign) |
| 652 | + // Singleplayer autosave. |
| 653 | + if (SessionClass::Instance.IsSingleplayer()) |
605 | 654 | { |
606 | | - static char saveFileName[32]; |
607 | | - static wchar_t saveDescription[32]; |
| 655 | + // ASSUMPTION: There will be no save events emitted in singleplayer |
| 656 | + // situations, and the only other way for the spawner to save is |
| 657 | + // through the autosave, which is what we are doing here. |
608 | 658 |
|
609 | | - // Prepare the save name and description. |
610 | | - const auto TXT_AUTOSAVE_DESCRIPTION_CAMPAIGN = StringTable::TryFetchString("TXT_AUTOSAVE_DESC_SP", L"Mission Auto-Save (Slot %d)"); |
611 | | - std::sprintf(saveFileName, "AUTOSAVE%d.SAV", Spawner::NextAutoSaveNumber + 1); |
612 | | - std::swprintf(saveDescription, TXT_AUTOSAVE_DESCRIPTION_CAMPAIGN, Spawner::NextAutoSaveNumber + 1); |
| 659 | + // If you want to fixup this - again, be my guest. |
| 660 | + // - Kerbiter |
| 661 | + |
| 662 | + assert(isAutoSaving); |
613 | 663 |
|
614 | | - // Pause the mission timer. |
615 | | - ScenarioClass::PauseGame(); |
616 | | - Game::CallBack(); |
| 664 | + static char saveFileName[32]; |
| 665 | + static wchar_t saveDescription[128]; |
617 | 666 |
|
618 | | - // Save! |
619 | | - ScenarioClass::Instance->SaveGame(saveFileName, saveDescription); |
| 667 | + saveGameDescription += StringTable::TryFetchString("TXT_AUTOSAVE_SUFFIX", L"Autosave (slot %d)"); |
| 668 | + std::sprintf(saveFileName, "AUTOSAVE%d.SAV", Spawner::NextAutoSaveNumber + 1); |
| 669 | + std::swprintf(saveDescription, saveGameDescription.c_str(), Spawner::NextAutoSaveNumber + 1); |
620 | 670 |
|
621 | | - // Unpause the mission timer. |
622 | | - ScenarioClass::ResumeGame(); |
| 671 | + SaveGame(saveFileName, saveDescription); |
623 | 672 |
|
624 | | - // Increment the autosave number. |
625 | 673 | Spawner::NextAutoSaveNumber = (Spawner::NextAutoSaveNumber + 1) % pConfig->AutoSaveCount; |
626 | | - |
627 | | - // Schedule the next autosave. |
628 | 674 | Spawner::NextAutoSaveFrame = Unsorted::CurrentFrame + pConfig->AutoSaveInterval; |
629 | 675 | } |
630 | 676 | else if (SessionClass::Instance.GameMode == GameMode::LAN) |
631 | 677 | { |
632 | | - // Save! |
633 | | - ScenarioClass::Instance->SaveGame("SAVEGAME.NET", StringTable::TryFetchString("TXT_AUTOSAVE_DESC_MP", L"Multiplayer Game")); |
| 678 | + // CnCNet client follows the legacy approach of fixed save name and copies it |
| 679 | + // over to it's own directory. The description isn't read now, but we write it |
| 680 | + // regardless as it shouldn't impact anything. The suffix for it is unavailable |
| 681 | + // though as it would require a custom event (seems overkill for such). |
| 682 | + saveGameDescription += StringTable::LoadString(GameStrings::TXT_MULTIPLAYER_GAME); |
| 683 | + SaveGame(GameStrings::SAVEGAME_NET, saveGameDescription.c_str()); |
634 | 684 |
|
635 | | - // Schedule the next autosave. |
636 | 685 | Spawner::NextAutoSaveFrame = Unsorted::CurrentFrame + pConfig->AutoSaveInterval; |
637 | 686 | } |
638 | 687 |
|
|
0 commit comments