mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 01:49:54 +00:00
503 lines
16 KiB
C++
503 lines
16 KiB
C++
#include "dialogue.hpp"
|
|
|
|
#include <iostream>
|
|
#include <iterator>
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/dialoguemanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
|
|
#include "../mwmechanics/npcstats.hpp"
|
|
|
|
#include "../mwdialogue/dialoguemanagerimp.hpp"
|
|
|
|
#include "dialogue_history.hpp"
|
|
#include "widgets.hpp"
|
|
#include "list.hpp"
|
|
#include "tradewindow.hpp"
|
|
#include "spellbuyingwindow.hpp"
|
|
#include "inventorywindow.hpp"
|
|
#include "travelwindow.hpp"
|
|
|
|
using namespace MWGui;
|
|
using namespace Widgets;
|
|
|
|
/**
|
|
*Copied from the internet.
|
|
*/
|
|
|
|
namespace {
|
|
|
|
std::string lower_string(const std::string& str)
|
|
{
|
|
std::string lowerCase = Misc::StringUtils::lowerCase (str);
|
|
|
|
return lowerCase;
|
|
}
|
|
|
|
std::string::size_type find_str_ci(const std::string& str, const std::string& substr,size_t pos)
|
|
{
|
|
return lower_string(str).find(lower_string(substr),pos);
|
|
}
|
|
|
|
bool sortByLength (const std::string& left, const std::string& right)
|
|
{
|
|
return left.size() > right.size();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
PersuasionDialog::PersuasionDialog(MWBase::WindowManager &parWindowManager)
|
|
: WindowModal("openmw_persuasion_dialog.layout", parWindowManager)
|
|
{
|
|
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)
|
|
{
|
|
mWindowManager.getTradeWindow()->addOrRemoveGold(-10);
|
|
type = MWBase::MechanicsManager::PT_Bribe10;
|
|
}
|
|
else if (sender == mBribe100Button)
|
|
{
|
|
mWindowManager.getTradeWindow()->addOrRemoveGold(-100);
|
|
type = MWBase::MechanicsManager::PT_Bribe100;
|
|
}
|
|
else /*if (sender == mBribe1000Button)*/
|
|
{
|
|
mWindowManager.getTradeWindow()->addOrRemoveGold(-1000);
|
|
type = MWBase::MechanicsManager::PT_Bribe1000;
|
|
}
|
|
|
|
MWBase::Environment::get().getDialogueManager()->persuade(type);
|
|
|
|
setVisible(false);
|
|
}
|
|
|
|
void PersuasionDialog::open()
|
|
{
|
|
WindowModal::open();
|
|
center();
|
|
|
|
int playerGold = mWindowManager.getInventoryWindow()->getPlayerGold();
|
|
|
|
mBribe10Button->setEnabled (playerGold >= 10);
|
|
mBribe100Button->setEnabled (playerGold >= 100);
|
|
mBribe1000Button->setEnabled (playerGold >= 1000);
|
|
|
|
mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(playerGold));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
DialogueWindow::DialogueWindow(MWBase::WindowManager& parWindowManager)
|
|
: WindowBase("openmw_dialogue_window.layout", parWindowManager)
|
|
, mPersuasionDialog(parWindowManager)
|
|
, mEnabled(false)
|
|
, mServices(0)
|
|
{
|
|
// Centre dialog
|
|
center();
|
|
|
|
mPersuasionDialog.setVisible(false);
|
|
|
|
//History view
|
|
getWidget(mHistory, "History");
|
|
mHistory->setOverflowToTheLeft(true);
|
|
mHistory->setMaxTextLength(1000000);
|
|
MyGUI::Widget* eventbox;
|
|
|
|
//An EditBox cannot receive mouse click events, so we use an
|
|
//invisible widget on top of the editbox to receive them
|
|
getWidget(eventbox, "EventBox");
|
|
eventbox->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onHistoryClicked);
|
|
eventbox->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel);
|
|
|
|
//Topics list
|
|
getWidget(mTopicsList, "TopicsList");
|
|
mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic);
|
|
|
|
MyGUI::ButtonPtr byeButton;
|
|
getWidget(byeButton, "ByeButton");
|
|
byeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked);
|
|
|
|
getWidget(mDispositionBar, "Disposition");
|
|
getWidget(mDispositionText,"DispositionText");
|
|
|
|
static_cast<MyGUI::Window*>(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize);
|
|
}
|
|
|
|
void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender)
|
|
{
|
|
MyGUI::ISubWidgetText* t = mHistory->getClient()->getSubWidgetText();
|
|
if(t == nullptr)
|
|
return;
|
|
|
|
const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left);
|
|
|
|
size_t cursorPosition = t->getCursorPosition(lastPressed);
|
|
MyGUI::UString color = mHistory->getColorAtPos(cursorPosition);
|
|
|
|
if (!mEnabled && color == "#572D21")
|
|
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
|
|
|
|
if(color != "#B29154")
|
|
{
|
|
MyGUI::UString key = mHistory->getColorTextAt(cursorPosition);
|
|
|
|
if(color == "#686EBA")
|
|
{
|
|
std::map<size_t, HyperLink>::iterator i = mHyperLinks.upper_bound(cursorPosition);
|
|
if( !mHyperLinks.empty() )
|
|
{
|
|
--i;
|
|
|
|
if( i->first + i->second.mLength > cursorPosition)
|
|
{
|
|
MWBase::Environment::get().getDialogueManager()->keywordSelected(i->second.mTrueValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the link was colored, but it is not in mHyperLinks.
|
|
// It means that those liunks are not marked with @# and found
|
|
// by topic name search
|
|
MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key));
|
|
}
|
|
}
|
|
|
|
if(color == "#572D21")
|
|
MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key));
|
|
}
|
|
}
|
|
|
|
void DialogueWindow::onWindowResize(MyGUI::Window* _sender)
|
|
{
|
|
mTopicsList->adjustSize();
|
|
}
|
|
|
|
void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
|
|
{
|
|
if (mHistory->getVScrollPosition() - _rel*0.3 < 0)
|
|
mHistory->setVScrollPosition(0);
|
|
else
|
|
mHistory->setVScrollPosition(mHistory->getVScrollPosition() - _rel*0.3);
|
|
}
|
|
|
|
void DialogueWindow::onByeClicked(MyGUI::Widget* _sender)
|
|
{
|
|
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
|
|
}
|
|
|
|
void DialogueWindow::onSelectTopic(std::string topic)
|
|
{
|
|
if (!mEnabled) return;
|
|
|
|
const MWWorld::Store<ESM::GameSetting> &gmst =
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
if (topic == gmst.find("sBarter")->getString())
|
|
{
|
|
/// \todo check if the player is allowed to trade with this actor (e.g. faction rank high enough)?
|
|
mWindowManager.pushGuiMode(GM_Barter);
|
|
mWindowManager.getTradeWindow()->startTrade(mPtr);
|
|
}
|
|
if (topic == gmst.find("sPersuasion")->getString())
|
|
{
|
|
mPersuasionDialog.setVisible(true);
|
|
}
|
|
else if (topic == gmst.find("sSpells")->getString())
|
|
{
|
|
mWindowManager.pushGuiMode(GM_SpellBuying);
|
|
mWindowManager.getSpellBuyingWindow()->startSpellBuying(mPtr);
|
|
}
|
|
else if (topic == gmst.find("sTravel")->getString())
|
|
{
|
|
mWindowManager.pushGuiMode(GM_Travel);
|
|
mWindowManager.getTravelWindow()->startTravel(mPtr);
|
|
}
|
|
else if (topic == gmst.find("sSpellMakingMenuTitle")->getString())
|
|
{
|
|
mWindowManager.pushGuiMode(GM_SpellCreation);
|
|
mWindowManager.startSpellMaking (mPtr);
|
|
}
|
|
else if (topic == gmst.find("sEnchanting")->getString())
|
|
{
|
|
mWindowManager.pushGuiMode(GM_Enchanting);
|
|
mWindowManager.startEnchanting (mPtr);
|
|
}
|
|
else if (topic == gmst.find("sServiceTrainingTitle")->getString())
|
|
{
|
|
mWindowManager.pushGuiMode(GM_Training);
|
|
mWindowManager.startTraining (mPtr);
|
|
}
|
|
else
|
|
MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(topic));
|
|
}
|
|
|
|
void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName)
|
|
{
|
|
mEnabled = true;
|
|
mPtr = actor;
|
|
mTopicsList->setEnabled(true);
|
|
setTitle(npcName);
|
|
|
|
mTopicsList->clear();
|
|
mHyperLinks.clear();
|
|
mHistory->setCaption("");
|
|
updateOptions();
|
|
}
|
|
|
|
void DialogueWindow::setKeywords(std::list<std::string> keyWords)
|
|
{
|
|
mTopicsList->clear();
|
|
|
|
bool anyService = mServices > 0;
|
|
|
|
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")->getString());
|
|
|
|
if (mServices & Service_Trade)
|
|
mTopicsList->addItem(gmst.find("sBarter")->getString());
|
|
|
|
if (mServices & Service_BuySpells)
|
|
mTopicsList->addItem(gmst.find("sSpells")->getString());
|
|
|
|
if (mServices & Service_Travel)
|
|
mTopicsList->addItem(gmst.find("sTravel")->getString());
|
|
|
|
if (mServices & Service_CreateSpells)
|
|
mTopicsList->addItem(gmst.find("sSpellmakingMenuTitle")->getString());
|
|
|
|
// if (mServices & Service_Enchant)
|
|
// mTopicsList->addItem(gmst.find("sEnchanting")->getString());
|
|
|
|
if (mServices & Service_Training)
|
|
mTopicsList->addItem(gmst.find("sServiceTrainingTitle")->getString());
|
|
|
|
if (anyService || mPtr.getTypeName() == typeid(ESM::NPC).name())
|
|
mTopicsList->addSeparator();
|
|
|
|
for(std::list<std::string>::iterator it = keyWords.begin(); it != keyWords.end(); ++it)
|
|
{
|
|
mTopicsList->addItem(*it);
|
|
}
|
|
mTopicsList->adjustSize();
|
|
}
|
|
|
|
void DialogueWindow::removeKeyword(std::string keyWord)
|
|
{
|
|
if(mTopicsList->hasItem(keyWord))
|
|
{
|
|
mTopicsList->removeItem(keyWord);
|
|
}
|
|
mTopicsList->adjustSize();
|
|
}
|
|
|
|
void addColorInString(std::string& str, const std::string& keyword,std::string color1, std::string color2)
|
|
{
|
|
size_t pos = 0;
|
|
while((pos = find_str_ci(str,keyword, pos)) != std::string::npos)
|
|
{
|
|
// do not add color if this portion of text is already colored.
|
|
{
|
|
MyGUI::TextIterator iterator (str);
|
|
MyGUI::UString colour;
|
|
while(iterator.moveNext())
|
|
{
|
|
size_t iteratorPos = iterator.getPosition();
|
|
iterator.getTagColour(colour);
|
|
if (iteratorPos == pos)
|
|
break;
|
|
}
|
|
|
|
if (colour == color1)
|
|
return;
|
|
}
|
|
|
|
str.insert(pos,color1);
|
|
pos += color1.length();
|
|
pos += keyword.length();
|
|
str.insert(pos,color2);
|
|
pos+= color2.length();
|
|
}
|
|
}
|
|
|
|
std::string DialogueWindow::parseText(const std::string& text)
|
|
{
|
|
bool separatorReached = false; // only parse topics that are below the separator (this prevents actions like "Barter" that are not topics from getting blue-colored)
|
|
|
|
std::vector<std::string> topics;
|
|
|
|
for(unsigned int i = 0;i<mTopicsList->getItemCount();i++)
|
|
{
|
|
std::string keyWord = mTopicsList->getItemNameAt(i);
|
|
if (separatorReached)
|
|
topics.push_back(keyWord);
|
|
else if (keyWord == "")
|
|
separatorReached = true;
|
|
}
|
|
|
|
// sort by length to make sure longer topics are replaced first
|
|
std::sort(topics.begin(), topics.end(), sortByLength);
|
|
|
|
std::vector<MWDialogue::HyperTextToken> hypertext = MWDialogue::ParseHyperText(text);
|
|
|
|
size_t historySize = 0;
|
|
if(mHistory->getClient()->getSubWidgetText() != nullptr)
|
|
{
|
|
historySize = mHistory->getOnlyText().size();
|
|
}
|
|
|
|
std::string result;
|
|
size_t hypertextPos = 0;
|
|
for (size_t i = 0; i < hypertext.size(); ++i)
|
|
{
|
|
if (hypertext[i].mLink)
|
|
{
|
|
size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText);
|
|
std::string standardForm = hypertext[i].mText;
|
|
for(; asterisk_count > 0; --asterisk_count)
|
|
standardForm.append("*");
|
|
|
|
standardForm =
|
|
MWBase::Environment::get().getWindowManager()->
|
|
getTranslationDataStorage().topicStandardForm(standardForm);
|
|
|
|
if( std::find(topics.begin(), topics.end(), std::string(standardForm) ) != topics.end() )
|
|
{
|
|
result.append("#686EBA").append(hypertext[i].mText).append("#B29154");
|
|
|
|
mHyperLinks[historySize+hypertextPos].mLength = MyGUI::UString(hypertext[i].mText).length();
|
|
mHyperLinks[historySize+hypertextPos].mTrueValue = lower_string(standardForm);
|
|
}
|
|
else
|
|
result += hypertext[i].mText;
|
|
}
|
|
else
|
|
{
|
|
if( !mWindowManager.getTranslationDataStorage().hasTranslation() )
|
|
{
|
|
for(std::vector<std::string>::const_iterator it = topics.begin(); it != topics.end(); ++it)
|
|
{
|
|
addColorInString(hypertext[i].mText, *it, "#686EBA", "#B29154");
|
|
}
|
|
}
|
|
|
|
result += hypertext[i].mText;
|
|
}
|
|
|
|
hypertextPos += MyGUI::UString(hypertext[i].mText).length();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void DialogueWindow::addText(std::string text)
|
|
{
|
|
mHistory->addDialogText("#B29154"+parseText(text)+"#B29154");
|
|
}
|
|
|
|
void DialogueWindow::addMessageBox(const std::string& text)
|
|
{
|
|
mHistory->addDialogText("\n#FFFFFF"+text+"#B29154");
|
|
}
|
|
|
|
void DialogueWindow::addTitle(std::string text)
|
|
{
|
|
// This is called from the dialogue manager, so text is
|
|
// case-smashed - thus we have to retrieve the correct case
|
|
// of the text through the topic list.
|
|
for (size_t i=0; i<mTopicsList->getItemCount(); ++i)
|
|
{
|
|
std::string item = mTopicsList->getItemNameAt(i);
|
|
if (lower_string(item) == text)
|
|
text = item;
|
|
}
|
|
|
|
mHistory->addDialogHeading(text);
|
|
}
|
|
|
|
void DialogueWindow::askQuestion(std::string question)
|
|
{
|
|
mHistory->addDialogText("#572D21"+question+"#B29154"+" ");
|
|
}
|
|
|
|
void DialogueWindow::updateOptions()
|
|
{
|
|
//Clear the list of topics
|
|
mTopicsList->clear();
|
|
mHyperLinks.clear();
|
|
mHistory->eraseText(0, mHistory->getTextLength());
|
|
|
|
if (mPtr.getTypeName() == typeid(ESM::NPC).name())
|
|
{
|
|
mDispositionBar->setProgressRange(100);
|
|
mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr));
|
|
mDispositionText->eraseText(0, mDispositionText->getTextLength());
|
|
mDispositionText->addText("#B29154"+boost::lexical_cast<std::string>(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")+"#B29154");
|
|
}
|
|
}
|
|
|
|
void DialogueWindow::goodbye()
|
|
{
|
|
mHistory->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sGoodbye")->getString());
|
|
mTopicsList->setEnabled(false);
|
|
mEnabled = false;
|
|
}
|
|
|
|
void DialogueWindow::onReferenceUnavailable()
|
|
{
|
|
mWindowManager.removeGuiMode(GM_Dialogue);
|
|
}
|
|
|
|
void DialogueWindow::onFrame()
|
|
{
|
|
if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name())
|
|
{
|
|
int disp = std::max(0, std::min(100,
|
|
MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)
|
|
+ MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()));
|
|
mDispositionBar->setProgressRange(100);
|
|
mDispositionBar->setProgressPosition(disp);
|
|
mDispositionText->eraseText(0, mDispositionText->getTextLength());
|
|
mDispositionText->addText("#B29154"+boost::lexical_cast<std::string>(disp)+std::string("/100")+"#B29154");
|
|
}
|
|
}
|