From 27dd8a8202b5797b324ab5aa3c96c468665fc351 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 7 Mar 2021 17:09:30 +0100 Subject: [PATCH] Initial support for item use scripts in angelscript. --- CInterface/Library/Item.cpp | 11 +- src/Library/Items/Item.hpp | 6 +- .../AngelScript/AngelScriptFunctionCall.hpp | 52 ++++++ .../AngelScript/AngelScriptItemUseScript.cpp | 65 ++++++++ .../AngelScript/AngelScriptItemUseScript.hpp | 50 ++++++ .../AngelScript/AngelScriptResolver.cpp | 151 ++++++++++++------ .../AngelScript/AngelScriptResolver.hpp | 9 ++ .../AngelScript/AngelScriptScript.cpp | 40 +---- .../TypeRegistry/BasicScriptClass.cpp | 16 +- tests/ScriptTests/ItemUseScriptTests.cpp | 88 ++++++++++ .../ScriptTypeTests/Battle/PokemonTests.cpp | 2 +- .../ScriptTypeTests/Library/FormesTests.cpp | 2 +- .../ScriptTypeTests/Library/ItemDataTests.cpp | 2 +- .../ScriptTypeTests/Library/MoveTests.cpp | 2 +- .../ScriptTypeTests/Library/SpeciesTests.cpp | 2 +- .../Library/StaticLibraryTests.cpp | 2 +- tests/TestLibrary/TestLibrary.cpp | 2 +- 17 files changed, 405 insertions(+), 97 deletions(-) create mode 100644 src/ScriptResolving/AngelScript/AngelScriptFunctionCall.hpp create mode 100644 src/ScriptResolving/AngelScript/AngelScriptItemUseScript.cpp create mode 100644 src/ScriptResolving/AngelScript/AngelScriptItemUseScript.hpp create mode 100644 tests/ScriptTests/ItemUseScriptTests.cpp diff --git a/CInterface/Library/Item.cpp b/CInterface/Library/Item.cpp index d397c8a..880ee6e 100644 --- a/CInterface/Library/Item.cpp +++ b/CInterface/Library/Item.cpp @@ -5,13 +5,20 @@ using namespace PkmnLib::Library; export Item* PkmnLib_Item_Construct(const char* name, CreatureLib::Library::ItemCategory category, CreatureLib::Library::BattleItemCategory battleCategory, int32_t price, - const char* flags[], size_t flagsCount, uint8_t flingPower) { + const char* effectName, CreatureLib::Library::EffectParameter* effectParameters[], + size_t effectParameterCount, const char* flags[], size_t flagsCount, + uint8_t flingPower) { std::unordered_set conversedFlags(flagsCount); for (size_t i = 0; i < flagsCount; i++) { conversedFlags.insert(ArbUt::StringView::CalculateHash(flags[i])); } - return new Item(ArbUt::StringView(name), category, battleCategory, price, conversedFlags, flingPower); + return new Item( + ArbUt::StringView(name), category, battleCategory, price, + new CreatureLib::Library::SecondaryEffect(100, effectName, + ArbUt::List( + effectParameters, effectParameters + effectParameterCount)), + conversedFlags, flingPower); }; export void PkmnLib_Item_Destruct(const Item* p) { delete p; } diff --git a/src/Library/Items/Item.hpp b/src/Library/Items/Item.hpp index 3522bc4..fd077f7 100644 --- a/src/Library/Items/Item.hpp +++ b/src/Library/Items/Item.hpp @@ -9,8 +9,10 @@ namespace PkmnLib::Library { public: Item(const ArbUt::StringView& name, CreatureLib::Library::ItemCategory category, CreatureLib::Library::BattleItemCategory battleCategory, int32_t price, - const std::unordered_set& flags, uint8_t flingPower) noexcept - : CreatureLib::Library::Item(name, category, battleCategory, price, flags), _flingPower(flingPower) {} + const CreatureLib::Library::SecondaryEffect* effect, const std::unordered_set& flags, + uint8_t flingPower) noexcept + : CreatureLib::Library::Item(name, category, battleCategory, price, effect, flags), + _flingPower(flingPower) {} inline uint8_t GetFlingPower() const noexcept { return _flingPower; } }; diff --git a/src/ScriptResolving/AngelScript/AngelScriptFunctionCall.hpp b/src/ScriptResolving/AngelScript/AngelScriptFunctionCall.hpp new file mode 100644 index 0000000..2946ca5 --- /dev/null +++ b/src/ScriptResolving/AngelScript/AngelScriptFunctionCall.hpp @@ -0,0 +1,52 @@ +#ifndef PKMNLIB_ANGELSCRIPTFUNCTIONCALL_HPP +#define PKMNLIB_ANGELSCRIPTFUNCTIONCALL_HPP + +#include "ContextPool.hpp" + +class AngelScriptUtils { +public: + static void AngelscriptFunctionCall(asIScriptFunction* func, ContextPool* ctxPool, asIScriptObject* obj, + const ArbUt::StringView& scriptName, + const std::function& setup, + const std::function& onEnd) { + auto ctx = asGetActiveContext(); + bool newContext = false; + if (ctx == nullptr) { + ctx = ctxPool->RequestContext(); + newContext = true; + } else { + ctx->PushState(); + } + ctx->Prepare(func); + ctx->SetObject(obj); + setup(ctx); + auto scriptResult = ctx->Execute(); + if (scriptResult != asEXECUTION_FINISHED) { + if (scriptResult == asEXECUTION_EXCEPTION) { + std::stringstream err; + err << "Script exception in script '" << scriptName.c_str() << "', line " + << ctx->GetExceptionLineNumber() << ". Message: '" << ctx->GetExceptionString() << "'."; + if (newContext) { + ctxPool->ReturnContextToPool(ctx); + } else { + ctx->PopState(); + } + throw ArbUt::Exception(err.str()); + } + if (newContext) { + ctxPool->ReturnContextToPool(ctx); + } else { + ctx->PopState(); + } + THROW("Script didn't finish properly; message " << scriptResult); + } + onEnd(ctx); + if (newContext) { + ctxPool->ReturnContextToPool(ctx); + } else { + ctx->PopState(); + } + } +}; + +#endif \ No newline at end of file diff --git a/src/ScriptResolving/AngelScript/AngelScriptItemUseScript.cpp b/src/ScriptResolving/AngelScript/AngelScriptItemUseScript.cpp new file mode 100644 index 0000000..9a2bac4 --- /dev/null +++ b/src/ScriptResolving/AngelScript/AngelScriptItemUseScript.cpp @@ -0,0 +1,65 @@ +#include "AngelScriptItemUseScript.hpp" +#include "AngelScriptResolver.hpp" + +bool AngelScriptItemUseScript::IsItemUsable() const { + if (!__IsItemUsable.Exists) { + return CreatureLib::Battling::ItemUseScript::IsItemUsable(); + } + bool res = false; + AngelScriptUtils::AngelscriptFunctionCall( + __IsItemUsable.Function, _resolver->GetContextPool(), _scriptObject, ""_cnc, + [&]([[maybe_unused]] asIScriptContext* ctx) {}, + [&]([[maybe_unused]] asIScriptContext* ctx) { res = ctx->GetReturnByte() == 1; }); + return res; +} + +bool AngelScriptItemUseScript::IsCreatureUseItem() const { + if (!__IsPokemonUseItem.Exists) { + return CreatureLib::Battling::ItemUseScript::IsCreatureUseItem(); + } + bool res = false; + AngelScriptUtils::AngelscriptFunctionCall( + __IsPokemonUseItem.Function, _resolver->GetContextPool(), _scriptObject, ""_cnc, + [&]([[maybe_unused]] asIScriptContext* ctx) {}, + [&]([[maybe_unused]] asIScriptContext* ctx) { res = ctx->GetReturnByte() == 1; }); + return res; +} +bool AngelScriptItemUseScript::IsUseValidForCreature(CreatureLib::Battling::Creature* creature) const { + if (!__IsPokemonUseItem.Exists) { + return CreatureLib::Battling::ItemUseScript::IsUseValidForCreature(creature); + } + bool res = false; + AngelScriptUtils::AngelscriptFunctionCall( + __IsPokemonUseItem.Function, _resolver->GetContextPool(), _scriptObject, ""_cnc, + [&]([[maybe_unused]] asIScriptContext* ctx) { ctx->SetArgObject(0, (void*)creature); }, + [&]([[maybe_unused]] asIScriptContext* ctx) { res = ctx->GetReturnByte() == 1; }); + return res; +} +bool AngelScriptItemUseScript::IsHoldable() const { + if (!__IsHoldable.Exists) { + return CreatureLib::Battling::ItemUseScript::IsHoldable(); + } + bool res = false; + AngelScriptUtils::AngelscriptFunctionCall( + __IsHoldable.Function, _resolver->GetContextPool(), _scriptObject, ""_cnc, + [&]([[maybe_unused]] asIScriptContext* ctx) {}, + [&]([[maybe_unused]] asIScriptContext* ctx) { res = ctx->GetReturnByte() == 1; }); + return res; +} +void AngelScriptItemUseScript::OnUse() const { + if (!__OnUse.Exists) { + CreatureLib::Battling::ItemUseScript::OnUse(); + } + AngelScriptUtils::AngelscriptFunctionCall( + __OnUse.Function, _resolver->GetContextPool(), _scriptObject, ""_cnc, + [&]([[maybe_unused]] asIScriptContext* ctx) {}, [&]([[maybe_unused]] asIScriptContext* ctx) {}); +} +void AngelScriptItemUseScript::OnCreatureUse(CreatureLib::Battling::Creature* creature) const { + if (!__OnPokemonUse.Exists) { + CreatureLib::Battling::ItemUseScript::OnUse(); + } + AngelScriptUtils::AngelscriptFunctionCall( + __OnPokemonUse.Function, _resolver->GetContextPool(), _scriptObject, ""_cnc, + [&]([[maybe_unused]] asIScriptContext* ctx) { ctx->SetArgObject(0, (void*)creature); }, + [&]([[maybe_unused]] asIScriptContext* ctx) {}); +} diff --git a/src/ScriptResolving/AngelScript/AngelScriptItemUseScript.hpp b/src/ScriptResolving/AngelScript/AngelScriptItemUseScript.hpp new file mode 100644 index 0000000..61a020e --- /dev/null +++ b/src/ScriptResolving/AngelScript/AngelScriptItemUseScript.hpp @@ -0,0 +1,50 @@ +#ifndef PKMNLIB_ANGELSCRIPTITEMUSESCRIPT_HPP +#define PKMNLIB_ANGELSCRIPTITEMUSESCRIPT_HPP + +#include +#include "AngelScriptFunctionCall.hpp" + +class AngelScriptResolver; + +class AngelScriptItemUseScript final : public CreatureLib::Battling::ItemUseScript { +public: + AngelScriptItemUseScript(asIScriptObject* scriptObject, const AngelScriptResolver* resolver) + : _scriptObject(scriptObject), _resolver(resolver) {} + + [[nodiscard]] bool IsItemUsable() const override; + [[nodiscard]] bool IsCreatureUseItem() const override; + bool IsUseValidForCreature(CreatureLib::Battling::Creature* creature) const override; + + bool IsHoldable() const override; + + void OnUse() const override; + void OnCreatureUse(CreatureLib::Battling::Creature* creature) const override; + +private: + asIScriptObject* _scriptObject; + const AngelScriptResolver* _resolver; + + struct FunctionInfo { + bool Exists = false; + asIScriptFunction* Function = nullptr; + }; + + FunctionInfo Initialize(const std::string& decl) { + auto val = _scriptObject->GetObjectType()->GetMethodByDecl(decl.c_str(), false); + if (val == nullptr) { + return FunctionInfo{.Exists = false, .Function = nullptr}; + } + return FunctionInfo{.Exists = true, .Function = val}; + } + +#define ITEM_USE_SCRIPT_HOOK_FUNCTION(name, decl) FunctionInfo __##name = Initialize(decl); + + ITEM_USE_SCRIPT_HOOK_FUNCTION(IsItemUsable, "bool IsItemUsable()"); + ITEM_USE_SCRIPT_HOOK_FUNCTION(IsPokemonUseItem, "bool IsPokemonUseItem()"); + ITEM_USE_SCRIPT_HOOK_FUNCTION(IsUseValidForPokemon, "bool IsUseValidForPokemon(Pokemon@ target)"); + ITEM_USE_SCRIPT_HOOK_FUNCTION(IsHoldable, "bool IsHoldable()"); + ITEM_USE_SCRIPT_HOOK_FUNCTION(OnUse, "void OnUse()"); + ITEM_USE_SCRIPT_HOOK_FUNCTION(OnPokemonUse, "void OnPokemonUse(Pokemon@ target)"); +}; + +#endif // PKMNLIB_ANGELSCRIPTITEMUSESCRIPT_HPP diff --git a/src/ScriptResolving/AngelScript/AngelScriptResolver.cpp b/src/ScriptResolving/AngelScript/AngelScriptResolver.cpp index 7b2385d..c2b2499 100644 --- a/src/ScriptResolving/AngelScript/AngelScriptResolver.cpp +++ b/src/ScriptResolving/AngelScript/AngelScriptResolver.cpp @@ -127,7 +127,7 @@ void AngelScriptResolver::MessageCallback(const asSMessageInfo* msg, void*) { } CreatureLib::Battling::BattleScript* AngelScriptResolver::LoadScript(ScriptCategory category, - const ArbUt::StringView& scriptName) { + const ArbUt::StringView& scriptName) { ArbUt::Dictionary innerDb; auto v = _typeDatabase.TryGet(category); if (!v.has_value()) { @@ -150,6 +150,36 @@ CreatureLib::Battling::BattleScript* AngelScriptResolver::LoadScript(ScriptCateg _contextPool->ReturnContextToPool(ctx); return new AngelScriptScript(this, t.value(), obj, _contextPool); } + +CreatureLib::Battling::ItemUseScript* AngelScriptResolver::LoadItemScript(const CreatureLib::Library::Item* item) { + auto v = this->_itemUseScripts.TryGet(item); + if (v.has_value()) { + return v.value(); + } + if (!item->GetEffect().HasValue()) { + return nullptr; + } + + auto typeInfoOption = _itemUseTypes.TryGet(item->GetEffect().GetValue()->GetEffectName()); + if (!typeInfoOption.has_value()) { + return nullptr; + } + + auto* ctx = _contextPool->RequestContext(); + auto factory = typeInfoOption.value().get()->GetFactoryByIndex(0); + ctx->Prepare(factory); + auto result = ctx->Execute(); + if (result != asEXECUTION_FINISHED) { + throw ArbUt::Exception("Instantiation failed."); + } + asIScriptObject* obj = *(asIScriptObject**)ctx->GetAddressOfReturnValue(); + obj->AddRef(); + auto scriptObject = new AngelScriptItemUseScript(obj, this); + _itemUseScripts.Insert(item, scriptObject); + _contextPool->ReturnContextToPool(ctx); + return scriptObject; +} + void AngelScriptResolver::FinalizeModule() { int r = _builder.BuildModule(); if (r < 0) @@ -159,62 +189,85 @@ void AngelScriptResolver::FinalizeModule() { std::regex variableMatcher(R"(\s*(\w+)=(\w+))", std::regex_constants::icase); std::smatch base_match; + auto pkmnScriptType = _mainModule->GetTypeInfoByName("PkmnScript"); + auto itemUseScriptType = _mainModule->GetTypeInfoByName("ItemUseScript"); for (asUINT n = 0; n < count; n++) { auto typeInfo = _mainModule->GetObjectTypeByIndex(n); - auto metadata = _builder.GetMetadataForType(typeInfo->GetTypeId()); - for (size_t m = 0; m < metadata.size(); m++) { - auto data = metadata[m]; - if (std::regex_match(data, base_match, metadataMatcher)) { - auto mt = base_match[1].str(); - auto metadataKind = ArbUt::StringView(mt.c_str(), mt.length()); - auto metadataVariables = base_match[2].str(); - if (!std::regex_match(metadataVariables, base_match, variableMatcher)) { - continue; - } - ArbUt::StringView effectName; - for (size_t variableIndex = 1; variableIndex < base_match.size(); variableIndex += 2) { - if (ArbUt::StringView::CalculateHash(base_match[variableIndex].str().c_str()) == "effect"_cnc) { - auto val = base_match[variableIndex + 1].str(); - effectName = ArbUt::StringView(val.c_str(), val.length()); + if (typeInfo->DerivesFrom(pkmnScriptType)) { + auto metadata = _builder.GetMetadataForType(typeInfo->GetTypeId()); + for (size_t m = 0; m < metadata.size(); m++) { + auto data = metadata[m]; + if (std::regex_match(data, base_match, metadataMatcher)) { + auto mt = base_match[1].str(); + auto metadataKind = ArbUt::StringView(mt.c_str(), mt.length()); + auto metadataVariables = base_match[2].str(); + if (!std::regex_match(metadataVariables, base_match, variableMatcher)) { + continue; } + ArbUt::StringView effectName; + for (size_t variableIndex = 1; variableIndex < base_match.size(); variableIndex += 2) { + if (ArbUt::StringView::CalculateHash(base_match[variableIndex].str().c_str()) == "effect"_cnc) { + auto val = base_match[variableIndex + 1].str(); + effectName = ArbUt::StringView(val.c_str(), val.length()); + } + } + RegisterScriptType(typeInfo, metadataKind, effectName); } - if (effectName.IsEmpty()) { - continue; - } - switch (metadataKind) { - case "Move"_cnc: - _typeDatabase[ScriptCategory::Attack].Insert(effectName, - new AngelScriptTypeInfo(effectName, typeInfo)); - break; - case "Pokemon"_cnc: - _typeDatabase[ScriptCategory::Creature].Insert(effectName, - new AngelScriptTypeInfo(effectName, typeInfo)); - break; - case "Ability"_cnc: - _typeDatabase[ScriptCategory::Talent].Insert(effectName, - new AngelScriptTypeInfo(effectName, typeInfo)); - break; - case "Status"_cnc: - _typeDatabase[ScriptCategory::Status].Insert(effectName, - new AngelScriptTypeInfo(effectName, typeInfo)); - break; - case "Battle"_cnc: - _typeDatabase[ScriptCategory::Battle].Insert(effectName, - new AngelScriptTypeInfo(effectName, typeInfo)); - break; - case "Side"_cnc: - _typeDatabase[ScriptCategory::Side].Insert(effectName, - new AngelScriptTypeInfo(effectName, typeInfo)); - break; - case "Weather"_cnc: - _typeDatabase[static_cast(PkmnScriptCategory::Weather)].Insert( - effectName, new AngelScriptTypeInfo(effectName, typeInfo)); - break; + } + } else if (typeInfo->DerivesFrom(itemUseScriptType)) { + auto metadata = _builder.GetMetadataForType(typeInfo->GetTypeId()); + for (size_t m = 0; m < metadata.size(); m++) { + auto data = metadata[m]; + if (std::regex_match(data, base_match, metadataMatcher)) { + auto mt = base_match[1].str(); + auto metadataKind = ArbUt::StringView(mt.c_str(), mt.length()); + auto metadataVariables = base_match[2].str(); + if (!std::regex_match(metadataVariables, base_match, variableMatcher)) { + continue; + } + ArbUt::StringView effectName; + for (size_t variableIndex = 1; variableIndex < base_match.size(); variableIndex += 2) { + if (ArbUt::StringView::CalculateHash(base_match[variableIndex].str().c_str()) == "effect"_cnc) { + auto val = base_match[variableIndex + 1].str(); + effectName = ArbUt::StringView(val.c_str(), val.length()); + } + } + _itemUseTypes.Insert(effectName, typeInfo); } } } } } +void AngelScriptResolver::RegisterScriptType(asITypeInfo* typeInfo, const ArbUt::StringView& metadataKind, + const ArbUt::StringView& effectName) { + if (effectName.IsEmpty()) { + return; + } + switch (metadataKind) { + case "Move"_cnc: + _typeDatabase[ScriptCategory::Attack].Insert(effectName, new AngelScriptTypeInfo(effectName, typeInfo)); + break; + case "Pokemon"_cnc: + _typeDatabase[ScriptCategory::Creature].Insert(effectName, new AngelScriptTypeInfo(effectName, typeInfo)); + break; + case "Ability"_cnc: + _typeDatabase[ScriptCategory::Talent].Insert(effectName, new AngelScriptTypeInfo(effectName, typeInfo)); + break; + case "Status"_cnc: + _typeDatabase[ScriptCategory::Status].Insert(effectName, new AngelScriptTypeInfo(effectName, typeInfo)); + break; + case "Battle"_cnc: + _typeDatabase[ScriptCategory::Battle].Insert(effectName, new AngelScriptTypeInfo(effectName, typeInfo)); + break; + case "Side"_cnc: + _typeDatabase[ScriptCategory::Side].Insert(effectName, new AngelScriptTypeInfo(effectName, typeInfo)); + break; + case "Weather"_cnc: + _typeDatabase[static_cast(PkmnScriptCategory::Weather)].Insert( + effectName, new AngelScriptTypeInfo(effectName, typeInfo)); + break; + } +} void AngelScriptResolver::CreateScript(const char* name, const char* script) { _builder.AddSectionFromMemory(name, script); } @@ -325,4 +378,4 @@ void AngelScriptResolver::InitializeByteCode( typeDatabase.Set(innerDb.first, newInnerDb); } _typeDatabase = typeDatabase; -} +} \ No newline at end of file diff --git a/src/ScriptResolving/AngelScript/AngelScriptResolver.hpp b/src/ScriptResolving/AngelScript/AngelScriptResolver.hpp index f862eaa..cf835d2 100644 --- a/src/ScriptResolving/AngelScript/AngelScriptResolver.hpp +++ b/src/ScriptResolving/AngelScript/AngelScriptResolver.hpp @@ -6,7 +6,9 @@ #include "../../Battling/Library/BattleLibrary.hpp" #define ANGELSCRIPT_DLL_LIBRARY_IMPORT +#include #include +#include "AngelScriptItemUseScript.hpp" #include "AngelScriptScript.hpp" #include "AngelScriptTypeInfo.hpp" @@ -21,10 +23,14 @@ private: static void Print(const std::string& str) { std::cout << str << std::endl; } ArbUt::Dictionary> _typeDatabase; ArbUt::Dictionary _baseTypes; + ArbUt::Dictionary _itemUseTypes; + ArbUt::Dictionary _itemUseScripts; void RegisterTypes(); void InitializeByteCode(const ArbUt::Dictionary>& types); + void RegisterScriptType(asITypeInfo* typeInfo, const ArbUt::StringView& metadataKind, + const ArbUt::StringView& effectName); public: ~AngelScriptResolver() override { @@ -46,6 +52,7 @@ public: CreatureLib::Battling::BattleScript* LoadScript(ScriptCategory category, const ArbUt::StringView& scriptName) override; + CreatureLib::Battling::ItemUseScript* LoadItemScript(const CreatureLib::Library::Item* item) override; void WriteByteCodeToFile(const char* file, bool stripDebugInfo = false); void LoadByteCodeFromFile(const char* file); @@ -81,5 +88,7 @@ public: } return t; } + + inline ContextPool* GetContextPool() const noexcept { return _contextPool; } }; #endif // PKMNLIB_ANGELSCRIPRESOLVER_HPP diff --git a/src/ScriptResolving/AngelScript/AngelScriptScript.cpp b/src/ScriptResolving/AngelScript/AngelScriptScript.cpp index b5e8754..d5f24df 100644 --- a/src/ScriptResolving/AngelScript/AngelScriptScript.cpp +++ b/src/ScriptResolving/AngelScript/AngelScriptScript.cpp @@ -1,46 +1,14 @@ #include "AngelScriptScript.hpp" +#include "AngelScriptFunctionCall.hpp" #include "AngelScriptResolver.hpp" #define CALL_HOOK(name, setup) \ auto s = _type->Get##name(); \ if (!s.Exists) \ return; \ - auto ctx = asGetActiveContext(); \ - bool newContext = false; \ - if (ctx == nullptr) { \ - ctx = _ctxPool->RequestContext(); \ - newContext = true; \ - } else { \ - ctx->PushState(); \ - } \ - ctx->Prepare(s.Function); \ - ctx->SetObject(_obj); \ - setup; \ - auto scriptResult = ctx->Execute(); \ - if (scriptResult != asEXECUTION_FINISHED) { \ - if (scriptResult == asEXECUTION_EXCEPTION) { \ - std::stringstream err; \ - err << "Script exception in script '" << GetName().c_str() << "', line " << ctx->GetExceptionLineNumber() \ - << ". Message: '" << ctx->GetExceptionString() << "'."; \ - if (newContext) { \ - _ctxPool->ReturnContextToPool(ctx); \ - } else { \ - ctx->PopState(); \ - } \ - throw ArbUt::Exception(err.str()); \ - } \ - if (newContext) { \ - _ctxPool->ReturnContextToPool(ctx); \ - } else { \ - ctx->PopState(); \ - } \ - THROW("Script didn't finish properly; message " << scriptResult); \ - } \ - if (newContext) { \ - _ctxPool->ReturnContextToPool(ctx); \ - } else { \ - ctx->PopState(); \ - } + AngelScriptUtils::AngelscriptFunctionCall( \ + s.Function, _ctxPool, _obj, GetName(), [&]([[maybe_unused]] asIScriptContext* ctx) { setup }, \ + [&]([[maybe_unused]] asIScriptContext* ctx) {}); CScriptArray* AngelScriptScript::GetEffectParameters(const ArbUt::List& ls) { asITypeInfo* t = _resolver->GetBaseType("array"_cnc); diff --git a/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp b/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp index 893933d..6326a8d 100644 --- a/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp +++ b/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp @@ -3,7 +3,7 @@ void BasicScriptClass::Register(asIScriptEngine* engine) { // As far as I am aware at the moment you can't create an abstract class with virtual members through the // application registry interface. As such, we just create it from string. - [[maybe_unused]] int r = engine->GetModuleByIndex(0)->AddScriptSection("PkmnScript", R"( + int r = engine->GetModuleByIndex(0)->AddScriptSection("PkmnScript", R"( shared abstract class PkmnScript { // CreatureLib methods void OnInitialize(const array &in parameters){}; @@ -46,4 +46,18 @@ shared abstract class PkmnScript { } )"); Ensure(r >= 0); + r = engine->GetModuleByIndex(0)->AddScriptSection("ItemUseScript", R"( +shared abstract class ItemUseScript { + bool IsItemUsable() { return false; }; + bool IsPokemonUseItem() { return false; }; + bool IsUseValidForPokemon(Pokemon@ target) { return false; }; + + bool IsHoldable() { return false; } + + void OnUse() { }; + void OnPokemonUse(Pokemon@ target) { }; +} +)"); + Ensure(r >= 0); + } diff --git a/tests/ScriptTests/ItemUseScriptTests.cpp b/tests/ScriptTests/ItemUseScriptTests.cpp new file mode 100644 index 0000000..c3207b7 --- /dev/null +++ b/tests/ScriptTests/ItemUseScriptTests.cpp @@ -0,0 +1,88 @@ +#ifdef TESTS_BUILD +#include "../../extern/doctest.hpp" +#include "../../src/Battling/Pokemon/CreatePokemon.hpp" +#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp" +#include "../TestLibrary/TestLibrary.hpp" + +#define AS_CLASS(name, contents) \ + { #name, "namespace Pokemon{ [ItemUse effect=" #name "] class " #name " : ItemUseScript { " contents "}}" } + +static std::unordered_map _scripts = std::unordered_map{ + AS_CLASS(blankClass, R"( +)"), + + AS_CLASS(isItemUsable, R"( +bool IsItemUsable() override { + return true; +} +)"), + + AS_CLASS(isPokemonUseItem, R"( +bool IsPokemonUseItem() override { + return true; +} +)"), +}; + +static AngelScriptResolver* _resolverCache = nullptr; +static AngelScriptResolver* GetScriptResolver(PkmnLib::Battling::BattleLibrary* mainLib) { + _resolverCache = dynamic_cast(PkmnLib::Battling::BattleLibrary::CreateScriptResolver()); + _resolverCache->Initialize(mainLib); + for (auto kv : _scripts) { + _resolverCache->CreateScript(kv.first, kv.second); + } + _resolverCache->FinalizeModule(); + return _resolverCache; +} + +static AngelScriptItemUseScript* GetScript(PkmnLib::Battling::BattleLibrary* mainLib, const ArbUt::StringView& name) { + auto lib = GetScriptResolver(mainLib); + auto item = CreatureLib::Library::Item(name, CreatureLib::Library::ItemCategory::MiscItem, + CreatureLib::Library::BattleItemCategory::None, 0, + new CreatureLib::Library::SecondaryEffect(100, name, {}), {}); + + auto s = lib->LoadItemScript(&item); + auto script = dynamic_cast(s); + REQUIRE(script != nullptr); + return script; +} + +TEST_CASE("Invoke isItemUsable item use script function on empty class") { + auto mainLib = TestLibrary::GetLibrary(); + + auto script = GetScript(mainLib, "blankClass"_cnc); + REQUIRE(script != nullptr); + REQUIRE_FALSE(script->IsItemUsable()); + + delete script; +} +TEST_CASE("Invoke isItemUsable item use script function") { + auto mainLib = TestLibrary::GetLibrary(); + + auto script = GetScript(mainLib, "isItemUsable"_cnc); + REQUIRE(script != nullptr); + REQUIRE(script->IsItemUsable()); + + delete script; +} + +TEST_CASE("Invoke isPokemonUseItem item use script function on empty class") { + auto mainLib = TestLibrary::GetLibrary(); + + auto script = GetScript(mainLib, "blankClass"_cnc); + REQUIRE(script != nullptr); + REQUIRE_FALSE(script->IsCreatureUseItem()); + + delete script; +} +TEST_CASE("Invoke isPokemonUseItem item use script function") { + auto mainLib = TestLibrary::GetLibrary(); + + auto script = GetScript(mainLib, "isPokemonUseItem"_cnc); + REQUIRE(script != nullptr); + REQUIRE(script->IsCreatureUseItem()); + + delete script; +} + +#endif \ No newline at end of file diff --git a/tests/ScriptTests/ScriptTypeTests/Battle/PokemonTests.cpp b/tests/ScriptTests/ScriptTypeTests/Battle/PokemonTests.cpp index bb7abe9..bc0162d 100644 --- a/tests/ScriptTests/ScriptTypeTests/Battle/PokemonTests.cpp +++ b/tests/ScriptTests/ScriptTypeTests/Battle/PokemonTests.cpp @@ -8,7 +8,7 @@ static std::unordered_map _scripts = std::unordered_map{{"testScript1", R"( namespace Pokemon{ [Pokemon effect=testScript1] -class testScript1 { +class testScript1 : PkmnScript { bool testSpecies(Pokemon@ p, const Species@ species){ return p.Species is species; } bool testForme(Pokemon@ p, const Forme@ forme){ return p.Forme is forme; } bool testLevel(Pokemon@ p, uint8 level){ return p.Level == level; } diff --git a/tests/ScriptTests/ScriptTypeTests/Library/FormesTests.cpp b/tests/ScriptTests/ScriptTypeTests/Library/FormesTests.cpp index 1735a24..d524af9 100644 --- a/tests/ScriptTests/ScriptTypeTests/Library/FormesTests.cpp +++ b/tests/ScriptTests/ScriptTypeTests/Library/FormesTests.cpp @@ -7,7 +7,7 @@ static std::unordered_map _scripts = std::unordered_map{{"testScript1", R"( namespace Pokemon{ [Pokemon effect=testScript1] -class testScript1 { +class testScript1 : PkmnScript { bool testName(const Forme@ s, const constString &in name){ return s.Name == name; } bool testWeight(const Forme@ s, float weight){ return s.Weight == weight; } bool testHeight(const Forme@ s, float height){ return s.Height == height; } diff --git a/tests/ScriptTests/ScriptTypeTests/Library/ItemDataTests.cpp b/tests/ScriptTests/ScriptTypeTests/Library/ItemDataTests.cpp index 70f5bcf..078bec8 100644 --- a/tests/ScriptTests/ScriptTypeTests/Library/ItemDataTests.cpp +++ b/tests/ScriptTests/ScriptTypeTests/Library/ItemDataTests.cpp @@ -7,7 +7,7 @@ static std::unordered_map _scripts = std::unordered_map{{"testScript1", R"( namespace Pokemon{ [Pokemon effect=testScript1] -class testScript1 { +class testScript1 : PkmnScript { bool testName(const Item@ i, const constString &in name){ return i.Name == name; } bool testCategory(const Item@ i, ItemCategory category){ return i.Category == category; } bool testBattleCategory(const Item@ i, BattleItemCategory category){ return i.BattleCategory == category; } diff --git a/tests/ScriptTests/ScriptTypeTests/Library/MoveTests.cpp b/tests/ScriptTests/ScriptTypeTests/Library/MoveTests.cpp index 2d6c620..65017b6 100644 --- a/tests/ScriptTests/ScriptTypeTests/Library/MoveTests.cpp +++ b/tests/ScriptTests/ScriptTypeTests/Library/MoveTests.cpp @@ -7,7 +7,7 @@ static std::unordered_map _scripts = std::unordered_map{{"testScript1", R"( namespace Pokemon{ [Pokemon effect=testScript1] -class testScript1 { +class testScript1 : PkmnScript { bool testName(const MoveData@ s, const constString &in name){ return s.Name == name; } bool testType(const MoveData@ s, uint8 type){ return s.Type == type; } bool testCategory(const MoveData@ s, MoveCategory category){ return s.Category == category; } diff --git a/tests/ScriptTests/ScriptTypeTests/Library/SpeciesTests.cpp b/tests/ScriptTests/ScriptTypeTests/Library/SpeciesTests.cpp index 9c95c8a..eeecf6b 100644 --- a/tests/ScriptTests/ScriptTypeTests/Library/SpeciesTests.cpp +++ b/tests/ScriptTests/ScriptTypeTests/Library/SpeciesTests.cpp @@ -7,7 +7,7 @@ static std::unordered_map _scripts = std::unordered_map{{"testScript1", R"( namespace Pokemon{ [Pokemon effect=testScript1] -class testScript1 { +class testScript1 : PkmnScript { bool testName(const Species@ s, const constString &in name){ return s.Name == name; } bool testId(const Species@ s, uint16 id){ return s.Id == id; } bool testGenderRate(const Species@ s, float rate){ return s.GenderRate == rate; } diff --git a/tests/ScriptTests/ScriptTypeTests/Library/StaticLibraryTests.cpp b/tests/ScriptTests/ScriptTypeTests/Library/StaticLibraryTests.cpp index be19da1..306673c 100644 --- a/tests/ScriptTests/ScriptTypeTests/Library/StaticLibraryTests.cpp +++ b/tests/ScriptTests/ScriptTypeTests/Library/StaticLibraryTests.cpp @@ -7,7 +7,7 @@ static std::unordered_map _scripts = std::unordered_map{{"testScript1", R"( namespace Pokemon{ [Pokemon effect=testScript1] -class testScript1 { +class testScript1 : PkmnScript { bool testMaximumLevel(const StaticLibrary@ s, uint8 level){ return s.Settings.MaximalLevel == level; } bool testMaximumMoves(const StaticLibrary@ s, uint8 moveCount){ return s.Settings.MaximalMoves == moveCount; } bool testSpeciesLibrary(const StaticLibrary@ s, const SpeciesLibrary@ speciesLib){ return s.SpeciesLibrary is speciesLib; } diff --git a/tests/TestLibrary/TestLibrary.cpp b/tests/TestLibrary/TestLibrary.cpp index 97e4ff5..abf814a 100644 --- a/tests/TestLibrary/TestLibrary.cpp +++ b/tests/TestLibrary/TestLibrary.cpp @@ -55,6 +55,6 @@ PkmnLib::Library::ItemLibrary* TestLibrary::BuildItemLibrary() { auto lib = new PkmnLib::Library::ItemLibrary(); lib->Insert("testItem"_cnc.GetHash(), new PkmnLib::Library::Item("testItem"_cnc, CreatureLib::Library::ItemCategory::MiscItem, - CreatureLib::Library::BattleItemCategory::None, 0, {}, 0)); + CreatureLib::Library::BattleItemCategory::None, 0, nullptr, {}, 0)); return lib; }