diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b9ce26a66..57a417722 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -34,7 +34,7 @@ add_openmw_dir (mwgui ) add_openmw_dir (mwdialogue - dialoguemanagerimp journalimp journalentry quest topic + dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper ) add_openmw_dir (mwscript diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a75a60223..d646d5aca 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -20,6 +20,7 @@ #include "mwscript/scriptmanagerimp.hpp" #include "mwscript/extensions.hpp" +#include "mwscript/interpretercontext.hpp" #include "mwsound/soundmanagerimp.hpp" diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 2181ddb58..a205e78db 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -33,8 +33,8 @@ namespace MWBase virtual void goodbye() = 0; - ///get the faction of the actor you are talking with - virtual std::string getFaction() const = 0; + virtual MWWorld::Ptr getActor() const = 0; + ///< Return the actor the player is currently talking to. //calbacks for the GUI virtual void keywordSelected (const std::string& keyword) = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 910838e12..edf5dd25d 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -62,10 +62,10 @@ namespace MWClass data->mCreatureStats.setLevel(ref->mBase->mData.mLevel); - data->mCreatureStats.setHello(ref->mBase->mAiData.mHello); - data->mCreatureStats.setFight(ref->mBase->mAiData.mFight); - data->mCreatureStats.setFlee(ref->mBase->mAiData.mFlee); - data->mCreatureStats.setAlarm(ref->mBase->mAiData.mAlarm); + data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello); + data->mCreatureStats.setAiSetting (1, ref->mBase->mAiData.mFight); + data->mCreatureStats.setAiSetting (2, ref->mBase->mAiData.mFlee); + data->mCreatureStats.setAiSetting (3, ref->mBase->mAiData.mAlarm); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 790d824f5..83a002447 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -110,10 +110,10 @@ namespace MWClass data->mCreatureStats.setLevel (1); } - data->mCreatureStats.setHello(ref->mBase->mAiData.mHello); - data->mCreatureStats.setFight(ref->mBase->mAiData.mFight); - data->mCreatureStats.setFlee(ref->mBase->mAiData.mFlee); - data->mCreatureStats.setAlarm(ref->mBase->mAiData.mAlarm); + data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello); + data->mCreatureStats.setAiSetting (1, ref->mBase->mAiData.mFight); + data->mCreatureStats.setAiSetting (2, ref->mBase->mAiData.mFlee); + data->mCreatureStats.setAiSetting (3, ref->mBase->mAiData.mAlarm); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 460a0e26d..881a02d00 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -6,23 +6,7 @@ #include #include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/scriptmanager.hpp" -#include "../mwbase/journal.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/refdata.hpp" -#include "../mwworld/player.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" - -#include "../mwgui/dialogue.hpp" - -#include +#include #include #include @@ -30,15 +14,31 @@ #include #include #include + #include +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/scriptmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" + +#include "../mwgui/dialogue.hpp" + #include "../mwscript/compilercontext.hpp" #include "../mwscript/interpretercontext.hpp" #include "../mwscript/extensions.hpp" -#include "../mwclass/npc.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "filter.hpp" + namespace { std::string toLower (const std::string& name) @@ -66,87 +66,6 @@ namespace return false; } - template - bool selectCompare (char comp, T1 value1, T2 value2) - { - switch (comp) - { - case '0': return value1==value2; - case '1': return value1!=value2; - case '2': return value1>value2; - case '3': return value1>=value2; - case '4': return value1 - bool checkLocal (char comp, const std::string& name, T value, const MWWorld::Ptr& actor, - const MWWorld::ESMStore& store) - { - std::string scriptName = MWWorld::Class::get (actor).getScript (actor); - - if (scriptName.empty()) - return false; // no script - - const ESM::Script *script = - store.get().find (scriptName); - - int i = 0; - - for (; i (script->mVarNames.size()); ++i) - if (script->mVarNames[i]==name) - break; - - if (i>=static_cast (script->mVarNames.size())) - return false; // script does not have a variable of this name - - const MWScript::Locals& locals = actor.getRefData().getLocals(); - - if (imData.mNumShorts) - return selectCompare (comp, locals.mShorts[i], value); - else - i -= script->mData.mNumShorts; - - if (imData.mNumLongs) - return selectCompare (comp, locals.mLongs[i], value); - else - i -= script->mData.mNumShorts; - - return selectCompare (comp, locals.mFloats.at (i), value); - } - - template - bool checkGlobal (char comp, const std::string& name, T value) - { - switch (MWBase::Environment::get().getWorld()->getGlobalVariableType (name)) - { - case 's': - return selectCompare (comp, MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort, value); - - case 'l': - - return selectCompare (comp, MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong, value); - - case 'f': - - return selectCompare (comp, MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat, value); - - case ' ': - - MWBase::Environment::get().getWorld()->getGlobalVariable (name); // trigger exception - break; - - default: - - throw std::runtime_error ("unsupported gobal variable type"); - } - - return false; - } - //helper function std::string::size_type find_str_ci(const std::string& str, const std::string& substr,size_t pos) { @@ -156,432 +75,6 @@ namespace namespace MWDialogue { - - - bool DialogueManager::functionFilter(const MWWorld::Ptr& actor, const ESM::DialInfo& info,bool choice) - { - bool isCreature = (actor.getTypeName() != typeid(ESM::NPC).name()); - - for (std::vector::const_iterator iter (info.mSelects.begin()); - iter != info.mSelects.end(); ++iter) - { - ESM::DialInfo::SelectStruct select = *iter; - char type = select.mSelectRule[1]; - if(type == '1') - { - char comp = select.mSelectRule[4]; - std::string name = select.mSelectRule.substr (5); - std::string function = select.mSelectRule.substr(2,2); - - int ifunction; - std::istringstream iss(function); - iss >> ifunction; - switch(ifunction) - { - case 39://PC Expelled - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 40://PC Common Disease - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 41://PC Blight Disease - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 43://PC Crime level - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 46://Same faction - { - if (isCreature) - return false; - - MWMechanics::NpcStats PCstats = MWWorld::Class::get(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()).getNpcStats(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - MWMechanics::NpcStats NPCstats = MWWorld::Class::get(actor).getNpcStats(actor); - int sameFaction = 0; - if(!NPCstats.getFactionRanks().empty()) - { - std::string NPCFaction = NPCstats.getFactionRanks().begin()->first; - if(PCstats.getFactionRanks().find(toLower(NPCFaction)) != PCstats.getFactionRanks().end()) sameFaction = 1; - } - if(!selectCompare(comp,sameFaction,select.mI)) return false; - } - break; - - case 48://Detected - if(!selectCompare(comp,1,select.mI)) return false; - break; - - case 49://Alarmed - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 50://choice - if(choice) - { - if(!selectCompare(comp,mChoice,select.mI)) return false; - } - break; - - case 60://PC Vampire - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 61://Level - if(!selectCompare(comp,1,select.mI)) return false; - break; - - case 62://Attacked - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 63://Talked to PC - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 64://PC Health - if(!selectCompare(comp,50,select.mI)) return false; - break; - - case 65://Creature target - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 66://Friend hit - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 67://Fight - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 68://Hello???? - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 69://Alarm - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 70://Flee - if(!selectCompare(comp,0,select.mI)) return false; - break; - - case 71://Should Attack - if(!selectCompare(comp,0,select.mI)) return false; - break; - - default: - break; - - } - } - } - - return true; - } - - bool DialogueManager::isMatching (const MWWorld::Ptr& actor, - const ESM::DialInfo::SelectStruct& select) const - { - bool isCreature = (actor.getTypeName() != typeid(ESM::NPC).name()); - - char type = select.mSelectRule[1]; - - if (type!='0') - { - char comp = select.mSelectRule[4]; - std::string name = select.mSelectRule.substr (5); - std::string function = select.mSelectRule.substr(1,2); - - switch (type) - { - case '1': // function - - return true; // Done elsewhere. - - case '2': // global - - if (select.mType==ESM::VT_Short || select.mType==ESM::VT_Int || - select.mType==ESM::VT_Long) - { - if (!checkGlobal (comp, toLower (name), select.mI)) - return false; - } - else if (select.mType==ESM::VT_Float) - { - if (!checkGlobal (comp, toLower (name), select.mF)) - return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - - return true; - - case '3': // local - - if (select.mType==ESM::VT_Short || select.mType==ESM::VT_Int || - select.mType==ESM::VT_Long) - { - if (!checkLocal (comp, toLower (name), select.mI, actor, - MWBase::Environment::get().getWorld()->getStore())) - return false; - } - else if (select.mType==ESM::VT_Float) - { - if (!checkLocal (comp, toLower (name), select.mF, actor, - MWBase::Environment::get().getWorld()->getStore())) - return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - - return true; - - case '4'://journal - if(select.mType==ESM::VT_Int) - { - if(!selectCompare(comp,MWBase::Environment::get().getJournal()->getJournalIndex(toLower(name)),select.mI)) return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - - return true; - - case '5'://item - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::ContainerStore& store = MWWorld::Class::get (player).getContainerStore (player); - - int sum = 0; - - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (toLower(iter->getCellRef().mRefID) == toLower(name)) - sum += iter->getRefData().getCount(); - if(!selectCompare(comp,sum,select.mI)) return false; - } - - return true; - - - case '6'://dead - if(!selectCompare(comp,0,select.mI)) return false; - - case '7':// not ID - if(select.mType==ESM::VT_String ||select.mType==ESM::VT_Int)//bug in morrowind here? it's not a short, it's a string - { - int isID = int(toLower(name)==toLower(MWWorld::Class::get (actor).getId (actor))); - if (selectCompare(comp,!isID,select.mI)) return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - - return true; - - case '8':// not faction - if (isCreature) - return false; - - if(select.mType==ESM::VT_Int) - { - MWWorld::LiveCellRef* npc = actor.get(); - int isFaction = int(toLower(npc->mBase->mFaction) == toLower(name)); - if(selectCompare(comp,!isFaction,select.mI)) - return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - - return true; - - case '9':// not class - if (isCreature) - return false; - - if(select.mType==ESM::VT_Int) - { - MWWorld::LiveCellRef* npc = actor.get(); - int isClass = int(toLower(npc->mBase->mClass) == toLower(name)); - if(selectCompare(comp,!isClass,select.mI)) - return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - - return true; - - case 'A'://not Race - if (isCreature) - return false; - - if(select.mType==ESM::VT_Int) - { - MWWorld::LiveCellRef* npc = actor.get(); - int isRace = int(toLower(npc->mBase->mRace) == toLower(name)); - if(selectCompare(comp,!isRace,select.mI)) - return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - - return true; - - case 'B'://not Cell - if(select.mType==ESM::VT_Int) - { - int isCell = int(toLower(actor.getCell()->mCell->mName) == toLower(name)); - if(selectCompare(comp,!isCell,select.mI)) - return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - return true; - - case 'C'://not local - if (select.mType==ESM::VT_Short || select.mType==ESM::VT_Int || - select.mType==ESM::VT_Long) - { - if (checkLocal (comp, toLower (name), select.mI, actor, - MWBase::Environment::get().getWorld()->getStore())) - return false; - } - else if (select.mType==ESM::VT_Float) - { - if (checkLocal (comp, toLower (name), select.mF, actor, - MWBase::Environment::get().getWorld()->getStore())) - return false; - } - else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); - return true; - - - default: - - std::cout << "unchecked select: " << type << " " << comp << " " << name << std::endl; - } - } - - return true; - } - - bool DialogueManager::isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo& info) const - { - bool isCreature = (actor.getTypeName() != typeid(ESM::NPC).name()); - - // actor id - if (!info.mActor.empty()) - if (toLower (info.mActor)!=MWWorld::Class::get (actor).getId (actor)) - return false; - - //NPC race - if (!info.mRace.empty()) - { - if (isCreature) - return false; - - MWWorld::LiveCellRef *cellRef = actor.get(); - - if (!cellRef) - return false; - - if (toLower (info.mRace)!=toLower (cellRef->mBase->mRace)) - return false; - } - - //NPC class - if (!info.mClass.empty()) - { - if (isCreature) - return false; - - MWWorld::LiveCellRef *cellRef = actor.get(); - - if (!cellRef) - return false; - - if (toLower (info.mClass)!=toLower (cellRef->mBase->mClass)) - return false; - } - - //NPC faction - if (!info.mNpcFaction.empty()) - { - if (isCreature) - return false; - - //MWWorld::Class npcClass = MWWorld::Class::get(actor); - MWMechanics::NpcStats stats = MWWorld::Class::get(actor).getNpcStats(actor); - std::map::iterator it = stats.getFactionRanks().find(toLower(info.mNpcFaction)); - if(it!=stats.getFactionRanks().end()) - { - //check rank - if(it->second < (int)info.mData.mRank) return false; - } - else - { - //not in the faction - return false; - } - } - - // TODO check player faction - if(!info.mPcFaction.empty()) - { - MWMechanics::NpcStats stats = MWWorld::Class::get(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()).getNpcStats(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - std::map::iterator it = stats.getFactionRanks().find(toLower(info.mPcFaction)); - if(it!=stats.getFactionRanks().end()) - { - //check rank - if(it->second < (int)info.mData.mPCrank) return false; - } - else - { - //not in the faction - return false; - } - } - - //check gender - if (!isCreature) - { - MWWorld::LiveCellRef* npc = actor.get(); - if(npc->mBase->mFlags & npc->mBase->Female) - { - if(static_cast (info.mData.mGender)==0) return false; - } - else - { - if(static_cast (info.mData.mGender)==1) return false; - } - } - - // check cell - if (!info.mCell.empty()) - if (MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mName != info.mCell) - return false; - - // TODO check DATAstruct - for (std::vector::const_iterator iter (info.mSelects.begin()); - iter != info.mSelects.end(); ++iter) - if (!isMatching (actor, *iter)) - return false; - - return true; - } - DialogueManager::DialogueManager (const Compiler::Extensions& extensions) : mCompilerContext (MWScript::CompilerContext::Type_Dialgoue), mErrorStream(std::cout.rdbuf()),mErrorHandler(mErrorStream) @@ -609,7 +102,7 @@ namespace MWDialogue mKnownTopics[toLower(topic)] = true; } - void DialogueManager::parseText (std::string text) + void DialogueManager::parseText (const std::string& text) { std::list::iterator it; for(it = mActorKnownTopics.begin();it != mActorKnownTopics.end();++it) @@ -636,6 +129,10 @@ namespace MWDialogue mIsInChoice = false; mActor = actor; + + MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); + mTalkedTo = creatureStats.hasTalkedToPlayer(); + creatureStats.talkedToPlayer(); mActorKnownTopics.clear(); @@ -648,35 +145,28 @@ namespace MWDialogue updateTopics(); //greeting - bool greetingFound = false; const MWWorld::Store &dialogs = MWBase::Environment::get().getWorld()->getStore().get(); - MWWorld::Store::iterator it = dialogs.begin(); - for (; it != dialogs.end(); ++it) + Filter filter (actor, mChoice, mTalkedTo); + + for (MWWorld::Store::iterator it = dialogs.begin(); it != dialogs.end(); ++it) { if(it->mType == ESM::Dialogue::Greeting) { - if (greetingFound) break; - for (std::vector::const_iterator iter (it->mInfo.begin()); - iter!=it->mInfo.end(); ++iter) + if (const ESM::DialInfo *info = filter.search (*it)) { - if (isMatching (actor, *iter) && functionFilter(mActor,*iter,true)) + if (!info->mSound.empty()) { - if (!iter->mSound.empty()) - { - // TODO play sound - } - - std::string text = iter->mResponse; - parseText(text); - win->addText(iter->mResponse); - executeScript(iter->mResultScript); - greetingFound = true; - mLastTopic = it->mId; - mLastDialogue = *iter; - break; + // TODO play sound } + + parseText (info->mResponse); + win->addText (info->mResponse); + executeScript (info->mResultScript); + mLastTopic = it->mId; + mLastDialogue = *info; + break; } } } @@ -724,7 +214,7 @@ namespace MWDialogue return false; } - void DialogueManager::executeScript(std::string script) + void DialogueManager::executeScript (const std::string& script) { std::vector code; if(compile(script,code)) @@ -749,29 +239,24 @@ namespace MWDialogue int choice = mChoice; mChoice = -1; mActorKnownTopics.clear(); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); const MWWorld::Store &dialogs = MWBase::Environment::get().getWorld()->getStore().get(); + Filter filter (mActor, mChoice, mTalkedTo); - MWWorld::Store::iterator it = dialogs.begin(); - for (; it != dialogs.end(); ++it) + for (MWWorld::Store::iterator iter = dialogs.begin(); iter != dialogs.end(); ++iter) { - if(it->mType == ESM::Dialogue::Topic) + if (iter->mType == ESM::Dialogue::Topic) { - for (std::vector::const_iterator iter (it->mInfo.begin()); - iter!=it->mInfo.end(); ++iter) + if (filter.search (*iter)) { - if (isMatching (mActor, *iter) && functionFilter(mActor,*iter,true)) + mActorKnownTopics.push_back (toLower (iter->mId)); + + //does the player know the topic? + if (mKnownTopics.find (toLower (iter->mId)) != mKnownTopics.end()) { - mActorKnownTopics.push_back(toLower(it->mId)); - //does the player know the topic? - if(mKnownTopics.find(toLower(it->mId)) != mKnownTopics.end()) - { - keywordList.push_back(it->mId); - break; - } + keywordList.push_back (iter->mId); } } } @@ -822,6 +307,8 @@ namespace MWDialogue if (services & ESM::NPC::Enchanting) windowServices |= MWGui::DialogueWindow::Service_Enchant; + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + win->setServices (windowServices); // sort again, because the previous sort was case-sensitive @@ -838,28 +325,25 @@ namespace MWDialogue if(mDialogueMap.find(keyword) != mDialogueMap.end()) { ESM::Dialogue ndialogue = mDialogueMap[keyword]; - if(ndialogue.mType == ESM::Dialogue::Topic) + if (mDialogueMap[keyword].mType == ESM::Dialogue::Topic) { - for (std::vector::const_iterator iter = ndialogue.mInfo.begin(); - iter!=ndialogue.mInfo.end(); ++iter) + Filter filter (mActor, mChoice, mTalkedTo); + + if (const ESM::DialInfo *info = filter.search (mDialogueMap[keyword])) { - if (isMatching (mActor, *iter) && functionFilter(mActor,*iter,true)) - { - std::string text = iter->mResponse; - std::string script = iter->mResultScript; + std::string text = info->mResponse; + std::string script = info->mResultScript; - parseText(text); + parseText (text); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->addTitle(keyword); - win->addText(iter->mResponse); + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + win->addTitle (keyword); + win->addText (info->mResponse); - executeScript(script); + executeScript (script); - mLastTopic = keyword; - mLastDialogue = *iter; - break; - } + mLastTopic = keyword; + mLastDialogue = *info; } } } @@ -875,7 +359,7 @@ namespace MWDialogue // Apply disposition change to NPC's base disposition if (mActor.getTypeName() == typeid(ESM::NPC).name()) { - MWMechanics::NpcStats npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor); + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor); npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange); } mPermanentDispositionChange = 0; @@ -884,41 +368,36 @@ namespace MWDialogue void DialogueManager::questionAnswered (const std::string& answer) { - if(mChoiceMap.find(answer) != mChoiceMap.end()) + if (mChoiceMap.find(answer) != mChoiceMap.end()) { mChoice = mChoiceMap[answer]; - std::vector::const_iterator iter; - if(mDialogueMap.find(mLastTopic) != mDialogueMap.end()) + if (mDialogueMap.find(mLastTopic) != mDialogueMap.end()) { - ESM::Dialogue ndialogue = mDialogueMap[mLastTopic]; - if(ndialogue.mType == ESM::Dialogue::Topic) + if (mDialogueMap[mLastTopic].mType == ESM::Dialogue::Topic) { - for (std::vector::const_iterator iter = ndialogue.mInfo.begin(); - iter!=ndialogue.mInfo.end(); ++iter) + Filter filter (mActor, mChoice, mTalkedTo); + + if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic])) { - if (isMatching (mActor, *iter) && functionFilter(mActor,*iter,true)) - { - mChoiceMap.clear(); - mChoice = -1; - mIsInChoice = false; - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - std::string text = iter->mResponse; - parseText(text); - win->addText(text); - executeScript(iter->mResultScript); - mLastTopic = mLastTopic; - mLastDialogue = *iter; - break; - } + mChoiceMap.clear(); + mChoice = -1; + mIsInChoice = false; + std::string text = info->mResponse; + parseText (text); + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (text); + executeScript (info->mResultScript); + mLastTopic = mLastTopic; + mLastDialogue = *info; } } } + updateTopics(); } } - void DialogueManager::printError (std::string error) + void DialogueManager::printError (const std::string& error) { MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->addText(error); @@ -932,22 +411,9 @@ namespace MWDialogue mIsInChoice = true; } - std::string DialogueManager::getFaction() const + MWWorld::Ptr DialogueManager::getActor() const { - if (mActor.getTypeName() != typeid(ESM::NPC).name()) - return ""; - - std::string factionID(""); - MWMechanics::NpcStats stats = MWWorld::Class::get(mActor).getNpcStats(mActor); - if(stats.getFactionRanks().empty()) - { - std::cout << "No faction for this actor!"; - } - else - { - factionID = stats.getFactionRanks().begin()->first; - } - return factionID; + return mActor; } void DialogueManager::goodbye() @@ -980,7 +446,6 @@ namespace MWDialogue if (success) MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, 0); - // add status message to dialogue window std::string text; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index a8dea3dba..8e862f932 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -3,48 +3,32 @@ #include "../mwbase/dialoguemanager.hpp" -#include +#include +#include #include -#include "../mwscript/compilercontext.hpp" -#include "../mwscript/interpretercontext.hpp" -#include #include "../mwworld/ptr.hpp" -#include +#include "../mwscript/compilercontext.hpp" namespace MWDialogue { class DialogueManager : public MWBase::DialogueManager { - bool isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo::SelectStruct& select) const; - - bool isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo& info) const; - - bool functionFilter(const MWWorld::Ptr& actor, const ESM::DialInfo& info,bool choice); - - void parseText(std::string text); - - void updateTopics(); - - std::map mDialogueMap; - std::map mKnownTopics;// Those are the topics the player knows. + std::map mDialogueMap; + std::map mKnownTopics;// Those are the topics the player knows. std::list mActorKnownTopics; MWScript::CompilerContext mCompilerContext; std::ostream mErrorStream; Compiler::StreamErrorHandler mErrorHandler; - - - bool compile (const std::string& cmd,std::vector& code); - void executeScript(std::string script); + MWWorld::Ptr mActor; - - void printError(std::string error); + bool mTalkedTo; int mChoice; - std::map mChoiceMap; + std::map mChoiceMap; std::string mLastTopic; ESM::DialInfo mLastDialogue; bool mIsInChoice; @@ -52,6 +36,15 @@ namespace MWDialogue float mTemporaryDispositionChange; float mPermanentDispositionChange; + void parseText (const std::string& text); + + void updateTopics(); + + bool compile (const std::string& cmd,std::vector& code); + void executeScript (const std::string& script); + + void printError (const std::string& error); + public: DialogueManager (const Compiler::Extensions& extensions); @@ -64,8 +57,8 @@ namespace MWDialogue virtual void goodbye(); - ///get the faction of the actor you are talking with - virtual std::string getFaction() const; + virtual MWWorld::Ptr getActor() const; + ///< Return the actor the player is currently talking to. //calbacks for the GUI virtual void keywordSelected (const std::string& keyword); diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp new file mode 100644 index 000000000..21904b4bc --- /dev/null +++ b/apps/openmw/mwdialogue/filter.cpp @@ -0,0 +1,494 @@ + +#include "filter.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/journal.hpp" +#include "../mwbase/mechanicsmanager.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/magiceffects.hpp" + +#include "selectwrapper.hpp" + +namespace +{ + std::string toLower (const std::string& name) + { + std::string lowerCase; + + std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), + (int(*)(int)) std::tolower); + + return lowerCase; + } +} + +bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const +{ + // actor id + if (!info.mActor.empty()) + if (toLower (info.mActor)!=MWWorld::Class::get (mActor).getId (mActor)) + return false; + + bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + + // NPC race + if (!info.mRace.empty()) + { + if (isCreature) + return false; + + MWWorld::LiveCellRef *cellRef = mActor.get(); + + if (toLower (info.mRace)!=toLower (cellRef->mBase->mRace)) + return false; + } + + // NPC class + if (!info.mClass.empty()) + { + if (isCreature) + return false; + + MWWorld::LiveCellRef *cellRef = mActor.get(); + + if (toLower (info.mClass)!=toLower (cellRef->mBase->mClass)) + return false; + } + + // NPC faction + if (!info.mNpcFaction.empty()) + { + if (isCreature) + return false; + + MWMechanics::NpcStats& stats = MWWorld::Class::get (mActor).getNpcStats (mActor); + std::map::iterator iter = stats.getFactionRanks().find (toLower (info.mNpcFaction)); + + if (iter==stats.getFactionRanks().end()) + return false; + + // check rank + if (iter->second < info.mData.mRank) + return false; + } + + // Gender + if (!isCreature) + { + MWWorld::LiveCellRef* npc = mActor.get(); + if (info.mData.mGender==(npc->mBase->mFlags & npc->mBase->Female ? 0 : 1)) + return false; + } + + return true; +} + +bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const +{ + const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + // check player faction + if (!info.mPcFaction.empty()) + { + MWMechanics::NpcStats& stats = MWWorld::Class::get (player).getNpcStats (player); + std::map::iterator iter = stats.getFactionRanks().find (toLower (info.mPcFaction)); + + if(iter==stats.getFactionRanks().end()) + return false; + + // check rank + if (iter->second < info.mData.mPCrank) + return false; + } + + // check cell + if (!info.mCell.empty()) + if (toLower (player.getCell()->mCell->mName) != toLower (info.mCell)) + return false; + + return true; +} + +bool MWDialogue::Filter::testSelectStructs (const ESM::DialInfo& info) const +{ + for (std::vector::const_iterator iter (info.mSelects.begin()); + iter != info.mSelects.end(); ++iter) + if (!testSelectStruct (*iter)) + return false; + + return true; +} + +bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const +{ + if (select.isNpcOnly() && mActor.getTypeName()!=typeid (ESM::NPC).name()) + return select.isInverted(); + + switch (select.getType()) + { + case SelectWrapper::Type_None: return true; + case SelectWrapper::Type_Integer: return select.selectCompare (getSelectStructInteger (select)); + case SelectWrapper::Type_Numeric: return testSelectStructNumeric (select); + case SelectWrapper::Type_Boolean: return select.selectCompare (getSelectStructBoolean (select)); + } + + return true; +} + +bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) const +{ + switch (select.getFunction()) + { + case SelectWrapper::Function_Global: + + // internally all globals are float :( + return select.selectCompare ( + MWBase::Environment::get().getWorld()->getGlobalVariable (select.getName()).mFloat); + + case SelectWrapper::Function_Local: + { + std::string scriptName = MWWorld::Class::get (mActor).getScript (mActor); + + if (scriptName.empty()) + return false; // no script + + const ESM::Script *script = + MWBase::Environment::get().getWorld()->getStore().get().find (scriptName); + + std::string name = select.getName(); + + int i = 0; + + for (; i (script->mVarNames.size()); ++i) + if (script->mVarNames[i]==name) + break; + + if (i>=static_cast (script->mVarNames.size())) + return false; // script does not have a variable of this name + + const MWScript::Locals& locals = mActor.getRefData().getLocals(); + + if (imData.mNumShorts) + return select.selectCompare (static_cast (locals.mShorts[i])); + + i -= script->mData.mNumShorts; + + if (imData.mNumLongs) + return select.selectCompare (locals.mLongs[i]); + + i -= script->mData.mNumShorts; + + return select.selectCompare (locals.mFloats.at (i)); + } + + case SelectWrapper::Function_PcHealthPercent: + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() / + MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified(); + + return select.selectCompare (ratio); + } + + case SelectWrapper::Function_PcDynamicStat: + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + float value = MWWorld::Class::get (player).getCreatureStats (player). + getDynamic (select.getArgument()).getCurrent(); + + return select.selectCompare (value); + } + + case SelectWrapper::Function_HealthPercent: + { + float ratio = MWWorld::Class::get (mActor).getCreatureStats (mActor).getHealth().getCurrent() / + MWWorld::Class::get (mActor).getCreatureStats (mActor).getHealth().getModified(); + + return select.selectCompare (ratio); + } + + default: + + throw std::runtime_error ("unknown numeric select function"); + } +} + +int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const +{ + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + switch (select.getFunction()) + { + case SelectWrapper::Function_Journal: + + return MWBase::Environment::get().getJournal()->getJournalIndex (select.getName()); + + case SelectWrapper::Function_Item: + { + MWWorld::ContainerStore& store = MWWorld::Class::get (player).getContainerStore (player); + + int sum = 0; + + std::string name = select.getName(); + + for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) + if (toLower(iter->getCellRef().mRefID) == name) + sum += iter->getRefData().getCount(); + + return sum; + } + + case SelectWrapper::Function_Dead: + + return MWBase::Environment::get().getMechanicsManager()->countDeaths (select.getName()); + + case SelectWrapper::Function_Choice: + + return mChoice; + + case SelectWrapper::Function_AiSetting: + + return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting (select.getArgument()); + + case SelectWrapper::Function_PcAttribute: + + return MWWorld::Class::get (player).getCreatureStats (player). + getAttribute (select.getArgument()).getModified(); + + case SelectWrapper::Function_PcSkill: + + return static_cast (MWWorld::Class::get (player). + getNpcStats (player).getSkill (select.getArgument()).getModified()); + + case SelectWrapper::Function_FriendlyHit: + { + int hits = MWWorld::Class::get (mActor).getCreatureStats (mActor).getFriendlyHits(); + + return hits>4 ? 4 : hits; + } + + case SelectWrapper::Function_PcLevel: + + return MWWorld::Class::get (player).getCreatureStats (player).getLevel(); + + case SelectWrapper::Function_PcGender: + + return player.get()->mBase->mFlags & ESM::NPC::Female ? 0 : 1; + + case SelectWrapper::Function_PcClothingModifier: + { + MWWorld::InventoryStore& store = MWWorld::Class::get (player).getInventoryStore (player); + + int value = 0; + + for (int i=0; i<=15; ++i) // everything except thigns held in hands and amunition + { + MWWorld::ContainerStoreIterator slot = store.getSlot (i); + + if (slot!=store.end()) + value += MWWorld::Class::get (*slot).getValue (*slot); + } + + return value; + } + + case SelectWrapper::Function_PcCrimeLevel: + + return MWWorld::Class::get (player).getNpcStats (player).getBounty(); + + case SelectWrapper::Function_RankRequirement: + { + if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty()) + return 0; + + std::string faction = + MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first; + + int rank = getFactionRank (player, faction); + + if (rank>=9) + return 0; // max rank + + int result = 0; + + if (hasFactionRankSkillRequirements (player, faction, rank+1)) + result += 1; + + if (hasFactionRankReputationRequirements (player, faction, rank+1)) + result += 2; + + return result; + } + + case SelectWrapper::Function_Level: + + return MWWorld::Class::get (mActor).getCreatureStats (mActor).getLevel(); + + case SelectWrapper::Function_PCReputation: + + return MWWorld::Class::get (player).getNpcStats (player).getReputation(); + + default: + + throw std::runtime_error ("unknown integer select function"); + } +} + +bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const +{ + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + switch (select.getFunction()) + { + case SelectWrapper::Function_False: + + return false; + + case SelectWrapper::Function_Id: + + return select.getName()==toLower (MWWorld::Class::get (mActor).getId (mActor)); + + case SelectWrapper::Function_Faction: + + return toLower (mActor.get()->mBase->mFaction)==select.getName(); + + case SelectWrapper::Function_Class: + + return toLower (mActor.get()->mBase->mClass)==select.getName(); + + case SelectWrapper::Function_Race: + + return toLower (mActor.get()->mBase->mRace)==select.getName(); + + case SelectWrapper::Function_Cell: + + return toLower (mActor.getCell()->mCell->mName)==select.getName(); + + case SelectWrapper::Function_SameGender: + + return (player.get()->mBase->mFlags & ESM::NPC::Female)== + (mActor.get()->mBase->mFlags & ESM::NPC::Female); + + case SelectWrapper::Function_SameRace: + + return toLower (mActor.get()->mBase->mRace)!= + toLower (player.get()->mBase->mRace); + + case SelectWrapper::Function_SameFaction: + + return MWWorld::Class::get (mActor).getNpcStats (mActor).isSameFaction ( + MWWorld::Class::get (player).getNpcStats (player)); + + case SelectWrapper::Function_PcCommonDisease: + + return MWWorld::Class::get (player).getCreatureStats (player).hasCommonDisease(); + + case SelectWrapper::Function_PcBlightDisease: + + return MWWorld::Class::get (player).getCreatureStats (player).hasBlightDisease(); + + case SelectWrapper::Function_PcCorprus: + + return MWWorld::Class::get (player).getCreatureStats (player). + getMagicEffects().get (132).mMagnitude!=0; + + case SelectWrapper::Function_PcExpelled: + { + if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty()) + return false; + + std::string faction = + MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first; + + std::set& expelled = MWWorld::Class::get (player).getNpcStats (player).getExpelled(); + + return expelled.find (faction)!=expelled.end(); + } + + case SelectWrapper::Function_PcVampire: + + return MWWorld::Class::get (player).getNpcStats (player).isVampire(); + + case SelectWrapper::Function_TalkedToPc: + + return mTalkedToPlayer; + + default: + + throw std::runtime_error ("unknown boolean select function"); + } +} + +int MWDialogue::Filter::getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const +{ + MWMechanics::NpcStats& stats = MWWorld::Class::get (actor).getNpcStats (actor); + + std::map::const_iterator iter = stats.getFactionRanks().find (factionId); + + if (iter==stats.getFactionRanks().end()) + return -1; + + return iter->second; +} + +bool MWDialogue::Filter::hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, + const std::string& factionId, int rank) const +{ + if (rank<0 || rank>=10) + throw std::runtime_error ("rank index out of range"); + + if (!MWWorld::Class::get (actor).getNpcStats (actor).hasSkillsForRank (factionId, rank)) + return false; + + const ESM::Faction& faction = + *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); + + MWMechanics::CreatureStats& stats = MWWorld::Class::get (actor).getCreatureStats (actor); + + return stats.getAttribute (faction.mData.mAttribute1).getBase()>=faction.mData.mRankData[rank].mAttribute1 && + stats.getAttribute (faction.mData.mAttribute2).getBase()>=faction.mData.mRankData[rank].mAttribute2; +} + +bool MWDialogue::Filter::hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, + const std::string& factionId, int rank) const +{ + if (rank<0 || rank>=10) + throw std::runtime_error ("rank index out of range"); + + MWMechanics::NpcStats& stats = MWWorld::Class::get (actor).getNpcStats (actor); + + const ESM::Faction& faction = + *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); + + return stats.getFactionReputation (factionId)>=faction.mData.mRankData[rank].mFactReaction; +} + +MWDialogue::Filter::Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer) +: mActor (actor), mChoice (choice), mTalkedToPlayer (talkedToPlayer) +{} + +bool MWDialogue::Filter::operator() (const ESM::DialInfo& info) const +{ + return testActor (info) && testPlayer (info) && testSelectStructs (info); +} + +const ESM::DialInfo *MWDialogue::Filter::search (const ESM::Dialogue& dialogue) const +{ + for (std::vector::const_iterator iter = dialogue.mInfo.begin(); + iter!=dialogue.mInfo.end(); ++iter) + if ((*this) (*iter)) + return &*iter; + + return 0; +} + diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp new file mode 100644 index 000000000..7c8f1116f --- /dev/null +++ b/apps/openmw/mwdialogue/filter.hpp @@ -0,0 +1,58 @@ +#ifndef GAME_MWDIALOGUE_FILTER_H +#define GAME_MWDIALOGUE_FILTER_H + +#include "../mwworld/ptr.hpp" + +namespace ESM +{ + struct DialInfo; + struct Dialogue; +} + +namespace MWDialogue +{ + class SelectWrapper; + + class Filter + { + MWWorld::Ptr mActor; + int mChoice; + bool mTalkedToPlayer; + + bool testActor (const ESM::DialInfo& info) const; + ///< Is this the right actor for this \a info? + + bool testPlayer (const ESM::DialInfo& info) const; + ///< Do the player and the cell the player is currently in match \a info? + + bool testSelectStructs (const ESM::DialInfo& info) const; + ///< Are all select structs matching? + + bool testSelectStruct (const SelectWrapper& select) const; + + bool testSelectStructNumeric (const SelectWrapper& select) const; + + int getSelectStructInteger (const SelectWrapper& select) const; + + bool getSelectStructBoolean (const SelectWrapper& select) const; + + int getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const; + + bool hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, const std::string& factionId, + int rank) const; + + bool hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, const std::string& factionId, + int rank) const; + + public: + + Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer); + + bool operator() (const ESM::DialInfo& info) const; + ///< \return does the dialogue match? + + const ESM::DialInfo *search (const ESM::Dialogue& dialogue) const; + }; +} + +#endif diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp new file mode 100644 index 000000000..ae5b8c582 --- /dev/null +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -0,0 +1,294 @@ + +#include "selectwrapper.hpp" + +#include + +#include +#include +#include + +namespace +{ + std::string toLower (const std::string& name) + { + std::string lowerCase; + + std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), + (int(*)(int)) std::tolower); + + return lowerCase; + } + + template + bool selectCompareImp (char comp, T1 value1, T2 value2) + { + switch (comp) + { + case '0': return value1==value2; + case '1': return value1!=value2; + case '2': return value1>value2; + case '3': return value1>=value2; + case '4': return value1 + bool selectCompareImp (const ESM::DialInfo::SelectStruct& select, T value1) + { + if (select.mType==ESM::VT_Short || select.mType==ESM::VT_Int || + select.mType==ESM::VT_Long) + { + return selectCompareImp (select.mSelectRule[4], value1, select.mI); + } + else if (select.mType==ESM::VT_Float) + { + return selectCompareImp (select.mSelectRule[4], value1, select.mF); + } + else + throw std::runtime_error ( + "unsupported variable type in dialogue info select"); + } +} + +MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const +{ + int index = 0; + + std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index; + + switch (index) + { + // 0, 1 + case 2: return Function_RankRequirement; + // 3 + case 4: return Function_HealthPercent; + case 5: return Function_PCReputation; + case 6: return Function_PcLevel; + case 7: return Function_PcHealthPercent; + case 8: case 9: return Function_PcDynamicStat; + case 10: return Function_PcAttribute; + case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: + case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: + case 31: case 32: case 33: case 34: case 35: case 36: case 37: return Function_PcSkill; + case 38: return Function_PcGender; + case 39: return Function_PcExpelled; + case 40: return Function_PcCommonDisease; + case 41: return Function_PcBlightDisease; + case 42: return Function_PcClothingModifier; + case 43: return Function_PcCrimeLevel; + case 44: return Function_SameGender; + case 45: return Function_SameRace; + case 46: return Function_SameFaction; + // 47-49 + case 50: return Function_Choice; + case 51: case 52: case 53: case 54: case 55: case 56: case 57: return Function_PcAttribute; + case 58: return Function_PcCorprus; + // 59 + case 60: return Function_PcVampire; + case 61: return Function_Level; + // 62 + case 63: return Function_TalkedToPc; + case 64: return Function_PcDynamicStat; + // 65 + case 66: return Function_FriendlyHit; + case 67: case 68: case 69: case 70: return Function_AiSetting; + // 71 + } + + return Function_False; +} + +MWDialogue::SelectWrapper::SelectWrapper (const ESM::DialInfo::SelectStruct& select) : mSelect (select) {} + +MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const +{ + char type = mSelect.mSelectRule[1]; + + switch (type) + { + case '1': return decodeFunction(); + case '2': return Function_Global; + case '3': return Function_Local; + case '4': return Function_Journal; + case '5': return Function_Item; + case '6': return Function_Dead; + case '7': return Function_Id; + case '8': return Function_Faction; + case '9': return Function_Class; + case 'A': return Function_Race; + case 'B': return Function_Cell; + case 'C': return Function_Local; + } + + return Function_None; +} + +int MWDialogue::SelectWrapper::getArgument() const +{ + if (mSelect.mSelectRule[1]!='1') + return 0; + + int index = 0; + + std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index; + + switch (index) + { + // AI settings + case 67: return 1; + case 68: return 0; + case 69: return 3; + case 70: return 2; + + // attributes + case 10: return 0; + case 51: return 1; + case 52: return 2; + case 53: return 3; + case 54: return 4; + case 55: return 5; + case 56: return 6; + case 57: return 7; + + // skills + case 11: return 0; + case 12: return 1; + case 13: return 2; + case 14: return 3; + case 15: return 4; + case 16: return 5; + case 17: return 6; + case 18: return 7; + case 19: return 8; + case 20: return 9; + case 21: return 10; + case 22: return 11; + case 23: return 12; + case 24: return 13; + case 25: return 14; + case 26: return 15; + case 27: return 16; + case 28: return 17; + case 29: return 18; + case 30: return 19; + case 31: return 20; + case 32: return 21; + case 33: return 22; + case 34: return 23; + case 35: return 24; + case 36: return 25; + case 37: return 26; + + // dynamic stats + case 8: return 1; + case 9: return 2; + case 64: return 0; + } + + return 0; +} + +MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const +{ + static const Function integerFunctions[] = + { + Function_Journal, Function_Item, Function_Dead, + Function_Choice, + Function_AiSetting, + Function_PcAttribute, Function_PcSkill, + Function_FriendlyHit, + Function_PcLevel, Function_PcGender, Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_RankRequirement, + Function_Level, Function_PCReputation, + Function_None // end marker + }; + + static const Function numericFunctions[] = + { + Function_Global, Function_Local, + Function_PcDynamicStat, Function_PcHealthPercent, + Function_HealthPercent, + Function_None // end marker + }; + + static const Function booleanFunctions[] = + { + Function_False, + Function_Id, Function_Faction, Function_Class, Function_Race, Function_Cell, + Function_SameGender, Function_SameRace, Function_SameFaction, + Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus, + Function_PcExpelled, + Function_PcVampire, Function_TalkedToPc, + Function_None // end marker + }; + + Function function = getFunction(); + + for (int i=0; integerFunctions[i]!=Function_None; ++i) + if (integerFunctions[i]==function) + return Type_Integer; + + for (int i=0; numericFunctions[i]!=Function_None; ++i) + if (numericFunctions[i]==function) + return Type_Numeric; + + for (int i=0; booleanFunctions[i]!=Function_None; ++i) + if (booleanFunctions[i]==function) + return Type_Boolean; + + return Type_None; +} + +bool MWDialogue::SelectWrapper::isInverted() const +{ + char type = mSelect.mSelectRule[1]; + + return type=='7' || type=='8' || type=='9' || type=='A' || type=='B' || type=='C'; +} + +bool MWDialogue::SelectWrapper::isNpcOnly() const +{ + static const Function functions[] = + { + Function_Faction, SelectWrapper::Function_Class, SelectWrapper::Function_Race, + Function_SameGender, Function_SameRace, Function_SameFaction, + Function_PcSkill, + Function_PcExpelled, + Function_PcVampire, + Function_PcCrimeLevel, + Function_RankRequirement, + Function_None // end marker + }; + + Function function = getFunction(); + + for (int i=0; functions[i]!=Function_None; ++i) + if (functions[i]==function) + return true; + + return false; +} + +bool MWDialogue::SelectWrapper::selectCompare (int value) const +{ + return selectCompareImp (mSelect, value)!=isInverted(); // logic XOR +} + +bool MWDialogue::SelectWrapper::selectCompare (float value) const +{ + return selectCompareImp (mSelect, value)!=isInverted(); // logic XOR +} + +bool MWDialogue::SelectWrapper::selectCompare (bool value) const +{ + return selectCompareImp (mSelect, static_cast (value))!=isInverted(); // logic XOR +} + +std::string MWDialogue::SelectWrapper::getName() const +{ + return toLower (mSelect.mSelectRule.substr (5)); +} diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp new file mode 100644 index 000000000..15cd5bfff --- /dev/null +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -0,0 +1,80 @@ +#ifndef GAME_MWDIALOGUE_SELECTWRAPPER_H +#define GAME_MWDIALOGUE_SELECTWRAPPER_H + +#include + +namespace MWDialogue +{ + class SelectWrapper + { + const ESM::DialInfo::SelectStruct& mSelect; + + public: + + enum Function + { + Function_None, Function_False, + Function_Journal, + Function_Item, + Function_Dead, + Function_Id, + Function_Faction, + Function_Class, + Function_Race, + Function_Cell, + Function_Local, + Function_Global, + Function_SameGender, Function_SameRace, Function_SameFaction, + Function_Choice, + Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus, + Function_AiSetting, + Function_PcAttribute, Function_PcSkill, + Function_PcExpelled, + Function_PcVampire, + Function_FriendlyHit, + Function_TalkedToPc, + Function_PcLevel, Function_PcHealthPercent, Function_PcDynamicStat, + Function_PcGender, Function_PcClothingModifier, Function_PcCrimeLevel, + Function_RankRequirement, + Function_HealthPercent, Function_Level, Function_PCReputation + }; + + enum Type + { + Type_None, + Type_Integer, + Type_Numeric, + Type_Boolean + }; + + private: + + Function decodeFunction() const; + + public: + + SelectWrapper (const ESM::DialInfo::SelectStruct& select); + + Function getFunction() const; + + int getArgument() const; + + Type getType() const; + + bool isInverted() const; + + bool isNpcOnly() const; + ///< \attention Do not call any of the select functions for this select struct! + + bool selectCompare (int value) const; + + bool selectCompare (float value) const; + + bool selectCompare (bool value) const; + + std::string getName() const; + ///< Return case-smashed name. + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index e94adf458..9ee7ca7c2 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -10,8 +10,10 @@ namespace MWMechanics { CreatureStats::CreatureStats() - : mLevel (0), mHello (0), mFight (0), mFlee (0), mAlarm (0), mLevelHealthBonus(0.f), mDead (false) + : mLevel (0), mLevelHealthBonus(0.f), mDead (false), mFriendlyHits (0), mTalkedTo (false) { + for (int i=0; i<4; ++i) + mAiSettings[i] = 0; } void CreatureStats::increaseLevelHealthBonus (float value) @@ -90,27 +92,13 @@ namespace MWMechanics { return mLevel; } + + int CreatureStats::getAiSetting (int index) const + { + assert (index>=0 && index<4); + return mAiSettings[index]; + } - int CreatureStats::getHello() const - { - return mHello; - } - - int CreatureStats::getFight() const - { - return mFight; - } - - int CreatureStats::getFlee() const - { - return mFlee; - } - - int CreatureStats::getAlarm() const - { - return mAlarm; - } - Stat &CreatureStats::getAttribute(int index) { if (index < 0 || index > 7) { @@ -196,25 +184,11 @@ namespace MWMechanics mMagicEffects = effects; } - void CreatureStats::setHello(int value) + void CreatureStats::setAiSetting (int index, int value) { - mHello = value; + assert (index>=0 && index<4); + mAiSettings[index] = value; } - - void CreatureStats::setFight(int value) - { - mFight = value; - } - - void CreatureStats::setFlee(int value) - { - mFlee = value; - } - - void CreatureStats::setAlarm(int value) - { - mAlarm = value; - } bool CreatureStats::isDead() const { @@ -242,4 +216,24 @@ namespace MWMechanics { return mSpells.hasBlightDisease(); } + + int CreatureStats::getFriendlyHits() const + { + return mFriendlyHits; + } + + void CreatureStats::friendlyHit() + { + ++mFriendlyHits; + } + + bool CreatureStats::hasTalkedToPlayer() const + { + return mTalkedTo; + } + + void CreatureStats::talkedToPlayer() + { + mTalkedTo = true; + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index cdeee6853..937cb61cc 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -24,13 +24,12 @@ namespace MWMechanics Spells mSpells; ActiveSpells mActiveSpells; MagicEffects mMagicEffects; - int mHello; - int mFight; - int mFlee; - int mAlarm; + int mAiSettings[4]; AiSequence mAiSequence; float mLevelHealthBonus; - bool mDead; + bool mDead; + int mFriendlyHits; + bool mTalkedTo; public: CreatureStats(); @@ -53,13 +52,8 @@ namespace MWMechanics int getLevel() const; - int getHello() const; - - int getFight() const; - - int getFlee() const; - - int getAlarm() const; + int getAiSetting (int index) const; + ///< 0: hello, 1 fight, 2 flee, 3 alarm Stat & getAttribute(int index); @@ -87,14 +81,9 @@ namespace MWMechanics void setLevel(int level); - void setHello(int value); - - void setFight(int value); - - void setFlee(int value); - - void setAlarm(int value); - + void setAiSetting (int index, int value); + ///< 0: hello, 1 fight, 2 flee, 3 alarm + const AiSequence& getAiSequence() const; AiSequence& getAiSequence(); @@ -112,7 +101,18 @@ namespace MWMechanics bool hasCommonDisease() const; - bool hasBlightDisease() const; + bool hasBlightDisease() const; + + int getFriendlyHits() const; + ///< Number of friendly hits received. + + void friendlyHit(); + ///< Increase number of friendly hits by one. + + bool hasTalkedToPlayer() const; + ///< Has this creature talked with the player before? + + void talkedToPlayer(); }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2d59b87f8..1b6e4b9f3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -572,8 +572,8 @@ namespace MWMechanics { float s = int(r * fPerDieRollMult * fPerTempMult); - npcStats.setFlee ( std::max(0, std::min(100, npcStats.getFlee() + int(std::max(iPerMinChange, s))))); - npcStats.setFight ( std::max(0, std::min(100, npcStats.getFight() + int(std::min(-iPerMinChange, -s))))); + npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + int(std::max(iPerMinChange, s))))); + npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + int(std::min(-iPerMinChange, -s))))); } float c = -std::abs(int(r * fPerDieRollMult)); @@ -607,8 +607,8 @@ namespace MWMechanics { float s = c * fPerDieRollMult * fPerTempMult; - npcStats.setFlee ( std::max(0, std::min(100, npcStats.getFlee() + std::min(-int(iPerMinChange), int(-s))))); - npcStats.setFight ( std::max(0, std::min(100, npcStats.getFight() + std::max(int(iPerMinChange), int(s))))); + npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + std::min(-int(iPerMinChange), int(-s))))); + npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + std::max(int(iPerMinChange), int(s))))); } x = int(-c * fPerDieRollMult); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index f37a45b49..26415b631 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -3,12 +3,15 @@ #include #include +#include +#include #include #include #include #include +#include #include "../mwworld/esmstore.hpp" @@ -19,8 +22,7 @@ MWMechanics::NpcStats::NpcStats() : mMovementFlags (0), mDrawState (DrawState_Nothing), mBounty (0) -, mLevelProgress(0), mDisposition(0), mReputation(0) - +, mLevelProgress(0), mDisposition(0), mVampire (0), mReputation(0) { mSkillIncreases.resize (ESM::Attribute::Length); for (int i=0; i& MWMechanics::NpcStats::getFactionRanks() return mFactionRank; } +std::set& MWMechanics::NpcStats::getExpelled() +{ + return mExpelled; +} + const std::map& MWMechanics::NpcStats::getFactionRanks() const { return mFactionRank; } +bool MWMechanics::NpcStats::isSameFaction (const NpcStats& npcStats) const +{ + for (std::map::const_iterator iter (mFactionRank.begin()); iter!=mFactionRank.end(); + ++iter) + if (npcStats.mFactionRank.find (iter->first)!=npcStats.mFactionRank.end()) + return true; + + return false; +} + float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& class_, int usageType, int level) const { @@ -260,6 +277,31 @@ void MWMechanics::NpcStats::setBounty (int bounty) mBounty = bounty; } +int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const +{ + std::map::const_iterator iter = mFactionReputation.find (faction); + + if (iter==mFactionReputation.end()) + return 0; + + return iter->second; +} + +void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, int value) +{ + mFactionReputation[faction] = value; +} + +bool MWMechanics::NpcStats::isVampire() const +{ + return mVampire; +} + +void MWMechanics::NpcStats::setVampire (bool set) +{ + mVampire = set; +} + int MWMechanics::NpcStats::getReputation() const { return mReputation; @@ -269,3 +311,29 @@ void MWMechanics::NpcStats::setReputation(int reputation) { mReputation = reputation; } + +bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int rank) const +{ + if (rank<0 || rank>=10) + throw std::runtime_error ("rank index out of range"); + + const ESM::Faction& faction = + *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); + + std::vector skills; + + for (int i=0; i<6; ++i) + skills.push_back (static_cast (getSkill (faction.mData.mSkillID[i]).getModified())); + + std::sort (skills.begin(), skills.end()); + + std::vector::const_reverse_iterator iter = skills.rbegin(); + + const ESM::RankData& rankData = faction.mData.mRankData[rank]; + + if (*iter=rankData.mSkill2; +} + diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index b6abbd342..46af216d9 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -47,6 +47,9 @@ namespace MWMechanics unsigned int mMovementFlags; Stat mSkill[27]; int mBounty; + std::set mExpelled; + std::map mFactionReputation; + bool mVampire; int mReputation; int mLevelProgress; // 0-10 @@ -80,6 +83,11 @@ namespace MWMechanics Stat& getSkill (int index); std::map& getFactionRanks(); + + std::set& getExpelled(); + + bool isSameFaction (const NpcStats& npcStats) const; + ///< Do *this and \a npcStats share a faction? const std::map& getFactionRanks() const; @@ -107,6 +115,16 @@ namespace MWMechanics int getBounty() const; void setBounty (int bounty); + + int getFactionReputation (const std::string& faction) const; + + void setFactionReputation (const std::string& faction, int value); + + bool isVampire() const; + + void setVampire (bool set); + + bool hasSkillsForRank (const std::string& factionId, int rank) const; }; } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 9d70c28bd..787962ad1 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setHello(value); + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (0, value); } }; @@ -146,7 +146,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setFight(value); + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (1, value); } }; @@ -162,7 +162,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setFlee(value); + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (2, value); } }; @@ -178,7 +178,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAlarm(value); + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (3, value); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 0c4c6d144..4fbb81b37 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -28,6 +28,21 @@ #include "interpretercontext.hpp" #include "ref.hpp" +namespace +{ + std::string getDialogueActorFaction() + { + MWWorld::Ptr actor = MWBase::Environment::get().getDialogueManager()->getActor(); + + MWMechanics::NpcStats stats = MWWorld::Class::get (actor).getNpcStats (actor); + + if (stats.getFactionRanks().empty()) + throw std::runtime_error ( + "failed to determine dialogue actors faction (because actor is factionless)"); + + return stats.getFactionRanks().begin()->first; + } +} namespace MWScript { @@ -450,7 +465,7 @@ namespace MWScript if(arg0==0) { - factionID = MWBase::Environment::get().getDialogueManager()->getFaction(); + factionID = getDialogueActorFaction(); } else { @@ -479,7 +494,7 @@ namespace MWScript if(arg0==0) { - factionID = MWBase::Environment::get().getDialogueManager()->getFaction(); + factionID = getDialogueActorFaction(); } else { @@ -512,7 +527,7 @@ namespace MWScript if(arg0==0) { - factionID = MWBase::Environment::get().getDialogueManager()->getFaction(); + factionID = getDialogueActorFaction(); } else { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index f04fe862e..f1decb9c6 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -32,10 +32,10 @@ struct DialInfo { int mUnknown1; int mDisposition; - char mRank; // Rank of NPC - char mGender; // See Gender enum - char mPCrank; // Player rank - char mUnknown2; + signed char mRank; // Rank of NPC + signed char mGender; // See Gender enum + signed char mPCrank; // Player rank + signed char mUnknown2; }; // 12 bytes DATAstruct mData;