diff --git a/src/client/ThumbnailRendererTask.cpp b/src/client/ThumbnailRendererTask.cpp index 6b6f22a30..5267ed88d 100644 --- a/src/client/ThumbnailRendererTask.cpp +++ b/src/client/ThumbnailRendererTask.cpp @@ -29,7 +29,7 @@ ThumbnailRendererTask::~ThumbnailRendererTask() bool ThumbnailRendererTask::doWork() { - thumbnail = std::unique_ptr(SaveRenderer::Ref().Render(save.get(), decorations, fire)); + std::tie(thumbnail, std::ignore) = SaveRenderer::Ref().Render(save.get(), decorations, fire); if (thumbnail) { thumbnail->ResizeToFit(size, true); diff --git a/src/gui/game/GameView.cpp b/src/gui/game/GameView.cpp index abfc52bef..c98317321 100644 --- a/src/gui/game/GameView.cpp +++ b/src/gui/game/GameView.cpp @@ -1958,7 +1958,7 @@ void GameView::NotifyTransformedPlaceSaveChanged(GameModel *sender) { if (sender->GetTransformedPlaceSave()) { - placeSaveThumb = SaveRenderer::Ref().Render(sender->GetTransformedPlaceSave(), true, true, sender->GetRenderer()); + std::tie(placeSaveThumb, std::ignore) = SaveRenderer::Ref().Render(sender->GetTransformedPlaceSave(), true, true, sender->GetRenderer()); selectMode = PlaceSave; selectPoint2 = mousePosition; } diff --git a/src/gui/preview/PreviewView.cpp b/src/gui/preview/PreviewView.cpp index 5909571ff..401ce109a 100644 --- a/src/gui/preview/PreviewView.cpp +++ b/src/gui/preview/PreviewView.cpp @@ -93,6 +93,22 @@ PreviewView::PreviewView(std::unique_ptr newSavePreview): browserOpenButton->SetActionCallback({ [this] { c->OpenInBrowser(); } }); AddComponent(browserOpenButton); + loadErrorButton = new ui::Button({ 0, 0 }, ui::Point(148, 19), "Error loading save"); + loadErrorButton->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; + loadErrorButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; + loadErrorButton->SetIcon(IconDelete); + loadErrorButton->SetActionCallback({ [this] { ShowLoadError(); } }); + loadErrorButton->Visible = false; + AddComponent(loadErrorButton); + + missingElementsButton = new ui::Button({ 0, 0 }, ui::Point(148, 19), "Missing custom elements"); + missingElementsButton->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; + missingElementsButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; + missingElementsButton->SetIcon(IconReport); + missingElementsButton->SetActionCallback({ [this] { ShowMissingCustomElements(); } }); + missingElementsButton->Visible = false; + AddComponent(missingElementsButton); + if(showAvatars) saveNameLabel = new ui::Label(ui::Point(39, (YRES/2)+4), ui::Point(100, 16), ""); else @@ -387,9 +403,9 @@ void PreviewView::OnTick(float dt) c->Update(); if (doError) { - new ErrorMessage("Error loading save", doErrorMessage, { [this]() { - c->Exit(); - } }); + openButton->Enabled = false; + loadErrorButton->Visible = true; + UpdateLoadStatus(); } if (reportSaveRequest && reportSaveRequest->CheckDone()) @@ -464,6 +480,36 @@ void PreviewView::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ct openButton->DoAction(); } +void PreviewView::ShowLoadError() +{ + new ErrorMessage("Error loading save", doErrorMessage, {}); +} + +void PreviewView::ShowMissingCustomElements() +{ + StringBuilder sb; + sb << "This save uses custom elements that are not currently available. Make sure that you use the mod and/or have all the scripts the save requires to fully load. A list of identifiers of missing custom elements follows, which may help you determine how to fix this problem.\n"; + for (auto &identifier : missingElementTypes) + { + sb << "\n - " << identifier.FromUtf8(); + } + new InformationMessage("Missing custom elements", sb.Build(), true); +} + +void PreviewView::UpdateLoadStatus() +{ + auto y = YRES / 2 - 22; + auto showButton = [&y](ui::Button *button) { + if (button->Visible) + { + button->Position = { XRES / 2 - button->Size.X - 3, y }; + y -= button->Size.Y + 3; + } + }; + showButton(missingElementsButton); + showButton(loadErrorButton); +} + void PreviewView::NotifySaveChanged(PreviewModel * sender) { auto *save = sender->GetSaveInfo(); @@ -510,9 +556,11 @@ void PreviewView::NotifySaveChanged(PreviewModel * sender) if(save->GetGameSave()) { - savePreview = SaveRenderer::Ref().Render(save->GetGameSave(), false, true); + std::tie(savePreview, missingElementTypes) = SaveRenderer::Ref().Render(save->GetGameSave(), false, true); if (savePreview) savePreview->ResizeToFit(RES / 2, true); + missingElementsButton->Visible = missingElementTypes.size(); + UpdateLoadStatus(); } else if (!sender->GetCanOpen()) openButton->Enabled = false; diff --git a/src/gui/preview/PreviewView.h b/src/gui/preview/PreviewView.h index 02deafd4f..6fb40e2f1 100644 --- a/src/gui/preview/PreviewView.h +++ b/src/gui/preview/PreviewView.h @@ -26,25 +26,28 @@ class PreviewModel; class PreviewController; class PreviewView: public ui::Window { - PreviewController * c; + PreviewController *c{}; + std::vector missingElementTypes; std::unique_ptr savePreview; - ui::Button * openButton; - ui::Button * browserOpenButton; - ui::Button * favButton; - ui::Button * reportButton; - ui::Button * submitCommentButton; - ui::Textbox * addCommentBox; - ui::Label * commentWarningLabel; - ui::Label * saveNameLabel; - ui::Label * authorDateLabel; - ui::AvatarButton * avatarButton; - ui::Label * pageInfo; - ui::Label * saveDescriptionLabel; - ui::Label * viewsLabel; - ui::Label * saveIDLabel; - ui::Label * saveIDLabel2; - ui::CopyTextButton * saveIDButton; - ui::ScrollPanel * commentsPanel; + ui::Button *openButton{}; + ui::Button *browserOpenButton{}; + ui::Button *favButton{}; + ui::Button *reportButton{}; + ui::Button *submitCommentButton{}; + ui::Button *loadErrorButton{}; + ui::Button *missingElementsButton{}; + ui::Textbox *addCommentBox{}; + ui::Label *commentWarningLabel{}; + ui::Label *saveNameLabel{}; + ui::Label *authorDateLabel{}; + ui::AvatarButton *avatarButton{}; + ui::Label *pageInfo{}; + ui::Label *saveDescriptionLabel{}; + ui::Label *viewsLabel{}; + ui::Label *saveIDLabel{}; + ui::Label *saveIDLabel2{}; + ui::CopyTextButton *saveIDButton{}; + ui::ScrollPanel *commentsPanel{}; std::vector commentComponents; std::vector commentTextComponents; int votesUp; @@ -70,6 +73,9 @@ class PreviewView: public ui::Window void submitComment(); bool CheckSwearing(String text); void CheckComment(); + void ShowMissingCustomElements(); + void ShowLoadError(); + void UpdateLoadStatus(); std::unique_ptr addCommentRequest; std::unique_ptr reportSaveRequest; diff --git a/src/simulation/SaveRenderer.cpp b/src/simulation/SaveRenderer.cpp index 50b0dbd2a..5626b45df 100644 --- a/src/simulation/SaveRenderer.cpp +++ b/src/simulation/SaveRenderer.cpp @@ -20,7 +20,7 @@ void SaveRenderer::Flush(int begin, int end) std::fill(ren->graphicscache + begin, ren->graphicscache + end, gcache_item()); } -std::unique_ptr SaveRenderer::Render(const GameSave *save, bool decorations, bool fire, Renderer *renderModeSource) +std::pair, std::vector> SaveRenderer::Render(const GameSave *save, bool decorations, bool fire, Renderer *renderModeSource) { std::lock_guard gx(renderMutex); @@ -32,11 +32,9 @@ std::unique_ptr SaveRenderer::Render(const GameSave *save, bool dec ren->SetColourMode(renderModeSource->GetColourMode()); } - std::unique_ptr tempThumb; - sim->clear_sim(); - sim->Load(save, true, { 0, 0 }); + auto missingElementTypes = sim->Load(save, true, { 0, 0 }); ren->decorations_enable = true; ren->blackDecorations = !decorations; ren->ClearAccumulation(); @@ -57,10 +55,10 @@ std::unique_ptr SaveRenderer::Render(const GameSave *save, bool dec ren->RenderBegin(); ren->RenderEnd(); - tempThumb = std::make_unique(save->blockSize * CELL); + auto tempThumb = std::make_unique(save->blockSize * CELL); tempThumb->BlendImage(ren->Data(), 0xFF, ren->Size().OriginRect()); - return tempThumb; + return { std::move(tempThumb), missingElementTypes }; } SaveRenderer::~SaveRenderer() diff --git a/src/simulation/SaveRenderer.h b/src/simulation/SaveRenderer.h index 9dc3b6bcd..68df598a3 100644 --- a/src/simulation/SaveRenderer.h +++ b/src/simulation/SaveRenderer.h @@ -1,7 +1,10 @@ #pragma once #include #include +#include +#include #include "common/ExplicitSingleton.h" +#include "common/String.h" class GameSave; class VideoBuffer; @@ -15,7 +18,7 @@ class SaveRenderer: public ExplicitSingleton { std::mutex renderMutex; public: SaveRenderer(); - std::unique_ptr Render(const GameSave *save, bool decorations = true, bool fire = true, Renderer *renderModeSource = nullptr); + std::pair, std::vector> Render(const GameSave *save, bool decorations = true, bool fire = true, Renderer *renderModeSource = nullptr); void Flush(int begin, int end); virtual ~SaveRenderer(); }; diff --git a/src/simulation/Simulation.cpp b/src/simulation/Simulation.cpp index 1950bf77a..5b1d7d508 100644 --- a/src/simulation/Simulation.cpp +++ b/src/simulation/Simulation.cpp @@ -19,8 +19,9 @@ extern int Element_LOLZ_lolz[XRES/9][YRES/9]; extern int Element_LOVE_RuleTable[9][9]; extern int Element_LOVE_love[XRES/9][YRES/9]; -void Simulation::Load(const GameSave *save, bool includePressure, Vec2 blockP) // block coordinates +std::vector Simulation::Load(const GameSave *save, bool includePressure, Vec2 blockP) // block coordinates { + std::vector missingElementTypes; auto partP = blockP * CELL; unsigned int pmapmask = (1<pmapbits)-1; @@ -44,8 +45,15 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2 bloc // if this is a custom element, set the ID to the ID we found when comparing identifiers in the palette map // set type to 0 if we couldn't find an element with that identifier present when loading, // unless this is a default element, in which case keep the current ID, because otherwise when an element is renamed it wouldn't show up anymore in older saves - if (myId != 0 || !pi.first.BeginsWith("DEFAULT_PT_")) + if (myId != 0) + { partMap[pi.second] = myId; + } + else if (!pi.first.BeginsWith("DEFAULT_PT_")) + { + missingElementTypes.push_back(pi.first); + partMap[pi.second] = myId; + } } } } @@ -318,6 +326,8 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2 bloc { air->ApproximateBlockAirMaps(); } + + return missingElementTypes; } std::unique_ptr Simulation::Save(bool includePressure, Rect partR) // particle coordinates diff --git a/src/simulation/Simulation.h b/src/simulation/Simulation.h index 036b5186a..de6ceb6b9 100644 --- a/src/simulation/Simulation.h +++ b/src/simulation/Simulation.h @@ -121,7 +121,7 @@ public: uint64_t frameCount; bool ensureDeterminism; - void Load(const GameSave *save, bool includePressure, Vec2 blockP); // block coordinates + std::vector Load(const GameSave *save, bool includePressure, Vec2 blockP); // block coordinates std::unique_ptr Save(bool includePressure, Rect partR); // particle coordinates void SaveSimOptions(GameSave &gameSave); SimulationSample GetSample(int x, int y);