void playmp_controller::play_human_turn(){ LOG_NG << "playmp::play_human_turn...\n"; remove_blindfold(); command_disabled_resetter reset_commands; int cur_ticks = SDL_GetTicks(); show_turn_dialog(); execute_gotos(); if (!linger_ || is_host_) { end_turn_enable(true); } while(!end_turn_) { try { config cfg; const network::connection res = network::receive_data(cfg); std::deque<config> backlog; if(res != network::null_connection) { if (turn_data_->process_network_data(cfg, res, backlog, skip_replay_) == turn_info::PROCESS_RESTART_TURN) { // Clean undo stack if turn has to be restarted (losing control) if ( undo_stack_->can_undo() ) { font::floating_label flabel(_("Undoing moves not yet transmitted to the server.")); SDL_Color color = {255,255,255,255}; flabel.set_color(color); SDL_Rect rect = gui_->map_area(); flabel.set_position(rect.w/2, rect.h/2); flabel.set_lifetime(150); flabel.set_clip_rect(rect); font::add_floating_label(flabel); } while( undo_stack_->can_undo() ) undo_stack_->undo(); throw end_turn_exception(gui_->playing_side()); } } play_slice(); check_end_level(); // give a chance to the whiteboard to continue an execute_all_actions resources::whiteboard->continue_execute_all(); } catch(const end_level_exception&) { turn_data_->send_data(); throw; } if (!linger_ && (current_team().countdown_time() > 0) && gamestate_.mp_settings().mp_countdown) { SDL_Delay(1); const int ticks = SDL_GetTicks(); int new_time = current_team().countdown_time()-std::max<int>(1,(ticks - cur_ticks)); if (new_time > 0 ){ current_team().set_countdown_time(new_time); cur_ticks = ticks; if(current_team().is_human() && !beep_warning_time_) { beep_warning_time_ = new_time - WARNTIME + ticks; } if(counting_down()) { think_about_countdown(ticks); } } else { // Clock time ended // If no turn bonus or action bonus -> defeat const int action_increment = gamestate_.mp_settings().mp_countdown_action_bonus; if ( (gamestate_.mp_settings().mp_countdown_turn_bonus == 0 ) && (action_increment == 0 || current_team().action_bonus_count() == 0)) { // Not possible to end level in MP with throw end_level_exception(DEFEAT); // because remote players only notice network disconnection // Current solution end remaining turns automatically current_team().set_countdown_time(10); } turn_data_->send_data(); if (!rand_rng::has_new_seed_callback()) { throw end_turn_exception(); } } } gui_->draw(); turn_data_->send_data(); } }
std::vector<target> default_ai_context_impl::find_targets(const move_map& enemy_dstsrc) { log_scope2(log_ai, "finding targets..."); unit_map &units_ = *resources::units; unit_map::iterator leader = units_.find_leader(get_side()); gamemap &map_ = *resources::game_map; std::vector<team> teams_ = *resources::teams; const bool has_leader = leader != units_.end(); std::vector<target> targets; //=== start getting targets //if enemy units are in range of the leader, then we target the enemies who are in range. if(has_leader) { double threat = power_projection(leader->get_location(), enemy_dstsrc); if(threat > 0.0) { //find the location of enemy threats std::set<map_location> threats; map_location adj[6]; get_adjacent_tiles(leader->get_location(), adj); for(size_t n = 0; n != 6; ++n) { std::pair<move_map::const_iterator,move_map::const_iterator> itors = enemy_dstsrc.equal_range(adj[n]); while(itors.first != itors.second) { if(units_.count(itors.first->second)) { threats.insert(itors.first->second); } ++itors.first; } } assert(threats.empty() == false); #ifdef SUOKKO //FIXME: suokko's revision 29531 included this change. Correct? const double value = threat*get_protect_leader()/leader->second.hitpoints(); #else const double value = threat/double(threats.size()); #endif for(std::set<map_location>::const_iterator i = threats.begin(); i != threats.end(); ++i) { LOG_AI << "found threat target... " << *i << " with value: " << value << "\n"; targets.push_back(target(*i,value,target::THREAT)); } } } double corner_distance = distance_between(map_location(0,0), map_location(map_.w(),map_.h())); double village_value = get_village_value(); if(has_leader && village_value > 0.0) { std::map<map_location,pathfind::paths> friends_possible_moves; move_map friends_srcdst, friends_dstsrc; calculate_possible_moves(friends_possible_moves, friends_srcdst, friends_dstsrc, false, true); const std::vector<map_location>& villages = map_.villages(); for(std::vector<map_location>::const_iterator t = villages.begin(); t != villages.end(); ++t) { assert(map_.on_board(*t)); bool ally_village = false; for (size_t i = 0; i != teams_.size(); ++i) { if (!current_team().is_enemy(i + 1) && teams_[i].owns_village(*t)) { ally_village = true; break; } } if (ally_village) { //Support seems to cause the AI to just 'sit around' a lot, so //only turn it on if it's explicitly enabled. if(get_support_villages()) { double enemy = power_projection(*t, enemy_dstsrc); if (enemy > 0) { enemy *= 1.7; double our = power_projection(*t, friends_dstsrc); double value = village_value * our / enemy; add_target(target(*t, value, target::SUPPORT)); } } } else { double leader_distance = distance_between(*t, leader->get_location()); double value = village_value * (1.0 - leader_distance / corner_distance); LOG_AI << "found village target... " << *t << " with value: " << value << " distance: " << leader_distance << '\n'; targets.push_back(target(*t,value,target::VILLAGE)); } } } std::vector<goal_ptr>& goals = get_goals(); //find the enemy leaders and explicit targets unit_map::const_iterator u; if (get_leader_value()>0.0) { for(u = units_.begin(); u != units_.end(); ++u) { //is a visible enemy leader if (u->can_recruit() && current_team().is_enemy(u->side()) && !u->invisible(u->get_location())) { assert(map_.on_board(u->get_location())); LOG_AI << "found enemy leader (side: " << u->side() << ") target... " << u->get_location() << " with value: " << get_leader_value() << "\n"; targets.push_back(target(u->get_location(), get_leader_value(), target::LEADER)); } } } //explicit targets for this team for(std::vector<goal_ptr>::iterator j = goals.begin(); j != goals.end(); ++j) { if (!(*j)->active()) { continue; } (*j)->add_targets(std::back_inserter(targets)); } //=== end getting targets std::vector<double> new_values; for(std::vector<target>::iterator i = targets.begin(); i != targets.end(); ++i) { new_values.push_back(i->value); for(std::vector<target>::const_iterator j = targets.begin(); j != targets.end(); ++j) { if(i->loc == j->loc) { continue; } const double distance = abs(j->loc.x - i->loc.x) + abs(j->loc.y - i->loc.y); new_values.back() += j->value/(distance*distance); } } assert(new_values.size() == targets.size()); for(size_t n = 0; n != new_values.size(); ++n) { LOG_AI << "target value: " << targets[n].value << " -> " << new_values[n] << "\n"; targets[n].value = new_values[n]; } return targets; }
void playmp_controller::play_human_turn(){ LOG_NG << "playmp::play_human_turn...\n"; command_disabled_resetter reset_commands; int cur_ticks = SDL_GetTicks(); show_turn_dialog(); execute_gotos(); if ((!linger_) || (is_host_)) gui_->enable_menu("endturn", true); while(!end_turn_) { try { config cfg; const network::connection res = network::receive_data(cfg); std::deque<config> backlog; if(res != network::null_connection) { if (turn_data_->process_network_data(cfg, res, backlog, skip_replay_) == turn_info::PROCESS_RESTART_TURN) { // Clean undo stack if turn has to be restarted (losing control) if (!undo_stack_.empty()) { const std::string msg =_("Undoing moves not yet transmitted to the server."); const int size = 20; const int lifetime = 150; SDL_Color colour = {255,255,255,255}; SDL_Rect rect = gui_->map_area(); font::add_floating_label(msg,size, colour, rect.w/2,rect.h/2,0.0,0.0,lifetime,rect,font::CENTER_ALIGN); } while(!undo_stack_.empty()) menu_handler_.undo(gui_->get_playing_team() + 1); throw end_turn_exception(gui_->get_playing_team() + 1); } } play_slice(); check_end_level(); } catch(end_level_exception& e) { turn_data_->send_data(); throw e; } if (!linger_ && (current_team().countdown_time() > 0) && gamestate_.mp_settings().mp_countdown) { SDL_Delay(1); const int ticks = SDL_GetTicks(); int new_time = current_team().countdown_time()-std::max<int>(1,(ticks - cur_ticks)); if (new_time > 0 ){ current_team().set_countdown_time(new_time); cur_ticks = ticks; if(current_team().is_human() && !beep_warning_time_) { beep_warning_time_ = new_time - WARNTIME + ticks; } if(counting_down()) { think_about_countdown(ticks); } } else { // Clock time ended // If no turn bonus or action bonus -> defeat const int action_increment = gamestate_.mp_settings().mp_countdown_action_bonus; if ( (gamestate_.mp_settings().mp_countdown_turn_bonus == 0 ) && (action_increment == 0 || current_team().action_bonus_count() == 0)) { // Not possible to end level in MP with throw end_level_exception(DEFEAT); // because remote players only notice network disconnection // Current solution end remaining turns automatically current_team().set_countdown_time(10); } else { const int maxtime = gamestate_.mp_settings().mp_countdown_reservoir_time; int secs = gamestate_.mp_settings().mp_countdown_turn_bonus; secs += action_increment * current_team().action_bonus_count(); current_team().set_action_bonus_count(0); secs = (secs > maxtime) ? maxtime : secs; current_team().set_countdown_time(1000 * secs); } turn_data_->send_data(); throw end_turn_exception(); } } gui_->draw(); turn_data_->send_data(); } menu_handler_.clear_undo_stack(player_number_); }
possible_end_play_signal playsingle_controller::play_turn() { whiteboard_manager_->on_gamestate_change(); gui_->new_turn(); gui_->invalidate_game_status(); events::raise_draw_event(); LOG_NG << "turn: " << turn() << "\n"; if(non_interactive()) { LOG_AIT << "Turn " << turn() << ":" << std::endl; } for (player_number_ = first_player_; player_number_ <= int(gamestate_.board_.teams().size()); ++player_number_) { // If a side is empty skip over it. if (current_team().is_empty()) continue; possible_end_play_signal signal; { save_blocker blocker; signal = init_side(); } if (signal) { switch (boost::apply_visitor(get_signal_type(), *signal)) { case END_TURN: if (current_team().is_network() == false) { turn_data_.send_data(); recorder.end_turn(); turn_data_.sync_network(); } continue; case END_LEVEL: return signal; } } if (replaying_) { LOG_NG << "doing replay " << player_number_ << "\n"; HANDLE_END_PLAY_SIGNAL ( replaying_ = ::do_replay() == REPLAY_FOUND_END_TURN ); LOG_NG << "result of replay: " << (replaying_?"true":"false") << "\n"; } else { ai_testing::log_turn_start(player_number_); PROPOGATE_END_PLAY_SIGNAL ( play_side() ); } finish_side_turn(); if(non_interactive()) { LOG_AIT << " Player " << player_number_ << ": " << current_team().villages().size() << " Villages" << std::endl; ai_testing::log_turn_end(player_number_); } HANDLE_END_PLAY_SIGNAL ( check_victory() ); //if loading a savegame, network turns might not have reset this yet loading_game_ = false; } //If the loop exits due to the last team having been processed, //player_number_ will be 1 too high if(player_number_ > static_cast<int>(gamestate_.board_.teams().size())) player_number_ = gamestate_.board_.teams().size(); finish_turn(); // Time has run out PROPOGATE_END_PLAY_SIGNAL ( check_time_over() ); return boost::none; }
possible_end_play_signal playsingle_controller::play_side() { //check for team-specific items in the scenario gui_->parse_team_overlays(); HANDLE_END_PLAY_SIGNAL( maybe_do_init_side(false) ); //flag used when we fallback from ai and give temporarily control to human bool temporary_human = false; do { // This flag can be set by derived classes (in overridden functions). player_type_changed_ = false; if (!skip_next_turn_) end_turn_ = false; statistics::reset_turn_stats(gamestate_.board_.teams()[player_number_ - 1].save_id()); if(current_team().is_human() || temporary_human) { LOG_NG << "is human...\n"; temporary_human = false; // If a side is dead end the turn, but play at least side=1's // turn in case all sides are dead if (gamestate_.board_.side_units(player_number_) != 0 || (resources::units->size() == 0 && player_number_ == 1)) { possible_end_play_signal signal = before_human_turn(); if (!signal) { signal = play_human_turn(); } if (signal) { switch (boost::apply_visitor(get_signal_type(), *signal)) { case END_LEVEL: return signal; case END_TURN: if (int(boost::apply_visitor(get_redo(),*signal)) == player_number_) { player_type_changed_ = true; // If new controller is not human, // reset gui to prev human one if (!gamestate_.board_.teams()[player_number_-1].is_human()) { browse_ = true; int s = find_human_team_before_current_player(); if (s <= 0) s = gui_->playing_side(); update_gui_to_player(s-1); } } } } } // Ending the turn commits all moves. undo_stack_->clear(); if ( !player_type_changed_ ) after_human_turn(); LOG_NG << "human finished turn...\n"; } else if(current_team().is_ai()) { try { play_ai_turn(); } catch(fallback_ai_to_human_exception&) { // Give control to a human for this turn. player_type_changed_ = true; temporary_human = true; } catch (end_level_exception & e) { //Don't know at the moment if these two are possible but can't hurt to add return possible_end_play_signal(e.to_struct()); } catch (end_turn_exception & e) { return possible_end_play_signal(e.to_struct()); } if(!player_type_changed_) { recorder.end_turn(); } } else if(current_team().is_network()) { PROPOGATE_END_PLAY_SIGNAL( play_network_turn() ); } else if(current_team().is_idle()) { end_turn_enable(false); do_idle_notification(); possible_end_play_signal signal = before_human_turn(); if (!signal) { signal = play_idle_loop(); } if (signal) { switch (boost::apply_visitor(get_signal_type(), *signal)) { case END_LEVEL: return signal; case END_TURN: LOG_NG << "Escaped from idle state with exception!" << std::endl; if (int(boost::apply_visitor(get_redo(), *signal)) == player_number_) { player_type_changed_ = true; // If new controller is not human, // reset gui to prev human one if (!gamestate_.board_.teams()[player_number_-1].is_human()) { browse_ = true; int s = find_human_team_before_current_player(); if (s <= 0) s = gui_->playing_side(); update_gui_to_player(s-1); } } } } } // Else current_team().is_empty(), so do nothing. } while (player_type_changed_); // Keep looping if the type of a team (human/ai/networked) // has changed mid-turn skip_next_turn_ = false; return boost::none; }
void playmp_controller::play_side() { utils::string_map player; player["name"] = current_team().current_player(); std::string turn_notification_msg = _("$name has taken control"); turn_notification_msg = utils::interpolate_variables_into_string(turn_notification_msg, &player); gui_->send_notification(_("Turn changed"), turn_notification_msg); int end_ticks = calculate_end_ticks(); player_type_changed_ = false; while (unit_map::main_ticks < end_ticks && !player_type_changed_) { VALIDATE(!unit::actor, "playmp_controller::play_side, unit::actor isn't NULL!"); if (!skip_next_turn_) { end_turn_ = false; } unit* u = &units_.current_unit(); if (!tent::turn_based) { int past_ticks = u->backward_ticks(u->ticks()); if (unit_map::main_ticks + past_ticks >= end_ticks) { units_.do_escape_ticks_uh(teams_, *gui_, end_ticks - unit_map::main_ticks, false); autosave_ticks_ = -1; continue; } units_.do_escape_ticks_uh(teams_, *gui_, past_ticks, true); } else { if (u->side() < player_number_) { unit_map::main_ticks = end_ticks; player_number_ = 1; continue; } } // if exist spirit feature/technology, second maybe goto first. need reget u = &units_.current_unit(); player_number_ = u->side(); // init_turn_data need player_number_ be valid. team& t = teams_[player_number_ - 1]; bool local = t.is_local(); bool new_side = false; if (local || loading_game_) { if (local) { init_turn_data(); } new_side = do_prefix_unit(end_ticks, loading_game_, false); } if (t.is_network()) { play_network_turn(); } else if (actor_can_continue_action(units_, player_number_)) { // we can't call playsingle_controller::play_side because // we need to catch exception here if (t.is_human()) { try { if (!loading_game_ && unit::actor->human_team_can_ai()) { play_ai_turn(turn_data_); } before_human_turn(new_side); play_human_turn(); after_human_turn(); } catch (end_turn_exception& end_turn) { if (end_turn.redo == player_number_ - 1) { player_type_changed_ = true; // if new controller is not human, // reset gui to prev human one if (!teams_[player_number_ - 2].is_human()) { browse_ = true; int t = find_human_team_before(player_number_ - 1); if (t <= 0) t = gui_->get_playing_team() + 1; gui_->set_team(t-1); gui_->recalculate_minimap(); gui_->invalidate_all(); gui_->draw(true,true); } } else { after_human_turn(); } } } else if (t.is_ai()) { play_ai_turn(turn_data_); } } if (local) { do_post_unit(false); release_turn_data(); } loading_game_ = false; } //keep looping if the type of a team (human/ai/networked) has changed mid-turn skip_next_turn_ = false; }
void play_controller::do_init_side() { set_scontext_synced sync; log_scope("player turn"); // In case we might end up calling sync:network during the side turn events, // and we don't want do_init_side to be called when a player drops. gamestate_->init_side_done() = true; init_side_done_now_ = true; const std::string turn_num = std::to_string(turn()); const std::string side_num = std::to_string(current_side()); gamestate().gamedata_.get_variable("side_number") = current_side(); // We might have skipped some sides because they were empty so it is not enough to check for side_num==1 if(!gamestate().tod_manager_.has_turn_event_fired()) { pump().fire("turn_" + turn_num); pump().fire("new_turn"); gamestate().tod_manager_.turn_event_fired(); } pump().fire("side_turn"); pump().fire("side_" + side_num + "_turn"); pump().fire("side_turn_" + turn_num); pump().fire("side_" + side_num + "_turn_" + turn_num); // We want to work out if units for this player should get healed, // and the player should get income now. // Healing/income happen if it's not the first turn of processing, // or if we are loading a game. if (turn() > 1) { gamestate().board_.new_turn(current_side()); current_team().new_turn(); // If the expense is less than the number of villages owned // times the village support capacity, // then we don't have to pay anything at all int expense = gamestate().board_.side_upkeep(current_side()) - current_team().support(); if(expense > 0) { current_team().spend_gold(expense); } calculate_healing(current_side(), !is_skipping_replay()); } // Prepare the undo stack. undo_stack().new_side_turn(current_side()); pump().fire("turn_refresh"); pump().fire("side_" + side_num + "_turn_refresh"); pump().fire("turn_" + turn_num + "_refresh"); pump().fire("side_" + side_num + "_turn_" + turn_num + "_refresh"); // Make sure vision is accurate. actions::clear_shroud(current_side(), true); init_side_end(); check_victory(); sync.do_final_checkup(); }
possible_end_play_signal playmp_controller::play_human_turn(){ LOG_NG << "playmp::play_human_turn...\n"; remove_blindfold(); int cur_ticks = SDL_GetTicks(); show_turn_dialog(); if (!preferences::disable_auto_moves()) { HANDLE_END_PLAY_SIGNAL(execute_gotos()); } if (!linger_ || is_host()) { end_turn_enable(true); } while(!end_turn_) { turn_info_send send_safe(turn_data_); config cfg; if(network_reader_.read(cfg)) { turn_info::PROCESS_DATA_RESULT res; HANDLE_END_PLAY_SIGNAL( res = turn_data_.process_network_data(cfg, skip_replay_) ); //PROCESS_RESTART_TURN_TEMPORARY_LOCAL should be impossible because that's means the currently active side (that's us) left. if (res == turn_info::PROCESS_RESTART_TURN || res == turn_info::PROCESS_RESTART_TURN_TEMPORARY_LOCAL) { // Clean undo stack if turn has to be restarted (losing control) if ( undo_stack_->can_undo() ) { font::floating_label flabel(_("Undoing moves not yet transmitted to the server.")); SDL_Color color = {255,255,255,255}; flabel.set_color(color); SDL_Rect rect = gui_->map_area(); flabel.set_position(rect.w/2, rect.h/2); flabel.set_lifetime(150); flabel.set_clip_rect(rect); font::add_floating_label(flabel); } while( undo_stack_->can_undo() ) undo_stack_->undo(); end_turn_struct ets = {static_cast<unsigned>(gui_->playing_side())}; return possible_end_play_signal(ets); //throw end_turn_exception(gui_->playing_side()); } else if(res == turn_info::PROCESS_END_LINGER) { if(!linger_) replay::process_error("Received unexpected next_scenario durign the game"); else { //we end the turn immidiately to prevent receiving data of the next scenario while we are not playing it. end_turn(); } } } HANDLE_END_PLAY_SIGNAL( play_slice() ); HANDLE_END_PLAY_SIGNAL( check_end_level() ); if (!linger_ && (current_team().countdown_time() > 0) && saved_game_.mp_settings().mp_countdown) { SDL_Delay(1); const int ticks = SDL_GetTicks(); int new_time = current_team().countdown_time()-std::max<int>(1,(ticks - cur_ticks)); if (new_time > 0 ){ current_team().set_countdown_time(new_time); cur_ticks = ticks; if(current_team().is_human() && !beep_warning_time_) { beep_warning_time_ = new_time - WARNTIME + ticks; } if(counting_down()) { think_about_countdown(ticks); } } else { // Clock time ended // If no turn bonus or action bonus -> defeat const int action_increment = saved_game_.mp_settings().mp_countdown_action_bonus; if ( (saved_game_.mp_settings().mp_countdown_turn_bonus == 0 ) && (action_increment == 0 || current_team().action_bonus_count() == 0)) { // Not possible to end level in MP with throw end_level_exception(DEFEAT); // because remote players only notice network disconnection // Current solution end remaining turns automatically current_team().set_countdown_time(10); } return possible_end_play_signal(end_turn_exception().to_struct()); //throw end_turn_exception(); } } gui_->draw(); } return boost::none; }
void playsingle_controller::play_turn(bool save) { resources::whiteboard->on_gamestate_change(); gui_->new_turn(); gui_->invalidate_game_status(); events::raise_draw_event(); LOG_NG << "turn: " << turn() << "\n"; if(non_interactive()) { LOG_AIT << "Turn " << turn() << ":" << std::endl; } for (player_number_ = first_player_; player_number_ <= int(teams_.size()); ++player_number_) { // If a side is empty skip over it. if (current_team().is_empty()) continue; try { save_blocker blocker; init_side(player_number_ - 1); } catch (end_turn_exception) { if (current_team().is_network() == false) { turn_info turn_data(player_number_, replay_sender_,network_reader_); recorder.end_turn(); turn_data.sync_network(); } continue; } if (replaying_) { LOG_NG << "doing replay " << player_number_ << "\n"; replaying_ = ::do_replay(player_number_) == REPLAY_FOUND_END_TURN; LOG_NG << "result of replay: " << (replaying_?"true":"false") << "\n"; } else { // If a side is dead end the turn, but play at least side=1's // turn in case all sides are dead if (current_team().is_human() && side_units(player_number_) == 0 && (resources::units->size() != 0 || player_number_ != 1)) { turn_info turn_data(player_number_, replay_sender_, network_reader_); recorder.end_turn(); turn_data.sync_network(); continue; } ai_testing::log_turn_start(player_number_); play_side(player_number_, save); } finish_side_turn(); if(non_interactive()) { LOG_AIT << " Player " << player_number_ << ": " << current_team().villages().size() << " Villages" << std::endl; ai_testing::log_turn_end(player_number_); } check_victory(); //if loading a savegame, network turns might not have reset this yet loading_game_ = false; } //If the loop exits due to the last team having been processed, //player_number_ will be 1 too high if(player_number_ > static_cast<int>(teams_.size())) player_number_ = teams_.size(); finish_turn(); // Time has run out check_time_over(); }
void playmp_controller::play_human_turn() { LOG_NG << "playmp::play_human_turn...\n"; assert(!linger_); remove_blindfold(); boost::scoped_ptr<countdown_clock> timer; if(saved_game_.mp_settings().mp_countdown) { timer.reset(new countdown_clock(current_team())); } show_turn_dialog(); if(undo_stack_->can_undo()) { // If we reload a networked mp game we cannot undo moves made before the save // Becasue other players already received them if(!current_team().auto_shroud_updates()) { synced_context::run_and_store("update_shroud", replay_helper::get_update_shroud()); } undo_stack_->clear(); } if (!preferences::disable_auto_moves()) { execute_gotos(); } end_turn_enable(true); while(!should_return_to_play_side()) { try { process_network_data(); if (player_type_changed_) { // Clean undo stack if turn has to be restarted (losing control) if ( undo_stack_->can_undo() ) { font::floating_label flabel(_("Undoing moves not yet transmitted to the server.")); SDL_Color color = {255,255,255,255}; flabel.set_color(color); SDL_Rect rect = gui_->map_area(); flabel.set_position(rect.w/2, rect.h/2); flabel.set_lifetime(150); flabel.set_clip_rect(rect); font::add_floating_label(flabel); } while( undo_stack_->can_undo() ) undo_stack_->undo(); } check_objectives(); play_slice_catch(); if(timer) { bool time_left = timer->update(); if(!time_left) { end_turn_ = END_TURN_REQUIRED; } } } catch(...) { turn_data_.send_data(); throw; } turn_data_.send_data(); gui_->draw(); } }
possible_end_play_signal playmp_controller::play_side() { mp_ui_alerts::turn_changed(current_team().current_player()); // Proceed with the parent function. return playsingle_controller::play_side(); }
void playsingle_controller::before_human_turn(bool save) { team& t = current_team(); if (pause_when_human_) { browse_ = false; } linger_ = false; if (gui_->access_is_null(game_display::taccess_list::TROOP)) { // change resolution will result to enter it. gui_->refresh_access_troops(player_number_ - 1, game_display::REFRESH_RELOAD); } if (!tent::tower_mode() && !preferences::developer()) { gui_->refresh_access_troops(player_number_ - 1, game_display::REFRESH_HIDE, NULL); } gui_->refresh_access_heros(player_number_ - 1, game_display::REFRESH_RELOAD); ai::manager::raise_turn_started(); if (tent::mode != mode_tag::LAYOUT && save) { autosave_ticks_ = unit_map::main_ticks; config snapshot; to_config(snapshot); savegame::autosave_savegame save(heros_, heros_start_, gamestate_, *gui_, snapshot); save.autosave(game_config::disable_autosave, preferences::autosavemax(), preferences::INFINITE_AUTO_SAVES); if (tent::turn_based) { const std::vector<hero*>& last_active_tactic = t.last_active_tactic(); for (std::vector<hero*>::const_iterator it = last_active_tactic.begin(); it != last_active_tactic.end(); ++ it) { hero& selected_hero = **it; unit* tactician = units_.find_unit(selected_hero); if (tactician && !tactician->is_resided()) { do_add_active_tactic(*tactician, selected_hero, true); } } } // autosave ticks changed, refresh top panel. gui_->invalidate_game_status(); } else { teams_[player_number_ - 1].refresh_tactic_slots(*gui_); } if (unit::actor && unit::actor->is_city()) { if (preferences::turn_bell()) { sound::play_bell(game_config::sounds::turn_bell); } if (game_config::show_side_report) { gui2::tside_report dlg(*gui_, teams_, units_, heros_, unit::actor->side()); dlg.show(gui_->video()); } } // button: undo undo_stack_.clear(); if (teams_[player_number_-1].uses_shroud() || teams_[player_number_-1].uses_fog()) { gui_->set_theme_object_visible("undo", gui2::twidget::INVISIBLE); } else { gui_->set_theme_object_visible("undo", gui2::twidget::VISIBLE); gui_->set_theme_object_active("undo", false); } // button: endturn if (tent::mode == mode_tag::SIEGE) { if (pause_when_human_) { refresh_endturn_button(*gui_, "buttons/ctrl-play.png"); gui_->set_theme_object_active("endturn", true); } } else if (tent::mode != mode_tag::LAYOUT) { gui_->set_theme_object_active("card", true); gui_->set_theme_object_active("endturn", true); } else { gui_->set_theme_object_visible("card", gui2::twidget::INVISIBLE); gui_->set_theme_object_visible("endturn", gui2::twidget::INVISIBLE); } // card refresh_card_button(t, *gui_); // if (!network::nconnections()) { // gui_->show_context_menu(); // } else { // Multiplayer mod, there is context-button when network player playing. // In order to display correct, need hide "before" button. gui_->goto_main_context_menu(); // } }
bool ai_default_recruitment_stage::recruit_usage(const std::string& usage) { raise_user_interact(); analyze_all(); const int min_gold = 0; log_scope2(log_ai, "recruiting troops"); LOG_AI << "recruiting '" << usage << "'\n"; //make sure id, usage and cost are known for the coming evaluation of unit types unit_types.build_all(unit_type::HELP_INDEX); std::vector<std::string> options; bool found = false; // Find an available unit that can be recruited, // matches the desired usage type, and comes in under budget. BOOST_FOREACH(const std::string &name, current_team().recruits()) { const unit_type *ut = unit_types.find(name); if (!ut) continue; // If usage is empty consider any unit. if (usage.empty() || ut->usage() == usage) { LOG_AI << name << " considered for " << usage << " recruitment\n"; found = true; if (current_team().gold() - ut->cost() < min_gold) { LOG_AI << name << " rejected, cost too high (cost: " << ut->cost() << ", current gold: " << current_team().gold() <<", min_gold: " << min_gold << ")\n"; continue; } if (not_recommended_units_.count(name)) { LOG_AI << name << " rejected, bad terrain or combat\n"; continue; } std::map<std::string,int>::iterator imc = maximum_counts_.find(name); if (imc != maximum_counts_.end()) { int count_active = 0; for (unit_map::const_iterator u = resources::units->begin(); u != resources::units->end(); ++u) { if (u->side() == get_side() && !u->incapacitated() && u->type().base_id() == name) { ++count_active; } } if (count_active >= imc->second) { LOG_AI << name << " rejected, too many in the field\n"; continue; } } LOG_AI << "recommending '" << name << "'\n"; options.push_back(name); } } // From the available options, choose one at random if(options.empty() == false) { const int option = rand()%options.size(); recruit_result_ptr recruit_res = check_recruit_action(options[option]); if (recruit_res->is_ok()) { recruit_res->execute(); if (!recruit_res->is_ok()) { ERR_AI << "recruitment failed "<< std::endl; } } return recruit_res->is_gamestate_changed(); } if (found) { LOG_AI << "No available units to recruit that come under the price.\n"; } else if (usage != "") { //FIXME: This message should be suppressed when WML author //chooses the default recruitment pattern. const std::string warning = "At difficulty level " + resources::gamedata->difficulty() + ", trying to recruit a:" + usage + " but no unit of that type (usage=) is" " available. Check the recruit and [ai]" " recruitment_pattern keys for team '" + current_team().name() + "' (" + lexical_cast<std::string>(get_side()) + ")" " against the usage key of the" " units in question! Removing invalid" " recruitment_pattern entry and continuing...\n"; WRN_AI << warning; // Uncommented until the recruitment limiting macro can be fixed to not trigger this warning. //lg::wml_error << warning; //@fixme //return current_team_w().remove_recruitment_pattern_entry(usage); return false; } return false; }
/** * There is no real hope for us: we should try to do some damage to the enemy. * We can spend some cycles here, since it's rare. */ bool ai_default::desperate_attack(const map_location &loc) { const unit &u = units_.find(loc)->second; LOG_AI << "desperate attack by '" << u.type_id() << "' " << loc << "\n"; map_location adj[6]; get_adjacent_tiles(loc, adj); double best_kill_prob = 0.0; unsigned int best_weapon = 0; unsigned best_dir = 0; for (unsigned n = 0; n != 6; ++n) { const unit *enemy = get_visible_unit(units_, adj[n], current_team()); if (!enemy || !current_team().is_enemy(enemy->side()) || enemy->incapacitated()) continue; const std::vector<attack_type> &attacks = u.attacks(); for (unsigned i = 0; i != attacks.size(); ++i) { // Skip weapons with attack_weight=0 if (attacks[i].attack_weight() == 0) continue; battle_context bc(units_, loc, adj[n], i); combatant att(bc.get_attacker_stats()); combatant def(bc.get_defender_stats()); att.fight(def); if (def.hp_dist[0] <= best_kill_prob) continue; best_kill_prob = def.hp_dist[0]; best_weapon = i; best_dir = n; } } if (best_kill_prob > 0.0) { attack_result_ptr attack_res = execute_attack_action(loc, adj[best_dir], best_weapon); if (!attack_res->is_ok()) { ERR_AI << "desperate attack failed"<<std::endl; } return attack_res->is_gamestate_changed(); } double least_hp = u.hitpoints() + 1; // Who would do most damage to us when they attack? (approximate: may be different ToD) for (unsigned n = 0; n != 6; ++n) { const unit *enemy = get_visible_unit(units_, adj[n], current_team()); if (!enemy || !current_team().is_enemy(enemy->side()) || enemy->incapacitated()) continue; const std::vector<attack_type> &attacks = enemy->attacks(); for (unsigned i = 0; i != attacks.size(); ++i) { // SKip weapons with attack_weight=0 if (attacks[i].attack_weight() == 0) continue; battle_context bc(units_, adj[n], loc, i); combatant att(bc.get_attacker_stats()); combatant def(bc.get_defender_stats()); att.fight(def); if (def.average_hp() < least_hp) { least_hp = def.average_hp(); best_dir = n; } } } // It is possible that there were no adjacent units to attack... if (least_hp != u.hitpoints() + 1) { battle_context bc(units_, loc, adj[best_dir], -1, -1, 0.5); attack_result_ptr attack_res = execute_attack_action(loc, adj[best_dir], bc.get_attacker_stats().attack_num); if (!attack_res->is_ok()) { ERR_AI << "desperate attack failed" << std::endl; } return attack_res->is_gamestate_changed(); } return false; }
void protect_goal::add_targets(std::back_insert_iterator< std::vector< target >> target_list) { std::string goal_type; if (protect_unit_) { goal_type = "protect_unit"; } else { goal_type ="protect_location"; } if (!(this)->active()) { LOG_AI_GOAL << "skipping " << goal_type << " goal - not active" << std::endl; return; } const config &criteria = cfg_.child("criteria"); if (!criteria) { LOG_AI_GOAL << "skipping " << goal_type << " goal - no criteria given" << std::endl; return; } else { DBG_AI_GOAL << "side " << get_side() << ": "<< goal_type << " goal with criteria" << std::endl << cfg_.child("criteria") << std::endl; } unit_map &units = resources::gameboard->units(); std::set<map_location> items; if (protect_unit_) { const unit_filter ufilt{ vconfig(criteria) }; for (const unit &u : units) { // 'protect_unit' can be set to any unit of any side -> exclude hidden units // unless they are visible to the AI side (e.g. allies with shared vision). // As is done in other parts of the AI, units under fog/shroud count as visible to the AI. if (ufilt(u) && (!u.invisible(u.get_location()) || u.is_visible_to_team(current_team(), false))) { DBG_AI_GOAL << "side " << get_side() << ": in " << goal_type << ": " << u.get_location() << " should be protected\n"; items.insert(u.get_location()); } } } else { filter_ptr_->get_locations(items); } DBG_AI_GOAL << "side " << get_side() << ": searching for threats in "+goal_type+" goal" << std::endl; // Look for directions to protect a specific location or specific unit. for (const map_location &loc : items) { for (const unit &u : units) { int distance = distance_between(u.get_location(), loc); if (current_team().is_enemy(u.side()) && distance < radius_ && !u.invisible(u.get_location())) { DBG_AI_GOAL << "side " << get_side() << ": in " << goal_type << ": found threat target. " << u.get_location() << " is a threat to "<< loc << '\n'; *target_list = target(u.get_location(), value_ * static_cast<double>(radius_ - distance) / radius_, target::TYPE::THREAT); } } } }
void playsingle_controller::play_side(const unsigned int side_number, bool save) { //check for team-specific items in the scenario gui_->parse_team_overlays(); //flag used when we fallback from ai and give temporarily control to human bool temporary_human = false; do { // This flag can be set by derived classes (in overridden functions). player_type_changed_ = false; if (!skip_next_turn_) end_turn_ = false; statistics::reset_turn_stats(teams_[side_number - 1].save_id()); if(current_team().is_human() || temporary_human) { LOG_NG << "is human...\n"; temporary_human = false; try{ before_human_turn(save); play_human_turn(); } catch(end_turn_exception& end_turn) { if (end_turn.redo == side_number) { player_type_changed_ = true; // If new controller is not human, // reset gui to prev human one if (!teams_[side_number-1].is_human()) { browse_ = true; int s = find_human_team_before(side_number); if (s <= 0) s = gui_->playing_side(); update_gui_to_player(s-1); } } } // Ending the turn commits all moves. undo_stack_->clear(); if ( !player_type_changed_ ) after_human_turn(); LOG_NG << "human finished turn...\n"; } else if(current_team().is_ai()) { try { play_ai_turn(); } catch(fallback_ai_to_human_exception&) { // Give control to a human for this turn. player_type_changed_ = true; temporary_human = true; } } else if(current_team().is_network()) { play_network_turn(); } else if(current_team().is_idle()) { try{ end_turn_enable(false); do_idle_notification(); before_human_turn(save); play_idle_loop(); } catch(end_turn_exception& end_turn) { LOG_NG << "Escaped from idle state with exception!" << std::endl; if (end_turn.redo == side_number) { player_type_changed_ = true; // If new controller is not human, // reset gui to prev human one if (!teams_[side_number-1].is_human()) { browse_ = true; int s = find_human_team_before(side_number); if (s <= 0) s = gui_->playing_side(); update_gui_to_player(s-1); } } } } // Else current_team().is_empty(), so do nothing. } while (player_type_changed_); // Keep looping if the type of a team (human/ai/networked) // has changed mid-turn skip_next_turn_ = false; }
void protect_goal::add_targets(std::back_insert_iterator< std::vector< target > > target_list) { std::string goal_type; if (protect_unit_) { if (protect_only_own_unit_) { goal_type = "protect_my_unit"; } else { goal_type = "protect_unit"; } } else { goal_type ="protect_location"; } if (!(this)->active()) { LOG_AI_GOAL << "skipping " << goal_type << " goal - not active" << std::endl; return; } const config &criteria = cfg_.child("criteria"); if (!criteria) { LOG_AI_GOAL << "skipping " << goal_type << " goal - no criteria given" << std::endl; return; } else { DBG_AI_GOAL << goal_type << " goal with criteria" << std::endl << cfg_.child("criteria") << std::endl; } unit_map &units = get_info().units; std::vector<team> &teams = get_info().teams; std::set<map_location> items; if (protect_unit_) { for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) { if (protect_only_own_unit_ && u->second.side()!=get_side()) { continue; } //TODO: we will protect hidden units, by not testing for invisibility to current side if (u->second.matches_filter(vconfig(criteria),u->first)) { DBG_AI_GOAL << "in "<<goal_type<< ": " << u->first << " should be protected " << std::endl; items.insert(u->first); } } } else { filter_ptr_->get_locations(items); } DBG_AI_GOAL << "seaching for threats in "+goal_type+" goal" << std::endl; // Look for directions to protect a specific location or specific unit. foreach (const map_location &loc, items) { for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) { const int distance = distance_between(u->first,loc); if(current_team().is_enemy(u->second.side()) && distance < radius_ && !u->second.invisible(u->first, units, teams)) { DBG_AI_GOAL << "in "<<goal_type<<": found threat target. " << u->first << " is a threat to "<< loc << std::endl; *target_list = target(u->first, value_ * double(radius_-distance) / double(radius_),target::THREAT); } } } }
void playmp_controller::play_human_turn() { command_disabled_resetter reset_commands; int cur_ticks = SDL_GetTicks(); show_turn_dialog(); execute_gotos(); bool auto_end_turn = can_auto_end_turn(true); if (!auto_end_turn) { if (!unit::actor->is_city() && unit::actor->task() == unit::TASK_NONE) { gui_->scroll_to_tile(unit::actor->get_location(), game_display::ONSCREEN, true, true); } } else { return; } if ((!linger_) || (is_host_)) { gui_->set_theme_object_active("endturn", true); } while (!end_turn_ && !auto_end_turn) { try { play_slice(); check_end_level(); std::deque<config> backlog; std::vector<config> cfgs = received_data_cfg_; received_data_cfg_.clear(); for (std::vector<config>::const_iterator it = cfgs.begin(); it != cfgs.end(); ++ it) { const config& cfg = *it; if (turn_data_->process_network_data(cfg, lobby->transit.conn(), backlog, skip_replay_) == turn_info::PROCESS_RESTART_TURN) { // Clean undo stack if turn has to be restarted (losing control) if (!undo_stack_.empty()) { font::floating_label flabel(_("Undoing moves not yet transmitted to the server.")); SDL_Color color = {255,255,255,255}; flabel.set_color(color); SDL_Rect rect = gui_->map_area(); flabel.set_position(rect.w/2, rect.h/2); flabel.set_lifetime(150); flabel.set_clip_rect(rect); font::add_floating_label(flabel); } while(!undo_stack_.empty()) { menu_handler_.undo(gui_->get_playing_team() + 1); } throw end_turn_exception(gui_->get_playing_team() + 1); } } } catch (end_level_exception& e) { turn_data_->send_data(); throw e; } if (!linger_ && (current_team().countdown_time() > 0) && gamestate_.mp_settings().mp_countdown) { SDL_Delay(1); const int ticks = SDL_GetTicks(); int new_time = current_team().countdown_time()-std::max<int>(1,(ticks - cur_ticks)); if (new_time > 0 ){ current_team().set_countdown_time(new_time); cur_ticks = ticks; if(current_team().is_human() && !beep_warning_time_) { beep_warning_time_ = new_time - WARNTIME + ticks; } if(counting_down()) { think_about_countdown(ticks); } } else { // Clock time ended // If no turn bonus or action bonus -> defeat const int action_increment = gamestate_.mp_settings().mp_countdown_action_bonus; if ( (gamestate_.mp_settings().mp_countdown_turn_bonus == 0 ) && (action_increment == 0 || current_team().action_bonus_count() == 0)) { // Not possible to end level in MP with throw end_level_exception(DEFEAT); // because remote players only notice network disconnection // Current solution end remaining turns automatically current_team().set_countdown_time(10); } else { const int maxtime = gamestate_.mp_settings().mp_countdown_reservoir_time; int secs = gamestate_.mp_settings().mp_countdown_turn_bonus; secs += action_increment * current_team().action_bonus_count(); current_team().set_action_bonus_count(0); secs = (secs > maxtime) ? maxtime : secs; current_team().set_countdown_time(1000 * secs); } turn_data_->send_data(); throw end_turn_exception(); } } gui_->draw(); turn_data_->send_data(); auto_end_turn = can_auto_end_turn(false); } menu_handler_.clear_undo_stack(player_number_); }