wesnoth/src/actions.hpp
Rusty Russell 8d19660966 Fix aggression calculations (bug #7432)
1) harm_weight is 1 - aggression, but we were handing aggression
directly in all but one place!  This lead to negative aggression
settings becoming actively masochistic: rewarding self-damage!
Changed battle_context constructor to take aggression and convert
internally, since that is how it was being used.

2) aggression was only taken into account when comparing chance to
kill, not damage.  This means that the bug above only showed up in
cases where there was a chance to kill.  This may re-balance some
scenarios slightly.

I suspect this may have been involved in bug #6095 (AI too uncautions
with its leader).
2006-10-16 01:58:47 +00:00

304 lines
14 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <davidnwhite@verizon.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef ACTIONS_H_INCLUDED
#define ACTIONS_H_INCLUDED
class display;
class gamestatus;
class replay;
struct combatant;
#include "global.hpp"
#include "map.hpp"
#include "unit.hpp"
#include <deque>
#define RECRUIT_POS -2
//this file defines various functions which implement different in-game
//events and commands.
bool can_recruit_on(const gamemap& map, const gamemap::location& leader, const gamemap::location loc);
//recruit_unit: function which recruits a unit into the game. A
//copy of u will be created and inserted as the new recruited unit.
//If need_castle is true, then the new unit must be on the same castle
//as the leader of the team is on the keep of.
//
//If preferred_location is in a valid location, it will be used, otherwise
//a valid location will be arbitrarily chosen. If disp is not NULL, the
//new unit will be faded in.
//
//If the unit cannot be recruited, then a human-readable message
//describing why not will be returned. On success, the return string is empty
std::string recruit_unit(const gamemap& map, int team, unit_map& units,
unit u, gamemap::location& recruit_location,
display *disp=NULL, bool need_castle=true, bool full_movement=false);
/* The battle_context class computes the statistics of a battle between an
* attacker and a defender unit.
*/
class battle_context
{
public:
// Structure describing the statistics of a unit involved in the battle.
struct unit_stats
{
const attack_type *weapon; // The weapon used by the unit to attack the opponent, or NULL if there is none.
int attack_num; // Index into unit->attacks() or -1 for none.
bool is_attacker; // True if the unit is the attacker.
bool is_poisoned; // True if the unit is poisoned at the beginning of the battle.
bool is_slowed; // True if the unit is slowed at the beginning of the battle.
bool slows; // Attack slows opponent when it hits.
bool drains; // Attack drains opponent when it hits.
bool stones; // Attack turns opponent to stone when it hits.
bool plagues; // Attack turns opponent into a zombie when fatal.
bool poisons; // Attack poisons opponent when it hits.
bool backstab_pos; // True if the attacker is in *position* to backstab the defender (this is used to
// determine whether to apply the backstab bonus in case the attacker has backstab).
bool swarm; // Attack has swarm special.
bool firststrike; // Attack has firststrike special.
unsigned int rounds; // Berserk special can force us to fight more than one round.
unsigned int hp; // Hitpoints of the unit at the beginning of the battle.
unsigned int max_hp; // Maximum hitpoints of the unit.
unsigned int chance_to_hit; // Effective chance to hit as a percentage (all factors accounted for).
int damage; // Effective damage of the weapon (all factors accounted for).
int slow_damage; // Effective damage if unit becomes slowed (== damage, if already slowed)
unsigned int num_blows; // Effective number of blows, takes swarm into account.
unsigned int swarm_min; // Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
unsigned int swarm_max; // Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
std::string plague_type; // The plague type used by the attack, if any.
unit_stats(const unit &u, const gamemap::location& u_loc,
int u_attack_num, bool attacking,
const unit &opp, const gamemap::location& opp_loc,
const attack_type *opp_weapon,
const unit_map& units,
const std::vector<team>& teams,
const gamestatus& status, const gamemap& map, const game_data& gamedata);
~unit_stats();
// This method dumps the statistics of a unit on stdout. Remove it eventually.
void dump() const;
};
// If no attacker_weapon is given, we select best one, based on
// harm_weight (1.0 means 1 hp lost counters 1 hp damage, 0.0
// means we ignore harm weight). prev_def is for predicting multiple attacks against a defender.
battle_context(const gamemap& map, const std::vector<team>& teams, const unit_map& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
int attacker_weapon = -1, int defender_weapon = -1, double aggression = 0.0, const combatant *prev_def = NULL);
// Used by the AI which caches unit_stats
battle_context(const unit_stats &att, const unit_stats &def);
battle_context(const battle_context &other);
~battle_context() ;
battle_context& operator=(const battle_context &other);
// This method returns the statistics of the attacker.
const unit_stats& get_attacker_stats() const { return *attacker_stats_; }
// This method returns the statistics of the defender.
const unit_stats& get_defender_stats() const { return *defender_stats_; }
// Get the simulation results.
const combatant &get_attacker_combatant(const combatant *prev_def = NULL);
const combatant &get_defender_combatant(const combatant *prev_def = NULL);
// Given this harm_weight, is this attack betteer than that?
bool better_attack(class battle_context &that, double harm_weight);
private:
bool better_combat(const combatant &us_a, const combatant &them_a,
const combatant &us_b, const combatant &them_b,
double harm_weight);
unsigned int choose_attacker_weapon(const unit &attacker, const unit &defender,
const gamemap& map, const std::vector<team>& teams, const unit_map& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
double harm_weight, int *defender_weapon, const combatant *prev_def);
int choose_defender_weapon(const unit &attacker, const unit &defender, unsigned attacker_weapon,
const gamemap& map, const std::vector<team>& teams, const unit_map& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc, const gamemap::location& defender_loc, const combatant *prev_def);
// Statistics of the units.
unit_stats *attacker_stats_, *defender_stats_;
// Outcome of simulated fight.
combatant *attacker_combatant_, *defender_combatant_;
};
//attack: executes an attack.
void attack(display& gui, const gamemap& map,
std::vector<team>& teams,
gamemap::location attacker,
gamemap::location defender,
int attack_with,
int defend_with,
unit_map& units,
const gamestatus& state,
const game_data& info,
bool update_display = true);
//given the location of a village, will return the 0-based index of the team
//that currently owns it, and -1 if it is unowned.
int village_owner(const gamemap::location& loc, const std::vector<team>& teams);
//makes it so the village at the given location is owned by the given
//0-based team number. Returns true if getting the village triggered a mutating event
bool get_village(const gamemap::location& loc, std::vector<team>& teams,
size_t team_num, const unit_map& units);
bool get_village(const gamemap::location& loc, std::vector<team>& teams,
size_t team_num, const unit_map& units, int *time_bonus);
//given the 1-based side, will find the leader of that side,
//and return an iterator to the leader
unit_map::iterator find_leader(unit_map& units, int side);
unit_map::const_iterator find_leader(const unit_map& units, int side);
// Resets resting for all units on this side: should be called after calculate_healing().
// FIXME: Try moving this to unit::new_turn, then move it above calculate_healing().
void reset_resting(unit_map& units, unsigned int side);
//calculates healing for all units for the given side. Should be called
//at the beginning of a side's turn.
void calculate_healing(display& disp, const gamemap& map,
unit_map& units, unsigned int side,
const std::vector<team>& teams, bool update_display);
//function which, given the location of a unit that is advancing, and the
//name of the unit it is advancing to, will return the advanced version of
//this unit. (with traits and items retained).
unit get_advanced_unit(const game_data& info,
unit_map& units,
const gamemap::location& loc, const std::string& advance_to);
//function which will advance the unit at loc to 'advance_to'.
//note that 'loc' is not a reference, because if it were a reference, we couldn't
//safely pass in a reference to the item in the map that we're going to delete,
//since deletion would invalidate the reference.
void advance_unit(const game_data& info,
unit_map& units,
gamemap::location loc, const std::string& advance_to);
//function which tests if the unit at loc is currently affected
//by leadership. (i.e. has a higher-level 'leadership' unit next to it).
//if it does, then the location of the leader unit will be returned, otherwise
//gamemap::location::null_location will be returned
//if 'bonus' is not NULL, the % bonus will be stored in it
gamemap::location under_leadership(const unit_map& units,
const gamemap::location& loc, int* bonus=NULL);
//checks to see if a side has won, and will throw an end_level_exception
//if one has. Will also remove control of villages from sides with dead leaders
void check_victory(unit_map& units,
std::vector<team>& teams);
//gets the time of day at a certain tile. Certain tiles may have a time of
//day that differs from 'the' time of day, if a unit that illuminates is
//in that tile or adjacent.
time_of_day timeofday_at(const gamestatus& status,
const unit_map& units,
const gamemap::location& loc,
const gamemap& map);
//returns the amount that a unit's damage should be multiplied by due to
//the current time of day.
int combat_modifier(const gamestatus& status,
const unit_map& units,
const gamemap::location& loc,
unit_type::ALIGNMENT alignment,
const gamemap& map);
//structure which records information to be able to undo a movement
struct undo_action {
undo_action(unit u,const std::vector<gamemap::location>& rt,int sm,int timebonus=0,int orig=-1)
: route(rt), starting_moves(sm), original_village_owner(orig), recall_pos(-1), affected_unit(u), countdown_time_bonus(timebonus) {}
undo_action(unit u,const gamemap::location& loc, int pos)
: recall_loc(loc), recall_pos(pos), affected_unit(u), countdown_time_bonus(1) {}
std::vector<gamemap::location> route;
int starting_moves;
int original_village_owner;
gamemap::location recall_loc;
int recall_pos; // set to RECRUIT_POS for an undo-able recruit
unit affected_unit;
int countdown_time_bonus;
bool is_recall() const { return recall_pos >= 0; }
bool is_recruit() const { return recall_pos == RECRUIT_POS; }
};
typedef std::deque<undo_action> undo_list;
//function which moves a unit along the sequence of locations given by
//steps. If the unit cannot make it completely along the path this turn,
//a goto order will be set. If move_recorder is not NULL, the move will
//be recorded in it. If undos is not NULL, undo information will be added.
size_t move_unit(display* disp, const game_data& gamedata,
const gamestatus& status, const gamemap& map,
unit_map& units, std::vector<team>& teams,
std::vector<gamemap::location> steps,
replay* move_recorder, undo_list* undos,
gamemap::location *next_unit = NULL,
bool continue_move = false, bool should_clear_shroud=true);
//function which recalculates the fog
void recalculate_fog(const gamemap& map, const gamestatus& status,
const game_data& gamedata,
unit_map& units, std::vector<team>& teams, int team);
//function which will clear shroud away for the given 0-based team based on
//current unit positions. Returns true if some shroud is actually cleared away.
bool clear_shroud(display& disp, const gamestatus& status,
const gamemap& map, const game_data& gamedata,
unit_map& units, std::vector<team>& teams, int team);
//function to apply pending shroud changes in the undo stack.
//it needs tons of parameters because it calls clear_shroud(...) (see above)
void apply_shroud_changes(undo_list& undos, display* disp, const gamestatus& status, const gamemap& map,
const game_data& gamedata, unit_map& units, std::vector<team>& teams, int team);
//will return true iff the unit at 'loc' has any possible moves it can do
//(including attacking etc).
bool unit_can_move(const gamemap::location& loc, const unit_map& units,
const gamemap& map, const std::vector<team>& teams);
namespace victory_conditions {
void set_victory_when_enemies_defeated(bool on);
bool victory_when_enemies_defeated();
}
//Function to check if an attack will satisfy the requirements for backstab
//given the location from which the attack will occur, the defending unit
//location, the list of units on the map and the list of teams.
//The defender and opposite units should be in place already. The
//attacking unit doesn't need to be, but if it isn't, an external check should
//be made to make sure the opposite unit isn't also the attacker.
bool backstab_check(const gamemap::location& attacker_loc,
const gamemap::location& defender_loc,
const unit_map& units, const std::vector<team>& teams);
#endif