Scripting progress
This commit is contained in:
parent
e2d5c93407
commit
3498944714
@ -7,6 +7,8 @@ namespace game
|
||||
{
|
||||
Cmd_AddCommand_t Cmd_AddCommand;
|
||||
|
||||
Com_Error_t Com_Error;
|
||||
|
||||
Conbuf_AppendText_t Conbuf_AppendText;
|
||||
|
||||
DB_LoadXAssets_t DB_LoadXAssets;
|
||||
@ -26,6 +28,8 @@ namespace game
|
||||
short* scrVarGlob;
|
||||
char** scrMemTreePub;
|
||||
|
||||
unsigned int* levelEntityId;
|
||||
|
||||
void AddRefToValue(VariableValue* value)
|
||||
{
|
||||
if (value->type == SCRIPT_OBJECT)
|
||||
@ -92,6 +96,8 @@ namespace game
|
||||
|
||||
native::Cmd_AddCommand = native::Cmd_AddCommand_t(SELECT_VALUE(0x558820, 0x545DF0, 0));
|
||||
|
||||
native::Com_Error = native::Com_Error_t(SELECT_VALUE(0x425540, 0x555450, 0x4D93F0));
|
||||
|
||||
native::Conbuf_AppendText = native::Conbuf_AppendText_t(SELECT_VALUE(0x4C84E0, 0x5CF610, 0x53C790));
|
||||
|
||||
native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770));
|
||||
@ -110,5 +116,7 @@ namespace game
|
||||
|
||||
native::scrVarGlob = reinterpret_cast<short*>(SELECT_VALUE(0x19AFC80, 0x1E72180, 0x1D3C800));
|
||||
native::scrMemTreePub = reinterpret_cast<char**>(SELECT_VALUE(0x196FB00, 0x1E32000, 0x1C152A4));
|
||||
|
||||
native::levelEntityId = reinterpret_cast<unsigned int*>(SELECT_VALUE(0x1BCBCA4, 0x208E1A4, 0x1CD873C));
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ namespace game
|
||||
typedef void (*Cmd_AddCommand_t)(const char* cmdName, void (*function)(), cmd_function_t* allocedCmd);
|
||||
extern Cmd_AddCommand_t Cmd_AddCommand;
|
||||
|
||||
typedef void (*Com_Error_t)(int code, const char *fmt, ...);
|
||||
extern Com_Error_t Com_Error;
|
||||
|
||||
typedef void (*Conbuf_AppendText_t)(const char* message);
|
||||
extern Conbuf_AppendText_t Conbuf_AppendText;
|
||||
|
||||
@ -37,6 +40,8 @@ namespace game
|
||||
extern short* scrVarGlob;
|
||||
extern char** scrMemTreePub;
|
||||
|
||||
extern unsigned int* levelEntityId;
|
||||
|
||||
void AddRefToValue(VariableValue* value);
|
||||
|
||||
scr_entref_t Scr_GetEntityIdRef(unsigned int id);
|
||||
|
@ -2,6 +2,8 @@
|
||||
#include "loader/module_loader.hpp"
|
||||
#include "notification.hpp"
|
||||
#include "utils/hook.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
std::mutex notification::mutex_;
|
||||
std::vector<std::function<void(notification::event*)>> notification::callbacks_;
|
||||
@ -45,7 +47,7 @@ void notification::dispatch(event* event)
|
||||
copy = callbacks_;
|
||||
}
|
||||
|
||||
for(const auto& callback : copy)
|
||||
for (const auto& callback : copy)
|
||||
{
|
||||
callback(event);
|
||||
}
|
||||
@ -54,18 +56,29 @@ void notification::dispatch(event* event)
|
||||
void notification::vm_notify_stub(const unsigned int notify_id, const unsigned short type,
|
||||
game::native::VariableValue* stack)
|
||||
{
|
||||
event e;
|
||||
e.name = game::native::SL_ConvertToString(type);
|
||||
e.entity = game::native::Scr_GetEntityIdRef(notify_id);
|
||||
|
||||
for (auto value = stack; value->type != game::native::SCRIPT_END; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
|
||||
dispatch(&e);
|
||||
|
||||
game::native::VM_Notify(notify_id, type, stack);
|
||||
|
||||
try
|
||||
{
|
||||
event e;
|
||||
e.name = game::native::SL_ConvertToString(type);
|
||||
e.entity_id = notify_id;
|
||||
|
||||
if (e.name == "touch") return; // Skip that for now
|
||||
|
||||
//printf("%X %X: %s\n", e.entity_id, *game::native::levelEntityId, e.name.data());
|
||||
|
||||
for (auto value = stack; value->type != game::native::SCRIPT_END; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
|
||||
dispatch(&e);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
scheduler::error(e.what(), 5);
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_MODULE(notification)
|
||||
|
@ -9,7 +9,7 @@ public:
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
game::native::scr_entref_t entity;
|
||||
unsigned int entity_id;
|
||||
//std::vector<scripting::variable> arguments;
|
||||
std::vector<game::native::VariableValue> arguments;
|
||||
};
|
||||
|
@ -1,7 +1,11 @@
|
||||
#include <std_include.hpp>
|
||||
#include "scheduler.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "game/structs.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
std::mutex scheduler::mutex_;
|
||||
std::queue<std::pair<std::string, int>> scheduler::errors_;
|
||||
std::vector<std::function<void()>> scheduler::callbacks_;
|
||||
std::vector<std::function<void()>> scheduler::single_callbacks_;
|
||||
|
||||
@ -17,7 +21,23 @@ void scheduler::once(const std::function<void()>& callback)
|
||||
single_callbacks_.push_back(callback);
|
||||
}
|
||||
|
||||
void scheduler::execute()
|
||||
void scheduler::error(const std::string& message, int level)
|
||||
{
|
||||
std::lock_guard _(mutex_);
|
||||
errors_.emplace(message, level);
|
||||
}
|
||||
|
||||
__declspec(naked) void scheduler::execute()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
call execute_error
|
||||
call execute_safe
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
void scheduler::execute_safe()
|
||||
{
|
||||
std::vector<std::function<void()>> callbacks_copy;
|
||||
std::vector<std::function<void()>> single_callbacks_copy;
|
||||
@ -40,6 +60,35 @@ void scheduler::execute()
|
||||
}
|
||||
}
|
||||
|
||||
void scheduler::execute_error()
|
||||
{
|
||||
const char* message;
|
||||
int level;
|
||||
|
||||
if(get_next_error(&message, &level) && message)
|
||||
{
|
||||
game::native::Com_Error(level, "%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
bool scheduler::get_next_error(const char** error_message, int* error_level)
|
||||
{
|
||||
std::lock_guard _(mutex_);
|
||||
if(errors_.empty())
|
||||
{
|
||||
*error_message = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto error = errors_.front();
|
||||
errors_.pop();
|
||||
|
||||
*error_level = error.second;
|
||||
*error_message = utils::string::va("%s", error.first.data());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void scheduler::pre_destroy()
|
||||
{
|
||||
std::lock_guard _(mutex_);
|
||||
|
@ -8,10 +8,17 @@ public:
|
||||
static void once(const std::function<void()>& callback);
|
||||
static void execute();
|
||||
|
||||
static void error(const std::string& message, int level);
|
||||
|
||||
void pre_destroy() override;
|
||||
|
||||
private:
|
||||
static std::mutex mutex_;
|
||||
static std::queue<std::pair<std::string, int>> errors_;
|
||||
static std::vector<std::function<void()>> callbacks_;
|
||||
static std::vector<std::function<void()>> single_callbacks_;
|
||||
|
||||
static void execute_safe();
|
||||
static void execute_error();
|
||||
static bool get_next_error(const char** error_message, int* error_level);
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "notification.hpp"
|
||||
#include "utils/io.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
utils::hook scripting::start_hook_;
|
||||
utils::hook scripting::stop_hook_;
|
||||
@ -10,6 +11,35 @@ std::mutex scripting::mutex_;
|
||||
std::vector<std::function<void()>> scripting::start_callbacks_;
|
||||
std::vector<std::function<void()>> scripting::stop_callbacks_;
|
||||
|
||||
scripting::entity::entity(scripting* environment, const unsigned int entity_id) : environment_(environment),
|
||||
entity_id_(entity_id)
|
||||
{
|
||||
}
|
||||
|
||||
void scripting::entity::on_notify(const std::string& event,
|
||||
const std::function<void(const std::vector<chaiscript::Boxed_Value>&)>& 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());
|
||||
}
|
||||
|
||||
scripting::variable::variable(game::native::VariableValue value) : value_(value)
|
||||
{
|
||||
game::native::AddRefToValue(&value);
|
||||
@ -27,7 +57,17 @@ scripting::variable::operator game::native::VariableValue() const
|
||||
|
||||
void scripting::post_start()
|
||||
{
|
||||
on_start(std::bind(&scripting::initialize, this));
|
||||
on_start([this]()
|
||||
{
|
||||
try
|
||||
{
|
||||
this->initialize();
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
scheduler::error(e.what(), 5);
|
||||
}
|
||||
});
|
||||
on_stop([this]()
|
||||
{
|
||||
this->chai_ = {};
|
||||
@ -56,21 +96,29 @@ void scripting::pre_destroy()
|
||||
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<chaiscript::ChaiScript>();
|
||||
this->chai_->add(chaiscript::user_type<entity>(), "entity");
|
||||
this->chai_->add(chaiscript::fun(&entity::on_notify), "onNotify");
|
||||
|
||||
this->chai_->add(chaiscript::fun([](const std::string& string)
|
||||
{
|
||||
printf("%s\n", string.data());
|
||||
}), "print");
|
||||
|
||||
this->chai_->add(chaiscript::fun(
|
||||
[this](const std::function<void(const std::string&,
|
||||
const std::vector<chaiscript::Boxed_Value>&)>& callback)
|
||||
{
|
||||
std::lock_guard _(mutex_);
|
||||
this->callbacks_.push_back(callback);
|
||||
}), "onNotify");
|
||||
this->chai_->add(chaiscript::fun([](const std::string& string)
|
||||
{
|
||||
MessageBoxA(nullptr, string.data(), nullptr, 0);
|
||||
}), "alert");
|
||||
|
||||
const auto level_id = *game::native::levelEntityId;
|
||||
this->chai_->add_global(chaiscript::var(entity(this, level_id)), "level");
|
||||
|
||||
this->load_scripts();
|
||||
|
||||
@ -80,28 +128,26 @@ void scripting::initialize()
|
||||
|
||||
for (const auto& argument : event->arguments)
|
||||
{
|
||||
arguments.push_back(make_boxed(argument));
|
||||
arguments.push_back(this->make_boxed(argument));
|
||||
}
|
||||
|
||||
decltype(this->callbacks_) copy;
|
||||
{
|
||||
std::lock_guard _(mutex_);
|
||||
copy = this->callbacks_;
|
||||
}
|
||||
|
||||
for (const auto& callback : copy)
|
||||
for (auto listener = this->event_listeners_.begin(); listener.is_valid(); ++listener)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(event->name, arguments);
|
||||
if (listener->event == event->name && listener->entity_id == event->entity_id)
|
||||
{
|
||||
if (listener->is_volatile)
|
||||
{
|
||||
this->event_listeners_.remove(listener);
|
||||
}
|
||||
|
||||
listener->callback(arguments);
|
||||
}
|
||||
}
|
||||
catch (chaiscript::exception::eval_error &e)
|
||||
catch (chaiscript::exception::eval_error& e)
|
||||
{
|
||||
printf("Failed to handle event: %s\n", e.pretty_print().data());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("Failed to handle event: %s\n", e.what());
|
||||
throw std::runtime_error(e.pretty_print());
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -119,13 +165,9 @@ void scripting::load_scripts() const
|
||||
{
|
||||
this->chai_->eval_file(script);
|
||||
}
|
||||
catch (chaiscript::exception::eval_error &e)
|
||||
catch (chaiscript::exception::eval_error& e)
|
||||
{
|
||||
printf("Failed to load script %s: %s\n", script.data(), e.pretty_print().data());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("Failed to load script %s: %s\n", script.data(), e.what());
|
||||
throw std::runtime_error(e.pretty_print());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,6 +188,10 @@ chaiscript::Boxed_Value scripting::make_boxed(const game::native::VariableValue
|
||||
{
|
||||
return chaiscript::var(value.u.intValue);
|
||||
}
|
||||
else if (value.type == game::native::SCRIPT_OBJECT)
|
||||
{
|
||||
return chaiscript::var(entity(this, value.u.entityId));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -2,10 +2,28 @@
|
||||
#include "loader/module_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "utils/hook.hpp"
|
||||
#include "utils/chain.hpp"
|
||||
|
||||
class scripting final : public module
|
||||
{
|
||||
public:
|
||||
class entity final
|
||||
{
|
||||
public:
|
||||
entity(scripting* environment, unsigned int entity_id);
|
||||
|
||||
void on_notify(const std::string& event,
|
||||
const std::function<void(const std::vector<chaiscript::Boxed_Value>&)>& callback,
|
||||
bool is_volatile) const;
|
||||
|
||||
unsigned int get_entity_id() const;
|
||||
game::native::scr_entref_t get_entity_reference() const;
|
||||
|
||||
private:
|
||||
scripting* environment_;
|
||||
unsigned int entity_id_;
|
||||
};
|
||||
|
||||
class variable final
|
||||
{
|
||||
public:
|
||||
@ -18,6 +36,15 @@ public:
|
||||
game::native::VariableValue value_;
|
||||
};
|
||||
|
||||
class event_listener final
|
||||
{
|
||||
public:
|
||||
std::string event = {};
|
||||
unsigned int entity_id = 0;
|
||||
std::function<void(const std::vector<chaiscript::Boxed_Value>&)> callback = {};
|
||||
bool is_volatile = false;
|
||||
};
|
||||
|
||||
void post_start() override;
|
||||
void post_load() override;
|
||||
void pre_destroy() override;
|
||||
@ -27,12 +54,14 @@ public:
|
||||
|
||||
private:
|
||||
std::unique_ptr<chaiscript::ChaiScript> chai_;
|
||||
std::vector<std::function<void(const std::string&, const std::vector<chaiscript::Boxed_Value>&)>> callbacks_;
|
||||
utils::chain<event_listener> event_listeners_;
|
||||
|
||||
void add_event_listener(const event_listener& listener);
|
||||
|
||||
void initialize();
|
||||
void load_scripts() const;
|
||||
|
||||
static chaiscript::Boxed_Value make_boxed(game::native::VariableValue value);
|
||||
chaiscript::Boxed_Value make_boxed(game::native::VariableValue value);
|
||||
|
||||
static utils::hook start_hook_;
|
||||
static utils::hook stop_hook_;
|
||||
|
147
src/utils/chain.hpp
Normal file
147
src/utils/chain.hpp
Normal file
@ -0,0 +1,147 @@
|
||||
namespace utils
|
||||
{
|
||||
template <typename T>
|
||||
class chain
|
||||
{
|
||||
public:
|
||||
class entry
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<T> object_;
|
||||
std::shared_ptr<entry> next_;
|
||||
|
||||
public:
|
||||
bool has_next()
|
||||
{
|
||||
return (this->next_.use_count() > 0);
|
||||
}
|
||||
|
||||
bool is_valid()
|
||||
{
|
||||
return (this->object_.use_count() > 0);
|
||||
}
|
||||
|
||||
void set(T object)
|
||||
{
|
||||
this->object_ = std::shared_ptr<T>(new T());
|
||||
*this->object_.get() = object;
|
||||
}
|
||||
|
||||
std::shared_ptr<T> get()
|
||||
{
|
||||
return this->object_;
|
||||
}
|
||||
|
||||
entry get_next()
|
||||
{
|
||||
if (this->has_next())
|
||||
{
|
||||
return *(this->next_.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<entry> get_next_entry()
|
||||
{
|
||||
return this->next_;
|
||||
}
|
||||
|
||||
void set_next_entry(std::shared_ptr<entry> entry)
|
||||
{
|
||||
this->next_ = entry;
|
||||
}
|
||||
|
||||
T *operator->()
|
||||
{
|
||||
return (this->object_.get());
|
||||
}
|
||||
|
||||
entry& operator++ ()
|
||||
{
|
||||
*this = this->get_next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
entry operator++ (int)
|
||||
{
|
||||
entry result = *this;
|
||||
this->operator++();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
entry object_;
|
||||
|
||||
public:
|
||||
void add(const T& object)
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
if (!this->empty())
|
||||
{
|
||||
// Create new chain entry
|
||||
std::shared_ptr<entry> current_object = std::shared_ptr<entry>(new entry);
|
||||
*current_object.get() = this->object_;
|
||||
|
||||
// Add it to the chain
|
||||
this->object_ = entry();
|
||||
this->object_.set_next_entry(current_object);
|
||||
}
|
||||
|
||||
this->object_.set(object);
|
||||
}
|
||||
|
||||
void remove(std::shared_ptr<T> object)
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
if (!this->empty())
|
||||
{
|
||||
if (this->object_.get().get() == object.get())
|
||||
{
|
||||
this->object_ = this->object_.get_next();
|
||||
}
|
||||
else if(this->object_.has_next())
|
||||
{
|
||||
for (auto entry = this->object_; entry.is_valid(); ++entry)
|
||||
{
|
||||
auto next = entry.get_next();
|
||||
|
||||
if (next.is_valid() && next.get().get() == object.get())
|
||||
{
|
||||
*entry.get_next_entry().get() = next.get_next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void remove(entry entry)
|
||||
{
|
||||
if (entry.is_valid())
|
||||
{
|
||||
this->remove(entry.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool empty()
|
||||
{
|
||||
return !this->object_.is_valid();
|
||||
}
|
||||
|
||||
entry begin()
|
||||
{
|
||||
return this->object_;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
this->object_ = entry();
|
||||
}
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user