diff --git a/src/game/scripting/context.cpp b/src/game/scripting/context.cpp new file mode 100644 index 0000000..17bd822 --- /dev/null +++ b/src/game/scripting/context.cpp @@ -0,0 +1,33 @@ +#include "std_include.hpp" +#include "context_initializer.hpp" + +namespace game +{ + namespace scripting + { + context::context() : executer_(this), parameters_(this), event_handler_(this) + { + context_initializer::initialize(this); + } + + executer* context::get_executer() + { + return &this->executer_; + } + + parameters* context::get_parameters() + { + return &this->parameters_; + } + + event_handler* context::get_event_handler() + { + return &this->event_handler_; + } + + chaiscript::ChaiScript* context::get_chai() + { + return &this->chai_; + } + } +} diff --git a/src/game/scripting/context.hpp b/src/game/scripting/context.hpp new file mode 100644 index 0000000..7e3e99e --- /dev/null +++ b/src/game/scripting/context.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "executer.hpp" +#include "parameters.hpp" +#include "event_handler.hpp" + +namespace game +{ + namespace scripting + { + class context final + { + public: + context(); + + executer* get_executer(); + parameters* get_parameters(); + event_handler* get_event_handler(); + chaiscript::ChaiScript* get_chai(); + + private: + executer executer_; + parameters parameters_; + event_handler event_handler_; + chaiscript::ChaiScript chai_; + }; + } +} diff --git a/src/game/scripting/context_initializer.cpp b/src/game/scripting/context_initializer.cpp new file mode 100644 index 0000000..6e31317 --- /dev/null +++ b/src/game/scripting/context_initializer.cpp @@ -0,0 +1,246 @@ +#include "std_include.hpp" +#include "context_initializer.hpp" + +namespace game +{ + namespace scripting + { + namespace context_initializer + { + void initialize_entity(context* context) + { + const auto chai = context->get_chai(); + + chai->add(chaiscript::user_type(), "entity"); + chai->add(chaiscript::constructor(), "entity"); + chai->add(chaiscript::constructor(), "entity"); + + chai->add(chaiscript::fun(&entity::get), "get"); + chai->add(chaiscript::fun(&entity::set), "set"); + + chai->add(chaiscript::fun(&entity::on_notify), "onNotify"); + chai->add(chaiscript::fun([](const entity& ent, const std::string& event, + const std::function&)>& + callback) + { + return ent.on_notify(event, callback, false); + }), "onNotify"); + + chai->add(chaiscript::fun([](entity& lhs, const entity& rhs) -> entity& + { + return lhs = rhs; + }), "="); + + chai->add(chaiscript::fun([context](const std::string& event, + const std::function&)>& + callback) + { + generic_event_listener listener; + listener.event = event; + listener.is_volatile = false; + listener.callback = callback; + + context->get_event_handler()->add_event_listener(listener); + }), "onNotify"); + + chai->add(chaiscript::fun([context](const std::string& event, + const std::function&)>& + callback, const bool is_volatile) + { + generic_event_listener listener; + listener.event = event; + listener.is_volatile = is_volatile; + listener.callback = callback; + + context->get_event_handler()->add_event_listener(listener); + }), "onNotify"); + + // Notification + chai->add(chaiscript::fun(&entity::notify), "vectorNotify"); + chai->add(chaiscript::fun([](const entity& ent, const std::string& event) + { + return ent.notify(event, {}); + }), "notify"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& event, + const chaiscript::Boxed_Value& a1) + { + return ent.notify(event, {a1}); + }), "notify"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& event, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2) + { + return ent.notify(event, {a1, a2}); + }), "notify"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& event, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3) + { + return ent.notify(event, {a1, a2, a3}); + }), "notify"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& event, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3, + const chaiscript::Boxed_Value& a4) + { + return ent.notify(event, {a1, a2, a3, a4}); + }), "notify"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& event, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3, + const chaiscript::Boxed_Value& a4, + const chaiscript::Boxed_Value& a5) + { + return ent.notify(event, {a1, a2, a3, a4, a5}); + }), "notify"); + + // Instance call + chai->add(chaiscript::fun(&entity::call), "vectorCall"); + chai->add(chaiscript::fun([](const entity& ent, const std::string& function) + { + return ent.call(function, {}); + }), "call"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& function, + const chaiscript::Boxed_Value& a1) + { + return ent.call(function, {a1}); + }), "call"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2) + { + return ent.call(function, {a1, a2}); + }), "call"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3) + { + return ent.call(function, {a1, a2, a3}); + }), "call"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3, + const chaiscript::Boxed_Value& a4) + { + return ent.call(function, {a1, a2, a3, a4}); + }), "call"); + + chai->add(chaiscript::fun( + [](const entity& ent, const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3, + const chaiscript::Boxed_Value& a4, + const chaiscript::Boxed_Value& a5) + { + return ent.call(function, {a1, a2, a3, a4, a5}); + }), "call"); + + // Global call + chai->add(chaiscript::fun( + [context](const std::string& function, + const std::vector& arguments) + { + return context->get_executer()->call(function, 0, arguments); + }), "vectorCall"); + chai->add(chaiscript::fun([context](const std::string& function) + { + return context->get_executer()->call(function, 0, {}); + }), "call"); + + chai->add(chaiscript::fun( + [context](const std::string& function, + const chaiscript::Boxed_Value& a1) + { + return context->get_executer()->call(function, 0, {a1}); + }), "call"); + + chai->add(chaiscript::fun( + [context](const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2) + { + return context->get_executer()->call(function, 0, {a1, a2}); + }), "call"); + + chai->add(chaiscript::fun( + [context](const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3) + { + return context->get_executer()->call(function, 0, {a1, a2, a3}); + }), "call"); + + chai->add(chaiscript::fun( + [context](const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3, + const chaiscript::Boxed_Value& a4) + { + return context->get_executer()->call(function, 0, {a1, a2, a3, a4}); + }), "call"); + + chai->add(chaiscript::fun( + [context](const std::string& function, + const chaiscript::Boxed_Value& a1, + const chaiscript::Boxed_Value& a2, + const chaiscript::Boxed_Value& a3, + const chaiscript::Boxed_Value& a4, + const chaiscript::Boxed_Value& a5) + { + return context->get_executer()->call(function, 0, {a1, a2, a3, a4, a5}); + }), "call"); + } + + void initialize(context* context) + { + initialize_entity(context); + + const auto chai = context->get_chai(); + + chai->add(chaiscript::fun([](const std::string& string) + { + printf("%s\n", string.data()); + }), "print"); + + chai->add(chaiscript::fun([](const std::string& string) + { + MessageBoxA(nullptr, string.data(), nullptr, 0); + }), "alert"); + + const auto level_id = *game::native::levelEntityId; + chai->add_global(chaiscript::var(entity(context, level_id)), "level"); + } + } + } +} diff --git a/src/game/scripting/context_initializer.hpp b/src/game/scripting/context_initializer.hpp new file mode 100644 index 0000000..416c9d8 --- /dev/null +++ b/src/game/scripting/context_initializer.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "context.hpp" + +namespace game +{ + namespace scripting + { + namespace context_initializer + { + void initialize(context* context); + } + } +} diff --git a/src/game/scripting/entity.cpp b/src/game/scripting/entity.cpp new file mode 100644 index 0000000..ae8ead3 --- /dev/null +++ b/src/game/scripting/entity.cpp @@ -0,0 +1,82 @@ +#include "std_include.hpp" +#include "context.hpp" + +namespace game +{ + namespace scripting + { + entity::entity() : entity(nullptr, 0) + { + } + + entity::entity(const entity& other) : entity(other.context_, other.entity_id_) + { + } + + entity::entity(context* context, const unsigned int entity_id) : context_(context), entity_id_(entity_id) + { + if (this->entity_id_) + { + native::VariableValue value{}; + value.type = native::SCRIPT_OBJECT; + value.u.entityId = this->entity_id_; + native::AddRefToValue(&value); + } + } + + entity::~entity() + { + if (this->entity_id_) + { + native::RemoveRefToValue(native::SCRIPT_OBJECT, {static_cast(this->entity_id_)}); + } + } + + void entity::on_notify(const std::string& event, + const std::function&)>& + callback, + const bool is_volatile) + const + { + event_listener listener; + listener.event = event; + listener.callback = callback; + listener.entity_id = this->entity_id_; + listener.is_volatile = is_volatile; + + this->context_->get_event_handler()->add_event_listener(listener); + } + + unsigned int entity::get_entity_id() const + { + return this->entity_id_; + } + + native::scr_entref_t entity::get_entity_reference() const + { + return game::native::Scr_GetEntityIdRef(this->get_entity_id()); + } + + chaiscript::Boxed_Value entity::call(const std::string& function, + const std::vector& arguments) const + { + return this->context_->get_executer()->call(function, this->get_entity_id(), arguments); + } + + void entity::notify(const std::string& event, + const std::vector& arguments) const + { + this->context_->get_executer()->notify(event, this->get_entity_id(), arguments); + } + + void entity::set(const std::string& field, const chaiscript::Boxed_Value& value) const + { + this->context_->get_executer()->set_entity_field(field, this->get_entity_id(), value); + } + + chaiscript::Boxed_Value entity::get(const std::string& field) const + { + return this->context_->get_executer()->get_entity_field(field, this->get_entity_id()); + } + } +} diff --git a/src/game/scripting/entity.hpp b/src/game/scripting/entity.hpp new file mode 100644 index 0000000..b9771b5 --- /dev/null +++ b/src/game/scripting/entity.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "game/game.hpp" + +namespace game +{ + namespace scripting + { + class context; + + class entity final + { + public: + entity(); + entity(const entity& other); + entity(context* context, unsigned int entity_id); + ~entity(); + + void on_notify(const std::string& event, + const std::function&)>& callback, + bool is_volatile) const; + + unsigned int get_entity_id() const; + game::native::scr_entref_t get_entity_reference() const; + + chaiscript::Boxed_Value call(const std::string& function, + const std::vector& arguments) const; + void notify(const std::string& event, const std::vector& arguments) const; + + void set(const std::string& field, const chaiscript::Boxed_Value& value) const; + chaiscript::Boxed_Value get(const std::string& field) const; + + private: + context* context_; + unsigned int entity_id_; + }; + } +} diff --git a/src/game/scripting/event.hpp b/src/game/scripting/event.hpp new file mode 100644 index 0000000..7b2160f --- /dev/null +++ b/src/game/scripting/event.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "game/game.hpp" + +namespace game +{ + namespace scripting + { + class event final + { + public: + std::string name; + unsigned int entity_id; + std::vector arguments; + }; + } +} diff --git a/src/game/scripting/event_handler.cpp b/src/game/scripting/event_handler.cpp new file mode 100644 index 0000000..554440b --- /dev/null +++ b/src/game/scripting/event_handler.cpp @@ -0,0 +1,76 @@ +#include "std_include.hpp" +#include "context.hpp" + +namespace game +{ + namespace scripting + { + event_handler::event_handler(context* context) : context_(context) + { + } + + void event_handler::dispatch(event* event) + { + try + { + std::vector arguments; + + for (const auto& argument : event->arguments) + { + arguments.push_back(this->context_->get_parameters()->load(argument)); + } + + this->dispatch_to_specific_listeners(event, arguments); + this->dispatch_to_generic_listeners(event, arguments); + } + catch (chaiscript::exception::eval_error& e) + { + throw std::runtime_error(e.pretty_print()); + } + } + + void event_handler::dispatch_to_specific_listeners(event* event, + const std::vector& arguments) + { + for (auto listener = this->event_listeners_.begin(); listener.is_valid(); ++listener) + { + if (listener->event == event->name && listener->entity_id == event->entity_id) + { + if (listener->is_volatile) + { + this->event_listeners_.remove(listener); + } + + listener->callback(arguments); + } + } + } + + void event_handler::dispatch_to_generic_listeners(event* event, + const std::vector& arguments) + { + for (auto listener = this->generic_event_listeners_.begin(); listener.is_valid(); ++listener) + { + if (listener->event == event->name) + { + if (listener->is_volatile) + { + this->generic_event_listeners_.remove(listener); + } + + listener->callback(entity(this->context_, event->entity_id), arguments); + } + } + } + + void event_handler::add_event_listener(const event_listener& listener) + { + this->event_listeners_.add(listener); + } + + void event_handler::add_event_listener(const generic_event_listener& listener) + { + this->generic_event_listeners_.add(listener); + } + } +} diff --git a/src/game/scripting/event_handler.hpp b/src/game/scripting/event_handler.hpp new file mode 100644 index 0000000..13d1dd0 --- /dev/null +++ b/src/game/scripting/event_handler.hpp @@ -0,0 +1,49 @@ +#pragma once +#include "utils/chain.hpp" +#include "entity.hpp" +#include "event.hpp" + +namespace game +{ + namespace scripting + { + class context; + + class event_listener final + { + public: + std::string event = {}; + unsigned int entity_id = 0; + std::function&)> callback = {}; + bool is_volatile = false; + }; + + class generic_event_listener final + { + public: + std::string event = {}; + std::function&)> callback = {}; + bool is_volatile = false; + }; + + class event_handler final + { + public: + explicit event_handler(context* context); + + void dispatch(event* event); + + void add_event_listener(const event_listener& listener); + void add_event_listener(const generic_event_listener& listener); + + private: + context* context_; + + void dispatch_to_specific_listeners(event* event, const std::vector& arguments); + void dispatch_to_generic_listeners(event* event, const std::vector& arguments); + + utils::chain event_listeners_; + utils::chain generic_event_listeners_; + }; + } +} diff --git a/src/game/scripting/executer.cpp b/src/game/scripting/executer.cpp new file mode 100644 index 0000000..87bbc24 --- /dev/null +++ b/src/game/scripting/executer.cpp @@ -0,0 +1,172 @@ +#include "std_include.hpp" +#include "game/game.hpp" +#include "utils/string.hpp" +#include "functions.hpp" +#include "stack_isolation.hpp" +#include "safe_executer.hpp" +#include "context.hpp" + +namespace game +{ + namespace scripting + { + executer::executer(context* context) : context_(context) + { + } + + int executer::get_field_id(const int classnum, const std::string& field) const + { + const auto field_name = utils::string::to_lower(field); + const auto class_id = native::g_classMap[classnum].id; + const auto field_str = native::SL_GetString(field_name.data(), 1); + const auto _ = gsl::finally([field_str]() + { + native::RemoveRefToValue(native::SCRIPT_STRING, {int(field_str)}); + }); + + const auto offset = native::FindVariable(class_id, field_str); + if (offset) + { + const auto index = 4 * (offset + 0xC800 * (class_id & 1)); + return PINT(SELECT_VALUE(0x1A3BC80, 0x1EFE180, 0x1DC8800))[index]; + } + + return -1; + } + + void executer::set_entity_field(const std::string& field, const unsigned int entity_id, + const chaiscript::Boxed_Value& value) + { + const auto entref = native::Scr_GetEntityIdRef(entity_id); + const int id = get_field_id(entref.raw.classnum, field); + + if (id != -1) + { + stack_isolation _; + this->context_->get_parameters()->push(value); + + native::scr_VmPub->outparamcount = native::scr_VmPub->inparamcount; + native::scr_VmPub->inparamcount = 0; + + if (!safe_executer::set_entity_field(entref, id)) + { + throw std::runtime_error("Failed to set value for field '" + field + "'"); + } + } + else + { + this->entity_fields_[entity_id][field] = value; + } + } + + chaiscript::Boxed_Value executer::get_entity_field(const std::string& field, const unsigned int entity_id) + { + const auto entref = native::Scr_GetEntityIdRef(entity_id); + const auto id = this->get_field_id(entref.raw.classnum, field); + + if (id != -1) + { + stack_isolation _; + + native::VariableValue value{}; + if (!safe_executer::get_entity_field(entref, id, &value)) + { + throw std::runtime_error("Failed to get value for field '" + field + "'"); + } + + const auto $ = gsl::finally([value]() + { + native::RemoveRefToValue(value.type, value.u); + }); + + return this->context_->get_parameters()->load(value); + } + else + { + const auto& map = this->entity_fields_[entity_id]; + const auto value = map.find(field); + if (value != map.end()) + { + return value->second; + } + } + + return {}; + } + + void executer::notify(const std::string& event, const unsigned int entity_id, + std::vector arguments) const + { + stack_isolation _; + + std::reverse(arguments.begin(), arguments.end()); + for (const auto& argument : arguments) + { + this->context_->get_parameters()->push(argument); + } + + const auto event_id = native::SL_GetString(event.data(), 0); + native::Scr_NotifyId(entity_id, event_id, native::scr_VmPub->inparamcount); + } + + chaiscript::Boxed_Value executer::call(const std::string& function, const unsigned int entity_id, + std::vector arguments) const + { + const auto function_index = find_function_index(function, entity_id == 0); + if (function_index < 0) + { + throw std::runtime_error("No function found for name '" + function + "'"); + } + + const auto entity = function_index > 0x1C7 + ? native::Scr_GetEntityIdRef(entity_id) + : native::scr_entref_t{~0u}; + + const auto function_ptr = native::Scr_GetFunc(function_index); + + stack_isolation _; + + std::reverse(arguments.begin(), arguments.end()); + for (const auto& argument : arguments) + { + this->context_->get_parameters()->push(argument); + } + + native::scr_VmPub->outparamcount = native::scr_VmPub->inparamcount; + native::scr_VmPub->inparamcount = 0; + + if (!safe_executer::call(function_ptr, entity)) + { + throw std::runtime_error("Error executing function '" + function + "'"); + } + + return this->context_->get_parameters()->get_return_value(); + } + + int executer::find_function_index(const std::string& function, const bool prefer_global) + { + const auto target = utils::string::to_lower(function); + + const auto primary_map = prefer_global + ? &global_function_map + : &instance_function_map; + const auto secondary_map = !prefer_global + ? &global_function_map + : &instance_function_map; + + auto function_entry = primary_map->find(target); + if (function_entry != primary_map->end()) + { + return function_entry->second; + } + + function_entry = secondary_map->find(target); + if (function_entry != secondary_map->end()) + { + return function_entry->second; + } + + return -1; + } + } +} diff --git a/src/game/scripting/executer.hpp b/src/game/scripting/executer.hpp new file mode 100644 index 0000000..32e1536 --- /dev/null +++ b/src/game/scripting/executer.hpp @@ -0,0 +1,34 @@ +#pragma once + +namespace game +{ + namespace scripting + { + class context; + + class executer final + { + public: + explicit executer(context* context); + + void set_entity_field(const std::string& field, unsigned int entity_id, + const chaiscript::Boxed_Value& value); + chaiscript::Boxed_Value get_entity_field(const std::string& field, unsigned int entity_id); + + void notify(const std::string& event, unsigned int entity_id, + std::vector arguments) const; + + chaiscript::Boxed_Value call(const std::string& function, unsigned int entity_id, + std::vector arguments) const; + + private: + context* context_; + + std::unordered_map> entity_fields_; + + int get_field_id(int classnum, const std::string& field) const; + + static int find_function_index(const std::string& function, bool prefer_global); + }; + } +} diff --git a/src/game/scripting/parameters.cpp b/src/game/scripting/parameters.cpp new file mode 100644 index 0000000..7cb427f --- /dev/null +++ b/src/game/scripting/parameters.cpp @@ -0,0 +1,152 @@ +#include "std_include.hpp" +#include "context.hpp" + +namespace game +{ + namespace scripting + { + parameters::parameters(context* context) : context_(context) + { + } + + chaiscript::Boxed_Value parameters::load(const native::VariableValue value) const + { + if (value.type == native::SCRIPT_STRING) + { + const std::string string = native::SL_ConvertToString(value.u.stringValue); + return chaiscript::var(string); + } + else if (value.type == native::SCRIPT_FLOAT) + { + return chaiscript::var(value.u.floatValue); + } + else if (value.type == native::SCRIPT_INTEGER) + { + return chaiscript::var(value.u.intValue); + } + else if (value.type == native::SCRIPT_OBJECT) + { + return chaiscript::var(entity(this->context_, value.u.entityId)); + } + else if (value.type == native::SCRIPT_VECTOR) + { + std::vector values; + values.push_back(chaiscript::var(value.u.vectorValue[0])); + values.push_back(chaiscript::var(value.u.vectorValue[1])); + values.push_back(chaiscript::var(value.u.vectorValue[2])); + + return chaiscript::var(values); + } + + return {}; + } + + void parameters::push(const chaiscript::Boxed_Value& value) const + { + if (native::scr_VmPub->outparamcount) + { + native::Scr_ClearOutParams(); + } + + if (native::scr_VmPub->top == native::scr_VmPub->maxstack) + { + throw std::runtime_error("Internal script stack overflow"); + } + + native::VariableValue* value_ptr = ++native::scr_VmPub->top; + ++native::scr_VmPub->inparamcount; + + value_ptr->type = native::SCRIPT_NONE; + value_ptr->u.intValue = 0; + + if (value.get_type_info() == typeid(float)) + { + const auto real_value = this->context_->get_chai()->boxed_cast(value); + value_ptr->type = native::SCRIPT_FLOAT; + value_ptr->u.floatValue = real_value; + } + else if (value.get_type_info() == typeid(double)) + { + const auto real_value = this->context_->get_chai()->boxed_cast(value); + value_ptr->type = native::SCRIPT_FLOAT; + value_ptr->u.floatValue = static_cast(real_value); + } + else if (value.get_type_info() == typeid(int)) + { + const auto real_value = this->context_->get_chai()->boxed_cast(value); + value_ptr->type = native::SCRIPT_INTEGER; + value_ptr->u.intValue = real_value; + } + else if (value.get_type_info() == typeid(bool)) + { + const auto real_value = this->context_->get_chai()->boxed_cast(value); + value_ptr->type = native::SCRIPT_INTEGER; + value_ptr->u.intValue = real_value; + } + else if (value.get_type_info() == typeid(entity)) + { + const auto real_value = this->context_->get_chai()->boxed_cast(value); + value_ptr->type = native::SCRIPT_OBJECT; + value_ptr->u.entityId = real_value.get_entity_id(); + + game::native::AddRefToValue(value_ptr); + } + else if (value.get_type_info() == typeid(std::string)) + { + const auto real_value = this->context_->get_chai()->boxed_cast(value); + value_ptr->type = native::SCRIPT_STRING; + value_ptr->u.stringValue = game::native::SL_GetString(real_value.data(), 0); + } + else if (value.get_type_info() == typeid(std::vector)) + { + float values[3]; + const auto real_value = this->context_->get_chai()->boxed_cast>(value); + if (real_value.size() != 3) + { + throw std::runtime_error("Invalid vector length. Size must be exactly 3"); + } + + const auto unbox_float = [&real_value, this](const size_t index) -> float + { + const auto value = real_value[index]; + if (value.get_type_info() == typeid(float)) + { + return this->context_->get_chai()->boxed_cast(value); + } + if (value.get_type_info() == typeid(double)) + { + return float(this->context_->get_chai()->boxed_cast(value)); + } + if (value.get_type_info() == typeid(int)) + { + return float(this->context_->get_chai()->boxed_cast(value)); + } + + throw std::runtime_error("Vector element at index " + std::to_string(index) + " is not a number"); + }; + + values[0] = unbox_float(0); + values[1] = unbox_float(1); + values[2] = unbox_float(2); + + value_ptr->type = native::SCRIPT_VECTOR; + value_ptr->u.vectorValue = native::Scr_AllocVector(values); + } + else + { + throw std::runtime_error("Unable to unbox value of type '" + value.get_type_info().bare_name() + "'"); + } + } + + chaiscript::Boxed_Value parameters::get_return_value() const + { + if (native::scr_VmPub->inparamcount == 0) return {}; + + native::Scr_ClearOutParams(); + native::scr_VmPub->outparamcount = native::scr_VmPub->inparamcount; + native::scr_VmPub->inparamcount = 0; + + return this->load(native::scr_VmPub->top[1 - native::scr_VmPub->outparamcount]); + } + } +} diff --git a/src/game/scripting/parameters.hpp b/src/game/scripting/parameters.hpp new file mode 100644 index 0000000..efcfd8c --- /dev/null +++ b/src/game/scripting/parameters.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "game/game.hpp" + +namespace game +{ + namespace scripting + { + class context; + + class parameters final + { + public: + explicit parameters(context* context); + + void push(const chaiscript::Boxed_Value& value) const; + chaiscript::Boxed_Value load(native::VariableValue value) const; + + chaiscript::Boxed_Value get_return_value() const; + private: + context* context_; + }; + } +} diff --git a/src/game/scripting/safe_executer.cpp b/src/game/scripting/safe_executer.cpp new file mode 100644 index 0000000..18c4567 --- /dev/null +++ b/src/game/scripting/safe_executer.cpp @@ -0,0 +1,63 @@ +#include "std_include.hpp" +#include "safe_executer.hpp" + +#pragma warning(push) +#pragma warning(disable: 4611) +namespace game +{ + namespace scripting + { + namespace safe_executer + { + static_assert(sizeof(jmp_buf) == 64); + + bool call(const native::scr_call_t function, const native::scr_entref_t entref) + { + *native::g_script_error_level += 1; + if (setjmp(native::g_script_error[*native::g_script_error_level])) + { + *native::g_script_error_level -= 1; + return false; + } + + function(entref.val); + + *native::g_script_error_level -= 1; + return true; + } + + bool set_entity_field(const native::scr_entref_t entref, const int offset) + { + *native::g_script_error_level += 1; + if (setjmp(native::g_script_error[*native::g_script_error_level])) + { + *native::g_script_error_level -= 1; + return false; + } + + native::Scr_SetObjectField(entref.raw.classnum, entref.raw.entnum, offset); + + *native::g_script_error_level -= 1; + return true; + } + + bool get_entity_field(const native::scr_entref_t entref, const int offset, native::VariableValue* value) + { + *native::g_script_error_level += 1; + if (setjmp(native::g_script_error[*native::g_script_error_level])) + { + value->type = native::SCRIPT_NONE; + value->u.intValue = 0; + *native::g_script_error_level -= 1; + return false; + } + + *value = native::GetEntityFieldValue(entref.raw.classnum, entref.raw.entnum, offset); + + *native::g_script_error_level -= 1; + return true; + } + } + } +} +#pragma warning(pop) diff --git a/src/game/scripting/safe_executer.hpp b/src/game/scripting/safe_executer.hpp new file mode 100644 index 0000000..89ae335 --- /dev/null +++ b/src/game/scripting/safe_executer.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "game/game.hpp" + +namespace game +{ + namespace scripting + { + namespace safe_executer + { + bool call(const native::scr_call_t function, const native::scr_entref_t entref); + bool set_entity_field(const native::scr_entref_t entref, const int offset); + bool get_entity_field(const native::scr_entref_t entref, const int offset, native::VariableValue* value); + } + } +} diff --git a/src/game/scripting/stack_isolation.cpp b/src/game/scripting/stack_isolation.cpp new file mode 100644 index 0000000..117c79d --- /dev/null +++ b/src/game/scripting/stack_isolation.cpp @@ -0,0 +1,30 @@ +#include "std_include.hpp" +#include "stack_isolation.hpp" + +namespace game +{ + namespace scripting + { + stack_isolation::stack_isolation() + { + this->in_param_count_ = native::scr_VmPub->inparamcount; + this->out_param_count_ = native::scr_VmPub->outparamcount; + this->top_ = native::scr_VmPub->top; + this->max_stack_ = native::scr_VmPub->maxstack; + + native::scr_VmPub->top = this->stack_; + native::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1]; + native::scr_VmPub->inparamcount = 0; + native::scr_VmPub->outparamcount = 0; + } + + stack_isolation::~stack_isolation() + { + native::Scr_ClearOutParams(); + native::scr_VmPub->inparamcount = this->in_param_count_; + native::scr_VmPub->outparamcount = this->out_param_count_; + native::scr_VmPub->top = this->top_; + native::scr_VmPub->maxstack = this->max_stack_; + } + } +} diff --git a/src/game/scripting/stack_isolation.hpp b/src/game/scripting/stack_isolation.hpp new file mode 100644 index 0000000..35777f5 --- /dev/null +++ b/src/game/scripting/stack_isolation.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "game/game.hpp" + +namespace game +{ + namespace scripting + { + class stack_isolation final + { + public: + stack_isolation(); + ~stack_isolation(); + + private: + native::VariableValue stack_[512]{}; + + native::VariableValue* max_stack_; + native::VariableValue* top_; + unsigned int in_param_count_; + unsigned int out_param_count_; + }; + } +} diff --git a/src/game/scripting/variable_value.cpp b/src/game/scripting/variable_value.cpp new file mode 100644 index 0000000..77eaf8d --- /dev/null +++ b/src/game/scripting/variable_value.cpp @@ -0,0 +1,23 @@ +#include "std_include.hpp" +#include "variable_value.hpp" + +namespace game +{ + namespace scripting + { + variable_value::variable_value(native::VariableValue value) : value_(value) + { + native::AddRefToValue(&value); + } + + variable_value::~variable_value() + { + native::RemoveRefToValue(this->value_.type, this->value_.u); + } + + variable_value::operator native::VariableValue() const + { + return this->value_; + } + } +} diff --git a/src/game/scripting/variable_value.hpp b/src/game/scripting/variable_value.hpp new file mode 100644 index 0000000..2a906bd --- /dev/null +++ b/src/game/scripting/variable_value.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "game/game.hpp" + +namespace game +{ + namespace scripting + { + class variable_value final + { + public: + explicit variable_value(native::VariableValue value); + ~variable_value(); + + explicit operator native::VariableValue() const; + + private: + native::VariableValue value_; + }; + } +} diff --git a/src/module/notification.cpp b/src/module/notification.cpp index f7d700c..6ad3a05 100644 --- a/src/module/notification.cpp +++ b/src/module/notification.cpp @@ -4,7 +4,7 @@ #include "utils/hook.hpp" std::mutex notification::mutex_; -std::vector> notification::callbacks_; +std::vector> notification::callbacks_; void notification::post_load() { @@ -25,7 +25,7 @@ void notification::pre_destroy() cleanup(); } -void notification::listen(const std::function& callback) +void notification::listen(const std::function& callback) { std::lock_guard _(mutex_); callbacks_.push_back(callback); @@ -37,7 +37,7 @@ void notification::cleanup() callbacks_.clear(); } -void notification::dispatch(event* event) +void notification::dispatch(game::scripting::event* event) { decltype(callbacks_) copy; { @@ -56,7 +56,7 @@ void notification::vm_notify_stub(const unsigned int notify_id, const unsigned s { try { - event e; + game::scripting::event e; e.name = game::native::SL_ConvertToString(type); e.entity_id = notify_id; diff --git a/src/module/notification.hpp b/src/module/notification.hpp index b205623..d81b9e4 100644 --- a/src/module/notification.hpp +++ b/src/module/notification.hpp @@ -1,30 +1,22 @@ #pragma once #include "loader/module_loader.hpp" +#include "game/scripting/event.hpp" #include "scripting.hpp" class notification final : public module { public: - class event final - { - public: - std::string name; - unsigned int entity_id; - //std::vector arguments; - std::vector arguments; - }; - void post_load() override; void pre_destroy() override; - static void listen(const std::function& callback); + static void listen(const std::function& callback); private: static std::mutex mutex_; - static std::vector> callbacks_; + static std::vector> callbacks_; static void cleanup(); - static void dispatch(event* event); + static void dispatch(game::scripting::event* event); static void vm_notify_stub(unsigned int notify_id, unsigned short type, game::native::VariableValue* stack); }; diff --git a/src/module/scripting.cpp b/src/module/scripting.cpp index 065997a..53babe7 100644 --- a/src/module/scripting.cpp +++ b/src/module/scripting.cpp @@ -1,9 +1,7 @@ #include #include "notification.hpp" #include "utils/io.hpp" -#include "utils/string.hpp" #include "scheduler.hpp" -#include "game/scripting/functions.hpp" utils::hook scripting::start_hook_; utils::hook scripting::stop_hook_; @@ -12,123 +10,20 @@ std::mutex scripting::mutex_; std::vector> scripting::start_callbacks_; std::vector> scripting::stop_callbacks_; -scripting::entity::entity() : entity(nullptr, 0) -{ -} - -scripting::entity::entity(const entity& other) : entity(other.environment_, other.entity_id_) -{ -} - -scripting::entity::entity(scripting* environment, const unsigned int entity_id) : environment_(environment), - entity_id_(entity_id) -{ - if (this->entity_id_) - { - game::native::VariableValue value{}; - value.type = game::native::SCRIPT_OBJECT; - value.u.entityId = this->entity_id_; - game::native::AddRefToValue(&value); - } -} - -scripting::entity::~entity() -{ - if (this->entity_id_) - { - game::native::RemoveRefToValue(game::native::SCRIPT_OBJECT, {static_cast(this->entity_id_)}); - } -} - -void scripting::entity::on_notify(const std::string& event, - const std::function&)>& callback, - const bool is_volatile) -const -{ - event_listener listener; - listener.event = event; - listener.callback = callback; - listener.entity_id = this->entity_id_; - listener.is_volatile = is_volatile; - - this->environment_->add_event_listener(listener); -} - -unsigned int scripting::entity::get_entity_id() const -{ - return this->entity_id_; -} - -game::native::scr_entref_t scripting::entity::get_entity_reference() const -{ - return game::native::Scr_GetEntityIdRef(this->get_entity_id()); -} - -chaiscript::Boxed_Value scripting::entity::call(const std::string& function, - const std::vector& arguments) const -{ - return this->environment_->call(function, this->get_entity_id(), arguments); -} - -void scripting::entity::notify(const std::string& event, const std::vector& arguments) const -{ - this->environment_->notify(event, this->get_entity_id(), arguments); -} - -void scripting::entity::set(const std::string& field, const chaiscript::Boxed_Value& value) const -{ - this->environment_->set_entity_field(field, this->get_entity_id(), value); -} - -chaiscript::Boxed_Value scripting::entity::get(const std::string& field) const -{ - return this->environment_->get_entity_field(field, this->get_entity_id()); -} - -scripting::variable::variable(game::native::VariableValue value) : value_(value) -{ - game::native::AddRefToValue(&value); -} - -scripting::variable::~variable() -{ - game::native::RemoveRefToValue(this->value_.type, this->value_.u); -} - -scripting::variable::operator game::native::VariableValue() const -{ - return this->value_; -} - -scripting::stack_context::stack_context() -{ - this->in_param_count_ = game::native::scr_VmPub->inparamcount; - this->out_param_count_ = game::native::scr_VmPub->outparamcount; - this->top_ = game::native::scr_VmPub->top; - this->max_stack_ = game::native::scr_VmPub->maxstack; - - game::native::scr_VmPub->top = this->stack_; - game::native::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1]; - game::native::scr_VmPub->inparamcount = 0; - game::native::scr_VmPub->outparamcount = 0; -} - -scripting::stack_context::~stack_context() -{ - game::native::Scr_ClearOutParams(); - game::native::scr_VmPub->inparamcount = this->in_param_count_; - game::native::scr_VmPub->outparamcount = this->out_param_count_; - game::native::scr_VmPub->top = this->top_; - game::native::scr_VmPub->maxstack = this->max_stack_; -} - void scripting::post_start() { on_start([this]() { try { - this->initialize(); + this->load_scripts(); + notification::listen([this](game::scripting::event* event) + { + for(const auto& script : this->scripts_) + { + script->get_event_handler()->dispatch(event); + } + }); } catch (std::exception& e) { @@ -137,7 +32,7 @@ void scripting::post_start() }); on_stop([this]() { - this->chai_ = {}; + this->scripts_.clear(); }); } @@ -158,290 +53,12 @@ void scripting::post_load() void scripting::pre_destroy() { - this->chai_ = {}; + this->scripts_.clear(); start_callbacks_.clear(); stop_callbacks_.clear(); } -void scripting::add_event_listener(const event_listener& listener) -{ - this->event_listeners_.add(listener); -} - -void scripting::initialize() -{ - this->chai_ = std::make_unique(); - this->chai_->add(chaiscript::fun([](const std::string& string) - { - printf("%s\n", string.data()); - }), "print"); - - this->chai_->add(chaiscript::fun([](const std::string& string) - { - MessageBoxA(nullptr, string.data(), nullptr, 0); - }), "alert"); - - this->initialize_entity(); - - const auto level_id = *game::native::levelEntityId; - this->chai_->add_global(chaiscript::var(entity(this, level_id)), "level"); - - this->load_scripts(); - - notification::listen([this](notification::event* event) - { - try - { - std::vector arguments; - - for (const auto& argument : event->arguments) - { - arguments.push_back(this->make_boxed(argument)); - } - - for (auto listener = this->event_listeners_.begin(); listener.is_valid(); ++listener) - { - if (listener->event == event->name && listener->entity_id == event->entity_id) - { - if (listener->is_volatile) - { - this->event_listeners_.remove(listener); - } - - listener->callback(arguments); - } - } - - for (auto listener = this->generic_event_listeners_.begin(); listener.is_valid(); ++listener) - { - if (listener->event == event->name) - { - if (listener->is_volatile) - { - this->generic_event_listeners_.remove(listener); - } - - listener->callback(entity(this, event->entity_id), arguments); - } - } - } - catch (chaiscript::exception::eval_error& e) - { - throw std::runtime_error(e.pretty_print()); - } - }); -} - -void scripting::initialize_entity() -{ - this->chai_->add(chaiscript::user_type(), "entity"); - this->chai_->add(chaiscript::constructor(), "entity"); - this->chai_->add(chaiscript::constructor(), "entity"); - - this->chai_->add(chaiscript::fun(&entity::get), "get"); - this->chai_->add(chaiscript::fun(&entity::set), "set"); - - this->chai_->add(chaiscript::fun(&entity::on_notify), "onNotify"); - this->chai_->add(chaiscript::fun([](const entity& ent, const std::string& event, - const std::function&)>& - callback) - { - return ent.on_notify(event, callback, false); - }), "onNotify"); - - this->chai_->add(chaiscript::fun([](entity& lhs, const entity& rhs) -> entity& - { - return lhs = rhs; - }), "="); - - this->chai_->add(chaiscript::fun([this](const std::string& event, - const std::function&)>& - callback) - { - generic_event_listener listener; - listener.event = event; - listener.is_volatile = false; - listener.callback = callback; - - this->generic_event_listeners_.add(listener); - }), "onNotify"); - - this->chai_->add(chaiscript::fun([this](const std::string& event, - const std::function&)>& - callback, const bool is_volatile) - { - generic_event_listener listener; - listener.event = event; - listener.is_volatile = is_volatile; - listener.callback = callback; - - this->generic_event_listeners_.add(listener); - }), "onNotify"); - - // Notification - this->chai_->add(chaiscript::fun(&entity::notify), "vectorNotify"); - this->chai_->add(chaiscript::fun([](const entity& ent, const std::string& event) - { - return ent.notify(event, {}); - }), "notify"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& event, - const chaiscript::Boxed_Value& a1) - { - return ent.notify(event, {a1}); - }), "notify"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& event, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2) - { - return ent.notify(event, {a1, a2}); - }), "notify"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& event, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3) - { - return ent.notify(event, {a1, a2, a3}); - }), "notify"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& event, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3, - const chaiscript::Boxed_Value& a4) - { - return ent.notify(event, {a1, a2, a3, a4}); - }), "notify"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& event, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3, - const chaiscript::Boxed_Value& a4, - const chaiscript::Boxed_Value& a5) - { - return ent.notify(event, {a1, a2, a3, a4, a5}); - }), "notify"); - - // Instance call - this->chai_->add(chaiscript::fun(&entity::call), "vectorCall"); - this->chai_->add(chaiscript::fun([](const entity& ent, const std::string& function) - { - return ent.call(function, {}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& function, - const chaiscript::Boxed_Value& a1) - { - return ent.call(function, {a1}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2) - { - return ent.call(function, {a1, a2}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3) - { - return ent.call(function, {a1, a2, a3}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3, - const chaiscript::Boxed_Value& a4) - { - return ent.call(function, {a1, a2, a3, a4}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [](const entity& ent, const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3, - const chaiscript::Boxed_Value& a4, - const chaiscript::Boxed_Value& a5) - { - return ent.call(function, {a1, a2, a3, a4, a5}); - }), "call"); - - // Global call - this->chai_->add(chaiscript::fun( - [this](const std::string& function, const std::vector& arguments) - { - return this->call(function, 0, arguments); - }), "vectorCall"); - this->chai_->add(chaiscript::fun([this](const std::string& function) - { - return this->call(function, 0, {}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [this](const std::string& function, - const chaiscript::Boxed_Value& a1) - { - return this->call(function, 0, {a1}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [this](const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2) - { - return this->call(function, 0, {a1, a2}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [this](const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3) - { - return this->call(function, 0, {a1, a2, a3}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [this](const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3, - const chaiscript::Boxed_Value& a4) - { - return this->call(function, 0, {a1, a2, a3, a4}); - }), "call"); - - this->chai_->add(chaiscript::fun( - [this](const std::string& function, - const chaiscript::Boxed_Value& a1, - const chaiscript::Boxed_Value& a2, - const chaiscript::Boxed_Value& a3, - const chaiscript::Boxed_Value& a4, - const chaiscript::Boxed_Value& a5) - { - return this->call(function, 0, {a1, a2, a3, a4, a5}); - }), "call"); -} - -void scripting::load_scripts() const +void scripting::load_scripts() { const auto scripts = utils::io::list_files("open-iw5/scripts/"); @@ -451,7 +68,9 @@ void scripting::load_scripts() const { try { - this->chai_->eval_file(script); + auto context = std::make_unique(); + context->get_chai()->eval_file(script); + this->scripts_.push_back(std::move(context)); } catch (chaiscript::exception::eval_error& e) { @@ -461,38 +80,6 @@ void scripting::load_scripts() const } } -chaiscript::Boxed_Value scripting::make_boxed(const game::native::VariableValue value) -{ - if (value.type == game::native::SCRIPT_STRING) - { - const std::string string = game::native::SL_ConvertToString(value.u.stringValue); - return chaiscript::var(string); - } - else if (value.type == game::native::SCRIPT_FLOAT) - { - return chaiscript::var(value.u.floatValue); - } - else if (value.type == game::native::SCRIPT_INTEGER) - { - return chaiscript::var(value.u.intValue); - } - else if (value.type == game::native::SCRIPT_OBJECT) - { - return chaiscript::var(entity(this, value.u.entityId)); - } - else if (value.type == game::native::SCRIPT_VECTOR) - { - std::vector values; - values.push_back(chaiscript::var(value.u.vectorValue[0])); - values.push_back(chaiscript::var(value.u.vectorValue[1])); - values.push_back(chaiscript::var(value.u.vectorValue[2])); - - return chaiscript::var(values); - } - - return {}; -} - void scripting::on_start(const std::function& callback) { std::lock_guard _(mutex_); @@ -543,320 +130,4 @@ void scripting::stop_execution() } } -int scripting::get_field_id(const int classnum, const std::string& field) const -{ - const auto field_name = utils::string::to_lower(field); - const auto class_id = game::native::g_classMap[classnum].id; - const auto field_str = game::native::SL_GetString(field_name.data(), 1); - const auto _ = gsl::finally([field_str]() - { - game::native::RemoveRefToValue(game::native::SCRIPT_STRING, {int(field_str)}); - }); - - const auto offset = game::native::FindVariable(class_id, field_str); - if (offset) - { - const auto index = 4 * (offset + 0xC800 * (class_id & 1)); - return PINT(SELECT_VALUE(0x1A3BC80, 0x1EFE180, 0x1DC8800))[index]; - } - - return -1; -} - -void scripting::set_entity_field(const std::string& field, const unsigned int entity_id, - const chaiscript::Boxed_Value& value) -{ - const auto entref = game::native::Scr_GetEntityIdRef(entity_id); - const int id = get_field_id(entref.raw.classnum, field); - - if (id != -1) - { - stack_context _; - this->push_param(value); - - game::native::scr_VmPub->outparamcount = game::native::scr_VmPub->inparamcount; - game::native::scr_VmPub->inparamcount = 0; - - if (!set_entity_field_safe(entref, id)) - { - throw std::runtime_error("Failed to set value for field '" + field + "'"); - } - } - else - { - this->entity_fields_[entity_id][field] = value; - } -} - -chaiscript::Boxed_Value scripting::get_entity_field(const std::string& field, const unsigned int entity_id) -{ - const auto entref = game::native::Scr_GetEntityIdRef(entity_id); - const auto id = this->get_field_id(entref.raw.classnum, field); - - if (id != -1) - { - stack_context _; - - game::native::VariableValue value{}; - if (!get_entity_field_safe(entref, id, &value)) - { - throw std::runtime_error("Failed to get value for field '" + field + "'"); - } - - const auto $ = gsl::finally([value]() - { - game::native::RemoveRefToValue(value.type, value.u); - }); - - return this->make_boxed(value); - } - else - { - const auto& map = this->entity_fields_[entity_id]; - const auto value = map.find(field); - if (value != map.end()) - { - return value->second; - } - } - - return {}; -} - -void scripting::notify(const std::string& event, const unsigned int entity_id, - std::vector arguments) const -{ - stack_context _; - - std::reverse(arguments.begin(), arguments.end()); - for (const auto& argument : arguments) - { - this->push_param(argument); - } - - const auto event_id = game::native::SL_GetString(event.data(), 0); - game::native::Scr_NotifyId(entity_id, event_id, game::native::scr_VmPub->inparamcount); -} - -chaiscript::Boxed_Value scripting::call(const std::string& function, const unsigned int entity_id, - std::vector arguments) -{ - const auto function_index = find_function_index(function, entity_id == 0); - if (function_index < 0) - { - throw std::runtime_error("No function found for name '" + function + "'"); - } - - const auto entity = function_index > 0x1C7 - ? game::native::Scr_GetEntityIdRef(entity_id) - : game::native::scr_entref_t{~0u}; - - const auto function_ptr = game::native::Scr_GetFunc(function_index); - - stack_context _; - - std::reverse(arguments.begin(), arguments.end()); - for (const auto& argument : arguments) - { - this->push_param(argument); - } - - game::native::scr_VmPub->outparamcount = game::native::scr_VmPub->inparamcount; - game::native::scr_VmPub->inparamcount = 0; - - if (!call_safe(function_ptr, entity)) - { - throw std::runtime_error("Error executing function '" + function + "'"); - } - - return this->get_return_value(); -} - -#pragma warning(push) -#pragma warning(disable: 4611) -bool scripting::call_safe(const game::native::scr_call_t function, const game::native::scr_entref_t entref) -{ - static_assert(sizeof(jmp_buf) == 64); - - *game::native::g_script_error_level += 1; - if (setjmp(game::native::g_script_error[*game::native::g_script_error_level])) - { - *game::native::g_script_error_level -= 1; - return false; - } - - function(entref.val); - - *game::native::g_script_error_level -= 1; - return true; -} - -bool scripting::set_entity_field_safe(game::native::scr_entref_t entref, int offset) -{ - *game::native::g_script_error_level += 1; - if (setjmp(game::native::g_script_error[*game::native::g_script_error_level])) - { - *game::native::g_script_error_level -= 1; - return false; - } - - game::native::Scr_SetObjectField(entref.raw.classnum, entref.raw.entnum, offset); - - *game::native::g_script_error_level -= 1; - return true; -} - -bool scripting::get_entity_field_safe(game::native::scr_entref_t entref, int offset, game::native::VariableValue* value) -{ - *game::native::g_script_error_level += 1; - if (setjmp(game::native::g_script_error[*game::native::g_script_error_level])) - { - value->type = game::native::SCRIPT_NONE; - value->u.intValue = 0; - *game::native::g_script_error_level -= 1; - return false; - } - - *value = game::native::GetEntityFieldValue(entref.raw.classnum, entref.raw.entnum, offset); - - *game::native::g_script_error_level -= 1; - return true; -} -#pragma warning(pop) - -int scripting::find_function_index(const std::string& function, const bool prefer_global) -{ - const auto target = utils::string::to_lower(function); - - const auto primary_map = prefer_global - ? &game::scripting::global_function_map - : &game::scripting::instance_function_map; - const auto secondary_map = !prefer_global - ? &game::scripting::global_function_map - : &game::scripting::instance_function_map; - - auto function_entry = primary_map->find(target); - if (function_entry != primary_map->end()) - { - return function_entry->second; - } - - function_entry = secondary_map->find(target); - if (function_entry != secondary_map->end()) - { - return function_entry->second; - } - - return -1; -} - -void scripting::push_param(const chaiscript::Boxed_Value& value) const -{ - if (game::native::scr_VmPub->outparamcount) - { - game::native::Scr_ClearOutParams(); - } - - if (game::native::scr_VmPub->top == game::native::scr_VmPub->maxstack) - { - throw std::runtime_error("Internal script stack overflow"); - } - - game::native::VariableValue* value_ptr = ++game::native::scr_VmPub->top; - ++game::native::scr_VmPub->inparamcount; - - value_ptr->type = game::native::SCRIPT_NONE; - value_ptr->u.intValue = 0; - - if (value.get_type_info() == typeid(float)) - { - const auto real_value = this->chai_->boxed_cast(value); - value_ptr->type = game::native::SCRIPT_FLOAT; - value_ptr->u.floatValue = real_value; - } - else if (value.get_type_info() == typeid(double)) - { - const auto real_value = this->chai_->boxed_cast(value); - value_ptr->type = game::native::SCRIPT_FLOAT; - value_ptr->u.floatValue = static_cast(real_value); - } - else if (value.get_type_info() == typeid(int)) - { - const auto real_value = this->chai_->boxed_cast(value); - value_ptr->type = game::native::SCRIPT_INTEGER; - value_ptr->u.intValue = real_value; - } - else if (value.get_type_info() == typeid(bool)) - { - const auto real_value = this->chai_->boxed_cast(value); - value_ptr->type = game::native::SCRIPT_INTEGER; - value_ptr->u.intValue = real_value; - } - else if (value.get_type_info() == typeid(entity)) - { - const auto real_value = this->chai_->boxed_cast(value); - value_ptr->type = game::native::SCRIPT_OBJECT; - value_ptr->u.entityId = real_value.get_entity_id(); - - game::native::AddRefToValue(value_ptr); - } - else if (value.get_type_info() == typeid(std::string)) - { - const auto real_value = this->chai_->boxed_cast(value); - value_ptr->type = game::native::SCRIPT_STRING; - value_ptr->u.stringValue = game::native::SL_GetString(real_value.data(), 0); - } - else if (value.get_type_info() == typeid(std::vector)) - { - float values[3]; - const auto real_value = this->chai_->boxed_cast>(value); - if (real_value.size() != 3) - { - throw std::runtime_error("Invalid vector length. Size must be exactly 3"); - } - - const auto unbox_float = [&real_value, this](const size_t index) -> float - { - const auto value = real_value[index]; - if (value.get_type_info() == typeid(float)) - { - return this->chai_->boxed_cast(value); - } - else if (value.get_type_info() == typeid(double)) - { - return float(this->chai_->boxed_cast(value)); - } - else if (value.get_type_info() == typeid(int)) - { - return float(this->chai_->boxed_cast(value)); - } - - throw std::runtime_error("Vector element at index " + std::to_string(index) + " is not a number"); - }; - - values[0] = unbox_float(0); - values[1] = unbox_float(1); - values[2] = unbox_float(2); - - value_ptr->type = game::native::SCRIPT_VECTOR; - value_ptr->u.vectorValue = game::native::Scr_AllocVector(values); - } - else - { - throw std::runtime_error("Unable to unbox value of type '" + value.get_type_info().bare_name() + "'"); - } -} - -chaiscript::Boxed_Value scripting::get_return_value() -{ - if (game::native::scr_VmPub->inparamcount == 0) return {}; - - game::native::Scr_ClearOutParams(); - game::native::scr_VmPub->outparamcount = game::native::scr_VmPub->inparamcount; - game::native::scr_VmPub->inparamcount = 0; - - return this->make_boxed(game::native::scr_VmPub->top[1 - game::native::scr_VmPub->outparamcount]); -} - - REGISTER_MODULE(scripting) diff --git a/src/module/scripting.hpp b/src/module/scripting.hpp index feda93a..5b4999a 100644 --- a/src/module/scripting.hpp +++ b/src/module/scripting.hpp @@ -1,67 +1,11 @@ #pragma once #include "loader/module_loader.hpp" -#include "game/game.hpp" #include "utils/hook.hpp" -#include "utils/chain.hpp" +#include "game/scripting/context.hpp" class scripting final : public module { public: - class entity final - { - public: - entity(); - entity(const entity& other); - entity(scripting* environment, unsigned int entity_id); - ~entity(); - - void on_notify(const std::string& event, - const std::function&)>& callback, - bool is_volatile) const; - - unsigned int get_entity_id() const; - game::native::scr_entref_t get_entity_reference() const; - - chaiscript::Boxed_Value call(const std::string& function, const std::vector& arguments) const; - void notify(const std::string& event, const std::vector& arguments) const; - - void set(const std::string& field, const chaiscript::Boxed_Value& value) const; - chaiscript::Boxed_Value get(const std::string& field) const; - - private: - scripting* environment_; - unsigned int entity_id_; - }; - - class variable final - { - public: - variable(game::native::VariableValue value); - ~variable(); - - operator game::native::VariableValue() const; - - private: - game::native::VariableValue value_; - }; - - class event_listener final - { - public: - std::string event = {}; - unsigned int entity_id = 0; - std::function&)> callback = {}; - bool is_volatile = false; - }; - - class generic_event_listener final - { - public: - std::string event = {}; - std::function&)> callback = {}; - bool is_volatile = false; - }; - void post_start() override; void post_load() override; void pre_destroy() override; @@ -72,34 +16,9 @@ public: static void propagate_error(const std::exception& e); private: - class stack_context final - { - public: - stack_context(); - ~stack_context(); + std::vector> scripts_; - private: - game::native::VariableValue stack_[512]{}; - - game::native::VariableValue *max_stack_; - game::native::VariableValue *top_; - unsigned int in_param_count_; - unsigned int out_param_count_; - }; - - std::unique_ptr chai_; - utils::chain event_listeners_; - utils::chain generic_event_listeners_; - - std::unordered_map> entity_fields_; - - void add_event_listener(const event_listener& listener); - - void initialize(); - void initialize_entity(); - void load_scripts() const; - - chaiscript::Boxed_Value make_boxed(game::native::VariableValue value); + void load_scripts(); static utils::hook start_hook_; static utils::hook stop_hook_; @@ -110,19 +29,4 @@ private: static void start_execution(); static void stop_execution(); - - int get_field_id(int classnum, const std::string& field) const; - void set_entity_field(const std::string& field, unsigned int entity_id, const chaiscript::Boxed_Value& value); - chaiscript::Boxed_Value get_entity_field(const std::string& field, unsigned int entity_id); - - static bool set_entity_field_safe(game::native::scr_entref_t entref, int offset); - static bool get_entity_field_safe(game::native::scr_entref_t entref, int offset, game::native::VariableValue* value); - - void notify(const std::string& event, unsigned int entity_id, std::vector arguments) const; - - void push_param(const chaiscript::Boxed_Value& value) const; - chaiscript::Boxed_Value get_return_value(); - chaiscript::Boxed_Value call(const std::string& function, unsigned int entity_id, std::vector arguments); - static bool call_safe(game::native::scr_call_t function, game::native::scr_entref_t entref); - static int find_function_index(const std::string& function, bool prefer_global); };