The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
menu_item.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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
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
37static 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.
42namespace game_events
43{
44namespace
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 */
52inline 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 */
62inline std::string make_item_hotkey(const std::string& id)
63{
65}
66
67} // anonymous namespace
68
69// Constructor for reading from a saved config.
70wml_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 , show_if_(cfg.child_or_empty("show_if"), true)
78 , filter_location_(cfg.child_or_empty("filter_location"), true)
79 , command_(cfg.child_or_empty("command"))
80 , default_hotkey_(cfg.child_or_empty("default_hotkey"))
81 , use_hotkey_(cfg["use_hotkey"].to_bool(true))
82 , use_wml_menu_(cfg["use_hotkey"].str() != "only")
83 , is_synced_(cfg["synced"].to_bool(true))
84 , persistent_(cfg["persistent"].to_bool(true))
85 , needs_select_(cfg["needs_select"].to_bool(false))
86{
87 if(cfg.has_attribute("needs_select")) {
88 deprecated_message("needs_select", DEP_LEVEL::FOR_REMOVAL, {1, 21, 0}, "needs_select effects networked multiplayer. Use [sync_variable] to keep variables consistent across clients");
89 }
90}
91
92// Constructor for items defined in an event.
93wml_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 , show_if_(vconfig::empty_vconfig())
101 , filter_location_(vconfig::empty_vconfig())
102 , command_()
103 , default_hotkey_()
104 , use_hotkey_(true)
105 , use_wml_menu_(true)
106 , is_synced_(true)
107 , persistent_(true)
108 , needs_select_(false)
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.
119wml_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 , show_if_(original.show_if_)
127 , filter_location_(original.filter_location_)
128 , command_(original.command_)
129 , default_hotkey_(original.default_hotkey_)
130 , use_hotkey_(original.use_hotkey_)
131 , use_wml_menu_(original.use_wml_menu_)
132 , is_synced_(original.is_synced_)
133 , persistent_(original.persistent_)
134 , needs_select_(original.needs_select_)
135{
136 // Apply WML.
137 update(definition);
138}
139
140const std::string& wml_menu_item::image() const
141{
142 // Default the image?
144}
145
146
147bool wml_menu_item::can_show(const map_location& hex, const game_data& data, filter_context& filter_con) const
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
168void 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()) {
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()) {
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
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::FOR_REMOVAL, {1, 21, 0}, "needs_select effects networked multiplayer. Use [sync_variable] to keep variables consistent across clients");
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
340void wml_menu_item::update_command(const config& new_command)
341{
342 // If there is an old command, remove it from the event handlers.
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_;
371 assert(resources::lua_kernel);
373 }
374}
375
376} // end namespace game_events
#define ERR_NG
#define LOG_NG
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:157
config & add_child(std::string_view key)
Definition config.cpp:436
bool has_attribute(std::string_view key) const
Definition config.cpp:157
bool empty() const
Definition config.cpp:823
void clear()
Definition config.cpp:802
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, const event_func_t &func)
Definition manager.cpp:209
game_events::wml_event_pump & pump()
Definition manager.cpp:257
void remove_event_handler(const std::string &id)
Removes an event handler.
Definition manager.cpp:114
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:68
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.
bool needs_select_
Whether or not this event says it makes use of the last selected unit.
config command_
Actions to take when this item is chosen.
void fire_event(const map_location &event_hex, const game_data &data) const
Causes the event associated with this item to fire.
void update(const vconfig &vcfg)
Updates *this based on vcfg.
const std::string hotkey_id_
The id for this item's hotkey; based on the item's id.
utils::optional< hotkey::wml_hotkey_record > hotkey_record_
Controls the lifetime of the associate hotkey's hotkey_command.
bool use_hotkey_
If true, allow using a hotkey to trigger this item.
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.
std::string image_
The image to display in the menu next to this item's description.
void update_command(const config &new_command)
Updates our command to new_command.
vconfig show_if_
A condition that must hold in order for this menu item to be visible.
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.
bool use_wml_menu_
If true, allow using the menu to trigger this item.
const std::string & image() const
The image associated with this menu item.
bool persistent_
If true, keep this menu item in later scenarios.
void init_handler(game_lua_kernel &lk)
Initializes the implicit event handler for an inlined [command].
config default_hotkey_
Config object containing the default hotkey.
vconfig filter_location_
A location filter to be applied to the hex where the menu is invoked.
void finish_handler()
Removes the implicit event handler for an inlined [command].
bool is_synced_
If true, this item will be sent to the clients.
void to_config(config &cfg) const
Writes *this to the provided config.
t_string description_
The text to display in the menu for this item.
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:289
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition variable.cpp:164
const config & get_config() const
Definition variable.hpp:75
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)
const config * cfg
Interfaces for manipulating version numbers of engine, add-ons, etc.
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
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")
Declarations for a class that implements WML-defined (right-click) menu items.
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
STL namespace.
std::string_view data
Definition picture.cpp:188
Define the game's event mechanism.
Encapsulates the map of the game.
Definition location.hpp:46
bool valid() const
Definition location.hpp:111