mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 05:19:55 +00:00
29556a1802
A Warning indicates a potential problem in the content file(s) that the user told OpenMW to load. E.g. this might cause an object to not display at all or as intended, however the rest of the game will run fine. An Error, however, is more likely to be a bug with the engine itself - it means that basic assumptions have been violated and the engine might not run correctly anymore. The above mostly applies to errors/warnings during game-play; startup issues are handled differently: when a file is completely invalid/corrupted to the point that the engine can not start, that might cause messages that are worded as Error due to the severity of the issue but are not necessarily the engine's fault. Hopefully, being a little more consistent here will alleviate confusion among users as to when a log message should be reported and to whom.
433 lines
16 KiB
C++
433 lines
16 KiB
C++
#include "savegamedialog.hpp"
|
|
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
#include <MyGUI_ComboBox.h>
|
|
#include <MyGUI_ImageBox.h>
|
|
#include <MyGUI_ListBox.h>
|
|
#include <MyGUI_InputManager.h>
|
|
|
|
#include <osgDB/ReadFile>
|
|
#include <osg/Texture2D>
|
|
|
|
#include <components/myguiplatform/myguitexture.hpp>
|
|
|
|
#include <components/misc/stringops.hpp>
|
|
|
|
#include <components/settings/settings.hpp>
|
|
|
|
#include <components/files/memorystream.hpp>
|
|
|
|
#include "../mwbase/statemanager.hpp"
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
#include "../mwstate/character.hpp"
|
|
|
|
#include "confirmationdialog.hpp"
|
|
#include "widgets.hpp"
|
|
|
|
namespace MWGui
|
|
{
|
|
SaveGameDialog::SaveGameDialog()
|
|
: WindowModal("openmw_savegame_dialog.layout")
|
|
, mSaving(true)
|
|
, mCurrentCharacter(NULL)
|
|
, mCurrentSlot(NULL)
|
|
{
|
|
getWidget(mScreenshot, "Screenshot");
|
|
getWidget(mCharacterSelection, "SelectCharacter");
|
|
getWidget(mInfoText, "InfoText");
|
|
getWidget(mOkButton, "OkButton");
|
|
getWidget(mCancelButton, "CancelButton");
|
|
getWidget(mDeleteButton, "DeleteButton");
|
|
getWidget(mSaveList, "SaveList");
|
|
getWidget(mSaveNameEdit, "SaveNameEdit");
|
|
getWidget(mSpacer, "Spacer");
|
|
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked);
|
|
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked);
|
|
mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked);
|
|
mCharacterSelection->eventComboAccept += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected);
|
|
mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected);
|
|
mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick);
|
|
mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated);
|
|
mSaveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &SaveGameDialog::onKeyButtonPressed);
|
|
mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept);
|
|
mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged);
|
|
}
|
|
|
|
void SaveGameDialog::onSlotActivated(MyGUI::ListBox *sender, size_t pos)
|
|
{
|
|
onSlotSelected(sender, pos);
|
|
accept();
|
|
}
|
|
|
|
void SaveGameDialog::onSlotMouseClick(MyGUI::ListBox* sender, size_t pos)
|
|
{
|
|
onSlotSelected(sender, pos);
|
|
|
|
if (pos != MyGUI::ITEM_NONE && MyGUI::InputManager::getInstance().isShiftPressed())
|
|
confirmDeleteSave();
|
|
}
|
|
|
|
void SaveGameDialog::confirmDeleteSave()
|
|
{
|
|
ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
|
|
dialog->askForConfirmation("#{sMessage3}");
|
|
dialog->eventOkClicked.clear();
|
|
dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed);
|
|
dialog->eventCancelClicked.clear();
|
|
}
|
|
|
|
void SaveGameDialog::onDeleteSlotConfirmed()
|
|
{
|
|
MWBase::Environment::get().getStateManager()->deleteGame (mCurrentCharacter, mCurrentSlot);
|
|
mSaveList->removeItemAt(mSaveList->getIndexSelected());
|
|
onSlotSelected(mSaveList, mSaveList->getIndexSelected());
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList);
|
|
|
|
if (mSaveList->getItemCount() == 0)
|
|
{
|
|
size_t previousIndex = mCharacterSelection->getIndexSelected();
|
|
mCurrentCharacter = NULL;
|
|
mCharacterSelection->removeItemAt(previousIndex);
|
|
if (mCharacterSelection->getItemCount())
|
|
{
|
|
size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1);
|
|
mCharacterSelection->setIndexSelected(nextCharacter);
|
|
onCharacterSelected(mCharacterSelection, nextCharacter);
|
|
}
|
|
else
|
|
fillSaveList();
|
|
}
|
|
}
|
|
|
|
void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender)
|
|
{
|
|
// This might have previously been a save slot from the list. If so, that is no longer the case
|
|
mSaveList->setIndexSelected(MyGUI::ITEM_NONE);
|
|
onSlotSelected(mSaveList, MyGUI::ITEM_NONE);
|
|
}
|
|
|
|
void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox *sender)
|
|
{
|
|
accept();
|
|
}
|
|
|
|
void SaveGameDialog::open()
|
|
{
|
|
WindowModal::open();
|
|
|
|
mSaveNameEdit->setCaption ("");
|
|
if (mSaving)
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit);
|
|
|
|
center();
|
|
|
|
mCharacterSelection->setCaption("");
|
|
mCharacterSelection->removeAllItems();
|
|
mCurrentCharacter = NULL;
|
|
mCurrentSlot = NULL;
|
|
mSaveList->removeAllItems();
|
|
onSlotSelected(mSaveList, MyGUI::ITEM_NONE);
|
|
|
|
MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager();
|
|
if (mgr->characterBegin() == mgr->characterEnd())
|
|
return;
|
|
|
|
mCurrentCharacter = mgr->getCurrentCharacter();
|
|
|
|
std::string directory =
|
|
Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves"));
|
|
|
|
size_t selectedIndex = MyGUI::ITEM_NONE;
|
|
|
|
for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it)
|
|
{
|
|
if (it->begin()!=it->end())
|
|
{
|
|
std::stringstream title;
|
|
title << it->getSignature().mPlayerName;
|
|
|
|
// For a custom class, we will not find it in the store (unless we loaded the savegame first).
|
|
// Fall back to name stored in savegame header in that case.
|
|
std::string className;
|
|
if (it->getSignature().mPlayerClassId.empty())
|
|
className = it->getSignature().mPlayerClassName;
|
|
else
|
|
{
|
|
// Find the localised name for this class from the store
|
|
const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().search(
|
|
it->getSignature().mPlayerClassId);
|
|
if (class_)
|
|
className = class_->mName;
|
|
else
|
|
className = "?"; // From an older savegame format that did not support custom classes properly.
|
|
}
|
|
|
|
title << " (Level " << it->getSignature().mPlayerLevel << " " << className << ")";
|
|
|
|
mCharacterSelection->addItem (title.str());
|
|
|
|
if (mCurrentCharacter == &*it ||
|
|
(!mCurrentCharacter && !mSaving && directory==Misc::StringUtils::lowerCase (
|
|
it->begin()->mPath.parent_path().filename().string())))
|
|
{
|
|
mCurrentCharacter = &*it;
|
|
selectedIndex = mCharacterSelection->getItemCount()-1;
|
|
}
|
|
}
|
|
}
|
|
|
|
mCharacterSelection->setIndexSelected(selectedIndex);
|
|
if (selectedIndex == MyGUI::ITEM_NONE)
|
|
mCharacterSelection->setCaption("Select Character ...");
|
|
|
|
fillSaveList();
|
|
|
|
}
|
|
|
|
void SaveGameDialog::exit()
|
|
{
|
|
setVisible(false);
|
|
}
|
|
|
|
void SaveGameDialog::setLoadOrSave(bool load)
|
|
{
|
|
mSaving = !load;
|
|
mSaveNameEdit->setVisible(!load);
|
|
mCharacterSelection->setUserString("Hidden", load ? "false" : "true");
|
|
mCharacterSelection->setVisible(load);
|
|
mSpacer->setUserString("Hidden", load ? "false" : "true");
|
|
|
|
mDeleteButton->setUserString("Hidden", load ? "false" : "true");
|
|
mDeleteButton->setVisible(load);
|
|
|
|
if (!load)
|
|
{
|
|
mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter();
|
|
}
|
|
|
|
center();
|
|
}
|
|
|
|
void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender)
|
|
{
|
|
exit();
|
|
}
|
|
|
|
void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget *sender)
|
|
{
|
|
if (mCurrentSlot)
|
|
confirmDeleteSave();
|
|
}
|
|
|
|
void SaveGameDialog::onConfirmationGiven()
|
|
{
|
|
accept(true);
|
|
}
|
|
|
|
void SaveGameDialog::accept(bool reallySure)
|
|
{
|
|
// Remove for MyGUI 3.2.2
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL);
|
|
|
|
if (mSaving)
|
|
{
|
|
// If overwriting an existing slot, ask for confirmation first
|
|
if (mCurrentSlot != NULL && !reallySure)
|
|
{
|
|
ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
|
|
dialog->askForConfirmation("#{sMessage4}");
|
|
dialog->eventOkClicked.clear();
|
|
dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven);
|
|
dialog->eventCancelClicked.clear();
|
|
return;
|
|
}
|
|
if (mSaveNameEdit->getCaption().empty())
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
setVisible(false);
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu);
|
|
|
|
if (mSaving)
|
|
{
|
|
MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), mCurrentSlot);
|
|
}
|
|
else
|
|
{
|
|
assert (mCurrentCharacter && mCurrentSlot);
|
|
MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot->mPath.string());
|
|
}
|
|
}
|
|
|
|
void SaveGameDialog::onKeyButtonPressed(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character)
|
|
{
|
|
if (key == MyGUI::KeyCode::Delete && mCurrentSlot)
|
|
confirmDeleteSave();
|
|
}
|
|
|
|
void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender)
|
|
{
|
|
accept();
|
|
}
|
|
|
|
void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox *sender, size_t pos)
|
|
{
|
|
MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager();
|
|
|
|
unsigned int i=0;
|
|
const MWState::Character* character = NULL;
|
|
for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i)
|
|
{
|
|
if (i == pos)
|
|
character = &*it;
|
|
}
|
|
assert(character && "Can't find selected character");
|
|
|
|
mCurrentCharacter = character;
|
|
mCurrentSlot = NULL;
|
|
fillSaveList();
|
|
}
|
|
|
|
void SaveGameDialog::fillSaveList()
|
|
{
|
|
mSaveList->removeAllItems();
|
|
if (!mCurrentCharacter)
|
|
return;
|
|
for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it)
|
|
{
|
|
mSaveList->addItem(it->mProfile.mDescription);
|
|
}
|
|
// When loading, Auto-select the first save, if there is one
|
|
if (mSaveList->getItemCount() && !mSaving)
|
|
{
|
|
mSaveList->setIndexSelected(0);
|
|
onSlotSelected(mSaveList, 0);
|
|
// Give key focus to save list so we can confirm the selection with Enter
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList);
|
|
}
|
|
else
|
|
onSlotSelected(mSaveList, MyGUI::ITEM_NONE);
|
|
}
|
|
|
|
std::string formatTimeplayed(const double timeInSeconds)
|
|
{
|
|
int timePlayed = (int)floor(timeInSeconds);
|
|
int days = timePlayed / 60 / 60 / 24;
|
|
int hours = (timePlayed / 60 / 60) % 24;
|
|
int minutes = (timePlayed / 60) % 60;
|
|
int seconds = timePlayed % 60;
|
|
|
|
std::stringstream stream;
|
|
stream << std::setfill('0') << std::setw(2) << days << ":";
|
|
stream << std::setfill('0') << std::setw(2) << hours << ":";
|
|
stream << std::setfill('0') << std::setw(2) << minutes << ":";
|
|
stream << std::setfill('0') << std::setw(2) << seconds;
|
|
return stream.str();
|
|
}
|
|
|
|
void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos)
|
|
{
|
|
mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving);
|
|
mDeleteButton->setEnabled(pos != MyGUI::ITEM_NONE);
|
|
|
|
if (pos == MyGUI::ITEM_NONE || !mCurrentCharacter)
|
|
{
|
|
mCurrentSlot = NULL;
|
|
mInfoText->setCaption("");
|
|
mScreenshot->setImageTexture("");
|
|
return;
|
|
}
|
|
|
|
if (mSaving)
|
|
mSaveNameEdit->setCaption(sender->getItemNameAt(pos));
|
|
|
|
mCurrentSlot = NULL;
|
|
unsigned int i=0;
|
|
for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i)
|
|
{
|
|
if (i == pos)
|
|
mCurrentSlot = &*it;
|
|
}
|
|
if (!mCurrentSlot)
|
|
throw std::runtime_error("Can't find selected slot");
|
|
|
|
std::stringstream text;
|
|
time_t time = mCurrentSlot->mTimeStamp;
|
|
struct tm* timeinfo;
|
|
timeinfo = localtime(&time);
|
|
|
|
// Use system/environment locale settings for datetime formatting
|
|
char* oldLctime = setlocale(LC_TIME, NULL);
|
|
setlocale(LC_TIME, "");
|
|
|
|
const int size=1024;
|
|
char buffer[size];
|
|
if (std::strftime(buffer, size, "%x %X", timeinfo) > 0)
|
|
text << buffer << "\n";
|
|
|
|
// reset
|
|
setlocale(LC_TIME, oldLctime);
|
|
|
|
text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
|
|
text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n";
|
|
|
|
int hour = int(mCurrentSlot->mProfile.mInGameTime.mGameHour);
|
|
bool pm = hour >= 12;
|
|
if (hour >= 13) hour -= 12;
|
|
if (hour == 0) hour = 12;
|
|
|
|
text
|
|
<< mCurrentSlot->mProfile.mInGameTime.mDay << " "
|
|
<< MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth)
|
|
<< " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}");
|
|
|
|
if (Settings::Manager::getBool("timeplayed","Saves"))
|
|
{
|
|
text << "\n" << "Time played: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed);
|
|
}
|
|
|
|
mInfoText->setCaptionWithReplacing(text.str());
|
|
|
|
|
|
// Decode screenshot
|
|
const std::vector<char>& data = mCurrentSlot->mProfile.mScreenshot;
|
|
Files::IMemStream instream (&data[0], data.size());
|
|
|
|
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
|
|
if (!readerwriter)
|
|
{
|
|
std::cerr << "Error: Can't open savegame screenshot, no jpg readerwriter found" << std::endl;
|
|
return;
|
|
}
|
|
|
|
osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(instream);
|
|
if (!result.success())
|
|
{
|
|
std::cerr << "Error: Failed to read savegame screenshot: " << result.message() << " code " << result.status() << std::endl;
|
|
return;
|
|
}
|
|
|
|
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
|
|
texture->setImage(result.getImage());
|
|
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
|
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
|
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
|
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
|
texture->setResizeNonPowerOfTwoHint(false);
|
|
texture->setUnRefImageDataAfterApply(true);
|
|
|
|
mScreenshotTexture.reset(new osgMyGUI::OSGTexture(texture));
|
|
|
|
mScreenshot->setRenderItemTexture(mScreenshotTexture.get());
|
|
mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
|
|
}
|
|
}
|