wesnoth/src/mouse_events.cpp
Mark de Wever 429b2b6d86 Added a new attack dialog.
This initial version is just a small proof of concept thing, its main
goal is to write a new chapter in the design documentation. The dialog
is available when started with --new-widgets.
2010-04-24 20:39:25 +00:00

937 lines
28 KiB
C++

/* $Id$ */
/*
Copyright (C) 2006 - 2010 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
wesnoth playturn Copyright (C) 2003 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.
*/
#include "global.hpp"
#include "mouse_events.hpp"
#include "attack_prediction_display.hpp"
#include "dialogs.hpp"
#include "foreach.hpp"
#include "game_end_exceptions.hpp"
#include "game_events.hpp"
#include "gettext.hpp"
#include "gui/dialogs/unit_attack.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "log.hpp"
#include "map.hpp"
#include "marked-up_text.hpp"
#include "menu_events.hpp"
#include "play_controller.hpp"
#include "sound.hpp"
#include "replay.hpp"
#include "resources.hpp"
#include "rng.hpp"
#include "tod_manager.hpp"
#include "wml_separators.hpp"
#include <boost/bind.hpp>
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
namespace events{
mouse_handler::mouse_handler(game_display* gui, std::vector<team>& teams,
unit_map& units, gamemap& map, tod_manager& tod_mng,
undo_list& undo_stack, undo_list& redo_stack) :
mouse_handler_base(),
map_(map),
gui_(gui),
teams_(teams),
units_(units),
tod_manager_(tod_mng),
undo_stack_(undo_stack),
redo_stack_(redo_stack),
previous_hex_(),
previous_free_hex_(),
selected_hex_(),
next_unit_(),
current_route_(),
waypoints_(),
current_paths_(),
enemy_paths_(false),
path_turns_(0),
side_num_(1),
undo_(false),
over_route_(false),
attackmove_(false),
reachmap_invalid_(false),
show_partial_move_(false)
{
singleton_ = this;
}
mouse_handler::~mouse_handler()
{
rand_rng::clear_new_seed_callback();
singleton_ = NULL;
}
void mouse_handler::set_side(int side_number)
{
side_num_ = side_number;
}
int mouse_handler::drag_threshold() const
{
return 14;
}
void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update)
{
if (attackmove_) return;
// we ignore the position coming from event handler
// because it's always a little obsolete and we don't need
// to hightlight all the hexes where the mouse passed.
// Also, sometimes it seems to have one *very* obsolete
// and isolated mouse motion event when using drag&drop
SDL_GetMouseState(&x,&y); // <-- modify x and y
if (mouse_handler_base::mouse_motion_default(x, y, update)) return;
const map_location new_hex = gui().hex_clicked_on(x,y);
if(new_hex != last_hex_) {
update = true;
if (last_hex_.valid()) {
// we store the previous hexes used to propose attack direction
previous_hex_ = last_hex_;
// the hex of the selected unit is also "free"
if (last_hex_ == selected_hex_ || find_unit(last_hex_) == units_.end()) {
previous_free_hex_ = last_hex_;
}
}
last_hex_ = new_hex;
}
if (reachmap_invalid_) update = true;
if (update) {
if (reachmap_invalid_) {
reachmap_invalid_ = false;
if (!current_paths_.destinations.empty() && !show_partial_move_) {
unit_map::iterator u = find_unit(selected_hex_);
if(selected_hex_.valid() && u != units_.end() ) {
// reselect the unit without firing events (updates current_paths_)
select_hex(selected_hex_, true);
}
// we do never deselect here, mainly because of canceled attack-move
}
}
// reset current_route_ and current_paths if not valid anymore
// we do it before cursor selection, because it uses current_paths_
if(new_hex.valid() == false) {
current_route_.steps.clear();
gui().set_route(NULL);
}
if(enemy_paths_) {
enemy_paths_ = false;
current_paths_ = pathfind::paths();
gui().unhighlight_reach();
} else if(over_route_) {
over_route_ = false;
current_route_.steps.clear();
gui().set_route(NULL);
}
gui().highlight_hex(new_hex);
const unit_map::iterator selected_unit = find_unit(selected_hex_);
const unit_map::iterator mouseover_unit = find_unit(new_hex);
// we search if there is an attack possibility and where
map_location attack_from = current_unit_attacks_from(new_hex);
//see if we should show the normal cursor, the movement cursor, or
//the attack cursor
//If the cursor is on WAIT, we don't change it and let the setter
//of this state end it
if (cursor::get() != cursor::WAIT) {
if (selected_unit != units_.end() &&
selected_unit->side() == side_num_ &&
!selected_unit->incapacitated() && !browse)
{
if (attack_from.valid()) {
cursor::set(dragging_started_ ? cursor::ATTACK_DRAG : cursor::ATTACK);
}
else if (mouseover_unit==units_.end() &&
current_paths_.destinations.contains(new_hex))
{
cursor::set(dragging_started_ ? cursor::MOVE_DRAG : cursor::MOVE);
} else {
// selecte unit can't attack or move there
cursor::set(cursor::NORMAL);
}
} else {
// no selected unit or we can't move it
cursor::set(cursor::NORMAL);
}
}
// show (or cancel) the attack direction indicator
if (attack_from.valid() && !browse) {
gui().set_attack_indicator(attack_from, new_hex);
} else {
gui().clear_attack_indicator();
}
// the destination is the pointed hex or the adjacent hex
// used to attack it
map_location dest;
unit_map::const_iterator dest_un;
if (attack_from.valid()) {
dest = attack_from;
dest_un = find_unit(dest);
} else {
dest = new_hex;
dest_un = mouseover_unit;
}
if(dest == selected_hex_ || dest_un != units_.end()) {
current_route_.steps.clear();
gui().set_route(NULL);
}
else if (!current_paths_.destinations.empty() &&
map_.on_board(selected_hex_) && map_.on_board(new_hex))
{
if (selected_unit != units_.end() && !selected_unit->incapacitated()) {
// Show the route from selected unit to mouseover hex
// the movement_reset is active only if it's not the unit's turn
unit_movement_resetter move_reset(*selected_unit,
selected_unit->side() != side_num_);
current_route_ = get_route(selected_unit, dest, waypoints_, viewing_team());
if(!browse) {
gui().set_route(&current_route_);
}
}
}
unit_map::iterator un = mouseover_unit;
if (un != units_.end() && current_paths_.destinations.empty() &&
!gui().fogged(un->get_location()))
{
if (un->side() != side_num_) {
//unit under cursor is not on our team, highlight reach
unit_movement_resetter move_reset(*un);
bool teleport = un->get_ability_bool("teleport");
current_paths_ = pathfind::paths(map_,units_,new_hex,teams_,
false,teleport,viewing_team(),path_turns_);
gui().highlight_reach(current_paths_);
enemy_paths_ = true;
} else {
//unit is on our team, show path if the unit has one
const map_location go_to = un->get_goto();
if(map_.on_board(go_to)) {
pathfind::marked_route route = get_route(un, go_to, un->waypoints(), current_team());
gui().set_route(&route);
}
over_route_ = true;
}
}
}
}
unit_map::iterator mouse_handler::selected_unit()
{
unit_map::iterator res = find_unit(selected_hex_);
if(res != units_.end()) {
return res;
} else {
return find_unit(last_hex_);
}
}
unit_map::iterator mouse_handler::find_unit(const map_location& hex)
{
return find_visible_unit(units_, hex, viewing_team());
}
unit_map::const_iterator mouse_handler::find_unit(const map_location& hex) const
{
return find_visible_unit(units_, hex, viewing_team());
}
map_location mouse_handler::current_unit_attacks_from(const map_location& loc)
{
const unit_map::const_iterator current = find_unit(selected_hex_);
if (current == units_.end() || current->side() != side_num_ ||
current->attacks_left()==0) {
return map_location();
}
const unit_map::const_iterator enemy = find_unit(loc);
if (enemy == units_.end() || !current_team().is_enemy(enemy->side()) ||
enemy->incapacitated())
{
return map_location();
}
const map_location::DIRECTION preferred = loc.get_relative_dir(previous_hex_);
const map_location::DIRECTION second_preferred = loc.get_relative_dir(previous_free_hex_);
int best_rating = 100;//smaller is better
map_location res;
map_location adj[6];
get_adjacent_tiles(loc,adj);
for(size_t n = 0; n != 6; ++n) {
if(map_.on_board(adj[n]) == false) {
continue;
}
if(adj[n] != selected_hex_ && find_unit(adj[n]) != units_.end()) {
continue;
}
if (current_paths_.destinations.contains(adj[n]))
{
static const size_t NDIRECTIONS = map_location::NDIRECTIONS;
unsigned int difference = abs(int(preferred - n));
if(difference > NDIRECTIONS/2) {
difference = NDIRECTIONS - difference;
}
unsigned int second_difference = abs(int(second_preferred - n));
if(second_difference > NDIRECTIONS/2) {
second_difference = NDIRECTIONS - second_difference;
}
const int rating = difference * 2 + (second_difference > difference);
if(rating < best_rating || res.valid() == false) {
best_rating = rating;
res = adj[n];
}
}
}
return res;
}
void mouse_handler::add_waypoint(const map_location& loc) {
std::vector<map_location>::iterator w = std::find(waypoints_.begin(), waypoints_.end(), loc);
//toggle between add a new one and remove an old one
if(w != waypoints_.end()){
waypoints_.erase(w);
} else {
waypoints_.push_back(loc);
}
// we need to update the route, simulate a mouse move for the moment
// (browse is supposed false here, 0,0 are dummy values)
mouse_motion(0,0, false, true);
}
pathfind::marked_route mouse_handler::get_route(unit_map::const_iterator un, map_location go_to, const std::vector<map_location>& waypoints, team &team)
{
// The pathfinder will check unit visibility (fogged/stealthy).
const pathfind::shortest_path_calculator calc(*un, team, units_, teams_, map_);
std::set<map_location> allowed_teleports = pathfind::get_teleport_locations(*un, units_, viewing_team());
pathfind::plain_route route;
if (waypoints.empty()) {
// standard shortest path
route = pathfind::a_star_search(un->get_location(), go_to, 10000.0, &calc, map_.w(), map_.h(), &allowed_teleports);
} else {
// initialize the main route with the first step
route.steps.push_back(un->get_location());
route.move_cost = 0;
//copy waypoints and add first source and last destination
//TODO: don't copy but use vector index trick
std::vector<map_location> waypts;
waypts.push_back(un->get_location());
waypts.insert(waypts.end(), waypoints.begin(), waypoints.end());
waypts.push_back(go_to);
std::vector<map_location>::iterator src = waypts.begin(),
dst = ++waypts.begin();
for(; dst != waypts.end(); ++src,++dst){
if (*src == *dst) continue;
pathfind::plain_route inter_route = pathfind::a_star_search(*src, *dst, 10000.0, &calc, map_.w(), map_.h(), &allowed_teleports);
if(inter_route.steps.size()>=1) {
// add to the main route but skip the head (already in)
route.steps.insert(route.steps.end(),
inter_route.steps.begin()+1,inter_route.steps.end());
route.move_cost+=inter_route.move_cost;
}
}
}
return mark_route(route, waypoints, *un, viewing_team(), units_,teams_,map_);
}
void mouse_handler::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
{
mouse_handler_base::mouse_press(event, browse);
}
bool mouse_handler::right_click_show_menu(int x, int y, const bool browse)
{
// The first right-click cancel the selection if any,
// the second open the context menu
if (selected_hex_.valid() && find_unit(selected_hex_) != units_.end()) {
select_hex(map_location(), browse);
return false;
} else {
return point_in_rect(x, y, gui().map_area());
}
}
bool mouse_handler::left_click(int x, int y, const bool browse)
{
undo_ = false;
if (mouse_handler_base::left_click(x, y, browse)) return false;
bool check_shroud = current_team().auto_shroud_updates();
//we use the last registered highlighted hex
//since it's what update our global state
map_location hex = last_hex_;
unit_map::iterator u = find_unit(selected_hex_);
//if the unit is selected and then itself clicked on,
//any goto command and waypoints are cancelled
if (u != units_.end() && !browse && selected_hex_ == hex && u->side() == side_num_) {
u->set_goto(map_location());
u->waypoints().clear();
waypoints_.clear();
}
unit_map::iterator clicked_u = find_unit(hex);
const map_location src = selected_hex_;
pathfind::paths orig_paths = current_paths_;
const map_location& attack_from = current_unit_attacks_from(hex);
//see if we're trying to do a attack or move-and-attack
if(!browse && !commands_disabled && attack_from.valid()) {
if (attack_from == selected_hex_) { //no move needed
int choice = show_attack_dialog(attack_from, clicked_u->get_location());
if (choice >=0 ) {
attack_enemy(u, clicked_u, choice);
}
return false;
}
else {
// we will now temporary move next to the enemy
pathfind::paths::dest_vect::const_iterator itor =
current_paths_.destinations.find(attack_from);
if(itor == current_paths_.destinations.end()) {
// can't reach the attacking location
// not supposed to happen, so abort
return false;
}
// update movement_left as if we did the move
int move_left_dst = itor->move_left;
int move_left_src = u->movement_left();
u->set_movement(move_left_dst);
int choice = -1;
// block where we temporary move the unit
{
temporary_unit_mover temp_mover(units_, src, attack_from);
choice = show_attack_dialog(attack_from, clicked_u->get_location());
}
// restore unit as before
u = units_.find(src);
u->set_movement(move_left_src);
u->set_standing();
if (choice < 0) {
// user hit cancel, don't start move+attack
return false;
}
//register the mouse-UI waypoints into the unit's waypoints
u->waypoints() = waypoints_;
// move the unit without clearing fog (to avoid interruption)
//TODO: clear fog and interrupt+resume move
if(!move_unit_along_current_route(false, true)) {
// interrupted move
// we assume that move_unit() did the cleaning
// (update shroud/fog, clear undo if needed)
return false;
}
// a WML event could have invalidated both attacker and defender
// so make sure they're valid before attacking
u = find_unit(attack_from);
unit_map::iterator enemy = find_unit(hex);
if (u != units_.end() && u->side() == side_num_ &&
enemy != units_.end() && current_team().is_enemy(enemy->side()) && !enemy->incapacitated()
&& !commands_disabled) {
attack_enemy(u, enemy, choice); // Fight !!
return false;
}
}
}
//otherwise we're trying to move to a hex
else if(!commands_disabled && !browse && selected_hex_.valid() && selected_hex_ != hex &&
u != units_.end() && u->side() == side_num_ &&
clicked_u == units_.end() && !current_route_.steps.empty() &&
current_route_.steps.front() == selected_hex_) {
gui().unhighlight_reach();
//register the mouse-UI waypoints into the unit's waypoints
u->waypoints() = waypoints_;
move_unit_along_current_route(check_shroud);
// during the move, we may have selected another unit
// (but without triggering a select event (command was disabled)
// in that case reselect it now to fire the event (+ anim & sound)
if (selected_hex_ != src) {
select_hex(selected_hex_, browse);
}
return false;
} else if (!attackmove_) {
// we select a (maybe empty) hex
// we block selection during attack+move (because motion is blocked)
select_hex(hex, browse);
}
return false;
//FIXME: clean all these "return false"
}
void mouse_handler::select_hex(const map_location& hex, const bool browse) {
selected_hex_ = hex;
gui().select_hex(hex);
gui().clear_attack_indicator();
gui().set_route(NULL);
waypoints_.clear();
show_partial_move_ = false;
unit_map::iterator u = find_unit(hex);
if (hex.valid() && u != units_.end() && !u->get_hidden()) {
next_unit_ = u->get_location();
{
// if it's not the unit's turn, we reset its moves
// and we restore them before the "select" event is raised
unit_movement_resetter move_reset(*u, u->side() != side_num_);
bool teleport = u->get_ability_bool("teleport");
current_paths_ = pathfind::paths(map_, units_, hex, teams_,
false, teleport, viewing_team(), path_turns_);
}
show_attack_options(u);
gui().highlight_reach(current_paths_);
// the highlight now comes from selection
// and not from the mouseover on an enemy
enemy_paths_ = false;
gui().set_route(NULL);
// selection have impact only if we are not observing and it's our unit
if (!browse && !commands_disabled && u->side() == gui().viewing_side()) {
sound::play_UI_sound("select-unit.wav");
u->set_selecting();
game_events::fire("select", hex);
}
} else {
gui().unhighlight_reach();
current_paths_ = pathfind::paths();
current_route_.steps.clear();
}
}
void mouse_handler::deselect_hex() {
select_hex(map_location(), true);
}
void mouse_handler::clear_undo_stack()
{
apply_shroud_changes(undo_stack_, side_num_);
undo_stack_.clear();
}
bool mouse_handler::move_unit_along_current_route(bool check_shroud, bool attackmove)
{
const std::vector<map_location> steps = current_route_.steps;
if(steps.empty()) {
return false;
}
// do not show footsteps during movement
gui().set_route(NULL);
// do not keep the hex highlighted that we started from
selected_hex_ = map_location();
gui().select_hex(map_location());
// will be invalid after the move
current_paths_ = pathfind::paths();
current_route_.steps.clear();
attackmove_ = attackmove;
size_t moves = 0;
try{
moves = ::move_unit(NULL, steps, &recorder, &undo_stack_, true, &next_unit_, false, check_shroud);
} catch(end_turn_exception&) {
attackmove_ = false;
cursor::set(cursor::NORMAL);
gui().invalidate_game_status();
throw;
}
attackmove_ = false;
cursor::set(cursor::NORMAL);
gui().invalidate_game_status();
if(moves == 0)
return false;
redo_stack_.clear();
assert(moves <= steps.size());
const map_location& dst = steps[moves-1];
const unit_map::const_iterator u = units_.find(dst);
//u may be equal to units_.end() in the case of e.g. a [teleport]
if(u != units_.end()) {
if(dst != steps.back()) {
// the move was interrupted (or never started)
if (u->movement_left() > 0) {
// reselect the unit (for "press t to continue")
select_hex(dst, false);
// the new discovery is more important than the new movement range
show_partial_move_ = true;
gui().unhighlight_reach();
}
}
}
return moves == steps.size();
}
int mouse_handler::fill_weapon_choices(std::vector<battle_context>& bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
{
int best = 0;
for (unsigned int i = 0; i < attacker->attacks().size(); i++) {
// skip weapons with attack_weight=0
if (attacker->attacks()[i].attack_weight() > 0) {
battle_context bc(units_, attacker->get_location(), defender->get_location(), i);
bc_vector.push_back(bc);
if (bc.better_attack(bc_vector[best], 0.5)) {
best = i;
}
}
}
return best;
}
int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
{
if(gui2::new_widgets) {
unit_map::iterator attacker = find_unit(attacker_loc);
unit_map::iterator defender = find_unit(defender_loc);
std::vector<battle_context> weapons;
const int best_weapon =
fill_weapon_choices(weapons, attacker, defender);
gui2::tunit_attack dlg(
attacker
, defender
, weapons
, best_weapon);
dlg.show(gui_->video());
if(dlg.get_retval() == gui2::twindow::OK) {
return dlg.get_selected_weapon();
} else {
return -1;
}
}
unit_map::iterator attacker = find_unit(attacker_loc);
unit_map::iterator defender = find_unit(defender_loc);
std::vector<battle_context> bc_vector;
int best = fill_weapon_choices(bc_vector, attacker, defender);
std::vector<std::string> items;
for (unsigned int i = 0; i < bc_vector.size(); i++) {
const battle_context::unit_stats& att = bc_vector[i].get_attacker_stats();
const battle_context::unit_stats& def = bc_vector[i].get_defender_stats();
config tmp_config;
attack_type no_weapon(tmp_config);
const attack_type& attw = attack_type(*att.weapon);
const attack_type& defw = attack_type(def.weapon ? *def.weapon : no_weapon);
attw.set_specials_context(attacker->get_location(), defender->get_location(), *attacker, true);
defw.set_specials_context(attacker->get_location(), defender->get_location(), *attacker, false);
// if missing, add dummy special, to be sure to have
// big enough mimimum width (weapon's name can be very short)
std::string att_weapon_special = attw.weapon_specials();
if (att_weapon_special.empty())
att_weapon_special += " ";
std::string def_weapon_special = defw.weapon_specials();
if (def_weapon_special.empty())
def_weapon_special += " ";
std::stringstream atts;
if (static_cast<int>(i) == best) {
atts << DEFAULT_ITEM;
}
std::string range = attw.range().empty() ? defw.range() : attw.range();
if (!range.empty()) {
range = gettext(range.c_str());
}
// add dummy names if missing, to keep stats aligned
std::string attw_name = attw.name();
if(attw_name.empty())
attw_name = " ";
std::string defw_name = defw.name();
if(defw_name.empty())
defw_name = " ";
// color CtH in red-yellow-green
SDL_Color att_cth_color =
int_to_color( game_config::red_to_green(att.chance_to_hit) );
SDL_Color def_cth_color =
int_to_color( game_config::red_to_green(def.chance_to_hit) );
atts << IMAGE_PREFIX << attw.icon() << COLUMN_SEPARATOR
<< font::BOLD_TEXT << attw_name << "\n"
<< att.damage << "-" << att.num_blows
<< " " << att_weapon_special << "\n"
<< font::color2markup(att_cth_color) << att.chance_to_hit << "%"
<< COLUMN_SEPARATOR << font::weapon_details << "- " << range << " -" << COLUMN_SEPARATOR
<< font::BOLD_TEXT << defw_name << "\n"
<< def.damage << "-" << def.num_blows
<< " " << def_weapon_special << "\n"
<< font::color2markup(def_cth_color) << def.chance_to_hit << "%"
<< COLUMN_SEPARATOR << IMAGE_PREFIX << defw.icon();
items.push_back(atts.str());
}
attack_prediction_displayer ap_displayer(bc_vector, attacker_loc, defender_loc);
std::vector<gui::dialog_button_info> buttons;
buttons.push_back(gui::dialog_button_info(&ap_displayer, _("Damage Calculations")));
int res = 0;
{
dialogs::units_list_preview_pane attacker_preview(*attacker, dialogs::unit_preview_pane::SHOW_BASIC, true);
dialogs::units_list_preview_pane defender_preview(*defender, dialogs::unit_preview_pane::SHOW_BASIC, false);
std::vector<gui::preview_pane*> preview_panes;
preview_panes.push_back(&attacker_preview);
preview_panes.push_back(&defender_preview);
res = gui::show_dialog(gui(),NULL,_("Attack Enemy"),
_("Choose weapon:")+std::string("\n"),
gui::OK_CANCEL,&items,&preview_panes,"",NULL,-1,NULL,-1,-1,
NULL,&buttons);
}
cursor::set(cursor::NORMAL);
return res;
}
void mouse_handler::attack_enemy(unit_map::iterator attacker, unit_map::iterator defender, int choice)
{
try {
attack_enemy_(attacker, defender, choice);
} catch(std::bad_alloc) {
lg::wml_error << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
}
}
void mouse_handler::attack_enemy_(unit_map::iterator attacker, unit_map::iterator defender, int choice)
{
std::vector<battle_context> bc_vector;
fill_weapon_choices(bc_vector, attacker, defender);
if(size_t(choice) >= bc_vector.size()) {
return;
}
//we must get locations by value instead of by references, because the iterators
//may become invalidated later
const map_location attacker_loc = attacker->get_location();
const map_location defender_loc = defender->get_location();
commands_disabled++;
const battle_context::unit_stats &att = bc_vector[choice].get_attacker_stats();
const battle_context::unit_stats &def = bc_vector[choice].get_defender_stats();
attacker->set_goto(map_location());
clear_undo_stack();
redo_stack_.clear();
current_paths_ = pathfind::paths();
// make the attacker's stats appear during the attack
gui().display_unit_hex(attacker_loc);
// remove highlighted hexes etc..
gui().select_hex(map_location());
gui().highlight_hex(map_location());
gui().clear_attack_indicator();
gui().unhighlight_reach();
gui().draw();
//@TODO: change ToD to be location specific for the defender
recorder.add_attack(attacker_loc, defender_loc, att.attack_num, def.attack_num,
attacker->type_id(), defender->type_id(), att.level,
def.level, resources::tod_manager->turn(), resources::tod_manager->get_time_of_day());
rand_rng::invalidate_seed();
if (rand_rng::has_valid_seed()) { //means SRNG is disabled
perform_attack(attacker_loc, defender_loc, att.attack_num, def.attack_num, rand_rng::get_last_seed());
} else {
rand_rng::set_new_seed_callback(boost::bind(&mouse_handler::perform_attack,
this, attacker_loc, defender_loc, att.attack_num, def.attack_num, _1));
}
}
void mouse_handler::perform_attack(
map_location attacker_loc, map_location defender_loc,
int attacker_weapon, int defender_weapon, rand_rng::seed_t seed)
{
// this function gets it's arguments by value because the calling function
// object might get deleted in the clear callback call below, invalidating
// const ref arguments
rand_rng::clear_new_seed_callback();
LOG_NG << "Performing attack with seed " << seed << "\n";
recorder.add_seed("attack", seed);
//MP_COUNTDOWN grant time bonus for attacking
current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
try {
events::command_disabler disabler; // Rather than decrementing for every possible exception, use RAII
commands_disabled--;
attack_unit(attacker_loc, defender_loc, attacker_weapon, defender_weapon);
} catch(end_level_exception&) {
//if the level ends due to a unit being killed, still see if
//either the attacker or defender should advance
dialogs::advance_unit(attacker_loc);
unit_map::const_iterator defu = units_.find(defender_loc);
if (defu != units_.end()) {
bool defender_human = teams_[defu->side() - 1].is_human();
dialogs::advance_unit(defender_loc, !defender_human);
}
throw;
}
dialogs::advance_unit(attacker_loc);
unit_map::const_iterator defu = units_.find(defender_loc);
if (defu != units_.end()) {
bool defender_human = teams_[defu->side() - 1].is_human();
dialogs::advance_unit(defender_loc, !defender_human);
}
resources::controller->check_victory();
gui().draw();
}
void mouse_handler::show_attack_options(const unit_map::const_iterator &u)
{
if (u == units_.end() || u->attacks_left() == 0)
return;
map_location adj[6];
get_adjacent_tiles(u->get_location(), adj);
foreach (const map_location &loc, adj)
{
if (!map_.on_board(loc)) continue;
unit_map::const_iterator i = units_.find(loc);
if (i == units_.end()) continue;
const unit &target = *i;
if (current_team().is_enemy(target.side()) && !target.incapacitated())
current_paths_.destinations.insert(loc);
}
}
bool mouse_handler::unit_in_cycle(unit_map::const_iterator it)
{
if (it == units_.end())
return false;
if (it->side() != side_num_ || it->user_end_turn()
|| gui().fogged(it->get_location()) || !unit_can_move(*it))
return false;
if (current_team().is_enemy(int(gui().viewing_team()+1)) &&
it->invisible(it->get_location(), units_, teams_))
return false;
if (it->get_hidden())
return false;
return true;
}
void mouse_handler::cycle_units(const bool browse, const bool reverse)
{
if (units_.begin() == units_.end()) {
return;
}
unit_map::const_iterator it = find_unit(next_unit_);
if (it == units_.end())
it = units_.begin();
const unit_map::const_iterator itx = it;
do {
if (reverse) {
if (it == units_.begin())
it = units_.end();
--it;
} else {
if (it == units_.end())
it = units_.begin();
else
++it;
}
} while (it != itx && !unit_in_cycle(it));
if (unit_in_cycle(it)) {
gui().scroll_to_tile(it->get_location(), game_display::WARP);
select_hex(it->get_location(), browse);
mouse_update(browse);
}
}
void mouse_handler::set_current_paths(pathfind::paths new_paths) {
gui().unhighlight_reach();
current_paths_ = new_paths;
current_route_.steps.clear();
gui().set_route(NULL);
}
mouse_handler *mouse_handler::singleton_ = NULL;
}