The Battle for Wesnoth  1.19.5+dev
menu_item.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Definitions for a class that implements WML-defined (right-click) menu items.
19  */
20 
22 
24 #include "game_events/handlers.hpp"
25 #include "game_events/pump.hpp"
26 
27 #include "game_config.hpp"
28 #include "game_version.hpp"
30 #include "log.hpp"
31 #include "replay_helper.hpp"
32 #include "resources.hpp"
33 #include "synced_context.hpp"
34 #include "terrain/filter.hpp"
35 #include "deprecation.hpp"
36 
37 static lg::log_domain log_engine("engine");
38 #define ERR_NG LOG_STREAM(err, log_engine)
39 #define LOG_NG LOG_STREAM(info, log_engine)
40 
41 // This file is in the game_events namespace.
42 namespace game_events
43 {
44 namespace
45 { // Some helpers for construction.
46 
47 /**
48  * Build the event name associated with the given menu item id.
49  * This is a separate function so it can be easily shared by multiple
50  * constructors.
51  */
52 inline std::string make_item_name(const std::string& id)
53 {
54  return std::string("menu item") + (id.empty() ? "" : ' ' + id);
55 }
56 
57 /**
58  * Build the hotkey id associated with the given menu item id.
59  * This is a separate function so it can be easily shared by multiple
60  * constructors.
61  */
62 inline std::string make_item_hotkey(const std::string& id)
63 {
65 }
66 
67 } // anonymous namespace
68 
69 // Constructor for reading from a saved config.
70 wml_menu_item::wml_menu_item(const std::string& id, const config& cfg)
71  : item_id_(id)
72  , event_name_(make_item_name(id))
73  , hotkey_id_(make_item_hotkey(id))
74  , hotkey_record_()
75  , image_(cfg["image"].str())
76  , description_(cfg["description"].t_str())
77  , needs_select_(cfg["needs_select"].to_bool(false))
78  , show_if_(cfg.child_or_empty("show_if"), true)
79  , filter_location_(cfg.child_or_empty("filter_location"), true)
80  , command_(cfg.child_or_empty("command"))
81  , default_hotkey_(cfg.child_or_empty("default_hotkey"))
82  , use_hotkey_(cfg["use_hotkey"].to_bool(true))
83  , use_wml_menu_(cfg["use_hotkey"].str() != "only")
84  , is_synced_(cfg["synced"].to_bool(true))
85  , persistent_(cfg["persistent"].to_bool(true))
86 {
87  if(cfg.has_attribute("needs_select")) {
88  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
89  }
90 }
91 
92 // Constructor for items defined in an event.
93 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition)
94  : item_id_(id)
95  , event_name_(make_item_name(id))
96  , hotkey_id_(make_item_hotkey(id))
97  , hotkey_record_()
98  , image_()
99  , description_()
100  , needs_select_(false)
101  , show_if_(vconfig::empty_vconfig())
102  , filter_location_(vconfig::empty_vconfig())
103  , command_()
104  , default_hotkey_()
105  , use_hotkey_(true)
106  , use_wml_menu_(true)
107  , is_synced_(true)
108  , persistent_(true)
109 {
110  // On the off-chance that update() doesn't do it, add the hotkey here.
111  // (Update can always modify it.)
113 
114  // Apply WML.
115  update(definition);
116 }
117 
118 // Constructor for items modified by an event.
119 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition, wml_menu_item& original)
120  : item_id_(id)
121  , event_name_(make_item_name(id))
122  , hotkey_id_(make_item_hotkey(id))
123  , hotkey_record_(std::move(original.hotkey_record_)) // Make sure we have full lifetime control of the old record
124  , image_(original.image_)
125  , description_(original.description_)
126  , needs_select_(original.needs_select_)
127  , show_if_(original.show_if_)
128  , filter_location_(original.filter_location_)
129  , command_(original.command_)
130  , default_hotkey_(original.default_hotkey_)
131  , use_hotkey_(original.use_hotkey_)
132  , use_wml_menu_(original.use_wml_menu_)
133  , is_synced_(original.is_synced_)
134  , persistent_(original.persistent_)
135 {
136  // Apply WML.
137  update(definition);
138 }
139 
140 const std::string& wml_menu_item::image() const
141 {
142  // Default the image?
143  return image_.empty() ? game_config::images::wml_menu : image_;
144 }
145 
146 
148 {
149  // Failing the [show_if] tag means no show.
151  return false;
152  }
153 
154  // Failing the [fiter_location] tag means no show.
155  if(!filter_location_.empty() && !terrain_filter(filter_location_, &filter_con, false)(hex)) {
156  return false;
157  }
158 
159  // Failing to have a required selection means no show.
160  if(needs_select_ && !data.last_selected.valid()) {
161  return false;
162  }
163 
164  // Passed all tests.
165  return true;
166 }
167 
168 void wml_menu_item::fire_event(const map_location& event_hex, const game_data& data) const
169 {
170  if(!this->is_synced()) {
171  // It is possible to for example show a help menu during a [delay] of a synced event.
173  assert(resources::game_events != nullptr);
175  return;
176  }
177 
178  const map_location& last_select = data.last_selected;
179 
180  // No new player-issued commands allowed while this is firing.
181  const events::command_disabler disable_commands;
182 
183  // instead of adding a second "select" event like it was done before, we just fire the select event again, and this
184  // time in a synced context.
185  // note that there couldn't be a user choice during the last "select" event because it didn't run in a synced
186  // context.
187  if(needs_select_ && last_select.valid()) {
189  "fire_event", replay_helper::get_event(event_name_, event_hex, &last_select));
190  } else {
192  "fire_event", replay_helper::get_event(event_name_, event_hex, nullptr));
193  }
194 }
195 
197 {
198  if(!command_.empty()) {
199  assert(resources::game_events);
201  }
202 
203  // Hotkey support
204  if(use_hotkey_) {
205  hotkey_record_.reset();
206  }
207 }
208 
210 {
211  // If this menu item has a [command], add a handler for it.
212  if(!command_.empty()) {
213  assert(resources::game_events);
215  }
216 
217  // Hotkey support
218  if(use_hotkey_) {
220  }
221 }
222 
224 {
225  cfg["id"] = item_id_;
226  cfg["image"] = image_;
227  cfg["description"] = description_;
228  cfg["synced"] = is_synced_;
229 
230  if(needs_select_) {
231  cfg["needs_select"] = true;
232  }
233 
234  if(use_hotkey_ && use_wml_menu_) {
235  cfg["use_hotkey"] = true;
236  }
237 
238  if(use_hotkey_ && !use_wml_menu_) {
239  cfg["use_hotkey"] = "only";
240  }
241 
242  if(!use_hotkey_ && use_wml_menu_) {
243  cfg["use_hotkey"] = false;
244  }
245 
246  if(!use_hotkey_ && !use_wml_menu_) {
247  ERR_NG << "Bad data: wml_menu_item with both use_wml_menu and "
248  "use_hotkey set to false is not supposed to be possible.";
249  cfg["use_hotkey"] = false;
250  }
251 
252  if(!show_if_.empty()) {
253  cfg.add_child("show_if", show_if_.get_config());
254  }
255 
256  if(!filter_location_.empty()) {
257  cfg.add_child("filter_location", filter_location_.get_config());
258  }
259 
260  if(!command_.empty()) {
261  cfg.add_child("command", command_);
262  }
263 
264  if(!default_hotkey_.empty()) {
265  cfg.add_child("default_hotkey", default_hotkey_);
266  }
267 }
268 
270 {
271  const bool old_use_hotkey = use_hotkey_;
272  // Tracks whether or not the hotkey has been updated.
273  bool hotkey_updated = false;
274 
275  if(vcfg.has_attribute("image")) {
276  image_ = vcfg["image"].str();
277  }
278 
279  if(vcfg.has_attribute("description")) {
280  description_ = vcfg["description"].t_str();
281  hotkey_updated = true;
282  }
283 
284  if(vcfg.has_attribute("needs_select")) {
285  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
286  needs_select_ = vcfg["needs_select"].to_bool();
287  }
288 
289  if(vcfg.has_attribute("synced")) {
290  is_synced_ = vcfg["synced"].to_bool(true);
291  }
292 
293  if(vcfg.has_attribute("persistent")) {
294  persistent_ = vcfg["persistent"].to_bool(true);
295  }
296 
297  if(const vconfig& child = vcfg.child("show_if")) {
298  show_if_ = child;
300  }
301 
302  if(const vconfig& child = vcfg.child("filter_location")) {
303  filter_location_ = child;
305  }
306 
307  if(const vconfig& child = vcfg.child("default_hotkey")) {
308  default_hotkey_ = child.get_parsed_config();
309  hotkey_updated = true;
310  }
311 
312  if(vcfg.has_attribute("use_hotkey")) {
313  const config::attribute_value& use_hotkey_av = vcfg["use_hotkey"];
314 
315  use_hotkey_ = use_hotkey_av.to_bool(true);
316  use_wml_menu_ = use_hotkey_av.str() != "only";
317  }
318 
319  if(const vconfig& cmd = vcfg.child("command")) {
320  const bool delayed = cmd["delayed_variable_substitution"].to_bool(true);
321  update_command(delayed ? cmd.get_config() : cmd.get_parsed_config());
322  }
323 
324  // Update the registered hotkey?
325 
326  if(use_hotkey_ && !old_use_hotkey) {
327  // The hotkey needs to be enabled.
329 
330  } else if(use_hotkey_ && hotkey_updated) {
331  // The hotkey needs to be updated.
333 
334  } else if(!use_hotkey_ && old_use_hotkey) {
335  // The hotkey needs to be disabled.
336  hotkey_record_.reset();
337  }
338 }
339 
340 void wml_menu_item::update_command(const config& new_command)
341 {
342  // If there is an old command, remove it from the event handlers.
343  assert(resources::game_events);
344 
346  if(ptr->is_menu_item()) {
347  LOG_NG << "Removing command for " << event_name_ << ".";
348  man.remove_event_handler(command_["id"].str());
349  }
350  });
351 
352  // Update our stored command.
353  if(new_command.empty()) {
354  command_.clear();
355  } else {
356  command_ = new_command;
357 
358  // Set some fields required by event processing.
359  config::attribute_value& event_id = command_["id"];
360  if(event_id.empty() && !item_id_.empty()) {
361  event_id = item_id_;
362  }
363 
364  command_["name"] = event_name_;
365  command_["first_time_only"] = false;
366  command_["priority"] = 0.;
367 
368  // Register the event.
369  LOG_NG << "Setting command for " << event_name_ << " to:\n" << command_;
370  assert(resources::game_events);
371  assert(resources::lua_kernel);
373  }
374 }
375 
376 } // end namespace game_events
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
bool to_bool(bool def=false) const
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
bool empty() const
Definition: config.cpp:849
void clear()
Definition: config.cpp:828
config & add_child(config_key_type key)
Definition: config.cpp:440
The game event manager loads the scenario configuration object, and ensures that events are handled a...
Definition: manager.hpp:45
void execute_on_events(const std::string &event_id, event_func_t func)
Definition: manager.cpp:205
game_events::wml_event_pump & pump()
Definition: manager.cpp:253
void remove_event_handler(const std::string &id)
Removes an event handler.
Definition: manager.cpp:112
void add_event_handler_from_wml(const config &handler, game_lua_kernel &lk, bool is_menu_item=false)
Create an event handler from an [event] tag.
Definition: manager.cpp:66
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:399
const std::string event_name_
The name of this item's event(s); based on the item's id.
Definition: menu_item.hpp:159
bool needs_select_
Whether or not this event says it makes use of the last selected unit.
Definition: menu_item.hpp:174
config command_
Actions to take when this item is chosen.
Definition: menu_item.hpp:193
void fire_event(const map_location &event_hex, const game_data &data) const
Causes the event associated with this item to fire.
Definition: menu_item.cpp:168
void update(const vconfig &vcfg)
Updates *this based on vcfg.
Definition: menu_item.cpp:269
const std::string hotkey_id_
The id for this item's hotkey; based on the item's id.
Definition: menu_item.hpp:162
utils::optional< hotkey::wml_hotkey_record > hotkey_record_
Controls the lifetime of the associate hotkey's hotkey_command.
Definition: menu_item.hpp:165
bool use_hotkey_
If true, allow using a hotkey to trigger this item.
Definition: menu_item.hpp:199
wml_menu_item(const std::string &id, const config &cfg)
Constructor for reading from a saved config.
Definition: menu_item.cpp:70
const std::string item_id_
The id of this menu item.
Definition: menu_item.hpp:156
std::string image_
The image to display in the menu next to this item's description.
Definition: menu_item.hpp:168
void update_command(const config &new_command)
Updates our command to new_command.
Definition: menu_item.cpp:340
vconfig show_if_
A condition that must hold in order for this menu item to be visible.
Definition: menu_item.hpp:182
bool can_show(const map_location &hex, const game_data &data, filter_context &context) const
Returns whether or not *this is applicable given the context.
Definition: menu_item.cpp:147
bool use_wml_menu_
If true, allow using the menu to trigger this item.
Definition: menu_item.hpp:202
const std::string & image() const
The image associated with this menu item.
Definition: menu_item.cpp:140
bool persistent_
If true, keep this menu item in later scenarios.
Definition: menu_item.hpp:211
void init_handler(game_lua_kernel &lk)
Initializes the implicit event handler for an inlined [command].
Definition: menu_item.cpp:209
config default_hotkey_
Config object containing the default hotkey.
Definition: menu_item.hpp:196
vconfig filter_location_
A location filter to be applied to the hex where the menu is invoked.
Definition: menu_item.hpp:190
void finish_handler()
Removes the implicit event handler for an inlined [command].
Definition: menu_item.cpp:196
bool is_synced_
If true, this item will be sent to the clients.
Definition: menu_item.hpp:208
void to_config(config &cfg) const
Writes *this to the provided config.
Definition: menu_item.cpp:223
t_string description_
The text to display in the menu for this item.
Definition: menu_item.hpp:171
A RAII object to temporary leave the synced context like in wesnoth.synchronize_choice.
static const std::string wml_menu_hotkey_prefix
static config get_event(const std::string &name, const map_location &loc, const map_location *last_select_loc)
An object to leave the synced context during draw or unsynced wml items when we don’t know whether we...
static bool run_in_synced_context_if_not_already(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
Checks whether we are currently running in a synced context, and if not we enters it.
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
bool has_attribute(const std::string &key) const
< Synonym for operator[]
Definition: variable.hpp:99
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
const config & get_config() const
Definition: variable.hpp:75
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition: variable.cpp:163
bool empty() const
Definition: variable.hpp:100
Define conditionals for the game's events mechanism, a.k.a.
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
Interfaces for manipulating version numbers of engine, add-ons, etc.
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:198
Define the handlers for the game's events mechanism.
This file implements all the hotkey handling and menu details for play controller.
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: menu_item.cpp:38
#define LOG_NG
Definition: menu_item.cpp:39
Declarations for a class that implements WML-defined (right-click) menu items.
std::string wml_menu
Domain specific events.
bool conditional_passed(const vconfig &cond)
std::shared_ptr< event_handler > handler_ptr
Definition: fwd.hpp:25
game_events::manager * game_events
Definition: resources.cpp:24
game_lua_kernel * lua_kernel
Definition: resources.cpp:25
filter_context * filter_con
Definition: resources.cpp:23
std::string_view data
Definition: picture.cpp:178
Define the game's event mechanism.
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: location.hpp:110