diff --git a/.gitignore b/.gitignore index ef34e54c..d3c62ffd 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ dist/ *~ .DS_Store thumbs.db +gmon.out # exclude unused and scratch pad files .trash diff --git a/api/lua/definition/poptracker.lua b/api/lua/definition/poptracker.lua index 9a101f9d..c83ba6cd 100644 --- a/api/lua/definition/poptracker.lua +++ b/api/lua/definition/poptracker.lua @@ -83,6 +83,12 @@ function Tracker:UiHint(name, value) end ---@type boolean Tracker.BulkUpdate = false +---Allow to evaluate logic rules fewer times than items are updated. +---Only available in PopTracker, since 0.27.1. +---Use as: `if Tracker.AllowDeferredLogicUpdate == false then Tracker.AllowDeferredLogicUpdate = true end` +---@type boolean +Tracker.AllowDeferredLogicUpdate = false + ---- ScriptHost ---- diff --git a/doc/PACKS.md b/doc/PACKS.md index 5da5885c..1c5e59e9 100644 --- a/doc/PACKS.md +++ b/doc/PACKS.md @@ -110,6 +110,7 @@ The following interfaces are provided: * `mixed :FindObjectForCode(string)`: returns items for `code` or location section for `@location/section` * `void :UiHint(name, value)`: sends a hint to the Ui, see [Ui Hints](#ui-hints). Only available in PopTracker, since 0.11.0 * `bool .BulkUpdate`: can be set to true from Lua to pause running logic rules. +* `bool .AllowDeferredLogicUpdate`: can be set to true from Lua to allow evaluating logic rules fewer times than items are updated. ### global ScriptHost diff --git a/src/core/baseitem.h b/src/core/baseitem.h index 2776d29b..316eb698 100644 --- a/src/core/baseitem.h +++ b/src/core/baseitem.h @@ -64,7 +64,7 @@ class BaseItem { // TODO: move stuff over to JsonItem; TODO: make some stuff pur std::string _id; std::string _name; Type _type = Type::NONE; - std::list _codes; + std::vector _codes; bool _capturable = false; bool _loop = false; bool _allowDisabled = false; @@ -122,9 +122,10 @@ class BaseItem { // TODO: move stuff over to JsonItem; TODO: make some stuff pur } - virtual int providesCode(const std::string code) const { // FIXME: make this pure? - if (_count && canProvideCode(code)) return _count; - return (_stage1 && canProvideCode(code)); + virtual int providesCode(const std::string& code) const { // FIXME: make this pure? + if (_count && canProvideCode(code)) + return _count; + return _stage1 && canProvideCode(code); } virtual bool canProvideCode(const std::string& code) const { // FIXME: make this pure? diff --git a/src/core/jsonitem.cpp b/src/core/jsonitem.cpp index fb9a6e7d..b976e91c 100644 --- a/src/core/jsonitem.cpp +++ b/src/core/jsonitem.cpp @@ -24,7 +24,7 @@ std::string JsonItem::getCodesString() const { return s; } -const std::list& JsonItem::getCodes(int stage) const { +const std::vector& JsonItem::getCodes(int stage) const { if (_type == Type::TOGGLE) return _codes; if (stage>=0 && (size_t)stage<_stages.size()) return _stages[stage].getCodes(); return _codes; @@ -117,7 +117,15 @@ JsonItem JsonItem::FromJSON(json& j) item._stage2 = std::max(0,std::min(to_int(j["initial_stage_idx"],0), (int)item._stages.size()-1)); item._count = std::max(item._minCount, std::min(to_int(j["initial_quantity"],0), item._maxCount)); if (item._type == Type::CONSUMABLE && item._count > 0) item._stage1=1; - + +#ifdef JSONITEM_CI_QUIRK + for (const auto& code : item._codes) + item._ciAllCodes.emplace(toLower(code)); + for (const auto& stage : item._stages) + for (const auto& code : stage.getCodes()) + item._ciAllCodes.emplace(toLower(code)); +#endif + return item; } diff --git a/src/core/jsonitem.h b/src/core/jsonitem.h index 76a02697..1494184f 100644 --- a/src/core/jsonitem.h +++ b/src/core/jsonitem.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include #ifdef _MSC_VER #define strncasecmp _strnicmp #define strcasecmp _stricmp @@ -24,7 +26,7 @@ class JsonItem final : public LuaInterface, public BaseItem { class Stage final { protected: - std::list _codes; + std::vector _codes; std::list _secondaryCodes; std::string _img; std::string _disabledImg; @@ -42,11 +44,18 @@ class JsonItem final : public LuaInterface, public BaseItem { const std::string& getDisabledImage() const { return _disabledImg; } const std::list& getImageMods() const { return _imgMods; } const std::list& getDisabledImageMods() const { return _disabledImgMods; } - const std::list& getCodes() const { return _codes; } + const std::vector& getCodes() const { return _codes; } const std::list& getSecondaryCodes() const { return _secondaryCodes; } std::string getCodesString() const; bool hasCode(const std::string& code) const { // NOTE: this is called canProvideCode in lua +#ifdef JSONITEM_CI_QUIRK + const auto cmp = [&code](const std::string& s) { + return code.length() == s.length() && strcasecmp(code.c_str(), s.c_str()) == 0; + }; + return std::find_if(_codes.begin(), _codes.end(), cmp) != _codes.end(); +#else return std::find(_codes.begin(), _codes.end(), code) != _codes.end(); +#endif } bool hasSecondaryCode(const std::string& code) const { return std::find(_secondaryCodes.begin(), _secondaryCodes.end(), code) != _secondaryCodes.end(); @@ -54,7 +63,7 @@ class JsonItem final : public LuaInterface, public BaseItem { const bool getInheritCodes() const { return _inheritCodes; } const std::string& getName() const { return _name; } }; - + protected: std::vector _stages; bool _imgOverridden = false; @@ -69,7 +78,23 @@ class JsonItem final : public LuaInterface, public BaseItem { bool _imgChanged = false; bool _ignoreUserInput = false; -public: +#ifdef JSONITEM_CI_QUIRK + // CI comparison in list is way too expensive, so we create a lowercase set instead + std::unordered_set _ciAllCodes; // includes codes for stages +#endif + +public: + static void toLowerInPlace(std::string& s) + { + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + } + + static std::string toLower(std::string s) + { + toLowerInPlace(s); + return s; + } + virtual size_t getStageCount() const override { return _stages.size(); } virtual const std::string& getImage(size_t stage) const override { @@ -100,9 +125,16 @@ class JsonItem final : public LuaInterface, public BaseItem { return _name; } +#ifdef JSONITEM_CI_QUIRK + bool canProvideCodeLower(const std::string& code) const + { + return _ciAllCodes.count(code); + } +#endif + virtual bool canProvideCode(const std::string& code) const override { #ifdef JSONITEM_CI_QUIRK - auto cmp = [&code](const std::string& s) { + const auto cmp = [&code](const std::string& s) { return code.length() == s.length() && strcasecmp(code.c_str(), s.c_str()) == 0; }; if (std::find_if(_codes.begin(), _codes.end(), cmp) != _codes.end()) return true; @@ -115,28 +147,58 @@ class JsonItem final : public LuaInterface, public BaseItem { return false; } - virtual int providesCode(const std::string code) const override { - // TODO: split at ':' for consumables to be able to check for a specific amount? +private: + int providesCodeImpl(const std::string& code, bool assumeCanProvide) const + { if (_type == Type::COMPOSITE_TOGGLE) { // composites do not provide left/right codes since that would duplicate numbers +#ifdef JSONITEM_CI_QUIRK + const auto cmp = [&code](const std::string& s) { + return code.length() == s.length() && strcasecmp(code.c_str(), s.c_str()) == 0; + }; + if (std::find_if(_codes.begin(), _codes.end(), cmp) != _codes.end()) +#else if (std::find(_codes.begin(), _codes.end(), code) != _codes.end()) +#endif return ((_stage2&1) ? 1 : 0) + ((_stage2&2) ? 1 : 0); - else - return 0; + return 0; } - if ((int)_stages.size()>_stage2) { - if (_allowDisabled && !_stage1) return 0; + + if ((int)_stages.size() > _stage2) { + if (_allowDisabled && !_stage1) + return 0; for (int i=_stage2; i>=0; i--) { - if (_stages[i].hasCode(code)) return 1; - if (!_stages[i].getInheritCodes()) break; + if (_stages[i].hasCode(code)) + return 1; + if (!_stages[i].getInheritCodes()) + break; } return false; } - if (_count && canProvideCode(code)) return _count; - return (_stage1 && canProvideCode(code)); + + if (_count && (assumeCanProvide || canProvideCode(code))) + return _count; + return _stage1 && (assumeCanProvide || canProvideCode(code)); + } + +public: +#ifdef JSONITEM_CI_QUIRK + int providesCodeLower(const std::string& code) const + { + if (!canProvideCodeLower(code)) + return 0; + return providesCodeImpl(code, true); + } +#endif + + int providesCode(const std::string& code) const override + { + // TODO: split at ':' for consumables to be able to check for a specific amount? + return providesCodeImpl(code, false); } - virtual std::string getCodesString() const override; - virtual const std::list& getCodes(int stage) const; + + std::string getCodesString() const override; + const std::vector& getCodes(int stage) const; virtual bool changeState(BaseItem::Action action) override { if (_ignoreUserInput) diff --git a/src/core/jsonutil.h b/src/core/jsonutil.h index 08de17f7..bee07c92 100644 --- a/src/core/jsonutil.h +++ b/src/core/jsonutil.h @@ -36,7 +36,8 @@ static nlohmann::json parse_jsonc(std::string& s) return j; } -static void commasplit(const std::string& s, std::list& l) +template