Scripting progress

This commit is contained in:
momo5502 2019-01-16 16:19:21 +01:00
parent e2d5c93407
commit 3498944714
9 changed files with 349 additions and 45 deletions

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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;
};

View File

@ -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_);

View File

@ -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);
};

View File

@ -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 {};
}

View File

@ -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
View 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();
}
};
}