Enable setting an arbitrary Lua function as an event handler.

Unlike the old wesnoth.game_events.on_event hook and the "convenient" on_event() wrapper for it, this new functionality supports all of the features of WML events, with the sole exception of serialization, since it's not possible to reliably serialize a Lua function.

This commit also divorces menu items from the event that they trigger. The undocumented wesnoth.interface.set_menu_item function no longer adds an event for the menu item; the caller needs to separately register an event using the new functionality.
This commit is contained in:
Celtic Minstrel 2021-04-03 20:52:22 -04:00 committed by Celtic Minstrel
parent 11e5700848
commit 8cd1332630
18 changed files with 586 additions and 226 deletions

View file

@ -64,7 +64,14 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then
wesnoth.print = wesnoth.deprecate_api('wesnoth.print', 'wesnoth.interface.add_overlay_text', 1, nil, function(cfg)
wesnoth.wml_actions.print(cfg)
end)
-- No deprecation for these since since they're not actually public API yet
wesnoth.set_menu_item = wesnoth.interface.set_menu_item
wesnoth.set_menu_item = wesnoth.deprecate_api('wesnoth.set_menu_item', 'wesnoth.interface.set_menu_item', 1, nil, function(id, cfg)
-- wesnoth.set_menu_item added both the menu item and the event that it triggers
-- wesnoth.interface.set_menu_item only adds the menu item
wesnoth.interface.set_menu_item(id, cfg)
wesnoth.game_events.add(cfg.id, wesnoth.wml_actions.command, true)
end)
wesnoth.clear_menu_item = wesnoth.interface.clear_menu_item
-- Event handlers don't have a separate module Lua file so dump those here
wesnoth.add_event_handler = wesnoth.deprecate_api('wesnoth.add_event_hander', 'wesnoth.game_events.add', 1, nil, function(cfg) wesnoth.wml_actions.event(cfg) end)
wesnoth.remove_event_handler = wesnoth.deprecate_api('wesnoth.remove_event_handler', 'wesnoth.game_events.remove', 1, nil, wesnoth.game_events.remove)
end

View file

@ -51,5 +51,5 @@ local function on_event(eventname, priority, fcn)
end
end
core_on_event = on_event
return on_event
core_on_event = wesnoth.deprecate_api("on_event", "wesnoth.game_events.add", 1, nil, on_event)
return core_on_event

View file

@ -663,6 +663,7 @@ end
function wml_actions.set_menu_item(cfg)
wesnoth.interface.set_menu_item(cfg.id, cfg)
wesnoth.game_events.add(cfg.id, wml_actions.command, true)
end
function wml_actions.place_shroud(cfg)
@ -732,7 +733,9 @@ function wml_actions.event(cfg)
wesnoth.deprecated_message("[event]remove=yes", 2, "1.17.0", "Use [remove_event] instead of [event]remove=yes")
wml_actions.remove_event(cfg)
else
wesnoth.add_event_handler(cfg)
local delay = cfg.delayed_variable_substitution
if delay == nil then delay = true end
wesnoth.game_events.add(delay, cfg)
end
end

View file

