#pragma once #include "globalincs/pstypes.h" #include "globalincs/version.h" #include "ade_api.h" #include "scripting.h" #include "utils/tuples.h" #include #include namespace scripting { namespace detail { ade_odata_setter convert_arg_type(object* objp); ade_odata_setter convert_arg_type(vec3d vec); template T&& convert_arg_type(T&& arg) { return std::forward(arg); } template struct HookParameterInstance { SCP_string name; char type = '\0'; T value; bool enabled = true; HookParameterInstance(SCP_string name_, char type_, T&& value_, bool enabled_) : name(std::move(name_)), type(type_), value(std::forward(value_)), enabled(enabled_) { } }; struct SetSingleHookVarHelper { SCP_vector& paramNames; SetSingleHookVarHelper(SCP_vector& paramNames_) : paramNames(paramNames_) {} template void operator()(HookParameterInstance&& instance) { // If a parameter is not enabled, skip it if (!instance.enabled) { return; } paramNames.push_back(instance.name); Script_system.SetHookVar(instance.name.c_str(), instance.type, detail::convert_arg_type(std::move(instance.value))); } }; template struct HookParameterInstanceList { std::tuple...> params; HookParameterInstanceList(HookParameterInstance&&... params_) : params(std::forward>(params_)...) { } void setHookVars(SCP_vector& paramNames) { util::tuples::for_each<0, SetSingleHookVarHelper, HookParameterInstance...>( std::move(params), SetSingleHookVarHelper(paramNames)); } }; } // namespace detail template detail::HookParameterInstance hook_param(SCP_string name_, char type_, T&& value_, bool enabled = true) { return detail::HookParameterInstance(std::move(name_), type_, std::forward(value_), enabled); } template detail::HookParameterInstanceList hook_param_list(detail::HookParameterInstance&&... params) { return detail::HookParameterInstanceList(std::forward>(params)...); } struct HookVariableDocumentation { const char* name = nullptr; scripting::ade_type_info type; const char* description = nullptr; HookVariableDocumentation(const char* name_, scripting::ade_type_info type_, const char* description_); }; struct HookDeprecationOptions { gameversion::version deprecatedSince; enum class DeprecationLevel { LEVEL_WARN, //Will warn a user if an engine version before deprecatedSince is targeted and error if a later version is targeted. LEVEL_ERROR //Will always error if used. } level_hook, level_override; HookDeprecationOptions(gameversion::version version, DeprecationLevel _level_hook, DeprecationLevel _level_override) : deprecatedSince(std::move(version)), level_hook(_level_hook), level_override(_level_override) { } HookDeprecationOptions(gameversion::version version, DeprecationLevel _level_hook = DeprecationLevel::LEVEL_WARN) : HookDeprecationOptions(std::move(version), _level_hook, _level_hook) { } }; class HookBase { public: HookBase(SCP_string hookName, SCP_string description, SCP_vector parameters, const SCP_unordered_map>& conditions, std::optional deprecation, int32_t hookId = -1); virtual ~HookBase(); const SCP_string& getHookName() const; const SCP_string& getDescription() const; const SCP_vector& getParameters() const; const std::optional& getDeprecation() const; int32_t getHookId() const; virtual bool isActive() const = 0; virtual bool isOverridable() const = 0; const SCP_unordered_map>& _conditions; protected: SCP_string _hookName; SCP_string _description; SCP_vector _parameters; std::optional _deprecation; int32_t _hookId = 0; bool hasParameter(const SCP_string& param) const { return std::find_if(_parameters.begin(), _parameters.end(), [¶m](const HookVariableDocumentation& test) { return test.name == param; }) != _parameters.end(); } }; template class HookImpl : public HookBase { protected: HookImpl(SCP_string hookName, SCP_string description, SCP_vector parameters, std::optional deprecation, int32_t hookId) : HookBase(std::move(hookName), std::move(description), std::move(parameters), condition_t::conditions, std::move(deprecation), hookId) { }; template int run(condition_t condition, detail::HookParameterInstanceList argsList = hook_param_list()) const { if (!Scripting_game_init_run) return 0; SCP_vector paramNames; argsList.setHookVars(paramNames); #ifndef NDEBUG std::for_each(paramNames.begin(), paramNames.end(), [this](const SCP_string& param) { Assertion(this->hasParameter(param), "Hook '%s' does not accept parameter '%s'.", this->_hookName.c_str(), param.c_str()); }); #endif const auto num_run = Script_system.RunCondition(this->_hookId, std::any(std::move(condition))); for (const auto& param : paramNames) { Script_system.RemHookVar(param.c_str()); } return num_run; } }; template<> class HookImpl : public HookBase { static const SCP_unordered_map> emptyConditions; protected: HookImpl(SCP_string hookName, SCP_string description, SCP_vector parameters, std::optional deprecation, int32_t hookId) : HookBase(std::move(hookName), std::move(description), std::move(parameters), emptyConditions, std::move(deprecation), hookId) { }; template int run(detail::HookParameterInstanceList argsList = hook_param_list()) const { if (!Scripting_game_init_run) return 0; SCP_vector paramNames; argsList.setHookVars(paramNames); #ifndef NDEBUG std::for_each(paramNames.begin(), paramNames.end(), [this](const SCP_string& param) { Assertion(this->hasParameter(param), "Hook '%s' does not accept parameter '%s'.", this->_hookName.c_str(), param.c_str()); }); #endif const auto num_run = Script_system.RunCondition(this->_hookId, std::any{}); for (const auto& param : paramNames) { Script_system.RemHookVar(param.c_str()); } return num_run; } }; template class Hook : public HookImpl { public: using HookImpl::HookImpl; ~Hook() override = default; static std::shared_ptr> Factory(SCP_string hookName, SCP_string description, SCP_vector parameters, std::optional deprecation = std::nullopt, int32_t hookId = -1) { return std::shared_ptr>(new Hook(std::move(hookName), std::move(description), std::move(parameters), std::move(deprecation), hookId)); } bool isActive() const override { return Script_system.IsActiveAction(this->_hookId); } bool isOverridable() const override { return false; }; using HookImpl::run; }; template class OverridableHookImpl : public Hook { protected: OverridableHookImpl(SCP_string hookName, SCP_string description, SCP_vector parameters, std::optional deprecation, int32_t hookId) : Hook(std::move(hookName), std::move(description), std::move(parameters), std::move(deprecation), hookId) { } template bool isOverride(condition_t condition, detail::HookParameterInstanceList argsList = hook_param_list()) const { if (!Scripting_game_init_run) return false; SCP_vector paramNames; argsList.setHookVars(paramNames); #ifndef NDEBUG std::for_each(paramNames.begin(), paramNames.end(), [this](const SCP_string& param) { Assertion(this->hasParameter(param), "Hook '%s' does not accept parameter '%s'.", this->_hookName.c_str(), param.c_str()); }); #endif const auto ret_val = Script_system.IsConditionOverride(this->_hookId, std::any(std::move(condition))); for (const auto& param : paramNames) { Script_system.RemHookVar(param.c_str()); } return ret_val; } }; template<> class OverridableHookImpl : public Hook { protected: OverridableHookImpl(SCP_string hookName, SCP_string description, SCP_vector parameters, std::optional deprecation, int32_t hookId) : Hook(std::move(hookName), std::move(description), std::move(parameters), std::move(deprecation), hookId) { } template bool isOverride(detail::HookParameterInstanceList argsList = hook_param_list()) const { if (!Scripting_game_init_run) return false; SCP_vector paramNames; argsList.setHookVars(paramNames); #ifndef NDEBUG std::for_each(paramNames.begin(), paramNames.end(), [this](const SCP_string& param) { Assertion(this->hasParameter(param), "Hook '%s' does not accept parameter '%s'.", this->_hookName.c_str(), param.c_str()); }); #endif const auto ret_val = Script_system.IsConditionOverride(this->_hookId, std::any{}); for (const auto& param : paramNames) { Script_system.RemHookVar(param.c_str()); } return ret_val; } }; template class OverridableHook : public OverridableHookImpl { public: using OverridableHookImpl::OverridableHookImpl; ~OverridableHook() override = default; static std::shared_ptr> Factory(SCP_string hookName, SCP_string description, SCP_vector parameters, std::optional deprecation = std::nullopt, int32_t hookId = -1) { return std::shared_ptr>(new OverridableHook(std::move(hookName), std::move(description), std::move(parameters), std::move(deprecation), hookId)); } bool isOverridable() const override { return true; } using OverridableHookImpl::isOverride; }; const SCP_vector& getHooks(); } // namespace scripting