wesnoth/src/game_events.cpp
2003-09-19 13:57:37 +00:00

741 lines
20 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "game_events.hpp"
#include "menu.hpp"
#include "language.hpp"
#include "playlevel.hpp"
#include "replay.hpp"
#include <cstdlib>
#include <deque>
#include <iostream>
#include <set>
#include <string>
namespace game_events {
bool conditional_passed(game_state& state_of_game,
const std::map<gamemap::location,unit>* units,
config& cond)
{
//if the if statement requires we have a certain unit, then
//check for that.
std::vector<config*>& have_unit = cond.children["have_unit"];
for(std::vector<config*>::iterator u = have_unit.begin();
u != have_unit.end(); ++u) {
if(units == NULL)
return false;
std::map<gamemap::location,unit>::const_iterator itor;
for(itor = units->begin(); itor != units->end(); ++itor) {
if(itor->second.matches_filter(**u)) {
break;
}
}
if(itor == units->end()) {
return false;
}
}
//check against each variable statement to see if the variable
//matches the conditions or not
std::vector<config*>& variables = cond.children["variable"];
for(std::vector<config*>::iterator var = variables.begin();
var != variables.end(); ++var) {
std::map<std::string,std::string>& values = (*var)->values;
std::map<std::string,std::string>& vars = state_of_game.variables;
const std::string& name = values["name"];
//if we don't have a record of the variable, then the statement
//is not true, unless it's a not equals statement, in which it's
//automatically true
if(vars.find(name) == vars.end()) {
if(values.find("not_equals") == values.end() &&
values.find("numerical_not_equals") == values.end()) {
return false;
}
continue;
}
const std::string& value = vars[name];
const double num_value = atof(value.c_str());
std::map<std::string,std::string>::iterator itor;
itor = values.find("equals");
if(itor != values.end() && itor->second != value) {
return false;
}
itor = values.find("numerical_equals");
if(itor != values.end() && atof(itor->second.c_str()) != num_value){
return false;
}
itor = values.find("not_equals");
if(itor != values.end() && itor->second == value) {
return false;
}
itor = values.find("numerical_not_equals");
if(itor != values.end() && atof(itor->second.c_str()) == num_value){
return false;
}
itor = values.find("greater_than");
if(itor != values.end() && atof(itor->second.c_str()) >= num_value){
return false;
}
itor = values.find("less_than");
if(itor != values.end() && atof(itor->second.c_str()) <= num_value){
return false;
}
itor = values.find("greater_than_equal_to");
if(itor != values.end() && atof(itor->second.c_str()) > num_value){
return false;
}
itor = values.find("less_than_equal_to");
if(itor != values.end() && atof(itor->second.c_str()) < num_value){
return false;
}
}
return true;
}
} //end namespace game_events
namespace {
display* screen = NULL;
gamemap* game_map = NULL;
std::map<gamemap::location,unit>* units = NULL;
game_state* state_of_game = NULL;
game_data* game_data_ptr = NULL;
bool events_init() { return screen != NULL; }
struct queued_event {
queued_event(const std::string& name, const gamemap::location& loc1,
const gamemap::location& loc2)
: name(name), loc1(loc1), loc2(loc2) {}
std::string name;
gamemap::location loc1;
gamemap::location loc2;
};
std::deque<queued_event> events_queue;
class event_handler
{
public:
event_handler(config* cfg) : name_(cfg->values["name"]),
first_time_only_(cfg->values["first_time_only"] != "no"),
disabled_(false),
cfg_(cfg)
{}
const std::string& name() const { return name_; }
bool first_time_only() const { return first_time_only_; }
void disable() { disabled_ = true; }
bool disabled() const { return disabled_; }
std::vector<config*>& first_arg_filters() {
return cfg_->children["filter"];
}
std::vector<config*>& second_arg_filters() {
return cfg_->children["filter_second"];
}
void handle_event(const queued_event& event_info, config* cfg=NULL);
private:
std::string name_;
bool first_time_only_;
bool disabled_;
config* cfg_;
};
std::multimap<std::string,event_handler> events_map;
void event_handler::handle_event(const queued_event& event_info, config* cfg)
{
if(cfg == NULL)
cfg = cfg_;
//sub commands that need to be handled in a guaranteed ordering
std::vector<config*>& commands = cfg->children["command"];
for(std::vector<config*>::iterator cmd = commands.begin();
cmd != commands.end(); ++cmd) {
handle_event(event_info,*cmd);
}
//setting a variable
std::vector<config*>& set_vars = cfg->children["set_variable"];
for(std::vector<config*>::iterator var = set_vars.begin();
var != set_vars.end(); ++var) {
std::map<std::string,std::string>& vals = (*var)->values;
const std::string& name = vals["name"];
const std::string& value = vals["value"];
if(value.empty() == false) {
state_of_game->variables[name] = value;
}
const std::string& add = vals["add"];
if(add.empty() == false) {
double value = atof(state_of_game->variables[name].c_str());
value += atof(add.c_str());
char buf[50];
sprintf(buf,"%f",value);
state_of_game->variables[name] = buf;
}
const std::string& multiply = vals["multiply"];
if(multiply.empty() == false) {
double value = atof(state_of_game->variables[name].c_str());
value *= atof(multiply.c_str());
char buf[50];
sprintf(buf,"%f",value);
state_of_game->variables[name] = buf;
}
}
//conditional statements
std::vector<config*>& conditionals = cfg->children["if"];
for(std::vector<config*>::iterator cond = conditionals.begin();
cond != conditionals.end(); ++cond) {
const std::string type = game_events::conditional_passed(
*state_of_game,units,**cond) ? "then":"else";
//if the if statement passed, then execute all 'then' statements,
//otherwise execute 'else' statements
std::vector<config*>& commands = cfg->children[type];
for(std::vector<config*>::iterator cmd = commands.begin();
cmd != commands.end(); ++cmd) {
handle_event(event_info,*cmd);
}
}
//if we are assigning a role to a unit from the available units list
std::vector<config*>& assign_role = cfg->children["role"];
for(std::vector<config*>::iterator rl = assign_role.begin();
rl != assign_role.end(); ++rl) {
//get a list of the types this unit can be
std::vector<std::string> types = config::split((*rl)->values["type"]);
//iterate over all the types, and for each type, try to find
//a unit that matches
std::vector<std::string>::iterator ti;
for(ti = types.begin(); ti != types.end(); ++ti) {
config cfg;
cfg.values["type"] = *ti;
std::map<gamemap::location,unit>::iterator itor;
for(itor = units->begin(); itor != units->end(); ++itor) {
if(itor->second.matches_filter(cfg)) {
itor->second.assign_role((*rl)->values["role"]);
break;
}
}
if(itor != units->end())
break;
std::vector<unit>::iterator ui;
//iterate over the units, and try to find one that matches
for(ui = state_of_game->available_units.begin();
ui != state_of_game->available_units.end(); ++ui) {
if(ui->matches_filter(cfg)) {
ui->assign_role((*rl)->values["role"]);
break;
}
}
//if we found a unit, we don't have to keep going.
if(ui != state_of_game->available_units.end())
break;
}
//if we found a unit in the current type, we don't have to
//look through any more types
if(ti != types.end())
break;
}
std::vector<config*>& remove_overlays = cfg->children["removeitem"];
for(std::vector<config*>::iterator rm = remove_overlays.begin();
rm != remove_overlays.end(); ++rm) {
gamemap::location loc(**rm);
if(!loc.valid()) {
loc = event_info.loc1;
}
screen->remove_overlay(loc);
}
//adding new items
std::vector<config*>& add_overlays = cfg->children["item"];
for(std::vector<config*>::iterator ni = add_overlays.begin();
ni != add_overlays.end(); ++ni) {
gamemap::location loc(**ni);
const std::string& img = (*ni)->values["image"];
if(!img.empty())
screen->add_overlay(loc,img);
}
//changing the terrain
std::vector<config*>& terrain_changes = cfg->children["terrain"];
for(std::vector<config*>::iterator tc = terrain_changes.begin();
tc != terrain_changes.end(); ++tc) {
gamemap::location loc(**tc);
const std::string& terrain_type = (*tc)->values["letter"];
if(terrain_type.size() > 0) {
game_map->set_terrain(loc,terrain_type[0]);
screen->recalculate_minimap();
screen->invalidate_all();
}
}
//if we should spawn a new unit on the map somewhere
std::vector<config*>& new_units = cfg->children["unit"];
for(std::vector<config*>::iterator ui = new_units.begin();
ui != new_units.end(); ++ui) {
unit new_unit(*game_data_ptr,**ui);
gamemap::location loc(**ui);
if(game_map->on_board(loc)) {
loc = find_vacant_tile(*game_map,*units,loc);
units->insert(std::pair<gamemap::location,unit>(loc,new_unit));
} else {
state_of_game->available_units.push_back(new_unit);
}
}
//if we should recall units that match a certain description
std::vector<config*>& recalls = cfg->children["recall"];
for(std::vector<config*>::iterator ir = recalls.begin();
ir != recalls.end(); ++ir) {
std::vector<unit>& avail = state_of_game->available_units;
for(std::vector<unit>::iterator u = avail.begin();
u != avail.end(); ++u) {
if(u->matches_filter(**ir)) {
recruit_unit(*game_map,1,*units,*u,gamemap::location(),screen);
u = avail.erase(u);
if(u == avail.end())
break;
}
}
}
std::vector<config*>& objects = cfg->children["object"];
for(std::vector<config*>::iterator obj = objects.begin();
obj != objects.end(); ++obj) {
std::map<std::string,std::string>& values = (*obj)->values;
//if this item has already been used
if(values["used"].empty() == false)
continue;
const std::string& id = values["id"];
const std::string image = values["image"];
std::string caption = values["name"];
const std::string& caption_lang = string_table[id + "_name"];
if(caption_lang.empty() == false)
caption = caption_lang;
const std::map<gamemap::location,unit>::iterator u =
units->find(event_info.loc1);
if(u == units->end())
continue;
std::string text;
std::vector<config*>& filters = (*obj)->children["filter"];
if(filters.empty() || u->second.matches_filter(*filters[0])) {
const std::string& lang = string_table[id];
if(!lang.empty())
text = lang;
else
text = values["description"];
u->second.add_modification("object",**obj);
screen->remove_overlay(event_info.loc1);
screen->select_hex(event_info.loc1);
screen->invalidate_unit();
//mark that this item won't be used again
values["used"] = "true";
} else {
const std::string& lang = string_table[id + "_cannot_use"];
if(!lang.empty())
text = lang;
else
text = values["cannot_use_message"];
}
SDL_Surface* surface = NULL;
if(image.empty() == false) {
surface = screen->getImage(image,display::UNSCALED);
}
gui::show_dialog(*screen,surface,caption,text);
//this will redraw the unit, with its new stats
screen->draw();
}
std::vector<config*>& messages = cfg->children["message"];
for(std::vector<config*>::iterator msg = messages.begin();
msg != messages.end(); ++msg) {
std::map<std::string,std::string>& values = (*msg)->values;
std::map<gamemap::location,unit>::iterator speaker = units->end();
if(values["speaker"] == "unit") {
speaker = units->find(event_info.loc1);
} else if(values["speaker"] == "second_unit") {
speaker = units->find(event_info.loc2);
} else if(values["speaker"] != "narrator") {
for(speaker = units->begin(); speaker != units->end();
++speaker){
if(speaker->second.matches_filter(**msg))
break;
}
if(speaker == units->end()) {
//no matching unit found, so the dialog can't come up
//continue onto the next message
continue;
}
}
const std::string& id = values["id"];
std::string image = (*msg)->values["image"];
std::string caption;
const std::string& lang_caption = string_table[id + "_caption"];
if(!lang_caption.empty())
caption = lang_caption;
else
caption = (*msg)->values["caption"];
if(speaker != units->end()) {
screen->highlight_hex(speaker->first);
screen->scroll_to_tile(speaker->first.x,speaker->first.y);
if(image.empty()) {
image = speaker->second.type().image_profile();
}
if(caption.empty()) {
caption = speaker->second.description();
if(caption.empty()) {
caption = speaker->second.type().language_name();
}
}
}
std::vector<std::string> options;
std::vector<std::vector<config*>*> option_events;
std::vector<config*>& menu_items = (*msg)->children["option"];
for(std::vector<config*>::iterator mi = menu_items.begin();
mi != menu_items.end(); ++mi) {
const std::string& lang_msg = string_table[(*mi)->values["id"]];
const std::string& msg = lang_msg.empty() ?
(*mi)->values["message"] : lang_msg;
options.push_back(msg);
option_events.push_back(&(*mi)->children["command"]);
}
SDL_Surface* surface = NULL;
if(image.empty() == false) {
surface = screen->getImage(image,display::UNSCALED);
}
const std::string& lang_message = string_table[id];
int option_chosen = gui::show_dialog(*screen,surface,caption,
lang_message.empty() ? values["message"] : lang_message,
options.empty() ? gui::MESSAGE : gui::OK_ONLY,
options.empty() ? NULL : &options);
if(screen->update_locked() && options.empty() == false) {
config* const cfg = recorder.get_next_action();
if(cfg == NULL || cfg->children["choose"].empty()) {
std::cerr << "choice expected but none found\n";
throw replay::error();
}
const std::string& val =
cfg->children["choose"].front()->values["value"];
option_chosen = atol(val.c_str());
} else if(options.empty() == false) {
recorder.choose_option(option_chosen);
}
if(options.empty() == false) {
assert(size_t(option_chosen) < menu_items.size());
std::vector<config*>& events = *option_events[option_chosen];
for(std::vector<config*>::iterator ev = events.begin();
ev != events.end(); ++ev) {
handle_event(event_info,*ev);
}
}
}
std::vector<config*>& dead_units = cfg->children["kill"];
for(std::vector<config*>::iterator du = dead_units.begin();
du != dead_units.end(); ++du) {
for(std::map<gamemap::location,unit>::iterator i = units->begin();
i != units->end(); ++i) {
while(i->second.matches_filter(**du) && i != units->end()) {
units->erase(i);
i = units->begin();
}
}
std::vector<unit>& avail_units = state_of_game->available_units;
for(std::vector<unit>::iterator j = avail_units.begin();
j != avail_units.end(); ++j) {
while(j->matches_filter(**du) && j != avail_units.end()) {
j = avail_units.erase(j);
}
}
}
//adding of new events
std::vector<config*>& new_events = cfg->children["event"];
for(std::vector<config*>::iterator ne = new_events.begin();
ne != new_events.end(); ++ne) {
event_handler new_handler(*ne);
events_map.insert(std::pair<std::string,event_handler>(
new_handler.name(),new_handler));
}
std::vector<config*>& end_level = cfg->children["endlevel"];
if(end_level.empty() == false) {
config* const end_info = end_level[0];
const std::string& result = end_info->values["result"];
if(result.size() == 0 || result == "victory") {
const bool bonus = (end_info->values["bonus"] == "yes");
throw end_level_exception(VICTORY,bonus);
} else {
throw end_level_exception(DEFEAT);
}
}
}
bool filter_loc_impl(const gamemap::location& loc, const std::string& xloc,
const std::string& yloc)
{
if(std::find(xloc.begin(),xloc.end(),',') != xloc.end()) {
std::vector<std::string> xlocs = config::split(xloc);
std::vector<std::string> ylocs = config::split(yloc);
const int size = xlocs.size() < ylocs.size()?xlocs.size():ylocs.size();
for(int i = 0; i != size; ++i) {
if(filter_loc_impl(loc,xlocs[i],ylocs[i]))
return true;
}
return false;
}
if(!xloc.empty()) {
const std::string::const_iterator dash =
std::find(xloc.begin(),xloc.end(),'-');
if(dash != xloc.end()) {
const std::string beg(xloc.begin(),dash);
const std::string end(dash+1,xloc.end());
const int bot = atoi(beg.c_str()) - 1;
const int top = atoi(end.c_str()) - 1;
if(loc.x < bot || loc.x > top)
return false;
} else {
const int xval = atoi(xloc.c_str()) - 1;
if(xval != loc.x)
return false;
}
}
if(!yloc.empty()) {
const std::string::const_iterator dash =
std::find(yloc.begin(),yloc.end(),'-');
if(dash != yloc.end()) {
const std::string beg(yloc.begin(),dash);
const std::string end(dash+1,yloc.end());
const int bot = atoi(beg.c_str()) - 1;
const int top = atoi(end.c_str()) - 1;
if(loc.y < bot || loc.y > top)
return false;
} else {
const int yval = atoi(yloc.c_str()) - 1;
if(yval != loc.y)
return false;
}
}
return true;
}
bool filter_loc(const gamemap::location& loc, config& cfg)
{
const std::string& xloc = cfg.values["x"];
const std::string& yloc = cfg.values["y"];
return filter_loc_impl(loc,xloc,yloc);
}
bool process_event(event_handler& handler, const queued_event& ev)
{
if(handler.disabled())
return false;
std::map<gamemap::location,unit>::iterator unit1 = units->find(ev.loc1);
std::map<gamemap::location,unit>::iterator unit2 = units->find(ev.loc2);
std::vector<config*>& first_filters = handler.first_arg_filters();
for(std::vector<config*>::iterator ffi = first_filters.begin();
ffi != first_filters.end(); ++ffi) {
if(!filter_loc(ev.loc1,**ffi))
return false;
if(unit1 != units->end() && !unit1->second.matches_filter(**ffi)) {
return false;
}
}
std::vector<config*>& second_filters = handler.second_arg_filters();
for(std::vector<config*>::iterator sfi = second_filters.begin();
sfi != second_filters.end(); ++sfi) {
if(!filter_loc(ev.loc2,**sfi))
return false;
if(unit2 != units->end() && !unit2->second.matches_filter(**sfi)) {
return false;
}
}
//the event hasn't been filtered out, so execute the handler
handler.handle_event(ev);
if(handler.first_time_only())
handler.disable();
return true;
}
} //end anonymous namespace
namespace game_events {
manager::manager(config& cfg, display& gui_, gamemap& map_,
std::map<gamemap::location,unit>& units_,
game_state& state_of_game_, game_data& game_data_)
{
std::vector<config*>& events_list = cfg.children["event"];
for(std::vector<config*>::iterator i = events_list.begin();
i != events_list.end(); ++i) {
event_handler new_handler(*i);
events_map.insert(std::pair<std::string,event_handler>(
new_handler.name(), new_handler));
}
screen = &gui_;
game_map = &map_;
units = &units_;
state_of_game = &state_of_game_;
game_data_ptr = &game_data_;
}
manager::~manager() {
events_queue.clear();
events_map.clear();
screen = NULL;
game_map = NULL;
units = NULL;
state_of_game = NULL;
game_data_ptr = NULL;
}
void raise(const std::string& event,
const gamemap::location& loc1,
const gamemap::location& loc2)
{
if(!events_init())
return;
events_queue.push_back(queued_event(event,loc1,loc2));
}
bool fire(const std::string& event,
const gamemap::location& loc1,
const gamemap::location& loc2)
{
raise(event,loc1,loc2);
return pump();
}
bool pump()
{
if(!events_init())
return false;
bool result = false;
while(events_queue.empty() == false) {
queued_event ev = events_queue.front();
events_queue.pop_front(); //pop now for exception safety
const std::string& event_name = ev.name;
typedef std::multimap<std::string,event_handler>::iterator itor;
//find all handlers for this event in the map
std::pair<itor,itor> i = events_map.equal_range(event_name);
while(i.first != i.second) {
event_handler& handler = i.first->second;
if(process_event(handler, ev))
result = true;
++i.first;
}
}
return result;
}
} //end namespace game_events