mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-01 07:45:36 +00:00
Merge branch 'dialogue_result_scripts' into 'master'
Reimplement --script-all-dialogue to be more useful to modders See merge request OpenMW/openmw!2494
This commit is contained in:
commit
7669a43495
4 changed files with 177 additions and 95 deletions
|
@ -814,8 +814,7 @@ void OMW::Engine::prepareEngine()
|
|||
{
|
||||
std::pair<int, int> result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode);
|
||||
if (result.first)
|
||||
Log(Debug::Info) << "compiled " << result.second << " of " << result.first
|
||||
<< " dialogue script/actor combinations a("
|
||||
Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " dialogue scripts ("
|
||||
<< 100 * static_cast<double>(result.second) / result.first << "%)";
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,58 @@
|
|||
|
||||
#include "selectwrapper.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor)
|
||||
{
|
||||
if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId)
|
||||
return !Misc::StringUtils::ciEqual(actor.getCellRef().getRefId(), select.getName());
|
||||
if (actor.getClass().isNpc())
|
||||
{
|
||||
if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotFaction)
|
||||
return !Misc::StringUtils::ciEqual(actor.getClass().getPrimaryFaction(actor), select.getName());
|
||||
else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotClass)
|
||||
return !Misc::StringUtils::ciEqual(actor.get<ESM::NPC>()->mBase->mClass, select.getName());
|
||||
else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotRace)
|
||||
return !Misc::StringUtils::ciEqual(actor.get<ESM::NPC>()->mBase->mRace, select.getName());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool matchesStaticFilters(const ESM::DialInfo& info, const MWWorld::Ptr& actor)
|
||||
{
|
||||
for (const ESM::DialInfo::SelectStruct& select : info.mSelects)
|
||||
{
|
||||
MWDialogue::SelectWrapper wrapper = select;
|
||||
if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean)
|
||||
{
|
||||
if (!wrapper.selectCompare(matchesStaticFilters(wrapper, actor)))
|
||||
return false;
|
||||
}
|
||||
else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Inverted)
|
||||
{
|
||||
if (!matchesStaticFilters(wrapper, actor))
|
||||
return false;
|
||||
}
|
||||
else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Numeric)
|
||||
{
|
||||
if (wrapper.getFunction() == MWDialogue::SelectWrapper::Function_Local)
|
||||
{
|
||||
std::string_view scriptName = actor.getClass().getScript(actor);
|
||||
if (scriptName.empty())
|
||||
return false;
|
||||
const Compiler::Locals& localDefs
|
||||
= MWBase::Environment::get().getScriptManager()->getLocals(scriptName);
|
||||
char type = localDefs.getType(wrapper.getName());
|
||||
if (type == ' ')
|
||||
return false; // script does not have a variable of this name.
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MWDialogue::Filter::testActor(const ESM::DialInfo& info) const
|
||||
{
|
||||
bool isCreature = (mActor.getType() != ESM::NPC::sRecordId);
|
||||
|
@ -144,9 +196,7 @@ bool MWDialogue::Filter::testPlayer(const ESM::DialInfo& info) const
|
|||
{
|
||||
// supports partial matches, just like getPcCell
|
||||
std::string_view playerCell = MWBase::Environment::get().getWorld()->getCellName(player.getCell());
|
||||
bool match = playerCell.length() >= info.mCell.length()
|
||||
&& Misc::StringUtils::ciEqual(playerCell.substr(0, info.mCell.length()), info.mCell);
|
||||
if (!match)
|
||||
if (!Misc::StringUtils::ciStartsWith(playerCell, info.mCell))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -183,7 +233,7 @@ bool MWDialogue::Filter::testFunctionLocal(const MWDialogue::SelectWrapper& sele
|
|||
if (scriptName.empty())
|
||||
return false; // no script
|
||||
|
||||
std::string name = Misc::StringUtils::lowerCase(select.getName());
|
||||
std::string name = select.getName();
|
||||
|
||||
const Compiler::Locals& localDefs = MWBase::Environment::get().getScriptManager()->getLocals(scriptName);
|
||||
|
||||
|
@ -502,8 +552,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
|
|||
case SelectWrapper::Function_NotCell:
|
||||
{
|
||||
std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell());
|
||||
return !(actorCell.length() >= select.getName().length()
|
||||
&& Misc::StringUtils::ciEqual(actorCell.substr(0, select.getName().length()), select.getName()));
|
||||
return !Misc::StringUtils::ciStartsWith(actorCell, select.getName());
|
||||
}
|
||||
case SelectWrapper::Function_SameGender:
|
||||
|
||||
|
@ -640,16 +689,9 @@ const ESM::DialInfo* MWDialogue::Filter::search(const ESM::Dialogue& dialogue, c
|
|||
return suitableInfos[0];
|
||||
}
|
||||
|
||||
std::vector<const ESM::DialInfo*> MWDialogue::Filter::listAll(const ESM::Dialogue& dialogue) const
|
||||
bool MWDialogue::Filter::couldPotentiallyMatch(const ESM::DialInfo& info) const
|
||||
{
|
||||
std::vector<const ESM::DialInfo*> infos;
|
||||
for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter != dialogue.mInfo.end();
|
||||
++iter)
|
||||
{
|
||||
if (testActor(*iter))
|
||||
infos.push_back(&*iter);
|
||||
}
|
||||
return infos;
|
||||
return testActor(info) && matchesStaticFilters(info, mActor);
|
||||
}
|
||||
|
||||
std::vector<const ESM::DialInfo*> MWDialogue::Filter::list(
|
||||
|
|
|
@ -58,9 +58,9 @@ namespace MWDialogue
|
|||
///< List all infos that could be used on the given actor, using the current runtime state of the actor.
|
||||
/// \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue.
|
||||
|
||||
std::vector<const ESM::DialInfo*> listAll(const ESM::Dialogue& dialogue) const;
|
||||
///< List all infos that could possibly be used on the given actor, ignoring runtime state filters and ignoring
|
||||
///< player filters.
|
||||
bool couldPotentiallyMatch(const ESM::DialInfo& info) const;
|
||||
///< Check if this INFO could potentially be said by the given actor, ignoring runtime state filters and
|
||||
///< ignoring player filters.
|
||||
|
||||
const ESM::DialInfo* search(const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const;
|
||||
///< Get a matching response for the requested dialogue.
|
||||
|
|
|
@ -21,81 +21,63 @@
|
|||
|
||||
#include "filter.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void test(
|
||||
const MWWorld::Ptr& actor, int& compiled, int& total, const Compiler::Extensions* extensions, int warningsMode)
|
||||
bool test(const MWWorld::Ptr& actor, const ESM::DialInfo& info, int& compiled, int& total,
|
||||
const Compiler::Extensions* extensions, const MWScript::CompilerContext& context,
|
||||
Compiler::StreamErrorHandler& errorHandler)
|
||||
{
|
||||
MWDialogue::Filter filter(actor, 0, false);
|
||||
|
||||
MWScript::CompilerContext compilerContext(MWScript::CompilerContext::Type_Dialogue);
|
||||
compilerContext.setExtensions(extensions);
|
||||
Compiler::StreamErrorHandler errorHandler;
|
||||
errorHandler.setWarningsMode(warningsMode);
|
||||
|
||||
const MWWorld::Store<ESM::Dialogue>& dialogues
|
||||
= MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
|
||||
for (MWWorld::Store<ESM::Dialogue>::iterator it = dialogues.begin(); it != dialogues.end(); ++it)
|
||||
bool success = true;
|
||||
++total;
|
||||
try
|
||||
{
|
||||
std::vector<const ESM::DialInfo*> infos = filter.listAll(*it);
|
||||
std::istringstream input(info.mResultScript + "\n");
|
||||
|
||||
for (std::vector<const ESM::DialInfo*>::iterator iter = infos.begin(); iter != infos.end(); ++iter)
|
||||
Compiler::Scanner scanner(errorHandler, input, extensions);
|
||||
|
||||
Compiler::Locals locals;
|
||||
|
||||
std::string_view actorScript = actor.getClass().getScript(actor);
|
||||
if (!actorScript.empty())
|
||||
{
|
||||
const ESM::DialInfo* info = *iter;
|
||||
if (!info->mResultScript.empty())
|
||||
{
|
||||
bool success = true;
|
||||
++total;
|
||||
try
|
||||
{
|
||||
errorHandler.reset();
|
||||
|
||||
std::istringstream input(info->mResultScript + "\n");
|
||||
|
||||
Compiler::Scanner scanner(errorHandler, input, extensions);
|
||||
|
||||
Compiler::Locals locals;
|
||||
|
||||
std::string_view actorScript = actor.getClass().getScript(actor);
|
||||
|
||||
if (!actorScript.empty())
|
||||
{
|
||||
// grab local variables from actor's script, if available.
|
||||
locals = MWBase::Environment::get().getScriptManager()->getLocals(actorScript);
|
||||
}
|
||||
|
||||
Compiler::ScriptParser parser(errorHandler, compilerContext, locals, false);
|
||||
|
||||
scanner.scan(parser);
|
||||
|
||||
if (!errorHandler.isGood())
|
||||
success = false;
|
||||
|
||||
++compiled;
|
||||
}
|
||||
catch (const Compiler::SourceException& /* error */)
|
||||
{
|
||||
// error has already been reported via error handler
|
||||
success = false;
|
||||
}
|
||||
catch (const std::exception& error)
|
||||
{
|
||||
Log(Debug::Error)
|
||||
<< std::string("Dialogue error: An exception has been thrown: ") + error.what();
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Log(Debug::Error) << "Error: compiling failed (dialogue script): \n"
|
||||
<< info->mResultScript << "\n";
|
||||
}
|
||||
}
|
||||
// grab local variables from actor's script, if available.
|
||||
locals = MWBase::Environment::get().getScriptManager()->getLocals(actorScript);
|
||||
}
|
||||
|
||||
Compiler::ScriptParser parser(errorHandler, context, locals, false);
|
||||
|
||||
scanner.scan(parser);
|
||||
|
||||
if (!errorHandler.isGood())
|
||||
success = false;
|
||||
|
||||
++compiled;
|
||||
}
|
||||
catch (const Compiler::SourceException&)
|
||||
{
|
||||
// error has already been reported via error handler
|
||||
success = false;
|
||||
}
|
||||
catch (const std::exception& error)
|
||||
{
|
||||
Log(Debug::Error) << "Dialogue error: An exception has been thrown: " << error.what();
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool superficiallyMatches(const MWWorld::Ptr& ptr, const ESM::DialInfo& info)
|
||||
{
|
||||
if (ptr.isEmpty())
|
||||
return false;
|
||||
MWDialogue::Filter filter(ptr, 0, false);
|
||||
return filter.couldPotentiallyMatch(info);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -109,20 +91,79 @@ namespace MWDialogue
|
|||
std::pair<int, int> compileAll(const Compiler::Extensions* extensions, int warningsMode)
|
||||
{
|
||||
int compiled = 0, total = 0;
|
||||
const MWWorld::Store<ESM::NPC>& npcs = MWBase::Environment::get().getWorld()->getStore().get<ESM::NPC>();
|
||||
for (MWWorld::Store<ESM::NPC>::iterator it = npcs.begin(); it != npcs.end(); ++it)
|
||||
const auto& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
MWScript::CompilerContext compilerContext(MWScript::CompilerContext::Type_Dialogue);
|
||||
compilerContext.setExtensions(extensions);
|
||||
Compiler::StreamErrorHandler errorHandler;
|
||||
errorHandler.setWarningsMode(warningsMode);
|
||||
|
||||
std::unique_ptr<MWWorld::ManualRef> ref;
|
||||
for (const ESM::Dialogue& topic : store.get<ESM::Dialogue>())
|
||||
{
|
||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId);
|
||||
test(ref.getPtr(), compiled, total, extensions, warningsMode);
|
||||
MWWorld::Ptr ptr;
|
||||
for (const ESM::DialInfo& info : topic.mInfo)
|
||||
{
|
||||
if (info.mResultScript.empty())
|
||||
continue;
|
||||
if (!info.mActor.empty())
|
||||
{
|
||||
// We know it can only ever be this actor
|
||||
try
|
||||
{
|
||||
ref = std::make_unique<MWWorld::ManualRef>(store, info.mActor);
|
||||
ptr = ref->getPtr();
|
||||
}
|
||||
catch (const std::logic_error&)
|
||||
{
|
||||
// Too bad it doesn't exist
|
||||
ptr = {};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to find a matching actor
|
||||
if (!superficiallyMatches(ptr, info))
|
||||
{
|
||||
ptr = {};
|
||||
bool found = false;
|
||||
for (const auto& npc : store.get<ESM::NPC>())
|
||||
{
|
||||
ref = std::make_unique<MWWorld::ManualRef>(store, npc.mId);
|
||||
if (superficiallyMatches(ref->getPtr(), info))
|
||||
{
|
||||
ptr = ref->getPtr();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
for (const auto& creature : store.get<ESM::Creature>())
|
||||
{
|
||||
ref = std::make_unique<MWWorld::ManualRef>(store, creature.mId);
|
||||
if (superficiallyMatches(ref->getPtr(), info))
|
||||
{
|
||||
ptr = ref->getPtr();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ptr.isEmpty())
|
||||
Log(Debug::Error) << "Could not find an actor to test " << info.mId << " in " << topic.mId;
|
||||
else
|
||||
{
|
||||
errorHandler.reset();
|
||||
errorHandler.setContext(info.mId + " in " + topic.mId);
|
||||
if (!test(ptr, info, compiled, total, extensions, compilerContext, errorHandler))
|
||||
Log(Debug::Error) << "Test failed for " << info.mId << " in " << topic.mId << '\n'
|
||||
<< info.mResultScript;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MWWorld::Store<ESM::Creature>& creatures
|
||||
= MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>();
|
||||
for (MWWorld::Store<ESM::Creature>::iterator it = creatures.begin(); it != creatures.end(); ++it)
|
||||
{
|
||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId);
|
||||
test(ref.getPtr(), compiled, total, extensions, warningsMode);
|
||||
}
|
||||
return std::make_pair(total, compiled);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue