mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-15 18:49:54 +00:00
abdaedd752
Previously, when using the Russian edition, all dialogue topics were translated into English before being sent to the server in an ObjectDialogueChoice packet and were then translated back into Russian when received again by the client. However, there were situations where different topics in English corresponded to the same Russian word, e.g. "chores" and "duties" were both translated as "задания", which led to the incorrect topic being used on the client in the end. This commit makes it so that users of the Russian edition send ObjectDialogueChoice packets where the topicId variable contains both the untranslated topic and the translated one, delimited by a | character, with the client simply using the former when receiving the packet again. This is a hotfix instead of the proper fix, as the proper fix would use different variables for the two versions of the topic and thus require the structure of the ObjectDialogueChoice packet to change.
908 lines
36 KiB
C++
908 lines
36 KiB
C++
#include "dialogue.hpp"
|
|
|
|
#include <MyGUI_LanguageManager.h>
|
|
#include <MyGUI_Window.h>
|
|
#include <MyGUI_ProgressBar.h>
|
|
#include <MyGUI_ScrollBar.h>
|
|
#include <MyGUI_Button.h>
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/widgets/list.hpp>
|
|
#include <components/translation/translation.hpp>
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Include additional headers for multiplayer purposes
|
|
*/
|
|
#include "../mwmp/Main.hpp"
|
|
#include "../mwmp/Networking.hpp"
|
|
#include "../mwmp/ObjectList.hpp"
|
|
#include <components/openmw-mp/TimedLog.hpp>
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/dialoguemanager.hpp"
|
|
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/containerstore.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
#include "../mwmechanics/creaturestats.hpp"
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
|
|
#include "bookpage.hpp"
|
|
#include "textcolours.hpp"
|
|
|
|
#include "journalbooks.hpp" // to_utf8_span
|
|
|
|
namespace MWGui
|
|
{
|
|
|
|
class ResponseCallback : public MWBase::DialogueManager::ResponseCallback
|
|
{
|
|
public:
|
|
ResponseCallback(DialogueWindow* win, bool needMargin=true)
|
|
: mWindow(win)
|
|
, mNeedMargin(needMargin)
|
|
{
|
|
|
|
}
|
|
|
|
void addResponse(const std::string& title, const std::string& text) override
|
|
{
|
|
mWindow->addResponse(title, text, mNeedMargin);
|
|
}
|
|
|
|
void updateTopics()
|
|
{
|
|
mWindow->updateTopics();
|
|
}
|
|
|
|
private:
|
|
DialogueWindow* mWindow;
|
|
bool mNeedMargin;
|
|
};
|
|
|
|
PersuasionDialog::PersuasionDialog(ResponseCallback* callback)
|
|
: WindowModal("openmw_persuasion_dialog.layout")
|
|
, mCallback(callback)
|
|
{
|
|
getWidget(mCancelButton, "CancelButton");
|
|
getWidget(mAdmireButton, "AdmireButton");
|
|
getWidget(mIntimidateButton, "IntimidateButton");
|
|
getWidget(mTauntButton, "TauntButton");
|
|
getWidget(mBribe10Button, "Bribe10Button");
|
|
getWidget(mBribe100Button, "Bribe100Button");
|
|
getWidget(mBribe1000Button, "Bribe1000Button");
|
|
getWidget(mGoldLabel, "GoldLabel");
|
|
|
|
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onCancel);
|
|
mAdmireButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
|
|
mIntimidateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
|
|
mTauntButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
|
|
mBribe10Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
|
|
mBribe100Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
|
|
mBribe1000Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
|
|
}
|
|
|
|
void PersuasionDialog::onCancel(MyGUI::Widget *sender)
|
|
{
|
|
setVisible(false);
|
|
}
|
|
|
|
void PersuasionDialog::onPersuade(MyGUI::Widget *sender)
|
|
{
|
|
MWBase::MechanicsManager::PersuasionType type;
|
|
if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire;
|
|
else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate;
|
|
else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt;
|
|
else if (sender == mBribe10Button)
|
|
type = MWBase::MechanicsManager::PT_Bribe10;
|
|
else if (sender == mBribe100Button)
|
|
type = MWBase::MechanicsManager::PT_Bribe100;
|
|
else /*if (sender == mBribe1000Button)*/
|
|
type = MWBase::MechanicsManager::PT_Bribe1000;
|
|
|
|
MWBase::Environment::get().getDialogueManager()->persuade(type, mCallback.get());
|
|
mCallback->updateTopics();
|
|
|
|
setVisible(false);
|
|
}
|
|
|
|
void PersuasionDialog::onOpen()
|
|
{
|
|
center();
|
|
|
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
|
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
|
|
|
|
mBribe10Button->setEnabled (playerGold >= 10);
|
|
mBribe100Button->setEnabled (playerGold >= 100);
|
|
mBribe1000Button->setEnabled (playerGold >= 1000);
|
|
|
|
mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold));
|
|
WindowModal::onOpen();
|
|
}
|
|
|
|
MyGUI::Widget* PersuasionDialog::getDefaultKeyFocus()
|
|
{
|
|
return mAdmireButton;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
Response::Response(const std::string &text, const std::string &title, bool needMargin)
|
|
: mTitle(title), mNeedMargin(needMargin)
|
|
{
|
|
mText = text;
|
|
}
|
|
|
|
void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const
|
|
{
|
|
typesetter->sectionBreak(mNeedMargin ? 9 : 0);
|
|
|
|
if (mTitle != "")
|
|
{
|
|
const MyGUI::Colour& headerColour = MWBase::Environment::get().getWindowManager()->getTextColours().header;
|
|
BookTypesetter::Style* title = typesetter->createStyle("", headerColour, false);
|
|
typesetter->write(title, to_utf8_span(mTitle.c_str()));
|
|
typesetter->sectionBreak();
|
|
}
|
|
|
|
typedef std::pair<size_t, size_t> Range;
|
|
std::map<Range, intptr_t> hyperLinks;
|
|
|
|
// We need this copy for when @# hyperlinks are replaced
|
|
std::string text = mText;
|
|
|
|
size_t pos_end = std::string::npos;
|
|
for(;;)
|
|
{
|
|
size_t pos_begin = text.find('@');
|
|
if (pos_begin != std::string::npos)
|
|
pos_end = text.find('#', pos_begin);
|
|
|
|
if (pos_begin != std::string::npos && pos_end != std::string::npos)
|
|
{
|
|
std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1);
|
|
const char specialPseudoAsteriskCharacter = 127;
|
|
std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*');
|
|
std::string topicName = MWBase::Environment::get().getWindowManager()->
|
|
getTranslationDataStorage().topicStandardForm(link);
|
|
|
|
std::string displayName = link;
|
|
while (displayName[displayName.size()-1] == '*')
|
|
displayName.erase(displayName.size()-1, 1);
|
|
|
|
text.replace(pos_begin, pos_end+1-pos_begin, displayName);
|
|
|
|
if (topicLinks.find(Misc::StringUtils::lowerCase(topicName)) != topicLinks.end())
|
|
hyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = intptr_t(topicLinks[Misc::StringUtils::lowerCase(topicName)]);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
typesetter->addContent(to_utf8_span(text.c_str()));
|
|
|
|
if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation())
|
|
{
|
|
const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
|
|
|
|
BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false);
|
|
size_t formatted = 0; // points to the first character that is not laid out yet
|
|
for (auto& hyperLink : hyperLinks)
|
|
{
|
|
intptr_t topicId = hyperLink.second;
|
|
BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, textColours.link,
|
|
textColours.linkOver, textColours.linkPressed,
|
|
topicId);
|
|
if (formatted < hyperLink.first.first)
|
|
typesetter->write(style, formatted, hyperLink.first.first);
|
|
typesetter->write(hotStyle, hyperLink.first.first, hyperLink.first.second);
|
|
formatted = hyperLink.first.second;
|
|
}
|
|
if (formatted < text.size())
|
|
typesetter->write(style, formatted, text.size());
|
|
}
|
|
else
|
|
{
|
|
std::vector<KeywordSearchT::Match> matches;
|
|
keywordSearch->highlightKeywords(text.begin(), text.end(), matches);
|
|
|
|
std::string::const_iterator i = text.begin ();
|
|
for (KeywordSearchT::Match& match : matches)
|
|
{
|
|
if (i != match.mBeg)
|
|
addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ());
|
|
|
|
addTopicLink (typesetter, match.mValue, match.mBeg - text.begin (), match.mEnd - text.begin ());
|
|
|
|
i = match.mEnd;
|
|
}
|
|
if (i != text.end ())
|
|
addTopicLink (typesetter, 0, i - text.begin (), text.size ());
|
|
}
|
|
}
|
|
|
|
void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const
|
|
{
|
|
const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
|
|
|
|
BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false);
|
|
|
|
|
|
if (topicId)
|
|
style = typesetter->createHotStyle (style, textColours.link, textColours.linkOver, textColours.linkPressed, topicId);
|
|
typesetter->write (style, begin, end);
|
|
}
|
|
|
|
Message::Message(const std::string& text)
|
|
{
|
|
mText = text;
|
|
}
|
|
|
|
void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const
|
|
{
|
|
const MyGUI::Colour& textColour = MWBase::Environment::get().getWindowManager()->getTextColours().notify;
|
|
BookTypesetter::Style* title = typesetter->createStyle("", textColour, false);
|
|
typesetter->sectionBreak(9);
|
|
typesetter->write(title, to_utf8_span(mText.c_str()));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
void Choice::activated()
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->playSound("Menu Click");
|
|
eventChoiceActivated(mChoiceId);
|
|
}
|
|
|
|
void Topic::activated()
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->playSound("Menu Click");
|
|
eventTopicActivated(mTopicId);
|
|
}
|
|
|
|
void Goodbye::activated()
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->playSound("Menu Click");
|
|
eventActivated();
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
DialogueWindow::DialogueWindow()
|
|
: WindowBase("openmw_dialogue_window.layout")
|
|
, mIsCompanion(false)
|
|
, mGoodbye(false)
|
|
, mPersuasionDialog(new ResponseCallback(this))
|
|
, mCallback(new ResponseCallback(this))
|
|
, mGreetingCallback(new ResponseCallback(this, false))
|
|
{
|
|
// Centre dialog
|
|
center();
|
|
|
|
mPersuasionDialog.setVisible(false);
|
|
|
|
//History view
|
|
getWidget(mHistory, "History");
|
|
|
|
//Topics list
|
|
getWidget(mTopicsList, "TopicsList");
|
|
mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectListItem);
|
|
|
|
getWidget(mGoodbyeButton, "ByeButton");
|
|
mGoodbyeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked);
|
|
|
|
getWidget(mDispositionBar, "Disposition");
|
|
getWidget(mDispositionText,"DispositionText");
|
|
getWidget(mScrollBar, "VScroll");
|
|
|
|
mScrollBar->eventScrollChangePosition += MyGUI::newDelegate(this, &DialogueWindow::onScrollbarMoved);
|
|
mHistory->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel);
|
|
|
|
BookPage::ClickCallback callback = std::bind (&DialogueWindow::notifyLinkClicked, this, std::placeholders::_1);
|
|
mHistory->adviseLinkClicked(callback);
|
|
|
|
mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize);
|
|
}
|
|
|
|
DialogueWindow::~DialogueWindow()
|
|
{
|
|
deleteLater();
|
|
for (Link* link : mLinks)
|
|
delete link;
|
|
for (const auto& link : mTopicLinks)
|
|
delete link.second;
|
|
for (auto history : mHistoryContents)
|
|
delete history;
|
|
}
|
|
|
|
void DialogueWindow::onTradeComplete()
|
|
{
|
|
addResponse("", MyGUI::LanguageManager::getInstance().replaceTags("#{sBarterDialog5}"));
|
|
}
|
|
|
|
bool DialogueWindow::exit()
|
|
{
|
|
if ((MWBase::Environment::get().getDialogueManager()->isInChoice()))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
resetReference();
|
|
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
|
|
mTopicsList->scrollToTop();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void DialogueWindow::onWindowResize(MyGUI::Window* _sender)
|
|
{
|
|
// if the window has only been moved, not resized, we don't need to update
|
|
if (mCurrentWindowSize == _sender->getSize()) return;
|
|
|
|
mTopicsList->adjustSize();
|
|
updateHistory();
|
|
updateTopicFormat();
|
|
mCurrentWindowSize = _sender->getSize();
|
|
}
|
|
|
|
void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
|
|
{
|
|
if (!mScrollBar->getVisible())
|
|
return;
|
|
mScrollBar->setScrollPosition(std::min(static_cast<int>(mScrollBar->getScrollRange()-1),
|
|
std::max(0, static_cast<int>(mScrollBar->getScrollPosition() - _rel*0.3))));
|
|
onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition());
|
|
}
|
|
|
|
void DialogueWindow::onByeClicked(MyGUI::Widget* _sender)
|
|
{
|
|
if (exit())
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
|
|
}
|
|
}
|
|
|
|
void DialogueWindow::onSelectListItem(const std::string& topic, int id)
|
|
{
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Instead of activating a list item here, send an ObjectDialogueChoice packet to the server
|
|
and let it decide whether the list item gets activated
|
|
*/
|
|
sendDialogueChoicePacket(topic);
|
|
return;
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
MWBase::DialogueManager* dialogueManager = MWBase::Environment::get().getDialogueManager();
|
|
|
|
if (mGoodbye || dialogueManager->isInChoice())
|
|
return;
|
|
|
|
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
const std::string sPersuasion = gmst.find("sPersuasion")->mValue.getString();
|
|
const std::string sCompanionShare = gmst.find("sCompanionShare")->mValue.getString();
|
|
const std::string sBarter = gmst.find("sBarter")->mValue.getString();
|
|
const std::string sSpells = gmst.find("sSpells")->mValue.getString();
|
|
const std::string sTravel = gmst.find("sTravel")->mValue.getString();
|
|
const std::string sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->mValue.getString();
|
|
const std::string sEnchanting = gmst.find("sEnchanting")->mValue.getString();
|
|
const std::string sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->mValue.getString();
|
|
const std::string sRepair = gmst.find("sRepair")->mValue.getString();
|
|
|
|
if (topic != sPersuasion && topic != sCompanionShare && topic != sBarter
|
|
&& topic != sSpells && topic != sTravel && topic != sSpellMakingMenuTitle
|
|
&& topic != sEnchanting && topic != sServiceTrainingTitle && topic != sRepair)
|
|
{
|
|
onTopicActivated(topic);
|
|
if (mGoodbyeButton->getEnabled())
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton);
|
|
}
|
|
else if (topic == sPersuasion)
|
|
mPersuasionDialog.setVisible(true);
|
|
else if (topic == sCompanionShare)
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr);
|
|
else if (!dialogueManager->checkServiceRefused(mCallback.get()))
|
|
{
|
|
if (topic == sBarter && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Barter))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr);
|
|
else if (topic == sSpells && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spells))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr);
|
|
else if (topic == sTravel && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Travel))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr);
|
|
else if (topic == sSpellMakingMenuTitle && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spellmaking))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr);
|
|
else if (topic == sEnchanting && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Enchanting))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr);
|
|
else if (topic == sServiceTrainingTitle && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Training))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr);
|
|
else if (topic == sRepair && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Repair))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr);
|
|
}
|
|
else
|
|
updateTopics();
|
|
}
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
A different event that should be used in multiplayer when clicking on choices
|
|
in the dialogue screen, sending DialogueChoice packets to the server so they can
|
|
be approved or denied
|
|
*/
|
|
void DialogueWindow::sendDialogueChoicePacket(const std::string& topic)
|
|
{
|
|
mwmp::ObjectList* objectList = mwmp::Main::get().getNetworking()->getObjectList();
|
|
objectList->reset();
|
|
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
|
|
objectList->addObjectDialogueChoice(mPtr, topic);
|
|
objectList->sendObjectDialogueChoice();
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Make it possible to activate any dialogue choice from elsewhere in the code
|
|
*/
|
|
void DialogueWindow::activateDialogueChoice(unsigned char dialogueChoiceType, std::string topic)
|
|
{
|
|
if (dialogueChoiceType == mwmp::DialogueChoiceType::TOPIC)
|
|
{
|
|
onTopicActivated(topic);
|
|
}
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::PERSUASION)
|
|
mPersuasionDialog.setVisible(true);
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::COMPANION_SHARE)
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr);
|
|
else
|
|
{
|
|
MWBase::DialogueManager* dialogueManager = MWBase::Environment::get().getDialogueManager();
|
|
|
|
if (dialogueChoiceType == mwmp::DialogueChoiceType::BARTER && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Barter))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr);
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::SPELLS && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spells))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr);
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::TRAVEL && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Travel))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr);
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::SPELLMAKING && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spellmaking))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr);
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::ENCHANTING && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Enchanting))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr);
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::TRAINING && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Training))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr);
|
|
else if (dialogueChoiceType == mwmp::DialogueChoiceType::REPAIR && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Repair))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr);
|
|
}
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Make it possible to get the Ptr of the actor involved in the dialogue
|
|
*/
|
|
MWWorld::Ptr DialogueWindow::getPtr()
|
|
{
|
|
return mPtr;
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
void DialogueWindow::setPtr(const MWWorld::Ptr& actor)
|
|
{
|
|
if (!actor.getClass().isActor())
|
|
{
|
|
Log(Debug::Warning) << "Warning: can not talk with non-actor object.";
|
|
return;
|
|
}
|
|
|
|
bool sameActor = (mPtr == actor);
|
|
if (!sameActor)
|
|
{
|
|
// The history is not reset here
|
|
mKeywords.clear();
|
|
mTopicsList->clear();
|
|
for (Link* link : mLinks)
|
|
mDeleteLater.push_back(link); // Links are not deleted right away to prevent issues with event handlers
|
|
mLinks.clear();
|
|
}
|
|
|
|
mPtr = actor;
|
|
mGoodbye = false;
|
|
mTopicsList->setEnabled(true);
|
|
|
|
if (!MWBase::Environment::get().getDialogueManager()->startDialogue(actor, mGreetingCallback.get()))
|
|
{
|
|
// No greetings found. The dialogue window should not be shown.
|
|
// If this is a companion, we must show the companion window directly (used by BM_bear_be_unique).
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
|
|
mPtr = MWWorld::Ptr();
|
|
if (isCompanion(actor))
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Companion, actor);
|
|
return;
|
|
}
|
|
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton);
|
|
|
|
setTitle(mPtr.getClass().getName(mPtr));
|
|
|
|
updateTopics();
|
|
updateTopicsPane(); // force update for new services
|
|
|
|
updateDisposition();
|
|
restock();
|
|
}
|
|
|
|
void DialogueWindow::restock()
|
|
{
|
|
MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
|
|
float delay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fBarterGoldResetDelay")->mValue.getFloat();
|
|
|
|
// Gold is restocked every 24h
|
|
if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay)
|
|
{
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Instead of restocking the NPC's gold pool or last restock time here, send a packet about them to the server
|
|
*/
|
|
/*
|
|
sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr));
|
|
|
|
sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp());
|
|
*/
|
|
mwmp::ObjectList* objectList = mwmp::Main::get().getNetworking()->getObjectList();
|
|
objectList->reset();
|
|
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
|
|
objectList->addObjectMiscellaneous(mPtr, mPtr.getClass().getBaseGold(mPtr), MWBase::Environment::get().getWorld()->getTimeStamp().getHour(),
|
|
MWBase::Environment::get().getWorld()->getTimeStamp().getDay());
|
|
objectList->sendObjectMiscellaneous();
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
}
|
|
}
|
|
|
|
void DialogueWindow::deleteLater()
|
|
{
|
|
for (Link* link : mDeleteLater)
|
|
delete link;
|
|
mDeleteLater.clear();
|
|
}
|
|
|
|
void DialogueWindow::onClose()
|
|
{
|
|
if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Dialogue))
|
|
return;
|
|
// Reset history
|
|
for (DialogueText* text : mHistoryContents)
|
|
delete text;
|
|
mHistoryContents.clear();
|
|
}
|
|
|
|
bool DialogueWindow::setKeywords(std::list<std::string> keyWords)
|
|
{
|
|
if (mKeywords == keyWords && isCompanion() == mIsCompanion)
|
|
return false;
|
|
mIsCompanion = isCompanion();
|
|
mKeywords = keyWords;
|
|
updateTopicsPane();
|
|
return true;
|
|
}
|
|
|
|
void DialogueWindow::updateTopicsPane()
|
|
{
|
|
mTopicsList->clear();
|
|
for (auto& linkPair : mTopicLinks)
|
|
mDeleteLater.push_back(linkPair.second);
|
|
mTopicLinks.clear();
|
|
mKeywordSearch.clear();
|
|
|
|
int services = mPtr.getClass().getServices(mPtr);
|
|
|
|
bool travel = (mPtr.getTypeName() == typeid(ESM::NPC).name() && !mPtr.get<ESM::NPC>()->mBase->getTransport().empty())
|
|
|| (mPtr.getTypeName() == typeid(ESM::Creature).name() && !mPtr.get<ESM::Creature>()->mBase->getTransport().empty());
|
|
|
|
const MWWorld::Store<ESM::GameSetting> &gmst =
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
if (mPtr.getTypeName() == typeid(ESM::NPC).name())
|
|
mTopicsList->addItem(gmst.find("sPersuasion")->mValue.getString());
|
|
|
|
if (services & ESM::NPC::AllItems)
|
|
mTopicsList->addItem(gmst.find("sBarter")->mValue.getString());
|
|
|
|
if (services & ESM::NPC::Spells)
|
|
mTopicsList->addItem(gmst.find("sSpells")->mValue.getString());
|
|
|
|
if (travel)
|
|
mTopicsList->addItem(gmst.find("sTravel")->mValue.getString());
|
|
|
|
if (services & ESM::NPC::Spellmaking)
|
|
mTopicsList->addItem(gmst.find("sSpellmakingMenuTitle")->mValue.getString());
|
|
|
|
if (services & ESM::NPC::Enchanting)
|
|
mTopicsList->addItem(gmst.find("sEnchanting")->mValue.getString());
|
|
|
|
if (services & ESM::NPC::Training)
|
|
mTopicsList->addItem(gmst.find("sServiceTrainingTitle")->mValue.getString());
|
|
|
|
if (services & ESM::NPC::Repair)
|
|
mTopicsList->addItem(gmst.find("sRepair")->mValue.getString());
|
|
|
|
if (isCompanion())
|
|
mTopicsList->addItem(gmst.find("sCompanionShare")->mValue.getString());
|
|
|
|
if (mTopicsList->getItemCount() > 0)
|
|
mTopicsList->addSeparator();
|
|
|
|
|
|
for(const auto& keyword : mKeywords)
|
|
{
|
|
std::string topicId = Misc::StringUtils::lowerCase(keyword);
|
|
mTopicsList->addItem(keyword);
|
|
|
|
Topic* t = new Topic(keyword);
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Instead of running DialogueWindow::onSelectListItem() when clicking a highlighted topic, run
|
|
onSendDialoguePacket() so the server can approve or deny a dialogue choice
|
|
*/
|
|
//t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated);
|
|
t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::sendDialogueChoicePacket);
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
mTopicLinks[topicId] = t;
|
|
|
|
mKeywordSearch.seed(topicId, intptr_t(t));
|
|
}
|
|
mTopicsList->adjustSize();
|
|
|
|
updateHistory();
|
|
// The topics list has been regenerated so topic formatting needs to be updated
|
|
updateTopicFormat();
|
|
}
|
|
|
|
void DialogueWindow::updateHistory(bool scrollbar)
|
|
{
|
|
if (!scrollbar && mScrollBar->getVisible())
|
|
{
|
|
mHistory->setSize(mHistory->getSize()+MyGUI::IntSize(mScrollBar->getWidth(),0));
|
|
mScrollBar->setVisible(false);
|
|
}
|
|
if (scrollbar && !mScrollBar->getVisible())
|
|
{
|
|
mHistory->setSize(mHistory->getSize()-MyGUI::IntSize(mScrollBar->getWidth(),0));
|
|
mScrollBar->setVisible(true);
|
|
}
|
|
|
|
BookTypesetter::Ptr typesetter = BookTypesetter::create (mHistory->getWidth(), std::numeric_limits<int>::max());
|
|
|
|
for (DialogueText* text : mHistoryContents)
|
|
text->write(typesetter, &mKeywordSearch, mTopicLinks);
|
|
|
|
BookTypesetter::Style* body = typesetter->createStyle("", MyGUI::Colour::White, false);
|
|
|
|
typesetter->sectionBreak(9);
|
|
// choices
|
|
const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
|
|
mChoices = MWBase::Environment::get().getDialogueManager()->getChoices();
|
|
for (std::pair<std::string, int>& choice : mChoices)
|
|
{
|
|
Choice* link = new Choice(choice.second);
|
|
link->eventChoiceActivated += MyGUI::newDelegate(this, &DialogueWindow::onChoiceActivated);
|
|
mLinks.push_back(link);
|
|
|
|
typesetter->lineBreak();
|
|
BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver,
|
|
textColours.answerPressed,
|
|
TypesetBook::InteractiveId(link));
|
|
typesetter->write(questionStyle, to_utf8_span(choice.first.c_str()));
|
|
}
|
|
|
|
mGoodbye = MWBase::Environment::get().getDialogueManager()->isGoodbye();
|
|
if (mGoodbye)
|
|
{
|
|
Goodbye* link = new Goodbye();
|
|
link->eventActivated += MyGUI::newDelegate(this, &DialogueWindow::onGoodbyeActivated);
|
|
mLinks.push_back(link);
|
|
std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sGoodbye")->mValue.getString();
|
|
BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver,
|
|
textColours.answerPressed,
|
|
TypesetBook::InteractiveId(link));
|
|
typesetter->lineBreak();
|
|
typesetter->write(questionStyle, to_utf8_span(goodbye.c_str()));
|
|
}
|
|
|
|
TypesetBook::Ptr book = typesetter->complete();
|
|
mHistory->showPage(book, 0);
|
|
size_t viewHeight = mHistory->getParent()->getHeight();
|
|
if (!scrollbar && book->getSize().second > viewHeight)
|
|
updateHistory(true);
|
|
else if (scrollbar)
|
|
{
|
|
mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second));
|
|
size_t range = book->getSize().second - viewHeight;
|
|
mScrollBar->setScrollRange(range);
|
|
mScrollBar->setScrollPosition(range-1);
|
|
mScrollBar->setTrackSize(static_cast<int>(viewHeight / static_cast<float>(book->getSize().second) * mScrollBar->getLineSize()));
|
|
onScrollbarMoved(mScrollBar, range-1);
|
|
}
|
|
else
|
|
{
|
|
// no scrollbar
|
|
onScrollbarMoved(mScrollBar, 0);
|
|
}
|
|
|
|
bool goodbyeEnabled = !MWBase::Environment::get().getDialogueManager()->isInChoice() || mGoodbye;
|
|
bool goodbyeWasEnabled = mGoodbyeButton->getEnabled();
|
|
mGoodbyeButton->setEnabled(goodbyeEnabled);
|
|
if (goodbyeEnabled && !goodbyeWasEnabled)
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton);
|
|
|
|
bool topicsEnabled = !MWBase::Environment::get().getDialogueManager()->isInChoice() && !mGoodbye;
|
|
mTopicsList->setEnabled(topicsEnabled);
|
|
}
|
|
|
|
void DialogueWindow::notifyLinkClicked (TypesetBook::InteractiveId link)
|
|
{
|
|
reinterpret_cast<Link*>(link)->activated();
|
|
}
|
|
|
|
void DialogueWindow::onTopicActivated(const std::string &topicId)
|
|
{
|
|
if (mGoodbye)
|
|
return;
|
|
|
|
MWBase::Environment::get().getDialogueManager()->keywordSelected(topicId, mCallback.get());
|
|
updateTopics();
|
|
}
|
|
|
|
void DialogueWindow::onChoiceActivated(int id)
|
|
{
|
|
if (mGoodbye)
|
|
{
|
|
onGoodbyeActivated();
|
|
return;
|
|
}
|
|
MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get());
|
|
updateTopics();
|
|
}
|
|
|
|
void DialogueWindow::onGoodbyeActivated()
|
|
{
|
|
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
|
|
resetReference();
|
|
}
|
|
|
|
void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos)
|
|
{
|
|
mHistory->setPosition(0, static_cast<int>(pos) * -1);
|
|
}
|
|
|
|
void DialogueWindow::addResponse(const std::string &title, const std::string &text, bool needMargin)
|
|
{
|
|
mHistoryContents.push_back(new Response(text, title, needMargin));
|
|
updateHistory();
|
|
}
|
|
|
|
void DialogueWindow::addMessageBox(const std::string& text)
|
|
{
|
|
mHistoryContents.push_back(new Message(text));
|
|
updateHistory();
|
|
}
|
|
|
|
void DialogueWindow::updateDisposition()
|
|
{
|
|
bool dispositionVisible = false;
|
|
if (!mPtr.isEmpty() && mPtr.getClass().isNpc())
|
|
{
|
|
dispositionVisible = true;
|
|
mDispositionBar->setProgressRange(100);
|
|
mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr));
|
|
mDispositionText->setCaption(MyGUI::utility::toString(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100"));
|
|
}
|
|
|
|
bool dispositionWasVisible = mDispositionBar->getVisible();
|
|
|
|
if (dispositionVisible && !dispositionWasVisible)
|
|
{
|
|
mDispositionBar->setVisible(true);
|
|
int offset = mDispositionBar->getHeight()+5;
|
|
mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0,offset,0,-offset));
|
|
mTopicsList->adjustSize();
|
|
}
|
|
else if (!dispositionVisible && dispositionWasVisible)
|
|
{
|
|
mDispositionBar->setVisible(false);
|
|
int offset = mDispositionBar->getHeight()+5;
|
|
mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0,offset,0,-offset));
|
|
mTopicsList->adjustSize();
|
|
}
|
|
}
|
|
|
|
void DialogueWindow::onReferenceUnavailable()
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
|
|
}
|
|
|
|
void DialogueWindow::onFrame(float dt)
|
|
{
|
|
checkReferenceAvailable();
|
|
if (mPtr.isEmpty())
|
|
return;
|
|
|
|
updateDisposition();
|
|
deleteLater();
|
|
|
|
if (mChoices != MWBase::Environment::get().getDialogueManager()->getChoices()
|
|
|| mGoodbye != MWBase::Environment::get().getDialogueManager()->isGoodbye())
|
|
updateHistory();
|
|
}
|
|
|
|
void DialogueWindow::updateTopicFormat()
|
|
{
|
|
if (!Settings::Manager::getBool("color topic enable", "GUI"))
|
|
return;
|
|
|
|
std::string specialColour = Settings::Manager::getString("color topic specific", "GUI");
|
|
std::string oldColour = Settings::Manager::getString("color topic exhausted", "GUI");
|
|
|
|
for (const std::string& keyword : mKeywords)
|
|
{
|
|
int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(keyword);
|
|
MyGUI::Button* button = mTopicsList->getItemWidget(keyword);
|
|
|
|
if (!specialColour.empty() && flag & MWBase::DialogueManager::TopicType::Specific)
|
|
button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(specialColour));
|
|
else if (!oldColour.empty() && flag & MWBase::DialogueManager::TopicType::Exhausted)
|
|
button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(oldColour));
|
|
}
|
|
}
|
|
|
|
void DialogueWindow::updateTopics()
|
|
{
|
|
// Topic formatting needs to be updated regardless of whether the topic list has changed
|
|
if (!setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()))
|
|
updateTopicFormat();
|
|
}
|
|
|
|
bool DialogueWindow::isCompanion()
|
|
{
|
|
return isCompanion(mPtr);
|
|
}
|
|
|
|
bool DialogueWindow::isCompanion(const MWWorld::Ptr& actor)
|
|
{
|
|
if (actor.isEmpty())
|
|
return false;
|
|
|
|
return !actor.getClass().getScript(actor).empty()
|
|
&& actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion");
|
|
}
|
|
|
|
}
|