@ -89,8 +89,7 @@ bool entity_location::matches_unit(const unit_map::const_iterator & un_it) const
* the unit is required to additionally match the unit that was supplied
* when this was constructed.
*/
bool entity_location::matches_unit_filter(const unit_map::const_iterator & un_it,
const vconfig & filter) const
bool entity_location::matches_unit_filter(const unit_map::const_iterator & un_it, const unit_filter& filter) const
{
if ( !un_it.valid() )
return false;
@ -101,7 +100,7 @@ bool entity_location::matches_unit_filter(const unit_map::const_iterator & un_it
// Filter the unit at the filter location (should be the unit's
// location if no special filter location was specified).
return unit_filter(filter).matches(*un_it, filter_loc_) &&
return filter.matches(*un_it, filter_loc_) &&
matches_unit(un_it);
}

View file

@ -25,7 +25,7 @@
class unit;
class vconfig;
class unit_filter;
namespace game_events
{
@ -39,7 +39,7 @@ namespace game_events
const map_location& filter_loc() const { return filter_loc_; }
bool matches_unit(const unit_map::const_iterator & un_it) const;
bool matches_unit_filter(const unit_map::const_iterator & un_it,
const vconfig & filter) const;
const unit_filter& filter) const;
unit_const_ptr get_unit() const;
static const entity_location null_entity;

View file

@ -20,11 +20,20 @@
*/
#include "game_events/handlers.hpp"
#include "game_events/conditional_wml.hpp"
#include "game_events/pump.hpp"
#include "game_events/manager_impl.hpp" // for standardize_name
#include "formula/string_utils.hpp"
#include "game_board.hpp"
#include "game_data.hpp"
#include "log.hpp"
#include "play_controller.hpp"
#include "resources.hpp"
#include "scripting/game_lua_kernel.hpp"
#include "side_filter.hpp"
#include "sound.hpp"
#include "units/filter.hpp"
#include "variable.hpp"
static lg::log_domain log_engine("engine");
@ -40,14 +49,44 @@ namespace game_events
{
/* ** event_handler ** */
event_handler::event_handler(config&& cfg, bool imi, const std::vector<std::string>& types, game_lua_kernel& lk)
: first_time_only_(cfg["first_time_only"].to_bool(true))
, is_menu_item_(imi)
event_handler::event_handler(const std::string& types, const std::string& id)
: first_time_only_(true)
, is_menu_item_(false)
, disabled_(false)
, cfg_(cfg)
, is_lua_(false)
, id_(id)
, types_(types)
{}
std::vector<std::string> event_handler::names(const variable_set* vars) const
{
event_ref_ = lk.save_wml_event(cfg);
std::string names = types_;
// Do some standardization on the name field.
// Split the name field and standardize each one individually.
// This ensures they're all valid for by-name lookup.
std::vector<std::string> standardized_names;
for(std::string single_name : utils::split(names)) {
if(utils::might_contain_variables(single_name)) {
if(!vars) {
// If we don't have gamedata, we can't interpolate variables, so there's
// no way the name will match. Move on to the next one in that case.
continue;
}
single_name = utils::interpolate_variables_into_string(single_name, *vars);
}
// Variable interpolation could've introduced additional commas, so split again.
for(const std::string& subname : utils::split(single_name)) {
standardized_names.emplace_back(event_handlers::standardize_name(subname));
}
}
return standardized_names;
}
bool event_handler::empty() const
{
return args_.empty();
}
void event_handler::disable()
@ -63,15 +102,150 @@ void event_handler::handle_event(const queued_event& event_info, game_lua_kernel
}
if(is_menu_item_) {
DBG_NG << cfg_["name"] << " will now invoke the following command(s):\n" << cfg_;
DBG_NG << "menu item " << id_ << " will now invoke the following command(s):\n" << args_;
}
if(first_time_only_) {
disable();
}
lk.run_wml_event(event_ref_, vconfig(cfg_, false), event_info);
lk.run_wml_event(event_ref_, vconfig(args_, false), event_info);
sound::commit_music_changes();
}
bool event_handler::filter_event(const queued_event& ev) const
{
return std::all_of(filters_.begin(), filters_.end(), [&ev](const auto& filter) {
return (*filter)(ev);
});
}
void event_handler::write_config(config &cfg) const
{
if(disabled_) {
WRN_NG << "Tried to serialize disabled event, skipping";
return;
}
if(is_lua_) {
WRN_NG << "Skipping serialization of an event bound to Lua code";
return;
}
if(!types_.empty()) cfg["name"] = types_;
if(!id_.empty()) cfg["id"] = id_;
cfg["first_time_only"] = first_time_only_;
for(const auto& filter : filters_) {
filter->serialize(cfg);
}
cfg.append(args_);
}
void event_filter::serialize(config&) const
{
WRN_NG << "Tried to serialize an event with a filter that cannot be serialized!";
}
struct filter_condition : public event_filter {
filter_condition(const vconfig& cfg) : cfg_(cfg.make_safe()) {}
bool operator()(const queued_event&) const override
{
return conditional_passed(cfg_);
}
void serialize(config& cfg) const override
{
cfg.add_child("filter_condition", cfg_.get_config());
}
private:
vconfig cfg_;
};
struct filter_side : public event_filter {
filter_side(const vconfig& cfg) : ssf_(cfg.make_safe(), &resources::controller->gamestate()) {}
bool operator()(const queued_event&) const override
{
return ssf_.match(resources::controller->current_side());
}
void serialize(config& cfg) const override
{
cfg.add_child("filter_side", ssf_.get_config());
}
private:
side_filter ssf_;
};
struct filter_unit : public event_filter {
filter_unit(const vconfig& cfg, bool first) : suf_(cfg.make_safe()), first_(first) {}
bool operator()(const queued_event& event_info) const override
{
const auto& loc = first_ ? event_info.loc1 : event_info.loc2;
auto unit = resources::gameboard->units().find(loc);
return loc.matches_unit_filter(unit, suf_);
}
void serialize(config& cfg) const override
{
cfg.add_child(first_ ? "filter" : "filter_second", suf_.to_config());
}
private:
unit_filter suf_;
bool first_;
};
struct filter_attack : public event_filter {
filter_attack(const vconfig& cfg, bool first) : swf_(cfg.make_safe()), first_(first) {}
bool operator()(const queued_event& event_info) const override
{
const unit_map& units = resources::gameboard->units();
const auto& loc = first_ ? event_info.loc1 : event_info.loc2;
auto unit = units.find(loc);
if(unit != units.end() && loc.matches_unit(unit)) {
const config& attack = event_info.data.child(first_ ? "first" : "second");
return swf_.empty() || matches_special_filter(attack, swf_);
}
return false;
}
void serialize(config& cfg) const override
{
cfg.add_child(first_ ? "filter_attack" : "filter_second_attack", swf_.get_config());
}
private:
vconfig swf_;
bool first_;
};
void event_handler::read_filters(const config &cfg)
{
for(auto filter : cfg.all_children_range()) {
vconfig vcfg(filter.cfg);
if(filter.key == "filter_condition") {
add_filter(std::make_unique<filter_condition>(vcfg));
} else if(filter.key == "filter_side") {
add_filter(std::make_unique<filter_side>(vcfg));
} else if(filter.key == "filter") {
add_filter(std::make_unique<filter_unit>(vcfg, true));
} else if(filter.key == "filter_attack") {
add_filter(std::make_unique<filter_attack>(vcfg, true));
} else if(filter.key == "filter_second") {
add_filter(std::make_unique<filter_unit>(vcfg, false));
} else if(filter.key == "filter_second_attack") {
add_filter(std::make_unique<filter_attack>(vcfg, false));
}
}
}
void event_handler::add_filter(std::unique_ptr<event_filter>&& filter)
{
filters_.push_back(std::move(filter));
}
void event_handler::register_wml_event(game_lua_kernel &lk)
{
event_ref_ = lk.save_wml_event();
}
void event_handler::set_event_ref(int idx)
{
event_ref_ = idx;
is_lua_ = true;
}
} // end namespace game_events

