wesnoth/src/game_events.cpp
2010-07-19 13:19:25 +00:00

3591 lines
103 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 - 2010 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file game_events.cpp
* Processing of WML-events.
*/
#include "global.hpp"
#include "actions.hpp"
#include "ai/manager.hpp"
#include "dialogs.hpp"
#include "foreach.hpp"
#include "game_display.hpp"
#include "game_events.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "gui/dialogs/gamestate_inspector.hpp"
#include "gui/dialogs/wml_message.hpp"
#include "gui/widgets/window.hpp"
#include "help.hpp"
#include "log.hpp"
#include "map.hpp"
#include "map_label.hpp"
#include "map_exception.hpp"
#include "replay.hpp"
#include "resources.hpp"
#include "scripting/lua.hpp"
#include "sound.hpp"
#include "soundsource.hpp"
#include "terrain_filter.hpp"
#include "unit_display.hpp"
#include "wml_exception.hpp"
#include "play_controller.hpp"
#include "persist_var.hpp"
#include <boost/scoped_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <algorithm>
#include <iomanip>
#include <iostream>
static lg::log_domain log_engine("engine");
#define DBG_NG LOG_STREAM(debug, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
#define WRN_NG LOG_STREAM(warn, log_engine)
#define ERR_NG LOG_STREAM(err, log_engine)
static lg::log_domain log_display("display");
#define DBG_DP LOG_STREAM(debug, log_display)
#define LOG_DP LOG_STREAM(info, log_display)
static lg::log_domain log_wml("wml");
#define DBG_WML LOG_STREAM(debug, log_wml)
#define LOG_WML LOG_STREAM(info, log_wml)
#define WRN_WML LOG_STREAM(warn, log_wml)
#define ERR_WML LOG_STREAM(err, log_wml)
static lg::log_domain log_config("config");
#define ERR_CF LOG_STREAM(err, log_config)
/**
* State when processing a flight of events or commands.
*/
struct event_context
{
bool mutated;
bool skip_messages;
event_context(bool s): mutated(true), skip_messages(s) {}
};
static event_context *current_context;
/**
* Context state with automatic lifetime handling.
*/
struct scoped_context
{
event_context *old_context;
event_context new_context;
scoped_context()
: old_context(current_context)
, new_context(old_context ? old_context->skip_messages : false)
{
current_context = &new_context;
}
~scoped_context()
{
if (old_context) old_context->mutated |= new_context.mutated;
current_context = old_context;
}
};
/**
* Failsafe context state, in case commands are executed outside an event.
*/
struct scoped_dummy_context
{
bool dummy_context;
scoped_dummy_context()
: dummy_context(!current_context)
{
if (!dummy_context) return;
current_context = new event_context(false);
}
~scoped_dummy_context()
{
if (!dummy_context) return;
delete current_context;
current_context = NULL;
}
};
static bool screen_needs_rebuild;
namespace {
std::stringstream wml_messages_stream;
bool manager_running = false;
int floating_label = 0;
std::vector< game_events::event_handler > new_handlers;
typedef std::pair< std::string, config* > wmi_command_change;
std::vector< wmi_command_change > wmi_command_changes;
const gui::msecs prevent_misclick_duration = 10;
const gui::msecs average_frame_time = 30;
class wml_event_dialog : public gui::message_dialog {
public:
wml_event_dialog(game_display &disp, const std::string& title="", const std::string& message="", const gui::DIALOG_TYPE type=gui::MESSAGE)
: message_dialog(disp, title, message, type)
{}
void action(gui::dialog_process_info &info) {
if(result() == gui::CLOSE_DIALOG && !info.key_down && info.key[SDLK_ESCAPE]) {
set_result(gui::ESCAPE_DIALOG);
}
}
};
class pump_manager {
public:
pump_manager() :
x1_(resources::state_of_game->get_variable("x1")),
x2_(resources::state_of_game->get_variable("x2")),
y1_(resources::state_of_game->get_variable("y1")),
y2_(resources::state_of_game->get_variable("y2"))
{
++instance_count;
}
~pump_manager() {
resources::state_of_game->get_variable("x1") = x1_;
resources::state_of_game->get_variable("x2") = x2_;
resources::state_of_game->get_variable("y1") = y1_;
resources::state_of_game->get_variable("y2") = y2_;
--instance_count;
}
static unsigned count() {
return instance_count;
}
private:
static unsigned instance_count;
int x1_, x2_, y1_, y2_;
};
unsigned pump_manager::instance_count=0;
} // end anonymous namespace (1)
#ifdef _MSC_VER
// std::getline might be broken in Visual Studio so show a warning
#if _MSC_VER < 1300
#ifndef GETLINE_PATCHED
#pragma message("warning: the std::getline implementation in your compiler might be broken.")
#pragma message(" http://support.microsoft.com/default.aspx?scid=kb;EN-US;q240015")
#endif
#endif
#endif
/**
* Helper function which determines whether a wml_message text can
* really be pushed into the wml_messages_stream, and does it.
*/
static void put_wml_message(const std::string& logger, const std::string& message)
{
if (logger == "err" || logger == "error") {
ERR_WML << message << "\n";
wml_messages_stream << _("Error: ") << message << "\n";
} else if (logger == "warn" || logger == "wrn" || logger == "warning") {
WRN_WML << message << "\n";
wml_messages_stream << _("Warning: ") << message << "\n";
} else if ((logger == "debug" || logger == "dbg") && !lg::debug.dont_log(log_wml)) {
DBG_WML << message << "\n";
wml_messages_stream << _("Debug: ") << message << "\n";
} else if (!lg::info.dont_log(log_wml)) {
LOG_WML << message << "\n";
wml_messages_stream << _("Info: ") << message << "\n";
}
}
/**
* Helper function for show_wml_errors(), which gathers
* the messages from a stringstream.
*/
static void fill_wml_messages_map(std::map<std::string, int>& msg_map, std::stringstream& source)
{
while(true) {
std::string msg;
std::getline(source, msg);
if(source.eof()) {
break;
}
if(msg == "") {
continue;
}
if(msg_map.find(msg) == msg_map.end()) {
msg_map[msg] = 1;
} else {
msg_map[msg]++;
}
}
// Make sure the eof flag is cleared otherwise no new messages are shown
source.clear();
}
/**
* Shows a summary of the errors encountered in WML thusfar,
* to avoid a lot of the same messages to be shown.
* Identical messages are shown once, with (between braces)
* the number of times that message was encountered.
* The order in which the messages are shown does not need
* to be the order in which these messages are encountered.
* Messages are always written to std::cerr.
*/
static void show_wml_errors()
{
// Get all unique messages in messages,
// with the number of encounters for these messages
std::map<std::string, int> messages;
fill_wml_messages_map(messages, lg::wml_error);
// Show the messages collected
const std::string caption = "Invalid WML found";
for(std::map<std::string, int>::const_iterator itor = messages.begin();
itor != messages.end(); ++itor) {
std::stringstream msg;
msg << itor->first;
if(itor->second > 1) {
msg << " (" << itor->second << ")";
}
resources::screen->add_chat_message(time(NULL), caption, 0, msg.str(),
events::chat_handler::MESSAGE_PUBLIC, false);
std::cerr << caption << ": " << msg.str() << '\n';
}
}
static void show_wml_messages()
{
// Get all unique messages in messages,
// with the number of encounters for these messages
std::map<std::string, int> messages;
fill_wml_messages_map(messages, wml_messages_stream);
// Show the messages collected
const std::string caption = "WML";
for(std::map<std::string, int>::const_iterator itor = messages.begin();
itor != messages.end(); ++itor) {
std::stringstream msg;
msg << itor->first;
if(itor->second > 1) {
msg << " (" << itor->second << ")";
}
resources::screen->add_chat_message(time(NULL), caption, 0, msg.str(),
events::chat_handler::MESSAGE_PUBLIC, false);
}
}
typedef void (*wml_handler_function)(
const game_events::queued_event &event_info, const vconfig &cfg);
typedef std::map<std::string, wml_handler_function> static_wml_action_map;
/** Map of the default action handlers known of the engine. */
static static_wml_action_map static_wml_actions;
typedef std::map<std::string, game_events::action_handler *> dynamic_wml_action_map;
/** Map of the action handlers either provided by the engine or added by the current scenario. */
static dynamic_wml_action_map dynamic_wml_actions;
/**
* WML_HANDLER_FUNCTION macro handles auto registeration for wml handlers
*
* @param pname wml tag name
* @param pei the variable name of game_events::queued_event object inside function
* @param pcfg the variable name of config object inside function
*
* You are warned! This is evil macro magic!
*
* The following code registers a [foo] tag:
* \code
* // comment out unused parameters to prevent compiler warnings
* WML_HANDLER_FUNCTION(foo, event_info, cfg)
* {
* // code for foo
* }
* \endcode
*
* Generated code looks like this:
* \code
* void wml_action_foo(...);
* struct wml_func_register_foo {
* wml_func_register_foo() {
* static_wml_actions["foo"] = &wml_func_foo;
* } wml_func_register_foo;
* void wml_func_foo(...)
* {
* // code for foo
* }
* \endcode
*/
#define WML_HANDLER_FUNCTION(pname, pei, pcfg) \
static void wml_func_##pname(const game_events::queued_event &pei, const vconfig &pcfg); \
struct wml_func_register_##pname \
{ \
wml_func_register_##pname() \
{ static_wml_actions[#pname] = &wml_func_##pname; } \
}; \
static wml_func_register_##pname wml_func_register_##pname##_aux; \
static void wml_func_##pname(const game_events::queued_event& pei, const vconfig& pcfg)
namespace game_events {
static bool matches_special_filter(const config &cfg, const vconfig& filter);
static bool internal_conditional_passed(const vconfig& cond, bool& backwards_compat)
{
static std::vector<std::pair<int,int> > default_counts = utils::parse_ranges("1-99999");
// If the if statement requires we have a certain unit,
// then check for that.
const vconfig::child_list& have_unit = cond.get_children("have_unit");
backwards_compat = backwards_compat && have_unit.empty();
for(vconfig::child_list::const_iterator u = have_unit.begin(); u != have_unit.end(); ++u) {
if(resources::units == NULL)
return false;
std::vector<std::pair<int,int> > counts = (*u).has_attribute("count")
? utils::parse_ranges((*u)["count"]) : default_counts;
int match_count = 0;
foreach (const unit &i, *resources::units)
{
if(i.hitpoints() > 0 && unit_matches_filter(i, *u)) {
++match_count;
if(counts == default_counts) {
// by default a single match is enough, so avoid extra work
break;
}
}
}
if(utils::string_bool((*u)["search_recall_list"]))
{
for(std::vector<team>::iterator team = resources::teams->begin();
team!=resources::teams->end(); ++team)
{
if(counts == default_counts && match_count) {
break;
}
const std::vector<unit>& avail_units = team->recall_list();
for(std::vector<unit>::const_iterator unit = avail_units.begin(); unit!=avail_units.end();) {
if(counts == default_counts && match_count) {
break;
}
scoped_recall_unit auto_store("this_unit", team->save_id(), unit - avail_units.begin());
if (unit_matches_filter(*unit, *u)) {
++match_count;
}
}
}
}
if(!in_ranges(match_count, counts)) {
return false;
}
}
// If the if statement requires we have a certain location,
// then check for that.
const vconfig::child_list& have_location = cond.get_children("have_location");
backwards_compat = backwards_compat && have_location.empty();
for(vconfig::child_list::const_iterator v = have_location.begin(); v != have_location.end(); ++v) {
std::set<map_location> res;
terrain_filter(*v, *resources::units).get_locations(res);
std::vector<std::pair<int,int> > counts = (*v).has_attribute("count")
? utils::parse_ranges((*v)["count"]) : default_counts;
if(!in_ranges<int>(res.size(), counts)) {
return false;
}
}
// Check against each variable statement,
// to see if the variable matches the conditions or not.
const vconfig::child_list& variables = cond.get_children("variable");
backwards_compat = backwards_compat && variables.empty();
foreach (const vconfig &values, variables)
{
const std::string name = values["name"];
std::string value = resources::state_of_game->get_variable_const(name);
const double num_value = atof(value.c_str());
#define TEST_STR_ATTR(name, test) do { \
if (values.has_attribute(name)) { \
std::string attr_str = values[name].str(); \
if (!(test)) return false; \
} \
} while (0)
#define TEST_NUM_ATTR(name, test) do { \
if (values.has_attribute(name)) { \
double attr_num = atof(values[name].c_str()); \
if (!(test)) return false; \
} \
} while (0)
TEST_STR_ATTR("equals", value == attr_str);
TEST_NUM_ATTR("numerical_equals", num_value == attr_num);
TEST_STR_ATTR("not_equals", value != attr_str);
TEST_NUM_ATTR("numerical_not_equals", num_value != attr_num);
TEST_NUM_ATTR("greater_than", num_value > attr_num);
TEST_NUM_ATTR("less_than", num_value < attr_num);
TEST_NUM_ATTR("greater_than_equal_to", num_value >= attr_num);
TEST_NUM_ATTR("less_than_equal_to", num_value <= attr_num);
TEST_STR_ATTR("boolean_equals",
utils::string_bool(value) == utils::string_bool(attr_str));
TEST_STR_ATTR("boolean_not_equals",
utils::string_bool(value) != utils::string_bool(attr_str));
TEST_STR_ATTR("contains", value.find(attr_str) != std::string::npos);
#undef TEST_STR_ATTR
#undef TEST_NUM_ATTR
}
return true;
}
bool conditional_passed(const vconfig& cond, bool backwards_compat)
{
bool allow_backwards_compat = backwards_compat = backwards_compat &&
utils::string_bool(cond["backwards_compat"],true);
bool matches = internal_conditional_passed(cond, allow_backwards_compat);
// Handle [and], [or], and [not] with in-order precedence
int or_count = 0;
vconfig::all_children_iterator cond_i = cond.ordered_begin();
vconfig::all_children_iterator cond_end = cond.ordered_end();
while(cond_i != cond_end)
{
const std::string& cond_name = cond_i.get_key();
const vconfig& cond_filter = cond_i.get_child();
// Handle [and]
if(cond_name == "and")
{
matches = matches && conditional_passed(cond_filter, backwards_compat);
backwards_compat = false;
}
// Handle [or]
else if(cond_name == "or")
{
matches = matches || conditional_passed(cond_filter, backwards_compat);
++or_count;
}
// Handle [not]
else if(cond_name == "not")
{
matches = matches && !conditional_passed(cond_filter, backwards_compat);
backwards_compat = false;
}
++cond_i;
}
// Check for deprecated [or] syntax
if(matches && or_count > 1 && allow_backwards_compat)
{
lg::wml_error << "possible deprecated [or] syntax: now forcing re-interpretation\n";
/**
* @todo For now we will re-interpret it according to the old
* rules, but this should be later to prevent re-interpretation
* errors.
*/
const vconfig::child_list& orcfgs = cond.get_children("or");
for(unsigned int i=0; i < orcfgs.size(); ++i) {
if(conditional_passed(orcfgs[i])) {
return true;
}
}
return false;
}
return matches;
}
void handle_wml_log_message(const config& cfg)
{
const std::string& logger = cfg["logger"];
const std::string& msg = cfg["message"];
put_wml_message(logger,msg);
}
void handle_deprecated_message(const config& cfg)
{
// Note: no need to translate the string, since only used for deprecated things.
const std::string& message = cfg["message"];
lg::wml_error << message << '\n';
}
} // end namespace game_events (1)
namespace {
std::set<std::string> used_items;
} // end anonymous namespace (2)
static bool events_init() { return resources::screen != 0; }
namespace {
std::deque<game_events::queued_event> events_queue;
} // end anonymous namespace (3)
static map_location cfg_to_loc(const vconfig& cfg,int defaultx = 0, int defaulty = 0)
{
int x = lexical_cast_default(cfg["x"], defaultx) - 1;
int y = lexical_cast_default(cfg["y"], defaulty) - 1;
return map_location(x, y);
}
namespace {
std::vector<game_events::event_handler> event_handlers;
} // end anonymous namespace (4)
static void toggle_shroud(const bool remove, const vconfig& cfg)
{
std::string side = cfg["side"];
const int side_num = lexical_cast_default<int>(side,1);
const size_t index = side_num-1;
if (index < resources::teams->size())
{
team &t = (*resources::teams)[index];
std::set<map_location> locs;
terrain_filter filter(cfg, *resources::units);
filter.restrict_size(game_config::max_loop);
filter.get_locations(locs, true);
foreach (map_location const &loc, locs)
{
if (remove) {
t.clear_shroud(loc);
} else {
t.place_shroud(loc);
}
}
}
resources::screen->labels().recalculate_shroud();
resources::screen->recalculate_minimap();
resources::screen->invalidate_all();
}
WML_HANDLER_FUNCTION(remove_shroud, /*event_info*/, cfg)
{
toggle_shroud(true,cfg);
}
WML_HANDLER_FUNCTION(place_shroud, /*event_info*/,cfg)
{
toggle_shroud(false,cfg );
}
WML_HANDLER_FUNCTION(teleport, event_info, cfg)
{
unit_map::iterator u = resources::units->find(event_info.loc1);
// Search for a valid unit filter, and if we have one, look for the matching unit
const vconfig filter = cfg.child("filter");
if(!filter.null()) {
for (u = resources::units->begin(); u != resources::units->end(); ++u){
if(game_events::unit_matches_filter(*u, filter))
break;
}
}
if (u == resources::units->end()) return;
// We have found a unit that matches the filter
const map_location dst = cfg_to_loc(cfg);
if (dst == u->get_location() || !resources::game_map->on_board(dst)) return;
const unit *pass_check = &*u;
if (utils::string_bool(cfg["ignore_passability"]))
pass_check = NULL;
const map_location vacant_dst = find_vacant_tile(*resources::game_map, *resources::units, dst, pathfind::VACANT_ANY, pass_check);
if (!resources::game_map->on_board(vacant_dst)) return;
int side = u->side();
if (utils::string_bool(cfg["clear_shroud"], true)) {
clear_shroud(side);
}
map_location src_loc = u->get_location();
std::vector<map_location> teleport_path;
teleport_path.push_back(src_loc);
teleport_path.push_back(vacant_dst);
bool animate = utils::string_bool(cfg["animate"]);
unit_display::move_unit(teleport_path, *u, *resources::teams, animate);
resources::units->move(src_loc, vacant_dst);
unit::clear_status_caches();
u = resources::units->find(vacant_dst);
u->set_standing();
if (resources::game_map->is_village(vacant_dst)) {
get_village(vacant_dst, side);
}
resources::screen->invalidate_unit_after_move(src_loc, dst);
resources::screen->draw();
}
WML_HANDLER_FUNCTION(unpetrify, /*event_info*/, cfg)
{
const vconfig filter = cfg.child("filter");
// Store which side will need a shroud/fog update
std::vector<bool> clear_fog_side(resources::teams->size(), false);
foreach (unit &u, *resources::units) {
if (!u.get_state(unit::STATE_PETRIFIED)) continue;
if (filter.null() || game_events::unit_matches_filter(u, filter)) {
u.set_state(unit::STATE_PETRIFIED, false);
clear_fog_side[u.side() - 1] = true;
}
}
for (size_t side = 0; side != resources::teams->size(); ++side) {
if (clear_fog_side[side] && (*resources::teams)[side].auto_shroud_updates()) {
clear_shroud(side + 1);
}
}
}
WML_HANDLER_FUNCTION(allow_recruit, /*event_info*/, cfg)
{
int side_num = lexical_cast_default<int>(cfg["side"], 1);
unsigned index = side_num - 1;
if (index >= resources::teams->size()) return;
foreach (const std::string &r, utils::split(cfg["type"])) {
(*resources::teams)[index].add_recruit(r);
preferences::encountered_units().insert(r);
}
}
WML_HANDLER_FUNCTION(volume, /*event_info*/, cfg)
{
int vol;
float rel;
std::string music = cfg["music"];
std::string sound = cfg["sound"];
if(!music.empty()) {
vol = preferences::music_volume();
rel = atof(music.c_str());
if (rel >= 0.0 && rel < 100.0) {
vol = static_cast<int>(rel*vol/100.0);
}
sound::set_music_volume(vol);
}
if(!sound.empty()) {
vol = preferences::sound_volume();
rel = atof(sound.c_str());
if (rel >= 0.0 && rel < 100.0) {
vol = static_cast<int>(rel*vol/100.0);
}
sound::set_sound_volume(vol);
}
}
WML_HANDLER_FUNCTION(sound, /*event_info*/, cfg)
{
play_controller *controller = resources::controller;
if(controller->is_skipping_replay()) {
return;
}
std::string sound = cfg["name"];
const int repeats = lexical_cast_default<int>(cfg["repeat"], 0);
sound::play_sound(sound, sound::SOUND_FX, repeats);
}
static void color_adjust(const vconfig& cfg)
{
game_display &screen = *resources::screen;
std::string red = cfg["red"];
std::string green = cfg["green"];
std::string blue = cfg["blue"];
const int r = atoi(red.c_str());
const int g = atoi(green.c_str());
const int b = atoi(blue.c_str());
screen.adjust_colors(r,g,b);
screen.invalidate_all();
screen.draw(true,true);
}
WML_HANDLER_FUNCTION(color_adjust, /*event_info*/, cfg)
{
color_adjust(cfg);
}
//Function handling old name
//TODO: remove it when deprecated (should be 1.9.2)
WML_HANDLER_FUNCTION(colour_adjust, /*event_info*/, cfg)
{
color_adjust(cfg);
}
WML_HANDLER_FUNCTION(delay, /*event_info*/, cfg)
{
game_display &screen = *resources::screen;
std::string delay_string = cfg["time"];
const int delay_time = atoi(delay_string.c_str());
screen.delay(delay_time);
}
WML_HANDLER_FUNCTION(scroll, /*event_info*/, cfg)
{
game_display &screen = *resources::screen;
std::string x = cfg["x"];
std::string y = cfg["y"];
const int xoff = atoi(x.c_str());
const int yoff = atoi(y.c_str());
screen.scroll(xoff,yoff);
screen.draw(true,true);
}
WML_HANDLER_FUNCTION(scroll_to, /*event_info*/, cfg)
{
game_display &screen = *resources::screen;
const map_location loc = cfg_to_loc(cfg);
std::string check_fogged = cfg["check_fogged"];
screen.scroll_to_tile(loc, game_display::SCROLL, utils::string_bool(check_fogged, false));
}
WML_HANDLER_FUNCTION(scroll_to_unit, /*event_info*/, cfg)
{
unit_map::const_iterator u;
for (u = resources::units->begin(); u != resources::units->end(); ++u){
if (game_events::unit_matches_filter(*u, cfg))
break;
}
std::string check_fogged = cfg["check_fogged"];
if (u != resources::units->end()) {
resources::screen->scroll_to_tile(u->get_location(), game_display::SCROLL, utils::string_bool(check_fogged, false));
}
}
// store time of day config in a WML variable; useful for those who
// are too lazy to calculate the corresponding time of day for a given turn,
// or if the turn / time-of-day sequence mutates in a scenario.
WML_HANDLER_FUNCTION(store_time_of_day, /*event_info*/, cfg)
{
const map_location loc = cfg_to_loc(cfg, -999, -999);
const size_t turn = lexical_cast_default<size_t>(cfg["turn"], 0);
const time_of_day tod = turn ? resources::tod_manager->get_time_of_day(0,loc,turn) : resources::tod_manager->get_time_of_day(0,loc);
std::string variable = cfg["variable"];
if(variable.empty()) {
variable = "time_of_day";
}
variable_info store(variable, true, variable_info::TYPE_CONTAINER);
config tod_cfg;
tod.write(tod_cfg);
(*store.vars).add_child(store.key, tod_cfg);
}
WML_HANDLER_FUNCTION(inspect, /*event_info*/, cfg)
{
if (game_config::debug) {
gui2::tgamestate_inspector inspect_dialog(cfg);
inspect_dialog.show(resources::screen->video());
}
}
WML_HANDLER_FUNCTION(modify_ai, /*event_info*/, cfg)
{
std::string side = cfg["side"];
const int side_num = lexical_cast_default<int>(side,0);
if (side_num==0) {
return;
}
ai::manager::modify_active_ai_for_side(side_num,cfg.get_parsed_config());
}
WML_HANDLER_FUNCTION(modify_side, /*event_info*/, cfg)
{
std::vector<team> &teams = *resources::teams;
std::string side = cfg["side"];
std::string income = cfg["income"];
std::string name = cfg["name"];
std::string team_name = cfg["team_name"];
std::string user_team_name = cfg["user_team_name"];
std::string gold = cfg["gold"];
std::string controller = cfg["controller"];
std::string recruit_str = cfg["recruit"];
std::string fog = cfg["fog"];
std::string shroud = cfg["shroud"];
std::string hidden = cfg["hidden"];
std::string shroud_data = cfg["shroud_data"];
std::string village_gold = cfg["village_gold"];
const config& parsed = cfg.get_parsed_config();
const config::const_child_itors &ai = parsed.child_range("ai");
/**
* @todo also allow client to modify a side's color if it is possible
* to change it on the fly without causing visual glitches
*/
std::string switch_ai = cfg["switch_ai"];
std::string share_view = cfg["share_view"];
std::string share_maps = cfg["share_maps"];
const int side_num = lexical_cast_default<int>(side,1);
const size_t team_index = side_num-1;
if (team_index < teams.size())
{
LOG_NG << "modifying side: " << side_num << "\n";
if(!team_name.empty()) {
LOG_NG << "change side's team to team_name '" << team_name << "'\n";
teams[team_index].change_team(team_name,
user_team_name);
} else if(!user_team_name.empty()) {
LOG_NG << "change side's user_team_name to '" << user_team_name << "'\n";
teams[team_index].change_team(teams[team_index].team_name(),
user_team_name);
}
// Modify recruit list (override)
if (!recruit_str.empty()) {
std::vector<std::string> recruit = utils::split(recruit_str);
if (recruit.size() == 1 && recruit.back() == "")
recruit.clear();
teams[team_index].set_recruits(std::set<std::string>(recruit.begin(),recruit.end()));
}
// Modify income
if (!income.empty()) {
teams[team_index].set_base_income(lexical_cast_default<int>(income) + game_config::base_income);
}
// Modify total gold
if (!gold.empty()) {
teams[team_index].set_gold(lexical_cast_default<int>(gold));
}
// Set controller
if (!controller.empty()) {
teams[team_index].change_controller(controller);
}
// Set shroud
if (!shroud.empty()) {
teams[team_index].set_shroud(utils::string_bool(shroud, true));
}
// Merge shroud data
if (!shroud_data.empty()) {
teams[team_index].merge_shroud_map_data(shroud_data);
}
// Set whether team is hidden in status table
if (!hidden.empty()) {
teams[team_index].set_hidden(utils::string_bool(hidden, true));
}
// Set fog
if (!fog.empty()) {
teams[team_index].set_fog(utils::string_bool(fog, true));
}
// Set income per village
if (!village_gold.empty()) {
teams[team_index].set_village_gold(lexical_cast_default<int>(village_gold));
}
// Redeploy ai from location (this ignores current AI parameters)
if (!switch_ai.empty()) {
ai::manager::add_ai_for_side_from_file(side_num,switch_ai,true);
}
// Override AI parameters
if (ai.first != ai.second) {
ai::manager::modify_active_ai_config_old_for_side(side_num,ai);
}
// Add shared view to current team
if (!share_view.empty()){
teams[team_index].set_share_view(utils::string_bool(share_view, true));
team::clear_caches();
resources::screen->recalculate_minimap();
resources::screen->invalidate_all();
}
// Add shared maps to current team
// IMPORTANT: this MUST happen *after* share_view is changed
if (!share_maps.empty()){
teams[team_index].set_share_maps(utils::string_bool(share_maps, true));
team::clear_caches();
resources::screen->recalculate_minimap();
resources::screen->invalidate_all();
}
}
}
WML_HANDLER_FUNCTION(store_side, /*event_info*/, cfg)
{
game_state *state_of_game = resources::state_of_game;
std::vector<team> &teams = *resources::teams;
std::string side = cfg["side"];
std::string var_name = cfg["variable"];
if (var_name.empty()) var_name = "side";
int side_num = lexical_cast_default<int>(side, 1);
size_t team_index = side_num - 1;
if (team_index >= teams.size()) return;
config side_data;
teams[team_index].write(side_data);
state_of_game->get_variable(var_name+".controller") = side_data["controller"];
state_of_game->get_variable(var_name+".recruit") = side_data["recruit"];
state_of_game->get_variable(var_name+".fog") = side_data["fog"];
state_of_game->get_variable(var_name+".shroud") = side_data["shroud"];
state_of_game->get_variable(var_name+".hidden") = side_data["hidden"];
state_of_game->get_variable(var_name+".income") = teams[team_index].total_income();
state_of_game->get_variable(var_name+".village_gold") = teams[team_index].village_gold();
state_of_game->get_variable(var_name+".name") = teams[team_index].name();
state_of_game->get_variable(var_name+".team_name") = teams[team_index].team_name();
state_of_game->get_variable(var_name+".user_team_name") = teams[team_index].user_team_name();
state_of_game->get_variable(var_name+".color") = teams[team_index].map_color_to();
state_of_game->get_variable(var_name+".gold") = teams[team_index].gold();
}
WML_HANDLER_FUNCTION(modify_turns, /*event_info*/, cfg)
{
std::string value = cfg["value"];
std::string add = cfg["add"];
std::string current = cfg["current"];
tod_manager& tod_man = *resources::tod_manager;
if(!add.empty()) {
tod_man.modify_turns(add);
} else if(!value.empty()) {
tod_man.add_turns(-tod_man.number_of_turns());
tod_man.add_turns(lexical_cast_default<int>(value,-1));
}
// change current turn only after applying mods
if(!current.empty()) {
const unsigned int current_turn_number = tod_man.turn();
const int new_turn_number = lexical_cast_default<int>(current, current_turn_number);
const unsigned int new_turn_number_u = static_cast<unsigned int>(new_turn_number);
if(new_turn_number_u < current_turn_number || (new_turn_number > tod_man.number_of_turns() && tod_man.number_of_turns() != -1)) {
ERR_NG << "attempted to change current turn number to one out of range (" << new_turn_number << ") or less than current turn\n";
} else if(new_turn_number_u != current_turn_number) {
tod_man.set_turn(new_turn_number_u);
resources::state_of_game->get_variable("turn_number") = new_turn_number;
resources::screen->new_turn();
}
}
}
WML_HANDLER_FUNCTION(store_turns, /*event_info*/, cfg)
{
std::string var_name = cfg["variable"];
if(var_name.empty()) {
var_name = "turns";
}
int turns = resources::tod_manager->number_of_turns();
resources::state_of_game->get_variable(var_name) = turns;
}
namespace {
std::auto_ptr<unit> create_fake_unit(const vconfig& cfg)
{
std::string type = cfg["type"];
std::string side = cfg["side"];
std::string variation = cfg["variation"];
size_t side_num = lexical_cast_default<int>(side,1)-1;
if (side_num >= resources::teams->size()) side_num = 0;
unit_race::GENDER gender = string_gender(cfg["gender"]);
const unit_type *ut = unit_types.find(type);
if (!ut) return std::auto_ptr<unit>();
std::auto_ptr<unit> fake_unit(new unit(ut, side_num + 1, false, gender));
if(!variation.empty()) {
config mod;
config &effect = mod.add_child("effect");
effect["apply_to"] = "variation";
effect["name"] = variation;
fake_unit->add_modification("variation",mod);
}
return fake_unit;
}
std::vector<map_location> fake_unit_path(const unit& fake_unit, const std::vector<std::string>& xvals, const std::vector<std::string>& yvals)
{
gamemap *game_map = resources::game_map;
std::vector<map_location> path;
map_location src;
map_location dst;
for(size_t i = 0; i != std::min(xvals.size(),yvals.size()); ++i) {
if(i==0){
src.x = atoi(xvals[i].c_str())-1;
src.y = atoi(yvals[i].c_str())-1;
if (!game_map->on_board(src)) {
ERR_CF << "invalid move_unit_fake source: " << src << '\n';
break;
}
path.push_back(src);
continue;
}
pathfind::shortest_path_calculator calc(fake_unit,
(*resources::teams)[fake_unit.side()-1],
*resources::units,
*resources::teams,
*game_map);
dst.x = atoi(xvals[i].c_str())-1;
dst.y = atoi(yvals[i].c_str())-1;
if (!game_map->on_board(dst)) {
ERR_CF << "invalid move_unit_fake destination: " << dst << '\n';
break;
}
pathfind::plain_route route = pathfind::a_star_search(src, dst, 10000, &calc,
game_map->w(), game_map->h());
if (route.steps.size() == 0) {
WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring complexities\n";
pathfind::emergency_path_calculator calc(fake_unit, *game_map);
route = pathfind::a_star_search(src, dst, 10000, &calc,
game_map->w(), game_map->h());
if(route.steps.size() == 0) {
// This would occur when trying to do a MUF of a unit
// over locations which are unreachable to it (infinite movement
// costs). This really cannot fail.
WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring terrain\n";
pathfind::dummy_path_calculator calc(fake_unit, *game_map);
route = a_star_search(src, dst, 10000, &calc, game_map->w(), game_map->h());
assert(route.steps.size() > 0);
}
}
// we add this section to the end of the complete path
// skipping section's head because already included
// by the previous iteration
path.insert(path.end(),
route.steps.begin()+1, route.steps.end());
src = dst;
}
return path;
}
} //end of anonymous namespace
// Moving a 'unit' - i.e. a dummy unit
// that is just moving for the visual effect
WML_HANDLER_FUNCTION(move_unit_fake, /*event_info*/, cfg)
{
std::auto_ptr<unit> dummy_unit = create_fake_unit(cfg);
if(!dummy_unit.get())
return;
const std::string x = cfg["x"];
const std::string y = cfg["y"];
const std::vector<std::string> xvals = utils::split(x);
const std::vector<std::string> yvals = utils::split(y);
const std::vector<map_location>& path = fake_unit_path(*dummy_unit, xvals, yvals);
if (!path.empty())
unit_display::move_unit(path, *dummy_unit, *resources::teams);
}
WML_HANDLER_FUNCTION(move_units_fake, /*event_info*/, cfg)
{
LOG_WML << "Processing [move_units_fake]\n";
const vconfig::child_list unit_cfgs = cfg.get_children("fake_unit");
size_t num_units = unit_cfgs.size();
boost::ptr_vector<unit> units(num_units);
std::vector<std::vector<map_location> > paths;
paths.reserve(num_units);
game_display* disp = game_display::get_singleton();
LOG_WML << "Moving " << num_units << " units\n";
size_t longest_path = 0;
foreach(const vconfig& config, unit_cfgs) {
const std::vector<std::string> xvals = utils::split(config["x"]);
const std::vector<std::string> yvals = utils::split(config["y"]);
const int skip_steps = lexical_cast_default<int>(config["skip_steps"]);
units.push_back(create_fake_unit(config));
paths.push_back(fake_unit_path(units.back(), xvals, yvals));
if(skip_steps > 0)
paths.back().insert(paths.back().begin(), skip_steps, paths.back().front());
longest_path = std::max(longest_path, paths.back().size());
DBG_WML << "Path " << paths.size() - 1 << " has length " << paths.back().size() << '\n';
units.back().set_location(paths.back().front());
disp->place_temporary_unit(&units.back());
}
LOG_WML << "Units placed, longest path is " << longest_path << " long\n";
std::vector<map_location> path_step(2);
path_step.resize(2);
for(size_t step = 1; step < longest_path; ++step) {
DBG_WML << "Doing step " << step << "...\n";
for(size_t un = 0; un < num_units; ++un) {
if(step >= paths[un].size() || paths[un][step - 1] == paths[un][step])
continue;
DBG_WML << "Moving unit " << un << ", doing step " << step << '\n';
path_step[0] = paths[un][step - 1];
path_step[1] = paths[un][step];
unit_display::move_unit(path_step, units[un], *resources::teams);
units[un].set_location(path_step[1]);
units[un].set_standing();
}
}
LOG_WML << "Units moved\n";
foreach(unit& u, units)
disp->remove_temporary_unit(&u);
LOG_WML << "Units removed\n";
}
// Helper function(s) for [set_variable]
static bool isint(const std::string &var) {
return var.find('.') == std::string::npos;
}
WML_HANDLER_FUNCTION(set_variable, /*event_info*/, cfg)
{
game_state *state_of_game = resources::state_of_game;
const std::string name = cfg["name"];
config::attribute_value &var = state_of_game->get_variable(name);
const t_string &literal = cfg.get_config()["literal"]; // no $var substitution
if(literal.empty() == false) {
var = literal;
}
const t_string value = cfg["value"];
if(value.empty() == false) {
var = value;
}
const t_string format = cfg["format"];
if(format.empty() == false) {
lg::wml_error << "Usage of 'format' is deprecated, use 'rand' instead, "
"support will be removed in 1.9.2.\n";
var = format;
}
const std::string to_variable = cfg["to_variable"];
if(to_variable.empty() == false) {
var = state_of_game->get_variable(to_variable);
}
const std::string add = cfg["add"];
if(add.empty() == false) {
if(isint(var.str()) && isint(add)) {
var = var.to_int() + atoi(add.c_str());
} else {
var = atof(var.str().c_str()) + atof(add.c_str());
}
}
const std::string sub = cfg["sub"];
if(sub.empty() == false) {
if(isint(var.str()) && isint(sub)) {
var = var.to_int() - atoi(sub.c_str());
} else {
var = atof(var.str().c_str()) - atof(sub.c_str());
}
}
const std::string multiply = cfg["multiply"];
if(multiply.empty() == false) {
if(isint(var.str()) && isint(multiply)) {
var = var.to_int() * atoi(multiply.c_str());
} else {
var = atof(var.str().c_str()) * atof(multiply.c_str());
}
}
const std::string divide = cfg["divide"];
if(divide.empty() == false) {
if (std::atof(divide.c_str()) == 0) {
ERR_NG << "division by zero on variable " << name << "\n";
return;
}
if(isint(var.str()) && isint(divide)) {
var = var.to_int() / atoi(divide.c_str());
} else {
var = atof(var.str().c_str()) / atof(divide.c_str());
}
}
const std::string modulo = cfg["modulo"];
if(modulo.empty() == false) {
if(std::atof(modulo.c_str()) == 0) {
ERR_NG << "division by zero on variable " << name << "\n";
return;
}
if(isint(var.str()) && isint(modulo)) {
var = var.to_int() % atoi(modulo.c_str());
} else {
var = std::fmod(atof(var.str().c_str()), atof(modulo.c_str()));
}
}
const std::string round_val = cfg["round"];
if(round_val.empty() == false) {
double value = atof(var.str().c_str());
if (round_val == "ceil") {
value = std::ceil(value);
} else if (round_val == "floor") {
value = std::floor(value);
} else {
// We assume the value is an integer.
// Any non-numerical values will be interpreted as 0
// Which is probably what was intended anyway
const int decimals = std::atoi(round_val.c_str());
value *= std::pow(10.0, decimals); //add $decimals zeroes
value = round_portable(value); // round() isn't implemented everywhere
value *= std::pow(10.0, -decimals); //and remove them
}
var = value;
}
const t_string ipart = cfg["ipart"];
if(ipart.empty() == false) {
double result;
std::modf(std::atof(ipart.c_str()), &result);
var = result;
}
const t_string fpart = cfg["fpart"];
if(fpart.empty() == false) {
double ignore;
var = std::modf(std::atof(fpart.c_str()), &ignore);
}
const t_string string_length_target = cfg["string_length"];
if(string_length_target.empty() == false) {
var = int(string_length_target.str().length());
}
// Note: maybe we add more options later, eg. strftime formatting.
// For now make the stamp mandatory.
const std::string time = cfg["time"];
if(time == "stamp") {
var = int(SDL_GetTicks());
}
// Random generation works as follows:
// rand=[comma delimited list]
// Each element in the list will be considered a separate choice,
// unless it contains "..". In this case, it must be a numerical
// range (i.e. -1..-10, 0..100, -10..10, etc).
const std::string random = cfg["random"];
std::string rand = cfg["rand"];
if(random.empty() == false) {
lg::wml_error << "Usage of 'random' is deprecated, use 'rand' instead, "
"support will be removed in 1.9.2.\n";
if(rand.empty()) {
rand = random;
}
}
// The new random generator, the logic is a copy paste of the old random.
if(rand.empty() == false) {
assert(state_of_game);
std::string random_value;
std::string word;
std::vector<std::string> words;
std::vector<std::pair<long,long> > ranges;
int num_choices = 0;
std::string::size_type pos = 0, pos2 = std::string::npos;
std::stringstream ss(std::stringstream::in|std::stringstream::out);
while (pos2 != rand.length()) {
pos = pos2+1;
pos2 = rand.find(",", pos);
if (pos2 == std::string::npos)
pos2 = rand.length();
word = rand.substr(pos, pos2-pos);
words.push_back(word);
std::string::size_type tmp = word.find("..");
if (tmp == std::string::npos) {
// Treat this element as a string
ranges.push_back(std::pair<int, int>(0,0));
num_choices += 1;
}
else {
// Treat as a numerical range
const std::string first = word.substr(0, tmp);
const std::string second = word.substr(tmp+2,
rand.length());
long low, high;
ss << first + " " + second;
ss >> low;
ss >> high;
ss.clear();
if (low > high) {
std::swap(low, high);
}
ranges.push_back(std::pair<long, long>(low,high));
num_choices += (high - low) + 1;
// Make 0..0 ranges work
if (high == 0 && low == 0) {
words.pop_back();
words.push_back("0");
}
}
}
int choice = state_of_game->rng().get_next_random() % num_choices;
int tmp = 0;
for(size_t i = 0; i < ranges.size(); ++i) {
tmp += (ranges[i].second - ranges[i].first) + 1;
if (tmp > choice) {
if (ranges[i].first == 0 && ranges[i].second == 0) {
random_value = words[i];
}
else {
tmp = (ranges[i].second - (tmp - choice)) + 1;
ss << tmp;
ss >> random_value;
}
break;
}
}
var = random_value;
}
const vconfig::child_list join_elements = cfg.get_children("join");
if(!join_elements.empty())
{
const vconfig join_element=join_elements.front();
std::string array_name=join_element["variable"];
std::string separator=join_element["separator"];
std::string key_name=join_element["key"];
if(key_name.empty())
{
key_name="value";
}
bool remove_empty=utils::string_bool(join_element["remove_empty"]);
std::string joined_string;
variable_info vi(array_name, true, variable_info::TYPE_ARRAY);
bool first = true;
foreach (const config &cfg, vi.as_array())
{
std::string current_string = cfg[key_name];
if (remove_empty && current_string.empty()) continue;
if (first) first = false;
else joined_string += separator;
joined_string += current_string;
}
var=joined_string;
}
}
WML_HANDLER_FUNCTION(set_variables, /*event_info*/, cfg)
{
const t_string& name = cfg["name"];
variable_info dest(name, true, variable_info::TYPE_CONTAINER);
std::string mode = cfg["mode"]; // replace, append, merge, or insert
if(mode == "extend") {
mode = "append";
} else if(mode != "append" && mode != "merge") {
if(mode == "insert") {
size_t child_count = dest.vars->child_count(dest.key);
if(dest.index >= child_count) {
while(dest.index >= ++child_count) {
//inserting past the end requires empty data
dest.vars->append(config(dest.key));
}
//inserting at the end is handled by an append
mode = "append";
}
} else {
mode = "replace";
}
}
const vconfig::child_list values = cfg.get_children("value");
const vconfig::child_list literals = cfg.get_children("literal");
const vconfig::child_list split_elements = cfg.get_children("split");
config data;
if(cfg.has_attribute("to_variable"))
{
variable_info tovar(cfg["to_variable"], false, variable_info::TYPE_CONTAINER);
if(tovar.is_valid) {
if(tovar.explicit_index) {
data.add_child(dest.key, tovar.as_container());
} else {
variable_info::array_range range = tovar.as_array();
while(range.first != range.second)
{
data.add_child(dest.key, *range.first++);
}
}
}
} else if(!values.empty()) {
for(vconfig::child_list::const_iterator i=values.begin(); i!=values.end(); ++i)
{
data.add_child(dest.key, (*i).get_parsed_config());
}
} else if(!literals.empty()) {
for(vconfig::child_list::const_iterator i=literals.begin(); i!=literals.end(); ++i)
{
data.add_child(dest.key, i->get_config());
}
} else if(!split_elements.empty()) {
const vconfig split_element=split_elements.front();
std::string split_string=split_element["list"];
std::string separator_string=split_element["separator"];
std::string key_name=split_element["key"];
if(key_name.empty())
{
key_name="value";
}
bool remove_empty=utils::string_bool(split_element["remove_empty"]);
char* separator = separator_string.empty() ? NULL : &separator_string[0];
std::vector<std::string> split_vector;
//if no separator is specified, explode the string
if(separator == NULL)
{
for(std::string::iterator i=split_string.begin(); i!=split_string.end(); ++i)
{
split_vector.push_back(std::string(1, *i));
}
}
else {
split_vector=utils::split(split_string, *separator, remove_empty ? utils::REMOVE_EMPTY | utils::STRIP_SPACES : utils::STRIP_SPACES);
}
for(std::vector<std::string>::iterator i=split_vector.begin(); i!=split_vector.end(); ++i)
{
data.add_child(dest.key)[key_name]=*i;
}
}
if(mode == "replace")
{
if(dest.explicit_index) {
dest.vars->remove_child(dest.key, dest.index);
} else {
dest.vars->clear_children(dest.key);
}
}
if(!data.empty())
{
if(mode == "merge")
{
if(dest.explicit_index) {
// merging multiple children into a single explicit index
// requires that they first be merged with each other
data.merge_children(dest.key);
dest.as_container().merge_with(data.child(dest.key));
} else {
dest.vars->merge_with(data);
}
} else if(mode == "insert" || dest.explicit_index) {
foreach (const config &child, data.child_range(dest.key))
{
dest.vars->add_child_at(dest.key, child, dest.index++);
}
} else {
dest.vars->append(data);
}
}
}
WML_HANDLER_FUNCTION(role, /*event_info*/, cfg)
{
bool found = false;
// role= represents the instruction, so we can't filter on it
config item = cfg.get_config();
item.remove_attribute("role");
vconfig filter(item);
// try to match units on the gamemap before the recall lists
std::vector<std::string> types = utils::split(filter["type"]);
const bool has_any_types = !types.empty();
std::vector<std::string>::iterator ti = types.begin(),
ti_end = types.end();
// loop to give precendence based on type order
do {
if (has_any_types) {
item["type"] = *ti;
}
unit_map::iterator itor;
foreach (unit &u, *resources::units) {
if (game_events::unit_matches_filter(u, filter)) {
u.set_role(cfg["role"]);
found = true;
break;
}
}
} while(!found && has_any_types && ++ti != ti_end);
if(!found) {
// next try to match units on the recall lists
std::set<std::string> player_ids;
std::vector<std::string> sides = utils::split(cfg["side"]);
const bool has_any_sides = !sides.empty();
foreach(std::string const& side_str, sides) {
size_t side_num = lexical_cast_default<size_t>(side_str,0);
if(side_num > 0 && side_num <= resources::teams->size()) {
player_ids.insert((resources::teams->begin() + (side_num - 1))->save_id());
}
}
// loop to give precendence based on type order
std::vector<std::string>::iterator ti = types.begin();
do {
if (has_any_types) {
item["type"] = *ti;
}
std::vector<team>::iterator pi,
pi_end = resources::teams->end();
for (pi = resources::teams->begin(); pi != pi_end; ++pi)
{
std::string const& player_id = pi->save_id();
// Verify the filter's side= includes this player
if(has_any_sides && !player_ids.count(player_id)) {
continue;
}
// Iterate over the player's recall list to find a match
for(size_t i=0; i < pi->recall_list().size(); ++i) {
unit& u = pi->recall_list()[i];
scoped_recall_unit auto_store("this_unit", player_id, i);
if (u.matches_filter(filter, map_location())) {
u.set_role(cfg["role"]);
found=true;
break;
}
}
}
} while(!found && has_any_types && ++ti != ti_end);
}
}
WML_HANDLER_FUNCTION(removeitem, event_info, cfg)
{
game_display &screen = *resources::screen;
std::string img = cfg["image"];
map_location loc = cfg_to_loc(cfg);
if(!loc.valid()) {
loc = event_info.loc1;
}
if(!img.empty()) { //If image key is set remove that one item
screen.remove_single_overlay(loc, img);
}
else { //Else remove the overlay completely
screen.remove_overlay(loc);
}
}
WML_HANDLER_FUNCTION(unit_overlay, /*event_info*/, cfg)
{
foreach (unit &u, *resources::units) {
if (game_events::unit_matches_filter(u, cfg)) {
u.add_overlay(cfg["image"]);
break;
}
}
}
WML_HANDLER_FUNCTION(remove_unit_overlay, /*event_info*/, cfg)
{
foreach (unit &u, *resources::units) {
if (game_events::unit_matches_filter(u, cfg)) {
u.remove_overlay(cfg["image"]);
break;
}
}
}
WML_HANDLER_FUNCTION(hide_unit, /*event_info*/, cfg)
{
// Hiding units
const map_location loc = cfg_to_loc(cfg);
unit_map::iterator u = resources::units->find(loc);
if (u.valid()) {
u->set_hidden(true);
resources::screen->invalidate(loc);
resources::screen->draw();
}
}
WML_HANDLER_FUNCTION(unhide_unit, /*event_info*/, cfg)
{
const map_location loc = cfg_to_loc(cfg);
// Unhide all for backward compatibility
foreach (unit &u, *resources::units) {
u.set_hidden(false);
resources::screen->invalidate(loc);
resources::screen->draw();
}
}
// Adding new items
WML_HANDLER_FUNCTION(item, /*event_info*/, cfg)
{
game_display &screen = *resources::screen;
map_location loc = cfg_to_loc(cfg);
const std::string img = cfg["image"];
const std::string halo = cfg["halo"];
const std::string team_name = cfg["team_name"];
const bool visible_in_fog = utils::string_bool(cfg["visible_in_fog"],true);
if (!img.empty() || !halo.empty()) {
screen.add_overlay(loc, img, halo, team_name, visible_in_fog);
screen.invalidate(loc);
screen.draw();
}
}
WML_HANDLER_FUNCTION(sound_source, /*event_info*/, cfg)
{
soundsource::sourcespec spec(cfg.get_parsed_config());
resources::soundsources->add(spec);
}
WML_HANDLER_FUNCTION(remove_sound_source, /*event_info*/, cfg)
{
resources::soundsources->remove(cfg["id"]);
}
void change_terrain(const map_location &loc, const t_translation::t_terrain &t,
gamemap::tmerge_mode mode, bool replace_if_failed)
{
gamemap *game_map = resources::game_map;
t_translation::t_terrain
old_t = game_map->get_terrain(loc),
new_t = game_map->merge_terrains(old_t, t, mode, replace_if_failed);
if (new_t == t_translation::NONE_TERRAIN) return;
preferences::encountered_terrains().insert(new_t);
if (game_map->is_village(old_t) && !game_map->is_village(new_t)) {
int owner = village_owner(loc, *resources::teams);
if (owner != -1)
(*resources::teams)[owner].lose_village(loc);
}
game_map->set_terrain(loc, new_t);
screen_needs_rebuild = true;
foreach (const t_translation::t_terrain &ut, game_map->underlying_union_terrain(loc)) {
preferences::encountered_terrains().insert(ut);
}
}
// Changing the terrain
WML_HANDLER_FUNCTION(terrain, /*event_info*/, cfg)
{
t_translation::t_terrain terrain = t_translation::read_terrain_code(cfg["terrain"]);
if (terrain == t_translation::NONE_TERRAIN) return;
gamemap::tmerge_mode mode = gamemap::BOTH;
if (cfg["layer"] == "base") mode = gamemap::BASE; else
if (cfg["layer"] == "overlay") mode = gamemap::OVERLAY;
bool replace_if_failed = utils::string_bool(cfg["replace_if_failed"]);
foreach (const map_location &loc, parse_location_range(cfg["x"], cfg["y"], true)) {
change_terrain(loc, terrain, mode, replace_if_failed);
}
}
// Creating a mask of the terrain
WML_HANDLER_FUNCTION(terrain_mask, /*event_info*/, cfg)
{
map_location loc = cfg_to_loc(cfg, 1, 1);
gamemap mask(*resources::game_map);
try {
mask.read(cfg["mask"]);
} catch(incorrect_map_format_exception&) {
ERR_NG << "terrain mask is in the incorrect format, and couldn't be applied\n";
return;
} catch(twml_exception& e) {
e.show(*resources::screen);
return;
}
bool border = utils::string_bool(cfg["border"]);
resources::game_map->overlay(mask, cfg.get_parsed_config(), loc.x, loc.y, border);
screen_needs_rebuild = true;
}
static bool try_add_unit_to_recall_list(const map_location& loc, const unit& u)
{
if((*resources::teams)[u.side()-1].persistent()) {
(*resources::teams)[u.side()-1].recall_list().push_back(u);
return true;
} else {
ERR_NG << "Cannot create unit: location (" << loc.x << "," << loc.y <<") is not on the map, and player "
<< u.side() << " has no recall list.\n";
return false;
}
}
// If we should spawn a new unit on the map somewhere
WML_HANDLER_FUNCTION(unit, /*event_info*/, cfg)
{
const config& parsed_cfg = cfg.get_parsed_config();
if (cfg.has_attribute("to_variable")) {
unit new_unit(parsed_cfg, true, resources::state_of_game);
config &var = resources::state_of_game->get_variable_cfg(parsed_cfg["to_variable"]);
var.clear();
new_unit.write(var);
var["placement"] = parsed_cfg["placement"];
var["x"] = parsed_cfg["x"];
var["y"] = parsed_cfg["y"];
return;
}
int side = parsed_cfg["side"].to_int(1);
if ((side<1)||(side > static_cast<int>(resources::teams->size()))) {
ERR_NG << "wrong side in [unit] tag - no such side: "<<side<<" ( number of teams :"<<resources::teams->size()<<")"<<std::endl;
DBG_NG << parsed_cfg.debug();
return;
}
team &tm = resources::teams->at(side-1);
unit_creator uc(tm,resources::game_map->starting_position(side));
uc
.allow_add_to_recall(true)
.allow_discover(true)
.allow_get_village(true)
.allow_invalidate(true)
.allow_rename_side(true)
.allow_show(true);
uc.add_unit(parsed_cfg);
}
// If we should recall units that match a certain description
WML_HANDLER_FUNCTION(recall, /*event_info*/, cfg)
{
LOG_NG << "recalling unit...\n";
bool unit_recalled = false;
config temp_config(cfg.get_config());
// Prevent the recall unit filter from using the location as a criterion
/**
* @todo FIXME: we should design the WML to avoid these types of
* collisions; filters should be named consistently and always have a
* distinct scope.
*/
temp_config["x"] = "recall";
temp_config["y"] = "recall";
vconfig unit_filter(temp_config);
for(int index = 0; !unit_recalled && index < int(resources::teams->size()); ++index) {
LOG_NG << "for side " << index + 1 << "...\n";
const std::string player_id = (*resources::teams)[index].save_id();
if((*resources::teams)[index].recall_list().size() < 1) {
WRN_NG << "recall list is empty when trying to recall!\n"
<< "player_id: " << player_id << " side: " << index+1 << "\n";
continue;
}
std::vector<unit>& avail = (*resources::teams)[index].recall_list();
for(std::vector<unit>::iterator u = avail.begin(); u != avail.end(); ++u) {
DBG_NG << "checking unit against filter...\n";
scoped_recall_unit auto_store("this_unit", player_id, u - avail.begin());
if (u->matches_filter(unit_filter, map_location())) {
map_location loc = cfg_to_loc(cfg);
unit to_recruit(*u);
avail.erase(u); // Erase before recruiting, since recruiting can fire more events
find_recruit_location(index + 1, loc, false);
place_recruit(to_recruit, loc, true, utils::string_bool(cfg["show"], true), true, true);
unit_recalled = true;
break;
}
}
}
}
WML_HANDLER_FUNCTION(object, event_info, cfg)
{
const vconfig filter = cfg.child("filter");
std::string id = cfg["id"];
// If this item has already been used
if(id != "" && used_items.count(id))
return;
std::string image = cfg["image"];
std::string caption = cfg["name"];
std::string text;
map_location loc;
if(!filter.null()) {
foreach (const unit &u, *resources::units) {
if (game_events::unit_matches_filter(u, filter)) {
loc = u.get_location();
break;
}
}
}
if(loc.valid() == false) {
loc = event_info.loc1;
}
const unit_map::iterator u = resources::units->find(loc);
std::string command_type = "then";
if (u != resources::units->end() && (filter.null() || game_events::unit_matches_filter(*u, filter)))
{
text = cfg["description"];
u->add_modification("object", cfg.get_parsed_config());
resources::screen->select_hex(event_info.loc1);
resources::screen->invalidate_unit();
// Mark this item as used up.
used_items.insert(id);
} else {
text = cfg["cannot_use_message"];
command_type = "else";
}
if(!utils::string_bool(cfg["silent"])) {
surface surface(NULL);
if(image.empty() == false) {
surface.assign(image::get_image(image));
}
// Redraw the unit, with its new stats
resources::screen->draw();
try {
const std::string duration_str = cfg["duration"];
const unsigned int lifetime = average_frame_time
* lexical_cast_default<unsigned int>(duration_str, prevent_misclick_duration);
wml_event_dialog to_show(*resources::screen, (surface.null() ? caption : ""), text);
if(!surface.null()) {
to_show.set_image(surface, caption);
}
to_show.layout();
to_show.show(lifetime);
} catch(utils::invalid_utf8_exception&) {
// we already had a warning so do nothing.
}
}
foreach (const vconfig &cmd, cfg.get_children(command_type)) {
handle_event_commands(event_info, cmd);
}
}
WML_HANDLER_FUNCTION(print, /*event_info*/, cfg)
{
// Remove any old message.
if (floating_label)
font::remove_floating_label(floating_label);
// Display a message on-screen
std::string text = cfg["text"];
if(text.empty())
return;
std::string size_str = cfg["size"];
std::string duration_str = cfg["duration"];
std::string red_str = cfg["red"];
std::string green_str = cfg["green"];
std::string blue_str = cfg["blue"];
const int size = lexical_cast_default<int>(size_str,font::SIZE_SMALL);
const int lifetime = lexical_cast_default<int>(duration_str,50);
const int red = lexical_cast_default<int>(red_str,0);
const int green = lexical_cast_default<int>(green_str,0);
const int blue = lexical_cast_default<int>(blue_str,0);
SDL_Color color = create_color(red, green, blue);
const SDL_Rect& rect = resources::screen->map_outside_area();
font::floating_label flabel(text);
flabel.set_font_size(size);
flabel.set_color(color);
flabel.set_position(rect.w/2,rect.h/2);
flabel.set_lifetime(lifetime);
flabel.set_clip_rect(rect);
floating_label = font::add_floating_label(flabel);
}
WML_HANDLER_FUNCTION(deprecated_message, /*event_info*/, cfg)
{
game_events::handle_deprecated_message( cfg.get_parsed_config() );
}
WML_HANDLER_FUNCTION(wml_message, /*event_info*/, cfg)
{
game_events::handle_wml_log_message( cfg.get_parsed_config() );
}
typedef std::map<map_location, int> recursion_counter;
class recursion_preventer {
static recursion_counter counter_;
static const int max_recursion = 10;
map_location loc_;
bool too_many_recursions_;
public:
recursion_preventer(map_location& loc) :
loc_(loc),
too_many_recursions_(false)
{
recursion_counter::iterator inserted = counter_.insert(std::make_pair(loc_, 0)).first;
++inserted->second;
too_many_recursions_ = inserted->second >= max_recursion;
}
~recursion_preventer()
{
recursion_counter::iterator itor = counter_.find(loc_);
if (--itor->second == 0)
{
counter_.erase(itor);
}
}
bool too_many_recursions() const
{
return too_many_recursions_;
}
};
recursion_counter recursion_preventer::counter_ = recursion_counter();
typedef boost::scoped_ptr<recursion_preventer> recursion_preventer_ptr;
WML_HANDLER_FUNCTION(kill, event_info, cfg)
{
// Use (x,y) iteration, because firing events ruins unit_map iteration
for (map_location loc(0,0); loc.x < resources::game_map->w(); ++loc.x)
{
for (loc.y = 0; loc.y < resources::game_map->h(); ++loc.y)
{
unit_map::iterator un = resources::units->find(loc);
if (un != resources::units->end() && game_events::unit_matches_filter(*un, cfg))
{
bool fire_event = false;
game_events::entity_location death_loc(*un);
if(utils::string_bool(cfg["fire_event"])) {
// Prevent infinite recursion of 'die' events
fire_event = true;
recursion_preventer_ptr recursion_prevent;
if (event_info.loc1 == death_loc && (event_info.name == "die" || event_info.name == "last breath"))
{
recursion_prevent.reset(new recursion_preventer(death_loc));
if(recursion_prevent->too_many_recursions())
{
fire_event = false;
ERR_NG << "tried to fire 'die' or 'last breath' event on primary_unit inside its own 'die' or 'last breath' event with 'first_time_only' set to false!\n";
}
}
}
if (fire_event) {
game_events::fire("last breath", death_loc, death_loc);
}
if(utils::string_bool(cfg["animate"])) {
resources::screen->scroll_to_tile(loc);
if (un.valid()) {
unit_display::unit_die(loc, *un);
}
}
if (fire_event)
{
game_events::fire("die", death_loc, death_loc);
un = resources::units->find(death_loc);
if (un != resources::units->end() && death_loc.matches_unit(*un)) {
resources::units->erase(un);
}
}
if (! utils::string_bool(cfg["fire_event"])) {
resources::units->erase(un);
}
}
}
}
// If the filter doesn't contain positional information,
// then it may match units on all recall lists.
t_string const& cfg_x = cfg["x"];
t_string const& cfg_y = cfg["y"];
if((cfg_x.empty() || cfg_x == "recall")
&& (cfg_y.empty() || cfg_y == "recall"))
{
//remove the unit from the corresponding team's recall list
for(std::vector<team>::iterator pi = resources::teams->begin();
pi!=resources::teams->end(); ++pi)
{
std::vector<unit>& avail_units = pi->recall_list();
for(std::vector<unit>::iterator j = avail_units.begin(); j != avail_units.end();) {
scoped_recall_unit auto_store("this_unit", pi->save_id(), j - avail_units.begin());
if (j->matches_filter(cfg, map_location())) {
j = avail_units.erase(j);
} else {
++j;
}
}
}
}
}
// Setting of menu items
WML_HANDLER_FUNCTION(set_menu_item, /*event_info*/, cfg)
{
/*
[set_menu_item]
id=test1
image="buttons/group_all.png"
description="Summon Troll"
[show_if]
[not]
[have_unit]
x,y=$x1,$y1
[/have_unit]
[/not]
[/show_if]
[filter_location]
[/filter_location]
[command]
{LOYAL_UNIT $side_number (Troll) $x1 $y1 (Myname) ( _ "Myname")}
[/command]
[/set_menu_item]
*/
std::string id = cfg["id"];
wml_menu_item*& mref = resources::state_of_game->wml_menu_items[id];
if(mref == NULL) {
mref = new wml_menu_item(id);
}
if(cfg.has_attribute("image")) {
mref->image = cfg["image"];
}
if(cfg.has_attribute("description")) {
mref->description = cfg["description"];
}
if(cfg.has_attribute("needs_select")) {
mref->needs_select = utils::string_bool(cfg["needs_select"], false);
}
if(cfg.has_child("show_if")) {
mref->show_if = cfg.child("show_if").get_config();
}
if(cfg.has_child("filter_location")) {
mref->filter_location = cfg.child("filter_location").get_config();
}
if(cfg.has_child("command")) {
config* new_command = new config(cfg.child("command").get_config());
wmi_command_changes.push_back(wmi_command_change(id, new_command));
}
}
// Unit serialization to and from variables
/** @todo FIXME: Check that store is automove bug safe */
WML_HANDLER_FUNCTION(store_unit, /*event_info*/, cfg)
{
const config empty_filter;
vconfig filter = cfg.child("filter");
if(filter.null()) {
filter = empty_filter;
lg::wml_error << "[store_unit] missing required [filter] tag\n";
}
std::string variable = cfg["variable"];
if(variable.empty()) {
variable="unit";
}
const std::string mode = cfg["mode"];
config to_store;
variable_info varinfo(variable, true, variable_info::TYPE_ARRAY);
const bool kill_units = utils::string_bool(cfg["kill"]);
for(unit_map::iterator i = resources::units->begin(); i != resources::units->end();) {
if (!game_events::unit_matches_filter(*i, filter)) {
++i;
continue;
}
config& data = to_store.add_child(varinfo.key);
i->get_location().write(data);
i->write(data);
if(kill_units) {
resources::units->erase(i++);
} else {
++i;
}
}
t_string const& filter_x = filter["x"];
t_string const& filter_y = filter["y"];
if((filter_x.empty() || filter_x == "recall")
&& (filter_y.empty() || filter_y == "recall"))
{
for(std::vector<team>::iterator pi = resources::teams->begin();
pi != resources::teams->end(); ++pi) {
std::vector<unit>& avail_units = pi->recall_list();
for(std::vector<unit>::iterator j = avail_units.begin(); j != avail_units.end();) {
scoped_recall_unit auto_store("this_unit", pi->save_id(), j - avail_units.begin());
if (!j->matches_filter(filter, map_location())) {
++j;
continue;
}
config& data = to_store.add_child(varinfo.key);
j->write(data);
data["x"] = "recall";
data["y"] = "recall";
if(kill_units) {
j = avail_units.erase(j);
} else {
++j;
}
}
}
}
if(mode != "append") {
varinfo.vars->clear_children(varinfo.key);
}
varinfo.vars->append(to_store);
}
WML_HANDLER_FUNCTION(unstore_unit, /*event_info*/, cfg)
{
const config &var = resources::state_of_game->get_variable_cfg(cfg["variable"]);
try {
config tmp_cfg(var);
const unit u(tmp_cfg, false);
preferences::encountered_units().insert(u.type_id());
map_location loc = cfg_to_loc(
(cfg.has_attribute("x") && cfg.has_attribute("y")) ? cfg : vconfig(var));
if(loc.valid()) {
if(utils::string_bool(cfg["find_vacant"])) {
loc = pathfind::find_vacant_tile(*resources::game_map, *resources::units,loc);
}
resources::units->erase(loc);
resources::units->add(loc, u);
std::string text = cfg["text"];
play_controller *controller = resources::controller;
if(!text.empty() && !controller->is_skipping_replay())
{
// Print floating label
std::string red_str = cfg["red"];
std::string green_str = cfg["green"];
std::string blue_str = cfg["blue"];
const int red = lexical_cast_default<int>(red_str,0);
const int green = lexical_cast_default<int>(green_str,0);
const int blue = lexical_cast_default<int>(blue_str,0);
{
resources::screen->float_label(loc,text,red,green,blue);
}
}
const int side = controller->current_side();
if (utils::string_bool(cfg["advance"], true) && get_replay_source().at_end()
&& (*resources::teams)[side-1].is_local()) {
// Try to advance the unit
// Select advancement if it is on the playing side and the player is a human
const bool sel = (side == static_cast<int>(u.side())
&& (*resources::teams)[side-1].is_human());
// The code in dialogs::advance_unit tests whether the unit can advance
dialogs::advance_unit(loc, !sel, true);
}
} else {
team& t = (*resources::teams)[u.side()-1];
if(t.persistent()) {
// Test whether the recall list has duplicates if so warn.
// This might be removed at some point but the uniqueness of
// the description is needed to avoid the recall duplication
// bugs. Duplicates here might cause the wrong unit being
// replaced by the wrong unit.
if(t.recall_list().size() > 1) {
std::vector<size_t> desciptions;
for(std::vector<unit>::const_iterator citor =
t.recall_list().begin();
citor != t.recall_list().end(); ++citor) {
const size_t desciption =
citor->underlying_id();
if(std::find(desciptions.begin(), desciptions.end(),
desciption) != desciptions.end()) {
lg::wml_error << "Recall list has duplicate unit "
"underlying_ids '" << desciption
<< "' unstore_unit may not work as expected.\n";
} else {
desciptions.push_back(desciption);
}
}
}
// Avoid duplicates in the list.
/**
* @todo it would be better to change recall_list() from
* a vector to a map and use the underlying_id as key.
*/
const size_t key = u.underlying_id();
for(std::vector<unit>::iterator itor =
t.recall_list().begin();
itor != t.recall_list().end(); ++itor) {
LOG_NG << "Replaced unit '"
<< key << "' on the recall list\n";
if(itor->underlying_id() == key) {
t.recall_list().erase(itor);
break;
}
}
t.recall_list().push_back(u);
} else {
ERR_NG << "Cannot unstore unit: recall list is empty for player " << u.side()
<< " and the map location is invalid.\n";
}
}
// If we unstore a leader make sure the team gets a leader if not the loading
// in MP might abort since a side without a leader has a recall list.
if(u.can_recruit()) {
(*resources::teams)[u.side() - 1].have_leader();
}
} catch (game::game_error &e) {
ERR_NG << "could not de-serialize unit: '" << e.message << "'\n";
}
}
WML_HANDLER_FUNCTION(store_starting_location, /*event_info*/, cfg)
{
std::string side = cfg["side"];
std::string variable = cfg["variable"];
if (variable.empty()) {
variable="location";
}
const int side_num = lexical_cast_default<int>(side,1);
const map_location& loc = resources::game_map->starting_position(side_num);
config &loc_store = resources::state_of_game->get_variable_cfg(variable);
loc_store.clear();
loc.write(loc_store);
resources::game_map->write_terrain(loc, loc_store);
if (resources::game_map->is_village(loc)) {
int side = village_owner(loc, *resources::teams) + 1;
loc_store["owner_side"] = side;
}
}
/* [store_villages] : store villages into an array
* Keys:
* - variable (mandatory): variable to store in
* - side: if present, the village should be owned by this side (0=unowned villages)
* - terrain: if present, filter the village types against this list of terrain types
*/
WML_HANDLER_FUNCTION(store_villages, /*event_info*/, cfg)
{
log_scope("store_villages");
std::string variable = cfg["variable"];
if (variable.empty()) {
variable="location";
}
config to_store;
variable_info varinfo(variable, true, variable_info::TYPE_ARRAY);
std::vector<map_location> locs = resources::game_map->villages();
for(std::vector<map_location>::const_iterator j = locs.begin(); j != locs.end(); ++j) {
bool matches = false;
if(cfg.has_attribute("side")) { /** @deprecated, use owner_side instead */
lg::wml_error << "side key is no longer accepted in [store_villages],"
<< " use owner_side instead.\n";
config temp_cfg(cfg.get_config());
temp_cfg["owner_side"] = temp_cfg["side"];
temp_cfg["side"] = "";
matches = terrain_filter(vconfig(temp_cfg), *resources::units).match(*j);
} else {
matches = terrain_filter(cfg, *resources::units).match(*j);
}
if(matches) {
config &loc_store = to_store.add_child(varinfo.key);
j->write(loc_store);
resources::game_map->write_terrain(*j, loc_store);
int side = village_owner(*j, *resources::teams) + 1;
loc_store["owner_side"] = side;
}
}
varinfo.vars->clear_children(varinfo.key);
varinfo.vars->append(to_store);
}
WML_HANDLER_FUNCTION(store_locations, /*event_info*/, cfg)
{
log_scope("store_locations");
std::string variable = cfg["variable"];
if (variable.empty()) {
variable="location";
}
std::set<map_location> res;
terrain_filter filter(cfg, *resources::units);
filter.restrict_size(game_config::max_loop);
filter.get_locations(res, true);
resources::state_of_game->clear_variable_cfg(variable);
for(std::set<map_location>::const_iterator j = res.begin(); j != res.end(); ++j) {
config &loc_store = resources::state_of_game->add_variable_cfg(variable);
j->write(loc_store);
resources::game_map->write_terrain(*j, loc_store);
if (resources::game_map->is_village(*j)) {
int side = village_owner(*j, *resources::teams) + 1;
loc_store["owner_side"] = side;
}
}
}
// Command to take control of a village for a certain side
WML_HANDLER_FUNCTION(capture_village, /*event_info*/, cfg)
{
int side_num = lexical_cast_default<int>(cfg["side"]);
foreach (const map_location &loc, parse_location_range(cfg["x"], cfg["y"])) {
if (resources::game_map->is_village(loc)) {
get_village(loc, side_num);
}
}
}
WML_HANDLER_FUNCTION(end_turn, /*event_info*/, /*cfg*/)
{
resources::controller->force_end_turn();
}
WML_HANDLER_FUNCTION(endlevel, /*event_info*/, cfg)
{
game_state *state_of_game = resources::state_of_game;
unit_map *units = resources::units;
// Remove 0-hp units from the unit map to avoid the following problem:
// In case a die event triggers an endlevel the dead unit is still as a
// 'ghost' in linger mode. After save loading in linger mode the unit
// is fully visible again.
unit_map::iterator u = units->begin();
while (u != units->end()) {
if (u->hitpoints() <= 0) {
units->erase(u++);
} else {
++u;
}
}
std::string next_scenario = cfg["next_scenario"];
if (!next_scenario.empty()) {
state_of_game->classification().next_scenario = next_scenario;
}
std::string end_of_campaign_text = cfg["end_text"];
if (!end_of_campaign_text.empty()) {
state_of_game->classification().end_text = end_of_campaign_text;
}
std::string end_of_campaign_text_delay = cfg["end_text_duration"];
if (!end_of_campaign_text_delay.empty()) {
state_of_game->classification().end_text_duration =
lexical_cast_default<unsigned int,const std::string&>(end_of_campaign_text_delay, state_of_game->classification().end_text_duration);
}
end_level_data &data = resources::controller->get_end_level_data();
std::string result = cfg["result"];
data.custom_endlevel_music = cfg["music"];
data.carryover_report = utils::string_bool(cfg["carryover_report"], true);
data.prescenario_save = utils::string_bool(cfg["save"], true);
data.linger_mode = utils::string_bool(cfg["linger_mode"], true)
&& !resources::teams->empty();
data.reveal_map = utils::string_bool(cfg["reveal_map"], true);
data.gold_bonus = utils::string_bool(cfg["bonus"], true);
data.carryover_percentage = lexical_cast_default<int>
(cfg["carryover_percentage"],
game_config::gold_carryover_percentage);
data.carryover_add = utils::string_bool(cfg["carryover_add"]);
if (result.empty() || result == "victory") {
resources::controller->force_end_level(VICTORY);
} else if (result == "continue") {
lg::wml_error << "continue is deprecated as result in [endlevel]"
" and will be removed in 1.9.2,"
" use the new attributes instead.\n";
data.carryover_percentage = 100;
data.carryover_add = false;
data.carryover_report = false;
data.linger_mode = false;
resources::controller->force_end_level(VICTORY);
} else if (result == "continue_no_save") {
lg::wml_error << "continue_no_save is deprecated as result in [endlevel]"
" and will be removed in 1.9.2,"
" use the new attributes instead.\n";
data.carryover_percentage = 100;
data.carryover_add = false;
data.carryover_report = false;
data.prescenario_save = false;
data.linger_mode = false;
resources::controller->force_end_level(VICTORY);
} else {
data.carryover_add = false;
resources::controller->force_end_level(DEFEAT);
}
}
WML_HANDLER_FUNCTION(redraw, /*event_info*/, cfg)
{
game_display &screen = *resources::screen;
std::string side = cfg["side"];
if (!side.empty()) {
const int side_num = lexical_cast_default<int>(side);
clear_shroud(side_num);
screen.recalculate_minimap();
}
if (screen_needs_rebuild) {
screen_needs_rebuild = false;
screen.recalculate_minimap();
screen.rebuild_all();
}
screen.invalidate_all();
screen.draw(true,true);
}
WML_HANDLER_FUNCTION(animate_unit, event_info, cfg)
{
const events::command_disabler disable_commands;
unit_display::wml_animation(cfg, event_info.loc1);
}
WML_HANDLER_FUNCTION(label, /*event_info*/, cfg)
{
game_display &screen = *resources::screen;
terrain_label label(screen.labels(), cfg.get_config());
screen.labels().set_label(label.location(), label.text(),
label.team_name(), label.color(), label.visible_in_fog(), label.visible_in_shroud(), label.immutable());
}
WML_HANDLER_FUNCTION(heal_unit, event_info, cfg)
{
unit_map *units = resources::units;
const bool animated = utils::string_bool(cfg["animate"],false);
const vconfig healed_filter = cfg.child("filter");
unit_map::iterator u;
if (healed_filter.null()) {
// Try to take the unit at loc1
u = units->find(event_info.loc1);
}
else {
for(u = units->begin(); u != units->end(); ++u) {
if (game_events::unit_matches_filter(*u, healed_filter))
break;
}
}
if (!u.valid()) return;
const vconfig healers_filter = cfg.child("filter_second");
std::vector<unit *> healers;
if (!healers_filter.null()) {
foreach (unit &v, *units) {
if (game_events::unit_matches_filter(v, healers_filter) &&
v.has_ability_type("heals")) {
healers.push_back(&v);
}
}
}
int amount = lexical_cast_default<int>(cfg["amount"],0);
int real_amount = u->hitpoints();
u->heal(amount);
real_amount = u->hitpoints() - real_amount;
if (animated) {
unit_display::unit_healing(*u, u->get_location(),
healers, real_amount);
}
resources::state_of_game->get_variable("heal_amount") = real_amount;
}
// Allow undo sets the flag saying whether the event has mutated the game to false
WML_HANDLER_FUNCTION(allow_undo,/*event_info*/,/*cfg*/)
{
current_context->mutated = false;
}
WML_HANDLER_FUNCTION(open_help, /*event_info*/, cfg)
{
game_display &screen = *resources::screen;
t_string topic_id = cfg["topic"];
help::show_help(screen, topic_id.to_serialized());
}
// Helper namespace to do some subparts for message function
namespace {
/**
* Helper to handle the speaker part of the message.
*
* @param event_info event_info of message.
* @param cfg cfg of message.
*
* @returns The unit who's the speaker or units->end().
*/
unit_map::iterator handle_speaker(
const game_events::queued_event& event_info,
const vconfig& cfg)
{
unit_map *units = resources::units;
game_display &screen = *resources::screen;
unit_map::iterator speaker = units->end();
const std::string speaker_str = cfg["speaker"];
if(speaker_str == "unit") {
speaker = units->find(event_info.loc1);
} else if(speaker_str == "second_unit") {
speaker = units->find(event_info.loc2);
} else if(speaker_str != "narrator") {
for(speaker = units->begin(); speaker != units->end(); ++speaker){
if (game_events::unit_matches_filter(*speaker, cfg))
break;
}
}
if(speaker != units->end()) {
LOG_NG << "set speaker to '" << speaker->name() << "'\n";
LOG_DP << "scrolling to speaker..\n";
const map_location &spl = speaker->get_location();
screen.highlight_hex(spl);
int offset_from_center = std::max<int>(0, spl.y - 1);
screen.scroll_to_tile(map_location(spl.x, offset_from_center));
screen.highlight_hex(spl);
} else if(speaker_str == "narrator") {
LOG_NG << "no speaker\n";
screen.highlight_hex(map_location::null_location);
} else {
return speaker;
}
screen.draw(false);
LOG_DP << "done scrolling to speaker...\n";
return speaker;
}
/**
* Helper to handle the image part of the message.
*
* @param cfg cfg of message.
* @param speaker The speaker of the message.
*
* @returns The image to show.
*/
std::string get_image(const vconfig& cfg, unit_map::iterator speaker)
{
std::string image = cfg["image"];
if (image.empty() && speaker != resources::units->end())
{
// At the moment we use a hack if the image in portrait has
// an image with the same name in the directory transparent
// that image is used.
image = speaker->profile();
const size_t offset = image.find_last_of('/');
if(offset != std::string::npos) {
image.insert(offset, "/transparent");
} else {
image = "transparent/" + image;
}
image::locator locator(image);
if(!locator.file_exists()) {
image = speaker->profile();
#ifndef LOW_MEM
if(image == speaker->absolute_image()) {
image += speaker->image_mods();
}
#endif
}
} else if(!image.empty()) {
// At the moment we use a hack if the image in portrait has
// an image with the same name in the directory transparent
// that image is used.
const size_t offset = image.find_last_of('/');
if(offset != std::string::npos) {
image.insert(offset, "/transparent");
} else {
image = "transparent/" + image;
}
image::locator locator(image);
if(!locator.file_exists()) {
image = cfg["image"];
}
}
return image;
}
/**
* Helper to handle the caption part of the message.
*
* @param cfg cfg of message.
* @param speaker The speaker of the message.
*
* @returns The caption to show.
*/
std::string get_caption(const vconfig& cfg, unit_map::iterator speaker)
{
std::string caption = cfg["caption"];
if (caption.empty() && speaker != resources::units->end()) {
caption = speaker->name();
if(caption.empty()) {
caption = speaker->type_name();
}
}
return caption;
}
} // namespace
// Display a message dialog
WML_HANDLER_FUNCTION(message, event_info, cfg)
{
// Check if there is any input to be made, if not the message may be skipped
const vconfig::child_list menu_items = cfg.get_children("option");
const vconfig::child_list text_input_elements = cfg.get_children("text_input");
const bool has_text_input = (text_input_elements.size() == 1);
bool has_input= (has_text_input || !menu_items.empty() );
// skip messages during quick replay
play_controller *controller = resources::controller;
if(!has_input && (
controller->is_skipping_replay() ||
current_context->skip_messages
))
{
return;
}
// Check if this message is for this side
std::string side_for_raw = cfg["side_for"];
bool side_for_show = true;
if (!side_for_raw.empty())
{
side_for_show = false;
std::vector<std::string> side_for =
utils::split(side_for_raw, ',', utils::STRIP_SPACES | utils::REMOVE_EMPTY);
std::vector<std::string>::iterator itSide;
size_t side;
// Check if any of side numbers are human controlled
for (itSide = side_for.begin(); itSide != side_for.end(); ++itSide)
{
side = lexical_cast_default<size_t>(*itSide);
// Make sanity check that side number is good
// then check if this side is human controlled.
if (side > 0 && side <= resources::teams->size() &&
(*resources::teams)[side-1].is_human())
{
side_for_show = true;
break;
}
}
if (!side_for_show)
{
DBG_NG << "player isn't controlling side which should get message\n";
}
}
unit_map::iterator speaker = handle_speaker(event_info, cfg);
if (speaker == resources::units->end() && cfg["speaker"] != "narrator") {
// No matching unit found, so the dialog can't come up.
// Continue onto the next message.
WRN_NG << "cannot show message\n";
return;
}
std::string sfx = cfg["sound"];
if(sfx != "") {
sound::play_sound(sfx);
}
std::string image = get_image(cfg, speaker);
std::string caption = get_caption(cfg, speaker);
std::vector<std::string> options;
std::vector<vconfig::child_list> option_events;
for(vconfig::child_list::const_iterator mi = menu_items.begin();
mi != menu_items.end(); ++mi) {
std::string msg_str = (*mi)["message"];
if (!mi->has_child("show_if")
|| game_events::conditional_passed(mi->child("show_if")))
{
options.push_back(msg_str);
option_events.push_back((*mi).get_children("command"));
}
}
if(text_input_elements.size()>1) {
lg::wml_error << "too many text_input tags, only one accepted\n";
}
const vconfig text_input_element = has_text_input ?
text_input_elements.front() : vconfig::empty_vconfig();
int option_chosen = 0;
std::string text_input_result;
DBG_DP << "showing dialog...\n";
// If we're not replaying, or if we are replaying
// and there is no input to be made, show the dialog.
if(get_replay_source().at_end() || (options.empty() && !has_text_input) ) {
if (side_for_show && !get_replay_source().is_skipping())
{
const size_t right_offset = image.find("~RIGHT()");
const bool left_side = (right_offset == std::string::npos);
if(!left_side) {
image.erase(right_offset);
}
// Parse input text, if not available all fields are empty
const std::string text_input_label =
text_input_element["label"];
std::string text_input_content = text_input_element["text"];
unsigned input_max_size = lexical_cast_default<unsigned>(
text_input_element["max_length"], 256);
if(input_max_size > 1024 || input_max_size < 1){
lg::wml_error << "invalid maximum size for input "
<< input_max_size<<"\n";
input_max_size=256;
}
const int dlg_result = gui2::show_wml_message(
left_side,
resources::screen->video(),
caption,
cfg["message"],
image,
false,
has_text_input,
text_input_label,
&text_input_content,
input_max_size,
options,
&option_chosen);
// Since gui2::show_wml_message needs to do undrawing the
// chatlines can get garbled and look dirty on screen. Force a
// redraw to fix it.
/** @todo This hack can be removed once gui2 is finished. */
resources::screen->invalidate_all();
resources::screen->draw(true,true);
if(!options.empty()) {
recorder.choose_option(option_chosen);
}
if(has_text_input) {
recorder.text_input(text_input_content);
text_input_result = text_input_content;
}
if(dlg_result == gui2::twindow::CANCEL) {
current_context->skip_messages = true;
}
/**
* @todo enable portrait code in 1.7 and write a clean api.
*/
#if 0
const tportrait* portrait =
speaker->second.portrait(400, tportrait::LEFT);
if(portrait) {
gui2::twml_message_left dlg(
caption,
cfg["message"],
portrait->image,
portrait->mirror);
dlg.show(screen->video());
if(dlg.get_retval() == gui2::twindow::CANCEL) {
handler.skip_messages(true);
}
return;
}
#endif
}
// Otherwise if an input has to be made, get it from the replay data
} else {
const int side = controller->current_side();
if(!options.empty()) {
do_replay_handle(side, "choose");
const config* action = get_replay_source().get_next_action();
if (!action || !*(action = &action->child("choose"))) {
replay::process_error("choice expected but none found\n");
return;
}
const std::string &val = (*action)["value"];
option_chosen = atol(val.c_str());
}
if(has_text_input) {
do_replay_handle(side, "input");
const config* action = get_replay_source().get_next_action();
if (!action || !*(action = &action->child("input"))) {
replay::process_error("input expected but none found\n");
return;
}
text_input_result = (*action)["text"].str();
}
}
// Implement the consequences of the choice
if(options.empty() == false) {
if(size_t(option_chosen) >= menu_items.size()) {
std::stringstream errbuf;
errbuf << "invalid choice (" << option_chosen
<< ") was specified, choice 0 to " << (menu_items.size() - 1)
<< " was expected.\n";
replay::process_error(errbuf.str());
return;
}
foreach (const vconfig &cmd, option_events[option_chosen]) {
handle_event_commands(event_info, cmd);
}
}
if(has_text_input) {
std::string variable_name=text_input_element["variable"];
if(variable_name.empty())
variable_name="input";
resources::state_of_game->set_variable(variable_name, text_input_result);
}
}
// Adding/removing new time_areas dynamically with Standard Location Filters.
WML_HANDLER_FUNCTION(time_area, /*event_info*/, cfg)
{
log_scope("time_area");
const bool remove = utils::string_bool(cfg["remove"],false);
std::string ids = cfg["id"];
if(remove) {
const std::vector<std::string> id_list =
utils::split(ids, ',', utils::STRIP_SPACES | utils::REMOVE_EMPTY);
foreach(const std::string& id, id_list) {
resources::tod_manager->remove_time_area(id);
LOG_NG << "event WML removed time_area '" << id << "'\n";
}
}
else {
std::string id;
if(ids.find(',') != std::string::npos) {
id = utils::split(ids,',',utils::STRIP_SPACES | utils::REMOVE_EMPTY).front();
ERR_NG << "multiple ids for inserting a new time_area; will use only the first\n";
} else {
id = ids;
}
std::set<map_location> locs;
terrain_filter filter(cfg, *resources::units);
filter.restrict_size(game_config::max_loop);
filter.get_locations(locs);
config parsed_cfg = cfg.get_parsed_config();
resources::tod_manager->add_time_area(id, locs, parsed_cfg);
LOG_NG << "event WML inserted time_area '" << id << "'\n";
}
}
//Replacing the current time of day schedule
WML_HANDLER_FUNCTION(replace_schedule, /*event_info*/, cfg)
{
if(cfg.get_children("time").empty()) {
ERR_NG << "attempted to to replace ToD schedule with empty schedule\n";
} else {
resources::tod_manager->replace_schedule(cfg.get_parsed_config());
LOG_NG << "replaced ToD schedule\n";
}
}
// Adding new events
WML_HANDLER_FUNCTION(event, /*event_info*/, cfg)
{
std::string behavior_flag = cfg["delayed_variable_substitution"];
if(!(utils::string_bool(behavior_flag,true)))
{
new_handlers.push_back(game_events::event_handler(cfg.get_parsed_config()));
}
else
{
new_handlers.push_back(game_events::event_handler(cfg.get_config()));
}
}
// Experimental map replace
WML_HANDLER_FUNCTION(replace_map, /*event_info*/, cfg)
{
gamemap *game_map = resources::game_map;
gamemap map(*game_map);
try {
map.read(cfg["map"]);
} catch(incorrect_map_format_exception&) {
lg::wml_error << "replace_map: Unable to load map " << cfg["map"] << "\n";
return;
} catch(twml_exception& e) {
e.show(*resources::screen);
return;
}
if (map.total_width() > game_map->total_width()
|| map.total_height() > game_map->total_height()) {
if (!utils::string_bool(cfg["expand"])) {
lg::wml_error << "replace_map: Map dimension(s) increase but expand is not set\n";
return;
}
}
if (map.total_width() < game_map->total_width()
|| map.total_height() < game_map->total_height()) {
if (!utils::string_bool(cfg["shrink"])) {
lg::wml_error << "replace_map: Map dimension(s) decrease but shrink is not set\n";
return;
}
unit_map *units = resources::units;
unit_map::iterator itor;
for (itor = units->begin(); itor != units->end(); ) {
if (!map.on_board(itor->get_location())) {
if (!try_add_unit_to_recall_list(itor->get_location(), *itor)) {
lg::wml_error << "replace_map: Cannot add a unit that would become off-map to the recall list\n";
}
units->erase(itor++);
} else {
++itor;
}
}
}
*game_map = map;
resources::screen->reload_map();
screen_needs_rebuild = true;
ai::manager::raise_map_changed();
}
// Experimental data persistence
WML_HANDLER_FUNCTION(set_global_variable,/**/,pcfg)
{
verify_and_set_global_variable(pcfg);
}
WML_HANDLER_FUNCTION(get_global_variable,/**/,pcfg)
{
verify_and_get_global_variable(pcfg);
}
WML_HANDLER_FUNCTION(clear_global_variable,/**/,pcfg)
{
verify_and_clear_global_variable(pcfg);
}
/** Handles all the different types of actions that can be triggered by an event. */
static void commit_new_handlers() {
// Commit any spawned events-within-events
while(new_handlers.size() > 0) {
event_handlers.push_back(new_handlers.back());
new_handlers.pop_back();
}
}
static void commit_wmi_commands() {
// Commit WML Menu Item command changes
while(wmi_command_changes.size() > 0) {
wmi_command_change wcc = wmi_command_changes.front();
const bool is_empty_command = wcc.second->empty();
wml_menu_item*& mref = resources::state_of_game->wml_menu_items[wcc.first];
const bool has_current_handler = !mref->command.empty();
mref->command = *(wcc.second);
mref->command["name"] = mref->name;
mref->command["first_time_only"] = false;
if(has_current_handler) {
if(is_empty_command) {
mref->command.add_child("allow_undo");
}
foreach(game_events::event_handler& hand, event_handlers) {
if(hand.is_menu_item() && hand.matches_name(mref->name)) {
LOG_NG << "changing command for " << mref->name << " to:\n" << *wcc.second;
hand = game_events::event_handler(mref->command, true);
}
}
} else if(!is_empty_command) {
LOG_NG << "setting command for " << mref->name << " to:\n" << *wcc.second;
event_handlers.push_back(game_events::event_handler(mref->command, true));
}
delete wcc.second;
wmi_command_changes.erase(wmi_command_changes.begin());
}
}
static bool process_event(game_events::event_handler& handler, const game_events::queued_event& ev)
{
if(handler.disabled())
return false;
unit_map *units = resources::units;
unit_map::iterator unit1 = units->find(ev.loc1);
unit_map::iterator unit2 = units->find(ev.loc2);
bool filtered_unit1 = false, filtered_unit2 = false;
scoped_xy_unit first_unit("unit", ev.loc1.x, ev.loc1.y, *units);
scoped_xy_unit second_unit("second_unit", ev.loc2.x, ev.loc2.y, *units);
scoped_weapon_info first_weapon("weapon", ev.data.child("first"));
scoped_weapon_info second_weapon("second_weapon", ev.data.child("second"));
vconfig filters(handler.get_config());
foreach (const vconfig &f, filters.get_children("filter"))
{
if (unit1 == units->end() || !game_events::unit_matches_filter(*unit1, f)) {
return false;
}
if (!f.empty()) {
filtered_unit1 = true;
}
}
vconfig::child_list special_filters = filters.get_children("filter_attack");
bool special_matches = special_filters.empty();
foreach (const vconfig &f, special_filters)
{
if (unit1 != units->end() && game_events::matches_special_filter(ev.data.child("first"), f)) {
special_matches = true;
}
if (!f.empty()) {
filtered_unit1 = true;
}
}
if(!special_matches) {
return false;
}
foreach (const vconfig &f, filters.get_children("filter_second"))
{
if (unit2 == units->end() || !game_events::unit_matches_filter(*unit2, f)) {
return false;
}
if (!f.empty()) {
filtered_unit2 = true;
}
}
special_filters = filters.get_children("filter_second_attack");
special_matches = special_filters.empty();
foreach (const vconfig &f, special_filters)
{
if (unit2 != units->end() && game_events::matches_special_filter(ev.data.child("second"), f)) {
special_matches = true;
}
if (!f.empty()) {
filtered_unit2 = true;
}
}
if(!special_matches) {
return false;
}
if (ev.loc1.requires_unit() && filtered_unit1 &&
(unit1 == units->end() || !ev.loc1.matches_unit(*unit1))) {
// Wrong or missing entity at src location
return false;
}
if (ev.loc2.requires_unit() && filtered_unit2 &&
(unit2 == units->end() || !ev.loc2.matches_unit(*unit2))) {
// Wrong or missing entity at dst location
return false;
}
// The event hasn't been filtered out, so execute the handler.
scoped_context evc;
handler.handle_event(ev);
if(ev.name == "select") {
resources::state_of_game->last_selected = ev.loc1;
}
if (screen_needs_rebuild) {
screen_needs_rebuild = false;
game_display *screen = resources::screen;
screen->recalculate_minimap();
screen->invalidate_all();
screen->rebuild_all();
}
return current_context->mutated;
}
namespace game_events {
event_handler::event_handler(const config &cfg, bool imi) :
first_time_only_(cfg["first_time_only"].to_bool(true)),
disabled_(false), is_menu_item_(imi), cfg_(cfg)
{}
void event_handler::handle_event(const game_events::queued_event& event_info)
{
if (first_time_only_)
{
disabled_ = true;
}
if (is_menu_item_) {
DBG_NG << cfg_["name"] << " will now invoke the following command(s):\n" << cfg_;
}
handle_event_commands(event_info, vconfig(cfg_));
}
void handle_event_commands(const game_events::queued_event& event_info, const vconfig &cfg)
{
for (vconfig::all_children_iterator i = cfg.ordered_begin(),
i_end = cfg.ordered_end(); i != i_end; ++i)
{
const std::string &cmd = i.get_key();
// Skip if this is a /^filter.*/ tag
if (cmd.compare(0, 6, "filter") == 0)
continue;
handle_event_command(cmd, event_info, i.get_child());
}
// We do this once the event has completed any music alterations
sound::commit_music_changes();
}
void handle_event_command(const std::string &cmd,
const game_events::queued_event &event_info, const vconfig &cfg)
{
log_scope2(log_engine, "handle_event_command");
LOG_NG << "handling command '" << cmd << "' from "
<< (cfg.is_volatile()?"volatile ":"") << "cfg 0x"
<< std::hex << std::setiosflags(std::ios::uppercase)
<< reinterpret_cast<uintptr_t>(&cfg.get_config()) << std::dec << "\n";
scoped_dummy_context dummy;
if (!resources::lua_kernel->run_wml_action(cmd, cfg, event_info))
{
ERR_NG << "Couldn't find function for wml tag: "<< cmd <<"\n";
}
DBG_NG << "done handling command...\n";
}
bool event_handler::matches_name(const std::string &name) const
{
const t_string& t_my_names = cfg_["name"];
const std::string& my_names = t_my_names;
std::string::const_iterator itor,
it_begin = my_names.begin(),
it_end = my_names.end(),
match_it = name.begin(),
match_begin = name.begin(),
match_end = name.end();
int skip_count = 0;
for(itor = it_begin; itor != it_end; ++itor) {
bool do_eat = false,
do_skip = false;
switch(*itor) {
case ',':
if(itor - it_begin - skip_count == match_it - match_begin && match_it == match_end) {
return true;
}
it_begin = itor + 1;
match_it = match_begin;
skip_count = 0;
continue;
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
do_skip = (match_it == match_begin || match_it == match_end);
break;
case ' ':
do_skip = (match_it == match_begin || match_it == match_end);
// fall through to case '_'
case '_':
do_eat = (match_it != match_end && (*match_it == ' ' || *match_it == '_'));
break;
default:
do_eat = (match_it != match_end && *match_it == *itor);
break;
}
if(do_eat) {
++match_it;
} else if(do_skip) {
++skip_count;
} else {
itor = std::find(itor, it_end, ',');
if(itor == it_end) {
return false;
}
it_begin = itor + 1;
match_it = match_begin;
skip_count = 0;
}
}
if(itor - it_begin - skip_count == match_it - match_begin && match_it == match_end) {
return true;
}
return false;
}
bool matches_special_filter(const config &cfg, const vconfig& filter)
{
if (!cfg) {
WRN_NG << "attempt to filter attack for an event with no attack data.\n";
// better to not execute the event (so the problem is more obvious)
return false;
}
const attack_type attack(cfg);
bool matches = attack.matches_filter(filter.get_parsed_config());
// Handle [and], [or], and [not] with in-order precedence
vconfig::all_children_iterator cond_i = filter.ordered_begin();
vconfig::all_children_iterator cond_end = filter.ordered_end();
while(cond_i != cond_end)
{
const std::string& cond_name = cond_i.get_key();
const vconfig& cond_filter = cond_i.get_child();
// Handle [and]
if(cond_name == "and")
{
matches = matches && matches_special_filter(cfg, cond_filter);
}
// Handle [or]
else if(cond_name == "or")
{
matches = matches || matches_special_filter(cfg, cond_filter);
}
// Handle [not]
else if(cond_name == "not")
{
matches = matches && !matches_special_filter(cfg, cond_filter);
}
++cond_i;
}
return matches;
}
bool unit_matches_filter(const unit &u, const vconfig& filter)
{
return u.matches_filter(filter, u.get_location());
}
static std::set<std::string> unit_wml_ids;
manager::manager(const config& cfg)
: variable_manager()
{
assert(!manager_running);
foreach (const config &ev, cfg.child_range("event")) {
event_handlers.push_back(game_events::event_handler(ev));
}
foreach (const std::string &id, utils::split(cfg["unit_wml_ids"])) {
unit_wml_ids.insert(id);
}
resources::lua_kernel = new lua::LuaKernel;
manager_running = true;
foreach (static_wml_action_map::value_type &action, static_wml_actions) {
resources::lua_kernel->set_wml_action(action.first, action.second);
}
const std::string used = cfg["used_items"];
if(!used.empty()) {
const std::vector<std::string>& v = utils::split(used);
for(std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i) {
used_items.insert(*i);
}
}
int wmi_count = 0;
typedef std::pair<std::string, wml_menu_item *> item;
foreach (const item &itor, resources::state_of_game->wml_menu_items) {
if (!itor.second->command.empty()) {
event_handlers.push_back(game_events::event_handler(itor.second->command, true));
}
++wmi_count;
}
if(wmi_count > 0) {
LOG_NG << wmi_count << " WML menu items found, loaded." << std::endl;
}
}
void write_events(config& cfg)
{
assert(manager_running);
foreach (const game_events::event_handler &eh, event_handlers) {
if (eh.disabled() || eh.is_menu_item()) continue;
cfg.add_child("event", eh.get_config());
}
std::stringstream used;
std::set<std::string>::const_iterator u;
for(u = used_items.begin(); u != used_items.end(); ++u) {
if(u != used_items.begin())
used << ",";
used << *u;
}
cfg["used_items"] = used.str();
std::stringstream ids;
for(u = unit_wml_ids.begin(); u != unit_wml_ids.end(); ++u) {
if(u != unit_wml_ids.begin())
ids << ",";
ids << *u;
}
cfg["unit_wml_ids"] = ids.str();
if (resources::soundsources)
resources::soundsources->write_sourcespecs(cfg);
if (resources::screen)
resources::screen->write_overlays(cfg);
}
manager::~manager() {
assert(manager_running);
manager_running = false;
events_queue.clear();
event_handlers.clear();
foreach (dynamic_wml_action_map::value_type &action, dynamic_wml_actions) {
delete action.second;
}
dynamic_wml_actions.clear();
delete resources::lua_kernel;
resources::lua_kernel = NULL;
unit_wml_ids.clear();
used_items.clear();
}
void raise(const std::string& event,
const entity_location& loc1,
const entity_location& loc2,
const config& data)
{
assert(manager_running);
if(!events_init())
return;
LOG_NG << "fire event: " << event << "\n";
events_queue.push_back(game_events::queued_event(event,loc1,loc2,data));
}
bool fire(const std::string& event,
const entity_location& loc1,
const entity_location& loc2,
const config& data)
{
assert(manager_running);
raise(event,loc1,loc2,data);
return pump();
}
void add_events(const config::const_child_itors &cfgs, const std::string &id)
{
if(std::find(unit_wml_ids.begin(),unit_wml_ids.end(),id) == unit_wml_ids.end()) {
unit_wml_ids.insert(id);
foreach (const config &new_ev, cfgs) {
std::vector<game_events::event_handler> &temp = (pump_manager::count()) ? new_handlers : event_handlers;
temp.push_back(game_events::event_handler(new_ev));
}
}
}
void commit()
{
if(pump_manager::count() == 1) {
commit_wmi_commands();
commit_new_handlers();
}
// Dialogs can only be shown if the display is not locked
if (!resources::screen->video().update_locked()) {
show_wml_errors();
show_wml_messages();
}
}
bool pump()
{
assert(manager_running);
if(!events_init())
return false;
pump_manager pump_instance;
if(pump_manager::count() >= game_config::max_loop) {
ERR_NG << "game_events::pump() waiting to process new events because "
<< "recursion level would exceed maximum " << game_config::max_loop << '\n';
return false;
}
bool result = false;
while(events_queue.empty() == false) {
game_events::queued_event ev = events_queue.front();
events_queue.pop_front(); // pop now for exception safety
const std::string& event_name = ev.name;
// Clear the unit cache, since the best clearing time is hard to figure out
// due to status changes by WML. Every event will flush the cache.
unit::clear_status_caches();
bool init_event_vars = true;
foreach(game_events::event_handler& handler, event_handlers) {
if(!handler.matches_name(event_name))
continue;
// Set the variables for the event
if (init_event_vars) {
resources::state_of_game->get_variable("x1") = ev.loc1.x + 1;
resources::state_of_game->get_variable("y1") = ev.loc1.y + 1;
resources::state_of_game->get_variable("x2") = ev.loc2.x + 1;
resources::state_of_game->get_variable("y2") = ev.loc2.y + 1;
init_event_vars = false;
}
LOG_NG << "processing event '" << event_name << "'\n";
if(process_event(handler, ev))
result = true;
}
// Only commit new handlers when finished iterating over event_handlers.
commit();
}
return result;
}
entity_location::entity_location(const map_location &loc, size_t id)
: map_location(loc), id_(id)
{}
entity_location::entity_location(const unit &u)
: map_location(u.get_location()), id_(u.underlying_id())
{}
bool entity_location::matches_unit(const unit& u) const
{
return id_ == u.underlying_id();
}
bool entity_location::requires_unit() const
{
return id_ > 0;
}
} // end namespace game_events (2)