Skip to content

Commit

Permalink
Enable setting an arbitrary Lua function as an event handler.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
CelticMinstrel committed Jul 19, 2022
1 parent 11e5700 commit 8cd1332
Show file tree
Hide file tree
Showing 18 changed files with 585 additions and 225 deletions.
11 changes: 9 additions & 2 deletions data/lua/core/interface.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions data/lua/on_event.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 4 additions & 1 deletion data/lua/wml-tags.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
5 changes: 2 additions & 3 deletions src/game_events/entity_location.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down
4 changes: 2 additions & 2 deletions src/game_events/entity_location.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

class unit;
class vconfig;

class unit_filter;

namespace game_events
{
Expand All @@ -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;
Expand Down
188 changes: 181 additions & 7 deletions src/game_events/handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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
{
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
{
event_ref_ = lk.save_wml_event(cfg);
return args_.empty();
}

void event_handler::disable()
Expand All @@ -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

0 comments on commit 8cd1332

Please sign in to comment.