View file

@ -30,17 +30,31 @@
class game_data;
class game_lua_kernel;
class variable_set;
namespace game_events
{
struct queued_event;
/** Represents a single filter condition on an event. */
struct event_filter {
/** Runs the filter and returns whether it passes on the given event. */
virtual bool operator()(const queued_event& event_info) const = 0;
/** Serializes the filter into a config, if possible. */
virtual void serialize(config& cfg) const;
virtual ~event_filter() = default;
event_filter() = default;
private:
event_filter(const event_filter&) = delete;
event_filter& operator=(const event_filter&) = delete;
};
class event_handler
{
public:
event_handler(config&& cfg, bool is_menu_item, const std::vector<std::string>& types, game_lua_kernel& lk);
event_handler(const std::string& types, const std::string& id = "");
const std::vector<std::string>& names() const
std::vector<std::string> names(const variable_set* vars) const;
const std::string& names_raw() const
{
return types_;
}
@ -66,18 +80,61 @@ public:
*/
void handle_event(const queued_event& event_info, game_lua_kernel& lk);
const config& get_config() const
bool filter_event(const queued_event& event_info) const;
const config& arguments() const
{
return cfg_;
return args_;
}
const std::string& id() const
{
return id_;
}
bool empty() const;
bool repeatable() const
{
return !first_time_only_;
}
void write_config(config& cfg) const;
void set_repeatable(bool repeat = true)
{
first_time_only_ = !repeat;
}
void set_menu_item(bool imi)
{
is_menu_item_ = imi;
}
void set_arguments(const config& cfg)
{
args_ = cfg;
}
void read_filters(const config& cfg);
void add_filter(std::unique_ptr<event_filter>&& filter);
void register_wml_event(game_lua_kernel& lk);
void set_event_ref(int idx);
private:
bool first_time_only_;
bool is_menu_item_;
bool disabled_;
config cfg_;
/**
* Tracks whether the event was registered from the Lua API.
* This allows a warning to be issued in cases that will break saved games.
*/
bool is_lua_;
int event_ref_;
std::vector<std::string> types_;
config args_;
std::vector<std::shared_ptr<event_filter>> filters_;
std::string id_, types_;
};
}

View file

