
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.
937 lines
28 KiB
C++
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(¤t_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;
|
|
}
|