pathfind::paths::paths(gamemap const &map, unit_map const &units, const unit& u, std::vector<team> const &teams, bool force_ignore_zoc, bool allow_teleport, const team &viewing_team, int additional_turns, bool see_all, bool ignore_units) : destinations() { if (u.side() < 1 || u.side() > int(teams.size())) { return; } find_routes(map, units, u, u.movement_left(), destinations, teams, force_ignore_zoc, allow_teleport,additional_turns,viewing_team, see_all, ignore_units); }
int aspect_attacks::rate_terrain(const unit& u, const map_location& loc) { gamemap &map_ = *resources::game_map; const t_translation::t_terrain terrain = map_.get_terrain(loc); const int defense = u.defense_modifier(terrain); int rating = 100 - defense; const int healing_value = 10; const int friendly_village_value = 5; const int neutral_village_value = 10; const int enemy_village_value = 15; if(map_.gives_healing(terrain) && u.get_ability_bool("regenerate",loc) == false) { rating += healing_value; } if(map_.is_village(terrain)) { int owner = village_owner(loc, *resources::teams) + 1; if(owner == u.side()) { rating += friendly_village_value; } else if(owner == 0) { rating += neutral_village_value; } else { rating += enemy_village_value; } } return rating; }
/** * Clears shroud (and fog) at the provided location and its immediate neighbors. * This is an aid for the [teleport] action, allowing the destination to be * cleared before teleporting, while the unit's full visual range gets cleared * after. * The @a viewer is needed for correct firing of sighted events. * * @return whether or not information was uncovered (i.e. returns true if the * locations in question were fogged/shrouded under shared vision/maps). */ bool shroud_clearer::clear_dest(const map_location &dest, const unit &viewer) { team & viewing_team = resources::gameboard->get_team(viewer.side()); // A pair of dummy variables needed to simplify some logic. std::size_t enemies, friends; // Abort if there is nothing to clear. if ( !viewing_team.fog_or_shroud() ) return false; // Cache some values. const map_location & real_loc = viewer.get_location(); const std::size_t viewer_id = viewer.underlying_id(); // Clear the destination. bool cleared_something = clear_loc(viewing_team, dest, dest, real_loc, viewer_id, true, enemies, friends); // Clear the adjacent hexes (will be seen even if vision is 0, and the // graphics do not work so well for an isolated cleared hex). adjacent_loc_array_t adjacent; get_adjacent_tiles(dest, adjacent.data()); for (unsigned i = 0; i < adjacent.size(); ++i ) if ( clear_loc(viewing_team, adjacent[i], dest, real_loc, viewer_id, true, enemies, friends) ) cleared_something = true; if ( cleared_something ) invalidate_after_clear(); return cleared_something; }
ttactic2::ttactic2(game_display& gui, std::vector<team>& teams, unit_map& units, hero_map& heros, unit& tactician, int operate) : gui_(gui) , teams_(teams) , units_(units) , heros_(heros) , tactician_(tactician) , operate_(operate) , candidate_() , valid_() , cannot_valid_(false) , selected_(-1) { if (tactician_.master().tactic_ != HEROS_NO_TACTIC) { candidate_.push_back(&tactician_.master()); } if (tactician_.second().valid() && tactician_.second().tactic_ != HEROS_NO_TACTIC) { candidate_.push_back(&tactician_.second()); } if (tactician_.third().valid() && tactician_.third().tactic_ != HEROS_NO_TACTIC) { candidate_.push_back(&tactician_.third()); } if (operate_ == ACTIVE) { std::vector<unit*> actives = teams_[tactician.side() - 1].active_tactics(); if ((int)actives.size() >= game_config::active_tactic_slots || std::find(actives.begin(), actives.end(), &tactician) != actives.end()) { cannot_valid_ = true; } } }
pathfind::teleport_map::teleport_map( const std::vector<teleport_group>& groups , const unit& u , const team &viewing_team , const bool see_all , const bool ignore_units) : teleport_map_() , sources_() , targets_() { foreach(const teleport_group& group, groups) { teleport_pair locations; group.get_teleport_pair(locations, u, ignore_units); if (!see_all && !group.always_visible() && viewing_team.is_enemy(u.side())) { teleport_pair filter_locs; foreach(const map_location &loc, locations.first) if(!viewing_team.fogged(loc)) filter_locs.first.insert(loc); foreach(const map_location &loc, locations.second) if(!viewing_team.fogged(loc)) filter_locs.second.insert(loc); locations.first.swap(filter_locs.first); locations.second.swap(filter_locs.second); }
bool display_context::unit_can_move(const unit &u) const { if(!u.attacks_left() && u.movement_left()==0) return false; // Units with goto commands that have already done their gotos this turn // (i.e. don't have full movement left) should have red globes. if(u.has_moved() && u.has_goto()) { return false; } const team ¤t_team = get_team(u.side()); map_location locs[6]; get_adjacent_tiles(u.get_location(), locs); for(int n = 0; n != 6; ++n) { if (map().on_board(locs[n])) { const unit_map::const_iterator i = units().find(locs[n]); if (i.valid() && !i->incapacitated() && current_team.is_enemy(i->side())) { return true; } if (u.movement_cost(map()[locs[n]]) <= u.movement_left()) { return true; } } } return false; }
int path_cost(std::vector<map_location> const& path, unit const& u) { if(path.size() < 2) return 0; map_location const& dest = path.back(); if((resources::game_map->is_village(dest) && !resources::teams->at(u.side()-1).owns_village(dest)) || pathfind::enemy_zoc(*resources::teams,dest,resources::teams->at(u.side()-1),u.side())) return u.total_movement(); int result = 0; gamemap const& map = *resources::game_map; BOOST_FOREACH(map_location const& loc, std::make_pair(path.begin()+1,path.end())) result += u.movement_cost(map[loc]); return result; }
void unit_creator::post_create(const map_location &loc, const unit &new_unit, bool anim) { if (discover_) { preferences::encountered_units().insert(new_unit.type_id()); } bool show = show_ && (resources::screen !=nullptr) && !resources::screen->fogged(loc); bool animate = show && anim; if (get_village_) { assert(resources::gameboard); if (board_->map().is_village(loc)) { actions::get_village(loc, new_unit.side()); } } // Only fire the events if it's safe; it's not if we're in the middle of play_controller::reset_gamestate() if (resources::lua_kernel != nullptr) { resources::game_events->pump().fire("unit_placed", loc); } if (resources::screen!=nullptr) { if (invalidate_ ) { resources::screen->invalidate(loc); } if (animate) { unit_display::unit_recruited(loc); } else if (show) { resources::screen->draw(); } } }
std::vector<map_location> fake_unit_path(const unit& fake_unit, const std::vector<std::string>& xvals, const std::vector<std::string>& yvals) { const gamemap *game_map = & resources::gameboard->map(); std::vector<map_location> path; map_location src; map_location dst; for(size_t i = 0; i != std::min(xvals.size(),yvals.size()); ++i) { if(i==0){ src.x = atoi(xvals[i].c_str())-1; src.y = atoi(yvals[i].c_str())-1; if (!game_map->on_board(src)) { ERR_CF << "invalid move_unit_fake source: " << src << '\n'; break; } path.push_back(src); continue; } pathfind::shortest_path_calculator calc(fake_unit, (*resources::teams)[fake_unit.side()-1], *resources::teams, *game_map); dst.x = atoi(xvals[i].c_str())-1; dst.y = atoi(yvals[i].c_str())-1; if (!game_map->on_board(dst)) { ERR_CF << "invalid move_unit_fake destination: " << dst << '\n'; break; } pathfind::plain_route route = pathfind::a_star_search(src, dst, 10000, &calc, game_map->w(), game_map->h()); if (route.steps.empty()) { WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring complexities" << std::endl; pathfind::emergency_path_calculator calc(fake_unit, *game_map); route = pathfind::a_star_search(src, dst, 10000, &calc, game_map->w(), game_map->h()); if(route.steps.empty()) { // This would occur when trying to do a MUF of a unit // over locations which are unreachable to it (infinite movement // costs). This really cannot fail. WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring terrain" << std::endl; pathfind::dummy_path_calculator calc(fake_unit, *game_map); route = a_star_search(src, dst, 10000, &calc, game_map->w(), game_map->h()); assert(!route.steps.empty()); } } // we add this section to the end of the complete path // skipping section's head because already included // by the previous iteration path.insert(path.end(), route.steps.begin()+1, route.steps.end()); src = dst; } return path; }
/** * Construct a list of paths for the specified unit. * * This function is used for several purposes, including showing a unit's * potential moves and generating currently possible paths. * @param u The unit whose moves and movement type will be used. * @param force_ignore_zoc Set to true to completely ignore zones of control. * @param allow_teleport Set to true to consider teleportation abilities. * @param viewing_team Usually the current team, except for "show enemy moves", etc. * @param additional_turns The number of turns to account for, in addition to the current. * @param see_all Set to true to remove unit visibility from consideration. * @param ignore_units Set to true if units should never obstruct paths (implies ignoring ZoC as well). */ paths::paths(const unit& u, bool force_ignore_zoc, bool allow_teleport, const team &viewing_team, int additional_turns, bool see_all, bool ignore_units) : destinations() { std::vector<team> const &teams = *resources::teams; if (u.side() < 1 || u.side() > int(teams.size())) { return; } find_routes(u.get_location(), u.movement_type().get_movement(), u.get_state(unit::STATE_SLOWED), u.movement_left(), u.total_movement(), additional_turns, destinations, NULL, allow_teleport ? &u : NULL, ignore_units ? NULL : &teams[u.side()-1], force_ignore_zoc ? NULL : &u, see_all ? NULL : &viewing_team); }
/** * Clears shroud (and fog) around the provided location as if @a viewer * was standing there. * This version of shroud_clearer::clear_unit() will abort if the viewer's * team uses neither fog nor shroud. If @a can_delay is left as true, then * this function also aborts on the viewing team's turn if delayed shroud * updates is on. (Not supplying a team suggests that it would be inconvenient * for the caller to check these.) * In addition, if @a invalidate is left as true, invalidate_after_clear() * will be called. * Setting @a instant to false allows some drawing delays that are used to * make movement look better. * * @return whether or not information was uncovered (i.e. returns true if any * locations in visual range were fogged/shrouded under shared vision/maps). */ bool shroud_clearer::clear_unit(const map_location &view_loc, const unit &viewer, bool can_delay, bool invalidate, bool instant) { team & viewing_team = resources::gameboard->get_team(viewer.side()); // Abort if there is nothing to clear. if ( !viewing_team.fog_or_shroud() ) return false; if ( can_delay && !viewing_team.auto_shroud_updates() && viewer.side() == resources::controller->current_side() ) return false; if ( !clear_unit(view_loc, viewer, viewing_team, instant) ) // Nothing uncovered. return false; if ( invalidate ) invalidate_after_clear(); return true; }
static stuff_list_adder add_unit_entry(stuff_list_adder& progress, const unit& u, const display_context& dc) { Uint32 team_color = game_config::tc_info(dc.get_team(u.side()).color())[0]; std::stringstream s; s << '(' << u.get_location() << ')'; progress.widget("loc", s.str()); s.str(""); s << "<span color='#" << std::hex << team_color << std::dec; s << "'>side=" << u.side() << "</span>"; progress.widget("side", s.str(), true); if(u.can_recruit()) { progress.widget("leader", "<span color='yellow'>LEADER</span> ", true); } s.str(""); s << "id=\"" << u.id() << '"'; progress.widget("id", s.str()); progress.widget("type", u.type_id()); s.str(""); s << "L" << u.level(); progress.widget("level", s.str()); s.str(""); s << u.experience() << '/' << u.max_experience() << " xp"; progress.widget("xp", s.str()); s.str(""); s << u.hitpoints() << '/' << u.max_hitpoints() << " hp"; progress.widget("hp", s.str()); progress.widget("traits", utils::join(u.get_traits_list(), ", ")); return progress; }
std::set<map_location> get_teleport_locations(const unit &u, const unit_map &units, const team &viewing_team, bool see_all, bool ignore_units) { std::set<map_location> res; if (!u.get_ability_bool("teleport")) return res; const team ¤t_team = (*resources::teams)[u.side() - 1]; const map_location &loc = u.get_location(); foreach (const map_location &l, current_team.villages()) { // This must be a vacant village (or occupied by the unit) // to be able to teleport. if (!see_all && viewing_team.is_enemy(u.side()) && viewing_team.fogged(l)) continue; if (!ignore_units && l != loc && get_visible_unit(units, l, viewing_team, see_all)) continue; res.insert(l); } return res; }
/** * Determines if @a loc is within @a viewer's visual range. * This is a moderately expensive function (vision is recalculated * with each call), so avoid using it heavily. * If @a jamming is left as nullptr, the jamming map is also calculated * with each invocation. */ static bool can_see(const unit & viewer, const map_location & loc, const std::map<map_location, int> * jamming = nullptr) { // Make sure we have a "jamming" map. std::map<map_location, int> local_jamming; if ( jamming == nullptr ) { create_jamming_map(local_jamming, resources::gameboard->get_team(viewer.side())); jamming = &local_jamming; } // Determine which hexes this unit can see. pathfind::vision_path sight(viewer, viewer.get_location(), *jamming); return sight.destinations.contains(loc) || sight.edges.count(loc) != 0; }
static std::string get_image_mods(const unit& u) { std::string res = "~RC(" + u.team_color() + ">" + team::get_side_color_index(u.side()) + ")"; if (u.can_recruit()) { res += "~BLIT(" + unit::leader_crown() + ")"; } BOOST_FOREACH(const std::string& overlay, u.overlays()) { res += "~BLIT(" + overlay + ")"; } return res; }
int path_cost(std::vector<map_location> const& path, unit const& u) { if(path.size() < 2) return 0; team const& u_team = resources::teams->at(u.side()-1); map_location const& dest = path.back(); if ( (resources::gameboard->map().is_village(dest) && !u_team.owns_village(dest)) || pathfind::enemy_zoc(u_team, dest, u_team) ) return u.total_movement(); int result = 0; gamemap const& map = resources::gameboard->map(); for(map_location const& loc : std::make_pair(path.begin()+1,path.end())) { result += u.movement_cost(map[loc]); } return result; }
bool game_state::can_recruit_from(const unit& leader) const { return can_recruit_from(leader.get_location(), leader.side()); }
marked_route mark_route(const plain_route &rt, const std::vector<map_location>& waypoints, const unit &u, const team &viewing_team, const unit_map &units, const std::vector<team> &teams, const gamemap &map) { marked_route res; if (rt.steps.empty()) return res; res.steps = rt.steps; int turns = 0; int movement = u.movement_left(); const team& unit_team = teams[u.side()-1]; bool zoc = false; std::vector<map_location>::const_iterator i = rt.steps.begin(), w = waypoints.begin(); // TODO fix the name confusion with waypoints and route.waypoints for (; i !=rt.steps.end(); i++) { bool last_step = (i+1 == rt.steps.end()); // move_cost of the next step is irrelevant for the last step assert(last_step || map.on_board(*(i+1))); const int move_cost = last_step ? 0 : u.movement_cost(map[*(i+1)]); bool capture = false; bool pass_here = false; if (w != waypoints.end() && *i == *w) { w++; pass_here = true; } if (last_step || zoc || move_cost > movement) { // check if we stop an a village and so maybe capture it // if it's an enemy unit and a fogged village, we assume a capture // (if he already owns it, we can't know that) // if it's not an enemy, we can always know if he owns the village bool capture = map.is_village(*i) && ( !unit_team.owns_village(*i) || (viewing_team.is_enemy(u.side()) && viewing_team.fogged(*i)) ); ++turns; bool invisible = u.invisible(*i,units,teams,false); res.waypoints[*i] = marked_route::waypoint(turns, pass_here, zoc, capture, invisible); if (last_step) break; // finished and we used dummy move_cost movement = u.total_movement(); if(move_cost > movement) { return res; //we can't reach destination } } else if (pass_here) { bool invisible = u.invisible(*i,units,teams,false); res.waypoints[*i] = marked_route::waypoint(0, pass_here, zoc, false, invisible); } zoc = enemy_zoc(units, teams, *(i + 1), viewing_team,u.side()) && !u.get_ability_bool("skirmisher", *(i+1)); if (zoc || capture) { movement = 0; } else { movement -= move_cost; } } return res; }
static void find_routes(const gamemap& map, const unit_map& units, const unit& u, const map_location& loc, int move_left, paths::dest_vect &destinations, std::vector<team> const &teams, bool force_ignore_zocs, bool allow_teleport, int turns_left, const team &viewing_team, bool see_all, bool ignore_units) { const team& current_team = teams[u.side() - 1]; std::set<map_location> teleports; if (allow_teleport) { teleports = get_teleport_locations(u, units, viewing_team, see_all, ignore_units); } const int total_movement = u.total_movement(); std::vector<map_location> locs(6 + teleports.size()); std::copy(teleports.begin(), teleports.end(), locs.begin() + 6); search_counter += 2; if (search_counter == 0) search_counter = 2; static std::vector<node> nodes; nodes.resize(map.w() * map.h()); indexer index(map.w(), map.h()); comp node_comp(nodes); int xmin = loc.x, xmax = loc.x, ymin = loc.y, ymax = loc.y, nb_dest = 1; nodes[index(loc)] = node(move_left, turns_left, map_location::null_location, loc); std::vector<int> pq; pq.push_back(index(loc)); while (!pq.empty()) { node& n = nodes[pq.front()]; std::pop_heap(pq.begin(), pq.end(), node_comp); pq.pop_back(); n.in = search_counter; get_adjacent_tiles(n.curr, &locs[0]); for (int i = teleports.count(n.curr) ? locs.size() : 6; i-- > 0; ) { if (!locs[i].valid(map.w(), map.h())) continue; node& next = nodes[index(locs[i])]; bool next_visited = next.in - search_counter <= 1u; // Classic Dijkstra allow to skip chosen nodes (with next.in==search_counter) // But the cost function and hex grid allow to also skip visited nodes: // if next was visited, then we already have a path 'src-..-n2-next' // - n2 was chosen before n, meaning that it is nearer to src. // - the cost of 'n-next' can't be smaller than 'n2-next' because // cost is independent of direction and we don't have more MP at n // (important because more MP may allow to avoid waiting next turn) // Thus, 'src-..-n-next' can't be shorter. if (next_visited) continue; const int move_cost = u.movement_cost(map[locs[i]]); node t = node(n.movement_left, n.turns_left, n.curr, locs[i]); if (t.movement_left < move_cost) { t.movement_left = total_movement; t.turns_left--; } if (t.movement_left < move_cost || t.turns_left < 0) continue; t.movement_left -= move_cost; if (!ignore_units) { const unit *v = get_visible_unit(units, locs[i], viewing_team, see_all); if (v && current_team.is_enemy(v->side())) continue; if (!force_ignore_zocs && t.movement_left > 0 && enemy_zoc(units, teams, locs[i], viewing_team, u.side(), see_all) && !u.get_ability_bool("skirmisher", locs[i])) { t.movement_left = 0; } } ++nb_dest; int x = locs[i].x; if (x < xmin) xmin = x; if (xmax < x) xmax = x; int y = locs[i].y; if (y < ymin) ymin = y; if (ymax < y) ymax = y; bool in_list = next.in == search_counter + 1; t.in = search_counter + 1; next = t; // if already in the priority queue then we just update it, else push it. if (in_list) { // never happen see next_visited above std::push_heap(pq.begin(), std::find(pq.begin(), pq.end(), index(locs[i])) + 1, node_comp); } else { pq.push_back(index(locs[i])); std::push_heap(pq.begin(), pq.end(), node_comp); } } } // Build the routes for every map_location that we reached. // The ordering must be compatible with map_location::operator<. destinations.reserve(nb_dest); for (int x = xmin; x <= xmax; ++x) { for (int y = ymin; y <= ymax; ++y) { const node &n = nodes[index(map_location(x, y))]; if (n.in - search_counter > 1u) continue; paths::step s = { n.curr, n.prev, n.movement_left + n.turns_left * total_movement }; destinations.push_back(s); } } }
std::deque<action_ptr> find_actions_of(unit const &target) { return resources::gameboard->teams()[target.side()-1].get_side_actions()->actions_of(target); }
static int attack_info(reports::context & rc, const attack_type &at, config &res, const unit &u, const map_location &displayed_unit_hex) { std::ostringstream str, tooltip; at.set_specials_context(displayed_unit_hex, u.side() == rc.screen().playing_side()); int base_damage = at.damage(); int specials_damage = at.modified_damage(false); int damage_multiplier = 100; int tod_bonus = combat_modifier(rc.units(), rc.map(), displayed_unit_hex, u.alignment(), u.is_fearless()); damage_multiplier += tod_bonus; int leader_bonus = 0; if (under_leadership(rc.units(), displayed_unit_hex, &leader_bonus).valid()) damage_multiplier += leader_bonus; bool slowed = u.get_state(unit::STATE_SLOWED); int damage_divisor = slowed ? 20000 : 10000; // Assume no specific resistance (i.e. multiply by 100). int damage = round_damage(specials_damage, damage_multiplier * 100, damage_divisor); // Hit points are used to calculate swarm, so they need to be bounded. unsigned max_hp = u.max_hitpoints(); unsigned cur_hp = std::min<unsigned>(std::max(0, u.hitpoints()), max_hp); unsigned base_attacks = at.num_attacks(); unsigned min_attacks, max_attacks; at.modified_attacks(false, min_attacks, max_attacks); unsigned num_attacks = swarm_blows(min_attacks, max_attacks, cur_hp, max_hp); SDL_Color dmg_color = font::weapon_color; if ( damage > specials_damage ) dmg_color = font::good_dmg_color; else if ( damage < specials_damage ) dmg_color = font::bad_dmg_color; str << span_color(dmg_color) << " " << damage << naps << span_color(font::weapon_color) << font::weapon_numbers_sep << num_attacks << ' ' << at.name() << "</span>\n"; tooltip << _("Weapon: ") << "<b>" << at.name() << "</b>\n" << _("Damage: ") << "<b>" << damage << "</b>\n"; if ( tod_bonus || leader_bonus || slowed || specials_damage != base_damage ) { tooltip << '\t' << _("Base damage: ") << base_damage << '\n'; if ( specials_damage != base_damage ) { tooltip << '\t' << _("With specials: ") << specials_damage << '\n'; } if (tod_bonus) { tooltip << '\t' << _("Time of day: ") << utils::signed_percent(tod_bonus) << '\n'; } if (leader_bonus) { tooltip << '\t' << _("Leadership: ") << utils::signed_percent(leader_bonus) << '\n'; } if (slowed) { tooltip << '\t' << _("Slowed: ") << "/ 2" << '\n'; } } tooltip << _("Attacks: ") << "<b>" << num_attacks << "</b>\n"; if ( max_attacks != min_attacks && cur_hp != max_hp ) { if ( max_attacks < min_attacks ) { // "Reverse swarm" tooltip << '\t' << _("Max swarm bonus: ") << (min_attacks-max_attacks) << '\n'; tooltip << '\t' << _("Swarm: ") << "* "<< (100 - cur_hp*100/max_hp) << "%\n"; tooltip << '\t' << _("Base attacks: ") << '+' << base_attacks << '\n'; // The specials line will not necessarily match up with how the // specials are calculated, but for an unusual case, simple brevity // trumps complexities. if ( max_attacks != base_attacks ) { int attack_diff = int(max_attacks) - int(base_attacks); tooltip << '\t' << _("Specials: ") << utils::signed_value(attack_diff) << '\n'; } } else { // Regular swarm tooltip << '\t' << _("Base attacks: ") << base_attacks << '\n'; if ( max_attacks != base_attacks ) { tooltip << '\t' << _("With specials: ") << max_attacks << '\n'; } if ( min_attacks != 0 ) { tooltip << '\t' << _("Subject to swarm: ") << (max_attacks-min_attacks) << '\n'; } tooltip << '\t' << _("Swarm: ") << "* "<< (cur_hp*100/max_hp) << "%\n"; } } else if ( num_attacks != base_attacks ) { tooltip << '\t' << _("Base attacks: ") << base_attacks << '\n'; tooltip << '\t' << _("With specials: ") << num_attacks << '\n'; } add_text(res, flush(str), flush(tooltip)); std::string range = string_table["range_" + at.range()]; std::string lang_type = string_table["type_" + at.type()]; str << span_color(font::weapon_details_color) << " " << " " << range << font::weapon_details_sep << lang_type << "</span>\n"; tooltip << _("Weapon range: ") << "<b>" << range << "</b>\n" << _("Damage type: ") << "<b>" << lang_type << "</b>\n" << _("Damage versus: ") << '\n'; // Show this weapon damage and resistance against all the different units. // We want weak resistances (= good damage) first. std::map<int, std::set<std::string>, std::greater<int> > resistances; std::set<std::string> seen_types; const team &unit_team = rc.teams()[u.side() - 1]; const team &viewing_team = rc.teams()[rc.screen().viewing_team()]; for (const unit &enemy : rc.units()) { if (enemy.incapacitated()) //we can't attack statues so don't display them in this tooltip continue; if (!unit_team.is_enemy(enemy.side())) continue; const map_location &loc = enemy.get_location(); if (viewing_team.fogged(loc) || (viewing_team.is_enemy(enemy.side()) && enemy.invisible(loc))) continue; bool new_type = seen_types.insert(enemy.type_id()).second; if (new_type) { int resistance = enemy.resistance_against(at, false, loc); resistances[resistance].insert(enemy.type_name()); } } typedef std::pair<int, std::set<std::string> > resist_units; for (const resist_units &resist : resistances) { int damage = round_damage(specials_damage, damage_multiplier * resist.first, damage_divisor); tooltip << "<b>" << damage << "</b> " << "<i>(" << utils::signed_percent(resist.first-100) << ")</i> : " << utils::join(resist.second, ", ") << '\n'; } add_text(res, flush(str), flush(tooltip)); const std::string &accuracy_parry = at.accuracy_parry_description(); if (!accuracy_parry.empty()) { str << span_color(font::weapon_details_color) << " " << accuracy_parry << "</span>\n"; int accuracy = at.accuracy(); if (accuracy) { tooltip << _("Accuracy:") << "<b>" << utils::signed_percent(accuracy) << "</b>\n"; } int parry = at.parry(); if (parry) { tooltip << _("Parry:") << "<b>" << utils::signed_percent(parry) << "</b>\n"; } add_text(res, flush(str), flush(tooltip)); } at.set_specials_context_for_listing(); std::vector<bool> active; const std::vector<std::pair<t_string, t_string> > &specials = at.special_tooltips(&active); const size_t specials_size = specials.size(); for ( size_t i = 0; i != specials_size; ++i ) { // Aliases for readability: const t_string &name = specials[i].first; const t_string &description = specials[i].second; const SDL_Color &details_color = active[i] ? font::weapon_details_color : font::inactive_details_color; str << span_color(details_color) << " " << " " << name << naps << '\n'; std::string help_page = "weaponspecial_" + name.base_str(); tooltip << _("Weapon special: ") << "<b>" << name << "</b>"; if ( !active[i] ) tooltip << "<i>" << _(" (inactive)") << "</i>"; tooltip << '\n' << description; add_text(res, flush(str), flush(tooltip), help_page); } return damage; }
std::deque<action_ptr> find_actions_of(unit const &target) { return (*resources::teams)[target.side()-1].get_side_actions()->actions_of(target); }
/** * Fires sighted events for the sides that can see @a target. * If @a cache is supplied, only those sides might get events. * If @a cache is nullptr, all sides might get events. * This function is for the sighting *of* units that clear the shroud; it is * the complement of shroud_clearer::fire_events(), which handles sighting *by* * units that clear the shroud. * * See get_sides_not_seeing() for a way to obtain a cache. * * @returns true if an event has mutated the game state. */ game_events::pump_result_t actor_sighted(const unit & target, const std::vector<int> * cache) /* Current logic: * 1) One event is fired per side that can see the target. * 2) The second unit for the event is one that can see the target, if possible. * 3) If no units on a side can see the target, a second unit is chosen as * close as possible (but this behavior should not be relied on; it is * subject to change at any time, should it become inconvenient). * 4) A side with no units at all will not get a sighted event. * 5) Sides that do not use fog or shroud CAN get sighted events. */ { const std::vector<team> & teams = resources::gameboard->teams(); const std::size_t teams_size = teams.size(); const map_location & target_loc = target.get_location(); // Determine the teams that (probably) should get events. boost::dynamic_bitset<> needs_event; needs_event.resize(teams_size, cache == nullptr); if ( cache != nullptr ) { // Flag just the sides in the cache as needing events. for (int side : *cache) needs_event[side-1] = true; } // Exclude the target's own team. needs_event[target.side()-1] = false; // Exclude those teams that cannot see the target. for ( std::size_t i = 0; i != teams_size; ++i ) needs_event[i] = needs_event[i] && target.is_visible_to_team(teams[i], false); // Cache "jamming". std::vector< std::map<map_location, int>> jamming_cache(teams_size); for ( std::size_t i = 0; i != teams_size; ++i ) if ( needs_event[i] ) create_jamming_map(jamming_cache[i], teams[i]); // Look for units that can be used as the second unit in sighted events. std::vector<const unit *> second_units(teams_size, nullptr); std::vector<std::size_t> distances(teams_size, UINT_MAX); for (const unit & viewer : resources::gameboard->units()) { const std::size_t index = viewer.side() - 1; // Does viewer belong to a team for which we still need a unit? if ( needs_event[index] && distances[index] != 0 ) { if ( can_see(viewer, target_loc, &jamming_cache[index]) ) { // Definitely use viewer as the second unit. second_units[index] = &viewer; distances[index] = 0; } else { // Consider viewer as a backup if it is close. std::size_t viewer_distance = distance_between(target_loc, viewer.get_location()); if ( viewer_distance < distances[index] ) { second_units[index] = &viewer; distances[index] = viewer_distance; } } } } // Raise events for the appropriate teams. const game_events::entity_location target_entity(target); for ( std::size_t i = 0; i != teams_size; ++i ) if ( second_units[i] != nullptr ) { resources::game_events->pump().raise(sighted_str, target_entity, game_events::entity_location(*second_units[i])); } // Fire the events and return. return resources::game_events->pump()(); }
BOOST_FOREACH (unit & i, units_) { if (i.side() == player_num) { i.new_turn(); } }
bool basic_unit_filter_impl::internal_matches_filter(const unit & u, const map_location& loc, const unit* u2) const { if (!vcfg["name"].blank() && vcfg["name"].t_str() != u.name()) { return false; } if (!vcfg["id"].empty()) { std::vector<std::string> id_list = utils::split(vcfg["id"]); if (std::find(id_list.begin(), id_list.end(), u.id()) == id_list.end()) { return false; } } // Allow 'speaker' as an alternative to id, since people use it so often if (!vcfg["speaker"].blank() && vcfg["speaker"].str() != u.id()) { return false; } if (vcfg.has_child("filter_location")) { if (vcfg.count_children("filter_location") > 1) { FAIL("Encountered multiple [filter_location] children of a standard unit filter. " "This is not currently supported and in all versions of wesnoth would have " "resulted in the later children being ignored. You must use [and] or similar " "to achieve the desired result."); } terrain_filter filt(vcfg.child("filter_location"), &fc_, use_flat_tod_); if (!filt.match(loc)) { return false; } } if(vcfg.has_child("filter_side")) { if (vcfg.count_children("filter_side") > 1) { FAIL("Encountered multiple [filter_side] children of a standard unit filter. " "This is not currently supported and in all versions of wesnoth would have " "resulted in the later children being ignored. You must use [and] or similar " "to achieve the desired result."); } side_filter filt(vcfg.child("filter_side"), &fc_); if(!filt.match(u.side())) return false; } // Also allow filtering on location ranges outside of the location filter if (!vcfg["x"].blank() || !vcfg["y"].blank()){ if(vcfg["x"] == "recall" && vcfg["y"] == "recall") { //locations on the map are considered to not be on a recall list if (fc_.get_disp_context().map().on_board(loc)) { return false; } } else if(vcfg["x"].empty() && vcfg["y"].empty()) { return false; } else if(!loc.matches_range(vcfg["x"], vcfg["y"])) { return false; } } // The type could be a comma separated list of types if (!vcfg["type"].empty()) { std::vector<std::string> types = utils::split(vcfg["type"]); if (std::find(types.begin(), types.end(), u.type_id()) == types.end()) { return false; } } // Shorthand for all advancements of a given type if (!vcfg["type_tree"].empty()) { std::set<std::string> types; for(const std::string type : utils::split(vcfg["type_tree"])) { if(types.count(type)) { continue; } if(const unit_type* ut = unit_types.find(type)) { const auto& tree = ut->advancement_tree(); types.insert(tree.begin(), tree.end()); types.insert(type); } } if(types.find(u.type_id()) == types.end()) { return false; } } // The variation_type could be a comma separated list of types if (!vcfg["variation"].empty()) { std::vector<std::string> types = utils::split(vcfg["variation"]); if (std::find(types.begin(), types.end(), u.variation()) == types.end()) { return false; } } // The has_variation_type could be a comma separated list of types if (!vcfg["has_variation"].empty()) { bool match = false; // If this unit is a variation itself then search in the base unit's variations. const unit_type* const type = u.variation().empty() ? &u.type() : unit_types.find(u.type().base_id()); assert(type); for (const std::string& variation_id : utils::split(vcfg["has_variation"])) { if (type->has_variation(variation_id)) { match = true; break; } } if (!match) return false; } if (!vcfg["ability"].empty()) { bool match = false; for (const std::string& ability_id : utils::split(vcfg["ability"])) { if (u.has_ability_by_id(ability_id)) { match = true; break; } } if (!match) return false; } if (!vcfg["race"].empty()) { std::vector<std::string> races = utils::split(vcfg["race"]); if (std::find(races.begin(), races.end(), u.race()->id()) == races.end()) { return false; } } if (!vcfg["gender"].blank() && string_gender(vcfg["gender"]) != u.gender()) { return false; } if (!vcfg["side"].empty() && vcfg["side"].to_int(-999) != u.side()) { std::vector<std::string> sides = utils::split(vcfg["side"]); const std::string u_side = std::to_string(u.side()); if (std::find(sides.begin(), sides.end(), u_side) == sides.end()) { return false; } } // handle statuses list if (!vcfg["status"].empty()) { bool status_found = false; for (const std::string status : utils::split(vcfg["status"])) { if(u.get_state(status)) { status_found = true; break; } } if(!status_found) { return false; } } if (vcfg.has_child("has_attack")) { const vconfig& weap_filter = vcfg.child("has_attack"); bool has_weapon = false; for(const attack_type& a : u.attacks()) { if(a.matches_filter(weap_filter.get_parsed_config())) { has_weapon = true; break; } } if(!has_weapon) { return false; } } else if (!vcfg["has_weapon"].blank()) { std::string weapon = vcfg["has_weapon"]; bool has_weapon = false; for(const attack_type& a : u.attacks()) { if(a.id() == weapon) { has_weapon = true; break; } } if(!has_weapon) { return false; } } if (!vcfg["role"].blank() && vcfg["role"].str() != u.get_role()) { return false; } if (!vcfg["ai_special"].blank() && ((vcfg["ai_special"].str() == "guardian") != u.get_state(unit::STATE_GUARDIAN))) { return false; } if (!vcfg["canrecruit"].blank() && vcfg["canrecruit"].to_bool() != u.can_recruit()) { return false; } if (!vcfg["recall_cost"].blank() && vcfg["recall_cost"].to_int(-1) != u.recall_cost()) { return false; } if (!vcfg["level"].blank() && vcfg["level"].to_int(-1) != u.level()) { return false; } if (!vcfg["defense"].blank() && vcfg["defense"].to_int(-1) != u.defense_modifier(fc_.get_disp_context().map().get_terrain(loc))) { return false; } if (!vcfg["movement_cost"].blank() && vcfg["movement_cost"].to_int(-1) != u.movement_cost(fc_.get_disp_context().map().get_terrain(loc))) { return false; } // Now start with the new WML based comparison. // If a key is in the unit and in the filter, they should match // filter only => not for us // unit only => not filtered config unit_cfg; // No point in serializing the unit once for each [filter_wml]! for (const vconfig& wmlcfg : vcfg.get_children("filter_wml")) { config fwml = wmlcfg.get_parsed_config(); /* Check if the filter only cares about variables. If so, no need to serialize the whole unit. */ config::all_children_itors ci = fwml.all_children_range(); if (fwml.all_children_count() == 1 && fwml.attribute_count() == 1 && ci.front().key == "variables") { if (!u.variables().matches(ci.front().cfg)) return false; } else { if (unit_cfg.empty()) u.write(unit_cfg); if (!unit_cfg.matches(fwml)) return false; } } for (const vconfig& vision : vcfg.get_children("filter_vision")) { std::set<int> viewers; // Use standard side filter side_filter ssf(vision, &fc_); std::vector<int> sides = ssf.get_teams(); viewers.insert(sides.begin(), sides.end()); bool found = false; for (const int viewer : viewers) { bool fogged = fc_.get_disp_context().teams()[viewer - 1].fogged(loc); bool hiding = u.invisible(loc, fc_.get_disp_context()); bool unit_hidden = fogged || hiding; if (vision["visible"].to_bool(true) != unit_hidden) { found = true; break; } } if (!found) {return false;} } if (vcfg.has_child("filter_adjacent")) { const unit_map& units = fc_.get_disp_context().units(); map_location adjacent[6]; get_adjacent_tiles(loc, adjacent); for (const vconfig& adj_cfg : vcfg.get_children("filter_adjacent")) { int match_count=0; unit_filter filt(adj_cfg, &fc_, use_flat_tod_); config::attribute_value i_adjacent = adj_cfg["adjacent"]; std::vector<map_location::DIRECTION> dirs; if (i_adjacent.blank()) { dirs = map_location::default_dirs(); } else { dirs = map_location::parse_directions(i_adjacent); } std::vector<map_location::DIRECTION>::const_iterator j, j_end = dirs.end(); for (j = dirs.begin(); j != j_end; ++j) { unit_map::const_iterator unit_itor = units.find(adjacent[*j]); if (unit_itor == units.end() || !filt(*unit_itor, u)) { continue; } boost::optional<bool> is_enemy; if (!adj_cfg["is_enemy"].blank()) { is_enemy = adj_cfg["is_enemy"].to_bool(); } if (!is_enemy || *is_enemy == fc_.get_disp_context().teams()[u.side() - 1].is_enemy(unit_itor->side())) { ++match_count; } } static std::vector<std::pair<int,int> > default_counts = utils::parse_ranges("1-6"); config::attribute_value i_count = adj_cfg["count"]; if(!in_ranges(match_count, !i_count.blank() ? utils::parse_ranges(i_count) : default_counts)) { return false; } } } if (!vcfg["find_in"].blank()) { // Allow filtering by searching a stored variable of units if (const game_data * gd = fc_.get_game_data()) { try { variable_access_const vi = gd->get_variable_access_read(vcfg["find_in"]); bool found_id = false; for (const config& c : vi.as_array()) { if(c["id"] == u.id()) found_id = true; } if(!found_id) { return false; } } catch(const invalid_variablename_exception&) { return false; } } } if (!vcfg["formula"].blank()) { try { const unit_callable main(loc,u); game_logic::map_formula_callable callable(&main); if (u2) { std::shared_ptr<unit_callable> secondary(new unit_callable(*u2)); callable.add("other", variant(secondary.get())); // It's not destroyed upon scope exit because the variant holds a reference } const game_logic::formula form(vcfg["formula"]); if(!form.evaluate(callable).as_bool()) { return false; } return true; } catch(game_logic::formula_error& e) { lg::wml_error() << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n"; // Formulae with syntax errors match nothing return false; } } if (!vcfg["lua_function"].blank()) { if (game_lua_kernel * lk = fc_.get_lua_kernel()) { bool b = lk->run_filter(vcfg["lua_function"].str().c_str(), u); if (!b) return false; } } return true; }
void unit_drawer::redraw_unit (const unit & u) const { unit_animation_component & ac = u.anim_comp(); map_location loc = u.get_location(); int side = u.side(); bool hidden = u.get_hidden(); bool is_flying = u.is_flying(); map_location::DIRECTION facing = u.facing(); int hitpoints = u.hitpoints(); int max_hitpoints = u.max_hitpoints(); int movement_left = u.movement_left(); int total_movement = u.total_movement(); bool can_recruit = u.can_recruit(); bool can_advance = u.can_advance(); int experience = u.experience(); int max_experience = u.max_experience(); bool emit_zoc = u.emits_zoc(); SDL_Color hp_color=u.hp_color(); SDL_Color xp_color=u.xp_color(); std::string ellipse=u.image_ellipse(); if ( hidden || is_blindfolded || !u.is_visible_to_team(viewing_team_ref,map, show_everything) ) { ac.clear_haloes(); if(ac.anim_) { ac.anim_->update_last_draw_time(); } return; } if (!ac.anim_) { ac.set_standing(); if (!ac.anim_) return; } if (ac.refreshing_) return; ac.refreshing_ = true; ac.anim_->update_last_draw_time(); frame_parameters params; const t_translation::t_terrain terrain = map.get_terrain(loc); const terrain_type& terrain_info = map.get_terrain_info(terrain); // do not set to 0 so we can distinguish the flying from the "not on submerge terrain" // instead use -1.0 (as in "negative depth", it will be ignored by rendering) params.submerge= is_flying ? -1.0 : terrain_info.unit_submerge(); if (u.invisible(loc) && params.highlight_ratio > 0.5) { params.highlight_ratio = 0.5; } if (loc == sel_hex && params.highlight_ratio == 1.0) { params.highlight_ratio = 1.5; } int height_adjust = static_cast<int>(terrain_info.unit_height_adjust() * zoom_factor); if (is_flying && height_adjust < 0) { height_adjust = 0; } params.y -= height_adjust; params.halo_y -= height_adjust; int red = 0,green = 0,blue = 0,tints = 0; double blend_ratio = 0; // Add future colored states here if(u.poisoned()) { green += 255; blend_ratio += 0.25; tints += 1; } if(u.slowed()) { red += 191; green += 191; blue += 255; blend_ratio += 0.25; tints += 1; } if(tints > 0) { params.blend_with = disp.rgb((red/tints),(green/tints),(blue/tints)); params.blend_ratio = ((blend_ratio/tints)); } //hackish : see unit_frame::merge_parameters // we use image_mod on the primary image // and halo_mod on secondary images and all haloes params.image_mod = u.image_mods(); params.halo_mod = u.TC_image_mods(); params.image= u.default_anim_image(); if(u.incapacitated()) params.image_mod +="~GS()"; params.primary_frame = t_true; const frame_parameters adjusted_params = ac.anim_->get_current_params(params); const map_location dst = loc.get_direction(facing); const int xsrc = disp.get_location_x(loc); const int ysrc = disp.get_location_y(loc); const int xdst = disp.get_location_x(dst); const int ydst = disp.get_location_y(dst); const int x = static_cast<int>(adjusted_params.offset * xdst + (1.0-adjusted_params.offset) * xsrc) + hex_size_by_2; const int y = static_cast<int>(adjusted_params.offset * ydst + (1.0-adjusted_params.offset) * ysrc) + hex_size_by_2; bool has_halo = ac.unit_halo_ && ac.unit_halo_->valid(); if(!has_halo && !u.image_halo().empty()) { ac.unit_halo_ = halo_man.add(0, 0, u.image_halo()+u.TC_image_mods(), map_location(-1, -1)); } if(has_halo && u.image_halo().empty()) { halo_man.remove(ac.unit_halo_); ac.unit_halo_ = halo::handle(); //halo::NO_HALO; } else if(has_halo) { halo_man.set_location(ac.unit_halo_, x, y - height_adjust); } // We draw bars only if wanted, visible on the map view bool draw_bars = ac.draw_bars_ ; if (draw_bars) { SDL_Rect unit_rect = sdl::create_rect(xsrc, ysrc +adjusted_params.y, hex_size, hex_size); draw_bars = sdl::rects_overlap(unit_rect, disp.map_outside_area()); } #ifdef SDL_GPU sdl::timage ellipse_front; sdl::timage ellipse_back; #else surface ellipse_front(nullptr); surface ellipse_back(nullptr); #endif int ellipse_floating = 0; // Always show the ellipse for selected units if(draw_bars && (preferences::show_side_colors() || sel_hex == loc)) { if(adjusted_params.submerge > 0.0) { // The division by 2 seems to have no real meaning, // It just works fine with the current center of ellipse // and prevent a too large adjust if submerge = 1.0 ellipse_floating = static_cast<int>(adjusted_params.submerge * hex_size_by_2); } if(ellipse.empty()){ ellipse="misc/ellipse"; } if(ellipse != "none") { // check if the unit has a ZoC or can recruit const char* const nozoc = emit_zoc ? "" : "nozoc-"; const char* const leader = can_recruit ? "leader-" : ""; const char* const selected = sel_hex == loc ? "selected-" : ""; // Load the ellipse parts recolored to match team color char buf[100]; std::string tc=team::get_side_color_index(side); #ifdef SDL_GPU snprintf(buf,sizeof(buf),"%s-%s%s%stop.png~RC(ellipse_red>%s)",ellipse.c_str(),leader,nozoc,selected,tc.c_str()); ellipse_back = image::get_texture(image::locator(buf), image::SCALED_TO_ZOOM); snprintf(buf,sizeof(buf),"%s-%s%s%sbottom.png~RC(ellipse_red>%s)",ellipse.c_str(),leader,nozoc,selected,tc.c_str()); ellipse_front = image::get_texture(image::locator(buf), image::SCALED_TO_ZOOM); #else snprintf(buf,sizeof(buf),"%s-%s%s%stop.png~RC(ellipse_red>%s)",ellipse.c_str(),leader,nozoc,selected,tc.c_str()); ellipse_back.assign(image::get_image(image::locator(buf), image::SCALED_TO_ZOOM)); snprintf(buf,sizeof(buf),"%s-%s%s%sbottom.png~RC(ellipse_red>%s)",ellipse.c_str(),leader,nozoc,selected,tc.c_str()); ellipse_front.assign(image::get_image(image::locator(buf), image::SCALED_TO_ZOOM)); #endif } } #ifdef SDL_GPU if (!ellipse_back.null()) { //disp.drawing_buffer_add(display::LAYER_UNIT_BG, loc, disp.drawing_buffer_add(display::LAYER_UNIT_FIRST, loc, xsrc, ysrc +adjusted_params.y-ellipse_floating, ellipse_back); } if (!ellipse_front.null()) { //disp.drawing_buffer_add(display::LAYER_UNIT_FG, loc, disp.drawing_buffer_add(display::LAYER_UNIT_FIRST, loc, xsrc, ysrc +adjusted_params.y-ellipse_floating, ellipse_front); } #else if (ellipse_back != nullptr) { //disp.drawing_buffer_add(display::LAYER_UNIT_BG, loc, disp.drawing_buffer_add(display::LAYER_UNIT_FIRST, loc, xsrc, ysrc +adjusted_params.y-ellipse_floating, ellipse_back); } if (ellipse_front != nullptr) { //disp.drawing_buffer_add(display::LAYER_UNIT_FG, loc, disp.drawing_buffer_add(display::LAYER_UNIT_FIRST, loc, xsrc, ysrc +adjusted_params.y-ellipse_floating, ellipse_front); } #endif if(draw_bars) { const image::locator* orb_img = nullptr; const surface unit_img = image::get_image(u.default_anim_image(), image::SCALED_TO_ZOOM); const int xoff = (hex_size - unit_img->w)/2; const int yoff = (hex_size - unit_img->h)/2; /*static*/ const image::locator partmoved_orb(game_config::images::orb + "~RC(magenta>" + preferences::partial_color() + ")" ); /*static*/ const image::locator moved_orb(game_config::images::orb + "~RC(magenta>" + preferences::moved_color() + ")" ); /*static*/ const image::locator ally_orb(game_config::images::orb + "~RC(magenta>" + preferences::allied_color() + ")" ); /*static*/ const image::locator enemy_orb(game_config::images::orb + "~RC(magenta>" + preferences::enemy_color() + ")" ); /*static*/ const image::locator unmoved_orb(game_config::images::orb + "~RC(magenta>" + preferences::unmoved_color() + ")" ); const std::string* energy_file = &game_config::images::energy; if(size_t(side) != viewing_team+1) { if(disp.team_valid() && viewing_team_ref.is_enemy(side)) { if (preferences::show_enemy_orb() && !u.incapacitated()) orb_img = &enemy_orb; else orb_img = nullptr; } else { if (preferences::show_allied_orb()) orb_img = &ally_orb; else orb_img = nullptr; } } else { if (preferences::show_moved_orb()) orb_img = &moved_orb; else orb_img = nullptr; if(playing_team == viewing_team && !u.user_end_turn()) { if (movement_left == total_movement) { if (preferences::show_unmoved_orb()) orb_img = &unmoved_orb; else orb_img = nullptr; } else if ( dc.unit_can_move(u) ) { if (preferences::show_partial_orb()) orb_img = &partmoved_orb; else orb_img = nullptr; } } } if (orb_img != nullptr) { surface orb(image::get_image(*orb_img,image::SCALED_TO_ZOOM)); disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, xsrc + xoff, ysrc + yoff + adjusted_params.y, orb); } double unit_energy = 0.0; if(max_hitpoints > 0) { unit_energy = double(hitpoints)/double(max_hitpoints); } const int bar_shift = static_cast<int>(-5*zoom_factor); const int hp_bar_height = static_cast<int>(max_hitpoints * u.hp_bar_scaling()); const fixed_t bar_alpha = (loc == mouse_hex || loc == sel_hex) ? ftofxp(1.0): ftofxp(0.8); draw_bar(*energy_file, xsrc+xoff+bar_shift, ysrc+yoff+adjusted_params.y, loc, hp_bar_height, unit_energy,hp_color, bar_alpha); if(experience > 0 && can_advance) { const double filled = double(experience)/double(max_experience); const int xp_bar_height = static_cast<int>(max_experience * u.xp_bar_scaling() / std::max<int>(u.level(),1)); draw_bar(*energy_file, xsrc+xoff, ysrc+yoff+adjusted_params.y, loc, xp_bar_height, filled, xp_color, bar_alpha); } if (can_recruit) { surface crown(image::get_image(u.leader_crown(),image::SCALED_TO_ZOOM)); if(!crown.null()) { //if(bar_alpha != ftofxp(1.0)) { // crown = adjust_surface_alpha(crown, bar_alpha); //} disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, xsrc+xoff, ysrc+yoff+adjusted_params.y, crown); } } for(std::vector<std::string>::const_iterator ov = u.overlays().begin(); ov != u.overlays().end(); ++ov) { #ifdef SDL_GPU const sdl::timage ov_img(image::get_texture(*ov, image::SCALED_TO_ZOOM)); if(!ov_img.null()) { disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, xsrc, ysrc +adjusted_params.y, ov_img); } #else const surface ov_img(image::get_image(*ov, image::SCALED_TO_ZOOM)); if(ov_img != nullptr) { disp.drawing_buffer_add(display::LAYER_UNIT_BAR, loc, xsrc+xoff, ysrc+yoff+adjusted_params.y, ov_img); } #endif } } // Smooth unit movements from terrain of different elevation. // Do this separately from above so that the health bar doesn't go up and down. const t_translation::t_terrain terrain_dst = map.get_terrain(dst); const terrain_type& terrain_dst_info = map.get_terrain_info(terrain_dst); int height_adjust_unit = static_cast<int>((terrain_info.unit_height_adjust() * (1.0 - adjusted_params.offset) + terrain_dst_info.unit_height_adjust() * adjusted_params.offset) * zoom_factor); if (is_flying && height_adjust_unit < 0) { height_adjust_unit = 0; } params.y -= height_adjust_unit - height_adjust; params.halo_y -= height_adjust_unit - height_adjust; ac.anim_->redraw(params, halo_man); ac.refreshing_ = false; }
bool game_state::can_recruit_on(const unit& leader, const map_location& recruit_loc) const { return can_recruit_on(leader.get_location(), recruit_loc, leader.side()); }