@ -32,6 +32,7 @@ static lg::log_domain log_engine("engine");
#define WRN_NG LOG_STREAM(warn, log_engine)
static lg::log_domain log_event_handler("event_handler");
#define LOG_EH LOG_STREAM(info, log_event_handler)
#define DBG_EH LOG_STREAM(debug, log_event_handler)
namespace
@ -64,9 +65,43 @@ namespace
namespace game_events
{
/** Create an event handler. */
void manager::add_event_handler(const config& handler, game_lua_kernel& lk, bool is_menu_item)
void manager::add_event_handler_from_wml(const config& handler, game_lua_kernel& lk, bool is_menu_item)
{
event_handlers_->add_event_handler(handler, lk, is_menu_item);
auto new_handler = event_handlers_->add_event_handler(handler["name"], handler["id"], !handler["first_time_only"].to_bool(true), is_menu_item);
if(new_handler.valid()) {
new_handler->read_filters(handler);
// Strip out anything that's used by the event system itself.
// TODO: "filter" and "formula" are stubs intended for Lua and WFL code respectively.
config args;
for(const auto& [attr, val] : handler.attribute_range()) {
if(attr == "id" || attr == "name" || attr == "first_time_only" || attr.compare(0, 6, "filter") == 0) {
continue;
}
args[attr] = val;
}
for(auto child : handler.all_children_range()) {
if(child.key.compare(0, 6, "filter") != 0) {
args.add_child(child.key, child.cfg);
}
}
new_handler->set_arguments(args);
new_handler->register_wml_event(lk);
DBG_EH << "Registered WML event "
<< (new_handler->names_raw().empty() ? "" : "'" + new_handler->names_raw() + "'")
<< (new_handler->id().empty() ? "" : "{id=" + new_handler->id() + "}")
<< (new_handler->repeatable() ? " (repeating" : " (first time only")
<< (is_menu_item ? "; menu item)" : ")")
<< " with the following actions:\n"
<< args.debug();
} else {
LOG_EH << "Content of failed event:\n" << handler.debug() << "\n";
}
}
pending_event_handler manager::add_event_handler_from_lua(const std::string& name, const std::string& id, bool repeat, bool is_menu_item)
{
return event_handlers_->add_event_handler(name, id, repeat, is_menu_item);
}
/** Removes an event handler. */
@ -94,7 +129,7 @@ manager::manager()
void manager::read_scenario(const config& scenario_cfg, game_lua_kernel& lk)
{
for(const config& ev : scenario_cfg.child_range("event")) {
add_event_handler(ev, lk);
add_event_handler_from_wml(ev, lk);
}
for(const std::string& id : utils::split(scenario_cfg["unit_wml_ids"])) {
@ -127,7 +162,7 @@ void manager::add_events(const config::const_child_itors& cfgs, game_lua_kernel&
continue;
}
add_event_handler(new_ev, lk);
add_event_handler_from_wml(new_ev, lk);
}
}
@ -150,7 +185,7 @@ void manager::write_events(config& cfg) const
assert(!eh->disabled());
}
cfg.add_child("event", eh->get_config());;
eh->write_config(cfg.add_child("event"));
}
cfg["unit_wml_ids"] = utils::join(unit_wml_ids_);
@ -188,24 +223,8 @@ void manager::execute_on_events(const std::string& event_id, manager::event_func
}
// Could be more than one.
for (const std::string& name : handler->names()) {
bool matches = false;
if (utils::might_contain_variables(name)) {
// If we don't have gamedata, we can't interpolate variables, so there's
// no way the name will match. Move on to the next one in that case.
if (!gd) {
continue;
}
matches = standardized_event_id ==
event_handlers::standardize_name(utils::interpolate_variables_into_string(name, *gd));
}
else {
matches = standardized_event_id == name;
}
if (matches) {
for(const std::string& name : handler->names(gd)) {
if(standardized_event_id == name) {
func(*this, handler);
break;
}

View file

@ -31,6 +31,7 @@ namespace game_events
{
class wml_event_pump;
class event_handlers;
class pending_event_handler;
/**
* The game event manager loads the scenario configuration object,
@ -59,8 +60,10 @@ public:
void read_scenario(const config& scenario_cfg, game_lua_kernel& lk);
~manager();
/** Create an event handler. */
void add_event_handler(const config& handler, game_lua_kernel& lk, bool is_menu_item = false);
/** Create an event handler from an [event] tag. */
void add_event_handler_from_wml(const config& handler, game_lua_kernel& lk, bool is_menu_item = false);
/** Create an empty event handler. Expects the caller to finish setting up the event. */
pending_event_handler add_event_handler_from_lua(const std::string& name, const std::string& id, bool repeat = false, bool is_menu_item = false);
/** Removes an event handler. */
void remove_event_handler(const std::string& id);

View file

@ -28,6 +28,8 @@ static lg::log_domain log_engine("engine");
#define WRN_NG LOG_STREAM(warn, log_engine)
static lg::log_domain log_event_handler("event_handler");
#define ERR_EH LOG_STREAM(err, log_event_handler)
#define LOG_EH LOG_STREAM(info, log_event_handler)
#define DBG_EH LOG_STREAM(debug, log_event_handler)
static lg::log_domain log_wml("wml");
@ -49,8 +51,7 @@ void event_handlers::log_handlers()
continue;
}
const config& cfg = h->get_config();
ss << "name=" << cfg["name"] << ", with id=" << cfg["id"] << "; ";
ss << "name=" << h->names_raw() << ", with id=" << h->id() << "; ";
}
DBG_EH << "active handlers are now " << ss.str() << "\n";
@ -92,70 +93,57 @@ handler_list& event_handlers::get(const std::string& name)
* An event with a nonempty ID will not be added if an event with that
* ID already exists.
*/
void event_handlers::add_event_handler(const config& cfg, game_lua_kernel& lk, bool is_menu_item)
pending_event_handler event_handlers::add_event_handler(const std::string& name, const std::string& id, bool repeat, bool is_menu_item)
{
// Someone decided to register an empty event... bail.
if(cfg.empty()) {
return;
}
std::string name = cfg["name"];
std::string id = cfg["id"];
if(!id.empty()) {
// Ignore this handler if there is already one with this ID.
auto find_it = id_map_.find(id);
if(find_it != id_map_.end() && !find_it->second.expired()) {
DBG_EH << "ignoring event handler for name='" << name << "' with id '" << id << "'\n";
return;
LOG_EH << "ignoring event handler for name='" << name << "' with id '" << id << "' because an event with that id already exists\n";
return {*this, nullptr};
}
}
if(name.empty() && id.empty()) {
lg::log_to_chat() << "[event] is missing name or id field\n";
ERR_WML << "[event] is missing name or id field\n";
DBG_WML << "Content of that event:\n" << cfg.debug() << "\n";
return;
}
// Make a copy of the event cfg here in order to do some standardization on the
// name field. Will be moved into the handler.
config event_cfg = cfg;
// Split the name field...
std::vector<std::string> standardized_names = utils::split(name);
if(!name.empty()) {
// ...and standardize each one individually. This ensures they're all valid for by-name lookup.
for(std::string& single_name : standardized_names) {
if(!utils::might_contain_variables(single_name)) {
single_name = standardize_name(single_name);
}
static const char* msg = "[event] is missing name or id field";
lg::log_to_chat() << msg << "\n";
if(lg::info().dont_log(log_event_handler)) {
ERR_EH << msg << " (run with --log-info=event_handler for more info)\n";
} else {
ERR_EH << msg << "\n";
}
assert(!standardized_names.empty());
// Write the new name back to the config.
name = utils::join(standardized_names);
event_cfg["name"] = name;
return {*this, nullptr};
}
// Create a new handler.
auto handler = std::make_shared<event_handler>(name, id);
handler->set_menu_item(is_menu_item);
handler->set_repeatable(repeat);
return {*this, handler};
}
void event_handlers::finish_adding_event_handler(handler_ptr handler)
{
// Someone decided to register an empty event... bail.
if(handler->empty()) {
return;
}
const std::string& names = handler->names_raw();
const std::string& id = handler->id();
// Register the new handler.
// Do note active_ holds the main shared_ptr, and the other three containers
// construct weak_ptrs from the shared one.
DBG_EH << "inserting event handler for name=" << name << " with id=" << id << "\n";
active_.emplace_back(new event_handler(std::move(event_cfg), is_menu_item, standardized_names, lk));
//
// !! event_cfg is invalid past this point! DO NOT USE!
//
DBG_EH << "inserting event handler for name=" << names << " with id=" << id << "\n";
active_.emplace_back(handler);
// File by name.
if(utils::might_contain_variables(name)) {
if(utils::might_contain_variables(names)) {
dynamic_.emplace_back(active_.back());
} else {
for(const std::string& single_name : standardized_names) {
for(const std::string& single_name : handler->names(nullptr)) {
by_name_[single_name].emplace_back(active_.back());
}
}
@ -168,6 +156,11 @@ void event_handlers::add_event_handler(const config& cfg, game_lua_kernel& lk, b
log_handlers();
}
pending_event_handler::~pending_event_handler()
{
if(valid()) list_.finish_adding_event_handler(handler_);
}
/**
* Removes an event handler, identified by its ID.
* Events with empty IDs cannot be removed.

View file

@ -25,6 +25,33 @@ class game_lua_kernel;
namespace game_events
{
class event_handlers;
/**
* Represents a handler that is about to be added to the events manager but is still waiting for some data.
* The handler will automatically be added when this class is destroyed, unless it has become invalid somehow.
*/
class pending_event_handler
{
event_handlers& list_;
handler_ptr handler_;
pending_event_handler(const pending_event_handler&) = delete;
pending_event_handler& operator=(const pending_event_handler&) = delete;
// It's move-constructible, but there's no way to make it move-assignable since it contains a reference...
pending_event_handler& operator=(pending_event_handler&&) = delete;
public:
pending_event_handler(event_handlers& list, handler_ptr handler)
: list_(list)
, handler_(handler)
{}
/** Check if this handler is valid. */
bool valid() const {return handler_.get();}
/** Access the event handler. */
event_handler* operator->() {return handler_.get();}
event_handler& operator*() {return *handler_;}
pending_event_handler(pending_event_handler&&) = default;
~pending_event_handler();
};
// event_handlers is essentially the implementation details of the manager
class event_handlers
{
@ -51,6 +78,9 @@ private:
void log_handlers();
friend pending_event_handler;
void finish_adding_event_handler(handler_ptr new_handler);
public:
/** Utility to standardize the event names used in by_name_. */
static std::string standardize_name(const std::string& name);
@ -84,7 +114,7 @@ public:
handler_list& get(const std::string& name);
/** Adds an event handler. */
void add_event_handler(const config& cfg, game_lua_kernel& lk, bool is_menu_item = false);
pending_event_handler add_event_handler(const std::string& name, const std::string& id, bool repeat, bool is_menu_item = false);
/** Removes an event handler, identified by its ID. */
void remove_event_handler(const std::string& id);

View file

@ -209,7 +209,7 @@ void wml_menu_item::init_handler(game_lua_kernel& lk)
// If this menu item has a [command], add a handler for it.
if(!command_.empty()) {
assert(resources::game_events);
resources::game_events->add_event_handler(command_, lk, true);
resources::game_events->add_event_handler_from_wml(command_, lk, true);
}
// Hotkey support
@ -338,16 +338,14 @@ void wml_menu_item::update(const vconfig& vcfg)
void wml_menu_item::update_command(const config& new_command)
{
// If there is an old command, remove it from the event handlers.
if(!command_.empty()) {
assert(resources::game_events);
assert(resources::game_events);
resources::game_events->execute_on_events(event_name_, [&](game_events::manager& man, handler_ptr& ptr) {
if(ptr->is_menu_item()) {
LOG_NG << "Removing command for " << event_name_ << ".\n";
man.remove_event_handler(command_["id"].str());
}
});
}
resources::game_events->execute_on_events(event_name_, [&](game_events::manager& man, handler_ptr& ptr) {
if(ptr->is_menu_item()) {
LOG_NG << "Removing command for " << event_name_ << ".\n";
man.remove_event_handler(command_["id"].str());
}
});
// Update our stored command.
if(new_command.empty()) {
@ -368,7 +366,7 @@ void wml_menu_item::update_command(const config& new_command)
LOG_NG << "Setting command for " << event_name_ << " to:\n" << command_;
assert(resources::game_events);
assert(resources::lua_kernel);
resources::game_events->add_event_handler(command_, *resources::lua_kernel, true);
resources::game_events->add_event_handler_from_wml(command_, *resources::lua_kernel, true);
}
}

View file

@ -20,7 +20,6 @@
*/
#include "game_events/pump.hpp"
#include "game_events/conditional_wml.hpp"
#include "game_events/handlers.hpp"
#include "display_chat_manager.hpp"
@ -186,84 +185,6 @@ pump_manager::~pump_manager()
}
}
/**
* Returns true iff the given event passes all its filters.
*/
bool wml_event_pump::filter_event(const event_handler& handler, const queued_event& ev)
{
const unit_map& units = resources::gameboard->units();
unit_map::const_iterator unit1 = units.find(ev.loc1);
unit_map::const_iterator unit2 = units.find(ev.loc2);
vconfig filters(handler.get_config());
for(const vconfig& condition : filters.get_children("filter_condition")) {
if(!conditional_passed(condition)) {
return false;
}
}
for(const vconfig& f : filters.get_children("filter_side")) {
side_filter ssf(f, &resources::controller->gamestate());
if(!ssf.match(resources::controller->current_side()))
return false;
}
for(const vconfig& f : filters.get_children("filter")) {
if(!ev.loc1.matches_unit_filter(unit1, f)) {
return false;
}
}
vconfig::child_list special_filters = filters.get_children("filter_attack");
bool special_matches = special_filters.empty();
if(!special_matches && unit1 != units.end()) {
const bool matches_unit = ev.loc1.matches_unit(unit1);
const config& attack = ev.data.child("first");
for(const vconfig& f : special_filters) {
if(f.empty()) {
special_matches = true;
} else if(!matches_unit) {
return false;
}
special_matches = special_matches || matches_special_filter(attack, f);
}
}
if(!special_matches) {
return false;
}
for(const vconfig& f : filters.get_children("filter_second")) {
if(!ev.loc2.matches_unit_filter(unit2, f)) {
return false;
}
}
special_filters = filters.get_children("filter_second_attack");
special_matches = special_filters.empty();
if(!special_matches && unit2 != units.end()) {
const bool matches_unit = ev.loc2.matches_unit(unit2);
const config& attack = ev.data.child("second");
for(const vconfig& f : special_filters) {
if(f.empty()) {
special_matches = true;
} else if(!matches_unit) {
return false;
}
special_matches = special_matches || matches_special_filter(attack, f);
}
}
if(!special_matches) {
return false;
}
// All filters passed.
return true;
}
/**
* Processes an event through a single event handler.
* This includes checking event filters, but not checking that the event
@ -289,7 +210,7 @@ void wml_event_pump::process_event(handler_ptr& handler_p, const queued_event& e
scoped_weapon_info first_weapon("weapon", ev.data.child("first"));
scoped_weapon_info second_weapon("second_weapon", ev.data.child("second"));
if(!filter_event(*handler_p, ev)) {
if(!handler_p->filter_event(ev)) {
return;
}
@ -572,7 +493,7 @@ pump_result_t wml_event_pump::operator()()
if(event_id.empty()) {
// Handle events of this name.
impl_->my_manager->execute_on_events(event_name, [&](game_events::manager&, handler_ptr& ptr) {
DBG_EH << "processing event " << event_name << " with id=" << ptr->get_config()["id"] << "\n";
DBG_EH << "processing event " << event_name << " with id=" << ptr->id() << "\n";
// Let this handler process our event.
process_event(ptr, ev);
@ -582,7 +503,7 @@ pump_result_t wml_event_pump::operator()()
handler_ptr cur_handler = impl_->my_manager->get_event_handler_by_id(event_id);
if(cur_handler) {
DBG_EH << "processing event " << event_name << " with id=" << cur_handler->get_config()["id"] << "\n";
DBG_EH << "processing event " << event_name << " with id=" << cur_handler->id() << "\n";
process_event(cur_handler, ev);
}
}

View file

@ -143,8 +143,6 @@ public:
void flush_messages();
private:
bool filter_event(const event_handler& handler, const queued_event& ev);
void process_event(handler_ptr& handler_p, const queued_event& ev);
void fill_wml_messages_map(std::map<std::string, int>& msg_map, std::stringstream& source);

View file

@ -54,6 +54,8 @@
#include "game_errors.hpp" // for game_error
#include "game_events/conditional_wml.hpp" // for conditional_passed
#include "game_events/entity_location.hpp"
#include "game_events/handlers.hpp"
#include "game_events/manager_impl.hpp" // for pending_event_handler
#include "game_events/pump.hpp" // for queued_event
#include "preferences/game.hpp" // for encountered_units
#include "log.hpp" // for LOG_STREAM, logger, etc
@ -3689,16 +3691,106 @@ int game_lua_kernel::intf_log_replay(lua_State* L)
return 0;
}
/** Adding new events */
static std::string read_event_name(lua_State* L, int idx)
{
if(lua_isstring(L, idx)) {
return lua_tostring(L, idx);
} else {
return utils::join(lua_check<std::vector<std::string>>(L, idx));
}
}
/** Add a new event handler
* Arg 1: Table of options.
* name: Event to handle, as a string or list of strings
* id: Event ID
* menu_item: True if this is a menu item (an ID is required); this means removing the menu item will automatically remove this event. Default false.
* first_time_only: Whether this event should fire again after the first time; default true.
* filter: Event filters as a config with filter tags, a table of the form {filter_type = filter_contents}, or a function
* content: The content of the event. This is a WML table passed verbatim into the event when it fires. If no function is specified, it will be interpreted as ActionWML.
* action: The function to call when the event triggers. Defaults to wesnoth.wml_actions.command.
* OR
* Arg 1: Event to handle, as a string or list of strings; or menu item ID if this is a menu item
* Arg 2: The function to call when the event triggers
* Arg 3: True if this is a menu item
* OR
* Arg 1: A boolean - whether to delay variable substitution
* Arg 2: A full event specification as a WML config
*/
int game_lua_kernel::intf_add_event(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
game_events::manager & man = *game_state_.events_manager_;
if (!cfg["delayed_variable_substitution"].to_bool(true)) {
man.add_event_handler(cfg.get_parsed_config(), *this);
if(lua_isboolean(L, 1)) {
bool delayed_variable_substitution = luaW_toboolean(L, 1);
vconfig cfg(luaW_checkvconfig(L, 2));
if(delayed_variable_substitution) {
man.add_event_handler_from_wml(cfg.get_config(), *this);
} else {
man.add_event_handler_from_wml(cfg.get_parsed_config(), *this);
}
} else if(lua_isstring(L, 1)) {
bool is_menu_item = luaW_toboolean(L, 3), repeat = false;
std::string name = read_event_name(L, 1), id;
if(is_menu_item) {
id = name;
name = "menu item " + name;
}
auto new_handler = man.add_event_handler_from_lua(name, id, repeat, is_menu_item);
if(new_handler.valid()) {
// An event with empty arguments is not added, so set some dummy arguments
new_handler->set_arguments(config{"__quick_lua_event", true});
new_handler->set_event_ref(save_wml_event(2));
}
} else if(lua_istable(L, 1)) {
using namespace std::literals;
std::string name, id = luaW_table_get_def(L, 1, "id", ""s);
bool repeat = !luaW_table_get_def(L, 1, "first_time_only", true), is_menu_item = luaW_table_get_def(L, 1, "menu_item", false);
if(luaW_tableget(L, 1, "name")) {
name = read_event_name(L, -1);
}
auto new_handler = man.add_event_handler_from_lua(name, id, repeat, is_menu_item);
if(new_handler.valid()) {
new_handler->set_arguments(luaW_table_get_def(L, 1, "content", config{"__empty_lua_event", true}));
if(luaW_tableget(L, 1, "filter")) {
int filterIdx = lua_gettop(L);
config filters;
if(!luaW_toconfig(L, filterIdx, filters)) {
if(lua_isfunction(L, filterIdx)) {
//new_handler->add_filter(std::make_unique<lua_event_filter>());
} else {
if(luaW_tableget(L, filterIdx, "condition")) {
filters.add_child("filter_condition", luaW_checkconfig(L, -1));
}
if(luaW_tableget(L, filterIdx, "side")) {
filters.add_child("filter_side", luaW_checkconfig(L, -1));
}
if(luaW_tableget(L, filterIdx, "unit")) {
filters.add_child("filter", luaW_checkconfig(L, -1));
}
if(luaW_tableget(L, filterIdx, "attack")) {
filters.add_child("filter_attack", luaW_checkconfig(L, -1));
}
if(luaW_tableget(L, filterIdx, "second_unit")) {
filters.add_child("filter_second", luaW_checkconfig(L, -1));
}
if(luaW_tableget(L, filterIdx, "second_attack")) {
filters.add_child("filter_second_attack", luaW_checkconfig(L, -1));
}
}
}
new_handler->read_filters(filters);
}
if(luaW_tableget(L, 1, "action")) {
new_handler->set_event_ref(save_wml_event(-1));
} else {
new_handler->register_wml_event(*this);
}
}
} else {
man.add_event_handler(cfg.get_config(), *this);
return luaW_type_error(L, 1, "boolean, string, or table");
}
return 0;
}
@ -4499,7 +4591,6 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "get_era", &intf_get_era },
{ "get_resource", &intf_get_resource },
{ "modify_ai", &intf_modify_ai_old },
{ "add_event_handler", &dispatch<&game_lua_kernel::intf_add_event > },
{ "allow_undo", &dispatch<&game_lua_kernel::intf_allow_undo > },
{ "cancel_action", &dispatch<&game_lua_kernel::intf_cancel_action > },
{ "fire_event", &dispatch2<&game_lua_kernel::intf_fire_event, false > },
@ -4507,7 +4598,6 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "log_replay", &dispatch<&game_lua_kernel::intf_log_replay > },
{ "log", &dispatch<&game_lua_kernel::intf_log > },
{ "redraw", &dispatch<&game_lua_kernel::intf_redraw > },
{ "remove_event_handler", &dispatch<&game_lua_kernel::intf_remove_event > },
{ "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > },
{ nullptr, nullptr }
};lua_getglobal(L, "wesnoth");
@ -4845,10 +4935,15 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
lua_pop(L, 1);
// Create the game_events table.
cmd_log_ << "Adding game_events table...\n";
cmd_log_ << "Adding game_events module...\n";
static luaL_Reg const event_callbacks[] {
{ "add", &dispatch<&game_lua_kernel::intf_add_event> },
{ "remove", &dispatch<&game_lua_kernel::intf_remove_event> },
{ nullptr, nullptr }
};
lua_getglobal(L, "wesnoth");
lua_newtable(L);
luaL_setfuncs(L, event_callbacks, 0);
lua_setfield(L, -2, "game_events");
lua_pop(L, 1);
@ -4893,6 +4988,10 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
lua_rawset(L, -3);
}
lua_settop(L, 0);
// Set up the registry table for event handlers
lua_newtable(L);
EVENT_TABLE = luaL_ref(L, LUA_REGISTRYINDEX);
}
void game_lua_kernel::initialize(const config& level)
@ -5238,38 +5337,54 @@ static int intf_run_event_wml(lua_State* L)
return 0;
}
int game_lua_kernel::save_wml_event(const config& evt)
int game_lua_kernel::save_wml_event()
{
lua_State* L = mState;
if(EVENT_TABLE == LUA_NOREF) {
lua_newtable(L);
EVENT_TABLE = luaL_ref(L, LUA_REGISTRYINDEX);
}
lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
int evtIdx = lua_gettop(L);
ON_SCOPE_EXIT(L) {
lua_pop(L, 1);
};
if(evt.has_attribute("code")) {
std::ostringstream name;
name << "event ";
if(evt.has_attribute("name")) {
name << evt["name"];
} else {
name << "<anon>";
}
if(evt.has_attribute("id")) {
name << "[id=" << evt["id"] << "]";
}
if(!load_string(evt["code"].str().c_str(), name.str())) {
ERR_LUA << "Failed to register WML event: " << name.str();
return LUA_NOREF;
}
lua_pushcfunction(L, intf_run_event_wml);
return luaL_ref(L, evtIdx);
}
int game_lua_kernel::save_wml_event(const std::string& name, const std::string& id, const std::string& code)
{
lua_State* L = mState;
lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
int evtIdx = lua_gettop(L);
ON_SCOPE_EXIT(L) {
lua_pop(L, 1);
};
std::ostringstream lua_name;
lua_name << "event ";
if(name.empty()) {
lua_name << "<anon>";
} else {
lua_pushcfunction(L, intf_run_event_wml);
lua_name << name;
}
int ref = luaL_ref(L, evtIdx);
return ref;
if(!id.empty()) {
lua_name << "[id=" << id << "]";
}
if(!load_string(code.c_str(), lua_name.str())) {
ERR_LUA << "Failed to register WML event: " << lua_name.str();
return LUA_NOREF;
}
return luaL_ref(L, evtIdx);
}
int game_lua_kernel::save_wml_event(int idx)
{
lua_State* L = mState;
idx = lua_absindex(L, idx);
lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
int evtIdx = lua_gettop(L);
ON_SCOPE_EXIT(L) {
lua_pop(L, 1);
};
lua_pushvalue(L, idx);
return luaL_ref(L, evtIdx);
}
void game_lua_kernel::clear_wml_event(int ref)

View file

@ -219,11 +219,40 @@ public:
bool run_filter(char const *name, const team& t);
bool run_filter(char const *name, int nArgs);
bool run_wml_conditional(const std::string&, const vconfig&);
/** Store a WML event in the Lua registry, as a function */
int save_wml_event(const config& evt);
/** Clear a WML event store in the Lua registry */
/**
* Store a WML event in the Lua registry, as a function.
* Uses a default function that interprets ActionWML.
* @return A unique index into the EVENT_TABLE within the Lua registry
*/
int save_wml_event();
/**
* Store a WML event in the Lua registry, as a function.
* Compiles the function from the given code.
* @param name The event name, used to generate a chunk name for the compiled function
* @param id The event id, used to generate a chunk name for the compiled function
* @param code The actual code of the function
* @return A unique index into the EVENT_TABLE within the Lua registry
*/
int save_wml_event(const std::string& name, const std::string& id, const std::string& code);
/**
* Store a WML event in the Lua registry, as a function.
* Uses the function at the specified Lua stack index.
* @param idx The Lua stack index of the function to store
* @return A unique index into the EVENT_TABLE within the Lua registry
*/
int save_wml_event(int idx);
/**
* Clear a WML event store in the Lua registry.
* @param ref The unique index into the EVENT_TABLE within the Lua registry
*/
void clear_wml_event(int ref);
/** Run a WML store in the Lua registry */
/**
* Run a WML stored in the Lua registry.
* @param ref The unique index into the EVENT_TABLE within the Lua registry
* @param args Arguments to pass to the event function, as a config
* @param ev The event data for the event being fired
* @return Whether the function was successfully called; could be false if @a ref was invalid or if the function raised an error
*/
bool run_wml_event(int ref, const vconfig& args, const game_events::queued_event& ev);
virtual void log_error(char const* msg, char const* context = "Lua error") override;

View file

@ -82,6 +82,12 @@ namespace lua_check_impl
return luaL_checkstring(L, n);
}
template<typename T>
std::enable_if_t<std::is_same_v<T, std::string>, std::string>
lua_to_or_default(lua_State *L, int n, const T& def)
{
return luaL_optstring(L, n, def.c_str());
}
template<typename T>
std::enable_if_t<std::is_same_v<T, std::string>, void>
lua_push(lua_State *L, const T& val)
{
@ -116,6 +122,13 @@ namespace lua_check_impl
return luaW_checkconfig(L, n);
}
template<typename T>
std::enable_if_t<std::is_same_v<T, config>, config>
lua_to_or_default(lua_State *L, int n, const T& def)
{
config cfg;
return luaW_toconfig(L, n, cfg) ? cfg : def;
}
template<typename T>
std::enable_if_t<std::is_same_v<T, config>, void>
lua_push(lua_State *L, const config& val)
{

View file

@ -40,6 +40,7 @@ public:
bool match(const team& t) const;
bool match(const int side) const;
std::vector<int> get_teams() const;
const config& get_config() const {return cfg_.get_config();}
private:
side_filter(const side_filter &other);